#!/usr/bin/env node // PROCITY Lane D — R41 §41.3 gate: THE POSTURE TABLE IS REAL AND IT IS DETERMINISTIC. // // node tools/qa/r41_postures.mjs 0 = green, 1 = red // // Zero deps, no browser, ~40 ms. Five arms, each with the control that makes it non-vacuous: // // 1. MANIFEST RESOLUTION every clip id `postures.js` can hand out resolves in Lane E's // web/assets/motion_manifest.json, with the category the pool claims and // the group file GROUP_OF claims. CONTROL: a deliberately bogus id is fed // through the same resolver and must fail — otherwise arm 1 proves nothing. // 2. DETERMINISM 1 000 citizen ids, twice, in two freshly-imported module instances → // byte-equal (sha256 of the joined signature block). CONTROL: one different // seed must produce a DIFFERENT digest, or "byte-equal" is just "constant". // 3. STREAM ISOLATION the R41 streams (`posture`, `benchstop`, `leanstop`, `browse-pose`, // `keeper-pose`, `gig-pose`) reproduce the pre-R41 streams' first 8 draws // for `citizen`/`turn`/`loiter`/`patron`/`benchsit`/`glance` unchanged — // i.e. no existing identity moved. CONTROL: they are not all the SAME // stream either (a copy-paste key would pass a naive equality test). // 4. POOL SPREAD the 10-idle pool is actually spread over a real crowd, not collapsed onto // one clip by a bad index. CONTROL: measured occupancy of every bucket. // 5. LOOP SAFETY every pool clip either loops (manifest `loopable`) or has a measured // `loopSeamDeg` the bank will ping-pong (> LOOP_SEAM_DEG) — no clip can be // put on repeat with a seam that pops. CONTROL: the count in each class. import { createHash } from 'node:crypto'; import { readFileSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import { dirname, resolve } from 'node:path'; const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..'); const P = (p) => resolve(ROOT, p); const SEED = 20261990; const LOOP_SEAM_DEG = 25; // clipbank.js — above this the bank plays ping-pong instead of repeat let fails = 0; const OK = (m) => console.log(` \x1b[32m✓\x1b[0m ${m}`); const FAIL = (m) => { fails++; console.log(` \x1b[31m✗ FAIL\x1b[0m ${m}`); }; const head = (m) => console.log(`\n\x1b[1m${m}\x1b[0m`); const check = (c, m) => { (c ? OK : FAIL)(m); return c; }; const sha = (s) => createHash('sha256').update(s).digest('hex').slice(0, 16); const po = await import(P('web/js/citizens/postures.js')); const { rng } = await import(P('web/js/core/prng.js')); const man = JSON.parse(readFileSync(P('web/assets/motion_manifest.json'), 'utf8')); const POOLS = { idle: [po.IDLE_POOL, 'idle'], walk: [po.WALK_POOL, 'locomotion'], sit: [po.SIT_POOL, 'sitlean'], lean: [po.LEAN_POOL, 'sitlean'], browse: [po.BROWSE_POOL, 'browse'], venueDance: [po.VENUE_DANCE_POOL, 'venue'], venueStand: [po.VENUE_STAND_POOL, 'venue'], keeperType: [Object.values(po.KEEPER_TYPE_CLIP), 'venue'], }; // ── 1. manifest resolution ─────────────────────────────────────────────────────────────────────── head('1. MANIFEST RESOLUTION — every posture clip resolves in Lane E\'s motion_manifest.json'); const resolves = (id) => { if (!id || id[0] === '@') return { sentinel: true }; // pre-R41 base asset, not a library clip const m = man.clips[id]; if (!m) return { err: 'not in manifest' }; if (man.groups[m.group] === undefined) return { err: `group ${m.group} not in manifest` }; if (!man.groups[m.group].clips.includes(id)) return { err: `not listed under ${m.group}` }; if (po.GROUP_OF[id] !== m.group) return { err: `GROUP_OF says ${po.GROUP_OF[id]}, manifest says ${m.group}` }; return { ok: true, meta: m }; }; let nClips = 0, nSentinel = 0; for (const [name, [pool, cat]] of Object.entries(POOLS)) { const bad = []; for (const id of pool) { const r = resolves(id); if (r.sentinel) { nSentinel++; continue; } if (r.err) { bad.push(`${id}: ${r.err}`); continue; } if (r.meta.category !== cat) bad.push(`${id}: category ${r.meta.category} != ${cat}`); nClips++; } check(!bad.length, `${name} pool (${pool.length}) → ${bad.length ? bad.join(' · ') : `all resolve, category=${cat}`}`); } check(resolves('idle_definitely_not_a_clip').err === 'not in manifest', 'CONTROL: a bogus clip id fails the same resolver (the arm is not vacuous)'); const distinct = new Set(Object.values(POOLS).flatMap(([p]) => p).filter((i) => i[0] !== '@')); OK(`${nClips} pool entries / ${distinct.size} distinct library clips of the manifest's ${man.clipCount} · ${nSentinel} base-asset sentinels`); const groupsUsed = new Set([...distinct].map((i) => po.GROUP_OF[i])); OK(`groups referenced: ${[...groupsUsed].sort().join(' ')} — boot fetches ${po.BOOT_GROUPS.join(' + ')}, rest lazy`); check(po.BOOT_GROUPS.every((g) => man.groups[g]), 'BOOT_GROUPS all exist in the manifest'); // ── 2. determinism ─────────────────────────────────────────────────────────────────────────────── head('2. DETERMINISM — same seed → same postures, byte-equal across two module instances'); const IDS = []; for (let cx = -2; cx <= 2; cx++) for (let cz = -2; cz <= 2; cz++) for (let i = 0; i < 40; i++) IDS.push(`${cx},${cz}#${i}`); const block = (mod, seed) => IDS.map((id) => mod.postureSig(id, mod.posturesFor(seed, id))).join('\n'); const po2 = await import(P('web/js/citizens/postures.js') + '?fresh=1'); // a second module instance const A = block(po, SEED), B = block(po2, SEED); check(A === B, `${IDS.length} citizens, two module instances → identical (sha256 ${sha(A)})`); const C = block(po, SEED + 1); check(C !== A, `CONTROL: seed ${SEED + 1} differs (sha256 ${sha(C)}) — "byte-equal" is not "constant"`); // the other three pickers const bA = IDS.map((id) => po.browsePostureFor(SEED, 'shop7', +id.split('#')[1] % 3)).join(','); const bB = IDS.map((id) => po2.browsePostureFor(SEED, 'shop7', +id.split('#')[1] % 3)).join(','); check(bA === bB, 'browsePostureFor byte-equal across instances'); check(po.keeperPostureFor(SEED, 'shop7', 'pub') === 'venue_bartending' && po.keeperPostureFor(SEED, 'shop7', 'record') === 'venue_headphones' && po.IDLE_POOL.includes(po.keeperPostureFor(SEED, 'shop7', 'opshop')), 'keeperPostureFor: pub pours, record shop listens, everything else takes a seeded idle'); const gA = IDS.map((id) => po.gigPostureFor(SEED, id, true)).join(','); check(gA === IDS.map((id) => po2.gigPostureFor(SEED, id, true)).join(','), 'gigPostureFor byte-equal across instances'); const swapped = IDS.filter((id) => po.gigPostureFor(SEED, id, true)).length; check(swapped > 0 && swapped < IDS.length, `CONTROL: the gig widening is a SWAP not a replace — ${swapped}/${IDS.length} dancers take the venue clip, the rest keep the v3 dance pick`); // ── 3. stream isolation ────────────────────────────────────────────────────────────────────────── head('3. STREAM ISOLATION — no pre-R41 stream moved, and the new keys are genuinely different'); const draws = (kind, id, n = 8) => { const r = rng(SEED, kind, id); return Array.from({ length: n }, () => r().toFixed(12)).join(','); }; // pre-R41 streams are pure functions of (seed, kind, id) — they cannot move unless a KEY collides. const OLD = ['citizen', 'turn', 'loiter', 'patron', 'benchsit', 'glance', 'chunkpop', 'keeper', 'browser', 'gig', 'gigdance', 'gigp']; const NEW = ['posture', 'benchstop', 'leanstop', 'browse-pose', 'keeper-pose', 'gig-pose']; check(NEW.every((k) => !OLD.includes(k)), `the ${NEW.length} R41 keys collide with none of the ${OLD.length} pre-R41 keys`); const sigs = new Map(); for (const k of [...OLD, ...NEW]) sigs.set(k, draws(k, '3,-1#7')); check(new Set(sigs.values()).size === sigs.size, `CONTROL: all ${sigs.size} streams produce DIFFERENT draws on the same id (no copy-pasted key)`); // and the pre-R41 values themselves, pinned, so a future prng edit shows up here OK(`pinned: rng(${SEED},'citizen','3,-1#7')[0] = ${draws('citizen', '3,-1#7', 1)}`); // ── 4. pool spread ─────────────────────────────────────────────────────────────────────────────── head('4. POOL SPREAD — the clone army is actually broken up, measured'); const hist = {}; for (const id of IDS) { const p = po.posturesFor(SEED, id); hist[p.idle] = (hist[p.idle] || 0) + 1; } const used = Object.keys(hist).length; check(used === po.IDLE_POOL.length, `all ${po.IDLE_POOL.length} idles appear across ${IDS.length} citizens (${used} distinct)`); const counts = po.IDLE_POOL.map((k) => hist[k] || 0); const lo = Math.min(...counts), hi = Math.max(...counts); check(lo >= IDS.length / po.IDLE_POOL.length * 0.6, `spread ${lo}–${hi} per clip (uniform would be ${(IDS.length / po.IDLE_POOL.length).toFixed(0)})`); const bagWalk = IDS.filter((id) => po.posturesFor(SEED, id).walk === 'walk_shopping_bag').length; check(bagWalk > 0, `${bagWalk}/${IDS.length} (${(bagWalk / IDS.length * 100).toFixed(1)}%) carry the shopping bag; the rest keep the base walk.glb gait`); // ── 5. loop safety ─────────────────────────────────────────────────────────────────────────────── head('5. LOOP SAFETY — nothing goes on repeat with a seam that pops'); let repeat = 0, ping = 0; const unsafe = []; for (const id of distinct) { const m = man.clips[id]; if (m.loopable) { repeat++; continue; } if ((m.loopSeamDeg || 0) > LOOP_SEAM_DEG) { ping++; continue; } // curated-not-loopable but the MEASURED seam closes (E's flag is conservative) → repeat is fine if ((m.loopSeamDeg || 0) <= 5) { repeat++; continue; } unsafe.push(`${id} (seam ${m.loopSeamDeg}°)`); } check(!unsafe.length, `${repeat} clips repeat (loopable or measured seam ≤5°) · ${ping} ping-pong (seam >${LOOP_SEAM_DEG}°)${unsafe.length ? ' · UNSAFE: ' + unsafe.join(', ') : ''}`); check(ping > 0, `CONTROL: the ping-pong class is non-empty — ${[...distinct].filter((i) => !man.clips[i].loopable && man.clips[i].loopSeamDeg > LOOP_SEAM_DEG).join(', ')}`); console.log(fails ? `\n\x1b[31m● RED\x1b[0m — ${fails} failure(s)` : '\n\x1b[32m● PASS\x1b[0m — posture table resolves, is deterministic, isolated, spread and loop-safe'); process.exit(fails ? 1 : 0);