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.
|
||||
|
||||
+46
-8
@@ -115,6 +115,24 @@ loudest passage on the disc is unmeasured. **(2) The transport is P6b's, not
|
||||
scaffolding**, and it worked first time. **P6b is next and its bytes are
|
||||
decided: DLXP2 encodes with `adpcm.CHIP`.**
|
||||
|
||||
Amended end of session 35: **P6b IS DONE AND ITS PADDING WAS DRIFT (FINDINGS
|
||||
67).** DLXP2 exists: a 64-byte header, groups of one `A`-sector audio lump then
|
||||
`F` records, `record i = off_frm + i*rec + (i//F)*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 it in **six instructions
|
||||
once a frame**, and 120 of 120 records are still pixel-exact off a real volume
|
||||
with the interleave in, against a **silent control** that says no picture byte
|
||||
moved. **65.3's 0.09% was not waste, it was a rate error**: a lump is 7,168 B of
|
||||
space and eleven frames of audio is 7,161.4583… B, so a player that fed the chip
|
||||
the whole lump would run 0.09% fast — **1.25 s of lip-sync over the game's 22.8
|
||||
minutes**. The payload is a remainder, `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 and the gate
|
||||
flips each one to prove they earn it (**order −31.99 dB, formula −24.86**). The
|
||||
wire is **589.6 KB/s**, 65.3's figure to the tenth. **What is left of P6 is the
|
||||
last quarter: no audio has been played out of this container on any layer, and
|
||||
two DMA channels have never run at once.**
|
||||
|
||||
**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
|
||||
@@ -747,10 +765,28 @@ throughout (65.5). The gap is the register semantics, and the way to close it is
|
||||
machine that is known-correct because Sharp wrote it. That is also the real
|
||||
design, so it is not scaffolding.
|
||||
|
||||
**P6b. A CONTAINER WITH SOUND IN IT.** DLXP1 has no audio section. 65.3 is the
|
||||
arithmetic a DLXP2 is built from and no byte of one is written. It waits on P6a,
|
||||
because a container full of audio encoded against the wrong formula is 25 dB of
|
||||
work to redo.
|
||||
**P6b. A CONTAINER WITH SOUND IN IT. DONE, session 35 — FINDINGS 67.**
|
||||
~~DLXP1 has no audio section. 65.3 is the arithmetic a DLXP2 is built from and no
|
||||
byte of one is written.~~ DLXP2 is written, gated and carried by the player:
|
||||
`tools/encoder/dlxp.py`, `pack.py --audio`, `tools/analysis/34_packed_audio.py`,
|
||||
the third LBA term in `src/player/packed.s`. The cadence arithmetic came through
|
||||
unchanged (F=11, A=14, 589.6 KB/s) and the **payload** did not: a lump's audio is
|
||||
7,161 or 7,162 B of a 7,168 B sector run, and feeding the chip the whole lump
|
||||
drifts 1.25 s over the game.
|
||||
|
||||
**P6c. AUDIO OUT OF THE CONTAINER, ON THE MACHINE (new, session 35).** Every
|
||||
piece exists and none is joined up. The container carries the bytes (67); the
|
||||
transport is `src/player/adpcm.i`'s channel-3 configuration, which is the IPL
|
||||
ROM's own and worked first time (66.1). What is missing is **the lump buffer**
|
||||
— 14,336 B, double-buffered, allocated by nobody — and **the remainder
|
||||
accumulator**, which is three instructions and is not optional.
|
||||
|
||||
**And the interaction neither half has met: TWO CHANNELS AT ONCE.** The video
|
||||
channel holds the bus and halts the 68000, which already costs the frame clock
|
||||
half its ticks without the clock being able to tell (64.3). An audio channel
|
||||
that must be serviced *during* that hold has never been run. 52's 1.25%..1.48%
|
||||
of a frame is a figure measured in isolation, and `32_audio_wire.py` names what
|
||||
it turns into here: audio does not merely cost clocks, it costs **darkness**.
|
||||
|
||||
**E6. Container v2** — audio interleave, per-record index, scene table. Depends
|
||||
on P6's answer and on P5's index.
|
||||
@@ -878,10 +914,12 @@ B2 blanking ─┬─ NOT blanked ─> K3's DMAC-DIRECT player is the one: 54.9%
|
||||
picks a different player.
|
||||
B1 BURST rate (NEW, 64.2) ──> which of the two K3/K4 wins, if B2 blanks
|
||||
|
||||
P6 audio DONE bar one (65): encoder gated, cadence F=11/A=14, 589.6 KB/s
|
||||
└─> P6a WHICH DELTA FORMULA? ─┬─ needs no board: MAME has :okim6258
|
||||
worth 25 dB, so it is a │ at $E92001/$E92003 (65.5)
|
||||
PRECONDITION not a tweak └─> then P6b, a container with sound in it
|
||||
P6 audio: encoder gated (65), P6a the chip's own decoder measured (66),
|
||||
P6b DLXP2 written and gated, 589.6 KB/s on the wire (67)
|
||||
└─> P6c AUDIO OUT OF THE CONTAINER ─┬─ the bytes exist, the transport
|
||||
needs no board: the transport │ exists, the lump buffer does not
|
||||
is the IPL ROM's own (66.1) └─> TWO CHANNELS AT ONCE, which is
|
||||
the one thing nothing has run
|
||||
```
|
||||
|
||||
**Read that top-left branch as the project's live question.** Everything else
|
||||
|
||||
+112
-1
@@ -1,4 +1,115 @@
|
||||
# Status & next-session handoff — end of session 34 (2026-08-25)
|
||||
# Status & next-session handoff — end of session 35 (2026-08-25)
|
||||
|
||||
## Session 35: the packed container gets sound, and the padding is drift
|
||||
|
||||
**Green light first and last: `./tools/bench/check.sh` was ALL GREEN before any
|
||||
of this (`tmp/check_s35_start.log`) and ALL GREEN after
|
||||
(`tmp/check_s35_end.log`)** — the same stages, plus one new one.
|
||||
|
||||
**FINDINGS 67. ROADMAP P6b is DONE.** `tools/encoder/dlxp.py` (DLXP2),
|
||||
`tools/encoder/pack.py --audio`, `tools/analysis/34_packed_audio.py`,
|
||||
`src/player/packed.s`, `tools/bench/prep_packed.py`, `tools/bench/packed.lua`,
|
||||
`tools/analysis/30_packed_container.py`, `tools/bench/check.sh`.
|
||||
|
||||
**NAME THE LAYER.** The container is **host arithmetic**. What ran on the
|
||||
emulated 68000 is its *video* consequence: `packed.s` now carries the third LBA
|
||||
term and fetches 120 records out of an interleaved container off a real MB89352
|
||||
volume, all 120 pixel-exact. **No audio byte has been played out of this
|
||||
container on any layer.**
|
||||
|
||||
**1. THE FORMAT.** 64-byte header; from sector 1, groups of one `A`-sector audio
|
||||
lump then `F` records. `record i = off_frm + i*rec + (i//F)*A*512`,
|
||||
`lump k = off_aud + k*(F*rec + A*512)`. **Still no index and still none needed**,
|
||||
which was the packed branch's whole claim and is the thing a second stream could
|
||||
have ended. The lump goes **before** the group it feeds; 65.3's formula put it
|
||||
after, and a stream is read forwards. On the 68000 it costs **six instructions
|
||||
once a frame** and zero parsing. The four ADPCM axes ride in the header as
|
||||
fields, not a version number.
|
||||
|
||||
**2. THE HEADLINE, AND 65.3 CALLED IT THE WRONG THING.** A lump is 7,168 B of
|
||||
*space*; eleven frames of audio is **7,161.4583… B**, so the payload alternates
|
||||
**7,161 / 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` — the same shape as `clock.i` and for
|
||||
the same reason. **Third time this tree has met it** (54, 65.3, now).
|
||||
|
||||
**3. THE GATE, AND ITS THREE NEGATIVE CONTROLS.** Nothing parses a packed
|
||||
container, so a lump one sector out does not fail, it paints. `34_packed_audio.py`
|
||||
therefore (a) **partitions the whole file** — 131 spans, no overlap, no gap,
|
||||
ending on the last byte — because a per-record read cannot see an off-by-one
|
||||
that shifts everything after it; (b) asserts a **cadence-blind player** gets
|
||||
exactly records 11..119 wrong, **109 of 120, and frames 0..10 identical either
|
||||
way**, which is how such a bug survives a rig that checks frame 0; (c) **flips
|
||||
each of the four ADPCM axes one at a time** — nibble order **−31.99 dB**, delta
|
||||
formula **−24.86 dB**, clamp 0.00, accumulator −0.49 — so the header fields are
|
||||
load-bearing rather than documentation. And the picture is gated against a
|
||||
**silent control** built from the same frames: all 120 records byte-identical.
|
||||
|
||||
**4. THE WIRE IS THE PREDICTION.** 582.0 + 7.64 = **589.6 KB/s**, which is
|
||||
65.3's figure to the tenth, because none of 66's four axes changes a byte count.
|
||||
|
||||
**RISKS IN THIS SESSION'S RESULT:**
|
||||
|
||||
- **Nothing has been heard.** The container has audio in it; no transport has
|
||||
fed it to a chip, on any layer.
|
||||
- **The lump buffer is not allocated anywhere.** 65.3 charges 14,336 B for
|
||||
double-buffering the cadence and no player holds it.
|
||||
- **Two channels have never run at once.** The audio DMA is 1.25%..1.48% of a
|
||||
frame *in isolation* (52); the video channel holds the bus (64.2/64.3).
|
||||
- **The level is still open downward** (66.3) and the loudest passage on the
|
||||
disc is still unmeasured.
|
||||
|
||||
## HANDOFF — start here
|
||||
|
||||
**THE TREE IS ALL GREEN**, session 35's stage included.
|
||||
|
||||
### The work, in the order it should be done
|
||||
|
||||
**1. THE LAST QUARTER OF P6 — audio out of the container, on the machine.**
|
||||
Everything it needs now exists and none of it has been joined up: the container
|
||||
carries the bytes (67), the transport is `adpcm.i`'s channel-3 configuration
|
||||
which worked first time (66.1), and the missing piece is **the lump buffer and
|
||||
the remainder accumulator in a player**. Do the accumulator as 67.2 states it or
|
||||
the run drifts 1.25 s over a game.
|
||||
|
||||
**2. AND THEN THE THING NEITHER HALF HAS MET: TWO CHANNELS AT ONCE.** The video
|
||||
channel holds the bus and halts the 68000 (64.3 — it costs the frame clock half
|
||||
its ticks *without the clock being able to tell*). An audio channel that has to
|
||||
be serviced during that hold has never been run. This is the interaction 52
|
||||
could not have had, and `32_audio_wire.py` names it: audio does not merely cost
|
||||
clocks, it costs **darkness**.
|
||||
|
||||
**3. THE REFILL CLIMB WITH A SECOND CONSUMER** through a real branch point
|
||||
(51.3, 55.4). The slack table is in `32_audio_wire.py`; nothing has been run.
|
||||
|
||||
**4. THE AUDIO LEVEL, which 66.3 reopened.** Measure the loudest passage on the
|
||||
disc before choosing a level.
|
||||
|
||||
### 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 — and the acceptance figure is now **589.6 KB/s**,
|
||||
not 582.0), 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.
|
||||
|
||||
### Reproducing this session
|
||||
|
||||
./tools/bench/check.sh # ALL GREEN
|
||||
python3 tools/encoder/pack.py tmp/fr_singe tmp/packed_singe.dlxp \
|
||||
--nframes 120 --audio tmp/au_singe.raw
|
||||
python3 tools/analysis/34_packed_audio.py # the DLXP2 gate on its own
|
||||
|
||||
**WHAT IS NEXT.** P6's last quarter: the container's own audio, out of a
|
||||
channel, beside the video channel that holds the bus.
|
||||
|
||||
---
|
||||
|
||||
## Session 34: the chip is asked, and the encoder was wrong on four axes
|
||||
|
||||
|
||||
Reference in New Issue
Block a user