""" verify_frontend_sim.py — scratch harness: simulate the deployed June-9 frontend (plain wrap, full-res bilinear, no mips) vs the current dev frontend (wrap-mode detection, mip pyramid, trilinear with per-pixel footprint LOD) on a real new-geometry bundle. Answers whether the distance-noise issue (#9) is already covered by the 2b + P0 frontend code that has not yet reached production. Usage: python verify_frontend_sim.py /tmp/hf_new_bundle.json """ import base64 import io import json import sys import numpy as np from PIL import Image def positive_modulo(v, d): return ((v % d) + d) % d def mirror_repeat(v): f = positive_modulo(v, 2.0) return np.where(f <= 1.0, f, 2.0 - f) def detect_wrap_mode(tex): h, w, _ = tex.shape if w < 4 or h < 4: return "mirror" col = lambda a, b: np.mean(np.abs(tex[:, a, :3].astype(float) - tex[:, b, :3].astype(float))) row = lambda a, b: np.mean(np.abs(tex[a, :, :3].astype(float) - tex[b, :, :3].astype(float))) internal = max((col(w // 2, w // 2 + 1) + row(h // 2, h // 2 + 1)) / 2, 1.5) return "wrap" if col(w - 1, 0) < internal * 3 and row(h - 1, 0) < internal * 3 else "mirror" def build_mips(tex): mips = [tex.astype(np.float32)] cur = mips[0] while cur.shape[0] > 1 or cur.shape[1] > 1: h, w, _ = cur.shape nh, nw = max(1, h // 2), max(1, w // 2) ys = np.minimum(2 * np.arange(nh), h - 1) xs = np.minimum(2 * np.arange(nw), w - 1) y1 = np.minimum(ys + 1, h - 1) x1 = np.minimum(xs + 1, w - 1) cur = (cur[np.ix_(ys, xs)] + cur[np.ix_(ys, x1)] + cur[np.ix_(y1, xs)] + cur[np.ix_(y1, x1)]) / 4 mips.append(cur) return mips def sample_bilinear(tex, x, y, wrap): h, w, _ = tex.shape x0 = np.clip(np.floor(x).astype(int), 0, w - 1) y0 = np.clip(np.floor(y).astype(int), 0, h - 1) if wrap: x1, y1 = (x0 + 1) % w, (y0 + 1) % h else: x1, y1 = np.minimum(x0 + 1, w - 1), np.minimum(y0 + 1, h - 1) fx = (x - np.floor(x))[:, None] fy = (y - np.floor(y))[:, None] return (tex[y0, x0] * (1 - fx) * (1 - fy) + tex[y0, x1] * fx * (1 - fy) + tex[y1, x0] * (1 - fx) * fy + tex[y1, x1] * fx * fy) def render(bundle, tex, mode): """mode: 'old' = June-9 prod (wrap modulo, full-res bilinear, min(W,H) scale) 'new' = dev (wrap detection, mips+trilinear, planeW scale)""" w, h = bundle["width"], bundle["height"] img = np.array(Image.open(io.BytesIO(base64.b64decode(bundle["pixels"]))).convert("RGB")) seg = bundle["segments"][0] idx = np.frombuffer(base64.b64decode(seg["mask"]), dtype=np.uint32) H = np.array(seg["homography"], dtype=np.float64).reshape(3, 3) p = seg["plane"] texH, texW = tex.shape[:2] ys, xs = idx // w, idx % w pw, ph = p["width"], p["height"] cx, cy = p["x"] + pw / 2, p["y"] + ph / 2 if mode == "old": repeat_w = max(48.0, min(pw, ph) * 0.22) else: repeat_w = max(32.0, pw * 0.18) repeat_h = repeat_w * (texH / texW) def to_plane(px, py): pts = np.column_stack([px, py, np.ones(len(px))]) @ H.T return pts[:, 0] / pts[:, 2], pts[:, 1] / pts[:, 2] fx, fy = to_plane(xs.astype(float), ys.astype(float)) rx, ry = fx - cx, fy - cy if mode == "old": u = positive_modulo(rx / repeat_w, 1.0) v = positive_modulo(ry / repeat_h, 1.0) sample = sample_bilinear(tex.astype(np.float32), u * texW, v * texH, wrap=True) else: wrap = detect_wrap_mode(tex) == "wrap" u = positive_modulo(rx / repeat_w, 1.0) if wrap else mirror_repeat(rx / repeat_w) v = positive_modulo(ry / repeat_h, 1.0) if wrap else mirror_repeat(ry / repeat_h) fx2, fy2 = to_plane(xs.astype(float) + 1, ys.astype(float)) fx3, fy3 = to_plane(xs.astype(float), ys.astype(float) + 1) tcx, tcy = (rx / repeat_w) * texW, (ry / repeat_h) * texH txx = ((fx2 - cx) / repeat_w) * texW txy = ((fy2 - cy) / repeat_h) * texH tyx = ((fx3 - cx) / repeat_w) * texW tyy = ((fy3 - cy) / repeat_h) * texH du = np.hypot(txx - tcx, txy - tcy) dv = np.hypot(tyx - tcx, tyy - tcy) footprint = np.maximum(np.maximum(du, dv), 1e-3) lod = np.log2(footprint) + 0.5 mips = build_mips(tex) max_l = len(mips) - 1 l0 = np.clip(np.floor(lod).astype(int), 0, max_l) frac = np.clip(lod - l0, 0, 1) sample = np.zeros((len(u), 3), np.float32) for level in np.unique(l0): m = l0 == level a = mips[level] sa = sample_bilinear(a, u[m] * a.shape[1], v[m] * a.shape[0], wrap) if level < max_l: b = mips[level + 1] sb = sample_bilinear(b, u[m] * b.shape[1], v[m] * b.shape[0], wrap) sa = sa + (sb - sa) * frac[m][:, None] sample[m] = sa[:, :3] # B4 shade decode (same in both versions) sr = seg.get("shadeRange") or [0.55, 1.35] shade_map = seg.get("shadeMap") if shade_map: sm = np.frombuffer(base64.b64decode(shade_map), dtype=np.uint8) shade = sr[0] + (sm[idx].astype(np.float32) / 255.0) * (sr[1] - sr[0]) else: shade = np.ones(len(idx), np.float32) out = img.copy() out[ys, xs] = np.clip(sample[:, :3] * shade[:, None], 0, 255).astype(np.uint8) return out def main(): bundle_path, tile_path, prefix = sys.argv[1], sys.argv[2], sys.argv[3] with open(bundle_path) as f: bundle = json.load(f) tex = np.array(Image.open(tile_path).convert("RGB")) print(f"tile: {tile_path} {tex.shape} wrapMode={detect_wrap_mode(tex)}") old = render(bundle, tex, "old") new = render(bundle, tex, "new") Image.fromarray(old).save(f"verify_out/{prefix}_old_frontend.png") Image.fromarray(new).save(f"verify_out/{prefix}_new_frontend.png") side = np.hstack([old, new]) Image.fromarray(side).save(f"verify_out/{prefix}_frontend_compare.png") print(f"saved verify_out/{prefix}_frontend_compare.png (left=June-9 prod, right=dev)") if __name__ == "__main__": main()