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
68 lines
3.3 KiB
Python
68 lines
3.3 KiB
Python
#!/usr/bin/env python3
|
|
"""Everything tools/bench/packed.lua needs to know about a DLXP container.
|
|
|
|
python3 tools/bench/prep_packed.py <in.dlxp> -> tmp/packed_meta.lua
|
|
|
|
THERE IS NO BLOB TO PREPARE, and that is the whole difference from
|
|
`prep_stream.py`. The codec's rig has to hand the machine expanded codebooks, a
|
|
packed palette and a record index, because a DLX record cannot be found or drawn
|
|
without them; `prep_dlx.py` and `prep_stream.py` exist for that and FINDINGS
|
|
49.7.5 records what it cost to have two copies of one of those transforms. A
|
|
packed container carries no such thing: record `i` is at sector 1 + i*97 by
|
|
geometry and its bytes are already in the order GVRAM wants them (dlxp.py). So
|
|
this file emits METADATA ONLY -- six numbers the rig would otherwise have to
|
|
hard-code, every one of them read out of the container's own header.
|
|
|
|
The volume is the container itself; tools/bench/mkvol.sh copies it.
|
|
"""
|
|
import os, sys
|
|
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
|
"..", "encoder"))
|
|
from dlxp import DLXP, SECTOR
|
|
|
|
if len(sys.argv) != 2:
|
|
sys.exit(__doc__)
|
|
d = DLXP(sys.argv[1])
|
|
|
|
# The record's sector count, and the array chain's entry count, DERIVED here and
|
|
# asserted by the 68000 (PG_ARRN). Two independent statements of one geometry
|
|
# is the only way a container and a player can be caught disagreeing about it --
|
|
# a chain one entry short delivers a picture with its last row missing, which
|
|
# looks like a decode bug and is a layout bug.
|
|
recs = d.rec_bytes // SECTOR
|
|
rows = d.H
|
|
entries = rows + (1 if d.has_palette else 0)
|
|
|
|
out = "tmp/packed_meta.lua"
|
|
with open(out, "w") as fh:
|
|
fh.write("-- generated by tools/bench/prep_packed.py; do not edit\n")
|
|
fh.write("return {\n")
|
|
for k, v in [("W", d.W), ("H", d.H), ("fps", d.fps), ("nframes", d.nframes),
|
|
("rec_bytes", d.rec_bytes), ("rec_sectors", recs),
|
|
("pal_bytes", d.pal_bytes), ("pic_bytes", d.pic_bytes),
|
|
("lba0", d.off_frm // SECTOR),
|
|
("palette_last", int(d.palette_last)),
|
|
("has_palette", int(d.has_palette)),
|
|
# DLXP2. Zero in a silent container, and the player branches on
|
|
# the zero rather than being built two ways.
|
|
("cad_f", d.cad_f), ("cad_a", d.cad_a),
|
|
("has_audio", int(d.has_audio)),
|
|
("aud_bytes", d.aud_bytes), ("aud_hz", d.aud_hz),
|
|
("n_lumps", d.n_lumps),
|
|
("entries", entries)]:
|
|
fh.write(f" {k} = {v},\n")
|
|
fh.write("}\n")
|
|
|
|
print(f"{sys.argv[1]}: DLXP{d.version} {d.W}x{d.H} {d.fps}fps {d.nframes} frames"
|
|
+ (f", AUDIO F={d.cad_f} A={d.cad_a}" if d.has_audio else ", silent"))
|
|
print(f" record {d.rec_bytes:,} B = {recs} sectors, palette "
|
|
f"{'LAST' if d.palette_last else 'FIRST'}, {d.pal_bytes} B")
|
|
cad = (f" + (i//{d.cad_f})*{d.cad_a}" if d.has_audio else "")
|
|
print(f" record i is at LBA {d.off_frm // SECTOR} + i*{recs}{cad} -- ARITHMETIC. "
|
|
f"There is no index in this container and none can be needed.")
|
|
print(f" the chain the 68000 must build: {entries} entries "
|
|
f"({rows} rows{' + 1 palette' if d.has_palette else ''})")
|
|
print(f" wire {d.video_kbps():.1f}"
|
|
+ (f" + {d.audio_kbps():.2f} = {d.kbps():.1f}" if d.has_audio else "")
|
|
+ f" KB/s, FIXED by geometry -> {out}")
|