src/player/load.i expands both codebooks to word-per-pixel form and packs the palette to GGGGGRRRRRBBBBBI out of the RAW container header, byte-exact against tools/bench/dlxload.py on both CPU cores. The palette half is gated on words read back out of the palette registers at $E82000, so "the words reached the hardware" is part of what passes. ROADMAP P1 is done; P2's encoder half (a reserved black entry, 23.4) is not, and is a re-encode rather than an edit. A scene change costs 18.96 ms of 68000 time, 22.8% of one 12 fps frame; boot costs 24.70 ms. The scratch tables describe the CRTC, not the scene, so pal_tables is a separate entry point built once at boot -- 5.29 ms off every scene change. The one that moves something: the scene header is 5,920 B that no rate table in this tree included, because it belongs to no frame record. In FINDINGS 51.3's currency it is divided by the surplus pipe - wire, so it is hypersensitive: 138 ms of extra refill climb at 488 KB/s and 1.099 s at 451.4 KB/s, for the same bytes. tools/analysis/22_scene_load.py prices it across explicit rates. Recorded as open: the two CPU cores agree to <3% on every stage but the table build, where they differ by 16.4%. px68k's C68K charges a flat 50 clocks for MULU/MULS (c68kmacro.h:1869) where the 68000 charges 38+2n, which explains 4,608 of the 8,703 clock gap. 4,095 clocks are unexplained. Nothing else in src/player/ multiplies, so no figure in FINDINGS 24-52 is affected. decode.s and stream.s are untouched; decode.bin is still 1,296 B at the same MD5. check.sh gains a stage that gates byte-exactness on both cores and deliberately does not gate the cycle counts -- MAME's clock is 1/55.46 s and a wall timing would make the green light host-sensitive. Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
74 lines
3.0 KiB
Python
74 lines
3.0 KiB
Python
#!/usr/bin/env python3
|
|
"""Check the 68000's load-time output against tools/bench/dlxload.py, byte for byte.
|
|
|
|
python3 tools/bench/verify_load.py <in.dlx> [--out tmp/load]
|
|
|
|
The 68000 ran src/player/load.i over the RAW container header; tools/bench/
|
|
load.lua read the results back out of emulated RAM and out of the PALETTE
|
|
REGISTERS. This compares them with what the host-side transforms produce.
|
|
|
|
Byte-for-byte and not "close enough", for both halves:
|
|
|
|
* the codebooks are indices, so a single wrong byte is a wrong COLOUR in
|
|
every block that uses that codeword, in every frame of the scene.
|
|
* the palette words carry the shared LSB the encoder's 1.96 dB (FINDINGS
|
|
23.3) depends on, and a wrong choice of it is invisible in a diff of the
|
|
picture's SHAPE -- it is a slightly wrong colour, which is exactly the sort
|
|
of thing that gets attributed to the codec.
|
|
|
|
The darkest-entry index is checked too: it is what the letterbox is filled
|
|
with until the encoder reserves a black entry (23.4, still open), and it comes
|
|
out of an argmin whose tie-break has to match numpy's -- first index wins.
|
|
"""
|
|
import sys, argparse
|
|
sys.path.insert(0, "tools/encoder")
|
|
sys.path.insert(0, "tools/bench")
|
|
from dlx import DLX
|
|
import dlxload as DL
|
|
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument("container")
|
|
ap.add_argument("--out", default="tmp/load")
|
|
ap.add_argument("--log", default="tmp/load_check.log",
|
|
help="the rig's log, for the DARK= line it printed")
|
|
a = ap.parse_args()
|
|
|
|
d = DLX(a.container)
|
|
cb1, cb4 = DL.expand_codebooks(d)
|
|
palb, dark, _ = DL.pack_palette(d)
|
|
want = cb1.tobytes() + cb4.tobytes() + palb.tobytes()
|
|
got = open(a.out + "_out.bin", "rb").read()
|
|
|
|
if len(got) != len(want):
|
|
sys.exit(f"FAIL: the 68000 produced {len(got)} B, expected {len(want)}")
|
|
|
|
n1, n4 = cb1.nbytes, cb4.nbytes
|
|
sections = (("CB1", 0, n1), ("CB4", n1, n1 + n4), ("palette", n1 + n4, len(want)))
|
|
bad = 0
|
|
for name, lo, hi in sections:
|
|
diff = [i for i in range(lo, hi) if got[i] != want[i]]
|
|
if diff:
|
|
bad += len(diff)
|
|
i = diff[0]
|
|
print(f"FAIL: {name}: {len(diff)}/{hi-lo} bytes differ; first at "
|
|
f"+{i-lo} (68000 {got[i]:#04x}, dlxload {want[i]:#04x})")
|
|
else:
|
|
print(f" OK {name}: {hi-lo} B identical to dlxload.py")
|
|
|
|
# The rig prints the index the 68000 chose; parse it rather than re-deriving,
|
|
# so a rig that failed to read LDARK cannot pass by silence.
|
|
got_dark = None
|
|
for line in open(a.log, "rb").read().decode("utf-8", "replace").splitlines():
|
|
if "DARK=" in line:
|
|
got_dark = int(line.split("DARK=")[1].split()[0].rstrip(","))
|
|
if got_dark is None:
|
|
sys.exit("FAIL: the rig printed no DARK= line -- it did not reach the dump")
|
|
if got_dark != dark:
|
|
sys.exit(f"FAIL: darkest palette entry: 68000 says {got_dark}, dlxload says {dark}")
|
|
print(f" OK darkest entry {dark}, chosen by the same argmin tie-break")
|
|
|
|
if bad:
|
|
sys.exit(f"FAIL: {bad} bytes differ in total")
|
|
print(f"OK the 68000 reproduced all {len(want)} B of load-time output exactly "
|
|
f"(P1 codebooks, P2 palette)")
|