#!/usr/bin/env python3 """PROCITY Lane D — R42 §42.4 gate: THE RECAST, WIRED. tools/.venv/bin/python tools/qa/r42_cast.py [--seed N] [--outdir DIR] 0 green · 1 red Ruling 1 (PROCITY is 1990s Australia) retired five ped bodies; Lane E sourced five replacements and this gate is the proof that swapping them into `rigs.js` changed the CAST and nothing else. Six arms, each with the control that makes it non-vacuous, every number measured on this tree. 1. CONTRACT the five replacement GLBs read off their own bytes — nodes / skin joints / primitives / materials / images / mime / extensions / baked animations / tris / bone-name set after `_canon` — and then the test that actually matters: replay rigs.js's OWN track filter (canonicalise, keep `.quaternion`, drop `Hips.quaternion`) over all 54 shipped clips and count bindable tracks against the reference ped. CONTROL: `comical_boy_01` (an outgoing reject, still on disk) must score STRICTLY LOWER — otherwise the replay cannot tell a good ped from a bad one. It scores 3 132/3 460, because six of its thumb bones lost the `mixamorig:` prefix, so the instrument discriminates on a real defect. 2. ROSTER fresh default and `?classic=1` contexts: the live fleet's names BY INDEX, the three pool lengths, and a NETWORK sweep of every GLB the boot fetched. None of the five retired bodies may be in the fleet or on the wire. CONTROL: the five replacements are present, at the exact indices the retired five held. 3. DETERMINISM two fresh contexts, same seed: byte-equal IDENTITY signature (which is where `pedIndex` lives) and byte-equal POSTURE signature over the whole active crowd. CONTROL: a different seed differs. This is the R2 fleet-order bug's gate — an in-place swap must leave every index where it was. 4. DRAWS ruling 4 gives the street nine draws. Measured per bookmark, and measured so the crowd's wall-clock drift cannot forge the answer: the citizens group is hidden and the frame re-rendered, so the CITIZEN CONTRIBUTION is a difference, not a total. Asserts one draw per near rig plus one for the entire mid crowd. 5. ANIMATION each replacement spawned BY NAME through the real `makeActor` path, playing walk and then idle: bound-track count, the mixer advancing, the skeleton actually moving, and the R16 flat-body trap checked as spine tilt (head above hips) and stature held across the clip. CONTROL: the reference ped runs the same course. 6. IMPOSTOR the mid-tier billboard against its own near-tier rig at the swap boundary — the R2 failure (a 1.67x exposure mismatch from a double-tone-mapped impostor). Three screenshots per subject (empty / rig / billboard) through the shell's real composer; ped pixels are the ones that differ from empty. CONTROL: the ratio is compared to a SHIPPED ped's ratio, so the claim is "as matched as it ever was" rather than an invented tolerance. Fresh headless contexts, own no-store server (this project's documented ES-module cache burn). """ import sys, os, re, io, json, time, math, struct, socket, subprocess, pathlib ROOT = pathlib.Path(__file__).resolve().parent.parent.parent PORT = int(os.environ.get('PROCITY_R42_PORT', '8992')) HOST = f'http://127.0.0.1:{PORT}' SEED = 20261990 OUTDIR = ROOT / 'docs' / 'shots' / 'laneD' if '--seed' in sys.argv: SEED = int(sys.argv[sys.argv.index('--seed') + 1]) if '--outdir' in sys.argv: OUTDIR = pathlib.Path(sys.argv[sys.argv.index('--outdir') + 1]) PEDS = ROOT / 'web' / 'models' / 'peds' CLIPS = ROOT / 'web' / 'models' / 'clips' REF = 'man_casual_01' BASE_CLIPS = ['walk', 'idle', 'sit', 'look', 'dance_party', 'dance_medium', 'dance_drink', 'dance_sway'] # the swap, as wired in web/js/citizens/rigs.js — pool, index, out, in SWAP = [('normal', 6, 'man_elder_01', 'man_casual_04'), ('normal', 9, 'man_soldier_ww2_01', 'person_casual_01'), ('djs', 4, 'dj_phrtt_01', 'man_smart_01'), ('comical', 0, 'comical_luchador_01', 'woman_smart_02'), ('comical', 1, 'comical_boy_01', 'person_youth_01')] RETIRED = [o for _, _, o, _ in SWAP] NEW = [n for _, _, _, n in SWAP] BAD_CONTROL = 'comical_boy_01' # on disk, retired, and measurably non-contract (thumb bones) POOL_LEN = {'normal': 17, 'djs': 5, 'comical': 2} BOOKMARKS = ['street_noon', 'crossroads_busy', 'night_crowd', 'night_neon', 'district_posters', 'venue_night', 'market_square', 'patronage_door'] fails = [] def FAIL(m): fails.append(m); print(f" \033[31m✗ FAIL\033[0m {m}") def OK(m): print(f" \033[32m✓\033[0m {m}") def head(m): print(f"\n\033[1m{m}\033[0m") def note(m): print(f" \033[33m·\033[0m {m}") def check(c, m): (OK if c else FAIL)(m); return c NOSTORE = r''' import sys, http.server, functools class H(http.server.SimpleHTTPRequestHandler): def end_headers(self): self.send_header('Cache-Control', 'no-store, no-cache, must-revalidate') super().end_headers() def log_message(self, *a): pass http.server.HTTPServer(('127.0.0.1', int(sys.argv[1])), functools.partial(H, directory=sys.argv[2])).serve_forever() ''' # ══ ARM 1: the contract, read off the GLB bytes (no browser, no deps) ═══════════════════════════════ _canon = lambda s: re.sub(r'mixamorig\d+', 'mixamorig', s or '') # glTF animation channel path -> the suffix three's GLTFLoader gives the track. rigs.js `_rotOnly` # filters on `.quaternion`, which is three's name for glTF `rotation` — comparing against the raw # glTF path finds nothing at all and silently "passes". _PATH = {'rotation': 'quaternion', 'translation': 'position', 'scale': 'scale'} def gltf(path): d = path.read_bytes() if d[:4] != b'glTF': raise ValueError(f'{path} is not a GLB') n = struct.unpack(' keep .quaternion -> drop Hips.quaternion.""" tot = bound = 0; worst = None; unb = set() for nm, tr in corpus: keep = [t for t in tr if t.endswith('.quaternion') and not re.search(r'Hips\.quaternion$', t, re.I)] b = sum(1 for t in keep if t.rsplit('.', 1)[0] in ped['nodeSet']) unb |= {t.rsplit('.', 1)[0] for t in keep if t.rsplit('.', 1)[0] not in ped['nodeSet']} tot += len(keep); bound += b if worst is None or b < worst[1]: worst = (nm, b) return tot, bound, worst, sorted(unb) def arm_contract(): head('1. CONTRACT — the five replacements measured off their own GLB bytes') ref = ped_facts(REF) corpus = clip_corpus() rt, rb, rw, ru = bind_score(ref, corpus) note(f"reference {REF}: {ref['nodes']} nodes / {ref['joints']} joints / {ref['prims']} prim / " f"{ref['mats']} mtl / {ref['imgs']} img {ref['mime']} / ext {ref['ext']} / {ref['anims']} anims / " f"{ref['tris']} tris") note(f"clip corpus: {len(corpus)} clips ({len(BASE_CLIPS)} base + {len(list(CLIPS.glob('*.glb')))} " f"library GLBs) → reference binds {rb}/{rt}, worst single clip {rw[1]}, unbound {ru}") rows = [] for n in NEW: p = PEDS / (n + '.glb') if not p.exists(): FAIL(f'{n}.glb is not on the tree'); continue f = ped_facts(n) t, b, w, u = bind_score(f, corpus) rows.append((f, t, b, w)) same = f['jnames'] == ref['jnames'] shape = (f['nodes'] == ref['nodes'] and f['joints'] == ref['joints'] and f['prims'] == 1 and f['mats'] == 1 and f['imgs'] == 1 and f['mime'] == ref['mime'] and f['anims'] == 0 and 'KHR_draco_mesh_compression' not in f['ext']) check(shape and same and (b, t) == (rb, rt) and w[1] == rw[1], f"{n}: {f['nodes']}/{f['joints']} nodes/joints · {f['prims']}/{f['mats']}/{f['imgs']} " f"prim/mtl/img {f['mime'][0] if f['mime'] else '-'} · {f['anims']} baked anims · " f"{f['tris']} tris · boneset {'IDENTICAL' if same else 'DIFFERENT'} · binds {b}/{t} " f"(worst clip {w[1]}) — {'contract-clean' if shape and same else 'DEVIATES'}") # CONTROL: the instrument must be able to fail if (PEDS / (BAD_CONTROL + '.glb')).exists(): bf = ped_facts(BAD_CONTROL) bt, bb, bw, bu = bind_score(bf, corpus) check(bb < rb, f"CONTROL: retired {BAD_CONTROL} scores {bb}/{bt} (worst clip {bw[1]}) — STRICTLY " f"LOWER than the reference's {rb}/{rt}. Unbound: {bu[:3]}… so the replay " f"discriminates; it was a real, silent contract break on a shipped ped.") else: note(f'{BAD_CONTROL}.glb not on disk — bind-score control unavailable') if rows: note('tri band ' + ' · '.join(f"{f['name']} {f['tris']}" for f, *_ in rows)) return {'ref': {k: ref[k] for k in ('nodes', 'joints', 'prims', 'mats', 'imgs', 'tris')}, 'refBind': [rb, rt], 'rows': [{'name': f['name'], 'tris': f['tris'], 'bind': [b, t]} for f, t, b, w in rows]} # ══ browser plumbing ════════════════════════════════════════════════════════════════════════════════ def port_up(p): with socket.socket() as s: s.settimeout(0.4); return s.connect_ex(('127.0.0.1', p)) == 0 def serve(): pr = subprocess.Popen([sys.executable, '-c', NOSTORE, str(PORT), str(ROOT / 'web')], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) for _ in range(80): if port_up(PORT): return pr time.sleep(0.1) pr.terminate(); raise SystemExit(f'could not serve on :{PORT}') def new_page(p): b = p.chromium.launch() pg = b.new_page(viewport={'width': 1280, 'height': 720}) errs, reqs = [], [] pg.on('console', lambda m: errs.append(m.text) if m.type == 'error' else None) pg.on('pageerror', lambda e: errs.append(str(e))) pg.on('request', lambda r: reqs.append(r.url)) return b, pg, errs, reqs def boot(pg, q='', fleet=True): pg.goto(f'{HOST}/index.html?seed={SEED}' + (('&' + q) if q else '') + '&dbg=1') pg.wait_for_function('window.DBG && window.DBG.ready === true', timeout=45000) pg.evaluate("() => { const o=document.getElementById('pc-start'); if(o) o.style.display='none'; }") if fleet: pg.wait_for_function('() => window.PROCITY.fleet && window.PROCITY.fleet.ready', timeout=25000) ROSTER = """() => { const f = window.PROCITY.fleet; return { normal: f.normal.map(r => r.pedName), comical: f.comical.map(r => r.pedName), all: f.all.map(r => r.pedName) }; }""" # max draws over n natural frames, then the same frame with the citizens hidden: the difference IS the # crowd's draw cost, and it is immune to the wall-clock drift that makes raw totals wobble +-3. DRAW_SPLIT = r""" async (n) => { const P = window.PROCITY, C = P.citizens; let mx = 0, mt = 0, near = 0, mid = 0; for (let i = 0; i < n; i++) { await new Promise(r => requestAnimationFrame(() => r())); const r = P.renderer.info.render; if (r.calls > mx) { mx = r.calls; mt = r.triangles; near = C.stats.rigged; mid = C.stats.mid; } } // measure the same pose twice more: crowd in, crowd out. Nothing else changes between them. const frame = async () => { await new Promise(r => requestAnimationFrame(() => r())); return P.renderer.info.render.calls; }; const withCrowd = await frame(); const nearNow = C.stats.rigged, midNow = C.stats.mid; C.group.visible = false; const without = await frame(); C.group.visible = true; await frame(); return { max: mx, tris: mt, nearAtMax: near, midAtMax: mid, withCrowd, without, near: nearNow, mid: midNow, budget: (P.budget || {}).draws || 300 }; } """ # each replacement spawned by name through the real makeActor path and actually animated ANIM = r""" async (names) => { const P = window.PROCITY, THREE = P.THREE, fleet = P.fleet; const rigs = await import('./js/citizens/rigs.js'); const out = []; for (const name of names) { const rig = fleet.all.find(r => r.pedName === name); if (!rig) { out.push({ name, missing: true }); continue; } const a = rigs.makeActor(rig, { walkClip: fleet.walkClip, idleClip: fleet.idleClip, nominalHeight: 1.75 }); if (!a) { out.push({ name, noActor: true }); continue; } const stage = new THREE.Group(); stage.add(a.fig); P.scene.add(stage); const bones = []; a.inner.traverse(o => { if (o.isBone) bones.push(o); }); const v = new THREE.Vector3(); const sample = () => { a.fig.updateWorldMatrix(true, true); let crown = 0, foot = Infinity, hips = null, head = null, pts = []; for (const o of bones) { o.getWorldPosition(v); if (/head/i.test(o.name)) crown = Math.max(crown, v.y); if (/Hips$/i.test(o.name)) hips = v.y; if (/Head$/i.test(o.name) && head === null) head = v.y; foot = Math.min(foot, v.y); pts.push(v.x, v.y, v.z); } return { crown, foot, hips, head, stature: crown - foot, pts }; }; const legs = []; for (const [mode, moving] of [['walk', true], ['idle', false]]) { a.setMoving(moving, 0); a.mixer.update(0.001); const s0 = sample(); let travel = 0, minStat = s0.stature, maxStat = s0.stature, minTilt = 1e9; const act = (a.mixer._actions || []).filter(x => x.getEffectiveWeight() > 0.5)[0] || null; const t0 = act ? act.time : null; let prev = s0.pts; for (let k = 0; k < 24; k++) { a.mixer.update(1 / 30); const s = sample(); let d = 0; for (let i = 0; i < prev.length; i++) d += Math.abs(s.pts[i] - prev[i]); travel += d; prev = s.pts; minStat = Math.min(minStat, s.stature); maxStat = Math.max(maxStat, s.stature); if (s.hips != null && s.head != null) minTilt = Math.min(minTilt, s.head - s.hips); } const tEnd = act ? act.time : null; legs.push({ mode, clip: act ? act.getClip().name : null, tracks: act ? act.getClip().tracks.length : 0, advanced: (t0 != null && tEnd != null) ? +(tEnd - t0).toFixed(3) : null, travel: +travel.toFixed(3), minStature: +minStat.toFixed(3), maxStature: +maxStat.toFixed(3), minHeadAboveHips: +minTilt.toFixed(3) }); } out.push({ name, height: a.height || 1.75, legs }); P.scene.remove(stage); a.dispose && a.dispose(); } return out; } """ # stage one subject at a fixed spot in front of the camera, as a near RIG or as a mid BILLBOARD, using # the LIVE atlas the sim baked. Rendered by the shell's own composer, so the tone-map path is the real one. IMPOSTOR_STAGE = r""" async ({ name, kind }) => { const P = window.PROCITY, THREE = P.THREE, C = P.citizens, fleet = P.fleet; const rigs = await import('./js/citizens/rigs.js'); const impM = await import('./js/citizens/impostor.js'); if (window.__d42) { P.scene.remove(window.__d42.stage); if (window.__d42.layer) { window.__d42.layer.mesh.geometry.dispose(); window.__d42.layer.material.dispose(); } if (window.__d42.actor && window.__d42.actor.dispose) window.__d42.actor.dispose(); window.__d42 = null; } C.group.visible = (kind === 'crowd'); if (kind === 'empty' || kind === 'crowd') return { ok: true, kind }; const idx = fleet.all.findIndex(r => r.pedName === name); if (idx < 0) return { ok: false, why: 'not in fleet' }; const cam = P.camera; cam.updateMatrixWorld(true); const fwd = new THREE.Vector3(0, 0, -1).applyQuaternion(cam.quaternion); fwd.y = 0; fwd.normalize(); const D = 7.0, H = 1.75; const x = cam.position.x + fwd.x * D, z = cam.position.z + fwd.z * D; const groundY = cam.position.y - 1.62; // DBG's eye height above the deck const facing = Math.atan2(-(cam.position.x - x), -(cam.position.z - z)); // front (-Z) toward the lens const stage = new THREE.Group(); P.scene.add(stage); const rec = { stage, layer: null, actor: null }; if (kind === 'rig') { const a = rigs.makeActor(fleet.all[idx], { walkClip: fleet.walkClip, idleClip: fleet.idleClip, nominalHeight: H }); a.setMoving(true, 0); a.mixer.update(0.35 + idx * 0.017); // the EXACT pose sim.js bakes into the atlas a.fig.position.set(x, groundY, z); a.fig.rotation.y = facing; stage.add(a.fig); rec.actor = a; } else { const layer = new impM.ImpostorLayer(C.impostor.atlas, { maxInstances: 2 }); layer.mesh.position.copy(C.group.position); // the live layer rides the citizens group stage.add(layer.mesh); layer.update([{ x, z, groundY, height: H, subject: idx, facing }], cam); rec.layer = layer; } window.__d42 = rec; // The measurement BOX: the billboard's own quad (h*1.14 square, feet at -0.07h — impostor.js's own // framing constants) projected to screen. Cropping to it is what keeps a chunk streaming in at the // far end of the street out of a luminance number about one ped; an unbounded full-frame difference // reported up to 68 000 "ped" pixels when a distant block popped between two shots. const right = new THREE.Vector3().crossVectors(new THREE.Vector3(0, 1, 0), fwd).normalize(); const half = H * 1.14 * 0.5, W = window.innerWidth, Hp = window.innerHeight; let x0 = 1e9, y0 = 1e9, x1 = -1e9, y1 = -1e9; for (const sx of [-1, 1]) for (const sy of [0, 1]) { const w = new THREE.Vector3(x + right.x * half * sx, groundY - H * 0.07 + H * 1.14 * sy, z + right.z * half * sx).project(cam); const px = (w.x * 0.5 + 0.5) * W, py = (1 - (w.y * 0.5 + 0.5)) * Hp; x0 = Math.min(x0, px); x1 = Math.max(x1, px); y0 = Math.min(y0, py); y1 = Math.max(y1, py); } const M = 10; return { ok: true, kind, idx, box: [Math.max(0, Math.floor(x0 - M)), Math.max(0, Math.floor(y0 - M)), Math.min(W, Math.ceil(x1 + M)), Math.min(Hp, Math.ceil(y1 + M))] }; } """ def crop_stats(png_a, png_b, box): """mean luminance of the pixels in `box` where b differs from a — i.e. the staged subject only. Differencing against an EMPTY frame is what makes this a measurement of the ped rather than of the street behind it: the background is identical in both shots, so every surviving pixel belongs to the thing that was staged.""" from PIL import Image, ImageChops, ImageStat a = Image.open(io.BytesIO(png_a)).convert('RGB').crop(box) b = Image.open(io.BytesIO(png_b)).convert('RGB').crop(box) d = ImageChops.difference(a, b).convert('L').point(lambda v: 255 if v > 8 else 0) st = ImageStat.Stat(b.convert('L'), d) n = st.count[0] return (st.mean[0] if n else 0.0), n, b, d def main(): OUTDIR.mkdir(parents=True, exist_ok=True) from playwright.sync_api import sync_playwright report = {'seed': SEED} report['contract'] = arm_contract() srv = serve() try: with sync_playwright() as p: # ── 2. roster + period law ──────────────────────────────────────────────────────────── head('2. ROSTER — the swap is wired, in place, and nothing retired is on the wire') b, pg, errs, reqs = new_page(p) boot(pg) pg.wait_for_timeout(2500) r = pg.evaluate(ROSTER) fetched = sorted({u.rsplit('/', 1)[-1].split('?')[0] for u in reqs if '/models/peds/' in u and u.endswith('.glb')}) report['roster'] = {'default': r, 'fetched': fetched} live = set(r['all']) check(not (live & set(RETIRED)), f"default boot fleet ({len(r['all'])} bodies) holds NONE of the five retired: " f"{', '.join(RETIRED)}") onwire = sorted(set(RETIRED) & {f[:-4] for f in fetched}) check(not onwire, f'default boot fetched {len(fetched)} ped GLBs and none of them is a retired body ' f'(dj_phrtt_01 on the wire: {"YES" if "dj_phrtt_01.glb" in fetched else "no"})') check(all(n in live for n in NEW), f"all five replacements present: {', '.join(NEW)}") # `fleet.normal` is PED_NAMES.normal ++ PED_NAMES.djs (R36 w2.5 appends the gated tail), # and `fleet.all` is that ++ comical — so the three source pools are slices of the live one. pools = {'normal': r['normal'][:POOL_LEN['normal']], 'djs': r['normal'][POOL_LEN['normal']:], 'comical': r['comical']} for pool, i, out, inn in SWAP: lst = pools[pool] got = lst[i] if len(lst) > i else None allIdx = r['all'].index(got) if got in r['all'] else None check(got == inn, f"{pool}[{i}] (fleet.all[{allIdx}]): {out} → {got} (expected {inn})") check(len(r['normal']) == POOL_LEN['normal'] + POOL_LEN['djs'] and len(r['comical']) == POOL_LEN['comical'] and len(r['all']) == 24, f"pool lengths unmoved: normal+djs {len(r['normal'])} (17+5) · comical " f"{len(r['comical'])} · fleet.all {len(r['all'])} — every pickRig index is where it was") check(not errs, f'default boot: 0 console errors ({len(reqs)} requests swept)') b.close() b, pg, errs, reqs = new_page(p) boot(pg, 'classic=1') pg.wait_for_timeout(2500) rc = pg.evaluate(ROSTER) cf = sorted({u.rsplit('/', 1)[-1].split('?')[0] for u in reqs if '/models/peds/' in u and u.endswith('.glb')}) report['roster']['classic'] = {'roster': rc, 'fetched': cf} check(len(rc['normal']) == POOL_LEN['normal'] and not (set(rc['all']) & set(RETIRED)), f"?classic=1: covenanted {len(rc['normal'])}-body pool, no retired body " f"({len(rc['all'])} in fleet.all)") check('dj_phrtt_01.glb' not in cf and not (set(RETIRED) & {f[:-4] for f in cf}), f'?classic=1 fetched {len(cf)} ped GLBs, none retired') check(not errs, '?classic=1: 0 console errors') b.close() # ── 3. determinism ──────────────────────────────────────────────────────────────────── head('3. DETERMINISM — same seed → same crowd, across fresh contexts (the R2 gate)') sigs = [] for _ in range(2): b, pg, errs, reqs = new_page(p) boot(pg) pg.evaluate("() => window.DBG.shot('street_noon')") pg.wait_for_timeout(3000) sigs.append((pg.evaluate("() => window.PROCITY.citizens.identitySignature().join('\\n')"), pg.evaluate("() => window.PROCITY.citizens.postureSignature().join('\\n')"))) b.close() n = len(sigs[0][0].splitlines()) check(sigs[0][0] == sigs[1][0] and n > 0, f'{n} active citizens, two fresh contexts → byte-equal IDENTITY signature ' f'(id:pedIndex:pvar:height:speed:edge:forward)') check(sigs[0][1] == sigs[1][1], 'and byte-equal POSTURE signature') report['determinism'] = {'n': n, 'idsig_equal': sigs[0][0] == sigs[1][0], 'psig_equal': sigs[0][1] == sigs[1][1]} b, pg, errs, reqs = new_page(p) pg.goto(f'{HOST}/index.html?seed={SEED + 1}&dbg=1') pg.wait_for_function('window.DBG && window.DBG.ready === true', timeout=45000) pg.evaluate("() => { const o=document.getElementById('pc-start'); if(o) o.style.display='none'; }") pg.evaluate("() => window.DBG.shot('street_noon')") pg.wait_for_timeout(3000) other = pg.evaluate("() => window.PROCITY.citizens.identitySignature().join('\\n')") check(other != sigs[0][0], f'CONTROL: seed {SEED + 1} gives a DIFFERENT identity signature ' f'({len(other.splitlines())} citizens) — byte-equal is not constant') b.close() # ── 4. draws ────────────────────────────────────────────────────────────────────────── head('4. DRAWS — the crowd\'s cost measured as a difference, not a total (ruling 4)') b, pg, errs, reqs = new_page(p) boot(pg) pg.wait_for_timeout(6000) print(f" {'bookmark':<20} {'max':>5} {'budget':>7} {'crowd in':>9} {'crowd out':>10} " f"{'Δ':>4} {'near':>5} {'mid':>4} {'expected':>9}") dtab = {} for bm in BOOKMARKS: pg.evaluate('(n) => window.DBG.shot(n)', bm) pg.wait_for_timeout(2500) pg.evaluate('(n) => window.DBG.shot(n)', bm) d = pg.evaluate(DRAW_SPLIT, 24) dtab[bm] = d exp = d['near'] + (1 if d['mid'] else 0) got = d['withCrowd'] - d['without'] print(f" {bm:<20} {d['max']:>5} {d['budget']:>7} {d['withCrowd']:>9} {d['without']:>10} " f"{got:>4} {d['near']:>5} {d['mid']:>4} {exp:>9}") check(d['max'] <= d['budget'], f'{bm}: {d["max"]} draws ≤ budget {d["budget"]}') check(got == exp, f'{bm}: the whole crowd costs {got} draws = {d["near"]} near rigs × 1 + ' f'{1 if d["mid"] else 0} instanced billboard layer for {d["mid"]} mid citizens') report['draws'] = dtab note('one primitive / one material per ped (arm 1) ⇒ a one-for-one swap cannot move this ' 'number; the totals wobble only with which citizens the wall clock has walked into frame') b.close() # ── 5. animation ────────────────────────────────────────────────────────────────────── head('5. ANIMATION — every new body plays walk and idle, and neither folds it') b, pg, errs, reqs = new_page(p) boot(pg) pg.wait_for_timeout(2500) anim = pg.evaluate(ANIM, NEW + [REF]) report['anim'] = anim for a in anim: if a.get('missing') or a.get('noActor'): FAIL(f"{a['name']}: could not be spawned ({a})"); continue bad = [] for leg in a['legs']: if leg['tracks'] != 64: bad.append(f"{leg['mode']} bound {leg['tracks']} tracks (want 64)") if not leg['advanced']: bad.append(f"{leg['mode']} mixer did not advance") if leg['travel'] < 1.0: bad.append(f"{leg['mode']} skeleton barely moved ({leg['travel']})") if leg['minStature'] < 0.85 * a['height']: bad.append( f"{leg['mode']} FOLDED — stature fell to {leg['minStature']}m of {a['height']}m") if leg['minHeadAboveHips'] < 0.35: bad.append( f"{leg['mode']} FLAT BODY — head only {leg['minHeadAboveHips']}m above hips") det = ' · '.join(f"{l['mode']} {l['tracks']}tr Δt{l['advanced']}s travel {l['travel']} " f"stature {l['minStature']}–{l['maxStature']}m head+{l['minHeadAboveHips']}m" for l in a['legs']) check(not bad, f"{a['name']} ({a['height']}m): {det}" + (' ⟵ ' + '; '.join(bad) if bad else '')) b.close() # ── 6. impostor vs rig at the swap boundary ─────────────────────────────────────────── head('6. IMPOSTOR — the mid billboard against its own near rig (the R2 exposure trap)') b, pg, errs, reqs = new_page(p) boot(pg) # settle hard before measuring: the bookmark pauses the day cycle, but the sky/PMREM and # the exposure ramp reach steady state on their own clock, and ACES makes a global exposure # change a NON-linear shift in any ratio measured off the canvas. Every subject in this arm # is therefore measured inside ONE run, and the claim is a within-run comparison against the # bodies that did not change — absolute ratios move a little run to run; that comparison does not. pg.evaluate("() => window.DBG.shot('street_noon')") pg.wait_for_timeout(4000) pg.evaluate("() => window.DBG.shot('street_noon')") pg.wait_for_timeout(8000) # Measure EVERY body in the fleet, not just the new five. The bake lights the ped with its # own key+hemi+env and the street lights it with the sun, so a billboard/rig ratio of # exactly 1.000 was never the contract — what the R2 fix bought is that the ratio is the # SAME KIND of number for every ped on the same code path. So the 19 unchanged bodies are # the control population, and the question this arm answers is the only one that matters # at a swap boundary: do the new five sit inside the spread the town already ships? roster = pg.evaluate(ROSTER)['all'] imp = {} strips = [] for name in roster: pg.evaluate(IMPOSTOR_STAGE, {'name': name, 'kind': 'empty'}) pg.wait_for_timeout(320) empty = pg.screenshot(type='png') st = pg.evaluate(IMPOSTOR_STAGE, {'name': name, 'kind': 'rig'}) if not st.get('ok'): FAIL(f"{name}: could not stage a rig ({st.get('why')})"); continue BOX = tuple(st['box']) pg.wait_for_timeout(320) rig_png = pg.screenshot(type='png') pg.evaluate(IMPOSTOR_STAGE, {'name': name, 'kind': 'billboard'}) pg.wait_for_timeout(320) bill_png = pg.screenshot(type='png') lr, nr, rig_img, _ = crop_stats(empty, rig_png, BOX) lb, nb, bill_img, _ = crop_stats(empty, bill_png, BOX) imp[name] = {'rigLum': round(lr, 2), 'rigPx': nr, 'billLum': round(lb, 2), 'billPx': nb, 'ratio': round(lb / lr, 4) if lr else None, 'new': name in NEW} if name in NEW or name == REF: strips.append((name, rig_img, bill_img)) note(f"{name:<20}{'NEW ' if name in NEW else ' '} rig {lr:6.2f} ({nr:5d} px) · " f"billboard {lb:6.2f} ({nb:5d} px) · ratio {imp[name]['ratio']}") pg.evaluate(IMPOSTOR_STAGE, {'name': REF, 'kind': 'crowd'}) report['impostor'] = imp ctrl = sorted(v['ratio'] for k, v in imp.items() if not v['new'] and v['ratio']) newr = sorted(imp[n]['ratio'] for n in NEW if imp.get(n) and imp[n]['ratio']) if len(ctrl) >= 10 and len(newr) == len(NEW): lo, hi, med = ctrl[0], ctrl[-1], ctrl[len(ctrl) // 2] nmed = newr[len(newr) // 2] note(f'CONTROL POPULATION — the {len(ctrl)} bodies this round did NOT touch span ' f'{lo:.3f}–{hi:.3f} (median {med:.3f}); the bake is lit by its own key+hemi+env and ' f'the street by the sun, so a per-albedo spread is the normal state of this seam and ' f'1.000 was never the contract') # (a) no SYSTEMATIC shift — that is what R2's 1.67x actually was: every ped at once check(0.85 <= nmed / med <= 1.18, f'no systematic shift: the five new bodies\' median ratio {nmed:.3f} vs the ' f'untouched roster\'s {med:.3f} = {nmed / med:.2f}x (R2\'s break was 1.67x on ' f'the whole fleet)') # (b) and no individual outlier beyond what the town already ships for name in NEW: v = imp[name] check(lo <= v['ratio'] <= hi, f"{name}: billboard/rig luminance {v['ratio']:.3f} sits inside the untouched " f"roster's own {lo:.3f}–{hi:.3f} — the swap does not widen the near↔mid seam") worst = max(imp.items(), key=lambda kv: abs(math.log(kv[1]['ratio'] or 1))) note(f"worst-matched body in the whole roster: {worst[0]} at {worst[1]['ratio']:.3f} " f"({'NEW' if worst[1]['new'] else 'shipped before this round'})") else: FAIL('control population too small — arm 6 is vacuous') if strips: from PIL import Image, ImageDraw CW, CH = 240, 268 # per-subject crops differ by a pixel or two sheet = Image.new('RGB', (CW * 2, (CH + 16) * len(strips)), (16, 16, 18)) dr = ImageDraw.Draw(sheet) for i, (nm, ri, bi) in enumerate(strips): y = i * (CH + 16) + 16 sheet.paste(ri.resize((CW, CH)), (0, y)); sheet.paste(bi.resize((CW, CH)), (CW, y)) dr.text((6, y - 13), f"{nm} LEFT near rig | RIGHT mid billboard " f"billboard/rig luminance {imp[nm]['ratio']}", fill=(255, 230, 120)) sheet.save(OUTDIR / 'r42_impostor_boundary.jpg', quality=88) note(f"contact strip → {OUTDIR / 'r42_impostor_boundary.jpg'}") check(not errs, f'impostor arm: 0 console errors') b.close() finally: srv.terminate() (OUTDIR / 'r42_cast.json').write_text(json.dumps(report, indent=1)) print() if fails: print(f"\033[31m● RED\033[0m — {len(fails)} failure(s)") for f in fails: print(' ' + f) return 1 print("\033[32m● PASS\033[0m — contract, roster, determinism, draws, animation and the impostor " "boundary all green") return 0 if __name__ == '__main__': sys.exit(main())