#!/usr/bin/env python3 # build_field_viz.py # Reads the REAL captured live field (predict_capture.npz) and bakes it into a # self-contained HTML file you open in a browser. No mockup — this renders the # actual 500 frames of the substrate the observer captured. # # Shows: all 6 channels (rho, ux, uy, sxx, syy, sxy) as 32x32 heatmaps, animated # through the real frames, PLUS a 7th panel = per-tile linear-prediction error # (where the field surprises the predictor). Play/pause/scrub. import sys, io, json, base64 sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace') import numpy as np NPZ = "predict_capture.npz" OUT = "field_viz.html" TILES, CH = 32, 6 CH_NAMES = ["rho","ux","uy","sxx","syy","sxy"] d = np.load(NPZ, allow_pickle=True) F = d["frames"].astype(np.float32) # (T, 1024, 6) C = d["cycles"] T = F.shape[0] print(f"Loaded {T} frames, cycles {C[0]}..{C[-1]}", flush=True) Fg = F.reshape(T, TILES, TILES, CH) # (T, y, x, ch) # Per-tile linear-prediction error field: |truth - (2*prev - prev2)| per tile, summed over channels # aligned to frames 2..T-1 pred = 2*Fg[1:-1] - Fg[0:-2] truth = Fg[2:] err = np.sqrt(np.mean((pred-truth)**2, axis=3)) # (T-2, y, x) # pad front so err index matches frame index err_full = np.zeros((T, TILES, TILES), dtype=np.float32) err_full[2:] = err # Per-channel normalization for display (diverging around each channel's mean) disp = np.zeros_like(Fg) ranges = [] for c in range(CH): ch = Fg[...,c] mu = ch.mean(); sd = ch.std() + 1e-9 z = (ch - mu) / sd z = np.clip(z, -3, 3) / 3.0 # -> [-1,1] disp[...,c] = z ranges.append((float(mu), float(sd))) # error display: normalize to [0,1] emax = np.percentile(err_full[2:], 99) + 1e-9 edisp = np.clip(err_full / emax, 0, 1) # Quantize to uint8 to keep the HTML small: disp in [-1,1]->[0,255], edisp [0,1]->[0,255] disp_q = ((disp*0.5+0.5)*255).astype(np.uint8) # (T,y,x,ch) edisp_q = (edisp*255).astype(np.uint8) # (T,y,x) # Pack as base64: frames as (T, ch, y, x) then err (T,y,x) buf = disp_q.transpose(0,3,1,2).tobytes() # T*6*32*32 ebuf = edisp_q.tobytes() # T*32*32 b64 = base64.b64encode(buf).decode() eb64 = base64.b64encode(ebuf).decode() cyc_list = [int(x) for x in C] html = f"""Khra'gixx live field

Khra'gixx — the live field, rendered

{T} real frames captured from port 5561 · cycles · 32×32 coarse grid · this is the substrate moving, not nine averages
frame 0/{T-1} · cycle
Six macroscopic channels + the linear-prediction error (orange). Blue↔red = each channel's own deviation (±3σ). The error panel lights up where the field's next state SURPRISES the constant-velocity predictor — i.e. where something non-trivial is happening. Smooth dark error = predictable flow. Bright spots = the interesting bits. Passive predict-test verdict: linear beats persistence by 21% — the field has real short-horizon dynamics.
""" with open(OUT,"w") as fp: fp.write(html) print(f"Wrote {OUT} ({len(html)//1024} KB). Open it in a browser.", flush=True)