#!/usr/bin/env bash # R0-1 — N1 parity gate: the TypeScript periodSnap/detectWrapMode in # canvas-engine.ts must make the same decisions as the certified Python # implementations in verify_n1_sim.py, on the real catalog tiles. # # Requires: python3 (PIL, numpy), node, esbuild (present via frontend # node_modules). Run from backend/floor-visualizer/. set -euo pipefail HERE="$(cd "$(dirname "$0")" && pwd)" FRONTEND="$HERE/../../frontend/viz2d-demo" TMP="$(mktemp -d)" trap 'rm -rf "$TMP"' EXIT # 1. Python: decode tiles to raw RGBA + record expected decisions python3 - "$TMP" << 'EOF' import json import sys import numpy as np from PIL import Image sys.path.insert(0, ".") from verify_n1_sim import detect_wrap_mode, period_snap tmp = sys.argv[1] TILES = "../../frontend/viz2d-demo/src/assets/tiles" cases = ["checkered.jpeg", "rustic-wood.jpg", "floor-natural-stone.jpg", "basalt-outside-wal.jpg", "mosaic-tile.jpg"] expected = {} for name in cases: key = name.split(".")[0] im = Image.open(f"{TILES}/{name}").convert("RGBA") open(f"{tmp}/{key}.bin", "wb").write(im.tobytes()) rgb = np.asarray(im.convert("RGB")) mode, _, _ = detect_wrap_mode(rgb) snap = None if mode != "wrap": out, info = period_snap(rgb) if info[0] == "snap": snap = [out.shape[1], out.shape[0]] expected[key] = {"w": im.width, "h": im.height, "mode": "wrap" if mode == "wrap" else "mirror", "snap": snap} json.dump(expected, open(f"{tmp}/expected.json", "w")) print("python decisions:", json.dumps(expected, default=str)) EOF # 2. Compile the actual frontend engine and replay the same decisions (cd "$FRONTEND" && npx esbuild src/visualizer-demo/canvas-engine.ts \ --format=cjs --outfile="$TMP/ce.cjs" --log-level=error) node - "$TMP" << 'EOF' const fs = require("fs"); const path = require("path"); const tmp = process.argv[2]; const { periodSnap, detectWrapMode } = require(path.join(tmp, "ce.cjs")); const expected = JSON.parse(fs.readFileSync(path.join(tmp, "expected.json"))); let ok = true; for (const [key, exp] of Object.entries(expected)) { const raw = new Uint8ClampedArray(fs.readFileSync(path.join(tmp, key + ".bin"))); const mode = detectWrapMode(raw, exp.w, exp.h); let snap = null; if (mode !== "wrap") { const r = periodSnap(raw, exp.w, exp.h); if (r) snap = [r.w, r.h]; } const pass = mode === exp.mode && JSON.stringify(snap) === JSON.stringify(exp.snap); console.log(` [${pass ? "PASS" : "FAIL"}] ${key}: ts mode=${mode} snap=${JSON.stringify(snap)}` + (pass ? "" : ` expected mode=${exp.mode} snap=${JSON.stringify(exp.snap)}`)); if (!pass) ok = false; } console.log(ok ? "N1 PARITY PASSED" : "N1 PARITY FAILED"); process.exit(ok ? 0 : 1); EOF