Measure the level off the whole disc, and find the headroom is not worth buying

FINDINGS 69, ROADMAP P6 -- the item 66.3 reopened in session 34 and sessions 35
and 36 both deferred.  The chip clamps its accumulator at 10 bits INSIDE the
recursion, and the ten seconds every audio figure in this tree is quoted on peak
at 435 of 511: it fits, and it fits by accident, because that window is a
-13.4 dBFS passage.  Nothing knew what the loudest passage of the game was.

tools/analysis/35_audio_level.py reads every stream of the unique scene footage
(00000-00201) through extract_audio.py's own chain -- 1,291.6 s, 201 of 202
streams -- and encodes windows of it with adpcm.CHIP.  The disc peaks at 946 of
2048 = -6.71 dBFS (00200 @ 2.11 s), which is 5.35 dB over the clamp, and the
census behind that peak is 687 samples of 20,182,000 (0.0034%) in 402 events,
44.0 ms, longest 0.90 ms.

THE HEADLINE IS A NEGATIVE: THE LEVEL DOES NOT CHANGE.  Forty 2 s windows drawn
over the game's timeline at six gains -- the disc's own level (gain 1.0) has the
best mean SNR (22.03 dB) and the best median, and loses the worst-window column
to -3 dB by 0.04 dB.  The gain that guarantees zero clamping disc-wide (0.5402)
costs 0.85 dB of mean SNR across the whole game to buy back 1.90 dB on the
2.11 s that clamp, because the OKI step table's floor is a constant 16 and does
not scale with the signal.

AND 66.3's MECHANISM DOES NOT SURVIVE A CONTROL.  Error after a clamp run is
elevated ~5x -- and so is the same window at a gain that never clamps, read at
the same indices, because those samples are simply loud.  Worst ratio 1.28 over
64 offsets, and the clamped encode's whole-window mean |error| is the LOWER of
the two (4.71 vs 5.05).  adpcm.encode runs the chip's clamp inside its own
sixteen-way search, so it never loses the chip's state.  The worry was right
about the mechanism and aimed one layer too late: an encoder clamping at 12 bits
while the chip clamps at 10 is exactly that divergence, and 66 closed it.

pack.py gains --audio-gain (default 1.0) so the level is a named parameter with
a measurement behind it instead of a shift buried in a list comprehension, and
prints the encoded window's peak against the clamp.  tmp/packed_singe.dlxp
rebuilds byte-identical, all 6,039,040 B.  New check.sh stage, ~18 s.

Three rig facts in 69.4, because a shipping encoder meets all three: 00176 has
no audio track at all; 00199 is 61.31 s of video with 1.25 s of audio; and 18
stream pairs share duration, peak and RMS, 7 of them byte-identical.

The 10-bit clamp is a DRIVER SETTING, not a chip constant -- x68k.cpp:1089 sets
OUTPUT_10BITS -- so it is MAME's reading of the board, and hardware item 5 is
what settles it.

Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
This commit is contained in:
prosolis
2026-08-25 14:20:55 -07:00
parent 191f2b47bb
commit ab15c0749a
7 changed files with 885 additions and 5 deletions
+161
View File
@@ -7368,3 +7368,164 @@ not of the format. On silicon the chip latches a byte and consumes both nibbles.
* **The silicon is untouched.** All four decoder axes are still MAME's, and
session 34's fifth hardware item — play a known nibble stream on a real
MSM6258V and record the line out — is still the cheap way to settle them.
## 69. The audio level is measured off the whole disc — and the headroom is not worth buying (session 37)
**ROADMAP P6, the item 66.3 reopened and two sessions deferred.**
`tools/analysis/35_audio_level.py` (new), `tools/encoder/pack.py`
(`--audio-gain`), `tools/bench/check.sh`.
**NAME THE LAYER.** This is **host arithmetic over the source disc**, encoded
with `adpcm.CHIP` — the four axes session 34 measured off MAME's MSM6258. No
68000 ran, no chip was fed. What it settles is what the *encoder* should hand
the chip; what it cannot settle is whether MAME's chip is the chip, which is
still hardware item 5.
66.3 left this open in as many words: the MSM6258's accumulator clamps at **10
bits inside the recursion**, the ten seconds every audio figure in this tree is
quoted on peaks at **435 of 511**, and that window is a 13.4 dBFS passage — so
it fits with 1.4 dB to spare and it fits *by accident*. The level could not be
chosen from it. It had to be chosen against the loudest thing the game can play.
### 69.1 The disc, all of it
Every stream of the unique scene footage — `00000`-`00201`, FINDINGS 32.1 —
through `extract_audio.py`'s own chain (AC-3 5.1, ffmpeg's default downmix
matrix, mono, 15,625 Hz), then `s16 >> 4`, which is exactly what `pack.py`
hands the encoder.
| | |
|---|---:|
| streams with an audio track | **201 of 202** |
| audio | **1,291.6 s = 21.5 min** |
| **DISC PEAK** | **946 of 2048 = 6.71 dBFS** (`00200` @ 2.11 s) |
| the chip's clamp, same units | **511 = 12.06 dBFS** |
| so the disc is over the clamp by | **5.35 dB** |
| samples above it | **687 of 20,182,000 = 0.0034%** |
| ...in | **402 events, 44.0 ms**, longest run **14 samples = 0.90 ms**, median run **1** |
**So the answer to "does the disc clip the chip" is yes, and the number behind
the yes is 44 milliseconds in 21.5 minutes.** A peak alone could not have said
that: 687 samples as one sustained 44 ms burst and 687 samples as 402
sub-millisecond events are the same census row and completely different sounds,
which is why the tool reports the run lengths and not just the count.
### 69.2 THE HEADLINE. Every attenuation that buys the headroom costs more than the clamping does
Forty 2-second windows drawn over the game's own timeline — weighted by stream
length, so a 24 s stream gets twenty times the draws of a 1.2 s one — encoded at
every gain with `adpcm.CHIP`, reported against the *scaled* source, which is the
honest comparison because the listener's amplifier is not this project's problem:
| gain | dB | mean SNR | median | worst window | windows that clamp |
|---:|---:|---:|---:|---:|---:|
| **1.0000** | **0.00** | **22.03** | **21.55** | 16.83 | 5 of 40 (20 samples) |
| 0.7071 | 3.01 | 21.63 | 21.46 | **16.87** | 0 |
| 0.5402 | 5.35 | 21.18 | 20.74 | 15.26 | 0 |
| 0.5000 | 6.02 | 20.95 | 20.60 | 13.91 | 0 |
| 0.3536 | 9.03 | 20.13 | 19.99 | 11.48 | 0 |
| 0.2500 | 12.04 | 18.81 | 19.12 | 8.23 | 0 |
**The disc's own level wins every column but one, and it loses that one by 0.04
dB.** 0.5402 is not a round number and is not meant to be: it is 511/946, the
gain at which the loudest sample on the disc lands exactly on the clamp with
nothing to spare, and buying that guarantee costs **0.85 dB of mean SNR across
the whole game**.
The mechanism is the OKI step table. Its floor is a constant **16 in 12-bit
units** and does not scale with the signal, so attenuation moves the programme
down toward a quantiser that stays where it is. The three named passages show
both ends of it:
| passage | gain 1.0 | 3.01 dB | 5.35 dB | 12.04 dB |
|---|---:|---:|---:|---:|
| the disc peak itself (`00200`, 2.11 s, 133 samples clamp) | 17.94 | 19.59 | **19.84** | 19.07 |
| the loudest sustained window (`00037`) | **24.67** | 24.64 | 24.55 | 21.89 |
| a quiet passage (`00001`) | **20.98** | 20.44 | 19.78 | 15.05 |
**Clamping costs the worst 2 seconds on the disc 1.90 dB. Avoiding it costs the
other 21.5 minutes 0.85.** That is the trade, and it is not close.
**USER-FACING DECISION, and it is a NEGATIVE: the level does not change.**
`pack.py` still hands the encoder `s16 >> 4`, and the container the gate builds
is byte-identical under the new code — which is checked rather than asserted:
`tmp/packed_singe.dlxp` was rebuilt and `cmp`-ed against the one session 36 ran
off a volume, all 6,039,040 bytes. What is new is
that the shift is now a named parameter, `--audio-gain`, with a measurement
behind its default instead of an accident, and `pack.py` prints the encoded
window's peak against the clamp and the count of samples above it. On the gate
window that line reads **435 of 511, 0 clamped**, which is 66.3 reproduced from
inside the encoder.
### 69.3 And 66.3's actual worry — that a clamp OUTLIVES its sample — does not survive a control
66.3's reasoning was exactly right for a recursive codec and is the reason this
was not settled by a peak meter: a clamped accumulator is a **wrong state**, the
next nibble is applied to it, so the damage should persist after the loud sample
has gone. Measure the error after a clamp run and it *is* elevated — about 5x
the off-clamp mean for at least 64 samples.
That is not evidence. The samples after a clamp run are **loud** samples, where
the step index is high and the error is large whether or not anything clamped.
So the control is the same window at the gain that never clamps, rescaled into
the same units and read at the **same sample indices**:
| after a clamp run ends | +0 | +1 | +2 | +8 | +32 | +63 |
|---|---:|---:|---:|---:|---:|---:|
| gain 1.0 (133 samples clamp) | 25.05 | 35.05 | 34.34 | 22.19 | 22.27 | 20.36 |
| control at 0.5402, rescaled | 27.02 | 32.13 | 29.89 | 21.37 | 19.41 | 21.33 |
| ratio | 0.93 | 1.09 | 1.15 | 1.04 | **1.15** | 0.95 |
**Worst ratio over 64 offsets: 1.28. Whole-window mean |error|: 4.71 clamped
against 5.05 for the control** — the clamped encode is the *better* one overall,
which is 69.2 arriving from the other direction.
The reason is that **the encoder's in-loop decoder models the clamp exactly**
(`adpcm.encode` clamps inside its own sixteen-way search, `adpcm.CHIP["bits"]`
= 10). It always knows the state the chip is in, including a saturated one, and
re-chooses the next nibble from there. A clamp costs the samples it happens on
and does not propagate.
**This is a property of a CHIP-MATCHED encoder and not of the format**, and it
is the sharpest argument yet for session 34's work: an encoder that clamped at
12 bits while the chip clamped at 10 — which is what this tree shipped until
session 34 — would have had the encoder and the chip in *different states*, and
that is the divergence 66.3 described. The worry was correct about the mechanism
and was aimed one layer too late.
### 69.4 Three rig facts the scan turned up, recorded because a shipping encoder meets all three
* **`00176` has NO AUDIO TRACK.** 3.0 s of mpeg2video, no stream to decode.
ffmpeg exits non-zero on it, so an encoder that shells out per scene has to
tell "this clip is silent" apart from "the extract failed", and emit silence
of the right length rather than a short lump.
* **`00199` is 61.31 s of video with 1.25 s of audio**, which is 60 s of the
75 s gap between the disc's 1,366.6 s of footage and its 1,291.6 s of audio.
The remaining ~15 s is spread over 200 streams as sub-half-second differences
— AC-3 frame alignment, not missing content. **A cadence-driven container
computes its lump count from the FRAME count**, so a stream whose audio is
shorter than its video is a case with a right answer (pad) and a wrong one
(run out mid-scene), and nothing has been built that makes that choice yet.
* **18 stream pairs share duration, peak and RMS to three decimals, and 7 of
those pairs are byte-identical audio** (25.0 s, every one of them a 3.5 s
clip — the reused death animations). It is a redundancy, it is small, and no
claim is made here about the *video* of those pairs; Dragon's Lair reuses
footage mirrored, which would leave the audio identical and the picture not.
### 69.5 What this does NOT settle
* **SNR is not a listener.** Every number above is a power ratio. 402 clamp
events of a millisecond each are 0.85 dB cheaper than avoiding them *by that
measure*; whether they are audible as a tick on a 6.7 dBFS transient is a
question this project has no instrument for and has never claimed one.
* **The 10-bit clamp is a DRIVER SETTING, not a chip constant.** MAME's
`okim6258` takes the width as a parameter and `x68k.cpp:1089` sets
`OUTPUT_10BITS` for this machine specifically. If a real MSM6258V on a real
X68000 runs 12-bit, the disc clears the clamp by 6.7 dB, the whole of 69.1 is
moot and the answer — do not attenuate — is unchanged and merely cheaper.
**The measurement that settles it is still hardware item 5.**
* **The level was measured, not the loudness.** No perceptual weighting, no
gate, no LUFS: the statistic is peak and RMS of the same samples the encoder
sees, which is the right unit for a clamp question and the wrong one for a
mixing question.
* **Nothing was listened to.** Unchanged since 68.6.
+22
View File
@@ -150,6 +150,28 @@ sound as well as a picture. 67.2's drift is measured rather than derived —
predicted 1.25 s, played **1.26 s**. **What is left of P6 is the level (66.3)
and the refill climb with a second consumer through a real branch point.**
Amended end of session 37: **THE AUDIO LEVEL IS MEASURED, AND THE ANSWER IS
THAT IT DOES NOT CHANGE (FINDINGS 69).** Every stream of the game's own footage
(`00000`-`00201`) through `extract_audio.py`'s chain says the disc peaks at
**946 of 2048 = 6.71 dBFS**, which is **5.35 dB over** the chip's 10-bit clamp
— and the census behind that peak is **687 samples of 20.2 million, 402 events,
44.0 ms in 21.5 minutes**. Forty windows drawn over the game and encoded at six
gains then price the choice: the disc's own level has the best mean SNR
(**22.03 dB**) and the gain that guarantees zero clamping costs **0.85 dB across
the whole game** to buy back **1.90 dB on the 2.11 s that clamp**, because the
OKI step floor is a constant 16 and does not scale with the signal. **And
66.3's mechanism does not survive a control**: error after a clamp run is
elevated ~5x, and so is the same window at a gain that never clamps, read at the
same indices — worst ratio **1.28** — because `adpcm.encode` runs the chip's
clamp inside its own search and therefore never loses the chip's state. The
worry was right about the mechanism and aimed one layer too late; session 34
had already closed it. `--audio-gain` exists so the level is a parameter with a
measurement behind it, its default is 1.0, and the gate container is
byte-identical. **What is left of P6 is the refill climb with a second consumer
through a real branch point** — and 69.4 adds two small unbuilt cases, a scene
with no audio track (`00176`) and a scene whose audio is shorter than its video
(`00199`).
**THE COMPLETION TARGET IS M3, THE VERTICAL SLICE** (USER DECISION): one scene
tree — a decision point, two outcomes, a death clip — with audio, streaming from
a real SCSI volume on a stock 2 MB machine, playable. That is the point at which
+113
View File
@@ -1,3 +1,116 @@
# Status & next-session handoff — end of session 37 (2026-08-25)
## Session 37: the audio level is measured off the whole disc, and the headroom is not worth buying
**FINDINGS 69.** ROADMAP P6, the item 66.3 reopened in session 34 and sessions
35 and 36 both deferred. It needed no new machinery on the machine and it did
need the whole disc: `tools/analysis/35_audio_level.py` reads every stream of
the unique scene footage (`00000`-`00201`, FINDINGS 32.1) through
`extract_audio.py`'s own chain and encodes windows of it with `adpcm.CHIP`.
**What was open.** The chip clamps its accumulator at 10 bits INSIDE the
recursion, so anything the encoder is handed above 511 (12-bit units) is
unreachable. The ten seconds this tree gates everything on peak at 435 of 511 —
it fits, and it fits by accident, because that window is 13.4 dBFS. Nothing
knew what the loudest passage of the game was.
**What it measures.**
| | |
|---|---:|
| audio on the game's own footage | 1,291.6 s = 21.5 min, 201 of 202 streams |
| **disc peak** | **946 of 2048 = 6.71 dBFS** (`00200` @ 2.11 s) |
| the clamp | 511 = 12.06 dBFS — the disc is **5.35 dB over** |
| samples above it | 687 of 20,182,000 = **0.0034%** |
| ...in | 402 events, **44.0 ms**, longest 0.90 ms, median run 1 |
**THE HEADLINE, and it is a negative: the level does not change.** Forty 2 s
windows drawn over the game's timeline, encoded at six gains: the disc's own
level (`s16 >> 4`, gain 1.0) has the best mean SNR (**22.03 dB**) and the best
median, and loses the worst-window column to 3 dB by **0.04 dB**. The gain that
guarantees zero clamping disc-wide (0.5402 = 511/946) costs **0.85 dB of mean
SNR across the whole game** to buy back **1.90 dB on the 2.11 s that clamp**.
The OKI step table's floor is a constant 16 and does not scale with the signal,
so attenuating walks the programme down toward a quantiser that stays put.
**And 66.3's mechanism does not survive a control.** The worry was that a
clamped accumulator is a wrong state the next nibble is applied to. Error after
a clamp run IS elevated ~5x — and so is the same window at a gain that never
clamps, read at the same indices, because those samples are simply LOUD. Worst
ratio against the control over 64 offsets: **1.28**, and the clamped encode's
whole-window mean |error| is **lower** (4.71 vs 5.05). The reason is that
`adpcm.encode` runs the chip's clamp inside its own sixteen-way search, so it
always knows the state the chip is in, saturated included. **The worry was
correct about the mechanism and aimed one layer too late**: an encoder clamping
at 12 bits while the chip clamps at 10 — what this tree shipped until session
34 — is exactly the divergence described, and 66 already closed it.
**What changed in the tree.** `--audio-gain` on `pack.py` (default 1.0), which
makes the level a named parameter with a measurement behind it instead of a
shift buried in a list comprehension; `pack.py` now prints the encoded window's
peak against the clamp and the count above it (gate window: **435 of 511, 0
clamped**). `tmp/packed_singe.dlxp` was rebuilt and is **byte-identical** to the
one session 36 ran off a volume, all 6,039,040 B. A new check.sh stage (~18 s)
gates the disc peak and the clamp census.
**Three rig facts recorded in 69.4**, because a shipping encoder meets all
three: `00176` has **no audio track at all** (ffmpeg exits non-zero — "silent
clip" has to be distinguishable from "extract failed"); `00199` is 61.31 s of
video with 1.25 s of audio, which is 60 s of the 75 s gap between the disc's
1,366.6 s of footage and its 1,291.6 s of audio, and a cadence-driven container
computes its lump count from the FRAME count; and 18 stream pairs share
duration, peak and RMS, of which 7 are byte-identical audio (25.0 s, all of them
3.5 s clips — the reused death animations).
## HANDOFF — start here
**THE TREE IS ALL GREEN**, session 37's stage included.
### The work, in the order it should be done
**1. THE REFILL CLIMB WITH A SECOND CONSUMER, through a real branch point**
(51.3, 55.4). Now the oldest open item in P6 and unchanged by this session: the
slack table is in `32_audio_wire.py`, nothing has been run, and session 36 gave
it the player that holds two streams at once. Pointing it at a scene change is
the work; nothing has to be built.
**2. THE BUFFERING DEPTH, which is cheap.** `PG_ANBUF` is 3 and `PG_APRE` is a
mailbox. Two slots is one constant and one run, and it is 7,168 B of a machine
that has 99,328 B of record buffer to find elsewhere (68.6).
**3. THE SILENT-CLIP AND SHORT-AUDIO CASES (69.4).** Neither is hard and
neither has an answer in the tree: a scene whose audio is shorter than its
frames, and a scene with no audio track. Both are decided by what the lump
count is computed from, and both are cheap to get wrong quietly, which is this
format's whole failure mode (67.4).
### What is still BLOCKED, so it is not picked up by mistake
**K4 — the packed player that is on screen — is conditional on B2**, a board
question. **E7, E4 and C1** are parked (61.8), and **P4a's wiring** is parked
with the ring K3 deleted.
**The hardware list is unchanged and is the user's**: B1 (sustained AND the
data-phase BURST rate, 64.2 — acceptance is **589.6 KB/s**), B2 (blanking), B3
(`#EXREQ`), B4 (a byte write to a palette register), and session 34's fifth,
play a known nibble stream on a real MSM6258V and record the line out. **Session
37 sharpens the fifth**: the 10-bit clamp this whole session is about is a
DRIVER SETTING — `x68k.cpp:1089` calls `set_outbits(OUTPUT_10BITS)` — not a chip
constant, so it is MAME's reading of the board. If real silicon runs 12-bit the
disc clears the clamp by 6.7 dB and the answer here is unchanged and cheaper.
### Reproducing this session
python3 tools/analysis/35_audio_level.py --gate # ~18 s
python3 tools/analysis/35_audio_level.py --survey 40 --ladder --recover
# ~7 min: the ladder, the disc-wide survey and the clamp-recovery control
./tools/bench/check.sh # ALL GREEN
**WHAT IS NEXT.** The refill climb with the second consumer, through a real
branch point.
---
# Status & next-session handoff — end of session 36 (2026-08-25)
## Session 36: the container is played, and holding the bus costs the audio