room-visualizer / verify_n1_parity.sh
GitHub Actions
Deploy from GitHub commit ca72656c17476e5aa37a4735af6e47ff9f94fa1a
b20c82e
Raw
History Blame Contribute Delete
2.77 kB
#!/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