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
+58 -4
View File
@@ -36,8 +36,16 @@
-- DLX_PK_PACE 1 = the machine holds itself to the container's fps off
-- V-DISP (default). 0 free-runs, which tests the CHAIN with
-- the clock out of the way.
-- DLX_PK_ITER passes over the scene (default 1). >1 exercises the seek,
-- which for this container is arithmetic and nothing else.
-- DLX_PK_ITER passes over the scene (default 1). >1 exercises the SEEK.
-- DLX_PK_SEEK the frame passes after the first start at (default 0, i.e.
-- a replay). On the video path the seek IS arithmetic and
-- nothing else; on the audio path it is a second read at a
-- separate LBA, because a DLXP2 group puts lump k in FRONT of
-- its records and a branch lands `f mod F` frames into it
-- (FINDINGS 70.3). Setting this to a frame that is NOT a
-- multiple of the cadence is the point: 36 of the arcade's
-- 409 within-container seek targets land on a boundary and
-- 373 do not.
-- DLX_PK_NFR play only the first N frames (default: all of them)
-- DLX_PK_FPS pace at this rate instead of the container's. NOT a
-- cosmetic knob and not a way to make a number look better:
@@ -76,6 +84,9 @@ local PG_GSPIN, PG_LOSTV, PG_ARRN = 0x18954, 0x18958, 0x1895C
-- ROADMAP P6c: the audio path's inputs and its own account of what it did.
local PG_AFPS, PG_AHZ, PG_ALBA0 = 0x18960, 0x18964, 0x18968
local PG_NLUMP, PG_ABYTES, PG_APRE = 0x1896C, 0x18970, 0x18974
local PG_SEEKF, PG_ARST = 0x18978, 0x1897C
local PG_APOS, PG_ASKIP = 0x189C8, 0x189CC
local PG_ASKN, PG_ASKB, PG_TSEQ = 0x189D0, 0x189D4, 0x189DC
local PG_AARM, PG_AFET, PG_ABYT = 0x18980, 0x18984, 0x18988
local PG_ADRY, PG_ASEAM, PG_ASRV = 0x1898C, 0x18990, 0x18994
local PG_ACSR, PG_ACER, PG_ALATE = 0x18998, 0x1899C, 0x189A0
@@ -90,6 +101,11 @@ local CRTC_R20 = 0xE80028
local HELD = (os.getenv("DLX_PK_HELD") or "1") == "1"
local PACED = (os.getenv("DLX_PK_PACE") or "1") == "1"
local ITERS = tonumber(os.getenv("DLX_PK_ITER") or "") or 1
local SEEKF = tonumber(os.getenv("DLX_PK_SEEK") or "") or 0
-- ROADMAP P6d. 1 = STOP and re-PLAY the chip at a branch. Not a tidiness
-- knob: the chip's accumulator has no leak, so the two settings are a large
-- decaying error against a small permanent one (FINDINGS 71.3).
local ARST = (os.getenv("DLX_PK_ARST") or "0") == "1"
local NFR = tonumber(os.getenv("DLX_PK_NFR") or "") or META.nframes
local FPS = tonumber(os.getenv("DLX_PK_FPS") or "") or META.fps
local CSV = os.getenv("DLX_PK_CSV")
@@ -123,6 +139,8 @@ local function setup()
SP:write_u32(PG_HELD, HELD and 1 or 0)
SP:write_u32(PG_PACEON, PACED and 1 or 0)
SP:write_u32(PG_ITER, ITERS)
SP:write_u32(PG_SEEKF, SEEKF)
SP:write_u32(PG_ARST, ARST and 1 or 0)
-- DLXP2's cadence. Zero for a silent container, and the 68000 branches on the
-- zero: a player told the wrong cadence does not fail, it reads an audio lump
-- as a record and paints it.
@@ -139,6 +157,23 @@ local function setup()
SP:write_u32(PG_NLUMP, META.n_lumps or 0)
SP:write_u32(PG_ABYTES, META.aud_bytes or 0)
SP:write_u32(PG_APRE, APRE)
if ITERS > 1 then
local grp = (META.cad_f or 0) > 0 and (SEEKF % META.cad_f) or 0
P(string.format("SEEK: %d passes, and passes 2..%d start at FRAME %d%s",
ITERS, ITERS, SEEKF,
(META.cad_f or 0) == 0 and " (silent container)"
or string.format(" -- lump %d, %d frame(s) into its group "
.."of %d, so the audio needs a second read and a byte "
.."offset (FINDINGS 70.3)%s",
SEEKF // META.cad_f, grp, META.cad_f,
grp == 0 and " -- ON a group boundary, the free case"
or "")))
P(ARST and " the chip is STOPPED and re-PLAYED at the branch: its "
.."accumulator goes to the container's own init and its step "
.."index to 0"
or " the chip PLAYS THROUGH the branch: it keeps the predictor "
.."state the previous scene's audio left it in")
end
P(string.format("packed.bin=%d B, %dx%d %d fps, %d of %d frames, %d passes",
#code, META.W, META.H, META.fps, NFR, META.nframes, ITERS))
local cad = ""
@@ -338,6 +373,20 @@ SUB = emu.add_machine_frame_notifier(function()
P(string.format(" re-arms with MTC still non-zero: %d (bytes "
.."fetched and never played; 0 is the correct value)",
late))
-- THE SEEK'S OWN ACCOUNT. PG_ASKN is the second reads and PG_ASKB the
-- bytes they skipped at the head of a lump: a seek path with no offset
-- term would report the first and zero for the second, and would be
-- indistinguishable from a correct one on any target that happened to
-- land on a group boundary.
local skn, skb = SP:read_u32(PG_ASKN), SP:read_u32(PG_ASKB)
if skn > 0 then
P(string.format(" SEEK: %d audio seek(s), %d B skipped into "
.."the head of a lump. Stream position ended at %d B "
.."and the chip was handed %d -- they differ BY the "
.."skip, which is the whole reason they are two cells "
.."(FINDINGS 71).", skn, skb, SP:read_u32(PG_APOS),
byt))
end
local ferr = SP:read_u32(PG_AFERR)
if ferr ~= 0 then
P(string.format(" A LUMP FETCH FAILED on lump %d: %s -- the "
@@ -366,10 +415,15 @@ SUB = emu.add_machine_frame_notifier(function()
local f = io.open(AJSON, "w")
f:write(string.format('{"armed":%d,"fetched":%d,"bytes":%d,'
..'"starve":%d,"seam":%d,"serv":%d,"late":%d,"acc":%d,'
..'"csr":%d,"cer":%d,"held":%s,"fps":%d,"shown":%d}\n',
..'"csr":%d,"cer":%d,"held":%s,"fps":%d,"shown":%d,'
..'"seekn":%d,"seekb":%d,"pos":%d,"seekf":%d,"iters":%d,'
..'"arst":%s}\n',
armed, fet, byt, dry, seam, srv, late, acc,
SP:read_u32(PG_ACSR), SP:read_u32(PG_ACER),
HELD and "true" or "false", FPS, shown))
HELD and "true" or "false", FPS, shown,
SP:read_u32(PG_ASKN), SP:read_u32(PG_ASKB),
SP:read_u32(PG_APOS), SEEKF, ITERS,
ARST and "true" or "false"))
f:close()
P("audio counters -> "..AJSON)
end