PROCITY/tools/qa/r41_postures.mjs
m3ultra 78f49f7113 Lane D R41 §41.3: the town stops walking — 99.3% to 78.1%, at zero draws
THE ROUND IN ONE MEASUREMENT (12 samples x 146 active, ?clips=0 vs default — a new flag that
turns off the library and nothing else): walking 99.3% -> 78.1% · bench-sit 0 -> 9.5% · lean
0 -> 7.1% · stopped in own idle 0.4% -> 5.1% · DISTINCT CLIPS ACROSS THE CROWD 4 -> 20.
The town was 99.3% people walking because standing still had nowhere to happen.

Wiring: new postures.js + clipbank.js. idles.glb (10/10) drives a per-citizen deterministic
idle on every near-tier actor plus the seeded shopkeeper. locomotion gives 33.7% of walkers a
shopping bag. sitlean (8/8, lazy) puts 4 sits on Lane B's ACTUAL benches and 4 leans on
shopfront walls. browse (5/8, lazy) is a real BROWSE state at C's browse points, seeded per
(shopId, slot). venue (5/6, lazy) widens the gig crowd, plus a publican pouring and a record
keeper in headphones. social (0/8) is never fetched — two-person conversation needs a paired
state machine, filed to R42.

Cost: boot = 4 requests, 1.24 MB / 16 clips resident; the rest lazy on first need; heap delta
+3.34 MB; mixer median 0.1 ms both arms. ?clips=0 / ?classic=1 / ?noassets=1 fetch ZERO clips
— not even clipbank.js (dynamic import). No shell edit needed.

DRAWS: +0 on every bookmark (street_noon 193, crossroads 108, night_crowd 128, market_square
94, night_neon 111, interior 110 — identical both arms). Ruling 4 respected exactly.

DETERMINISM: 150 citizens, two fresh contexts, byte-equal posture signature. Controls: seed+1
differs; EVERY clip GLB delayed 2 s -> identical signature (posture is a pure function of
(citySeed, id), never of residency). 6 new streams collide with none of the 12 pre-R41 keys.

TWO FINDINGS THAT CHANGED THE DESIGN: the idle pool was INVISIBLE — wired only to the R17/R29
node loiter, so only 0.8% of citizens were ever stopped; and the lean never fired at all (0 in
a 9 s run). Both moved to the patronage stride check. Bench stations are GATED not trusted:
14/14 derived stations coincide with real instanced geometry within 2 cm, and the control
(same stations offset 2 m) matches 0/14. Filed to B: one benchStops(plan) export retires the
mirror, and furniture.js puts the bench's front ALONG the street rather than facing the road,
contradicting its own comment.

Leak: +0 geometries, +1 texture over 6 enter/exit cycles. Goldens 157,647/157,647, 0x5f76e76.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-07 18:11:30 +10:00

152 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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);