Move the loader onto the 68000, and find 5,920 bytes nobody counted
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
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
#!/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)")
|
||||
Reference in New Issue
Block a user