Handoff: reconcile docs with the verified display path
Session 3 summary in STATUS.md, plus the things a cold start needs. - Reproduce section for the display result, verified cold from the Blu-ray at end of session: extract -> prep -> MAME -> verify, exact match, 38.88 dB. The frames are not in the repo and the old ones lived in /tmp, so the chain starts from extract.py rather than assuming a scratch directory survives. - tools/bench/verify_frame.py turns FINDINGS 22 into a regression check. It is deliberately an exact test rather than a PSNR threshold, since the whole point of that section is that the render is bit-for-bit predictable. It prints the three registers to check when it fails. - Recorded where the MAME source now lives, and why to read it first: six register-poking attempts failed against a gate that one grep found. - Split the CRTC mode table out as its own next step. It is the prerequisite for the decoder skeleton and the smallest well-defined task available, with an explicit warning not to write the timing values from memory. Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Regression check for the display path (FINDINGS 22).
|
||||
|
||||
Compares a MAME snapshot of GVRAM against what the X68000's 15-bit+I palette
|
||||
MUST produce for the same source frame. This is an exact test, not a threshold:
|
||||
if the palette packing, the line stride, or the monitor-contrast setting drifts,
|
||||
`exact` goes False. Do not soften it into a PSNR threshold -- the whole point of
|
||||
FINDINGS 22 is that the render is bit-for-bit predictable.
|
||||
|
||||
python3 tools/bench/verify_frame.py tmp/frame_ref.png tmp/snap_verify/x68000/0000.png
|
||||
"""
|
||||
import sys, numpy as np
|
||||
from PIL import Image
|
||||
|
||||
ref_path = sys.argv[1] if len(sys.argv) > 1 else "tmp/frame_ref.png"
|
||||
snap_path = sys.argv[2] if len(sys.argv) > 2 else "tmp/snap_verify/x68000/0000.png"
|
||||
|
||||
ref = np.asarray(Image.open(ref_path).convert("RGB")).astype(int)
|
||||
snap = np.asarray(Image.open(snap_path).convert("RGB")).astype(int)
|
||||
H, W = ref.shape[:2]
|
||||
got = snap[0:H, 0:W]
|
||||
|
||||
# MAME x68k_v.cpp: GGGGGRRRRRBBBBBI, expanded via pal6bit((field << 1) | I)
|
||||
pal6bit = lambda v: (v << 2) | (v >> 4)
|
||||
pred = pal6bit(((ref >> 3) << 1) | 1)
|
||||
|
||||
exact = bool((pred == got).all())
|
||||
mse = ((ref - got) ** 2).mean()
|
||||
psnr = 99.0 if mse == 0 else 10 * np.log10(255 * 255 / mse)
|
||||
|
||||
print(f"exact match vs 15-bit+I prediction : {exact}")
|
||||
print(f"display quantisation cost : {psnr:.2f} dB (expected 38.88 on 00020 f0001)")
|
||||
if not exact:
|
||||
bad = np.argwhere((pred != got).any(axis=2))
|
||||
print(f" {len(bad)} differing pixels; first at {tuple(bad[0])}"
|
||||
f" pred={tuple(pred[tuple(bad[0])])} got={tuple(got[tuple(bad[0])])}")
|
||||
print(" check: CRTC R20 bit 11 clear, R20 bits 9-8 = 0x0100, $E8E001 contrast = 15")
|
||||
sys.exit(0 if exact else 1)
|
||||
Reference in New Issue
Block a user