"""R0-1 — golden-image gate. Renders the reference matrix (bundle x tile) through golden_render and compares against the committed goldens. Any drift beyond tolerance fails with a side-by-side (golden | current | amplified diff) written to verify_out/. Usage: python verify_goldens.py # check against goldens (CI mode) python verify_goldens.py --bless # regenerate goldens (intentional change) Tolerances: renders are deterministic numpy, so genuine engine changes show up as large diffs; the small allowance absorbs PIL/numpy version drift only. """ import os import sys import numpy as np from PIL import Image import golden_render HERE = os.path.dirname(os.path.abspath(__file__)) TILES = os.path.join(HERE, "..", "..", "frontend", "viz2d-demo", "src", "assets", "tiles") GOLDEN_DIR = os.path.join(HERE, "goldens") OUT = os.path.join(HERE, "verify_out") MEAN_TOL = 0.5 # mean abs diff per channel P999_TOL = 8.0 # 99.9th percentile abs diff MATRIX = [ # (golden name, bundle, tile) — tiles cover the three texture-prep paths: # checkered = period-snap, rustic-wood = masked-shift, basalt = native wrap ("desk_checkered", "data/current_bundle.vizbundle.json", "checkered.jpeg"), ("desk_rustic", "data/current_bundle.vizbundle.json", "rustic-wood.jpg"), ("desk_basalt", "data/current_bundle.vizbundle.json", "basalt-outside-wal.jpg"), ("kitchen_checkered", "data/ref_kitchen.vizbundle.json", "checkered.jpeg"), ("kitchen_rustic", "data/ref_kitchen.vizbundle.json", "rustic-wood.jpg"), ("kitchen_basalt", "data/ref_kitchen.vizbundle.json", "basalt-outside-wal.jpg"), ] def main(): bless = "--bless" in sys.argv os.makedirs(GOLDEN_DIR, exist_ok=True) os.makedirs(OUT, exist_ok=True) ok = True for name, bundle, tile in MATRIX: img = golden_render.render(os.path.join(HERE, bundle), os.path.join(TILES, tile)) golden_path = os.path.join(GOLDEN_DIR, f"{name}.png") if bless: img.save(golden_path) print(f" blessed {name}.png ({img.width}x{img.height})") continue if not os.path.exists(golden_path): print(f" [FAIL] {name}: golden missing — run `make bless`") ok = False continue cur = np.asarray(img).astype(np.float64) gold = np.asarray(Image.open(golden_path).convert("RGB")).astype(np.float64) if cur.shape != gold.shape: print(f" [FAIL] {name}: size changed {gold.shape} -> {cur.shape}") ok = False continue diff = np.abs(cur - gold) mean_d = float(diff.mean()) p999 = float(np.percentile(diff, 99.9)) passed = mean_d <= MEAN_TOL and p999 <= P999_TOL print(f" [{'PASS' if passed else 'FAIL'}] {name}: mean={mean_d:.3f} p99.9={p999:.1f}") if not passed: ok = False amplified = np.clip(diff * 8, 0, 255).astype(np.uint8) panel = np.concatenate( [gold.astype(np.uint8), cur.astype(np.uint8), amplified], axis=1 ) fail_path = os.path.join(OUT, f"golden_fail_{name}.png") Image.fromarray(panel).save(fail_path) print(f" side-by-side: {fail_path} (golden | current | diff x8)") if bless: print("goldens regenerated — commit backend/floor-visualizer/goldens/") return 0 print("\n" + ("ALL GOLDEN CHECKS PASSED" if ok else "GOLDEN CHECKS FAILED")) return 0 if ok else 1 if __name__ == "__main__": raise SystemExit(main())