#!/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)