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:
+122
@@ -1,3 +1,125 @@
|
||||
# Status & next-session handoff — end of session 39 (2026-08-25)
|
||||
|
||||
## Session 39: the player seeks with sound on it, and the chip's predictor does not seek with it
|
||||
|
||||
**FINDINGS 71.** ROADMAP P6d, and it is the item session 38's handoff put first.
|
||||
70.3 named exactly what was missing — "`src/player/packed.s` starts
|
||||
`PG_AK`/`PG_AKF` at lump 0 and has no audio seek path at all" — 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: it rebuilds the lump
|
||||
index, the stream position, the remainder accumulator and the byte offset into
|
||||
the group, issues the **second READ(10)** at the lump's own LBA, and re-arms
|
||||
channel 3 part way into the buffer.
|
||||
|
||||
**Measured on the machine, off a real volume**: two passes over the container,
|
||||
the second starting at **frame 37** — four frames into lump 3, chosen *not* to
|
||||
be a multiple of F, because a target on a group boundary needs no offset and 36
|
||||
of the arcade's 409 land on one. **132,162 B of spliced stream accounted for
|
||||
byte by byte in MAME's own capture**, in both chip configurations, and the skip
|
||||
the 68000 computed is **2,604 B** against the cadence's 2,604.
|
||||
|
||||
**THE HEADLINE, and it is a property of the codec rather than of the player: the
|
||||
predictor does not seek.** 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** — not a transient with a
|
||||
time constant. Playing *through* the branch: DC **−355** with **AC 0.00** (the
|
||||
right shape from the wrong ground), still **−108** four seconds later. Stopping
|
||||
and re-PLAYing the chip: 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**, 12.7% of the 10-bit clamp's headroom, permanent. **A re-PLAY is 5.5x
|
||||
better and neither is zero**, so `PG_ARST` is a mailbox with a number under it
|
||||
rather than a preference.
|
||||
|
||||
**And the host agrees with the machine to the digit.**
|
||||
`tools/analysis/37_audio_seek.py` computes `init − acc(24,088)` out of the
|
||||
container's own bytes and gets **−65**; the capture measured −65. The gate
|
||||
asserts the equality. Its census over all 119 frame boundaries: mean **55.4 of
|
||||
511 (10.8%)**, median 41, p90 126, worst **313 (61.3%)**.
|
||||
|
||||
**THE FIX IS THE ENCODER'S AND IT IS CHEAP.** A player cannot set the chip's
|
||||
accumulator, only reset it — so the way to make a branch free is to encode with
|
||||
the predictor reset where a branch can land, and re-PLAY there. **Resetting
|
||||
every frame makes all 119 branch points exact for 0.33 dB** (21.99 → 21.66),
|
||||
because the step table's floor is a constant 16 and the recursion re-converges
|
||||
in a few samples. It is a container change (a DLXP3) and it is deliberately
|
||||
**not** in `tools/encoder`.
|
||||
|
||||
**TWO SILENT BUGS, BOTH CAUGHT BY THE CAPTURE AND NOT BY A COUNTER.** Mine:
|
||||
`pg_udiv32` trashes `d4` and `pg_aseek` was holding `hz` in it, 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 that was already in
|
||||
the tree and had passed this gate three times**: `pg_ainit` waited for a
|
||||
*read-back* MTC to change before PLAY, which is the same test as "a byte has
|
||||
left RAM" only if no byte leaves between the channel start and the read. One
|
||||
does whenever `#DRQ3`'s phase falls that way, 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*.
|
||||
It is a race — 386 bytes of new code moved the phase — and what makes it a bug
|
||||
rather than bad luck is that the witness was a read.
|
||||
|
||||
**What changed in the tree.** `src/player/packed.s` 3,954 → 4,340 B (`pg_aseek`,
|
||||
`pg_udiv32`, `PG_SEEKF`, `PG_ARST`, `PG_APOS`, `PG_ASKIP`, `PG_AMTCA`, and the
|
||||
pace gate now counts TICKS rather than frame indices, because after a seek they
|
||||
are different numbers); `ad_abort` in `adpcm.i`; run 7 of `packed_run.sh` (both
|
||||
chip configurations, ~20 s); `verify_packed_audio.py` grew `--seek`, the spliced
|
||||
expected stream, and a walk that SPLITS at a chip reset; one new analysis file
|
||||
and two new check.sh stages.
|
||||
|
||||
## HANDOFF — start here
|
||||
|
||||
**THE TREE IS ALL GREEN**, session 39's two stages included.
|
||||
|
||||
### The work, in the order it should be done
|
||||
|
||||
**1. THE PREDICTOR-RESET CONTAINER (71.4), and it is now the cheapest thing that
|
||||
removes a whole class of error.** Reset the encoder's predictor at every frame
|
||||
(0.33 dB) or at the frames the scene graph names as targets (free at those and
|
||||
unchanged elsewhere), carry the choice in the DLXP header the way the four ADPCM
|
||||
axes are carried (67.3), and have `pg_aseek` re-PLAY. The player half already
|
||||
exists and is `PG_ARST`. This is the only path to a branch that costs the audio
|
||||
nothing, and it is entirely inside this project.
|
||||
|
||||
**2. THE BUFFERING DEPTH, still cheap and now three sessions old.** `PG_ANBUF`
|
||||
is 3 and `PG_APRE` is a mailbox. Two slots is one constant and one run. 70.2's
|
||||
reason to care is unchanged: the audio buffer is the packed branch's *only*
|
||||
buffer.
|
||||
|
||||
**3. THE SILENT-CLIP AND SHORT-AUDIO CASES (69.4).** Unchanged: a scene whose
|
||||
audio is shorter than its frames (`00199`), and a scene with no audio track at
|
||||
all (`00176`). Both are decided by what the lump count is computed from.
|
||||
|
||||
**4. THE CADENCE PICK (70.4) is answerable now and was not before.** With a
|
||||
working seek path the silence F=11 costs is zero, so the trade goes back to
|
||||
padding against RAM — plus one SCSI command per branch, which is B1's.
|
||||
|
||||
### 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 the user's** and session 39 adds nothing to it but does
|
||||
sharpen the fifth. B1 (sustained AND data-phase BURST rate, plus session 38's
|
||||
sixth: what one extra SCSI command costs), 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. **Session 39 gives that fifth two more questions and
|
||||
both are small**: does a real MSM6258V reset its accumulator, step index and
|
||||
nibble select on PLAY *only when it was not already playing*, the way MAME's
|
||||
model does; and does it go on asserting `#DRQ` while STOPped. The second decides
|
||||
whether `pg_aseek`'s re-PLAY wait terminates on silicon; the bounded count is
|
||||
what turns a `no` into a report rather than a hang.
|
||||
|
||||
### Reproducing this session
|
||||
|
||||
bash tools/bench/packed_run.sh # run 7 is the seek, ~20 s
|
||||
python3 tools/analysis/37_audio_seek.py # the host half, ~30 s
|
||||
./tools/bench/check.sh # ALL GREEN
|
||||
|
||||
**WHAT IS NEXT.** The predictor-reset container — the first thing in a while
|
||||
that makes a measured cost go to zero rather than down.
|
||||
|
||||
---
|
||||
|
||||
# Status & next-session handoff — end of session 38 (2026-08-25)
|
||||
|
||||
## Session 38: the climb meets a branch point with sound on it, and the cadence has a third column
|
||||
|
||||
Reference in New Issue
Block a user