Take the player through a branch with sound, and find the predictor does not seek

FINDINGS 71, ROADMAP P6d.  70.3 named exactly what was missing -- packed.s starts
PG_AK/PG_AKF at lump 0 and has no audio seek path -- and priced its absence at a
mean 416.5 ms of silence over the arcade's 409 within-container seek targets.
pg_aseek is that path: the lump index, the stream position, the remainder
accumulator and the byte offset into the group, then the second READ(10) at the
lump's own LBA and a re-arm part way into the buffer.  Measured off a real volume:
132,162 B of spliced stream accounted for byte by byte in MAME's own capture,
across a branch at frame 37 -- four frames into lump 3, deliberately NOT on a
group boundary -- in both chip configurations.  Skip computed 2,604 B, cadence
says 2,604.

THE PREDICTOR DOES NOT SEEK, AND THE ERROR IS DC.  The MSM6258's accumulator is a
pure integrator with no leakage term, so a branch that hands the chip bytes chosen
for a state it is not in produces an offset that does not decay.  Playing through:
DC -355 of 511 with AC 0.00 -- the right shape from the wrong ground -- still -108
four seconds later.  STOP and re-PLAY: all 62,500 post-seek samples are EXACTLY a
decode from the container's own init, and the whole error is the single constant
-65.  A re-PLAY is 5.5x better and neither is zero, so PG_ARST is a mailbox with a
number under it.  The host computes -65 out of the container's bytes and the gate
asserts the equality rather than printing both.

AND THE ONLY FIX THAT REACHES ZERO IS THE ENCODER'S.  A player cannot set the
chip's accumulator, only reset it.  Resetting the encoder's predictor every frame
makes all 119 of the container's branch points exact for 0.33 dB (21.99 -> 21.66),
because the step table's floor is a constant 16.  That is a DLXP3 and it is
deliberately not in tools/encoder.

TWO SILENT BUGS, BOTH CAUGHT BY THE CAPTURE.  pg_udiv32 trashes d4 and pg_aseek
held hz there, so the offset came out 1 byte instead of 2,604 -- 166 ms of the
wrong part of the scene at exactly the right rate, every counter agreeing.  And
one already in the tree that had passed this gate three times: pg_ainit waited on
a READ-BACK MTC before PLAY, which is the same test as "a byte has left RAM" only
if no byte leaves in between.  One does, and the chip then plays the scene one
byte in, forever.  Found by locating the capture's opening samples in the
container image: sector 1 + 1.  The witness is now the count that was written.

ALL GREEN, two new stages included.

Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
This commit is contained in:
prosolis
2026-08-25 15:10:20 -07:00
parent 8b5f51704c
commit ba966efe7e
11 changed files with 1400 additions and 35 deletions
+36
View File
@@ -510,6 +510,35 @@ group to enter off-boundary and gives back **12,288 B of RAM for +4.36 KB/s of
wire**. The deciding number is what one more SCSI command costs, and that is
hardware's (FINDINGS 70).
**The player seeks with sound on it now — and the chip's predictor does not seek
with it.** `pg_aseek` rebuilds the four numbers a branch needs — the lump index,
the stream position, the remainder accumulator and the byte offset into the
group — and issues the second read: **132,162 B of spliced stream accounted for
byte by byte in MAME's own capture**, across a branch at frame 37 that is four
frames into lump 3 and so deliberately *not* on a group boundary. 70.3's silence
is gone, and what is left is smaller and is not a player problem. **The
MSM6258's accumulator is a pure integrator with no leakage term**, so a branch
that hands the chip bytes chosen for a state it is not in produces **a DC offset
that does not decay**: playing through, **355 of 511 with AC 0.00** — the right
shape from the wrong ground — and still **108 four seconds later**. Stop and
re-PLAY the chip and all 62,500 post-seek samples are *exactly* a decode from the
container's own start state, with the whole error a **single permanent constant,
65**. A re-PLAY is **5.5x better and neither is zero**.
**The only fix that reaches zero is the encoder's, and it costs 0.33 dB.** A
player cannot set the chip's accumulator, only reset it — so a branch is free
only if the stream was *encoded* with the predictor reset where the branch
lands. Resetting every frame makes all 119 of the container's branch points
exact for **21.99 → 21.66 dB**, because the OKI step table's floor is a constant
16 and the recursion re-converges in a few samples. Across the container's frame
boundaries the offset a re-PLAY costs averages **55.4 of 511 (10.8%)** and
reaches **313**, and the host's arithmetic for the machine's own branch point
agrees with the capture **to the digit**. The seek also turned up a race that
had passed this gate three times: `pg_ainit` waited on a *read-back* MTC before
telling the chip to PLAY, which can start the scene **one byte in, forever**,
with every counter in the player exact — found by locating the capture's opening
samples in the container image (FINDINGS 71).
**Nothing outside-derived is committed here.** The scene graph is not
redistributable from this tree; it is regenerated from a reader's own clones
into gitignored `tmp/`, and `tools/import/scenegraph.py` is the single file in
@@ -698,6 +727,13 @@ tools/analysis/ measurement scripts, numbered in the order they were written.
costs. It reads only DLXSCENE1 and both containers' own
headers, and its one design assumption -- one container per
SCENE -- is printed in its own output.
37 prices what a BRANCH costs the chip, which is a codec
property rather than a player one: the accumulator has no leak,
so a re-PLAY at a branch is an exact permanent DC offset and
playing through is a larger one that decays only by clamping.
It gives the census over every frame boundary, checks its own
arithmetic for the machine's branch point against what the
capture measured, and prices the encoder-side fix.
buscost.py is the shared bus-cycle table. The per-block
constants live in tools/encoder/vq_hybrid.py and are imported,
never copied.