SlapStackStudio / studio /studio.html
Aluode's picture
Upload 18 files
e38621f verified
Raw
History Blame Contribute Delete
109 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Slapstack Studio — spawn objects, move them, in Gabor space</title>
<style>
:root {
--chassis:#1d222e; --panel:#262c3b; --panel-edge:#333b4f; --well:#10131b;
--ink:#e3e7f0; --muted:#8d96ab; --phosphor:#ffb454; --outlier:#78808f;
--entropy:#c86bff; --go:#7fd98a; --bad:#ff7a7a;
--mono: ui-monospace,"SF Mono","Cascadia Mono",Menlo,Consolas,monospace;
--cond:"Bahnschrift","Arial Narrow","Roboto Condensed",sans-serif;
}
*{box-sizing:border-box;margin:0}
html,body{height:100%}
body{background:var(--chassis);color:var(--ink);font-family:var(--mono);
font-size:13px;display:flex;flex-direction:column;overflow:hidden}
@media (prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}
header{display:flex;align-items:baseline;gap:14px;padding:10px 16px 9px;
border-bottom:1px solid var(--panel-edge);flex-wrap:wrap}
header h1{font-family:var(--cond);font-size:20px;letter-spacing:2.5px;font-weight:700}
header h1 em{color:var(--phosphor);font-style:normal}
header .claim{color:var(--muted);font-size:11.5px}
#state-lamp{margin-left:auto;font-size:11px;letter-spacing:1.5px;padding:3px 10px;
border-radius:3px;border:1px solid var(--panel-edge);color:var(--muted)}
#state-lamp.soup{color:var(--outlier);border-color:var(--outlier)}
#state-lamp.binding{color:var(--entropy);border-color:var(--entropy)}
#state-lamp.bound{color:var(--go);border-color:var(--go)}
main{flex:1;display:flex;gap:12px;padding:12px 16px;min-height:0}
#stage-wrap{flex:1;display:flex;align-items:center;justify-content:center;min-width:0}
#stage{position:relative;background:var(--well);border:1px solid var(--panel-edge);
border-radius:6px;box-shadow:inset 0 0 60px rgba(0,0,0,.55)}
#stage canvas{position:absolute;inset:0;width:100%;height:100%;border-radius:6px}
#overlay-canvas{cursor:grab}
#overlay-canvas.dragging{cursor:grabbing}
#stage .hint{position:absolute;bottom:7px;left:0;right:0;text-align:center;
color:var(--muted);font-size:10.5px;pointer-events:none;text-shadow:0 1px 3px #000}
aside{width:330px;flex-shrink:0;display:flex;flex-direction:column;gap:9px;
overflow-y:auto;padding-right:2px}
.card{background:var(--panel);border:1px solid var(--panel-edge);border-radius:6px;
padding:10px 11px}
.card h2{font-family:var(--cond);font-size:12px;letter-spacing:2px;font-weight:700;
color:var(--muted);margin-bottom:8px}
button{font-family:var(--mono);font-size:12px;background:var(--well);color:var(--ink);
border:1px solid var(--panel-edge);border-radius:4px;padding:6px 11px;cursor:pointer}
button:hover{border-color:var(--phosphor);color:var(--phosphor)}
button:focus-visible{outline:2px solid var(--phosphor);outline-offset:1px}
button:disabled{opacity:.35;cursor:default}
button:disabled:hover{border-color:var(--panel-edge);color:var(--ink)}
button.primary{border-color:var(--phosphor);color:var(--phosphor)}
.row{display:flex;gap:7px;align-items:center}
.row+.row{margin-top:7px}
.readout{color:var(--phosphor)} .dim{color:var(--muted)} .spacer{flex:1}
#bind-meter{height:4px;background:var(--well);border-radius:2px;overflow:hidden;margin-top:8px}
#bind-meter>div{height:100%;width:0%;background:var(--entropy)}
#metrics{display:grid;grid-template-columns:1fr 1fr;gap:3px 10px;margin-top:8px;font-size:11.5px}
.layer{border-top:1px solid var(--panel-edge);padding:7px 0;border-radius:3px}
.layer:first-of-type{border-top:0}
.layer.sel{outline:1px solid var(--phosphor);outline-offset:2px}
.layer .name{display:flex;align-items:center;gap:7px;cursor:pointer}
.swatch{width:10px;height:10px;border-radius:2px;flex-shrink:0}
.layer .pose{color:var(--muted);font-size:10.5px;margin:4px 0 5px;min-height:13px}
.belief-bar{flex:1;height:5px;background:var(--well);border-radius:3px;overflow:hidden;max-width:80px}
.belief-bar>div{height:100%;background:var(--go)}
.layer .ctl{display:flex;gap:4px;flex-wrap:wrap;margin-top:5px}
.layer .ctl button{padding:3px 7px;font-size:10.5px}
#library{display:flex;gap:6px;flex-wrap:wrap;margin-top:6px}
.chip{display:flex;align-items:center;gap:5px;background:var(--well);
border:1px solid var(--panel-edge);border-radius:12px;padding:3px 9px;font-size:11px}
.chip button{border:0;padding:0 2px;font-size:12px;background:none}
label.tog{display:flex;gap:8px;align-items:center;cursor:pointer;padding:2px 0;font-size:12px}
label.tog input{accent-color:var(--phosphor)}
input[type=range]{flex:1;accent-color:var(--phosphor)}
.gain-row{display:grid;grid-template-columns:54px 1fr 40px;gap:6px;align-items:center;margin-top:3px}
.gain-row input[type=range]{width:100%;margin:0}
.gain-row .val{text-align:right}
input[type=color]{width:36px;height:22px;padding:1px;border:1px solid var(--panel-edge);background:var(--well);border-radius:4px;cursor:pointer}
#stage.erasing #overlay-canvas{cursor:crosshair}
input[type=text],textarea{font-family:var(--mono);font-size:12px;background:var(--well);
color:var(--ink);border:1px solid var(--panel-edge);border-radius:4px;padding:6px 8px;width:100%}
input[type=text]:focus-visible,textarea:focus-visible{outline:2px solid var(--phosphor);border-color:transparent}
#spawn-status,#prompt-log{color:var(--muted);font-size:10.5px;margin-top:6px;
min-height:13px;white-space:pre-wrap}
#ledger p{font-size:10.5px;line-height:1.5;color:var(--muted);margin-bottom:6px}
#ledger b{color:var(--go);font-weight:400}
#ledger .no{color:var(--entropy)}
details summary{cursor:pointer;font-family:var(--cond);font-size:12px;letter-spacing:2px;color:var(--muted)}
details[open] summary{margin-bottom:7px}
input[type=file]{font-size:10.5px;color:var(--muted);max-width:170px}
@media (max-width:900px){main{flex-direction:column;overflow-y:auto}body{overflow:auto}aside{width:100%}}
</style>
</head>
<body>
<header>
<h1>SLAPSTACK <em>STUDIO</em></h1>
<span class="claim">spawn layers with AI &middot; scramble &middot; bind &middot; grab &middot; stack &middot; every entity is a posterior</span>
<span id="state-lamp" class="soup">ATOM SOUP</span>
</header>
<main>
<div id="stage-wrap">
<div id="stage">
<canvas id="scene-canvas"></canvas>
<canvas id="overlay-canvas"></canvas>
<div class="hint">drag = pose clamp &middot; wheel rotate &middot; shift+wheel scale &middot; shift+click = hint &middot; erase tool: brush deletes atoms</div>
</div>
</div>
<aside>
<div class="card">
<h2>SCENE</h2>
<div class="row">
<button id="btn-scramble">Scramble</button>
<button id="btn-bind" class="primary">Bind</button>
<span class="spacer"></span>
<span class="dim">noise</span>
<input type="range" id="noise" min="0" max="10" value="1" style="width:60px" aria-label="observation noise">
</div>
<div class="row">
<span class="dim">composite</span>
<label class="tog"><input type="radio" name="comp" value="field" checked> field</label>
<label class="tog"><input type="radio" name="comp" value="painter"> painter (depth)</label>
<span class="spacer"></span>
<label class="tog"><input type="checkbox" id="auto-occ" checked> depth occlusion</label>
</div>
<div id="bind-meter"><div></div></div>
<div id="metrics">
<span class="dim">binding vs truth</span><span class="readout" id="m-acc"></span>
<span class="dim">mean entropy</span><span class="readout" id="m-ent"></span>
<span class="dim">BP iterations</span><span class="readout" id="m-it"></span>
<span class="dim">atoms in scene</span><span class="readout" id="m-n"></span>
</div>
</div>
<div class="card">
<h2>EDIT &amp; LOOK</h2>
<div class="row">
<span class="dim">tool</span>
<label class="tog"><input type="radio" name="tool" value="pose" checked> pose</label>
<label class="tog"><input type="radio" name="tool" value="erase"> erase</label>
<span class="spacer"></span>
<span class="dim">brush</span>
<label class="tog"><input type="radio" name="brush" value="0.05"> S</label>
<label class="tog"><input type="radio" name="brush" value="0.12" checked> M</label>
<label class="tog"><input type="radio" name="brush" value="0.25"> L</label>
</div>
<div class="row">
<button id="btn-undo-erase" disabled>Undo erase</button>
<button id="btn-restore-erase" disabled>Restore all</button>
<label class="tog"><input type="checkbox" id="erase-burn"> burn into template</label>
</div>
<div class="row">
<span class="dim">backdrop</span>
<input type="color" id="bg-color" value="#808080" title="backdrop color">
<input type="file" id="bg-file" accept="image/*" style="max-width:46%">
<button id="btn-bg-clear">clear img</button>
</div>
<div class="gain-row"><span class="dim">coarse</span><input type="range" id="g-coarse" min="0" max="200" value="100"><span class="val readout" id="g-coarse-v">1.00</span></div>
<div class="gain-row"><span class="dim">mid</span><input type="range" id="g-mid" min="0" max="200" value="100"><span class="val readout" id="g-mid-v">1.00</span></div>
<div class="gain-row"><span class="dim">fine</span><input type="range" id="g-fine" min="0" max="200" value="100"><span class="val readout" id="g-fine-v">1.00</span></div>
<div class="gain-row"><span class="dim">opacity</span><input type="range" id="g-op" min="0" max="200" value="100"><span class="val readout" id="g-op-v">1.00</span></div>
<div class="dim" style="font-size:10.5px;margin-top:6px">Erase deletes atoms under the brush (render only, until <b>burn</b> writes it into the layer template; Scramble/Bind rebuild the scene from templates). Undo works until the next Bind. Backdrop is presentation and survives everything. Band gains (coarse&lt;4, mid 4&ndash;10, fine&gt;10&nbsp;c/img) scale atom colors in field mode; opacity blends the whole field over the backdrop.</div>
</div>
<div class="card">
<h2>LAYERS <span class="dim" style="letter-spacing:0">(front is highest)</span></h2>
<div id="layer-list"></div>
<div class="dim" style="font-size:10.5px;margin-top:6px">library — place to add to the scene:</div>
<div id="library"></div>
</div>
<div class="card">
<h2>SPAWN A LAYER</h2>
<div class="row">
<input type="text" id="spawn-prompt" placeholder='prompt, e.g. "a red rocket, flat colors"'>
</div>
<div class="row">
<input type="text" id="spawn-token" placeholder="matte token (e.g. rocket)" style="width:55%">
<button id="btn-spawn-text">Distill (GPU)</button>
</div>
<div class="row">
<input type="file" id="spawn-file" accept="image/*">
<button id="btn-spawn-img">Fit (CPU)</button>
</div>
<div class="row">
<input type="text" id="spawn-json" placeholder="or paste layer JSON">
<button id="btn-spawn-json">Add</button>
</div>
<div id="spawn-status">Distill runs Stable Diffusion score distillation with a cross-attention matte (GPU Space only, untested — first run is a smoke test). Fit trains atoms to your image on CPU (~1 min; objects on plain mid-gray come out compact).</div>
</div>
<div class="card">
<h2>PROMPT AS INTERVENTION</h2>
<div class="row">
<input type="text" id="prompt" placeholder='e.g. "move the boat left, put the star behind the tractor"'>
<button id="btn-prompt">Do</button>
</div>
<div id="prompt-log">Commands become pose clamps and depth moves; BP re-settles. Free-form uses Claude when this runs inside claude.ai; a local grammar otherwise.</div>
</div>
<div class="card">
<h2>OVERLAYS</h2>
<label class="tog"><input type="checkbox" id="ov-atoms" checked> atoms, colored by belief</label>
<label class="tog"><input type="checkbox" id="ov-own"> ownership tint</label>
<label class="tog"><input type="checkbox" id="ov-ent"> entropy border glow</label>
<label class="tog"><input type="checkbox" id="ov-pose" checked> pose belief (crosshair + covariance)</label>
<label class="tog"><input type="checkbox" id="ov-hints" checked> semantic hints</label>
</div>
<div class="card" id="ledger">
<details>
<summary>HONEST LEDGER</summary>
<p><b>Verified in this build:</b> the BP core matches the SlapstackBet6 Python (transform 2e-16, render MSE 8.6e-8, identical binding); drag = pose clamp + re-settle; identity persists through occlusion; depth occlusion removes evidence honestly (marginals revert toward the prior, coverage releases when moved away); the semantic likelihood factor resolves the twins case (two identical stars: 0.05 acc on geometry alone, 0.99 with location priors) and a mildly wrong prior is overridden by evidence; image&rarr;layer fitting runs end to end on CPU. <b>Bet 9:</b> the eraser filters per-atom state (order-free, so undo is re-append), burn edits the template so erases survive rebuilds; the backdrop is pure presentation and persists across Bind/Scramble; band gains exploit render linearity (gain&middot;color &equiv; gain&middot;contribution), verified in tests_edit.js.</p>
<p><span class="no">Not verified:</span> text&rarr;layer (SDS) and cross-attention matte are line-for-line adaptations of the verified Bet-5 GPU loop but have NOT been executed on a GPU — first run is a smoke test; SD attention maps flicker across seeds, which is why they enter BP as a prior with a weight, never as truth. Real-photo scene&rarr;atoms remains open. Painter compositing and the alpha matte are presentation choices, not the additive field model.</p>
</details>
</div>
</aside>
</main>
<script>
/* Slapstack Playroom core — faithful JS port of SlapstackBet6 math.
FIELDS: [x, y, theta, su, sv, f, phase, r, g, b]
Verified against bet6_open.py / bet6_bp_binding.py / bet6_multimodal.py
by tests_node.js before being embedded in the playroom. */
"use strict";
const TAU = Math.PI * 2;
function wrapPi(d) {
return ((d + Math.PI) % TAU + TAU) % TAU - Math.PI;
}
function rotApply(rho, x, y) {
const c = Math.cos(rho), s = Math.sin(rho);
return [c * x - s * y, s * x + c * y];
}
/* Exact Sim(2) action on atom parameters (the Bet 5 algebra):
xy -> s R xy + t, theta -> theta + rho, sigma -> s sigma, f -> f/s.
Envelope-relative phase and color are INVARIANT. */
function transformAtoms(atoms, xi) {
const [tx, ty, rho, lam] = xi;
const s = Math.exp(lam);
const out = new Array(atoms.length);
for (let i = 0; i < atoms.length; i++) {
const a = atoms[i];
const [rx, ry] = rotApply(rho, a[0], a[1]);
out[i] = [
s * rx + tx, s * ry + ty,
a[2] + rho,
a[3] * s, a[4] * s,
a[5] / s,
a[6],
a[7], a[8], a[9],
];
}
return out;
}
/* Sim(2)-invariant intrinsic signature: identity lives here. */
function signature(atoms) {
return atoms.map(a => [
Math.log(a[3] * a[5]),
Math.log(a[3] / a[4]),
Math.cos(a[6]), Math.sin(a[6]),
a[7], a[8], a[9],
]);
}
/* Two pose-vote hypotheses per correspondence (pi-ambiguity fix):
H0: rho = d_theta, phi_obs == phi_tmpl
H1: rho = d_theta + pi, phi_obs == -phi_tmpl */
function poseVotes2pi(obs, tmpl, sigPhase = 0.35) {
const s = Math.pow((obs[3] / tmpl[3]) * (obs[4] / tmpl[4]) * (tmpl[5] / obs[5]), 1 / 3);
const dTheta = obs[2] - tmpl[2];
const out = [];
for (let H = 0; H < 2; H++) {
const rho = wrapPi(dTheta + H * Math.PI);
const phiExp = H === 0 ? tmpl[6] : -tmpl[6];
const dphi = wrapPi(obs[6] - phiExp);
const pc = -0.5 * dphi * dphi / (sigPhase * sigPhase);
const [rx, ry] = rotApply(rho, tmpl[0], tmpl[1]);
out.push([[obs[0] - s * rx, obs[1] - s * ry, rho, Math.log(s)], pc]);
}
return out;
}
/* ------- small dense linear algebra on 4x4 (row-major flat arrays) ------- */
function mat4Inv(m) {
// Gauss-Jordan, fine for well-conditioned SPD 4x4s here.
const a = m.map(r => r.slice());
const inv = [[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]];
for (let col = 0; col < 4; col++) {
let piv = col;
for (let r = col + 1; r < 4; r++)
if (Math.abs(a[r][col]) > Math.abs(a[piv][col])) piv = r;
[a[col], a[piv]] = [a[piv], a[col]];
[inv[col], inv[piv]] = [inv[piv], inv[col]];
const d = a[col][col];
for (let j = 0; j < 4; j++) { a[col][j] /= d; inv[col][j] /= d; }
for (let r = 0; r < 4; r++) {
if (r === col) continue;
const f = a[r][col];
for (let j = 0; j < 4; j++) { a[r][j] -= f * a[col][j]; inv[r][j] -= f * inv[col][j]; }
}
}
return inv;
}
function mat4Det(m) {
const a = m.map(r => r.slice());
let det = 1;
for (let col = 0; col < 4; col++) {
let piv = col;
for (let r = col + 1; r < 4; r++)
if (Math.abs(a[r][col]) > Math.abs(a[piv][col])) piv = r;
if (piv !== col) { [a[col], a[piv]] = [a[piv], a[col]]; det = -det; }
det *= a[col][col];
if (a[col][col] === 0) return 0;
for (let r = col + 1; r < 4; r++) {
const f = a[r][col] / a[col][col];
for (let j = col; j < 4; j++) a[r][j] -= f * a[col][j];
}
}
return det;
}
function mat4Vec(m, v) {
return [0,1,2,3].map(i => m[i][0]*v[0]+m[i][1]*v[1]+m[i][2]*v[2]+m[i][3]*v[3]);
}
function matAdd(A, B, wB = 1) {
return A.map((row, i) => row.map((x, j) => x + wB * B[i][j]));
}
/* Greedy mode-seeking init (angle-aware), port of _density_peaks. */
function densityPeaks(votes, weights, M, radius = 0.45) {
const scale = [0.15, 0.15, 0.30, 0.20];
const n = votes.length;
const dens = new Float64Array(n);
for (let i = 0; i < n; i++) {
let acc = 0;
for (let k = 0; k < n; k++) {
const d0 = (votes[k][0] - votes[i][0]) / scale[0];
const d1 = (votes[k][1] - votes[i][1]) / scale[1];
const d2 = wrapPi(votes[k][2] - votes[i][2]) / scale[2];
const d3 = (votes[k][3] - votes[i][3]) / scale[3];
acc += weights[k] * Math.exp(-0.5 * (d0*d0 + d1*d1 + d2*d2 + d3*d3));
}
dens[i] = acc;
}
const peaks = [];
const alive = new Uint8Array(n).fill(1);
for (let m = 0; m < M; m++) {
let best = -1, bestD = -Infinity;
for (let i = 0; i < n; i++)
if (alive[i] && dens[i] > bestD) { bestD = dens[i]; best = i; }
if (best < 0) break;
peaks.push(votes[best].slice());
for (let i = 0; i < n; i++) {
const dxy = Math.hypot(votes[i][0] - votes[best][0], votes[i][1] - votes[best][1]);
const dr = Math.abs(wrapPi(votes[i][2] - votes[best][2]));
if (dxy + dr <= radius) alive[i] = 0;
}
}
return peaks;
}
/* Loopy BP binding of obs atoms to K templates at unknown poses.
Port of bet6_open.bp_bind: candidates carry both pi-hypotheses,
cavity messages, damping, branch-aligned rotation fusion.
Options: clampPose — array of length K; if clampPose[k] is a pose xi,
object k's pose is held fixed (conditioning-as-intervention) and only
the assignment beliefs re-equilibrate around it. */
function bpBind(templates, obs, opts = {}) {
const iters = opts.iters ?? 40;
const damping = opts.damping ?? 0.5;
const cavity = opts.cavity ?? true;
const sigVar = opts.sigVar ?? 0.08;
const outLL = opts.outLL ?? -14.0;
const clampPose = opts.clampPose ?? null;
const hiddenMask = opts.hiddenMask ?? null; // per-atom: true = no evidence
const onIter = opts.onIter ?? null;
const K = templates.length;
const sigT = templates.map(signature);
const sigO = signature(obs);
const N = obs.length;
const Vdiag = [0.03 * 0.03, 0.03 * 0.03, 0.05 * 0.05, 0.05 * 0.05];
const Vinv = [[1/Vdiag[0],0,0,0],[0,1/Vdiag[1],0,0],[0,0,1/Vdiag[2],0],[0,0,0,1/Vdiag[3]]];
const Vmat = [[Vdiag[0],0,0,0],[0,Vdiag[1],0,0],[0,0,Vdiag[2],0],[0,0,0,Vdiag[3]]];
const P0inv = [[1e-2,0,0,0],[0,1e-2,0,0],[0,0,1e-2,0],[0,0,0,1e-2]];
// ---- candidate generation: 3 signature-nearest per template, 2 hypotheses
const cands = [], votes = [], base = [];
for (let i = 0; i < N; i++) {
const c = [], v = [], b = [];
if (!(hiddenMask && hiddenMask[i])) {
for (let k = 0; k < K; k++) {
const d2 = sigT[k].map(st => {
let acc = 0;
for (let q = 0; q < 7; q++) { const d = st[q] - sigO[i][q]; acc += d * d; }
return acc;
});
const order = d2.map((d, j) => [d, j]).sort((p, q) => p[0] - q[0]).slice(0, 3);
for (const [dj, j] of order) {
for (const [xi, pc] of poseVotes2pi(obs[i], templates[k][j])) {
c.push([k, j]); v.push(xi); b.push(-0.5 * dj / sigVar + pc);
}
}
}
}
cands.push(c); votes.push(v); base.push(b);
}
// ---- beliefs seeded from the identity+phase channel
let B = [];
for (let i = 0; i < N; i++) {
const ll = base[i].concat([outLL]);
const mx = Math.max(...ll);
let e = ll.map(x => Math.exp(x - mx));
const s = e.reduce((a, x) => a + x, 0);
B.push(e.map(x => x / s));
}
// ---- pose init: density peak of each object's votes (or the clamp)
let mu = [];
for (let k = 0; k < K; k++) {
if (clampPose && clampPose[k]) { mu.push(clampPose[k].slice()); continue; }
const vk = [], wk = [];
for (let i = 0; i < N; i++)
for (let ci = 0; ci < cands[i].length; ci++)
if (cands[i][ci][0] === k) { vk.push(votes[i][ci]); wk.push(B[i][ci]); }
mu.push(vk.length ? densityPeaks(vk, wk, 1)[0] : [0, 0, 0, 0]);
}
let Sig = mu.map(() => [[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]]);
for (let it = 0; it < iters; it++) {
// pose fusion
const Lam = [], eta = [];
for (let k = 0; k < K; k++) { Lam.push(P0inv.map(r => r.slice())); eta.push([0,0,0,0]); }
for (let i = 0; i < N; i++) {
for (let ci = 0; ci < cands[i].length; ci++) {
const k = cands[i][ci][0];
const v = votes[i][ci].slice();
v[2] = mu[k][2] + wrapPi(v[2] - mu[k][2]);
const w = B[i][ci];
Lam[k] = matAdd(Lam[k], Vinv, w);
const Vv = mat4Vec(Vinv, v);
for (let q = 0; q < 4; q++) eta[k][q] += w * Vv[q];
}
}
Sig = Lam.map(mat4Inv);
for (let k = 0; k < K; k++) {
if (clampPose && clampPose[k]) {
mu[k] = clampPose[k].slice();
Sig[k] = [[1e-6,0,0,0],[0,1e-6,0,0],[0,0,1e-6,0],[0,0,0,1e-6]];
} else {
mu[k] = mat4Vec(Sig[k], eta[k]);
mu[k][2] = wrapPi(mu[k][2]);
}
}
// assignment update with cavity
const newB = [];
for (let i = 0; i < N; i++) {
const nc = cands[i].length;
const ll = new Array(nc + 1);
for (let ci = 0; ci < nc; ci++) {
const k = cands[i][ci][0];
const v = votes[i][ci].slice();
v[2] = mu[k][2] + wrapPi(v[2] - mu[k][2]);
let mC, Sk;
if (cavity && !(clampPose && clampPose[k])) {
const Lc = matAdd(Lam[k], Vinv, -B[i][ci]);
const Vv = mat4Vec(Vinv, v);
const ecav = [0,1,2,3].map(q => eta[k][q] - B[i][ci] * Vv[q]);
const Sc = mat4Inv(Lc);
mC = mat4Vec(Sc, ecav);
Sk = Sc;
} else {
mC = mu[k]; Sk = Sig[k];
}
const r = [v[0] - mC[0], v[1] - mC[1], wrapPi(v[2] - mC[2]), v[3] - mC[3]];
const Cov = matAdd(Sk, Vmat, 1);
const Ci = mat4Inv(Cov);
const Cr = mat4Vec(Ci, r);
const quad = r[0]*Cr[0] + r[1]*Cr[1] + r[2]*Cr[2] + r[3]*Cr[3];
ll[ci] = base[i][ci] - 0.5 * quad - 0.5 * Math.log(mat4Det(Cov));
}
ll[nc] = outLL;
const mx = Math.max(...ll);
let e = ll.map(x => Math.exp(x - mx));
const s = e.reduce((a, x) => a + x, 0);
e = e.map(x => x / s);
newB.push(e.map((x, q) => damping * x + (1 - damping) * B[i][q]));
}
B = newB;
if (onIter) onIter(it, marginals(), mu, Sig);
}
function marginals() {
const marg = [];
for (let i = 0; i < N; i++) {
if (cands[i].length === 0) {
// no evidence: assignment belief reverts to the prior (uniform),
// not to a confident "outlier" — this is what permanence means.
marg.push(new Array(K + 1).fill(1 / (K + 1)));
continue;
}
const m = new Array(K + 1).fill(0);
for (let ci = 0; ci < cands[i].length; ci++) m[cands[i][ci][0]] += B[i][ci];
m[K] = B[i][cands[i].length];
marg.push(m);
}
return marg;
}
return { marg: marginals(), mu, Sig, cands, votes, B };
}
/* Numpy-matching reference render (verification + full-res compositor).
pre[c] += color_c * env * carrier ; out = sigmoid(2 * pre).
Returns {pre: Float32Array(3*H*H)} pre-sigmoid field. */
function renderPre(atoms, H, pre) {
pre = pre || new Float32Array(3 * H * H);
const lim = 3.2; // envelope support cut, in sigmas
for (const a of atoms) {
const [ax, ay, th, su, sv, f, ph, r, g, b] = a;
const ct = Math.cos(th), st = Math.sin(th);
const rad = lim * Math.max(su, sv);
// pixel bbox: x in [-1,1] maps to col (H-1)*(x+1)/2
const x0 = Math.max(0, Math.floor((ax - rad + 1) / 2 * (H - 1)));
const x1 = Math.min(H - 1, Math.ceil((ax + rad + 1) / 2 * (H - 1)));
const y0 = Math.max(0, Math.floor((ay - rad + 1) / 2 * (H - 1)));
const y1 = Math.min(H - 1, Math.ceil((ay + rad + 1) / 2 * (H - 1)));
for (let py = y0; py <= y1; py++) {
const Y = -1 + 2 * py / (H - 1);
const dy = Y - ay;
for (let px = x0; px <= x1; px++) {
const X = -1 + 2 * px / (H - 1);
const dx = X - ax;
const u = ct * dx + st * dy;
const v = -st * dx + ct * dy;
const eArg = 0.5 * ((u / su) * (u / su) + (v / sv) * (v / sv));
if (eArg > lim * lim / 2 * 1.6) continue;
const env = Math.exp(-eArg);
const car = Math.cos(TAU * f * u + ph);
const ec = env * car;
const idx = py * H + px;
pre[idx] += r * ec;
pre[H * H + idx] += g * ec;
pre[2 * H * H + idx] += b * ec;
}
}
}
return pre;
}
function sigmoidField(pre, H, out) {
out = out || new Uint8ClampedArray(4 * H * H);
const n = H * H;
for (let i = 0; i < n; i++) {
out[4 * i] = 255 / (1 + Math.exp(-2 * pre[i]));
out[4 * i + 1] = 255 / (1 + Math.exp(-2 * pre[n + i]));
out[4 * i + 2] = 255 / (1 + Math.exp(-2 * pre[2 * n + i]));
out[4 * i + 3] = 255;
}
return out;
}
/* Ownership field: P(k|pixel) through the atoms' actual envelopes,
energy-weighted. Port of bet6_open.ownership_field. */
function ownershipField(obs, marg, K, H) {
const O = [];
for (let k = 0; k <= K; k++) O.push(new Float32Array(H * H));
const lim = 3.2;
for (let i = 0; i < obs.length; i++) {
const a = obs[i];
const energy = Math.hypot(a[7], a[8], a[9]);
const ct = Math.cos(a[2]), st = Math.sin(a[2]);
const rad = lim * Math.max(a[3], a[4]);
const x0 = Math.max(0, Math.floor((a[0] - rad + 1) / 2 * (H - 1)));
const x1 = Math.min(H - 1, Math.ceil((a[0] + rad + 1) / 2 * (H - 1)));
const y0 = Math.max(0, Math.floor((a[1] - rad + 1) / 2 * (H - 1)));
const y1 = Math.min(H - 1, Math.ceil((a[1] + rad + 1) / 2 * (H - 1)));
for (let py = y0; py <= y1; py++) {
const Y = -1 + 2 * py / (H - 1);
const dy = Y - a[1];
for (let px = x0; px <= x1; px++) {
const X = -1 + 2 * px / (H - 1);
const dx = X - a[0];
const u = ct * dx + st * dy;
const v = -st * dx + ct * dy;
const env = Math.exp(-0.5 * ((u / a[3]) ** 2 + (v / a[4]) ** 2));
const idx = py * H + px;
for (let k = 0; k <= K; k++) O[k][idx] += marg[i][k] * energy * env;
}
}
}
const P = O.map(() => new Float32Array(H * H));
const ent = new Float32Array(H * H);
const support = new Uint8Array(H * H);
for (let idx = 0; idx < H * H; idx++) {
let tot = 0;
for (let k = 0; k <= K; k++) tot += O[k][idx];
support[idx] = tot > 0.05 ? 1 : 0;
let e = 0;
for (let k = 0; k <= K; k++) {
const p = O[k][idx] / (tot + 1e-6);
P[k][idx] = p;
e -= p * Math.log2(p + 1e-12);
}
ent[idx] = e;
}
return { P, ent, support };
}
/* Assignment entropy per atom, in bits. */
function atomEntropy(marg) {
return marg.map(m => {
let e = 0;
for (const p of m) e -= p * Math.log2(p + 1e-12);
return e;
});
}
</script>
<script>
/* Slapstack Studio layer math — NEW mechanics on top of the verified core.
core.js is untouched; everything here is presentation-adjacent inference
plumbing, tested by tests_studio.js:
renderEnv per-layer envelope-energy buffer (for alpha + coverage)
coverageOf how much front-stack mass sits on each atom center
autoHidden coverage -> per-atom evidence mask (depth occlusion)
compositePainter back-to-front alpha compositing of layer buffers
composePose Sim(2) composition in vote coordinates [tx,ty,rho,lam]
*/
"use strict";
const ALPHA_C = 2.0; // alpha = 1 - exp(-ALPHA_C * bounded envelope energy)
const COVER_THRESH = 0.55; // atom loses evidence when this covered
/* Opacity of a layer = where it actually PAINTS: per-pixel deviation of
the pre-sigmoid field from neutral. Cancelling atom pairs (high energy,
zero sum) are correctly transparent — envelope-based opacity is not used
because it saturates on exactly those invisible pairs.
a = 1 - exp(-ALPHA_C * max(0, |preR|+|preG|+|preB| - FLOOR)). */
const ALPHA_FLOOR = 0.08;
function alphaFromPre(pre, H, out) {
out = out || new Float32Array(H * H);
const n = H * H;
for (let i = 0; i < n; i++) {
const m = Math.abs(pre[i]) + Math.abs(pre[n + i]) + Math.abs(pre[2 * n + i]);
out[i] = m > ALPHA_FLOOR ? 1 - Math.exp(-ALPHA_C * (m - ALPHA_FLOOR)) : 0;
}
return out;
}
/* coverage of each atom center by a front stack of alpha buffers:
cov_i = 1 - prod_front (1 - a_m(x_i)). */
function coverageOf(atoms, frontAlphaBufs, H) {
const out = new Float32Array(atoms.length);
for (let i = 0; i < atoms.length; i++) {
const px = Math.max(0, Math.min(H - 1, Math.round((atoms[i][0] + 1) / 2 * (H - 1))));
const py = Math.max(0, Math.min(H - 1, Math.round((atoms[i][1] + 1) / 2 * (H - 1))));
let keep = 1;
for (const buf of frontAlphaBufs) keep *= 1 - buf[py * H + px];
out[i] = 1 - keep;
}
return out;
}
/* Per-atom evidence mask from the depth stack.
alphaBufs aligned with layers. An atom of layer k is evidence-free if the
strictly-in-front stack covers it beyond COVER_THRESH, or its layer is
user-hidden. */
function autoHidden(obs, layerOf, layers, alphaBufs, H) {
const mask = new Array(obs.length).fill(false);
const covFrac = layers.map(() => 0);
const counts = layers.map(() => 0);
for (let i = 0; i < obs.length; i++) {
const k = layerOf[i];
if (k < 0) continue; // clutter: always evidenced
counts[k]++;
if (layers[k].hidden) { mask[i] = true; covFrac[k]++; continue; }
const front = [];
for (let m = 0; m < layers.length; m++)
if (m !== k && !layers[m].hidden && layers[m].depth > layers[k].depth)
front.push(alphaBufs[m]);
if (!front.length) continue;
const cov = coverageOf([obs[i]], front, H)[0];
if (cov > COVER_THRESH) { mask[i] = true; covFrac[k]++; }
}
for (let k = 0; k < layers.length; k++)
covFrac[k] = counts[k] ? covFrac[k] / counts[k] : 0;
return { mask, covFrac };
}
/* Painter compositing: layers back-to-front by depth.
rgbBufs[k]: Float32Array(3*H*H) pre-sigmoid; alphaBufs[k]: from
alphaFromPre. Base is mid-gray (sigmoid(0)). */
function compositePainter(order, rgbBufs, alphaBufs, H, out) {
out = out || new Uint8ClampedArray(4 * H * H);
const n = H * H;
const acc = new Float32Array(3 * n);
for (let i = 0; i < n; i++) { acc[i] = 127.5; acc[n + i] = 127.5; acc[2 * n + i] = 127.5; }
for (const k of order) {
const pre = rgbBufs[k], al = alphaBufs[k];
for (let i = 0; i < n; i++) {
const a = al[i];
if (a < 1e-3) continue;
acc[i] += a * (255 / (1 + Math.exp(-2 * pre[i])) - acc[i]);
acc[n + i] += a * (255 / (1 + Math.exp(-2 * pre[n + i])) - acc[n + i]);
acc[2 * n + i] += a * (255 / (1 + Math.exp(-2 * pre[2 * n + i])) - acc[2 * n + i]);
}
}
for (let i = 0; i < n; i++) {
out[4 * i] = acc[i]; out[4 * i + 1] = acc[n + i];
out[4 * i + 2] = acc[2 * n + i]; out[4 * i + 3] = 255;
}
return out;
}
/* Backdrop trim: whole-image fits carry large-sigma background pads that
cover the full frame; for OBJECT layers, drop atoms whose envelope is
wider than sigMax. HEURISTIC, honestly: it also kills any genuinely
large object parts. The untrimmed layer is kept in the library. */
function trimAtoms(atoms, sigMax = 0.35) {
const kept = atoms.filter(a => Math.max(a[3], a[4]) <= sigMax);
if (!kept.length) return atoms.slice();
// re-center xy so the trimmed set is a canonical template again
let mx = 0, my = 0;
for (const a of kept) { mx += a[0]; my += a[1]; }
mx /= kept.length; my /= kept.length;
return kept.map(a => { const b = a.slice(); b[0] -= mx; b[1] -= my; return b; });
}
/* ---- Bet 9 additions: eraser + backdrop + band gains (pure, testable) ---- */
/* Frequency band of an atom. f is cycles/unit; the frame spans 2 units, so
cycles-per-image = 2f. Bands follow the original splatstack dials:
coarse < 4 c/img, mid 4–10, fine > 10. Returns 0|1|2. */
const BAND_EDGES = [2.0, 5.0];
function bandOf(f) { return f < BAND_EDGES[0] ? 0 : f < BAND_EDGES[1] ? 1 : 2; }
/* Erase mask: true for atoms whose center falls inside the brush circle.
skip(i) can veto (e.g. atoms of hidden layers stay untouched). */
function eraseMask(obs, wx, wy, r, skip) {
const r2 = r * r;
return obs.map((a, i) => {
if (skip && skip(i)) return false;
const dx = a[0] - wx, dy = a[1] - wy;
return dx * dx + dy * dy < r2;
});
}
/* Composite an already-sigmoided field over a backdrop.
img: Uint8ClampedArray RGBA from sigmoidField/compositePainter (mutated).
alpha: Float32Array(H*H) from alphaFromPre of the (band-gained) pre field.
opacity: global gain on alpha, clamped to [0,1] per pixel.
bg: either [r,g,b] flat color or a Uint8ClampedArray(4*H*H) RGBA image.
Identity check: with alpha==1 everywhere or bg==the field itself the
output is unchanged; with opacity such that a==1 the field wins. */
function blendOverBackdrop(img, alpha, opacity, bg, H) {
const n = H * H;
const flat = !(bg && bg.length === 4 * n);
for (let i = 0; i < n; i++) {
const a = Math.min(1, alpha[i] * opacity);
const br = flat ? bg[0] : bg[4 * i],
bgr = flat ? bg[1] : bg[4 * i + 1],
bb = flat ? bg[2] : bg[4 * i + 2];
img[4 * i] = br + a * (img[4 * i] - br);
img[4 * i + 1] = bgr + a * (img[4 * i + 1] - bgr);
img[4 * i + 2] = bb + a * (img[4 * i + 2] - bb);
}
return img;
}
/* Sim(2) composition in vote coordinates: (g2 ∘ g1). */
function composePose(g2, g1) {
const s2 = Math.exp(g2[3]);
const c = Math.cos(g2[2]), s = Math.sin(g2[2]);
const wrap = d => ((d + Math.PI) % (2 * Math.PI) + 2 * Math.PI) % (2 * Math.PI) - Math.PI;
return [s2 * (c * g1[0] - s * g1[1]) + g2[0],
s2 * (s * g1[0] + c * g1[1]) + g2[1],
wrap(g1[2] + g2[2]), g1[3] + g2[3]];
}
function gestureTranslate(dx, dy) { return [dx, dy, 0, 0]; }
function gestureRotateAbout(c, drho) {
const co = Math.cos(drho), si = Math.sin(drho);
return [c[0] - (co * c[0] - si * c[1]), c[1] - (si * c[0] + co * c[1]), drho, 0];
}
function gestureScaleAbout(c, ds) {
const s = Math.exp(ds);
return [c[0] - s * c[0], c[1] - s * c[1], 0, ds];
}
</script>
<script>
const BUILTINS = {"tractor": [[-0.6824, -0.45011, 2.66975, 0.70848, 0.92606, 0.44334, 5.19345, -0.09237, 0.04058, -0.01663], [-0.08584, -0.04802, 0.61626, 0.49888, 0.51436, 0.28444, 2.98078, -0.10011, 0.19875, 0.39398], [0.74864, 0.1407, 2.20893, 0.54502, 0.36724, 0.58724, 1.0072, 0.08821, 0.17557, 0.02929], [0.16677, -0.78973, 0.94754, 0.31915, 0.39057, 0.59383, 1.7488, 0.04293, 0.29863, 0.19046], [0.13943, -0.4621, 0.34775, 0.43809, 0.50801, 0.30553, 2.8646, -0.17673, 0.21056, 0.20304], [-0.05499, 0.43338, 1.6514, 0.61284, 0.45687, 0.32725, 1.11694, 0.16899, -0.3567, -0.20526], [-0.34716, 0.78101, 1.2311, 0.74696, 0.43588, 0.47387, 4.34096, 0.2865, 0.32634, 0.40835], [0.38634, 0.15072, 1.59185, 0.38671, 0.80153, 0.2006, 3.93334, -0.038, 0.2346, 0.21658], [-0.24576, -0.6867, 1.53196, 0.713, 0.38647, 0.52652, 3.95442, 0.02936, -0.05187, 0.00121], [0.35205, 0.08337, 0.53545, 0.47834, 0.43423, 0.76246, 4.15911, -0.20329, -0.08114, 0.10186], [0.35364, -0.86473, 2.06911, 1.01878, 0.6701, 0.60244, 6.00887, -0.05083, 0.02103, 0.00718], [-0.46331, 0.69726, 1.8988, 0.44293, 0.3441, 0.55859, 4.08877, 0.28312, 0.04183, 0.30179], [0.73797, -0.72063, 2.42647, 0.56377, 0.93892, 0.57773, 0.71269, -0.00084, 0.10102, 0.07746], [-0.15339, 0.31724, 0.95726, 0.50801, 0.54256, 0.30858, 0.53938, 0.02523, -0.10313, -0.26048], [0.70309, 0.45386, -0.24644, 0.30297, 0.45741, 0.70925, 4.42917, -0.02503, 0.12105, 0.27342], [0.23943, -0.29863, 0.8422, 0.33233, 0.34051, 0.22202, 3.33993, -0.1503, 0.45788, 0.30974], [-0.56384, 0.25397, 2.42246, 0.96112, 1.21737, 0.23618, 4.36676, 0.05613, 0.13365, 0.11434], [-0.36512, -0.00686, 1.45209, 0.49457, 0.31707, 0.8374, 0.28444, 0.49811, 0.06634, -0.11866], [-0.03486, 0.01865, 1.8686, 0.4669, 0.33716, 0.43868, 0.29331, 0.29541, -0.03762, -0.31555], [0.62497, 0.27048, 2.18782, 0.3361, 0.40184, 0.5221, 5.30031, -0.00354, -0.13067, -0.12973], [-0.41317, 0.11105, 0.67898, 0.48606, 0.59036, 0.28682, 5.49711, -0.10229, -0.22111, -0.29212], [-0.27571, -0.1504, 1.77764, 0.69312, 0.48048, 0.49554, 0.54236, 0.18581, 0.06243, -0.04552], [0.4641, 0.77883, 2.26806, 0.4379, 1.14871, 0.39349, 4.6492, -0.06707, -0.03793, 0.11233], [-0.56653, -0.8675, 1.28471, 0.68397, 0.3365, 0.29354, 1.07811, 0.03291, 0.04284, 0.04632], [-0.08734, 0.59009, 1.70998, 0.5352, 0.32565, 0.59399, 5.31167, -0.02259, 0.03768, 0.00686], [-0.00891, -0.13649, 0.47363, 0.39481, 0.42164, 0.37599, 6.05673, 0.05368, -0.38657, -0.22384], [0.36102, -0.14155, 1.21607, 0.16981, 0.19346, 0.64559, 0.12328, 0.14206, -0.0136, -0.00415], [-0.45032, 0.49812, 2.35641, 0.38137, 0.27461, 0.56179, 6.26456, -0.56531, -0.34132, -0.1892], [0.66365, 0.91126, 2.85526, 0.78185, 0.62224, 0.62045, 4.89805, 0.05002, -0.02027, -0.00306], [-0.78133, -0.49309, 2.58298, 0.49602, 0.94653, 0.35357, 5.84621, -0.0226, -0.05471, 0.01001], [-0.23984, 0.22701, 2.64843, 0.37651, 0.42961, 0.25471, 5.58736, -0.05288, -0.16166, -0.17766], [0.55904, 0.56313, 0.8993, 0.30673, 0.17036, 0.47454, 0.48998, -0.20619, -0.03915, -0.40454], [-0.51254, 0.78367, -0.03254, 0.35668, 0.5844, 0.4059, 5.63151, -0.02093, -0.4402, -0.38576], [-0.49183, 0.16821, -0.10355, 0.13637, 0.01697, 1.68471, 6.20816, -2.19706, -0.06284, -0.29033], [-0.75865, -0.37915, 3.63614, 0.10401, 0.17838, 2.60281, 3.56605, 0.09073, -0.11981, -0.17238], [0.17649, -0.18151, 3.10036, 0.11139, 0.32721, 0.96393, 0.67937, 0.28827, -0.27353, -0.49037], [-0.48705, 0.35371, 1.2931, 0.24513, 0.16342, 2.2225, 1.28356, -1.2217, -0.46139, -0.37179], [-0.01754, 0.55558, 2.53858, 0.27124, 0.13969, 1.24224, 3.99958, 0.01855, -0.10741, -0.03076], [-0.25695, 0.4068, 2.89412, 0.23868, 0.29239, 1.08662, 1.92178, 0.45239, 0.34315, 0.5327], [-0.23956, 0.25592, 2.23383, 0.04539, 0.10912, 3.7646, 2.57708, 5.02682, 0.98909, 0.47807], [0.628, -0.22848, 3.15552, 0.00944, 0.50113, 4.66656, 0.93257, -0.90353, 1.54592, 1.79618], [0.49409, 0.54229, 2.89216, 0.21768, 0.21806, 0.8736, 6.24187, -0.72302, -0.646, -0.51336], [-0.47663, 0.8961, 1.59818, 0.08723, 0.12112, 3.82644, 3.48794, -0.86773, -1.11731, -1.16287], [-0.71023, 0.2454, 1.27084, 0.22689, 0.22869, 1.32049, 1.42892, 0.24752, 0.09838, 0.2392], [-0.6411, -0.32881, 2.22783, 0.10306, 0.05474, 2.24301, 1.94946, -0.72905, 0.94648, 1.07426], [-0.10236, 0.0571, 2.16689, 0.11368, 0.21031, 1.10909, 2.59632, -0.30966, 0.21218, 0.25668], [0.40293, 0.14539, 3.149, 0.16438, 0.29952, 0.80921, 0.18805, 0.0656, -0.28125, -0.43057], [-0.33196, 0.22949, 2.05313, 0.13608, 0.28771, 1.73023, 4.73087, -0.82913, 0.45633, 0.43345], [0.22227, 0.09197, 1.3324, 0.09975, 0.09312, 1.08971, 3.3524, -0.11906, 0.54156, 0.12403], [0.11793, 0.39258, 1.5849, 0.06849, 0.14492, 4.87662, 2.85658, -0.79292, 1.02084, 1.32427], [0.52823, -0.67727, 0.21753, 0.11492, 0.15154, 1.38408, 3.847, -0.26577, 0.63177, 0.75418], [0.59787, -0.90371, 2.38042, 0.07061, 0.07692, 3.31604, 3.69185, 0.2139, -0.53046, -0.54509], [-0.10296, -0.98696, 0.9158, 0.28364, 0.09523, 3.94613, 1.23748, 0.07247, -0.17598, -0.20063], [-0.42743, 0.12942, 1.74352, 0.02628, 0.13178, 2.05194, 3.66078, -1.69632, 0.02142, 0.37604], [0.43108, 0.31065, 1.26171, 0.04935, 0.11457, 3.65908, 0.71281, 1.81829, -0.01638, -0.1652], [-0.64551, 0.15029, 2.79823, 0.10818, 0.0286, 1.30001, 0.2492, 1.64158, -0.61879, -0.63809], [-0.2631, -0.22718, 1.08716, 0.14952, 0.32458, 1.25624, 6.07547, 0.25972, -0.30185, -0.5652], [0.64946, -0.76278, 2.69333, 0.15504, 0.25941, 1.33319, 3.44865, 0.01556, -0.14194, -0.15755], [-0.07011, 0.47643, 0.05323, 0.05179, 0.10503, 2.85549, 4.88342, 0.95629, 1.01843, 1.2136], [0.51219, 0.76466, 1.61491, 0.06949, 0.12168, 2.3771, 0.83677, -1.79585, -1.71946, -1.62417], [-0.65308, -0.0253, 0.05561, 0.12108, 0.24282, 1.96344, 2.99588, -0.63173, 0.8612, 0.96971], [-0.75393, 0.6394, -0.58857, 0.07367, 0.13364, 1.96551, 5.57205, -1.79448, -1.70385, -1.79319], [0.67947, 0.7066, 0.76501, 0.05174, 0.08365, 3.63395, 3.78731, 2.9312, 2.8337, 2.84961], [0.78374, -0.85168, 2.90191, 0.08335, 0.05401, 1.72051, 3.89837, 0.067, -0.14074, -0.13737], [-0.65644, 0.48538, 2.2971, 0.07061, 0.13933, 1.6288, 5.87677, -0.28017, -0.3744, -0.77021], [-0.07299, -0.56282, 2.95749, 0.18481, 0.15234, 1.14593, 4.68782, -0.06461, 0.37184, 0.25643], [0.11921, -0.59593, 2.97536, 0.28076, 0.2065, 0.65973, 0.82948, 0.25906, -0.57613, -0.79168], [-0.15672, 0.40291, -0.49942, 0.09191, 0.12886, 2.13207, 3.86166, 2.41648, 0.83947, 0.87012], [-0.41411, 0.13279, 0.71077, 0.13767, 0.22816, 1.17284, 4.23561, -0.01769, 0.48793, 0.43909], [0.47887, 0.44707, 1.43521, 0.23495, 0.19113, 1.11754, 5.76585, -0.91451, -0.41865, -0.21804], [0.2231, 0.39557, 0.52648, 0.34275, 0.29421, 1.20222, 0.59739, 0.44874, 0.14287, 0.14493], [-0.07979, -0.47271, 2.52165, 0.37694, 0.05142, 2.13301, 4.86104, -0.25241, 0.37106, 0.44839], [-0.06135, -0.67232, 0.00337, 0.06471, 0.16861, 2.13669, 2.27478, -0.64686, 1.07496, 1.29683], [-0.85238, 0.43497, 0.10646, 0.19737, 0.14188, 2.26408, 0.61625, 0.72284, 0.51988, 0.7092], [-0.51825, 0.43221, -0.10887, 0.13855, 0.23206, 1.18669, 3.31322, 1.01161, 0.7205, 0.53151], [-0.12745, 0.61162, 0.59207, 0.07377, 0.20372, 3.15871, 1.16311, -0.81843, -0.967, -1.00712], [0.56756, -0.5616, 3.11854, 0.05498, 0.22625, 2.42556, 3.04, -0.24507, 0.9606, 0.9633], [0.42554, -0.29332, 3.22275, 0.14231, 0.29628, 1.05207, 0.46887, 0.32789, -0.28524, -0.58433], [0.6174, -0.22144, 3.13034, 0.0889, 0.28451, 1.9402, 1.46343, -0.21528, 0.40253, 0.44874], [0.38453, 0.49359, 3.2826, 0.29519, 0.09099, 3.12073, 2.76174, 0.91117, 0.85602, 0.87243], [0.2619, 0.34869, 2.75335, 0.10021, 0.12359, 1.57645, 2.52743, -0.44928, 0.49646, 0.50057], [0.16446, 0.27981, 1.25228, 0.11339, 0.22752, 2.15115, 5.9815, 0.7878, -0.54335, -0.68222], [0.13585, -0.71683, 2.32833, 0.05899, 0.23033, 1.45565, 3.1888, -0.16916, 0.51429, 0.6052], [0.16468, -0.60197, 3.15185, 0.07581, 0.16841, 1.76863, 6.14408, 0.08836, -0.70628, -0.68243], [-0.35743, -0.28283, 1.54047, 0.03937, 0.19287, 1.5841, 3.32628, -0.36208, 0.43891, 0.36518], [-0.62251, -0.38234, 2.61567, 0.15456, 0.32413, 1.04598, 2.40596, -0.06698, 0.0187, 0.04889], [0.79814, 0.37616, 2.43606, 0.18443, 0.12374, 2.91468, 5.89122, 0.37607, 0.34732, 0.36404], [0.51853, 0.51633, 1.9986, 0.1856, 0.16773, 0.71136, 2.96926, 1.181, 0.3687, 0.52399], [-0.85179, -0.45927, 1.21504, 0.16331, 0.12762, 2.08546, 4.38702, -0.06148, 0.04644, 0.05241], [0.82346, -0.30704, 3.00411, 0.11575, 0.11242, 0.90949, 2.33484, 0.08476, -0.0924, -0.16091], [0.27188, -0.88797, 1.57825, 0.03127, 0.2239, 2.98966, 2.26823, -0.58261, 1.38576, 1.67028], [-0.46132, -0.65263, 1.91629, 0.13799, 0.14213, 0.84698, 2.25948, -0.08674, -0.07736, -0.01761], [-0.0886, -0.07273, 0.12768, 0.10024, 0.26852, 1.98655, 3.07155, -0.14939, 0.18659, 0.28131], [0.27019, 0.29867, 0.82501, 0.1431, 0.0816, 3.81301, 1.75651, -0.80205, -0.04131, 0.0189], [0.00722, 0.14136, 0.27264, 0.09149, 0.26244, 1.54872, 3.31034, -0.3603, 0.23216, 0.43633], [-0.13459, 0.52946, 3.39189, 0.04356, 0.13784, 1.61563, 6.20546, -0.968, -0.99753, -0.75933], [0.04028, -0.67528, 3.17229, 0.15631, 0.14623, 2.75131, 2.57373, -0.08824, 0.54121, 0.48411], [0.39384, -0.52184, 3.20355, 0.12168, 0.17843, 0.94272, 0.41448, -0.02293, -0.64596, -0.4227], [0.62453, 0.47184, 2.91956, 0.16326, 0.21354, 1.26663, 2.48642, 0.30674, 0.3324, 0.37472], [0.55621, 0.38204, 1.82273, 0.09645, 0.21059, 1.67487, 2.48445, 0.84619, 0.66986, 0.67577], [-0.81937, 0.493, -0.0036, 0.05319, 0.0932, 2.92422, 2.67774, 2.59517, 2.63921, 2.22331], [-0.09406, -0.6307, 3.13683, 0.01255, 0.21603, 2.82704, 3.40586, 0.63554, -1.39215, -1.53166], [0.64661, 0.49075, 2.50093, 0.15143, 0.13703, 0.80325, 5.39367, -0.41876, -1.24179, -0.74772], [0.52089, 0.00283, 3.20765, 0.15302, 0.20652, 1.36938, 2.85748, -0.45495, 0.55844, 0.44882], [-0.74526, -0.0199, 3.14128, 0.01984, 0.22471, 4.63492, 4.49001, -2.32443, 3.06353, 3.78369], [-0.32624, 0.77647, 1.11274, 0.05544, 0.12966, 2.98644, 0.48049, -2.01201, -1.79998, -2.19936], [0.35253, 0.66839, 2.45969, 0.0704, 0.10646, 2.89173, 3.52159, 2.50863, 2.51213, 2.52348], [-0.35928, 0.52445, 0.30938, 0.27926, 0.20368, 0.67397, 0.64618, -0.68931, -0.71052, -0.44067], [0.26325, -0.73415, 1.56602, 0.14298, 0.22818, 1.3569, 3.30544, -0.26757, 0.49021, 0.37976], [0.586, 0.26585, 2.14199, 0.03439, 0.14258, 1.46884, 5.34673, 0.96545, -0.18662, 0.04527], [-0.43325, 0.67077, 1.4374, 0.34834, 0.21183, 0.64381, 0.78005, -1.10733, -1.12475, -0.93446], [0.60188, -0.86897, 0.58349, 0.06483, 0.08777, 3.37717, 3.87752, -0.28056, 0.65673, 0.6977], [-0.82983, -0.80561, 1.16311, 0.09632, 0.14492, 1.60901, 2.93708, -0.03843, 0.02064, 0.03901], [0.27384, -0.9034, 1.56931, 0.01144, 0.25121, 2.51038, 5.78922, 0.83105, -1.51501, -1.32257], [-0.17077, 0.69291, 1.04495, 0.08344, 0.08726, 2.63368, 1.62487, -0.56447, -0.58933, -0.59421], [0.61545, -0.21363, 0.00674, 0.02712, 0.44163, 3.80444, 4.43907, -1.05569, 1.55318, 1.85023], [-0.45204, -0.24833, 1.67065, 0.15145, 0.20898, 1.1456, 5.80945, 0.25417, -0.58287, -0.44938], [0.91085, 0.30058, 2.55159, 0.32394, 0.11989, 4.42473, 3.81485, -0.19905, -0.20616, -0.19025], [-0.01544, -0.44709, 2.87273, 0.09374, 0.05014, 2.55513, 3.60033, -0.121, 0.72357, 1.00287], [-0.51983, -0.20343, -0.26748, 0.06577, 0.16795, 1.95382, 2.6593, -0.33308, 0.46895, 0.3713], [0.26357, -0.76429, 1.54218, 0.15758, 0.22336, 1.04113, 2.74606, -0.2448, 0.28127, 0.71722], [-0.3753, -0.36271, 1.57935, 0.02425, 0.2412, 3.96846, 1.94527, -1.9547, 2.67872, 3.41142], [-0.54932, -0.16918, 0.31415, 0.12651, 0.14755, 0.72287, 0.30894, 0.4183, -0.36801, -0.68576], [-0.23035, -0.49408, 1.66203, 0.23955, 0.24268, 1.84685, 2.22684, 0.09834, -0.12612, -0.15135], [-0.30076, -1.01231, 0.68756, 0.22471, 0.22201, 1.60389, 0.8549, 0.01592, -0.08469, -0.10949], [-0.62797, 0.69828, 2.19777, 0.09535, 0.15567, 2.33143, 6.10749, -1.68191, -2.18704, -2.31563], [0.54776, 0.16296, 3.13115, 0.07107, 0.14779, 3.33182, 0.19249, 0.53257, -0.48905, -0.70101], [0.28274, -0.85851, 1.56778, 0.07122, 0.22749, 3.08328, 5.4518, 0.32783, -1.029, -0.99974], [-0.75271, 0.29227, 0.74579, 0.03556, 0.08729, 4.60293, 2.32435, 4.08807, 3.24784, 3.005], [0.38325, 0.54477, 0.94102, 0.40818, 0.28921, 1.7405, 1.45806, -0.04071, 0.23743, 0.32516]], "star": [[-0.51053, -0.15273, 0.68805, 0.35961, 0.16414, 0.36307, 5.26169, 0.35404, 0.26297, -0.02025], [-0.07018, -0.79386, 1.46149, 0.27812, 0.34827, 0.66017, 0.5998, 0.02515, -0.03127, -0.01383], [-0.04242, -0.06369, 0.41342, 0.52432, 0.492, 0.32373, 2.82758, -0.0946, -0.04359, 0.19702], [-0.5847, -0.79101, 0.74844, 0.71803, 0.3309, 0.57434, 3.00178, 0.00359, -0.00168, -0.00322], [-0.08832, 0.12294, 1.53853, 0.459, 0.39519, 0.38586, 0.2186, 0.20673, 0.06584, -0.12705], [-0.60689, -0.33177, 1.17408, 0.96799, 0.62049, 0.69716, 5.23717, -0.01384, 0.00645, -0.01124], [0.11267, 0.28884, 1.11652, 0.39831, 0.30015, 0.46066, 4.32536, -0.14439, 0.02783, 0.16473], [0.32993, 0.06371, 0.41569, 0.16036, 0.30224, 0.78318, 6.03392, 0.31667, 0.33124, -0.08115], [0.46703, -0.0168, -0.08327, 0.40889, 0.45086, 0.59604, 4.14405, -0.04784, -0.04327, -0.01626], [0.61214, -0.16662, 2.05353, 0.39932, 0.47518, 0.62537, 6.12889, 0.00102, 0.02287, 0.0025], [-0.86329, -0.74587, 2.69009, 0.63739, 0.86173, 0.3242, 4.08681, -0.04421, -0.04775, 0.0649], [-0.01049, -0.1416, 1.38894, 0.47137, 0.34564, 0.30828, 2.61817, -0.42365, -0.03028, 0.21405], [-0.34958, 0.02443, 1.04418, 0.35261, 0.13573, 0.36934, 2.2475, -0.06532, -0.19908, 0.0011], [0.76035, -0.07882, 0.09598, 0.48621, 0.25489, 0.319, 4.09159, -0.1085, -0.11318, 0.15415], [0.28869, -0.26514, 0.83971, 0.49766, 0.42968, 0.42586, 4.07843, -0.00878, -0.0057, -0.00669], [0.09774, 0.02822, 2.87191, 0.62577, 0.52997, 0.44792, 0.1387, 0.03507, 0.06726, -0.03023], [-0.0697, 0.00158, 0.11391, 0.33412, 0.28107, 0.70131, 5.68176, 0.08611, -0.12642, -0.14202], [0.0488, 0.10928, 1.1197, 0.42092, 0.33285, 0.27918, 3.51575, -0.38059, -0.08911, 0.27614], [-0.0164, 0.04503, 2.89844, 0.54267, 0.40763, 0.33239, 0.07615, 0.26878, 0.18469, -0.06475], [0.62477, -0.25381, 2.3837, 0.37851, 0.37412, 0.23939, 4.97219, 0.17725, 0.07981, -0.00017], [-0.34377, 0.556, 1.43592, 0.30259, 0.52825, 0.61379, 4.12942, -0.10279, -0.05566, 0.04159], [0.03347, -0.16549, 1.55273, 0.26411, 0.54085, 0.52546, 5.74224, 0.38154, 0.11136, -0.06104], [0.57593, 0.35328, 1.28074, 0.52591, 0.59368, 0.28347, 3.16451, -0.0194, 0.00598, -0.00804], [0.56303, 0.91735, 1.15253, 0.38446, 0.53731, 0.41035, 0.65649, -0.01641, -0.00829, -0.03907], [-0.0675, 0.58166, 2.06245, 0.19321, 0.19147, 0.60727, 3.05455, 0.02441, -0.07817, -0.06201], [0.09489, -0.20717, 0.78309, 0.61948, 0.61941, 0.54474, 5.3332, 0.13564, 0.05384, -0.05197], [0.76998, -0.59085, 0.72889, 0.2626, 0.36886, 0.78807, 1.89753, -0.03333, -0.01984, -0.0037], [-0.92937, -0.2769, 1.48164, 0.16458, 0.51391, 0.88393, 4.68962, 0.11147, 0.02193, -0.12731], [-0.04073, -0.3637, 0.41262, 0.24729, 0.51547, 0.74959, 3.67937, -0.13205, -0.08886, 0.04567], [-0.1472, -0.41196, 1.58488, 0.58691, 0.55867, 0.38693, 2.37401, -0.03823, -0.02307, 0.02535], [-0.00614, 0.03833, 2.69916, 0.48144, 0.53005, 0.49706, 3.20669, -0.20773, 0.04657, 0.06422], [-0.20447, 0.05738, 2.97441, 0.15979, 0.22661, 2.44411, 1.39823, -0.30817, -0.09173, 0.12606], [-0.76942, -0.19783, 0.21829, 0.13352, 0.04064, 1.49619, 5.39481, 1.10947, 0.52703, -0.51469], [-0.46293, -0.23911, 1.61092, 0.04634, 0.33172, 3.03375, 5.41794, 1.26103, 0.78286, -0.49522], [0.06065, -0.83499, 1.1654, 0.14828, 0.005, 2.62333, 4.3315, -1.33619, -0.7626, 0.64493], [-0.31575, 0.31703, 0.19491, 0.13551, 0.27143, 1.33853, 6.08375, 0.8196, 0.30475, -0.20821], [-0.56296, -0.08599, 2.18115, 0.11669, 0.28658, 1.93389, 0.64648, 0.58107, 0.24135, -0.26829], [-0.112, 0.09234, 2.63498, 0.09472, 0.08023, 3.33312, 3.20024, 0.55321, 0.85241, 0.62275], [-0.42245, -0.08477, 0.01726, 0.22589, 0.12844, 1.69061, 0.16619, 0.23566, -0.00869, -0.03135], [0.06776, 0.6268, 2.58094, 0.06912, 0.05267, 2.03968, 1.49948, 0.11016, 0.04239, -0.04381], [0.28122, 0.59345, 2.17899, 0.01946, 0.24747, 3.82596, 4.42208, -3.84417, -2.18766, 2.01159], [0.31386, -0.36299, 1.20103, 0.13599, 0.07654, 1.6408, 0.16886, -0.29135, -0.0623, 0.17822], [0.55473, -0.31699, 1.59136, 0.11557, 0.16764, 2.06004, 1.14207, -0.62327, -0.31521, 0.27353], [-0.44106, 0.45828, 0.26594, 0.03281, 0.22193, 3.7483, 2.17469, -2.02698, -1.09346, 0.96396], [0.65717, 0.59846, -0.27109, 0.14513, 0.11559, 3.48087, 5.39501, 0.12239, 0.03585, -0.03452], [-0.14798, -0.45393, 0.32433, 0.01861, 0.79582, 4.4202, 4.92375, 3.28216, 1.43719, -1.25199], [-0.60536, -0.04913, 2.21412, 0.02314, 0.24465, 4.18919, 4.46277, -4.16041, -1.95161, 1.7603], [-0.62697, -0.5098, 1.45758, 0.08486, 0.15698, 1.52263, 1.89466, 0.12829, 0.10561, -0.03876], [0.51449, -0.28711, 1.60491, 0.06129, 0.2162, 3.89484, 3.87665, 0.95616, 0.46459, -0.42435], [0.32116, 0.38261, 2.63277, 0.18795, 0.14359, 1.17108, 0.03199, 0.37471, 0.26449, -0.44471], [0.29948, 0.06897, 2.81495, 0.04949, 0.39792, 2.97239, 5.91684, 1.89518, 0.56408, -0.23856], [-0.5663, 0.85365, 2.05972, 0.08528, 0.06723, 0.8634, 4.01119, 0.17046, 0.05918, -0.06136], [-0.03235, -0.1062, 1.30497, 0.0568, 0.10161, 1.98305, 2.61473, 0.27054, 0.64028, 0.97997], [0.22074, -0.61255, -0.17283, 0.21073, 0.19802, 1.33841, 5.61818, -0.11967, -0.07294, 0.12897], [0.11109, -0.54965, 2.90996, 0.05438, 0.19995, 2.67146, 5.49666, 0.89583, 0.30495, -0.30528], [0.4764, -0.2783, 1.57209, 0.01317, 0.20611, 3.7017, 3.85687, 1.86438, 0.81372, -0.68967], [-0.79551, -0.69126, 1.68869, 0.18938, 0.18063, 1.48869, 2.0417, -0.09177, -0.04576, 0.05076], [-0.69563, -0.39051, 1.65237, 0.17716, 0.12341, 3.1259, 4.8354, -0.17853, -0.09295, 0.08889], [0.4658, 0.20264, 1.3172, 0.11945, 0.05827, 0.84899, 2.47738, 0.35942, 0.24163, -0.24982], [0.12673, -0.61229, -0.30152, 0.02136, 0.30796, 3.50656, 4.4067, -2.95706, -1.8063, 1.69187], [0.01654, -0.88378, 1.64948, 0.13909, 0.02471, 2.18708, 4.81224, 1.36907, 0.71828, -0.64168], [0.52927, 0.0586, 0.94322, 0.05803, 0.13738, 3.00829, 1.65677, 1.20588, 0.70135, -0.64307], [0.11272, 0.53699, 1.59168, 0.27775, 0.20228, 1.15561, 4.67559, -0.23347, -0.19164, 0.09554], [0.19108, 0.06434, 0.15906, 0.05952, 0.08047, 2.55291, 0.86874, -0.65442, -1.18447, -1.1727], [-0.28411, -0.42135, 3.38232, 0.09539, 0.08902, 0.98512, 2.76018, 0.0392, 0.02729, -0.10188], [-0.42219, -0.22683, 2.16676, 0.17983, 0.29019, 0.84596, 2.02503, -0.16512, -0.1051, 0.16705], [0.24219, 0.4444, 2.16647, 0.13174, 0.20905, 1.64917, 0.1576, 0.92216, 0.24084, -0.18353], [0.14429, 0.51728, 1.87757, 0.09842, 0.12765, 2.3035, 0.3321, -0.21602, -0.00533, -0.04041], [0.00122, -0.42501, 3.22421, 0.14635, 0.15283, 1.00079, 3.56606, -0.95341, -0.46443, 0.19811], [0.01914, 0.02327, 1.68727, 0.21134, 0.13233, 1.49743, 6.22365, -0.51815, -0.83356, -0.67064], [0.28187, 0.36377, -0.38082, 0.10839, 0.14596, 0.8208, 5.92113, 1.09395, 0.3801, -0.17753], [0.22536, 0.49825, 2.20265, 0.05758, 0.25291, 2.76728, 0.46329, 1.42985, 0.51988, -0.41777], [-0.45783, -0.0778, 1.84375, 0.14555, 0.14505, 1.25657, 0.34373, 1.67452, 0.522, -0.42744], [0.13987, -0.2105, 2.01916, 0.13301, 0.19415, 1.81491, 0.56274, 1.00172, 0.68436, -0.27158], [0.50098, -0.05012, 0.76651, 0.19449, 0.2237, 1.15756, 3.65245, -0.66405, -0.35603, 0.28384], [0.45732, 0.61373, 1.1175, 0.15351, 0.04725, 0.83834, 0.31755, 1.27973, 0.78829, -0.76025], [-0.31064, 0.32627, 3.03941, 0.17271, 0.11314, 0.89206, 3.90119, -1.08711, -0.48405, 0.42314], [-0.05244, 0.46698, 1.28082, 0.41268, 0.19876, 1.82854, 1.96378, 0.14533, 0.08843, -0.04229], [0.65368, -0.04039, 0.91601, 0.01431, 0.2486, 2.64038, 0.37863, -1.53102, -0.6903, 0.61226], [0.11256, -0.49758, 2.90797, 0.08858, 0.20068, 1.66331, 1.9988, -0.95035, -0.3115, 0.39809], [-0.55115, -0.2651, 1.56625, 0.01558, 0.26992, 4.61937, 1.74074, -4.7308, -2.21314, 2.21696], [-0.57442, 0.29028, 2.07148, 0.07815, 0.11578, 1.77736, 3.97166, 0.24663, 0.10249, -0.12441], [-0.57351, -0.03527, 2.31236, 0.08946, 0.13378, 1.57572, 4.21907, -0.42142, -0.45787, 0.33859], [0.44745, -0.04054, 1.57304, 0.1578, 0.12902, 1.05371, 0.84855, 1.15497, 0.12029, -0.08756], [0.47798, 0.46267, -0.28408, 0.01747, 0.25291, 4.78872, 1.38827, 3.57491, 2.16954, -2.14804], [-0.39951, 0.56329, 3.60983, 0.06129, 0.12474, 2.31554, 0.20968, 1.7986, 0.71715, -0.67198], [0.2029, 0.94365, 1.51817, 0.12817, 0.15872, 0.90829, 5.2715, 0.09825, 0.00224, -0.02612], [-0.2578, 0.40853, 0.87754, 0.17928, 0.23013, 1.80534, 6.23272, 0.42828, 0.23362, -0.20952], [0.62698, -0.08746, 0.60216, 0.13076, 0.15134, 1.24408, 4.03669, -0.78655, -0.2753, 0.33121], [-0.11036, -0.36616, 0.44709, 0.11328, 0.21919, 1.53497, 2.63921, -1.00777, -0.44399, 0.4229], [0.57028, 0.65051, -0.01295, 0.18803, 0.20359, 1.5268, 2.61712, 0.20971, 0.08408, -0.06806], [0.91737, -0.25154, 3.13936, 0.36808, 0.01164, 0.55714, 4.98839, 3.41089, 1.68578, -1.47782], [0.00464, -0.67149, 0.51839, 0.07402, 0.08209, 1.77517, 6.14924, 1.13503, 0.69195, -0.43628], [0.45897, 0.66312, 1.85728, 0.07313, 0.11845, 1.14973, 3.4493, -0.06586, -0.09912, 0.17318], [0.03662, 0.17978, 2.97047, 0.11176, 0.035, 1.47455, 6.27172, -0.71513, -1.26976, -0.78411], [0.61477, -0.81121, 1.1419, 0.13361, 0.13146, 0.78051, 3.17935, -0.0222, 0.00013, -0.0463], [-0.47039, 0.16684, 2.52823, 0.13584, 0.09956, 2.82399, 0.4835, -0.37612, -0.15246, 0.13208], [0.0925, 0.27645, 0.13734, 0.26465, 0.1313, 1.96463, 3.63057, 0.45913, 0.11807, -0.09602], [0.74131, -0.17449, 2.55596, 0.14172, 0.03612, 0.77917, 3.09129, -2.06065, -1.08434, 0.96287], [-0.49518, 0.1883, 0.21456, 0.10268, 0.09144, 2.59442, 3.75846, -0.11141, -0.00155, -0.05859], [-0.08749, -0.04503, 0.36005, 0.16451, 0.14805, 1.75564, 1.96861, 0.37147, 0.61096, 0.57908], [-0.07382, -0.4671, 0.31571, 0.07188, 0.24398, 1.9183, 0.03077, 0.76225, 0.24352, -0.29235], [-0.41184, 0.73294, 4.04825, 0.01519, 0.12271, 2.8311, 3.22725, 0.54958, 0.21104, -0.20166], [0.05866, -0.48599, 1.87293, 0.06712, 0.14864, 1.88058, 2.60092, -0.29807, -0.2459, 0.00046], [-0.22666, 0.54185, 4.08157, 0.04808, 0.18162, 2.6353, 2.15601, -1.70536, -0.5344, 0.645], [0.11034, 0.555, 1.8767, 0.10493, 0.15919, 2.8434, 3.33574, 0.11173, 0.08681, -0.12987], [-0.00432, -0.59956, 0.68151, 0.21, 0.35642, 1.18027, 1.97778, -0.13986, -0.03979, 0.08981], [-0.22001, -0.41801, 1.9694, 0.2259, 0.02907, 0.6921, 0.39981, -0.62639, -0.23372, 0.2716], [-0.69313, 0.19448, 1.77782, 0.09053, 0.15994, 2.16316, 3.30231, 0.03186, 0.04356, -0.026], [-0.07556, 0.43344, 0.94755, 0.04052, 0.33977, 1.96234, 0.87423, 1.98507, 0.77476, -0.48832], [-0.25397, 0.58587, 0.97557, 0.01057, 0.2231, 2.65974, 3.26704, -2.58728, -1.3095, 1.18591], [0.51088, -0.39587, 1.97375, 0.26423, 0.20145, 1.12756, 1.03371, -0.17067, -0.13254, 0.15733], [-0.51523, 0.7042, 1.79156, 0.18224, 0.13081, 1.36296, 5.3965, -0.16144, -0.0463, -0.00371], [-0.32852, -0.24906, 1.52681, 0.08517, 0.13409, 2.13653, 1.6727, -0.87072, -0.15162, 0.25915], [0.00806, 0.84109, 0.15468, 0.10667, 0.14355, 0.93857, 6.05272, -0.02536, -0.02323, -0.01905]], "boat": [[-0.35803, -0.04712, 0.33676, 0.46106, 0.39276, 0.50475, 4.13232, 0.00283, 0.01229, -0.00976], [-0.41203, 0.10897, 2.52432, 0.40229, 0.57243, 0.23464, 5.08722, -0.00107, 0.00666, -0.07483], [0.12509, -0.17784, 2.45227, 0.25832, 0.32034, 0.71608, 3.56406, -0.55495, -0.81191, -0.24526], [-0.14167, -0.02618, 0.79131, 0.39298, 0.77199, 0.53086, 5.38417, 0.08289, -0.01826, -0.03796], [-0.01467, -0.16899, 0.41817, 0.36342, 0.33713, 0.73269, 5.66457, 0.22791, 0.20826, 0.12245], [0.11882, 0.17061, 1.5263, 0.42596, 0.48433, 0.64705, 4.39323, -0.0514, -0.11341, -0.10323], [0.38566, 0.39142, 0.93088, 0.41204, 0.58494, 0.39645, 0.64687, 0.12816, -0.03829, -0.08142], [0.14899, 0.39127, 1.63262, 0.57184, 0.2499, 0.65196, 5.76974, -0.0592, -0.16024, -0.1084], [-0.19028, -0.94504, 1.12366, 0.76464, 0.29497, 0.38367, 4.45984, 0.18743, 0.14662, 0.10917], [0.23686, 0.09142, 1.30661, 0.29763, 0.25272, 0.73147, 4.36912, -0.39693, -0.46958, -0.27099], [0.18348, 0.89217, 1.06554, 0.55058, 0.48248, 0.40182, 1.53431, 0.07829, 0.00204, -0.18879], [0.70699, -0.80219, 0.73109, 0.56942, 0.85824, 0.65426, 3.40796, -0.00738, -0.0733, -0.06328], [0.08463, 0.35836, 1.62099, 0.36608, 0.36466, 0.74301, 2.07875, -0.09204, 0.31805, 0.30015], [0.76143, 0.12624, 2.48314, 0.52285, 0.57768, 0.2519, 4.92292, 0.148, 0.04802, -0.05123], [-0.05321, -0.44144, 2.33538, 0.36346, 0.49199, 0.60418, 3.36667, -0.18666, -0.15261, -0.08766], [-0.49942, 0.39841, 2.1084, 0.46562, 0.37552, 0.79064, 4.52097, 0.01144, -0.00033, 0.00854], [0.12141, -0.21516, 0.92039, 0.34648, 0.19827, 0.3196, 6.14339, 0.22338, 0.23482, 0.45767], [0.11031, -0.58101, 1.14666, 0.51466, 0.84179, 0.23172, 2.30048, -0.1002, 0.06144, 0.03765], [0.03505, 0.09251, 1.79332, 0.55181, 0.44314, 0.35617, 1.28299, 0.10084, 0.28937, 0.17874], [0.19853, 0.07481, 3.03637, 0.23448, 0.52977, 0.37181, 5.88989, 0.19077, 0.22606, -0.15106], [0.05457, -0.27555, 0.96562, 0.33537, 0.23879, 0.41219, 2.59711, -0.4955, -0.37183, -0.08309], [0.24537, -0.88183, -0.01118, 0.39644, 0.76785, 0.60463, 0.83211, 0.08243, 0.07792, 0.08496], [0.35768, -0.15754, 0.02243, 0.40371, 0.34036, 0.4328, 0.9687, 0.27805, 0.11875, 0.05968], [0.45203, -0.6876, 2.51104, 0.64175, 0.47358, 0.9028, 2.91444, 0.03378, 0.01227, 0.00709], [-0.84711, 0.43104, 1.14221, 0.48062, 0.811, 0.58063, 1.42528, -0.01733, 0.03998, 0.02921], [-0.65691, -0.00547, 1.40793, 0.59829, 0.31731, 0.56019, 3.49149, -0.00514, 0.01508, -0.01819], [0.31537, 0.54621, 2.14771, 0.42914, 0.33822, 0.52531, 2.59495, -0.12871, 0.04671, 0.30871], [0.18389, -0.0192, 1.72127, 0.40317, 0.25492, 0.65319, 3.35443, -0.23199, -0.1824, -0.46588], [0.24018, -0.26153, -0.18801, 0.30371, 0.29881, 0.47301, 3.88628, -0.36114, -0.17004, -0.31887], [-0.51405, 0.71201, 1.76857, 0.34024, 0.31019, 0.61892, 3.98611, -0.1618, 0.17146, 0.45924], [-0.56883, 0.49773, 1.86237, 0.20481, 0.23447, 0.84602, 0.11957, 0.15594, -0.29436, -0.53234], [0.23848, 0.06412, 1.51014, 0.11924, 0.22533, 1.53357, 0.82589, 2.20238, 1.65043, 0.62457], [-0.21027, -0.54098, 2.46659, 0.22927, 0.1359, 1.27134, 5.19799, -0.11871, -0.0959, -0.13234], [-0.10351, -0.26834, 0.01181, 0.07839, 0.31304, 2.06626, 2.96916, -1.71056, -1.0688, -0.53551], [-0.0248, -0.55869, 2.72252, 0.17045, 0.16361, 1.86115, 0.88871, 0.67705, 0.66146, 0.1311], [0.72597, 0.40145, 3.83772, 0.0798, 0.09103, 2.35273, 4.93156, 0.25208, -0.42274, -0.58041], [0.34289, 0.39094, 1.38718, 0.16128, 0.19588, 1.19757, 5.685, 0.16747, -0.20192, -0.7312], [0.06644, -0.60577, -0.63073, 0.08736, 0.19748, 1.87261, 0.1431, 1.77121, 0.94503, 0.64713], [-0.21724, -0.33086, 0.47813, 0.08997, 0.08946, 0.89736, 6.21413, -0.23098, -0.24363, -0.16915], [-0.22979, -0.72439, 1.33041, 0.1924, 0.02362, 2.09594, 5.21617, -0.2779, -0.3288, -0.2743], [-0.28104, -0.15587, 1.23101, 0.13788, 0.09774, 0.8023, 3.10253, 0.0241, -0.01377, 0.13467], [0.52665, 0.56464, 0.60309, 0.11355, 0.19963, 1.72993, 0.41176, 0.25423, -0.35009, -1.00354], [-0.09769, 0.05324, 2.59468, 0.10418, 0.0784, 1.84332, 5.30456, -0.43615, -0.27265, -0.47339], [0.14923, 0.43003, 1.26489, 0.25233, 0.08449, 2.11129, 2.12432, -0.07108, -0.0937, -0.14313], [-0.79014, 0.61709, 1.90558, 0.37037, 0.11136, 1.64229, 5.18906, -0.13822, 0.16428, 0.3693], [-0.12659, 0.30183, 1.57897, 0.03235, 0.65149, 3.35957, 4.83163, 0.62631, -1.09254, -2.34957], [0.74631, -0.27895, 2.57092, 0.32236, 0.22899, 1.70782, 4.40745, -0.10511, -0.11866, -0.0974], [-0.4706, -1.01786, 1.562, 0.11467, 0.13857, 1.21133, 5.87333, 0.02135, -0.01299, 0.02737], [-0.05836, 0.04774, 0.62651, 0.22665, 0.15889, 1.20993, 2.69819, -0.14953, -0.32859, -0.33054], [0.48753, 0.18505, 1.40111, 0.09815, 0.21416, 1.35927, 0.28737, -0.22569, -0.15438, -0.10252], [0.19376, 0.78175, 0.86577, 0.232, 0.101, 1.39569, 2.35936, 0.06397, 0.01969, -0.15017], [-0.32573, 0.28337, 1.44029, 0.10177, 0.09071, 2.11344, 1.90905, -0.0877, 0.22625, 0.5373], [-0.10059, 0.20726, -0.15807, 0.04677, 0.10242, 1.83554, 3.26912, -2.11536, -1.82547, -1.57415], [0.84624, -0.47251, 1.47539, 0.20866, 0.22456, 1.01765, 2.08865, 0.01409, 0.01689, 0.01502], [0.50083, -0.29505, 1.39077, 0.08953, 0.07203, 1.88329, 0.12959, -0.05621, -0.08363, -0.1078], [-0.1445, -0.95257, 2.90764, 0.07775, 0.01977, 2.03443, 3.97165, 0.97008, 0.86927, 0.57117], [-0.47625, -0.61653, 2.43245, 0.11514, 0.11414, 1.41168, 3.49398, 0.02675, 0.04917, 0.00409], [0.15157, 0.15012, 1.47682, 0.03508, 0.16766, 3.63825, 3.18341, 0.92059, 0.83145, 0.42329], [0.14193, 0.25131, 2.49569, 0.17593, 0.13607, 2.08532, 0.75981, -0.11116, 0.00501, 0.01148], [-0.66478, 0.65038, 1.19491, 0.35746, 0.06058, 2.73093, 5.53523, 0.11769, -0.16289, -0.3357], [0.61342, 0.56992, 3.7336, 0.02526, 0.21197, 4.26954, 1.86086, -0.6535, 0.93151, 2.24838], [0.6787, -0.92989, 0.41269, 0.25026, 0.38995, 1.63202, 5.8353, -0.00703, -0.02194, -0.01415], [0.04638, -0.55831, 2.42571, 0.12093, 0.16263, 0.73856, 6.14543, 0.36226, 0.49601, 0.35012], [-0.21198, -0.14439, 1.58812, 0.15066, 0.05129, 0.70131, 6.16365, -0.33509, -0.32551, -0.12919], [-0.2144, 0.03032, 2.91624, 0.05753, 0.07852, 1.87215, 0.1741, -0.39673, -0.36386, -0.22987], [-0.13844, -0.71483, 0.07072, 0.07635, 0.33219, 2.26688, 4.82888, 0.2467, 0.18457, 0.16929], [0.15999, -0.29622, 2.69916, 0.20213, 0.24233, 1.002, 5.81437, 1.45309, 1.05946, 0.17348], [-0.07523, 0.37701, 0.46661, 0.06141, 0.03343, 1.56853, 0.70886, -1.05117, -1.23321, -0.96933], [-0.16682, -0.99045, 1.12432, 0.05948, 0.2007, 1.9382, 3.04478, 0.1796, 0.16536, 0.09781], [0.80351, -0.49286, 1.98354, 0.17694, 0.25954, 1.03301, 1.10691, 0.00779, 0.03798, 0.06703], [0.82117, 0.6799, 1.03574, 0.09622, 0.19606, 0.89922, 4.38744, 0.08018, -0.11594, -0.18364], [-0.13149, -0.81441, 0.15398, 0.07111, 0.12125, 1.76403, 2.42052, -0.97914, -1.07737, -0.75186], [-0.61828, -0.31465, 0.80308, 0.12261, 0.14744, 0.48786, 2.49841, -0.08821, -0.04903, 0.05376], [0.39459, -0.10493, 2.5056, 0.09057, 0.16075, 1.27382, 3.16278, -2.35689, -1.60934, -0.60862], [-0.09579, 0.81222, 1.57241, 0.01623, 0.37915, 5.3683, 1.47924, 0.88204, -1.30361, -2.71795], [-0.28004, 0.49948, 1.65494, 0.17664, 0.2661, 0.75148, 0.10037, 0.22502, -0.18689, -0.88598], [0.24841, -0.53404, 2.51898, 0.01783, 0.41616, 3.90649, 2.67916, 1.43219, 1.213, 0.7207], [0.11963, -0.67874, 0.9541, 0.60554, 0.00669, 0.856, 0.23229, -1.54654, -1.29281, -0.86237], [-0.52329, 0.77623, 1.58572, 0.03163, 0.13249, 1.86478, 3.20299, -0.22208, 0.35925, 0.72493], [0.0299, 0.77708, 1.54304, 0.06955, 0.29003, 2.61709, 3.92385, -0.28405, 0.4157, 1.04792], [-0.17194, 0.06405, 2.53053, 0.20492, 0.08149, 1.43345, 5.17384, -0.2267, 0.02543, 0.00314], [0.55317, 0.66661, 1.90108, 0.42554, 0.21636, 1.83571, 6.12928, -0.07583, 0.08871, 0.17245], [-0.44615, 0.12555, 3.12824, 0.23051, 0.18191, 1.53885, 4.09845, 0.04691, 0.01757, 0.05995], [-0.24918, -0.17663, 0.32413, 0.05639, 0.06614, 0.86147, 1.95925, 0.19675, 0.15027, 0.25504], [0.29911, 0.60437, 0.50524, 0.14357, 0.11689, 0.9624, 2.34493, -0.14827, 0.15435, 0.16659], [-0.41892, 0.7034, 1.53665, 0.09705, 0.16946, 1.9209, 3.17691, -0.1839, 0.26102, 0.75296], [-0.79271, 0.58647, 2.62031, 0.05162, 0.19227, 2.43745, 1.26147, 0.4843, -0.74477, -1.61327], [-0.60484, 0.03067, 1.28536, 0.17084, 0.14292, 1.52045, 3.6335, -0.04817, 0.02099, 0.0977], [-0.34671, 0.04527, 3.40945, 0.07248, 0.08656, 1.07657, 2.47549, 0.04936, 0.10153, 0.02661], [-0.22547, 0.15271, 0.20253, 0.16391, 0.13752, 1.71726, 4.2191, -0.02961, 0.15912, 0.14774], [-0.00687, -0.73161, 2.5028, 0.07599, 0.1783, 3.06414, 0.4712, 1.32528, 1.34062, 0.86031], [-0.15703, -0.95468, 3.25628, 0.1045, 0.01257, 2.11908, 1.13725, -0.45612, -0.42957, -0.29404], [-0.05372, 0.24665, 0.22127, 0.08319, 0.07884, 2.87674, 4.38322, -0.70704, -0.72359, -0.67824], [0.24342, 0.81571, 1.44644, 0.29137, 0.25659, 0.76395, 1.4982, 0.29314, -0.19535, -0.53801], [-0.68747, 0.54977, 0.22252, 0.25197, 0.1895, 0.88591, 2.94688, -0.12023, 0.23612, 0.56521], [0.47923, 0.30185, 1.57186, 0.00961, 0.2431, 1.65118, 0.45513, 0.60631, -0.81851, -1.97919], [0.04753, -0.49167, 2.745, 0.17775, 0.21465, 1.05867, 0.09611, 0.74664, 0.67871, 0.72987], [-0.91372, 0.37551, 2.54913, 0.04007, 0.06297, 3.36503, 3.85676, -0.40663, 0.58702, 1.40486], [0.409, 0.69172, 1.80022, 0.23277, 0.04852, 2.95077, 6.09774, 0.09222, -0.18616, -0.43305], [-0.96688, 0.90454, 1.94801, 0.08507, 0.17094, 1.69168, 6.06099, -0.06892, 0.06249, 0.09266], [-0.44291, -0.08531, -0.15556, 0.15896, 0.14925, 2.22345, 4.15054, -0.02685, -0.03191, -0.01925], [-0.22339, 0.07173, 0.92299, 0.05924, 0.04536, 1.11415, 5.24568, -0.18604, -0.34506, -0.07539], [0.56561, -0.04271, -0.6544, 0.02191, 0.16757, 3.69071, 4.20103, -4.09617, -4.05649, -2.88399], [0.35068, -0.35148, 2.51338, 0.05881, 0.30758, 2.72514, 1.79961, -1.26945, -1.30986, -1.07607], [-0.18379, -0.3334, 0.0015, 0.01045, 0.44674, 3.65024, 2.58839, 1.95094, 1.62632, 1.11477], [0.55417, 0.01449, 2.11492, 0.08191, 0.07838, 2.38535, 2.59567, -1.26342, -1.06981, -0.79691], [0.32377, 0.11651, 1.57898, 0.0271, 0.21155, 4.63407, 3.87996, -6.17162, -5.12337, -3.60569]]};
</script>
<script>
"use strict";
/* ============================ studio app ============================ */
const PALETTE = [
["#ff6b5e",[255,107,94]], ["#5ec8ff",[94,200,255]], ["#ffd35e",[255,211,94]],
["#7fd98a",[127,217,138]], ["#c86bff",[200,107,255]], ["#5effd0",[94,255,208]],
["#ff8fc7",[255,143,199]], ["#b0b8ff",[176,184,255]],
];
const OUTLIER_RGB = [120,128,143];
const RES = 192, OWN_RES = 128;
const NOISE_SCALE = [1,1,1,.3,.3,20,3,.5,.5,.5];
const HINT_W = 6.0, HINT_SIG = 0.4;
/* ------------- state ------------- */
const S = {
phase: "soup",
layers: [], // {id,name,color,rgb,template,placed,pose,hidden,depth,hints:[]}
selected: -1, // index into layers
obs: [], gt: [], // gt = placed-order index, -1 clutter
placed: [], // layer indices in placed order (BP object order)
marg: null, mu: null, Sig: null, assign: null,
evid: [], covFrac: [],
groupPre: [], groupAlpha: [], // per placed group + clutter last
ownField: null, coastT: [],
};
let nextDepth = 1, nextId = 1;
/* ---- Bet 9: eraser / backdrop / band-gain state ---- */
const GAINS = { band: [1, 1, 1], opacity: 1 };
const backdrop = { custom: false, rgb: [128, 128, 128], img: null }; // img: RGBA RES*RES
let bandPre = null, bandDirty = true;
const fieldAlpha = new Float32Array(RES * RES);
let eraseStack = [], lastCursor = null;
function toolMode(){ return document.querySelector('input[name=tool]:checked').value; }
function brushR(){ return +document.querySelector('input[name=brush]:checked').value; }
function updateEraseButtons(){
document.getElementById("btn-undo-erase").disabled = !eraseStack.length;
document.getElementById("btn-restore-erase").disabled = !eraseStack.length;
}
function addLayer(name, template, autoPlace) {
const [color, rgb] = PALETTE[(S.layers.length) % PALETTE.length];
const L = { id: nextId++, name, color, rgb, template, placed: false,
pose: [0,0,0,Math.log(0.5)], hidden: false, depth: 0, hints: [] };
S.layers.push(L);
if (autoPlace) placeLayer(S.layers.length - 1);
else { renderLibrary(); }
return L;
}
function placeLayer(li) {
const L = S.layers[li];
L.placed = true; L.hidden = false; L.depth = nextDepth++;
L.pose = [ -0.3 + 0.6*rand(), -0.3 + 0.6*rand(), -Math.PI + TAU*rand(),
Math.log(0.45 + 0.2*rand()) ];
rebuildScene();
}
function removeLayer(li) {
S.layers[li].placed = false;
rebuildScene();
}
/* ------------- scene construction ------------- */
let rngState = 22222;
function rand(){ rngState=(rngState*1103515245+12345)&0x7fffffff; return rngState/0x7fffffff; }
function rebuildScene() {
S.placed = S.layers.map((L,i)=>L.placed?i:-1).filter(i=>i>=0);
const noiseAmt = 0.006 * Math.pow(3, +document.getElementById("noise").value / 3);
S.obs = []; S.gt = []; S.src = [];
S.placed.forEach((li,k)=>{
const tmpl = S.layers[li].template;
const tr = transformAtoms(tmpl, S.layers[li].pose);
for (let j = 0; j < tr.length; j++) {
S.obs.push(tr[j]); S.gt.push(k); S.src.push({ li, ref: tmpl[j] });
}
});
for (let i=0;i<12;i++){
const su=0.04+0.08*rand();
S.obs.push([-0.95+1.9*rand(),-0.95+1.9*rand(),-Math.PI/2+Math.PI*rand(),
su,su*(0.4+0.5*rand()),4+10*rand(),TAU*rand(),rand(),rand(),rand()]);
S.gt.push(-1); S.src.push(null);
}
for (const a of S.obs) for(let q=0;q<10;q++){
const g=Math.sqrt(-2*Math.log(rand()+1e-9))*Math.cos(TAU*rand());
a[q]+=g*noiseAmt*NOISE_SCALE[q];
}
for (const a of S.obs){a[3]=Math.max(a[3],.012);a[4]=Math.max(a[4],.008);a[5]=Math.max(a[5],.5);}
S.marg=null; S.mu=null; S.Sig=null; S.assign=null;
S.evid=S.obs.map(()=>true);
S.covFrac=S.placed.map(()=>0);
S.coastT=S.placed.map(()=>0);
S.ownField=null;
eraseStack=[]; updateEraseButtons();
setPhase("soup");
rebuildGroupBuffers();
renderScene(); drawOverlay(); renderLayerList(); renderLibrary(); updatePanels();
}
function scramble(){
for (const li of S.placed){
S.layers[li].pose=[-0.35+0.7*rand(),-0.35+0.7*rand(),-Math.PI+TAU*rand(),
Math.log(0.4+0.25*rand())];
}
rebuildScene();
}
/* ------------- believed grouping (persistent through occlusion) ------------- */
function believedGroup(i){ return S.assign ? S.assign[i] : -2; }
function updateAssign(){
if(!S.marg){S.assign=null;return;}
const K=S.placed.length;
if(!S.assign) S.assign=new Array(S.obs.length).fill(-1);
for(let i=0;i<S.obs.length;i++){
if(S.assign[i]>=0 && !S.evid[i]) continue; // no evidence: identity persists
const m=S.marg[i]; let b=0;
for(let k=1;k<=K;k++) if(m[k]>m[b]) b=k;
S.assign[i]= b===K ? -1 : b;
}
}
function groupAtomIdx(k){
const out=[];
for(let i=0;i<S.obs.length;i++) if(believedGroup(i)===k) out.push(i);
return out;
}
/* ------------- rendering ------------- */
const stage=document.getElementById("stage");
const sceneC=document.getElementById("scene-canvas");
const overC=document.getElementById("overlay-canvas");
sceneC.width=sceneC.height=RES;
let CSS=600;
function fitStage(){
const wrap=document.getElementById("stage-wrap");
CSS=Math.max(280,Math.floor(Math.min(wrap.clientWidth,wrap.clientHeight)-8));
stage.style.width=stage.style.height=CSS+"px";
overC.width=overC.height=CSS*(window.devicePixelRatio||1);
drawOverlay();
}
window.addEventListener("resize",fitStage);
function rebuildGroupBuffers(){
const K=S.placed.length;
S.groupPre=[]; S.groupAlpha=[];
for(let g=0;g<=K;g++) S.groupPre.push(new Float32Array(3*RES*RES));
const groupOf=i=>{ const g=S.marg?believedGroup(i):S.gt[i]; return g>=0?g:K; };
for(let i=0;i<S.obs.length;i++){
const g=groupOf(i);
if(g<K && S.layers[S.placed[g]].hidden) continue;
renderPre([S.obs[i]],RES,S.groupPre[g]);
}
for(let g=0;g<=K;g++) S.groupAlpha.push(alphaFromPre(S.groupPre[g],RES));
bandDirty=true;
}
function rebuildOneGroup(k){
S.groupPre[k]=new Float32Array(3*RES*RES);
if(!S.layers[S.placed[k]].hidden)
renderPre(groupAtomIdx(k).map(i=>S.obs[i]),RES,S.groupPre[k]);
S.groupAlpha[k]=alphaFromPre(S.groupPre[k],RES);
bandDirty=true;
}
/* frequency-band buffers, built lazily only when a band gain != 1.
Same visibility rule as the group buffers; render is linear in atom color,
so gain*color == gain*contribution and Σ_b gain_b·bandPre_b is exact. */
function rebuildBandBuffers(){
const K=S.placed.length;
bandPre=[new Float32Array(3*RES*RES),new Float32Array(3*RES*RES),new Float32Array(3*RES*RES)];
const groupOf=i=>{ const g=S.marg?believedGroup(i):S.gt[i]; return g>=0?g:K; };
for(let i=0;i<S.obs.length;i++){
const g=groupOf(i);
if(g<K && S.layers[S.placed[g]].hidden) continue;
renderPre([S.obs[i]],RES,bandPre[bandOf(S.obs[i][5])]);
}
bandDirty=false;
}
const sceneCtx=sceneC.getContext("2d");
const compositePre=new Float32Array(3*RES*RES);
const sceneImg=new ImageData(RES,RES);
function compMode(){ return document.querySelector('input[name=comp]:checked').value; }
function renderScene(){
const K=S.placed.length;
const gb=GAINS.band, gainsOn=gb[0]!==1||gb[1]!==1||gb[2]!==1;
const bgOn=backdrop.custom||backdrop.img||GAINS.opacity!==1;
if(compMode()==="field"){
compositePre.fill(0);
if(gainsOn){
if(bandDirty) rebuildBandBuffers();
for(let b=0;b<3;b++){
const g=gb[b]; if(!g) continue;
const buf=bandPre[b];
for(let i=0;i<compositePre.length;i++) compositePre[i]+=g*buf[i];
}
} else {
for(const buf of S.groupPre)
for(let i=0;i<compositePre.length;i++) compositePre[i]+=buf[i];
}
sigmoidField(compositePre,RES,sceneImg.data);
if(bgOn){
alphaFromPre(compositePre,RES,fieldAlpha);
blendOverBackdrop(sceneImg.data,fieldAlpha,GAINS.opacity,backdrop.img||backdrop.rgb,RES);
}
} else {
// painter: clutter at the back, then placed layers by depth
const order=[K, ...S.placed.map((li,k)=>k)
.sort((a,b)=>S.layers[S.placed[a]].depth-S.layers[S.placed[b]].depth)];
compositePainter(order,S.groupPre,S.groupAlpha,RES,sceneImg.data);
if(bgOn){
// union alpha of everything rendered (band gains are field-mode only)
for(let i=0;i<RES*RES;i++){
let keep=1;
for(let q=0;q<=K;q++) keep*=1-S.groupAlpha[q][i];
fieldAlpha[i]=1-keep;
}
blendOverBackdrop(sceneImg.data,fieldAlpha,GAINS.opacity,backdrop.img||backdrop.rgb,RES);
}
}
sceneCtx.putImageData(sceneImg,0,0);
}
function w2p(x){return (x+1)/2*overC.width;}
function p2w(px){return px/overC.width*2-1;}
const ovAtoms=document.getElementById("ov-atoms"),ovOwn=document.getElementById("ov-own"),
ovEnt=document.getElementById("ov-ent"),ovPose=document.getElementById("ov-pose"),
ovHints=document.getElementById("ov-hints");
for(const el of [ovAtoms,ovOwn,ovEnt,ovPose,ovHints]) el.addEventListener("change",drawOverlay);
document.querySelectorAll('input[name=comp]').forEach(el=>
el.addEventListener("change",()=>{renderScene();}));
function drawOverlay(){
const ctx=overC.getContext("2d");
ctx.clearRect(0,0,overC.width,overC.height);
const px1=overC.width/CSS;
const K=S.placed.length;
const colOf=k=>S.layers[S.placed[k]];
if(S.ownField && (ovOwn.checked||ovEnt.checked)){
const {P,ent,support}=S.ownField;
const im=ctx.createImageData(OWN_RES,OWN_RES);
for(let i=0;i<OWN_RES*OWN_RES;i++){
if(!support[i]) continue;
let r=0,g=0,b=0,a=0;
if(ovOwn.checked){
let best=0;
for(let k=1;k<=K;k++) if(P[k][i]>P[best][i]) best=k;
const rgb=best===K?OUTLIER_RGB:colOf(best).rgb;
r=rgb[0];g=rgb[1];b=rgb[2];a=90*P[best][i];
}
if(ovEnt.checked){
const maxE=Math.log2(K+1);
const e=Math.min(1,ent[i]/maxE), ea=160*e*e;
if(ea>a){r=200;g=107;b=255;a=ea;}
}
im.data[4*i]=r;im.data[4*i+1]=g;im.data[4*i+2]=b;im.data[4*i+3]=a;
}
const tmp=document.createElement("canvas");
tmp.width=tmp.height=OWN_RES;
tmp.getContext("2d").putImageData(im,0,0);
ctx.imageSmoothingEnabled=true;
ctx.drawImage(tmp,0,0,overC.width,overC.height);
}
if(ovAtoms.checked){
for(let i=0;i<S.obs.length;i++){
const a=S.obs[i], g=believedGroup(i);
if(g>=0 && S.layers[S.placed[g]].hidden) continue;
let rgb=OUTLIER_RGB, alpha=0.5;
if(g===-2){rgb=[150,155,168];alpha=0.55;}
else if(g>=0){rgb=colOf(g).rgb;alpha=S.evid[i]?0.25+0.75*S.marg[i][g]:0.35;}
else if(S.marg){alpha=0.25+0.55*S.marg[i][K];}
const len=Math.max(3*px1,a[3]*overC.width*0.5);
const cx=w2p(a[0]),cy=w2p(a[1]);
const dx=Math.cos(a[2])*len/2,dy=Math.sin(a[2])*len/2;
ctx.strokeStyle=`rgba(${rgb[0]},${rgb[1]},${rgb[2]},${alpha})`;
ctx.lineWidth=1.6*px1;
ctx.setLineDash(g>=0&&!S.evid[i]?[3*px1,3*px1]:[]);
ctx.beginPath();ctx.moveTo(cx-dx,cy-dy);ctx.lineTo(cx+dx,cy+dy);ctx.stroke();
}
ctx.setLineDash([]);
}
if(ovHints.checked){
for(let k=0;k<K;k++){
const L=colOf(k);
for(const h of L.hints){
ctx.strokeStyle=L.color; ctx.lineWidth=1.2*px1; ctx.setLineDash([2*px1,3*px1]);
ctx.beginPath();ctx.arc(w2p(h[0]),w2p(h[1]),HINT_SIG*overC.width/2,0,TAU);ctx.stroke();
ctx.setLineDash([]);
ctx.beginPath();ctx.arc(w2p(h[0]),w2p(h[1]),3*px1,0,TAU);
ctx.fillStyle=L.color;ctx.fill();
}
}
}
if(ovPose.checked && S.mu){
for(let k=0;k<K;k++){
const mu=S.mu[k],Sg=S.Sig[k],L=colOf(k);
const cx=w2p(mu[0]),cy=w2p(mu[1]);
ctx.strokeStyle=L.color;ctx.lineWidth=1.2*px1;
ctx.beginPath();
ctx.moveTo(cx-7*px1,cy);ctx.lineTo(cx+7*px1,cy);
ctx.moveTo(cx,cy-7*px1);ctx.lineTo(cx,cy+7*px1);ctx.stroke();
const Ln=26*px1*Math.exp(mu[3]);
ctx.beginPath();ctx.moveTo(cx,cy);
ctx.lineTo(cx+Math.cos(mu[2])*Ln,cy+Math.sin(mu[2])*Ln);ctx.stroke();
const coast=S.coastT[k], q=coast*coast*0.0012;
const a11=Sg[0][0]+q,a12=Sg[0][1],a22=Sg[1][1]+q;
const tr=a11+a22,det=a11*a22-a12*a12;
const disc=Math.sqrt(Math.max(0,tr*tr/4-det));
const l1=tr/2+disc,l2=Math.max(1e-12,tr/2-disc);
const ang=Math.atan2(l1-a11,a12||1e-12);
const ghost=L.hidden||S.covFrac[k]>0.7;
ctx.save();ctx.translate(cx,cy);ctx.rotate(-ang+Math.PI/2);
ctx.setLineDash(ghost?[5*px1,4*px1]:[]);
ctx.beginPath();
ctx.ellipse(0,0,2*Math.sqrt(l1)*overC.width/2,2*Math.sqrt(l2)*overC.width/2,0,0,TAU);
ctx.stroke();ctx.restore();ctx.setLineDash([]);
if(ghost){
ctx.fillStyle=L.color;
ctx.font=`${11*px1}px ${getComputedStyle(document.body).fontFamily}`;
ctx.fillText("believed, unseen",cx+10*px1,cy-10*px1);
}
}
}
if(toolMode()==="erase"&&lastCursor){ // eraser brush cursor
ctx.strokeStyle="rgba(255,120,110,0.9)";
ctx.lineWidth=1.4*px1;ctx.setLineDash([4*px1,3*px1]);
ctx.beginPath();
ctx.arc(w2p(lastCursor[0]),w2p(lastCursor[1]),brushR()*overC.width/2,0,TAU);
ctx.stroke();ctx.setLineDash([]);
}
}
/* ------------- semantic factor from hints ------------- */
function makeSemLL(){
const K=S.placed.length;
let any=false;
for(const li of S.placed) if(S.layers[li].hints.length) any=true;
if(!any) return null;
return (i,k)=>{
const hs=S.layers[S.placed[k]].hints;
if(!hs.length) return 0;
let best=0;
for(const h of hs){
const d2=(S.obs[i][0]-h[0])**2+(S.obs[i][1]-h[1])**2;
best=Math.max(best,Math.exp(-0.5*d2/(HINT_SIG*HINT_SIG)));
}
return HINT_W*(best-0.5);
};
}
/* ------------- BP wiring + depth occlusion ------------- */
function currentTemplates(){ return S.placed.map(li=>S.layers[li].template); }
function computeEvidence(){
const K=S.placed.length;
if(!document.getElementById("auto-occ").checked || !S.assign){
S.evid=S.obs.map((_,i)=>{
const g=believedGroup(i);
return !(g>=0 && S.layers[S.placed[g]].hidden);
});
S.covFrac=S.placed.map((li,k)=>S.layers[li].hidden?1:0);
return;
}
const layersMeta=S.placed.map(li=>({depth:S.layers[li].depth,hidden:S.layers[li].hidden}));
const {mask,covFrac}=autoHidden(S.obs,S.assign,layersMeta,S.groupAlpha,RES);
S.evid=mask.map(m=>!m);
S.covFrac=covFrac;
}
function runBind(opts={}){
const hiddenMask=S.evid.map(e=>!e);
const frames=[];
const res=bpBind(currentTemplates(),S.obs,{
iters:opts.iters??40,
clampPose:opts.clampPose??null,
hiddenMask,
semLL:makeSemLL(),
onIter:(it,marg,mu,Sig)=>{ if(opts.animate) frames.push({
marg,mu:mu.map(m=>m.slice()),Sig:Sig.map(M=>M.map(r=>r.slice()))}); },
});
return {res,frames};
}
function bind(animate=true){
if(S.phase==="binding"||!S.placed.length) return;
S.assign=null;
eraseStack=[]; updateEraseButtons(); // erase history is stale once BP re-runs
S.evid=S.obs.map(()=>true); // full re-bind: everything is evidence again
for(const li of S.placed) if(S.layers[li].hidden){
// user-hidden layers stay evidence-free
// (their atoms are excluded per-atom below via gt at soup time)
}
setPhase("binding");
setTimeout(()=>{
const {res,frames}=runBind({iters:40,animate});
if(!animate){finishBind(res);return;}
let f=0;
const meter=document.querySelector("#bind-meter > div");
const step=()=>{
if(f<frames.length){
S.marg=frames[f].marg;S.mu=frames[f].mu;S.Sig=frames[f].Sig;
updateAssign();
meter.style.width=(100*(f+1)/frames.length)+"%";
document.getElementById("m-it").textContent=(f+1);
drawOverlay();updatePanels();
f++;requestAnimationFrame(step);
} else finishBind(res);
};
step();
},30);
}
function finishBind(res){
S.marg=res.marg;S.mu=res.mu;S.Sig=res.Sig;
updateAssign();
rebuildGroupBuffers();
occlusionRefresh(true);
renderScene();refreshOwnership();
setPhase("bound");
drawOverlay();updatePanels();renderLayerList();
}
/* recompute evidence from the depth stack; if it changed, refresh marginals
with all poses clamped (occlusion changes evidence, not where things are) */
function occlusionRefresh(silent){
const before=S.evid.join("");
computeEvidence();
if(S.evid.join("")===before) return;
const clamp=S.mu.map(m=>m.slice());
const {res}=runBind({iters:8,clampPose:clamp});
S.marg=res.marg;
updateAssign();
// coasting for heavily covered layers
for(let k=0;k<S.placed.length;k++)
S.coastT[k]=(S.covFrac[k]>0.7||S.layers[S.placed[k]].hidden)?S.coastT[k]+1:0;
if(!silent){drawOverlay();updatePanels();renderLayerList();}
}
function refreshOwnership(){
if(!S.marg){S.ownField=null;return;}
const K=S.placed.length;
const vis=[],visMarg=[];
for(let i=0;i<S.obs.length;i++){
const g=believedGroup(i);
if(g>=0&&S.layers[S.placed[g]].hidden) continue;
vis.push(S.obs[i]);visMarg.push(S.marg[i]);
}
S.ownField=ownershipField(vis,visMarg,K,OWN_RES);
}
function resettle(k,clampXi){
const clamp=S.placed.map(()=>null);clamp[k]=clampXi;
const {res}=runBind({iters:10,clampPose:clamp});
S.marg=res.marg;S.mu=res.mu;S.Sig=res.Sig;
S.mu[k]=clampXi.slice();
updateAssign();
rebuildGroupBuffers();
occlusionRefresh(true);
renderScene();refreshOwnership();
drawOverlay();updatePanels();renderLayerList();
document.getElementById("m-it").textContent="10 (re-settle)";
}
/* ------------- interventions ------------- */
function applyGesture(k,g){
for(const i of groupAtomIdx(k)) S.obs[i]=transformAtoms([S.obs[i]],g)[0];
S.mu[k]=composePose(g,S.mu[k]);
rebuildOneGroup(k);
renderScene();drawOverlay();updatePanels();
}
/* ------------- eraser (Bet 9) -------------
Deleting an atom = filtering every per-atom parallel array by the brush
mask. Order of atoms carries no meaning anywhere (additive render, per-atom
marginals), so undo can simply re-append the removed records.
Undo is valid until the next Bind/Scramble/place/remove (those rebuild or
re-infer the per-atom state). "Burn" additionally removes the source atom
from the layer TEMPLATE, so the erase survives scene rebuilds. */
function eraseAt(wx,wy,stroke){
const skip=i=>{ const g=believedGroup(i); return g>=0&&S.layers[S.placed[g]].hidden; };
const mask=eraseMask(S.obs,wx,wy,brushR(),skip);
let any=false; for(const m of mask) if(m){any=true;break;}
if(!any) return;
const kObs=[],kGt=[],kSrc=[],kEvid=[],
kAssign=S.assign?[]:null, kMarg=S.marg?[]:null;
for(let i=0;i<S.obs.length;i++){
if(mask[i]){
stroke.atoms.push({a:S.obs[i],gt:S.gt[i],src:S.src[i],evid:S.evid[i],
assign:S.assign?S.assign[i]:null, marg:S.marg?S.marg[i]:null});
} else {
kObs.push(S.obs[i]);kGt.push(S.gt[i]);kSrc.push(S.src[i]);kEvid.push(S.evid[i]);
if(kAssign)kAssign.push(S.assign[i]);
if(kMarg)kMarg.push(S.marg[i]);
}
}
S.obs=kObs;S.gt=kGt;S.src=kSrc;S.evid=kEvid;
if(kAssign)S.assign=kAssign;
if(kMarg)S.marg=kMarg;
rebuildGroupBuffers();renderScene();drawOverlay();updatePanels();
}
function commitStroke(stroke){
if(!stroke.atoms.length) return;
if(document.getElementById("erase-burn").checked){
for(const rec of stroke.atoms){
if(!rec.src) continue; // clutter has no template
const L=S.layers[rec.src.li];
const idx=L.template.indexOf(rec.src.ref);
if(idx>=0){L.template.splice(idx,1);stroke.tmpl.push(rec.src);}
}
}
eraseStack.push(stroke);
updateEraseButtons();
refreshOwnership();drawOverlay();renderLayerList();renderLibrary();
}
function undoErase(redraw=true){
const stroke=eraseStack.pop();
if(!stroke) return;
for(const t of stroke.tmpl) S.layers[t.li].template.push(t.ref);
for(const rec of stroke.atoms){
S.obs.push(rec.a);S.gt.push(rec.gt);S.src.push(rec.src);S.evid.push(rec.evid);
if(S.assign)S.assign.push(rec.assign??-1);
if(S.marg)S.marg.push(rec.marg);
}
updateEraseButtons();
if(redraw){
rebuildGroupBuffers();renderScene();refreshOwnership();
drawOverlay();updatePanels();renderLayerList();renderLibrary();
}
}
function restoreAllErases(){
while(eraseStack.length) undoErase(false);
rebuildGroupBuffers();renderScene();refreshOwnership();
drawOverlay();updatePanels();renderLayerList();renderLibrary();
}
/* ------------- pointer interaction ------------- */
let drag=null;
overC.addEventListener("pointerdown",e=>{
const rect=overC.getBoundingClientRect();
const wx=p2w((e.clientX-rect.left)*overC.width/rect.width);
const wy=p2w((e.clientY-rect.top)*overC.width/rect.width);
if(e.shiftKey){ // semantic hint for the selected layer
const li=S.selected;
if(li>=0&&S.layers[li].placed){
S.layers[li].hints.push([wx,wy]);
drawOverlay();renderLayerList();
document.getElementById("prompt-log").textContent=
`hint added for ${S.layers[li].name} — Bind applies it as a location prior.`;
}
return;
}
if(toolMode()==="erase"){ // erase works in soup AND bound
drag={erase:true,stroke:{atoms:[],tmpl:[]}};
overC.setPointerCapture(e.pointerId);
lastCursor=[wx,wy];
eraseAt(wx,wy,drag.stroke);
return;
}
if(S.phase!=="bound") return;
let best=-1,bd=0.12*0.12;
for(let i=0;i<S.obs.length;i++){
const g=believedGroup(i);
if(g<0) continue;
if(S.layers[S.placed[g]].hidden) continue;
const d=(S.obs[i][0]-wx)**2+(S.obs[i][1]-wy)**2;
if(d<bd){bd=d;best=i;}
}
let k=best>=0?believedGroup(best):-1;
if(k<0) for(let kk=0;kk<S.placed.length;kk++)
if((S.layers[S.placed[kk]].hidden||S.covFrac[kk]>0.7)&&
(S.mu[kk][0]-wx)**2+(S.mu[kk][1]-wy)**2<0.05*0.05) k=kk;
if(k<0) return;
drag={k,lx:wx,ly:wy,moved:false};
overC.classList.add("dragging");
overC.setPointerCapture(e.pointerId);
});
overC.addEventListener("pointermove",e=>{
const rect=overC.getBoundingClientRect();
const wx=p2w((e.clientX-rect.left)*overC.width/rect.width);
const wy=p2w((e.clientY-rect.top)*overC.width/rect.width);
if(toolMode()==="erase") lastCursor=[wx,wy];
if(!drag){ if(toolMode()==="erase") drawOverlay(); return; }
if(drag.erase){ eraseAt(wx,wy,drag.stroke); return; }
const dx=wx-drag.lx,dy=wy-drag.ly;
if(dx||dy){applyGesture(drag.k,gestureTranslate(dx,dy));drag.lx=wx;drag.ly=wy;drag.moved=true;}
});
overC.addEventListener("pointerup",e=>{
if(!drag) return;
if(drag.erase){ commitStroke(drag.stroke); drag=null; return; }
overC.classList.remove("dragging");
if(drag.moved) resettle(drag.k,S.mu[drag.k]);
drag=null;
});
overC.addEventListener("pointerleave",()=>{
if(lastCursor){ lastCursor=null; drawOverlay(); }
});
overC.addEventListener("wheel",e=>{
if(S.phase!=="bound") return;
const rect=overC.getBoundingClientRect();
const wx=p2w((e.clientX-rect.left)*overC.width/rect.width);
const wy=p2w((e.clientY-rect.top)*overC.width/rect.width);
let k=-1,bd=0.16*0.16;
for(let kk=0;kk<S.placed.length;kk++){
const d=(S.mu[kk][0]-wx)**2+(S.mu[kk][1]-wy)**2;
if(d<bd){bd=d;k=kk;}
}
if(k<0) return;
e.preventDefault();
const c=[S.mu[k][0],S.mu[k][1]];
const g=e.shiftKey?gestureScaleAbout(c,e.deltaY>0?-0.05:0.05)
:gestureRotateAbout(c,e.deltaY>0?0.08:-0.08);
applyGesture(k,g);
clearTimeout(overC._wt);
overC._wt=setTimeout(()=>resettle(k,S.mu[k]),350);
},{passive:false});
/* ------------- panels ------------- */
function setPhase(p){
S.phase=p;
const lamp=document.getElementById("state-lamp");
lamp.className=p==="soup"?"soup":p==="binding"?"binding":"bound";
lamp.textContent=p==="soup"?"ATOM SOUP":p==="binding"?"BELIEFS SETTLING":"BOUND — GRABBABLE";
document.getElementById("btn-bind").disabled=p==="binding"||!S.placed.length;
}
function renderLayerList(){
const el=document.getElementById("layer-list");
el.innerHTML="";
const byDepth=[...S.placed].map((li,k)=>({li,k}))
.sort((a,b)=>S.layers[b.li].depth-S.layers[a.li].depth);
for(const {li,k} of byDepth){
const L=S.layers[li];
const idx=S.assign?groupAtomIdx(k):[];
const conf=idx.length?idx.reduce((a,i)=>a+(S.evid[i]?S.marg[i][k]:0),0)/Math.max(1,idx.filter(i=>S.evid[i]).length):0;
const cov=S.covFrac[k]||0;
const d=document.createElement("div");
d.className="layer"+(S.selected===li?" sel":"");
d.innerHTML=`
<div class="name" data-li="${li}">
<span class="swatch" style="background:${L.color}"></span>
<span>${L.name}</span>
<span class="dim">${idx.length||L.template.length} atoms${L.hints.length?` · ${L.hints.length} hint${L.hints.length>1?"s":""}`:""}</span>
<span class="spacer"></span>
<div class="belief-bar"><div style="width:${100*conf}%"></div></div>
</div>
<div class="pose">${S.mu?`t=(${S.mu[k][0].toFixed(2)}, ${S.mu[k][1].toFixed(2)}) \u03c1=${(S.mu[k][2]*180/Math.PI).toFixed(0)}\u00b0 s=${Math.exp(S.mu[k][3]).toFixed(2)}`:"pose: unbound"}${cov>0.05?` · <span style="color:var(--entropy)">${(100*cov).toFixed(0)}% occluded${S.coastT[k]?`, coasting ${S.coastT[k]}`:""}</span>`:""}</div>
<div class="ctl">
<button data-li="${li}" data-op="rotl">&#8634;</button>
<button data-li="${li}" data-op="rotr">&#8635;</button>
<button data-li="${li}" data-op="grow">+</button>
<button data-li="${li}" data-op="shrink">&minus;</button>
<button data-li="${li}" data-op="front">front</button>
<button data-li="${li}" data-op="back">back</button>
<button data-li="${li}" data-op="hide">${L.hidden?"reveal":"hide"}</button>
${L.hints.length?`<button data-li="${li}" data-op="clearhints">hints&nbsp;&#10005;</button>`:""}
<button data-li="${li}" data-op="remove">&#10005;</button>
</div>`;
el.appendChild(d);
}
if(!byDepth.length)
el.innerHTML='<div class="dim" style="font-size:11px">no layers placed — place from the library or spawn one</div>';
}
document.getElementById("layer-list").addEventListener("click",e=>{
const nm=e.target.closest(".name");
if(nm&&!e.target.closest("button")){
S.selected=+nm.dataset.li===S.selected?-1:+nm.dataset.li;
renderLayerList();return;
}
const b=e.target.closest("button");if(!b)return;
const li=+b.dataset.li,op=b.dataset.op;
const k=S.placed.indexOf(li);
if(op==="remove"){removeLayer(li);return;}
if(op==="clearhints"){S.layers[li].hints=[];drawOverlay();renderLayerList();return;}
if(op==="front"||op==="back"){
S.layers[li].depth=op==="front"?nextDepth++:(Math.min(...S.placed.map(x=>S.layers[x].depth))-1);
if(S.phase==="bound"){rebuildGroupBuffers();occlusionRefresh(false);renderScene();refreshOwnership();}
renderLayerList();return;
}
if(S.phase!=="bound")return;
if(op==="hide"){
S.layers[li].hidden=!S.layers[li].hidden;
rebuildGroupBuffers();occlusionRefresh(false);renderScene();refreshOwnership();drawOverlay();renderLayerList();
return;
}
const c=[S.mu[k][0],S.mu[k][1]];
if(op==="rotl"){applyGesture(k,gestureRotateAbout(c,-Math.PI/12));resettle(k,S.mu[k]);}
if(op==="rotr"){applyGesture(k,gestureRotateAbout(c,Math.PI/12));resettle(k,S.mu[k]);}
if(op==="grow"){applyGesture(k,gestureScaleAbout(c,0.12));resettle(k,S.mu[k]);}
if(op==="shrink"){applyGesture(k,gestureScaleAbout(c,-0.12));resettle(k,S.mu[k]);}
});
function renderLibrary(){
const el=document.getElementById("library");
el.innerHTML="";
S.layers.forEach((L,li)=>{
if(L.placed) return;
const c=document.createElement("span");
c.className="chip";
c.innerHTML=`<span class="swatch" style="background:${L.color}"></span>${L.name}
<span class="dim">${L.template.length}</span>
<button data-li="${li}" data-op="place" title="place">+</button>`;
el.appendChild(c);
});
}
document.getElementById("library").addEventListener("click",e=>{
const b=e.target.closest("button");if(!b)return;
placeLayer(+b.dataset.li);
});
function updatePanels(){
document.getElementById("m-n").textContent=S.obs.length;
const K=S.placed.length;
if(!S.marg){
document.getElementById("m-acc").textContent="—";
document.getElementById("m-ent").textContent="—";
return;
}
let correct=0;
for(let i=0;i<S.gt.length;i++) if(believedGroup(i)===S.gt[i]) correct++;
document.getElementById("m-acc").textContent=(100*correct/S.gt.length).toFixed(1)+"%";
const ent=atomEntropy(S.marg);
document.getElementById("m-ent").textContent=
(ent.reduce((a,x)=>a+x,0)/ent.length).toFixed(2)+" bits";
}
/* ------------- spawn: server endpoints ------------- */
const spawnStatus=document.getElementById("spawn-status");
async function gradioCall(name,data){
const base=location.pathname.includes("/studio/")?"..":"";
const r=await fetch(`${base}/gradio_api/call/${name}`,{
method:"POST",headers:{"Content-Type":"application/json"},
body:JSON.stringify({data})});
const {event_id}=await r.json();
const resp=await fetch(`${base}/gradio_api/call/${name}/${event_id}`);
const text=await resp.text();
const lines=text.split("\n").filter(l=>l.startsWith("data:"));
if(!lines.length) throw new Error("no data from server");
const payload=JSON.parse(lines[lines.length-1].slice(5));
if(payload==null||payload.error) throw new Error(payload?.error||"server error");
return JSON.parse(payload[0]);
}
function addGenerated(layer,fallbackName){
const name=(layer.ledger&&layer.ledger.prompt)
? (layer.ledger.attention&&layer.ledger.attention.token!=="(all content tokens)"
? layer.ledger.attention.token : layer.ledger.prompt.split(/[ ,]/)[1]||fallbackName)
: fallbackName;
addLayer(name,layer.atoms,true);
spawnStatus.textContent=`layer "${name}": ${layer.atoms.length} atoms · ${layer.ledger.status}`+
(layer.ledger.attention?` · matte kept ${layer.ledger.attention.atoms_after_matte}/${layer.ledger.attention.atoms_before_matte}`:"");
}
document.getElementById("btn-spawn-text").addEventListener("click",async()=>{
const prompt=document.getElementById("spawn-prompt").value.trim();
const token=document.getElementById("spawn-token").value.trim();
if(!prompt){spawnStatus.textContent="write a prompt first";return;}
spawnStatus.textContent="distilling on the server (GPU, minutes)\u2026";
try{
const layer=await gradioCall("layer_from_text",[prompt,"",192,900,30,token,0.35]);
addGenerated(layer,"generated");
}catch(err){spawnStatus.textContent="distill failed: "+err.message+
" (needs a GPU Space; on CPU use Fit or paste JSON)";}
});
document.getElementById("btn-spawn-img").addEventListener("click",async()=>{
const f=document.getElementById("spawn-file").files[0];
if(!f){spawnStatus.textContent="choose an image first";return;}
spawnStatus.textContent="fitting atoms to the image (CPU, ~1 min)\u2026";
try{
const b64=await new Promise((res,rej)=>{
const img=new Image();
img.onload=()=>{
const c=document.createElement("canvas");
const s=Math.min(1,160/Math.max(img.width,img.height));
c.width=Math.round(img.width*s);c.height=Math.round(img.height*s);
c.getContext("2d").drawImage(img,0,0,c.width,c.height);
res(c.toDataURL("image/png").split(",")[1]);
};
img.onerror=rej;
img.src=URL.createObjectURL(f);
});
const layer=await gradioCall("layer_from_image_b64",[b64,140,400]);
addGenerated(layer,f.name.replace(/\.[^.]+$/,""));
}catch(err){spawnStatus.textContent="fit failed: "+err.message+
" (is this page running from the Space? standalone files can only paste JSON)";}
});
document.getElementById("btn-spawn-json").addEventListener("click",()=>{
try{
const raw=JSON.parse(document.getElementById("spawn-json").value);
const atoms=raw.atoms||raw;
if(!Array.isArray(atoms)||atoms[0].length!==10) throw new Error("expect [[10 fields]...]");
addLayer("pasted-"+nextId,atoms,true);
spawnStatus.textContent=`pasted layer: ${atoms.length} atoms`;
document.getElementById("spawn-json").value="";
}catch(err){spawnStatus.textContent="bad JSON: "+err.message;}
});
/* ------------- prompt as intervention ------------- */
const promptLog=document.getElementById("prompt-log");
function layerByName(part){
for(let k=0;k<S.placed.length;k++)
if(part.includes(S.layers[S.placed[k]].name.toLowerCase())) return k;
return -1;
}
function localParse(text){
const t=text.toLowerCase(),ops=[];
for(const part of t.split(/,|;| and /)){
const k=layerByName(part);
if(k<0) continue;
if(/hide|occlude/.test(part)){ops.push({op:"hide",k});continue;}
if(/show|reveal|unhide/.test(part)){ops.push({op:"show",k});continue;}
if(/front|top|forward/.test(part)&&/bring|put|move|send/.test(part)){ops.push({op:"front",k});continue;}
if(/back|behind|bottom/.test(part)&&/send|put|move|push/.test(part)){ops.push({op:"back",k});continue;}
const deg=part.match(/(-?\d+)\s*(deg|\u00b0)?/);
if(/rotate|turn|spin/.test(part))
ops.push({op:"rotate",k,deg:deg?+deg[1]:30*(/left|counter|ccw/.test(part)?-1:1)});
if(/scale|grow|bigger|larger|shrink|smaller/.test(part))
ops.push({op:"scale",k,ds:/shrink|smaller/.test(part)?-0.15:0.15});
if(/move|push|slide|put|drag/.test(part)){
let dx=0,dy=0;const step=0.22;
if(/left/.test(part))dx-=step;
if(/right/.test(part))dx+=step;
if(/up|top/.test(part))dy-=step;
if(/down|bottom/.test(part))dy+=step;
if(/center|middle/.test(part))ops.push({op:"moveto",k,x:0,y:0});
else if(dx||dy)ops.push({op:"move",k,dx,dy});
}
}
return ops;
}
async function claudeParse(text){
const names=S.placed.map(li=>S.layers[li].name);
const sceneDesc=S.placed.map((li,k)=>
`${S.layers[li].name}: t=(${S.mu[k][0].toFixed(2)},${S.mu[k][1].toFixed(2)}) rho=${S.mu[k][2].toFixed(2)} scale=${Math.exp(S.mu[k][3]).toFixed(2)} depth=${S.layers[li].depth} hidden=${S.layers[li].hidden}`).join("; ");
const resp=await fetch("https://api.anthropic.com/v1/messages",{
method:"POST",headers:{"Content-Type":"application/json"},
body:JSON.stringify({model:"claude-sonnet-4-6",max_tokens:1000,
messages:[{role:"user",content:
`You control a probabilistic layered scene editor. Coordinates x,y in [-1,1], y grows DOWNWARD. Higher depth = in front. Layers: ${sceneDesc}.
User command: "${text}"
Respond ONLY with a JSON array (no prose, no fences) of ops:
{"op":"move","object":<name>,"dx":n,"dy":n} {"op":"moveto","object":<name>,"x":n,"y":n}
{"op":"rotate","object":<name>,"deg":n} {"op":"scale","object":<name>,"factor":n}
{"op":"front","object":<name>} {"op":"back","object":<name>}
{"op":"hide","object":<name>} {"op":"show","object":<name>}
Valid names: ${JSON.stringify(names)}`}]})});
const data=await resp.json();
const txt=data.content.filter(c=>c.type==="text").map(c=>c.text).join("");
const arr=JSON.parse(txt.replace(/```json|```/g,"").trim());
return arr.map(o=>{
const k=S.placed.findIndex(li=>S.layers[li].name===o.object);
if(k<0)return null;
if(o.op==="move")return{op:"move",k,dx:o.dx,dy:o.dy};
if(o.op==="moveto")return{op:"moveto",k,x:o.x,y:o.y};
if(o.op==="rotate")return{op:"rotate",k,deg:o.deg};
if(o.op==="scale")return{op:"scale",k,ds:Math.log(o.factor)};
return{op:o.op,k};
}).filter(Boolean);
}
function execOps(ops){
if(!ops.length){promptLog.textContent="Parsed no intervention. Layers: "+
S.placed.map(li=>S.layers[li].name).join(", ")+". Verbs: move/rotate/scale/hide/show/front/back.";return;}
const touched=new Set();
for(const o of ops){
const li=S.placed[o.k];
if(o.op==="hide"&&!S.layers[li].hidden){S.layers[li].hidden=true;continue;}
if(o.op==="show"&&S.layers[li].hidden){S.layers[li].hidden=false;continue;}
if(o.op==="front"){S.layers[li].depth=nextDepth++;continue;}
if(o.op==="back"){S.layers[li].depth=Math.min(...S.placed.map(x=>S.layers[x].depth))-1;continue;}
const c=[S.mu[o.k][0],S.mu[o.k][1]];
if(o.op==="move"){applyGesture(o.k,gestureTranslate(o.dx,o.dy));touched.add(o.k);}
if(o.op==="moveto"){applyGesture(o.k,gestureTranslate(o.x-c[0],o.y-c[1]));touched.add(o.k);}
if(o.op==="rotate"){applyGesture(o.k,gestureRotateAbout(c,o.deg*Math.PI/180));touched.add(o.k);}
if(o.op==="scale"){applyGesture(o.k,gestureScaleAbout(c,o.ds));touched.add(o.k);}
}
for(const k of touched) resettle(k,S.mu[k]);
rebuildGroupBuffers();occlusionRefresh(true);renderScene();refreshOwnership();
drawOverlay();updatePanels();renderLayerList();
promptLog.textContent="Clamped: "+ops.map(o=>`${o.op} ${S.layers[S.placed[o.k]].name}`).join(", ")+
" — beliefs re-settled around the intervention.";
}
async function doPrompt(){
if(S.phase!=="bound"){promptLog.textContent="Bind first — there is nothing to address in a soup.";return;}
const text=document.getElementById("prompt").value.trim();
if(!text)return;
let ops=localParse(text);
if(!ops.length){
promptLog.textContent="Local grammar found nothing; asking Claude\u2026";
try{ops=await claudeParse(text);}
catch{promptLog.textContent="Claude unavailable outside claude.ai — try: move/rotate/scale/hide/front/back + a layer name.";return;}
}
execOps(ops);
}
document.getElementById("btn-prompt").addEventListener("click",doPrompt);
document.getElementById("prompt").addEventListener("keydown",e=>{if(e.key==="Enter")doPrompt();});
document.getElementById("auto-occ").addEventListener("change",()=>{
if(S.phase==="bound"){occlusionRefresh(false);renderScene();}
});
/* ------------- edit & look wiring (Bet 9) ------------- */
document.getElementById("btn-undo-erase").addEventListener("click",()=>undoErase());
document.getElementById("btn-restore-erase").addEventListener("click",restoreAllErases);
document.querySelectorAll('input[name=tool]').forEach(el=>el.addEventListener("change",()=>{
stage.classList.toggle("erasing",toolMode()==="erase");
if(toolMode()!=="erase") lastCursor=null;
drawOverlay();
}));
document.querySelectorAll('input[name=brush]').forEach(el=>el.addEventListener("change",drawOverlay));
document.getElementById("bg-color").addEventListener("input",e=>{
const h=e.target.value;
backdrop.rgb=[parseInt(h.slice(1,3),16),parseInt(h.slice(3,5),16),parseInt(h.slice(5,7),16)];
backdrop.custom=true;renderScene();
});
document.getElementById("bg-file").addEventListener("change",e=>{
const f=e.target.files[0];if(!f)return;
const img=new Image();
img.onload=()=>{
const c=document.createElement("canvas");c.width=c.height=RES;
const sc=Math.max(RES/img.width,RES/img.height); // cover-crop
c.getContext("2d").drawImage(img,(RES-img.width*sc)/2,(RES-img.height*sc)/2,img.width*sc,img.height*sc);
backdrop.img=c.getContext("2d").getImageData(0,0,RES,RES).data;
backdrop.custom=true;renderScene();
URL.revokeObjectURL(img.src);
};
img.src=URL.createObjectURL(f);
});
document.getElementById("btn-bg-clear").addEventListener("click",()=>{
backdrop.img=null;document.getElementById("bg-file").value="";
renderScene();
});
for(const [id,set] of [
["g-coarse",v=>GAINS.band[0]=v],["g-mid",v=>GAINS.band[1]=v],
["g-fine",v=>GAINS.band[2]=v],["g-op",v=>GAINS.opacity=v]]){
const el=document.getElementById(id);
el.addEventListener("input",()=>{
const v=el.value/100; set(v);
document.getElementById(id+"-v").textContent=v.toFixed(2);
renderScene();
});
}
/* ------------- boot ------------- */
document.getElementById("btn-scramble").addEventListener("click",scramble);
document.getElementById("btn-bind").addEventListener("click",()=>bind(true));
addLayer("tractor",BUILTINS.tractor,false);
addLayer("star",BUILTINS.star,false);
addLayer("boat",BUILTINS.boat,false);
S.layers[0].placed=true;S.layers[0].depth=nextDepth++;S.layers[0].pose=[0.3,-0.25,0.3,Math.log(0.55)];
S.layers[1].placed=true;S.layers[1].depth=nextDepth++;S.layers[1].pose=[-0.4,0.35,-0.9,Math.log(0.5)];
fitStage();
rebuildScene();
</script>
</body>
</html>