Put sound in the packed container, and find the padding is a rate error
ROADMAP P6b, FINDINGS 67. DLXP2: a 64-byte header and then groups -- one audio lump of A sectors, then F records -- so record i is at off_frm + i*rec + (i//F)*A*512 and lump k at off_aud + k*(F*rec + A*512). Still no index and still none needed, which is the packed branch's whole claim surviving the one change that could have ended it. The player carries the third term in six instructions once a frame and zero parsing, and 120 of 120 records are still pixel-exact off a real MB89352 volume with the interleave in, against a silent control that says no picture byte moved. The finding is what 65.3 called padding. A lump is 7,168 B of SPACE; eleven frames of audio is 7,161.4583... B, so the payload alternates 7,161 and 7,162 and the rest is zero. A player that fed the chip the whole lump -- which is what "14 sectors every 11 frames" invites -- runs 0.09% fast, and that is not waste, it is drift: 0.84 ms a group, 1.25 s of lip-sync over the game's 22.8 minutes. What a player carries is one accumulator, acc += 11*15625; n = acc//24; acc %= 24, which is clock.i's shape for clock.i's reason and the third time this tree has met the pattern. The four ADPCM axes ride in the header as fields rather than a version number, and the gate flips each one to prove they earn it: nibble order -31.99 dB, delta formula -24.86, clamp 0.00, accumulator -0.49. Nothing parses a packed container, so the gate partitions the whole file -- 131 spans, no overlap, no gap -- and asserts what a cadence-blind player would read: exactly records 11..119 wrong, and frames 0..10 identical either way, which is how an off-by-one like that survives a rig that checks frame 0. Wire 582.0 + 7.64 = 589.6 KB/s, 65.3's prediction to the tenth. Green light ALL GREEN before (tmp/check_s35_start.log) and after (tmp/check_s35_end.log), with the new stage in it. Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
This commit is contained in:
@@ -7075,3 +7075,147 @@ write*.
|
||||
`adpcm.CHIP`, and 65.3's cadence arithmetic (F=11, A=14, wire 582.0 → 589.6
|
||||
KB/s) is untouched by any of this — it is a byte count, and none of the four
|
||||
axes changes how many bytes a second the format needs.
|
||||
|
||||
---
|
||||
|
||||
## 67. DLXP2 — a packed container with sound in it, and the padding turns out to be drift
|
||||
|
||||
**ROADMAP P6b. Session 35.** `tools/encoder/dlxp.py` (DLXP2),
|
||||
`tools/encoder/pack.py --audio`, `tools/analysis/34_packed_audio.py`,
|
||||
`src/player/packed.s` (the third LBA term), `tools/bench/prep_packed.py`,
|
||||
`tools/bench/packed.lua`.
|
||||
|
||||
**NAME THE LAYER.** The container is **host arithmetic**, gated against itself
|
||||
and against a silent control. The one thing that ran on the emulated 68000 is
|
||||
the *video* consequence: `packed.s` fetches records out of an interleaved
|
||||
container and all 120 are still pixel-exact off a real volume. **No audio byte
|
||||
has been fed to a chip out of this container**, on any layer — the chip gate
|
||||
(66) feeds a designed stream, not this one.
|
||||
|
||||
### 67.1 The format, and what it costs the player: one `divu` and one `mulu`
|
||||
|
||||
A DLXP2 is a DLXP1 with a 64-byte header and, from sector 1, **groups**: one
|
||||
audio lump of `A` sectors, then `F` records.
|
||||
|
||||
record i = off_frm + i*rec_bytes + (i//F)*A*512
|
||||
lump k = off_aud + k*(F*rec_bytes + A*512)
|
||||
|
||||
Neither is a lookup. **The format still has no index and still needs none** —
|
||||
which was 63/64.1's whole claim for the packed branch, and a second stream at an
|
||||
unrelated rate is exactly the thing that could have ended it. The lump goes
|
||||
**before** the group it feeds rather than after it (65.3's formula put it after):
|
||||
a stream is read forwards, so bytes that arrive after the slot they belong to
|
||||
are bytes a player had to fetch early anyway.
|
||||
|
||||
On the 68000 the third term is **six instructions in `pg_frame`** — a `divu`, an
|
||||
`andi` to drop the remainder `divu` leaves in the high half, a `mulu` and an
|
||||
`add` — and **zero instructions of parsing**, because the cadence is two numbers
|
||||
in the header rather than a table in the stream.
|
||||
|
||||
**Measured, on the emulated machine, off a real MB89352 volume: 120 of 120
|
||||
frames pixel-exact** out of the interleaved container, every one compared. The
|
||||
interleave moved no picture byte — asserted against a **silent control** built
|
||||
from the same frames with `--audio` off, all 120 records byte-identical.
|
||||
|
||||
### 67.2 The finding: the payload is not the lump
|
||||
|
||||
65.3 chose the cadence F=11, A=14 and called the 0.09% "padding". It is padding
|
||||
on the wire. **It is drift in the player**, and that is a different thing.
|
||||
|
||||
A lump is `A*512` = **7,168 B of space**. Eleven frames of audio is
|
||||
`11*15625/24` = **7,161.4583… B**. So the *payload* alternates **7,161 and
|
||||
7,162** — the same remainder FINDINGS 54's frame clock carries, one dimension
|
||||
over — and the last 6.54 B of the sector run are zero.
|
||||
|
||||
**A player that fed the chip the whole lump** — the obvious implementation, and
|
||||
the one the phrase "14 sectors of audio every 11 frames" invites — hands it
|
||||
6.54 B a group it should not have. That is **0.84 ms of extra audio every
|
||||
0.9167 s**, and it does not average out:
|
||||
|
||||
| play | lip-sync error |
|
||||
|---|---:|
|
||||
| 1 min | 0.05 s |
|
||||
| **22.8 min (the game)** | **1.25 s** |
|
||||
|
||||
A second and a quarter is a scene of dialogue arriving after the mouth that
|
||||
spoke it. So `lump_bytes(k)` is **part of the format**, not a convenience, and
|
||||
what a player carries is the same shape `clock.i` carries and for the same
|
||||
reason — a rate whose denominator is 24 cannot be a count:
|
||||
|
||||
acc += 11*15625 ; = 171,875
|
||||
n = acc // 24 ; the MTC for this lump's channel
|
||||
acc %= 24
|
||||
|
||||
**The general shape, and it is the third time this tree has hit it**: 54's frame
|
||||
clock, 65.3's `.0417` B a slot, and now this. A ratio with a remainder that is
|
||||
rounded *once* is a rounding error; rounded *every period* it is a rate error,
|
||||
and a rate error integrates.
|
||||
|
||||
### 67.3 The four ADPCM axes are in the header, and the gate proves they earn it
|
||||
|
||||
66 measured which decoder MAME's chip runs and priced getting it wrong at up to
|
||||
−25.74 dB. DLXP2 carries all four — nibble order, delta formula, clamp width,
|
||||
the accumulator at PLAY — as **three header fields rather than a version
|
||||
number**, so a mismatch is legible in a hexdump instead of inferred from a
|
||||
container's age.
|
||||
|
||||
`34_packed_audio.py` decodes the container's own lumps and **flips one axis at a
|
||||
time**, which is the negative control that makes the fields load-bearing rather
|
||||
than documentation:
|
||||
|
||||
| axis | header | flipped to | SNR | cost |
|
||||
|---|---|---|---:|---:|
|
||||
| — | | | **21.99 dB** | |
|
||||
| nibble order | low | high | −10.00 dB | **−31.99 dB** |
|
||||
| delta formula | terms | shift | −2.87 dB | **−24.86 dB** |
|
||||
| clamp | 10 | 12 | 21.99 dB | +0.00 dB |
|
||||
| accumulator at PLAY | −2 | 0 | 21.50 dB | −0.49 dB |
|
||||
|
||||
These are **decode-side** flips on bytes that were encoded correctly, where
|
||||
66.2's table encoded and decoded on the wrong model together; the two agree on
|
||||
which axes are expensive and disagree by a few dB on how expensive, which is
|
||||
what different experiments on the same fact look like. The clamp is still free
|
||||
**on this window only** and for 66.3's reason: it peaks at 435 of 511.
|
||||
|
||||
### 67.4 The failure mode this format has and the codec's does not
|
||||
|
||||
A DLX record is found through an index, so a player that reads the wrong entry
|
||||
gets a length word that does not parse. **A packed record is found by
|
||||
arithmetic and nothing parses it.** A player that drops the `(i//F)*A` term
|
||||
reads 97 sectors starting 14 sectors early and *paints them*: the tail of the
|
||||
previous record, then most of this one, shifted down the screen. It is a
|
||||
picture. Nothing errors.
|
||||
|
||||
And it is invisible where a gate usually looks: **frames 0..10 are byte-identical
|
||||
either way**. The gate asserts the whole shape — that a cadence-blind read is
|
||||
wrong for exactly records `F..n-1`, 109 of 120, first at frame 11 — rather than
|
||||
that some frame differs.
|
||||
|
||||
The same argument is why the gate **partitions the file** instead of reading
|
||||
records back one at a time: an off-by-one that shifts everything after it reads
|
||||
back fine record by record. 131 spans, no overlap, no gap, ending exactly at the
|
||||
file's last byte.
|
||||
|
||||
### 67.5 The wire, unchanged from the prediction
|
||||
|
||||
video 582.0 KB/s geometry, no lever
|
||||
audio 7.64 KB/s the cadence's, padding included
|
||||
total 589.6 KB/s +1.31%
|
||||
|
||||
**65.3 predicted 589.6 and the container is 589.6** — which is what a byte count
|
||||
should do, since none of 66's four axes changes how many bytes a second the
|
||||
format needs. The audio figure charges the padding on purpose: the disc moves
|
||||
whole sectors and the wire pays for the zero ones.
|
||||
|
||||
### 67.6 What is still open
|
||||
|
||||
* **No audio has been played out of this container**, on any layer. The chip
|
||||
gate (66) fed a designed nibble stream through the IPL ROM's channel-3
|
||||
configuration; wiring *this* stream to *that* transport, and running it beside
|
||||
the video channel, is P6's remaining quarter.
|
||||
* **The second consumer has not met a branch point.** 51.3's refill climb with
|
||||
audio on the wire is arithmetic in `32_audio_wire.py` and has not been run.
|
||||
* **The level is still open downward** (66.3), and the loudest passage on the
|
||||
disc is still unmeasured.
|
||||
* **The lump buffer is not allocated anywhere.** 65.3 charges 14,336 B for
|
||||
double-buffering the cadence and no player holds it.
|
||||
|
||||
Reference in New Issue
Block a user