Put the container on the chip, and find the held bus costs 463 times the seam

ROADMAP P6c, FINDINGS 68. 78,125 B of a DLXP2's audio out of channel 3,
sample-exact, while the video channel fetches records off the same disc.

The two pieces 67.6 said were missing: the lump buffer (pg_afill/pg_afetch,
three slots and the minimum is unmeasured) and 67.2's remainder accumulator
(pg_apay). The capture prices what the accumulator avoided at 1.26 s of
lip-sync over the game, against 67.2's predicted 1.25.

The finding is the third piece, which nothing had named: the MSM6258 has no
starvation state, so the gap between a channel counting out and the next arm
is a held nibble pair driving the predictor. Stealing, the seam is 0.51 ms
over ten seconds because dma.i's new DM_HOOK services the chip from inside
the transfer wait -- 250,000 of 250,240 looks. Held, the 68000 is halted and
gets 369: every one of the ten lump boundaries has a seam, worst 72.8 ms,
2.31% of the audio. Identical bytes, different sound. 64.3 reaching the audio.

Two bugs, and no counter in the player could see either. Clearing DM_BARV does
not unchain a channel -- OCR bits 3-2 are what it obeys -- and the symptom is
POLL TIMEOUT on the lump and every record after it. And the refill ran one lump
ahead of its ring and overwrote the buffer the channel was reading: 11 of 11
armed, 11 fetched, no starve, and the sound wrong from 0.2 s in. Which is why
the gate is a WAV: verify_packed_audio.py walks the stream one delivered byte
at a time, because MAME's okim6258 resets the nibble select on every write and
a byte is two nibbles only 99.994% of the time.

check.sh ALL GREEN before (tmp/check_s36_start.log) and after
(tmp/check_s36_end.log), with the new stage.

Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
This commit is contained in:
prosolis
2026-08-25 13:11:37 -07:00
parent e3778f62b0
commit 191f2b47bb
13 changed files with 1413 additions and 20 deletions
+74 -1
View File
@@ -70,7 +70,7 @@ run() {
( cd tmp && env SDL_VIDEODRIVER=dummy "$@" stdbuf -oL timeout -k 5 900 \
mame x68000 -bios ipl10 -exp1 cz6bs1 \
-rompath "$HOME/mame/roms;./p4roms" -hard dlxpdisk.chd \
-ramsize 2M -video soft -window -sound none -nothrottle -plugins \
-ramsize 2M -video soft -window $SOUNDARGS -nothrottle -plugins \
-autoboot_script ../tools/bench/packed.lua \
-snapshot_directory "./snap_packed_$tag" -snapview native \
-seconds_to_run "$SECS" > "packed_$tag.log" 2>&1 )
@@ -80,6 +80,11 @@ run() {
grep -a "^\[PK\]" "tmp/packed_$tag.log" | sed 's/^\[PK\] / /'
}
fail() { echo "FAIL: $1"; exit 1; }
# -sound none for every run that is not about sound, which is all of them until
# run 5. 15,625 is not a preference there: it is the chip's own stream rate
# (8 MHz / 512), and equal rates are what keep MAME's resampler from filtering
# the thing being measured (FINDINGS 66, adpcm_run.sh).
SOUNDARGS="-sound none"
echo "--- 1. THE GATE: $NFR records, paced at $GATE_FPS fps, channel stealing ---"
SECS=$(( NFR / GATE_FPS + 25 ))
@@ -120,6 +125,39 @@ grep -aq "0 frames NOT SAMPLED" tmp/packed_gate.log || \
python3 tools/bench/verify_packed.py "$DLXP" --snap tmp/snap_packed_gate \
--map tmp/packed_snaps_gate.csv --min-frames "$NFR"
# ---------------------------------------------------------------------------
# 5. THE AUDIO. ROADMAP P6c: the container's own bytes, out of channel 3,
# beside the video channel. Paced at the CONTAINER's rate rather than the
# gate's half rate, because the audio was cut at 12 fps and a 6 fps run would
# starve the chip for half of every group -- the picture can be slowed down and
# a crystal cannot. Nothing is snapshotted; the instrument is the WAV, and
# tools/bench/verify_packed_audio.py accounts for every byte of the stream in
# it. The tag is `aud` and not `audio` because tmp/packed_audio.log is
# tools/analysis/34_packed_audio.py's, in check.sh.
#
# CYCLE STEALING, and that is a result rather than a setting -- run 6 below is
# the same run with the bus held and it is a CONTRAST, not a gate.
AUDIO_ON=$(sed -n 's/.*has_audio = \([01]\),.*/\1/p' tmp/packed_meta.lua)
CFPS=$(sed -n 's/^ fps = \([0-9]*\),.*/\1/p' tmp/packed_meta.lua)
if [ "$AUDIO_ON" = "1" ]; then
echo
echo "--- 5. THE AUDIO: the container's own lumps, out of channel 3, while"
echo " the video channel is on the same bus (ROADMAP P6c) ---"
SECS=$(( NFR / 8 + 30 ))
SOUNDARGS="-samplerate 15625 -wavwrite packed_aud.wav"
run aud DLX_PK_HELD=0 DLX_PK_PACE=1 DLX_PK_FPS="$CFPS" DLX_PK_NFR=$NFR \
DLX_PK_AUD=1 DLX_PK_AJSON=packed_aud.json
SOUNDARGS="-sound none"
grep -aq "^\[PK\] FLAG=\$FF" tmp/packed_aud.log || \
fail "the audio run did not reach the end of the scene."
python3 tools/bench/verify_packed_audio.py "$DLXP" tmp/packed_aud.wav \
tmp/packed_aud.json || \
fail "the chip did not play the container. The counters above can all be
right while this fails -- nothing parses a packed container, so a lump
fetched into a buffer that is still being read is not an error, it is a
sound (FINDINGS 67.4, and it is the bug session 36 shipped and caught)."
fi
if [ "${DLX_PK_GATE_ONLY:-0}" = "1" ]; then exit 0; fi
echo
@@ -150,6 +188,41 @@ LOST=$(sed -n 's/.*-> \([0-9]*\) edges LOST.*/\1/p' tmp/packed_held_paced.log |
rasters -- and the comparison with the stealing run below is then a
comparison of two configurations that do the same thing."
LOSTS=$(sed -n 's/.*-> \([0-9]*\) edges LOST.*/\1/p' tmp/packed_gate.log | head -1)
# ---------------------------------------------------------------------------
# 6. THE SAME AUDIO WITH THE BUS HELD, and this is the interaction ROADMAP P6c
# said neither half had met. A burst channel HALTS the 68000 for the whole
# 88 ms record, so the audio service cannot run during it -- src/player/dma.i's
# DM_HOOK is never reached and the chip is looked at twice a frame instead of
# two thousand times. The bytes are the same bytes either way; what changes is
# WHEN the next lump is armed, and the chip has no starvation state: what it
# does in between is replay the byte pair it is holding.
#
# NOT A GATE. Both configurations play the container byte for byte and the
# verifier passes on both; the difference is entirely in the seams, and a seam
# is a design cost rather than a correctness one.
if [ "$AUDIO_ON" = "1" ]; then
echo
echo "--- 6. THE AUDIO AGAIN, WITH THE BUS HELD (the contrast, not a gate) ---"
SECS=$(( NFR / 6 + 30 ))
SOUNDARGS="-samplerate 15625 -wavwrite packed_aud_held.wav"
run aud_held DLX_PK_HELD=1 DLX_PK_PACE=1 DLX_PK_FPS="$CFPS" DLX_PK_NFR=$NFR \
DLX_PK_AUD=1 DLX_PK_AJSON=packed_aud_held.json
SOUNDARGS="-sound none"
python3 tools/bench/verify_packed_audio.py "$DLXP" tmp/packed_aud_held.wav \
tmp/packed_aud_held.json || fail "the held run did not play the
container. The bytes are not what holding the bus was expected to cost."
echo
echo " THE SEAM, STEALING AGAINST HELD -- audio does not merely cost clocks:"
for t in aud aud_held; do
printf ' %-9s ' "$t"
python3 tools/bench/verify_packed_audio.py "$DLXP" "tmp/packed_$t.wav" \
| sed -n 's/^ worst \(.*\)$/\1/p' | head -1
done
echo " Stealing, the 68000 sees the channel from inside dma.i's transfer"
echo " wait. Held, it is halted for the whole record and cannot look at all."
fi
echo
echo " V-DISP edges lost: $LOST held at 12 fps, $LOSTS stealing at $GATE_FPS fps."
echo " A player keeps a clock, reads a stick and feeds ADPCM. Which of the two"