Files
retroDE_ps2/tools/preview_scanout_ch418.py
thejayman77 ba74bbd5aa Snapshot: fog implementation + fidelity tooling baseline (pre bilinear-clamp fix)
Per-vertex GS fog end-to-end (gs_stub emit incl. persp_emit5, gs_prim_list_feeder
XYZ2->XYZF2 on PRIM.FGE, gs_make_sh3_scheduler_fixture.py F/FGE packing), new fog
TBs, fidelity attribution tooling. Functional baseline before removing the dead
bilinear lerp8 clamps (Codex: 161-node comb loop -> -0.042ns setup fail).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 19:56:46 -04:00

49 lines
1.6 KiB
Python

#!/usr/bin/env python3
"""Preview and score the Ch418 captured SH3 scanout presentation map.
RTL maps source_x=floor(output_x*4/5) and
source_y=32+floor(output_y*14/15). This host utility applies the same
integer mapping to a 640x480 framebuffer PNG, saves the expected HDMI
presentation, and reports RGB MAE/RMSE against a 640x480 reference.
"""
import math
import sys
from PIL import Image
def score(a, b):
ae = 0
se = 0
n = a.width * a.height * 3
for pa, pb in zip(a.getdata(), b.getdata()):
for ca, cb in zip(pa, pb):
d = int(ca) - int(cb)
ae += abs(d)
se += d * d
return ae / n, math.sqrt(se / n)
def main(argv):
if len(argv) != 4:
raise SystemExit("usage: preview_scanout_ch418.py <fb.png> <reference.png> <out.png>")
src = Image.open(argv[1]).convert("RGB")
if src.size != (640, 480):
raise SystemExit(f"expected 640x480 framebuffer, got {src.size}")
ref = Image.open(argv[2]).convert("RGB").resize((640, 480), Image.Resampling.BILINEAR)
mapped = Image.new("RGB", (640, 480))
mapped.putdata([
src.getpixel(((x * 4) // 5, 32 + (y * 14) // 15))
for y in range(480) for x in range(640)
])
mapped.save(argv[3])
raw_mae, raw_rmse = score(src, ref)
map_mae, map_rmse = score(mapped, ref)
print(f"raw MAE={raw_mae:.4f} RMSE={raw_rmse:.4f}")
print(f"mapped MAE={map_mae:.4f} RMSE={map_rmse:.4f}")
print(f"delta MAE={map_mae-raw_mae:+.4f} RMSE={map_rmse-raw_rmse:+.4f}")
print(f"wrote {argv[3]}")
if __name__ == "__main__":
main(sys.argv)