Spaces:
Running
Running
File size: 6,758 Bytes
e85709c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 | /* Studio battery — the big-prize claims, tested headless:
1. K=3 bind where the third layer came from the LIVE SERVER (image->layer)
2. depth auto-occlusion: drag star behind tractor -> atoms lose evidence,
marginals honestly uncertain, identity persists; move away -> rebinds
3. painter compositor: the front layer wins where it covers
*/
"use strict";
const fs = require("fs");
const path = require("path");
const C = require("./studio/core.js");
const S = require("./studio/studio_core.js");
const B = JSON.parse(fs.readFileSync(__dirname + "/studio/builtins.json"));
let pass = 0, fail = 0;
const check = (n, ok, d) => { console.log((ok ? "GO " : "FAIL ") + n + (d ? " [" + d + "]" : "")); ok ? pass++ : fail++; };
const templates = [B.tractor, B.star, B.boat];
const K = 3;
function argAssign(marg, prev, evid) {
return marg.map((m, i) => {
if (prev && prev[i] >= 0 && !evid[i]) return prev[i];
let b = 0;
for (let k = 1; k <= K; k++) if (m[k] > m[b]) b = k;
return b === K ? -1 : b;
});
}
// ---- scene: three layers + clutter, seeded
let st = 777; const rand = () => { st = (st * 1103515245 + 12345) & 0x7fffffff; return st / 0x7fffffff; };
const TAU = Math.PI * 2;
const xis = [[0.35, -0.3, 0.9, 0.0], [-0.45, 0.35, -2.1, -0.12], [0.05, 0.45, 0.4, -0.25]];
const obs = [], gt = [];
for (let k = 0; k < K; k++)
for (const a of C.transformAtoms(templates[k], xis[k])) { obs.push(a); gt.push(k); }
for (let i = 0; i < 15; i++) {
const su = 0.04 + 0.08 * rand();
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()]);
gt.push(-1);
}
const NS = [1, 1, 1, .3, .3, 20, 3, .5, .5, .5];
for (const a of 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 * 0.006 * NS[q];
}
for (const a of obs) { a[3] = Math.max(a[3], .012); a[4] = Math.max(a[4], .008); a[5] = Math.max(a[5], .5); }
// ---- 1. dynamic K=3 bind, third template fresh from the generation path
let r = C.bpBind(templates, obs, { iters: 40 });
let evid = obs.map(() => true);
let assign = argAssign(r.marg, null, evid);
{
let ok = 0, okBoat = 0, nBoat = 0;
for (let i = 0; i < gt.length; i++) {
if (assign[i] === gt[i]) ok++;
if (gt[i] === 2) { nBoat++; if (assign[i] === 2) okBoat++; }
}
check("K=3 bind with generated boat layer", ok / gt.length > 0.85,
(ok / gt.length).toFixed(3) + " overall");
check("boat atoms bind to boat", okBoat / nBoat > 0.85, okBoat + "/" + nBoat);
}
// ---- 2. depth auto-occlusion on TRIMMED object layers (studio semantics)
{
const trimmed = templates; // builtins are compact by construction (gray-fit)
const xisT = [[0.3, -0.25, 0.4, -0.6], [0.3, -0.25, -1.0, -0.6], [-0.45, 0.45, 0.2, -0.6]];
const obsT = [], gtT = [];
for (let k = 0; k < K; k++)
for (const a of C.transformAtoms(trimmed[k], xisT[k])) { obsT.push(a); gtT.push(k); }
// star placed ON the tractor; tractor in front by depth
const rT = C.bpBind(trimmed, obsT, { iters: 30 });
let evT = obsT.map(() => true);
let asT = argAssign(rT.marg, null, evT);
const H = 128;
const layers = [
{ depth: 2, hidden: false }, // tractor front
{ depth: 1, hidden: false }, // star behind
{ depth: 0, hidden: false }, // boat back, far away
];
const alphaBufs = layers.map((_, k) =>
S.alphaFromPre(C.renderPre(obsT.filter((_, i) => asT[i] === k), H), H));
const { mask, covFrac } = S.autoHidden(obsT, asT, layers, alphaBufs, H);
check("star (behind, overlapped) loses evidence", covFrac[1] > 0.5,
(100 * covFrac[1]).toFixed(0) + "% of star atoms covered");
check("tractor (front) keeps evidence", covFrac[0] < 0.25,
(100 * covFrac[0]).toFixed(0) + "% covered");
check("boat (far away) keeps evidence", covFrac[2] < 0.1,
(100 * covFrac[2]).toFixed(0) + "% covered");
const wasStar = asT.map(a => a === 1);
const r2 = C.bpBind(trimmed, obsT, {
iters: 15, hiddenMask: mask, clampPose: [null, rT.mu[1], null] });
evT = mask.map(m => !m);
const as2 = argAssign(r2.marg, asT, evT);
const ent = C.atomEntropy(r2.marg);
let hEnt = 0, nH = 0, kept = 0, nWas = 0;
for (let i = 0; i < gtT.length; i++) {
if (mask[i]) { hEnt += ent[i]; nH++; }
if (wasStar[i] && mask[i]) { nWas++; if (as2[i] === 1) kept++; }
}
check("covered atoms honestly uncertain", hEnt / nH > 1.5,
(hEnt / nH).toFixed(2) + " bits");
check("identity persists under the tractor", kept === nWas,
kept + "/" + nWas + " covered star atoms stay star");
// move the star away: coverage releases, full rebind recovers
const g2 = S.gestureTranslate(-0.7, -0.35);
for (let i = 0; i < obsT.length; i++)
if (as2[i] === 1) obsT[i] = C.transformAtoms([obsT[i]], g2)[0];
const alphaBufs2 = layers.map((_, k) =>
S.alphaFromPre(C.renderPre(obsT.filter((_, i) => as2[i] === k), H), H));
const { covFrac: cf2 } = S.autoHidden(obsT, as2, layers, alphaBufs2, H);
check("coverage releases when moved away", cf2[1] < 0.15,
(100 * cf2[1]).toFixed(0) + "%");
const r3 = C.bpBind(trimmed, obsT, { iters: 25 });
const as3 = argAssign(r3.marg, null, obsT.map(() => true));
let ok = 0;
for (let i = 0; i < gtT.length; i++) if (as3[i] === gtT[i]) ok++;
check("re-emergence from behind rebinds", ok / gtT.length > 0.85,
(ok / gtT.length).toFixed(3));
}
// ---- 3. painter compositor: depth order decides the overlap
{
const H = 96;
const red = [[-0.3, 0, 0, 0.28, 0.28, 0.05, 0, 1.5, -1.5, -1.5]];
const blue = [[ 0.3, 0, 0, 0.28, 0.28, 0.05, 0, -1.5, -1.5, 1.5]];
const rgb = [C.renderPre(red, H), C.renderPre(blue, H)];
const env = [S.alphaFromPre(rgb[0], H), S.alphaFromPre(rgb[1], H)];
const pick = (out, x, y) => {
const px = Math.round((x + 1) / 2 * (H - 1)), py = Math.round((y + 1) / 2 * (H - 1));
const i = 4 * (py * H + px);
return [out[i], out[i + 1], out[i + 2]];
};
const blueFront = S.compositePainter([0, 1], rgb, env, H);
const redFront = S.compositePainter([1, 0], rgb, env, H);
const a = pick(blueFront, 0.3, 0), b = pick(blueFront, -0.3, 0);
check("front layer wins at its center", a[2] > 170 && a[0] < 90,
"rgb(" + a.join(",") + ")");
check("back layer shows where uncovered", b[0] > 170 && b[2] < 90,
"rgb(" + b.join(",") + ")");
const m1 = pick(blueFront, 0, 0), m2 = pick(redFront, 0, 0);
check("depth order decides the overlap", m1[2] > m2[2] && m2[0] > m1[0],
"blue-front rgb(" + m1.join(",") + ") vs red-front rgb(" + m2.join(",") + ")");
}
console.log("\n" + pass + " GO, " + fail + " FAIL");
process.exit(fail ? 1 : 0);
|