#!/usr/bin/env python3 """Fail if the p40-night frame's Wu Peng boat is not dark enough against its glow. The `LV_P40_SHOT=1` night hook centers tile (234,50) in a 1280x720 viewport; the two-tile boat occupies tiles (234,50)-(235,50), so its screen box and the ring of lantern glow around it are pure functions of those constants. The regression budget is: mean luma of the boat box must be at least 40 grey below the mean luma of a 24px ring around it. Usage: python3 scripts/check-p40-night-boat-contrast.py [p40-night.png] Defaults to docs/evidence/p40-night.png. Exit 0 clean, 1 below budget, 2 on usage/read error. """ import sys from pathlib import Path import numpy as np from PIL import Image MIN_CONTRAST = 40.0 TILE = 32 VIEW_W, VIEW_H = 1280, 720 BOAT_TX, BOAT_TY = 234, 50 RING = 24 def measure(path): rgba = np.asarray(Image.open(path).convert("RGB"), dtype=np.float64) luma = 0.299 * rgba[:, :, 0] + 0.587 * rgba[:, :, 1] + 0.114 * rgba[:, :, 2] cam_x = BOAT_TX * TILE + TILE // 2 - VIEW_W // 2 cam_y = BOAT_TY * TILE + TILE // 2 - VIEW_H // 2 x0 = BOAT_TX * TILE - cam_x y0 = BOAT_TY * TILE - cam_y x1 = x0 + 2 * TILE y1 = y0 + TILE boat = float(luma[y0:y1, x0:x1].mean()) patch = luma[y0 - RING:y1 + RING, x0 - RING:x1 + RING].copy() patch[RING:RING + TILE, RING:RING + 2 * TILE] = np.nan ring = float(np.nanmean(patch)) return boat, ring def main(argv): target = Path(argv[1]) if len(argv) > 1 else Path("docs/evidence/p40-night.png") if not target.is_file(): sys.stderr.write(f"missing frame: {target}\n") return 2 boat, ring = measure(target) contrast = ring - boat status = "OK" if contrast >= MIN_CONTRAST else "FAIL" print(f"p40-night boat={boat:.1f} ring={ring:.1f} contrast={contrast:.1f} (>= {MIN_CONTRAST:.0f}) {status}") return 0 if contrast >= MIN_CONTRAST else 1 if __name__ == "__main__": raise SystemExit(main(sys.argv))