Synced from resonance-engine-active - July 16 2026
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
#!/usr/bin/env python3
|
||||
# baseline_breathing_test.py
|
||||
# Question (Jason): left completely alone, does the organism keep changing, and is that
|
||||
# change genuine self-evolution (the state DRIFTS over time) or just churn-in-place
|
||||
# (jitter around a fixed center = noise, not evolution)?
|
||||
#
|
||||
# NO injection. NO intervention. Pure observation of the free-running field.
|
||||
# This establishes THE BASELINE that any later on/off perturbation test measures against.
|
||||
#
|
||||
# Two data sources, both read-only:
|
||||
# A) telemetry.jsonl -> per-channel natural movement (the noise floor of each scalar)
|
||||
# B) live 5561 coarse field -> spatial frame-to-frame change + drift-vs-churn
|
||||
#
|
||||
# Drift-vs-churn discriminator:
|
||||
# - churn (noise): frame(t) vs frame(t+k) distance is FLAT in k -> it wanders around a
|
||||
# fixed center, never gets further away. Memoryless jitter.
|
||||
# - drift (self-evolving): distance GROWS with k -> the center itself is moving, the
|
||||
# field at t+k is systematically further from t than t+1 is. State evolves.
|
||||
|
||||
import sys, io, json, time, struct, os
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
|
||||
import numpy as np
|
||||
|
||||
# ---------- PART A: telemetry per-channel natural movement ----------
|
||||
TEL = None
|
||||
for p in ["/mnt/d/resonance-engine/telemetry.jsonl", r"D:\resonance-engine\telemetry.jsonl"]:
|
||||
if os.path.exists(p): TEL=p; break
|
||||
|
||||
CHANS = ['coherence','asymmetry','vel_mean','vel_max','vel_var','vorticity_mean',
|
||||
'stress_xx','stress_yy','stress_xy']
|
||||
|
||||
print("="*64); print("BASELINE BREATHING TEST — organism left alone"); print("="*64)
|
||||
|
||||
if TEL:
|
||||
rows=[]
|
||||
with open(TEL) as f:
|
||||
for line in f:
|
||||
try: d=json.loads(line)
|
||||
except: continue
|
||||
if abs(d.get('omega',0)-1.97)<0.01:
|
||||
rows.append([d.get(k,np.nan) for k in CHANS]+[d.get('cycle',0)])
|
||||
A=np.array(rows,dtype=np.float64)
|
||||
print(f"\n[A] telemetry.jsonl: {len(A)} canonical rows, cycles {int(A[:,-1].min())}..{int(A[:,-1].max())}", flush=True)
|
||||
print(" per-channel natural movement (this IS the baseline noise floor):", flush=True)
|
||||
print(f" {'channel':<16}{'mean':>12}{'std':>12}{'CV%':>10}{'range':>14}", flush=True)
|
||||
for i,ch in enumerate(CHANS):
|
||||
col=A[:,i]; col=col[~np.isnan(col)]
|
||||
if len(col)<2: continue
|
||||
mu,sd=col.mean(),col.std()
|
||||
cv=100*sd/abs(mu) if abs(mu)>1e-12 else float('nan')
|
||||
print(f" {ch:<16}{mu:>12.5f}{sd:>12.5f}{cv:>9.2f}%{col.max()-col.min():>14.5f}", flush=True)
|
||||
print(" -> channels with high CV move a lot on their own; low CV sit still.", flush=True)
|
||||
print(" -> ANY on/off perturbation must exceed THIS movement to be detectable.", flush=True)
|
||||
else:
|
||||
print("\n[A] telemetry.jsonl not found — skipping scalar baseline.", flush=True)
|
||||
|
||||
# ---------- PART B: live coarse field, drift vs churn ----------
|
||||
TILES,CH=32,6; NVALS=TILES*TILES*CH; HDR=16; FB=HDR+NVALS*4
|
||||
PORT="tcp://localhost:5561"; NCAP=600
|
||||
try:
|
||||
import zmq
|
||||
except ImportError:
|
||||
print("\n[B] pyzmq missing; skip live part"); sys.exit(0)
|
||||
|
||||
print(f"\n[B] Capturing {NCAP} live frames from {PORT} (read-only)...", flush=True)
|
||||
ctx=zmq.Context(); s=ctx.socket(zmq.SUB); s.setsockopt(zmq.RCVTIMEO,5000); s.setsockopt_string(zmq.SUBSCRIBE,"")
|
||||
s.connect(PORT)
|
||||
F=[]; C=[]
|
||||
while len(F)<NCAP:
|
||||
try: buf=s.recv()
|
||||
except zmq.Again: print(f" timeout at {len(F)} frames"); break
|
||||
if len(buf)!=FB or buf[:4]!=b"KGCF": continue
|
||||
C.append(struct.unpack_from("<I",buf,4)[0])
|
||||
F.append(np.frombuffer(buf,dtype=np.float32,count=NVALS,offset=HDR).copy())
|
||||
s.close(); ctx.term()
|
||||
F=np.array(F); C=np.array(C)
|
||||
if len(F)<50: print("too few frames"); sys.exit(1)
|
||||
print(f" got {len(F)} frames, cycles {C[0]}..{C[-1]}", flush=True)
|
||||
|
||||
# z-score per channel so no channel dominates
|
||||
Fr=F.reshape(len(F),TILES*TILES,CH).astype(np.float64)
|
||||
mu=Fr.mean(axis=(0,1),keepdims=True); sd=Fr.std(axis=(0,1),keepdims=True); sd[sd<1e-9]=1
|
||||
Z=(Fr-mu)/sd
|
||||
Zflat=Z.reshape(len(F),-1)
|
||||
|
||||
# frame-to-frame change (is it even moving spatially?)
|
||||
step=np.sqrt(np.mean(np.diff(Zflat,axis=0)**2,axis=1))
|
||||
print(f"\n spatial frame-to-frame change (per ~10 cyc): mean={step.mean():.4f} std={step.std():.4f}", flush=True)
|
||||
print(f" -> {'MOVING (field changes every frame)' if step.mean()>1e-3 else 'STATIC (barely changes)'}", flush=True)
|
||||
|
||||
# DRIFT vs CHURN: distance between frames as a function of separation k
|
||||
print("\n=== DRIFT vs CHURN (does the state get FURTHER over time, or wander in place?) ===", flush=True)
|
||||
ks=[1,2,5,10,20,50,100,200]
|
||||
print(f" {'gap(frames)':>12}{'gap(cyc)':>10}{'mean_dist':>12}", flush=True)
|
||||
dvals=[]
|
||||
for k in ks:
|
||||
if k>=len(Zflat): continue
|
||||
dd=np.sqrt(np.mean((Zflat[k:]-Zflat[:-k])**2,axis=1))
|
||||
dvals.append((k,dd.mean()))
|
||||
print(f" {k:>12}{k*10:>10}{dd.mean():>12.4f}", flush=True)
|
||||
d1=dvals[0][1]; dlast=dvals[-1][1]
|
||||
grow=dlast/max(d1,1e-9)
|
||||
# saturation: does distance keep climbing or plateau?
|
||||
print(f"\n distance at gap={dvals[0][0]}: {d1:.4f} at gap={dvals[-1][0]}: {dlast:.4f} growth {grow:.2f}x", flush=True)
|
||||
|
||||
print("\n=== READ (description only, no 'memory' verdict) ===", flush=True)
|
||||
if grow>1.3:
|
||||
print(" Distance GROWS with time separation, then likely plateaus.", flush=True)
|
||||
print(" => The field DRIFTS: state at t+k is systematically further from t than t+1 is.", flush=True)
|
||||
print(" This is self-evolution, not jitter-in-place. It is genuinely going somewhere.", flush=True)
|
||||
print(" The plateau distance = the 'diameter' of its wandering; the gap where it", flush=True)
|
||||
print(" saturates = the timescale over which it forgets where it was. THAT number is", flush=True)
|
||||
print(" the natural memory-horizon of the undisturbed organism. Note it.", flush=True)
|
||||
else:
|
||||
print(" Distance is roughly FLAT with time separation.", flush=True)
|
||||
print(" => The field CHURNS in place: t+1 and t+200 are about equally far from t.", flush=True)
|
||||
print(" It moves constantly but does not go anywhere — jitter around a fixed center.", flush=True)
|
||||
print(" That is baseline noise, not self-evolution. A perturbation would have to push", flush=True)
|
||||
print(" the center itself to leave a trace above this churn.", flush=True)
|
||||
|
||||
np.savez("baseline_breathing.npz", step=step, ks=[k for k,_ in dvals], dist=[v for _,v in dvals], cyc=C)
|
||||
print("\nWrote baseline_breathing.npz. DONE.", flush=True)
|
||||
@@ -0,0 +1,151 @@
|
||||
#!/usr/bin/env python3
|
||||
# baseline_orbit_test.py
|
||||
# QUESTION (baseline, before any perturbation): left to breathe with NOTHING done to it,
|
||||
# does the SPATIAL field return to itself — is there a stable attractor it relaxes onto
|
||||
# (=> old perturbations wash out, we effectively HAVE a baseline despite never resetting)
|
||||
# or does it accumulate/drift forever (=> contaminated, no clean baseline)?
|
||||
#
|
||||
# The global telemetry shows a clean ~120-cycle coherence limit cycle. This asks whether
|
||||
# the SPATIAL field (5561 coarse stream) is ALSO on a clean orbit, or churns underneath it.
|
||||
#
|
||||
# Read-only: subscribes to 5561, sends NOTHING to the daemon. No injection.
|
||||
#
|
||||
# Method — "does it return to itself":
|
||||
# Capture a long undisturbed stretch. For a reference frame at time t0, measure the
|
||||
# spatial distance to every later frame. If there's an attractor/orbit, distance should
|
||||
# DIP periodically (field comes back near where it was) — a recurrence signature.
|
||||
# If it's accumulating, distance grows monotonically and never returns.
|
||||
# Cross-check against the mass-leak: work on MEAN-SUBTRACTED, per-frame-normalized fields
|
||||
# so we test PATTERN return, not the slow density ramp.
|
||||
|
||||
import sys, io, struct, time
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
|
||||
import numpy as np
|
||||
try:
|
||||
import zmq
|
||||
except ImportError:
|
||||
print("pip install pyzmq --break-system-packages"); sys.exit(1)
|
||||
|
||||
TILES, CH = 32, 6
|
||||
NVALS = TILES*TILES*CH
|
||||
HDR = 16
|
||||
FRAME_BYTES = HDR + NVALS*4
|
||||
PORT = "tcp://localhost:5561"
|
||||
CH_NAMES = ["rho","ux","uy","sxx","syy","sxy"]
|
||||
N_CAPTURE = 1500 # ~150s at 100Hz => ~15000 cycles => >100 of the ~120-cycle global orbits
|
||||
OUT_DATA = "baseline_orbit.npz"
|
||||
OUT_REPORT = "baseline_orbit_report.txt"
|
||||
|
||||
def capture(n):
|
||||
ctx=zmq.Context(); s=ctx.socket(zmq.SUB)
|
||||
s.setsockopt(zmq.RCVTIMEO,5000); s.setsockopt_string(zmq.SUBSCRIBE,"")
|
||||
s.connect(PORT)
|
||||
frames=[]; cycles=[]
|
||||
print(f"Capturing {n} frames (read-only, NO injection)...", flush=True)
|
||||
t0=time.time()
|
||||
while len(frames)<n:
|
||||
try: buf=s.recv()
|
||||
except zmq.Again:
|
||||
print(f" timeout after {len(frames)} — is 5561 streaming?"); break
|
||||
if len(buf)!=FRAME_BYTES or buf[:4]!=b"KGCF": continue
|
||||
cyc=struct.unpack_from("<I",buf,4)[0]
|
||||
vals=np.frombuffer(buf,dtype=np.float32,count=NVALS,offset=HDR).copy()
|
||||
frames.append(vals.reshape(TILES*TILES,CH)); cycles.append(cyc)
|
||||
if len(frames)%250==0: print(f" {len(frames)}/{n} cycle={cyc}", flush=True)
|
||||
s.close(); ctx.term()
|
||||
print(f"Captured {len(frames)} in {time.time()-t0:.1f}s", flush=True)
|
||||
return np.array(frames), np.array(cycles)
|
||||
|
||||
def norm_frames(F):
|
||||
# per-frame, per-channel mean-subtract + scale -> tests PATTERN, removes mass-leak amplitude
|
||||
G=F.astype(np.float64).copy() # (T, tiles, CH)
|
||||
mu=G.mean(axis=1,keepdims=True) # per-frame per-channel mean
|
||||
G=G-mu
|
||||
sd=G.std(axis=1,keepdims=True); sd[sd<1e-12]=1.0
|
||||
G=G/sd
|
||||
return G.reshape(len(G),-1) # (T, tiles*CH)
|
||||
|
||||
def main():
|
||||
F,C=capture(N_CAPTURE)
|
||||
if len(F)<300:
|
||||
print("Too few frames; aborting."); sys.exit(1)
|
||||
dcyc=np.diff(C)
|
||||
print(f"cycle step median={np.median(dcyc):.0f} (expect ~10)", flush=True)
|
||||
|
||||
G=norm_frames(F) # (T, D)
|
||||
T=len(G)
|
||||
|
||||
lines=[];
|
||||
def out(s): print(s,flush=True); lines.append(s)
|
||||
out("="*64)
|
||||
out("BASELINE ORBIT / RETURN TEST — undisturbed spatial field (5561)")
|
||||
out("="*64)
|
||||
out(f"frames={T} cycles {C[0]}..{C[-1]} (~{C[-1]-C[0]} cycles)")
|
||||
|
||||
# --- RECURRENCE: distance from several reference frames to all later frames ---
|
||||
# Look for periodic DIPS (field returns near a past state).
|
||||
refs=[0, T//4, T//2]
|
||||
out("")
|
||||
out("RETURN SIGNATURE (does distance-from-reference dip periodically?):")
|
||||
dip_periods=[]
|
||||
for r in refs:
|
||||
d=np.sqrt(np.mean((G-G[r])**2,axis=1)) # distance from ref to all frames
|
||||
# ignore the trivial zero at r; look forward
|
||||
fwd=d[r+5:]
|
||||
if len(fwd)<50: continue
|
||||
# find local minima (returns) and their spacing in frames
|
||||
mins=[]
|
||||
for i in range(2,len(fwd)-2):
|
||||
if fwd[i]<fwd[i-1] and fwd[i]<fwd[i+1] and fwd[i]<np.median(fwd)*0.85:
|
||||
mins.append(i)
|
||||
if len(mins)>=2:
|
||||
spac=np.diff(mins)
|
||||
# convert frame-spacing to cycle-spacing (~10 cyc/frame)
|
||||
cyc_per=np.median(spac)*np.median(dcyc)
|
||||
dip_periods.append(cyc_per)
|
||||
out(f" ref@frame{r}: {len(mins)} returns, ~{cyc_per:.0f} cycles between returns, "
|
||||
f"min dist reached {fwd[mins].min():.3f} (start dist {d[r]:.3f}->{fwd.min():.3f})")
|
||||
else:
|
||||
out(f" ref@frame{r}: NO periodic returns found (distance does not dip back)")
|
||||
|
||||
# --- ACCUMULATION vs BOUNDED: does distance-from-start grow forever or stay bounded? ---
|
||||
d0=np.sqrt(np.mean((G-G[0])**2,axis=1))
|
||||
# linear trend of distance over time
|
||||
tt=np.arange(T)
|
||||
slope=np.polyfit(tt,d0,1)[0]
|
||||
early=d0[:T//5].mean(); late=d0[-T//5:].mean()
|
||||
out("")
|
||||
out("ACCUMULATION CHECK (pattern distance from start over time):")
|
||||
out(f" early mean dist {early:.3f} | late mean dist {late:.3f} | trend slope {slope:+.2e}/frame")
|
||||
bounded = abs(late-early)/max(early,1e-9) < 0.15 and abs(slope) < (early/ (T*3))
|
||||
out(f" {'BOUNDED — pattern distance stays flat: field orbits, does NOT accumulate' if bounded else 'GROWING — pattern distance trends up: field is drifting/accumulating'}")
|
||||
|
||||
# --- global coherence-analog on coarse field for cross-check with telemetry ~120-cyc ---
|
||||
out("")
|
||||
if dip_periods:
|
||||
out(f"SPATIAL return period ~{np.median(dip_periods):.0f} cycles "
|
||||
f"(telemetry global coherence orbit was ~120 cyc — compare).")
|
||||
out("")
|
||||
out("=== BASELINE VERDICT ===")
|
||||
has_orbit = len(dip_periods)>0
|
||||
if has_orbit and bounded:
|
||||
out(" ATTRACTOR PRESENT: the undisturbed spatial field RETURNS to near past states")
|
||||
out(" on a stable period and does not accumulate. => It relaxes onto an attractor;")
|
||||
out(" old perturbations plausibly wash out; we effectively HAVE a baseline despite")
|
||||
out(" never resetting. The natural churn is a bounded orbit = a clean noise floor")
|
||||
out(" to run an on/off perturbation test against.")
|
||||
elif not has_orbit and not bounded:
|
||||
out(" ACCUMULATING / NON-RETURNING: the spatial field does not come back to itself")
|
||||
out(" and drifts. => Jason's contamination worry is supported: no clean baseline;")
|
||||
out(" history accumulates. A perturbation test needs each trial from a fresh daemon,")
|
||||
out(" or must measure deltas over windows short vs the drift.")
|
||||
else:
|
||||
out(" MIXED: orbit and accumulation signals disagree — report both, do not force a verdict.")
|
||||
out(" (e.g. bounded but no clean periodic return = wanders on a bounded manifold.)")
|
||||
|
||||
np.savez(OUT_DATA, frames=F.astype(np.float32), cycles=C, d0=d0)
|
||||
with open(OUT_REPORT,"w") as f: f.write("\n".join(lines)+"\n")
|
||||
print(f"\nWrote {OUT_REPORT} and {OUT_DATA}. DONE.", flush=True)
|
||||
|
||||
if __name__=="__main__":
|
||||
main()
|
||||
@@ -0,0 +1,136 @@
|
||||
#!/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"""<!doctype html><html><head><meta charset="utf-8"><title>Khra'gixx live field</title>
|
||||
<style>
|
||||
body{{margin:0;background:#0a0a0f;color:#c8c8d0;font-family:ui-monospace,Menlo,monospace}}
|
||||
.wrap{{max-width:1100px;margin:0 auto;padding:18px}}
|
||||
h1{{font-size:15px;font-weight:600;color:#e6e6ee;margin:0 0 2px}}
|
||||
.sub{{font-size:11px;color:#7a7a88;margin-bottom:14px}}
|
||||
.grid{{display:grid;grid-template-columns:repeat(4,1fr);gap:14px}}
|
||||
.panel{{background:#12121a;border:1px solid #22222e;border-radius:8px;padding:8px}}
|
||||
.panel h2{{font-size:11px;margin:0 0 6px;color:#9a9aae;font-weight:600;letter-spacing:.04em}}
|
||||
canvas{{width:100%;image-rendering:pixelated;border-radius:4px;background:#000;aspect-ratio:1}}
|
||||
.err h2{{color:#ff9a6a}}
|
||||
.ctl{{display:flex;align-items:center;gap:12px;margin:16px 0 6px}}
|
||||
button{{background:#1c1c28;color:#d0d0dc;border:1px solid #33334a;border-radius:6px;padding:7px 14px;font:inherit;cursor:pointer}}
|
||||
button:hover{{background:#26263a}}
|
||||
input[type=range]{{flex:1;accent-color:#ff9a6a}}
|
||||
.meta{{font-size:11px;color:#7a7a88}}
|
||||
.cyc{{color:#ff9a6a;font-weight:600}}
|
||||
.legend{{font-size:10px;color:#66667a;margin-top:8px;line-height:1.5}}
|
||||
</style></head><body><div class="wrap">
|
||||
<h1>Khra'gixx — the live field, rendered</h1>
|
||||
<div class="sub">{T} real frames captured from port 5561 · cycles <span id="cr"></span> · 32×32 coarse grid · this is the substrate moving, not nine averages</div>
|
||||
<div class="grid" id="panels"></div>
|
||||
<div class="ctl">
|
||||
<button id="play">⏸ pause</button>
|
||||
<input type="range" id="scrub" min="0" max="{T-1}" value="0">
|
||||
<span class="meta">frame <span id="fn">0</span>/{T-1} · cycle <span class="cyc" id="cyc"></span></span>
|
||||
</div>
|
||||
<div class="legend">
|
||||
Six macroscopic channels + the linear-prediction error (orange). Blue↔red = each channel's own deviation (±3σ).
|
||||
The <b style="color:#ff9a6a">error panel</b> 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.
|
||||
</div></div>
|
||||
<script>
|
||||
const T={T}, N={TILES}, CH={CH}, names={json.dumps(CH_NAMES)}, cycles={json.dumps(cyc_list)};
|
||||
const raw=Uint8Array.from(atob("{b64}"),c=>c.charCodeAt(0)); // T*CH*N*N
|
||||
const eraw=Uint8Array.from(atob("{eb64}"),c=>c.charCodeAt(0)); // T*N*N
|
||||
document.getElementById('cr').textContent=cycles[0]+"→"+cycles[T-1];
|
||||
const panels=document.getElementById('panels'); const cvs=[];
|
||||
function mk(title,cls){{const d=document.createElement('div');d.className='panel'+(cls?' '+cls:'');d.innerHTML='<h2>'+title+'</h2>';const c=document.createElement('canvas');c.width=N;c.height=N;d.appendChild(c);panels.appendChild(d);return c;}}
|
||||
for(let c=0;c<CH;c++)cvs.push(mk(names[c]));
|
||||
const ecv=mk('prediction error','err');
|
||||
const ctxs=cvs.map(c=>c.getContext('2d')); const ectx=ecv.getContext('2d');
|
||||
const imgs=cvs.map(()=>ctxs[0].createImageData(N,N)); const eimg=ectx.createImageData(N,N);
|
||||
// diverging blue-white-red
|
||||
function div(v){{ // v in [0,255] -> rgb
|
||||
const t=(v/255)*2-1; // -1..1
|
||||
if(t<0){{const a=-t;return[Math.round(30+ (1-a)*200),Math.round(60+(1-a)*180),Math.round(120+a*135)];}}
|
||||
else{{return[Math.round(230),Math.round(230-t*180),Math.round(230-t*200)];}}
|
||||
}}
|
||||
function heat(v){{ // 0..255 -> black->orange->white
|
||||
const t=v/255; const r=Math.min(255,t*3*255), g=Math.min(255,Math.max(0,(t-0.33)*3*255)), b=Math.min(255,Math.max(0,(t-0.66)*3*255));
|
||||
return[r,g,b];
|
||||
}}
|
||||
function draw(f){{
|
||||
for(let c=0;c<CH;c++){{const off=(f*CH+c)*N*N; const im=imgs[c];
|
||||
for(let i=0;i<N*N;i++){{const [r,g,b]=div(raw[off+i]);im.data[i*4]=r;im.data[i*4+1]=g;im.data[i*4+2]=b;im.data[i*4+3]=255;}}
|
||||
ctxs[c].putImageData(im,0,0);}}
|
||||
const eoff=f*N*N; for(let i=0;i<N*N;i++){{const [r,g,b]=heat(eraw[eoff+i]);eimg.data[i*4]=r;eimg.data[i*4+1]=g;eimg.data[i*4+2]=b;eimg.data[i*4+3]=255;}}
|
||||
ectx.putImageData(eimg,0,0);
|
||||
document.getElementById('fn').textContent=f; document.getElementById('cyc').textContent=cycles[f];
|
||||
document.getElementById('scrub').value=f;
|
||||
}}
|
||||
let f=0,playing=true;
|
||||
const scrub=document.getElementById('scrub'),playb=document.getElementById('play');
|
||||
scrub.oninput=()=>{{f=+scrub.value;draw(f);}};
|
||||
playb.onclick=()=>{{playing=!playing;playb.textContent=playing?'⏸ pause':'▶ play';}};
|
||||
function loop(){{if(playing){{f=(f+1)%T;draw(f);}}setTimeout(loop,60);}}
|
||||
draw(0);loop();
|
||||
</script></body></html>"""
|
||||
|
||||
with open(OUT,"w") as fp:
|
||||
fp.write(html)
|
||||
print(f"Wrote {OUT} ({len(html)//1024} KB). Open it in a browser.", flush=True)
|
||||
@@ -0,0 +1,164 @@
|
||||
#!/usr/bin/env python3
|
||||
# capture_and_predict.py
|
||||
# PASSIVE PREDICT-TEST on the live 5561 coarse stream.
|
||||
# Read-only: subscribes to the observer daemon, sends NOTHING.
|
||||
# Captures N frames, then asks the ONE honest question:
|
||||
# Can ANY predictor beat persistence ("next = current") on the live field?
|
||||
# Pre-committed, bootstrapped, no rescue. Writes results + a data file for visualization.
|
||||
#
|
||||
# Per KHRAGIXX_SOURCE_OF_TRUTH.md:
|
||||
# - short window (leak-safe)
|
||||
# - persistence + linear baselines, pre-committed
|
||||
# - bootstrap distribution, not single sample
|
||||
# - a clean null is a real result; do not rescue
|
||||
|
||||
import sys, io, json, time, struct
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
|
||||
import numpy as np
|
||||
|
||||
try:
|
||||
import zmq
|
||||
except ImportError:
|
||||
print("pip install pyzmq --break-system-packages"); sys.exit(1)
|
||||
|
||||
TILES, CH = 32, 6
|
||||
NVALS = TILES * TILES * CH
|
||||
HDR = 16
|
||||
FRAME_BYTES = HDR + NVALS * 4
|
||||
N_CAPTURE = 500 # ~50s at 100Hz. Short => leak-safe.
|
||||
PORT = "tcp://localhost:5561"
|
||||
CH_NAMES = ["rho", "ux", "uy", "sxx", "syy", "sxy"]
|
||||
OUT_DATA = "predict_capture.npz"
|
||||
OUT_REPORT = "predict_report.txt"
|
||||
|
||||
def capture(n):
|
||||
ctx = zmq.Context()
|
||||
s = ctx.socket(zmq.SUB)
|
||||
s.setsockopt(zmq.RCVTIMEO, 5000)
|
||||
s.setsockopt_string(zmq.SUBSCRIBE, "")
|
||||
s.connect(PORT)
|
||||
frames, cycles = [], []
|
||||
print(f"Capturing {n} frames from {PORT} (read-only)...", flush=True)
|
||||
t0 = time.time()
|
||||
while len(frames) < n:
|
||||
try:
|
||||
buf = s.recv()
|
||||
except zmq.Again:
|
||||
print(f" timeout after {len(frames)} frames — is the daemon streaming 5561?"); break
|
||||
if len(buf) != FRAME_BYTES: continue
|
||||
if buf[:4] != b"KGCF": continue
|
||||
cyc = struct.unpack_from("<I", buf, 4)[0]
|
||||
vals = np.frombuffer(buf, dtype=np.float32, count=NVALS, offset=HDR).copy()
|
||||
frames.append(vals.reshape(TILES*TILES, CH))
|
||||
cycles.append(cyc)
|
||||
if len(frames) % 100 == 0:
|
||||
print(f" {len(frames)}/{n} cycle={cyc}", flush=True)
|
||||
s.close(); ctx.term()
|
||||
dt = time.time() - t0
|
||||
print(f"Captured {len(frames)} frames in {dt:.1f}s ({len(frames)/max(dt,1e-9):.1f} fps)", flush=True)
|
||||
return np.array(frames), np.array(cycles)
|
||||
|
||||
def zscore_per_channel(F):
|
||||
# F: (T, tiles, CH). Normalize each channel so no single channel dominates the error metric.
|
||||
mu = F.mean(axis=(0,1), keepdims=True)
|
||||
sd = F.std(axis=(0,1), keepdims=True); sd[sd < 1e-9] = 1.0
|
||||
return (F - mu) / sd
|
||||
|
||||
def frame_err(pred, truth):
|
||||
# RMSE over all tiles*channels of one frame-step, per step -> array of length T-1 (or T-k)
|
||||
d = pred - truth
|
||||
return np.sqrt(np.mean(d*d, axis=(1,2)))
|
||||
|
||||
def main():
|
||||
F, C = capture(N_CAPTURE)
|
||||
if len(F) < 50:
|
||||
print("Too few frames; aborting."); sys.exit(1)
|
||||
|
||||
# cycle sanity: should advance ~10/frame
|
||||
dcyc = np.diff(C)
|
||||
print(f"cycle step: median={np.median(dcyc):.0f} (expect ~10), min={dcyc.min()}, max={dcyc.max()}", flush=True)
|
||||
|
||||
Fz = zscore_per_channel(F.astype(np.float64)) # (T, 1024, 6)
|
||||
T = len(Fz)
|
||||
|
||||
# ---- PRE-COMMITTED PREDICTORS (declared before scoring) ----
|
||||
# 1) PERSISTENCE: pred[t] = frame[t-1] (the baseline to beat)
|
||||
# 2) LINEAR: pred[t] = 2*frame[t-1] - frame[t-2] (constant-velocity extrapolation)
|
||||
# 3) MEAN: pred[t] = running mean of all prior frames (dumb global)
|
||||
# Score = mean per-step RMSE over t = 2..T-1 (aligned window for all three).
|
||||
|
||||
truth = Fz[2:] # frames 2..T-1
|
||||
persist = Fz[1:-1] # frame t-1
|
||||
linear = 2*Fz[1:-1] - Fz[0:-2] # 2*(t-1) - (t-2)
|
||||
# running mean predictor
|
||||
cummean = np.cumsum(Fz, axis=0) / np.arange(1, T+1)[:,None,None]
|
||||
meanpred = cummean[1:-1] # mean of frames 0..t-1 approx (uses through t-1)
|
||||
|
||||
e_persist = frame_err(persist, truth)
|
||||
e_linear = frame_err(linear, truth)
|
||||
e_mean = frame_err(meanpred, truth)
|
||||
|
||||
P = e_persist.mean(); L = e_linear.mean(); M = e_mean.mean()
|
||||
|
||||
# ---- BOOTSTRAP: is linear reliably better (lower err) than persistence? ----
|
||||
# Pre-committed: linear "beats" persistence only if the 95% bootstrap CI of
|
||||
# (persist_err - linear_err) is entirely > 0 (i.e. linear strictly lower error),
|
||||
# AND the median improvement exceeds 2% of persistence error (not noise-width).
|
||||
rng = np.random.default_rng(42)
|
||||
diffs = e_persist - e_linear # >0 means linear better
|
||||
n = len(diffs)
|
||||
boot = np.array([rng.choice(diffs, n, replace=True).mean() for _ in range(2000)])
|
||||
lo, hi = np.percentile(boot, [2.5, 97.5])
|
||||
improve_frac = (P - L) / P
|
||||
linear_beats = (lo > 0) and (improve_frac > 0.02)
|
||||
|
||||
# How predictable is the field at all? (persistence error relative to frame-to-frame scale)
|
||||
# scale = typical magnitude of a z-scored frame's per-step change baseline
|
||||
field_change = frame_err(Fz[1:], Fz[:-1]).mean() # avg step-to-step change
|
||||
|
||||
lines = []
|
||||
def out(s): print(s, flush=True); lines.append(s)
|
||||
|
||||
out("="*64)
|
||||
out("PASSIVE PREDICT-TEST — live 5561 coarse field")
|
||||
out("="*64)
|
||||
out(f"frames used: {T} (cycles {C[0]} -> {C[-1]}, ~{(C[-1]-C[0])} cycles)")
|
||||
out(f"channels: {CH_NAMES}")
|
||||
out("")
|
||||
out("Per-step RMSE (z-scored field, lower = better predictor):")
|
||||
out(f" PERSISTENCE (next=current): {P:.4f} <- baseline to beat")
|
||||
out(f" LINEAR (const-velocity extrap): {L:.4f}")
|
||||
out(f" RUNNING MEAN (dumb global): {M:.4f}")
|
||||
out("")
|
||||
out("PRE-COMMITTED TEST: does LINEAR reliably beat PERSISTENCE?")
|
||||
out(f" improvement: {improve_frac*100:+.2f}% (need > +2.00%)")
|
||||
out(f" bootstrap 95% CI of (persist_err - linear_err): [{lo:+.4f}, {hi:+.4f}] (need lo > 0)")
|
||||
out(f" VERDICT: {'LINEAR BEATS PERSISTENCE — field carries short-horizon dynamics' if linear_beats else 'NULL — no predictor beats persistence at threshold'}")
|
||||
out("")
|
||||
# Interpretation guardrails (from the doc, so the next reader doesn't over-read)
|
||||
if linear_beats:
|
||||
out("READ: The field's next state is better predicted by its trajectory than by")
|
||||
out("assuming it stays still. That means the live fast field carries structured")
|
||||
out("short-horizon motion the 9 global scalars could not show. This is the FIRST")
|
||||
out("positive signal that the live organism has learnable dynamics. It is NOT yet")
|
||||
out("memory (that needs the poke/history test). It IS a green light to proceed.")
|
||||
else:
|
||||
out("READ: Nothing beats 'it stays the same' at the committed threshold. The live")
|
||||
out("coarse field, at this resolution and horizon, is either near-static frame to")
|
||||
out("frame or its motion is not linearly predictable. A clean null. Do not rescue.")
|
||||
out("Next: try shorter tiles (finer grid) or the stress channels alone before")
|
||||
out("concluding the substrate has no fast structure.")
|
||||
|
||||
with open(OUT_REPORT, "w") as f:
|
||||
f.write("\n".join(lines) + "\n")
|
||||
|
||||
# Save for visualization: raw frames (un-normalized), cycles, and per-step errors
|
||||
np.savez(OUT_DATA,
|
||||
frames=F.astype(np.float32), cycles=C,
|
||||
e_persist=e_persist, e_linear=e_linear,
|
||||
ch_names=np.array(CH_NAMES))
|
||||
print(f"\nWrote {OUT_REPORT} and {OUT_DATA}", flush=True)
|
||||
print("DONE.", flush=True)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,113 @@
|
||||
#!/usr/bin/env python3
|
||||
# snapshot_uniqueness_test.py
|
||||
# Jason's claim: perturbations are indelible and unique — no two snapshots the same.
|
||||
# This tests the DIFFERENCE that matters:
|
||||
# (A) trivial: every frame differs from every other (chaos / non-repetition) -> NOT memory
|
||||
# (B) real: the DISTANCE between snapshots is STRUCTURED by history/time,
|
||||
# not just uniform noise -> memory-like
|
||||
#
|
||||
# Read-only on checkpoints. Computes pairwise field distances and asks:
|
||||
# - Are all snapshots unique? (yes/no, and by how much)
|
||||
# - Is the distance between two snapshots a FUNCTION of how far apart in cycles they are?
|
||||
# If distance grows with time-gap then plateaus -> the field "forgets" at a timescale (memory horizon).
|
||||
# If distance is flat/random vs gap -> unique but memoryless (just non-repeating).
|
||||
# - Do NEARBY-in-time snapshots stay more similar than FAR ones? (persistence of state)
|
||||
|
||||
import sys, io, glob, os, struct
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
|
||||
import numpy as np
|
||||
|
||||
NX=NY=1024; Q=9; HDR=64
|
||||
DIR = "/mnt/d/Resonance_Engine/pattern_vocab/exp_instance"
|
||||
if not os.path.isdir(DIR):
|
||||
DIR = r"D:\Resonance_Engine\pattern_vocab\exp_instance"
|
||||
|
||||
def read_header(fp):
|
||||
with open(fp,"rb") as f:
|
||||
h=f.read(HDR)
|
||||
if h[:4]!=b"KHRG": return None
|
||||
cyc=struct.unpack_from("<I",h,8)[0]
|
||||
omega=struct.unpack_from("<f",h,24)[0]
|
||||
khra=struct.unpack_from("<f",h,28)[0]
|
||||
gixx=struct.unpack_from("<f",h,32)[0]
|
||||
return dict(cycle=cyc,omega=omega,khra=khra,gixx=gixx,path=fp)
|
||||
|
||||
def load_rho_downsampled(fp, ds=16):
|
||||
# load f_data, compute density per cell, downsample by block-mean to (NX/ds)^2
|
||||
with open(fp,"rb") as f:
|
||||
f.read(HDR)
|
||||
arr=np.frombuffer(f.read(NX*NY*Q*4),dtype=np.float32).reshape(NY,NX,Q)
|
||||
rho=arr.sum(axis=2) # (NY,NX)
|
||||
n=NX//ds
|
||||
rho_d=rho.reshape(n,ds,n,ds).mean(axis=(1,3)) # (n,n)
|
||||
return rho_d
|
||||
|
||||
files=sorted(glob.glob(os.path.join(DIR,"*.bin")))
|
||||
print(f"Found {len(files)} checkpoints in {DIR}", flush=True)
|
||||
|
||||
# canonical-only, sorted by cycle
|
||||
meta=[read_header(fp) for fp in files]
|
||||
meta=[m for m in meta if m and abs(m['omega']-1.97)<0.01 and abs(m['khra']-0.03)<0.001 and abs(m['gixx']-0.008)<0.001]
|
||||
meta.sort(key=lambda m:m['cycle'])
|
||||
print(f"Canonical checkpoints: {len(meta)}, cycles {meta[0]['cycle']}..{meta[-1]['cycle']}", flush=True)
|
||||
|
||||
# sample up to ~40 evenly to keep pairwise cost sane
|
||||
K=min(40,len(meta))
|
||||
idx=np.linspace(0,len(meta)-1,K).astype(int)
|
||||
sel=[meta[i] for i in idx]
|
||||
cyc=np.array([m['cycle'] for m in sel])
|
||||
|
||||
print(f"Loading {K} downsampled density fields...", flush=True)
|
||||
fields=np.array([load_rho_downsampled(m['path']).ravel() for m in sel]) # (K, n*n)
|
||||
|
||||
# --- 1. UNIQUENESS: is any pair identical? ---
|
||||
# normalize each field (remove mean drift so we test PATTERN not the mass-leak amplitude)
|
||||
fn = fields - fields.mean(axis=1, keepdims=True)
|
||||
fn = fn / (fn.std(axis=1, keepdims=True)+1e-12)
|
||||
|
||||
D=np.zeros((K,K))
|
||||
for i in range(K):
|
||||
for j in range(K):
|
||||
D[i,j]=np.sqrt(np.mean((fn[i]-fn[j])**2))
|
||||
offdiag=D[np.triu_indices(K,1)]
|
||||
print("\n=== UNIQUENESS ===", flush=True)
|
||||
print(f" min pairwise distance (excluding self): {offdiag.min():.4f}", flush=True)
|
||||
print(f" are any two snapshots identical? {'NO — all unique' if offdiag.min()>1e-6 else 'YES some identical'}", flush=True)
|
||||
print(f" distance range: {offdiag.min():.3f} .. {offdiag.max():.3f} mean {offdiag.mean():.3f}", flush=True)
|
||||
|
||||
# --- 2. IS DISTANCE A FUNCTION OF TIME-GAP? (the memory-vs-chaos discriminator) ---
|
||||
gaps=[]; dists=[]
|
||||
for i in range(K):
|
||||
for j in range(i+1,K):
|
||||
gaps.append(abs(cyc[i]-cyc[j])); dists.append(D[i,j])
|
||||
gaps=np.array(gaps); dists=np.array(dists)
|
||||
# correlation of distance with time-gap
|
||||
r=np.corrcoef(gaps,dists)[0,1]
|
||||
print("\n=== STRUCTURE OF DISTANCE vs TIME-GAP ===", flush=True)
|
||||
print(f" correlation(time_gap, field_distance) = {r:+.3f}", flush=True)
|
||||
print(" (near 0 = distance unrelated to time = unique-but-memoryless chaos;", flush=True)
|
||||
print(" strongly >0 = closer-in-time stays more similar = STATE PERSISTS = memory-like)", flush=True)
|
||||
|
||||
# binned: mean distance at small gap vs large gap
|
||||
order=np.argsort(gaps)
|
||||
q=len(gaps)//4
|
||||
near=dists[order[:q]].mean(); far=dists[order[-q:]].mean()
|
||||
print(f" mean distance, NEAREST-in-time quartile: {near:.3f}", flush=True)
|
||||
print(f" mean distance, FARTHEST-in-time quartile: {far:.3f}", flush=True)
|
||||
print(f" ratio far/near: {far/max(near,1e-9):.2f}x", flush=True)
|
||||
|
||||
print("\n=== VERDICT ===", flush=True)
|
||||
unique = offdiag.min()>1e-6
|
||||
persists = (r>0.3) and (far/max(near,1e-9) > 1.15)
|
||||
if unique and persists:
|
||||
print(" UNIQUE *and* STRUCTURED: every snapshot differs, AND closer-in-time snapshots", flush=True)
|
||||
print(" are more similar than distant ones. The field's state PERSISTS and drifts —", flush=True)
|
||||
print(" this is memory-like: where it is now depends on where it was. Jason's read is supported.", flush=True)
|
||||
elif unique and not persists:
|
||||
print(" UNIQUE but NOT time-structured: every snapshot differs, but distance is ~unrelated", flush=True)
|
||||
print(" to time-gap. That is non-repetition (chaos), NOT memory. Uniqueness alone != memory.", flush=True)
|
||||
else:
|
||||
print(" Some snapshots near-identical — investigate before concluding.", flush=True)
|
||||
|
||||
np.savez("snapshot_distance.npz", D=D, cyc=cyc, gaps=gaps, dists=dists)
|
||||
print("\nWrote snapshot_distance.npz. DONE.", flush=True)
|
||||
Reference in New Issue
Block a user