PROCITY/tools/flags_check.py
m3ultra 29942002eb Lane F R24 (v5.0-alpha): THE CRATE WALKS — the per-shop wire + a gate that can't lie
You can boot redhill_godverse, walk down Musgrave Road into Monster Robot Party, riffle its
own real crate and buy a real record. Proven falsifiably, not by eyeball:
  base      assets/stock_godverse/3962749/ (its own, not the town-wide)
  TITLES    120/120 the shop's atlas · 0/120 the generic pack
  TEXTURES  2 from stock_godverse/3962749/*.webp · 0 generic — the pixels' own file
  BUY       rec_0000 "Abstract Latin Lounge II (Part II)" @ $15 → cash 191→176, exact
  REBOOT    191/0 restored — gone-this-session, back next boot
Ladder: ?noassets → ZERO network requests · keyed-but-atlas-less shop → 404 → parody, live ·
newtown_real → base null, 350 items: the wire is a NO-OP off the godverse path.
flags_check GREEN (0 fails, 0 warns).

#6a THE WIRE: godverseBaseFor(shop), exported and used by BOTH the shell's boot preload and
the room's lookup — C's cache identity is the LITERAL (type, base) pair, so two sides building
that string differently would drop a real shop to parody, a bug indistinguishable from "no
stock" by eye. One function, one string, one key. Gated on stockReal, never the id alone.

#6b THE GATE: smoke_real_crate + DBG.stockInfo() — permanent, and it cannot pass without
touching its subject (SKIPs with a printed reason when the town/atlas is absent).

FINDING — THE BRIEF'S OWN HONEST-GATE SPEC WAS VACUOUS. It specified id equality. Both packs
number items POSITIONALLY (rec_0000…), so all 120 atlas ids are generic-pack ids too: an
id-equality gate goes GREEN on the generic pack — the exact failure the vacuous-gate law was
written to stop, reproduced inside its first application. Ids there are slot numbers, not
identity. Titles (0 overlap) and the texture URL discriminate; F asserts on those.

FINDING — THE ID SPACES COLLIDE, and it bit F's own selector inside the anti-vacuous gate.
Plan ids (1..N) and godverse ids (31, 767, 3962749) share a numeric space: Silky Oak's godverse
id is 31 and another shop's PLAN id is 31, so enterShop(31) silently entered "Wholefood Cafe"
and the soft-fall assertion PASSED ON AN UNKEYED CAFE. Fixed both ends: 'g:<n>' selects a
godverse id explicitly, a bare number stays the plan id and now REPORTS the collision, and the
smoke asserts it landed in the shop it asked for before asserting anything about it.

FINDING — F'S OWN GATE CRIED THE LOUDEST WOLF WE OWN. classic_regression reported "plan
fingerprint != golden 0x3fa36874 — determinism/prime-law broken" while the covenant was
provably intact. F wrote it in R16 as `hash_ok and returncode == 0` — two assertions, ONE
verdict, reported with the fingerprint's message — so any unrelated selfcheck red screamed that
the byte-identical-forever covenant had broken. Two assertions, two verdicts now. It isn't
enough to fail for a reason; you have to fail for YOUR reason.

FINDING — fail-soft-by-404 vs the 0-console-errors law. The boot preload must resolve a keyed
shop's pack before the door opens (getStockPack is sync), so it probes blind: 9 of Red Hill's
10 keyed shops have no atlas → 404 → parody. That IS fail-soft working, and it is also 8
console errors no .catch() can suppress. Fix is not to loosen the law but to stop probing for
what isn't there: an atlas index in G's namespace (or hasStock on the cache entry, A+G). Filed
as a contract ask, not invented. Until then the smoke counts and NAMES the probes and fails on
any other error — tolerance stated out loud.

Verified the lift is clean: cache 10 keyed → plan 9 keyed, every one a real secondhand shop.
No cafe keyed, zero duplicate ids — there is no mis-stocking.

F did NOT touch A's identity sweep or E's validator. F was the party that would benefit, and
changing a red gate to green in another lane's file to unblock F's own tag would retroactively
devalue every green F has ever reported. Filed instead — and R25 has since landed A's
reconciliation and the six pins, which is the right hands doing it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 08:43:29 +10:00

1230 lines
75 KiB
Python
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 python3
"""PROCITY Lane F — v2 flag enforcement harness (round-6 F1).
The machine version of what the R5 review did by hand. Three things, headless, <2 min:
1. FLAGS-OFF REGRESSION — the enforcement arm of the prime law. Boot with no flags → the plan
fingerprint must equal the golden 0x3fa36874, the settled street view must stay within the v1
budget (≤300 draws / ≤200k tris) AND within tolerance of the v1.1 snapshot, every v2 subsystem
must report inactive, and there must be ZERO console errors. A v2 flag leaking into the default
boot trips this.
2. PER-FLAG SMOKE — for each landed flag (winmap, dig, roster, +plansrc when A lands): boot flag-on,
cross ≥2 chunks, enter one interior (dig: also open+close a riffle), 0 console errors.
3. ALL-ON COMBO (flag-composition law, decision #4): every landed flag on at once, same smoke.
Run: cd web && python3 -m http.server 8130 (or let this script spawn its own)
tools/.venv/bin/python tools/flags_check.py
Wired into tools/qa.sh as a WARN-level gate this round (strict in round 7). Exit 0 = all pass;
non-zero = a check failed (qa.sh reports it as a warning this round).
"""
import sys, json, time, socket, subprocess, pathlib
try:
from playwright.sync_api import sync_playwright
except ImportError:
sys.exit("playwright not installed — python3 -m venv tools/.venv && "
"tools/.venv/bin/pip install playwright && tools/.venv/bin/python -m playwright install chromium")
ROOT = pathlib.Path(__file__).resolve().parent.parent
PORT = 8130
HOST = f'http://127.0.0.1:{PORT}'
SEED = 20261990
GOLDEN_HASH = 0x3fa36874 # frozen v2 determinism anchor (?classic boot) — UNCHANGED by the flips
GIG_GOLDEN = 0xb1d48ea1 # [R16] the DEFAULT boot's gig-augmented plan fingerprint (A's R16 corner-poster re-pin)
# flags-off DBG.shot('street_noon') snapshot. RE-PINNED at the R7 roster flip (prime-law amendment):
# old (v1.1, roster-off): 163 draws, 19000 tris
# new (R7, roster default-on): 164 draws, ~66000 tris
# Draws barely move — E1's ped sub-mesh merge makes each near-rig ~1 draw, which is exactly what let
# the roster flip to full density. Tris jump is the streamed crowd (each near-rig ~2.4k tris); it is
# inherently variable frame-to-frame (how many rigs are near at sample time), so the drift WARN below
# is DRAWS-ONLY — tris is guarded strictly by the STREET_TRIS_MAX hard cap, not a soft snapshot.
BASE_DRAWS, BASE_TRIS = 164, 66000
STREET_DRAWS_MAX, STREET_TRIS_MAX = 300, 200_000
KNOWN_FLAGS = ['winmap=1', 'dig=1', 'roster=stream'] # plansrc=osm auto-appended if Lane A landed it
# [R12] gig-night interior budget (ROUND12 law: ≤350 draws incl. the crowd — a room with a band and an
# audience in it is allowed more than a shop, which is what the ≤300 street/room numbers were tuned for).
GIG_DRAWS_MAX = 350
# `cover` is seeded per gig, so which door-charge path the default seed exercises is luck. Pin one seed
# per half of John's "free AND paid" ruling: 20261990's tonight is $10 (Screaming Utes at The Thornbury
# Hotel), 1234's is free (Dazza & The Dropbears at The Imperial). Re-pin if Lane A re-seeds the gig stream.
FREE_GIG_SEED = 1234
fails, warns = [], []
def FAIL(m): fails.append(m); print(f" \033[31m✗ FAIL\033[0m {m}")
def WARN(m): warns.append(m); print(f" \033[33m! WARN\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 stature_ok(f):
# [R16 ledger #3] seated figs (the drummer — fig.userData.procitySeated) pass the no-giants floor at a
# SEATED band [0.9,1.5] m; standing figs stay [1.4,2.0]. A seated human fails the standing floor by design.
lo, hi = (0.9, 1.5) if f.get('seated') else (1.4, 2.0)
return lo <= f['stature'] <= hi
def port_up(port):
with socket.socket() as s:
s.settimeout(0.4)
return s.connect_ex(('127.0.0.1', port)) == 0
def ensure_server():
if port_up(PORT):
return None # reuse a running dev server
proc = subprocess.Popen(['python3', '-m', 'http.server', str(PORT)],
cwd=str(ROOT / 'web'), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
for _ in range(50):
if port_up(PORT): return proc
time.sleep(0.1)
proc.terminate(); sys.exit(f"could not start http.server on :{PORT}")
# ── page helpers ─────────────────────────────────────────────────────────────
def new_page(p):
b = p.chromium.launch()
pg = b.new_page(viewport={'width': 1280, 'height': 720})
errs = []
pg.on('console', lambda m: errs.append(m.text) if m.type == 'error' else None)
pg.on('pageerror', lambda e: errs.append(str(e)))
return b, pg, errs
def boot(pg, query):
pg.goto(f'{HOST}/index.html?seed={SEED}&dbg=1' + (('&' + query) if query else ''))
pg.wait_for_function("window.DBG && window.DBG.ready === true", timeout=20000)
pg.evaluate("() => { const o=document.getElementById('pc-start'); if(o) o.style.display='none'; }")
try:
pg.wait_for_function("() => window.PROCITY && (!window.PROCITY.fleet || window.PROCITY.fleet.ready)", timeout=12000)
except Exception:
pass
def gigs_landed(p):
"""Boot ?gigs=1; landed iff Lane A's augmentation produced a schedule (else the flag is inert)."""
b, pg, _ = new_page(p)
try:
boot(pg, '')
return bool(pg.evaluate("() => !!(window.PROCITY.plan.gigs && window.PROCITY.plan.gigs.length)"))
except Exception:
return False
finally:
b.close()
def plansrc_landed(p):
"""Boot ?plansrc=osm; landed iff the shell exposes an 'osm' plan source (else the flag is ignored)."""
b, pg, _ = new_page(p)
try:
boot(pg, 'plansrc=osm')
src = pg.evaluate("() => (window.PROCITY.plan && (window.PROCITY.plan.source||window.PROCITY.plan.planSource)) "
"|| window.PROCITY.planSource || null")
return src == 'osm'
except Exception:
return False
finally:
b.close()
# ── the three checks ─────────────────────────────────────────────────────────
def classic_regression(p):
# [R16 — THE FLIP] The prime-law covenant MOVED from "flags off" to "?classic=1": the classic boot must
# stay byte-identical to the frozen v2 baseline FOREVER — synthetic fingerprint 0x3fa36874, zero gig
# layer, no opt-in flags, the R7 streamed roster (which IS the v2.0 baseline). Same assertions as the old
# flags-off gate, now booted with ?classic=1 (the four flipped flags are off, everything else v2-default).
head('CLASSIC REGRESSION (?classic=1 → the frozen v2 covenant)')
b, pg, errs = new_page(p)
try:
boot(pg, 'classic=1')
state = pg.evaluate("""() => {
const P=window.PROCITY, D=window.DBG;
D.shot('street_noon'); // fixed pose → comparable draws/tris
const i = D.info();
const c = P.citizens || {};
return { draws:i.drawCalls, tris:i.tris, mode:i.mode,
winmap: !!(P.winmap && P.winmap.active),
digActive: !!(P.interiorMode && P.interiorMode.digActive),
streamMode: !!c.streamMode, // R7: real roster-mode flag (was reading a nonexistent prop)
// [R12] the gig layer must be wholly absent flags-off: no state machine, no schedule
// in the plan, no pub converted, and none of Lane B's venue geometry in the scene.
gigState: !!P.gigs, planGigs: (P.plan.gigs||[]).length, posters: (P.plan.posters||[]).length,
pub: !!(P.plan.shops||[]).find(s => s.venue || s.type === 'pub'),
venueGeo: !!P.scene.getObjectByName('venue-presentation') };
}""")
# determinism anchor — run Lane A's selfcheck in-process (golden fingerprint)
r = subprocess.run(['node', 'web/js/citygen/selfcheck.js'], cwd=str(ROOT),
capture_output=True, text=True)
hash_ok = (f'{GOLDEN_HASH:#010x}'.replace('0x', '0x') in r.stdout) or (hex(GOLDEN_HASH) in r.stdout) \
or ('3fa36874' in r.stdout)
# [R24] These are TWO assertions and they get two verdicts. This gate used to require
# `hash_ok and r.returncode == 0` and report BOTH failures with the fingerprint's message — so
# any unrelated red anywhere in Lane A's selfcheck (R24: 8 godverse-identity checks in a
# different town) screamed "determinism/prime-law broken", the loudest alarm we own, about a
# covenant that was provably intact (0x3fa36874, node-side, unmoved). A gate must name what it
# actually measured; F wrote this one, so F fixed it. The covenant is asserted on its own.
if hash_ok: OK(f'plan fingerprint == golden {hex(GOLDEN_HASH)} — the classic covenant holds')
else: FAIL(f'plan fingerprint != golden {hex(GOLDEN_HASH)} — determinism/prime-law broken')
if r.returncode == 0: OK('Lane A selfcheck green')
else:
tail = [l for l in r.stdout.splitlines() if l.strip().startswith('')][:3]
FAIL('Lane A selfcheck RED (separate from the covenant above): '
+ ('; '.join(t.strip() for t in tail) if tail else 'see selfcheck output'))
# winmap flips OFF under ?classic (it's default-on now); dig stays opt-in. The streamed roster IS the
# v2.0 baseline (R7 flip / prime-law amendment) — so it must be ON under ?classic.
if state['winmap'] or state['digActive']:
FAIL(f"a flipped/opt-in flag leaked into ?classic (winmap/dig): {state}")
else: OK('flipped + opt-in flags off under ?classic (winmap/dig)')
if state['streamMode']:
OK('R7 roster flip in effect — streamed roster ACTIVE under ?classic (the v2 baseline)')
else: FAIL('roster flip REGRESSED — ?classic is not streaming (expected streamMode:true)')
# [R12/R16] gig layer wholly absent under ?classic. Any of these present means the gig layer escaped
# the classic gate and the frozen v2 covenant is broken.
gig_leaks = [k for k in ('gigState', 'planGigs', 'posters', 'pub', 'venueGeo') if state[k]]
if not gig_leaks:
OK('v3 gig layer wholly absent under ?classic (no state machine, no plan.gigs/posters, no pub, no venue geometry)')
else:
FAIL(f"v3 gig layer LEAKED into ?classic: {[(k, state[k]) for k in gig_leaks]}")
if state['draws'] <= STREET_DRAWS_MAX and state['tris'] <= STREET_TRIS_MAX:
OK(f"?classic street view within budget (draws {state['draws']}{STREET_DRAWS_MAX}, tris {state['tris']}{STREET_TRIS_MAX})")
else: FAIL(f"?classic street view OVER budget: draws {state['draws']}, tris {state['tris']}")
# Drift WARN is DRAWS-ONLY — tris is crowd-variable post-flip (guarded by the hard cap above).
dd = abs(state['draws'] - BASE_DRAWS) / BASE_DRAWS
if dd <= 0.40:
OK(f"draws within 40% of re-pinned baseline ({state['draws']} vs {BASE_DRAWS}); tris {state['tris']} (crowd-variable, ≤cap)")
else:
WARN(f"draws drifted >40% from baseline ({state['draws']} vs {BASE_DRAWS}) — investigate")
if errs: FAIL(f"{len(errs)} console error(s) under ?classic; first: {errs[0][:140]}")
else: OK('0 console errors under ?classic')
# R7 escape hatch: ?roster=v1 must restore the fixed roster (streamMode:false), render, no errors.
b2, pg2, errs2 = new_page(p)
try:
boot(pg2, 'roster=v1')
esc = pg2.evaluate("() => ({ streamMode: !!(window.PROCITY.citizens||{}).streamMode, mode: window.DBG.info().mode })")
if not esc['streamMode'] and esc['mode'] == 'street':
OK('escape hatch ?roster=v1 → fixed roster (streamMode:false), renders')
else: FAIL(f"escape hatch ?roster=v1 broken: {esc}")
if errs2: FAIL(f"?roster=v1: {len(errs2)} console error(s); first: {errs2[0][:140]}")
finally:
b2.close()
finally:
b.close()
def default_boot_gate(p):
# [R16 — THE FLIP] The enforcement arm of the DEFAULT law: the no-flag boot is now the full experience —
# all four flags on, the gig layer present, deterministic to the gig golden. This is the number every
# budget/smoke now means (its inverse is classic_regression above).
head('DEFAULT-BOOT GATE (the flip → the town shows its best by default)')
b, pg, errs = new_page(p)
try:
boot(pg, '')
state = pg.evaluate("""() => {
const P = window.PROCITY, D = window.DBG;
D.setSegment(5); D.shot('venue_night'); // NIGHT at a venue → the busiest default view (gigs+pools+winmap+tram)
const i = D.info();
return { flags: P.flags,
gigState: !!P.gigs, planGigs: (P.plan.gigs||[]).length, posters: (P.plan.posters||[]).length,
pub: !!(P.plan.shops||[]).find(s => s.venue), venueGeo: !!P.scene.getObjectByName('venue-presentation'),
winmapActive: !!(P.winmap && P.winmap.active), tram: !!P.scene.getObjectByName('tram'),
draws: i.drawCalls, tris: i.tris };
}""")
f = state['flags'] or {}
if f.get('gigs') and f.get('weather') and f.get('winmap') and f.get('tram') and not f.get('classic'):
OK('default boot flips all four ON (gigs · weather · winmap · tram), classic off')
else:
FAIL(f"default boot flag intent wrong: {f}")
present = [k for k in ('gigState', 'planGigs', 'posters', 'pub', 'venueGeo') if state[k]]
if len(present) == 5 and state['tram']:
OK(f"the town is live: gig layer present ({state['planGigs']} gigs, {state['posters']} posters, venue geo) · tram up (winmap is a window shader — its own smoke)")
else:
FAIL(f"default boot not fully live: gig={present}, tram={state['tram']}")
# The default boot's plan IS the gig-augmented plan; selfcheck pins its fingerprint to the gig golden
# (0xb1d48ea1) INTERNALLY and is ALL GREEN only if it matches. (selfcheck prints the base fingerprint
# in its summary but asserts the gig golden silently — so we gate on the green run, not a substring.)
r = subprocess.run(['node', 'web/js/citygen/selfcheck.js'], cwd=str(ROOT), capture_output=True, text=True)
if r.returncode == 0 and 'ALL GREEN' in r.stdout:
OK(f"default plan pins to the gig golden {hex(GIG_GOLDEN)} (selfcheck ALL GREEN — the gig-layer golden asserts pass)")
else:
FAIL(f"selfcheck not green — the gig golden {hex(GIG_GOLDEN)} or determinism broke: {r.stdout[-160:]}")
# budget on the DEFAULT boot at the busiest venue block (the number the budget law now means)
if state['draws'] <= STREET_DRAWS_MAX and state['tris'] <= STREET_TRIS_MAX:
OK(f"default-boot street budget @ venue block: {state['draws']} draws ≤ {STREET_DRAWS_MAX}, {state['tris']:,} tris ≤ {STREET_TRIS_MAX:,}")
else:
FAIL(f"default-boot street OVER budget @ venue block: {state['draws']} draws / {state['tris']:,} tris (Fable scope call — not silent-trim)")
if errs: FAIL(f"{len(errs)} console error(s) on the default boot; first: {errs[0][:140]}")
else: OK('0 console errors on the default boot')
finally:
b.close()
def smoke(p, label, query, is_combo=False):
head(f'SMOKE: {label} (?{query})')
b, pg, errs = new_page(p)
try:
boot(pg, query)
# cross ≥2 chunks by teleporting between separated street nodes
chunks = pg.evaluate("""() => {
const P=window.PROCITY, D=window.DBG, seen=new Set();
const ns = P.plan.streets.nodes;
for (const idx of [0, (ns.length/3|0), (2*ns.length/3|0), ns.length-1]) {
const n = ns[idx]; D.teleport(n.x, n.z, 0);
for (let k=0;k<10;k++) (P.citizens&&P.citizens.update&&P.citizens.update(0.05));
const c = D.info().chunk; if (c!=null) seen.add(String(c));
}
return seen.size;
}""")
if chunks >= 2: OK(f'crossed {chunks} chunks streaming (flag active, no crash)')
else: WARN(f'only {chunks} distinct chunks seen (teleport/stream probe)')
# enter one interior (open shop), exit
entered = pg.evaluate("""() => {
const P=window.PROCITY, D=window.DBG;
D.setSegment(2);
const s=(P.plan.shops||[]).find(x=>x.type==='record' && P.isOpen(x)) || (P.plan.shops||[]).find(x=>P.isOpen(x));
if(!s) return {ok:false};
D.enterShop(s.id);
const inMode = D.info().mode==='interior';
return {ok:inMode, shop:s.name};
}""")
if entered.get('ok'): OK(f"entered interior '{entered.get('shop')}' (mode=interior)")
else: FAIL('could not enter an interior')
# dig-specific: open one riffle + close, leak-free
if 'dig=1' in query:
dug = pg.evaluate("""() => {
const P=window.PROCITY, THREE=P.THREE, room=P.interiorMode.current;
if(!room) return {ok:false, why:'no room'};
const bins=[]; room.group.traverse(o=>{ if(o.userData&&o.userData.kind==='bin') bins.push(o); });
if(!bins.length) return {ok:false, why:'no bins'};
const bp=new THREE.Vector3(); bins[0].getWorldPosition(bp);
P.camera.position.set(bp.x,1.6,bp.z+1.8); P.camera.lookAt(bp.x,bp.y+0.4,bp.z); P.camera.updateMatrixWorld();
window.dispatchEvent(new KeyboardEvent('keydown',{code:'KeyE'}));
const opened = P.interiorMode.digActive;
const btn=document.querySelector('.pcdg-out'); if(btn) btn.click();
return { ok: opened, closed: !P.interiorMode.digActive };
}""")
pg.wait_for_timeout(300)
if dug.get('ok') and dug.get('closed'): OK('dig: riffle opened (E on bin) + closed (WALK OUT)')
elif dug.get('ok'): WARN(f"dig: opened but close not confirmed ({dug})")
else: FAIL(f"dig: riffle did not open ({dug.get('why','?')})")
pg.evaluate("() => window.DBG.exitShop && window.DBG.exitShop()")
if errs: FAIL(f"{label}: {len(errs)} console error(s); first: {errs[0][:140]}")
else: OK(f'{label}: 0 console errors')
finally:
b.close()
def interior_budget(p):
"""Decision #2: interior draw budget is law — ≤350 draws worst room. Sweep every shop type
(dig on, so this also confirms batching kept ?dig=1 riffling). C enforces via its sweep; F pins it."""
head('INTERIOR DRAW BUDGET (≤350 draws/room law — decision #2)')
b, pg, errs = new_page(p)
try:
boot(pg, 'dig=1')
pg.evaluate("() => window.DBG.setSegment(2)") # midday → shops open
types = ['record', 'opshop', 'toy', 'book', 'video', 'pawn', 'milkbar', 'dept', 'stall']
rows, seen = [], set()
for t in types:
picked = pg.evaluate("""(t) => {
const P=window.PROCITY, D=window.DBG;
const s=(P.plan.shops||[]).find(x=>x.type===t && P.isOpen(x));
if(!s) return null; D.enterShop(s.id); return {id:s.id, name:s.name};
}""", t)
if not picked:
continue
pg.wait_for_timeout(500) # let async GLB fittings load → settled worst-case draws
info = pg.evaluate("() => { const i=window.DBG.info(); return {draws:i.drawCalls, tris:i.tris, mode:i.mode}; }")
rows.append({'type': t, 'shop': picked['name'], **info})
pg.evaluate("() => window.DBG.exitShop && window.DBG.exitShop()")
pg.wait_for_timeout(150)
over = [r for r in rows if r['draws'] > 350]
for r in over:
FAIL(f"interior {r['type']} '{r['shop']}': {r['draws']} draws > 350")
if rows and not over:
worst = max(rows, key=lambda r: r['draws'])
OK(f"all {len(rows)} room types ≤350 draws (worst: {worst['type']} {worst['draws']}, tris {worst['tris']})")
elif not rows:
WARN('no interiors sampled (no open shops of the swept types)')
if errs:
FAIL(f"interior sweep: {len(errs)} console error(s); first: {errs[0][:140]}")
finally:
b.close()
def _enter_type(pg, t, extra=''):
"""Enter an open shop of type `t`; returns the shop name or {err}."""
return pg.evaluate("""(t) => {
const P=window.PROCITY, D=window.DBG; D.setSegment(2);
const s=(P.plan.shops||[]).find(x=>x.type===t && P.isOpen(x));
if(!s) return {err:'no open '+t+' shop'};
D.enterShop(s.id); return { shop: s.name };
}""", t)
def _pack_ready(pg, t):
try:
pg.wait_for_function("async () => { const m = await import('./js/interiors/interiors.js');"
f" return !!(m.getStockPack && m.getStockPack('{t}')); }}", timeout=12000)
return True
except Exception:
return False
def smoke_stock(p):
"""GRADUATED STRICT (R8): ?stock=real → real GODVERSE sleeves (batched userData.isStock atlas meshes)."""
head('SMOKE: stock=real (?stock=real — real record sleeves; STRICT r8)')
b, pg, errs = new_page(p)
try:
boot(pg, 'stock=real&dig=1')
ready = _pack_ready(pg, 'record')
r = _enter_type(pg, 'record')
if r.get('err'): FAIL(f"stock=real: {r['err']}"); return
n = pg.evaluate("() => { let n=0; window.PROCITY.interiorMode.current.group.traverse(o=>{ if(o.userData&&o.userData.isStock&&o.material&&o.material.map) n++; }); return n; }")
if not ready: FAIL(f"stock=real: record pack didn't preload in 12s (E ships it) — got {n} isStock meshes")
elif n > 0: OK(f"stock=real: {n} real atlas sleeve mesh(es) in '{r['shop']}' — non-parody, batched")
else: FAIL(f"stock=real: pack loaded but 0 isStock atlas meshes in '{r['shop']}'")
if errs: FAIL(f"stock=real: {len(errs)} console error(s); first: {errs[0][:140]}")
finally:
b.close()
def smoke_weather(p):
"""GRADUATED STRICT (R8): ?weather=rain → rain THREE.Points layer + PROCITY.weather.state=='rain'."""
head('SMOKE: weather (?weather=rain — rain particles + wet ground; STRICT r8)')
b, pg, errs = new_page(p)
try:
boot(pg, 'weather=rain')
st = pg.evaluate("() => { const P=window.PROCITY; return { state: P.weather&&P.weather.state, "
"intensity: P.weather&&P.weather.intensity, rainPts: !!P.scene.getObjectByProperty('type','Points') }; }")
if st['state'] == 'rain' and st['rainPts']:
OK(f"weather=rain: rain Points present + PROCITY.weather.state=='rain' (intensity {st['intensity']})")
else: FAIL(f"weather=rain broken: {st}")
if errs: FAIL(f"weather=rain: {len(errs)} console error(s); first: {errs[0][:140]}")
finally:
b.close()
def smoke_buy(p):
"""NEW (warn): buy-v0 — open dig, pull + BUY a sleeve, assert wallet cash dropped + item banked."""
head('SMOKE: buy-v0 (dig BUY → session wallet; STRICT r9)')
b, pg, errs = new_page(p)
try:
boot(pg, 'dig=1')
setup = pg.evaluate("""() => {
const P=window.PROCITY, D=window.DBG; D.setSegment(2);
const rec=(P.plan.shops||[]).find(s=>s.type==='record' && P.isOpen(s));
if(!rec) return {err:'no record shop'};
D.enterShop(rec.id);
const bins=[]; P.interiorMode.current.group.traverse(o=>{if(o.userData&&o.userData.kind==='bin')bins.push(o)});
if(!bins.length) return {err:'no bins'};
const bp=new P.THREE.Vector3(); bins[0].getWorldPosition(bp);
P.camera.position.set(bp.x,1.6,bp.z+1.4); P.camera.lookAt(bp.x,bp.y+0.4,bp.z); P.camera.updateMatrixWorld();
return { cash0: P.wallet.cash(), count0: P.wallet.count() };
}""")
if setup.get('err'): FAIL(f"buy-v0: {setup['err']}"); return
pg.evaluate("() => window.dispatchEvent(new KeyboardEvent('keydown',{code:'KeyE'}))") # open dig
pg.wait_for_timeout(600)
# pull the front sleeve (canvas click, no drag → dig.pull) then click its BUY IT button
pg.evaluate("""() => { const c=window.PROCITY.renderer.domElement;
c.dispatchEvent(new PointerEvent('pointerdown',{clientX:640,clientY:360,bubbles:true}));
c.dispatchEvent(new PointerEvent('pointerup',{clientX:640,clientY:360,bubbles:true})); }""")
pg.wait_for_timeout(400)
res = pg.evaluate("""() => {
const w=window.PROCITY.wallet, cash0=w.cash(), count0=w.count();
const btn=document.querySelector('.pcdg-panel button');
if(!btn) return {err:'no BUY panel (pull failed)'};
if(btn.disabled) return {err:'BUY disabled (broke?)'};
btn.click();
return { cash0, count0, cash1:w.cash(), count1:w.count() };
}""")
if res.get('err'): FAIL(f"buy-v0: {res['err']}")
elif res['cash1'] < res['cash0'] and res['count1'] == res['count0'] + 1:
OK(f"buy-v0: bought 1 — cash {res['cash0']}{res['cash1']}, bag {res['count0']}{res['count1']}")
else: FAIL(f"buy-v0: unexpected wallet delta {res}")
if errs: FAIL(f"buy-v0: {len(errs)} console error(s); first: {errs[0][:140]}")
finally:
b.close()
def smoke_patronage(p):
"""NEW (warn): peds duck into open shops — assert ≥1 ped parks at a shop door over N seconds."""
head('SMOKE: patronage (peds visit open shops; STRICT r9)')
b, pg, errs = new_page(p)
try:
boot(pg, '') # default: streamed roster + patronage on
# advance the sim ~12s of ticks at a busy midday node; count peds flagged as inside/at-door
res = pg.evaluate("""() => {
const P=window.PROCITY, D=window.DBG; D.setSegment(2);
const plan=P.plan, deg={}; for(const e of plan.streets.edges){deg[e.a]=(deg[e.a]||0)+1;deg[e.b]=(deg[e.b]||0)+1;}
let best=null,bd=0; for(const n of plan.streets.nodes){const d=deg[n.id]||0;if(d>bd){bd=d;best=n;}}
D.teleport(best.x,best.z,0); P.citizens.setPopulation(200);
let everInside=0; const seen=new Set();
for(let i=0;i<360;i++){ P.citizens.update(0.05);
const list=P.citizens._activeList||[];
for(const c of list){ if(c && (c.patronInside||c.inside||c.patronTarget) && !seen.has(c.id)){ seen.add(c.id); everInside++; } }
}
return { active:(P.citizens._activeList||[]).length, everVisited: everInside, patronOn: P.citizens.patronageOn };
}""")
if not res['patronOn']: FAIL("patronage: off (patronageOn=false) — expected default-on")
elif res['everVisited'] > 0: OK(f"patronage: {res['everVisited']} ped(s) headed to/into a shop door over ~18s (active {res['active']})")
else: FAIL(f"patronage: 0 peds visited a shop over ~18s (active {res['active']}) — check door points / timing")
if errs: FAIL(f"patronage: {len(errs)} console error(s); first: {errs[0][:140]}")
finally:
b.close()
def smoke_booktoy(p):
"""NEW (warn): book/toy packs — ?stock=real static shelf stock (real spine/box atlas meshes)."""
head('SMOKE: book/toy stock (?stock=real — real spines/boxes; STRICT r9)')
b, pg, errs = new_page(p)
try:
boot(pg, 'stock=real')
for t in ('book', 'toy'):
_pack_ready(pg, t)
r = _enter_type(pg, t)
if r.get('err'): FAIL(f"book/toy: {r['err']}"); continue
# R9: real static stock is EITHER an isStock atlas mesh (records/toys) OR a buyMesh shelf
# (book spines / toy boxes, C2 buy-anywhere). Parody stock has neither.
n = pg.evaluate("""() => { let n=0; window.PROCITY.interiorMode.current.group.traverse(o=>{
const u=o.userData||{}; if((u.isStock && o.material && o.material.map) || (u.buyMesh && u.buyItems && u.buyItems.length)) n++; }); return n; }""")
if n > 0: OK(f"{t} stock: {n} real stock mesh(es) in '{r['shop']}'")
else: FAIL(f"{t} stock: 0 isStock atlas meshes in '{r['shop']}' (pack missing → parody fallback?)")
pg.evaluate("() => window.DBG.exitShop && window.DBG.exitShop()")
pg.wait_for_timeout(150)
if errs: FAIL(f"book/toy: {len(errs)} console error(s); first: {errs[0][:140]}")
finally:
b.close()
def smoke_presence(p):
"""NEW (warn): interior presence — enter an OCCUPIED shop, assert browser rigs == min(occupancy,3),
dispose clean on exit, 0 errors (the C→D→F seam)."""
head('SMOKE: interior presence (browsers in occupied shops; new → warn-level)')
b, pg, errs = new_page(p)
try:
boot(pg, 'stock=real')
res = pg.evaluate("""() => {
const P=window.PROCITY, D=window.DBG; D.setSegment(2);
const plan=P.plan, deg={}; for(const e of plan.streets.edges){deg[e.a]=(deg[e.a]||0)+1;deg[e.b]=(deg[e.b]||0)+1;}
let best=null,bd=0; for(const n of plan.streets.nodes){const d=deg[n.id]||0;if(d>bd){bd=d;best=n;}}
D.teleport(best.x,best.z,0); P.citizens.setPopulation(200);
for(let i=0;i<400;i++) P.citizens.update(0.05);
let shop=null,occ=null;
for(const s of plan.shops){ const o=P.citizens.occupancyOf(s.id); if(o&&o.count>0){shop=s;occ=o;break;} }
if(!shop) return {err:'no occupied shop after patronage'};
D.enterShop(shop.id);
const kl=P.interiorMode.keepers.keepers||[];
const total=kl.length, pts=(P.interiorMode.current.browsePoints||[]).length;
return { shop:shop.name, occ:occ.count, expect:Math.min(occ.count,3), totalKeepers:total, browsePoints:pts };
}""")
if res.get('err'): WARN(f"presence: {res['err']}"); return
# total keepers = 1 counter + browsers; browsers should == min(occ,3)
browsers = res['totalKeepers'] - 1
if browsers == res['expect'] and res['browsePoints'] >= 1:
OK(f"presence: {browsers} browser(s) in '{res['shop']}' == min(occupancy {res['occ']},3); {res['browsePoints']} browse points")
else:
WARN(f"presence: browsers {browsers} != expect {res['expect']} (occ {res['occ']}, pts {res['browsePoints']}, keepers {res['totalKeepers']})")
clean = pg.evaluate("() => { window.DBG.exitShop(); return (window.PROCITY.interiorMode.keepers.keepers||[]).length; }")
if clean == 0: OK("presence: browsers disposed clean on exit (0 keepers)")
else: WARN(f"presence: {clean} keeper(s) leaked after exit")
if errs: WARN(f"presence: {len(errs)} console error(s); first: {errs[0][:140]}")
finally:
b.close()
def smoke_shelfbuy(p):
"""NEW (warn): buy-anywhere — E aimed at a book spine / toy box buys over the wallet (Lane C C2)."""
head('SMOKE: shelf-buy (?stock=real book/toy pull-and-buy; new → warn-level)')
b, pg, errs = new_page(p)
try:
boot(pg, 'stock=real')
_pack_ready(pg, 'book')
res = pg.evaluate("""() => {
const P=window.PROCITY, D=window.DBG; D.setSegment(2);
const bk=(P.plan.shops||[]).find(s=>s.type==='book' && P.isOpen(s));
if(!bk) return {err:'no book shop'};
D.enterShop(bk.id);
const meshes=[]; P.interiorMode.current.group.traverse(o=>{ if(o.userData&&o.userData.buyMesh&&o.userData.buyItems&&o.userData.buyItems.length) meshes.push(o); });
if(!meshes.length) return {err:'no buy meshes (pack not loaded?)'};
const m=meshes[0], it=m.userData.buyItems[0], wp=new P.THREE.Vector3();
m.localToWorld(wp.copy(it.center));
P.camera.position.set(wp.x,wp.y,wp.z+1.0); P.camera.lookAt(wp.x,wp.y,wp.z); P.camera.updateMatrixWorld();
const cash0=P.wallet.cash(), count0=P.wallet.count();
window.dispatchEvent(new KeyboardEvent('keydown',{code:'KeyE'}));
return { shop:bk.name, buyMeshes:meshes.length, cash0, count0, cash1:P.wallet.cash(), count1:P.wallet.count() };
}""")
if res.get('err'): WARN(f"shelf-buy: {res['err']}")
elif res['cash1'] < res['cash0'] and res['count1'] == res['count0'] + 1:
OK(f"shelf-buy: bought a book spine in '{res['shop']}' — cash {res['cash0']}{res['cash1']}, bag {res['count0']}{res['count1']} ({res['buyMeshes']} buy meshes)")
else: WARN(f"shelf-buy: no purchase — {res}")
if errs: WARN(f"shelf-buy: {len(errs)} console error(s); first: {errs[0][:140]}")
finally:
b.close()
def smoke_tram(p):
"""NEW (warn): ?tram=1 — Lane B tram loop. Auto-skips if not landed."""
b, pg, errs = new_page(p)
try:
boot(pg, 'tram=1')
present = pg.evaluate("() => !!window.PROCITY.scene.getObjectByName('tram')")
if not present:
print("· tram=1 not landed (no 'tram' in scene) — skipping (Lane B)")
return
head('SMOKE: tram (?tram=1; new → warn-level)')
OK("tram=1: 'tram' present in scene")
if errs: WARN(f"tram: {len(errs)} console error(s); first: {errs[0][:140]}")
finally:
b.close()
def smoke_audio(p):
"""R11 audio house-law (Lane F): silent-and-happy, nothing plays pre-gesture, ?mute=1 silences,
?noassets=1 fetches zero audio, and interior beds play + release AudioNodes across enter/exit.
Each arm uses a fresh Chromium context (standing law — a reused tab's module cache burned us in R10)."""
head('SMOKE: audio (R11 — silent-happy · pre-gesture silence · mute · noassets · leak)')
# 1) boot-with-audio: 0 console errors, and nothing plays before a gesture (ctx suspended → state.ready false)
b, pg, errs = new_page(p)
try:
boot(pg, '')
pg.wait_for_function("() => window.PROCITY && window.PROCITY.audio && window.PROCITY.audio.state", timeout=8000)
st = pg.evaluate("() => { const s=window.PROCITY.audio.state; return {ready:!!s.ready, muted:!!s.muted, mode:s.mode}; }")
if not st['ready']:
OK(f"pre-gesture: audio NOT unlocked (state.ready=false, mode={st['mode']}) — silent until first gesture")
else:
FAIL(f"audio unlocked before any user gesture (autoplay-policy violation): {st}")
if errs: FAIL(f"audio boot: {len(errs)} console error(s); first: {errs[0][:140]}")
else: OK("audio boot: 0 console errors (silent-and-happy)")
finally:
b.close()
# 2) ?mute=1 → engine reports muted
b, pg, errs = new_page(p)
try:
boot(pg, 'mute=1')
pg.wait_for_function("() => window.PROCITY && window.PROCITY.audio", timeout=8000)
st = pg.evaluate("() => ({muted:!!window.PROCITY.audio.state.muted, mode:window.PROCITY.audio.state.mode})")
if st['muted']: OK(f"?mute=1: engine muted (mode={st['mode']})")
else: FAIL(f"?mute=1 not honored: {st}")
if errs: FAIL(f"?mute=1: {len(errs)} console error(s)")
finally:
b.close()
# 3) ?noassets=1 → zero audio network fetches (house law: the noassets gate implies no audio)
b, pg, errs = new_page(p)
audio_reqs = []
pg.on('request', lambda r: audio_reqs.append(r.url)
if ('/assets/audio/' in r.url or r.url.endswith('.ogg') or r.url.endswith('.m4a')) else None)
try:
boot(pg, 'noassets=1')
pg.keyboard.press('Space') # a keyboard gesture unlocks the AudioContext WITHOUT a pointer-lock attempt (headless-safe) # a gesture, so the audio path is genuinely exercised
pg.evaluate("""() => { const P=window.PROCITY, D=window.DBG; D.setSegment(2);
const s=(P.plan.shops||[]).find(x=>P.isOpen(x)); if(s) D.enterShop(s.id); }""")
pg.wait_for_timeout(800)
man = pg.evaluate("() => window.PROCITY.audio.state.manifest")
if len(audio_reqs) == 0: OK(f"?noassets=1: 0 audio fetches (manifest={man})")
else: FAIL(f"?noassets=1: {len(audio_reqs)} audio fetch(es) leaked; first {audio_reqs[0]}")
if errs: FAIL(f"?noassets=1 audio: {len(errs)} console error(s)")
finally:
b.close()
# 4) interior beds play + enter/exit is AudioNode-leak-free (live BufferSources stay bounded, not O(N))
b = p.chromium.launch()
pg = b.new_page(viewport={'width': 1280, 'height': 720})
errs = []
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.add_init_script("""
window.__live = new Set(); window.__made = 0;
const AC = window.AudioContext || window.webkitAudioContext;
if (AC) { const orig = AC.prototype.createBufferSource;
AC.prototype.createBufferSource = function () { const s = orig.call(this);
window.__made++; window.__live.add(s);
const ostop = s.stop.bind(s); s.stop = function (w) { window.__live.delete(s); return ostop(w); };
s.addEventListener('ended', () => window.__live.delete(s)); return s; }; }
""")
try:
boot(pg, '')
pg.keyboard.press('Space') # a keyboard gesture unlocks the AudioContext WITHOUT a pointer-lock attempt (headless-safe) # user gesture → unlock the AudioContext
pg.wait_for_timeout(250)
played = pg.evaluate("""async () => {
const P=window.PROCITY, D=window.DBG; D.setSegment(2);
const types=['record','milkbar','video','opshop','book','toy'];
let entered=0, withMusic=0;
for (let rep=0; rep<2; rep++) for (const t of types) {
const s=(P.plan.shops||[]).find(x=>x.type===t && P.isOpen(x)); if(!s) continue;
D.enterShop(s.id); entered++;
const a = P.interiorMode.current && P.interiorMode.current.audio;
if (a && a.musicKey) withMusic++;
await new Promise(r=>setTimeout(r,120));
D.exitShop && D.exitShop(); await new Promise(r=>setTimeout(r,120));
}
return { entered, withMusic, ready:!!P.audio.state.ready };
}""")
pg.wait_for_timeout(600)
live = pg.evaluate("() => window.__live.size")
made = pg.evaluate("() => window.__made")
if not played['ready']:
WARN(f"audio interior: ctx never unlocked in headless — leak check inconclusive ({played}, live {live}/made {made})")
elif played['entered'] >= 4 and live <= 12:
OK(f"audio interior: {played['entered']} enter/exit, {played['withMusic']} with a music bed; live AudioNodes {live} bounded (made {made}) — leak-free")
else:
FAIL(f"audio interior: {live} live AudioNodes after {played['entered']} enter/exit (made {made}) — possible leak")
if errs: FAIL(f"audio interior: {len(errs)} console error(s); first: {errs[0][:140]}")
else: OK("audio interior: 0 console errors across enter/exit cycles")
finally:
b.close()
def smoke_gigs(p):
"""R13 v3.0-beta THE DISTRICT gig layer (Lane F). The ROUND13 §Lane F.6 gates — district determinism ·
a 4-piece band · per-venue crowd ≤ watchPoints · EVERY venue's gigKey resolves to a real manifest bed
(debt #1, now across 3 genres) · gig-night interior draw budget · street budget at the busiest venue
block · no-giants (STATURE) on the stage/crowd rigs · queue ≤ cap and drains by 'done' (pending Lane D)
· ?noassets=1 still silent-and-fine with ?gigs=1 — plus John's cover ruling, now PER VENUE (paid · free
· broke-knockback · no re-charge on re-entry · a night at two venues is two covers).
Seeds are pinned because `cover` is seeded: 20261990's tonight is a $10 night (Screaming Utes at The
Thornbury Hotel) and 1234's is free (Dazza & The Dropbears at The Imperial), so BOTH halves of "free
AND paid" are covered deterministically rather than whichever the default seed happened to roll."""
head('SMOKE: gigs (R13 v3.0-beta district — determinism · 4-piece · per-venue crowd/cover · gigKeys · budgets · queue · noassets)')
if not gigs_landed(p):
print('· gigs=1 not landed (no plan.gigs) — skipping (Lane A)')
return
# 1) schedule determinism — two independent boots (fresh contexts) → byte-equal gigs + posters
sigs = []
for _ in range(2):
b, pg, errs = new_page(p)
try:
boot(pg, '')
sigs.append(pg.evaluate("() => JSON.stringify({g: window.PROCITY.plan.gigs, p: window.PROCITY.plan.posters})"))
if errs: FAIL(f"gigs boot: {len(errs)} console error(s); first: {errs[0][:140]}")
finally:
b.close()
if sigs[0] == sigs[1]:
n = len(json.loads(sigs[0])['g'])
OK(f"gig schedule deterministic: two runs byte-equal ({n} nights + posters)")
else:
FAIL('gig schedule NOT deterministic across two runs (byte-diff in plan.gigs/plan.posters)')
# 2) the gig night itself: band · crowd · dance mix · budget · the live bed · no giants
b, pg, errs = new_page(p)
try:
boot(pg, '')
pg.keyboard.press('Space') # gesture → AudioContext unlock (headless-safe, no pointer-lock)
night = pg.evaluate("""async () => {
const P = window.PROCITY, D = window.DBG, THREE = P.THREE, v = new THREE.Vector3();
const id = P.gigs.venueShopIds[0]; // R15: no scalar alias — the primary venue by id
D.setSegment(5); // NIGHT — the gig is on
const state = P.gigs.stateOf(id);
D.enterShop(id);
await new Promise(r => setTimeout(r, 500));
const room = P.interiorMode.current;
if (!room) return { entered: false, state };
const crew = P.interiorMode.crew, info = P.interiorMode.crewInfo;
const mem = crew ? crew.members : [];
// STATURE, not world crown: the band stands on Lane C's stage deck (deckY ~0.32 m), so a
// perfectly human 1.73 m guitarist has a world crown of 2.05 m and a naive [1.4,2.0] world-Y
// check fails a correct band (it did, on the first R12 verify run). Measure crown MINUS the
// figure's own base y — that is what "human-sized" actually means — and keep the separate
// "never clips the ceiling" assert on the world crown, which is the real R10 failure mode.
const figs = mem.map(m => {
let crown = 0;
m.actor.fig.updateWorldMatrix(true, true);
(m.actor.inner || m.actor.fig).traverse(o => { if (o.isBone && /head/i.test(o.name)) {
o.getWorldPosition(v); crown = Math.max(crown, v.y); } });
return { part: m.part, kind: m.kind, dance: !!m.dance,
crown: +crown.toFixed(3), stature: +(crown - (m.base.y || 0)).toFixed(3),
seated: !!(m.actor.fig.userData && m.actor.fig.userData.procitySeated) }; // [R16 #3]
});
// Force one clean interior frame rather than trusting whatever the last rAF left in
// renderer.info — a throttled/backgrounded tab otherwise reports the last STREET frame
// (composer + bloom), which reads as a wildly over-budget room. Bit us on the first verify.
P.renderer.info.reset();
P.renderer.render(P.interiorMode.scene, P.camera);
return {
entered: true, state, H: +room.dims.H.toFixed(2),
watchPoints: room.watchPoints.length, stage: !!room.stage,
band: info ? info.band : 0, crowd: info ? info.crowd : 0,
dancers: mem.filter(m => m.part === 'crowd' && m.dance).length,
gigKey: room.audio.gigKey || null, musicKey: room.audio.musicKey || null,
draws: P.renderer.info.render.calls, tris: P.renderer.info.render.triangles, figs,
};
}""")
if not night.get('entered'):
FAIL(f"gig night: could not enter the venue at NIGHT ({night})")
else:
if night['state'] == 'on': OK(f"gig state machine: NIGHT → 'on' (venue {night['H']}m room)")
else: FAIL(f"gig state machine: NIGHT should be 'on', got '{night['state']}'")
# band is a 4-piece (R13 debt #3: front-line trio + a drummer on C's riser)
if night['band'] == 4: OK('band: 4-piece on the stage (front-line trio + drummer)')
else: FAIL(f"band: expected a 4-piece (trio + drummer), got {night['band']}")
# crowd cap ≤ watchPoints
if night['crowd'] <= night['watchPoints'] and night['crowd'] > 0:
OK(f"crowd: {night['crowd']} at {night['watchPoints']} watch points (≤ cap)")
else:
FAIL(f"crowd {night['crowd']} vs {night['watchPoints']} watch points (must be >0 and ≤ cap)")
# John's "a bit of both": the crowd must not be all-standing or all-dancing
d, c = night['dancers'], night['crowd']
if c and 0 < d < c: OK(f"crowd mix: {d} dancing / {c - d} standing — 'a bit of both'")
else: FAIL(f"crowd is not mixed: {d} dancing of {c} (John's ruling: some of both)")
# the live bed actually resolves (the C↔E gigKey seam F bridges — see LANE_F_NOTES §12)
if night['gigKey']: OK(f"live bed: room.audio.gigKey = {night['gigKey']} (F resolves → manifest key)")
else: FAIL('live bed: no room.audio.gigKey on a gig-on venue (Lane C opts.gig seam)')
# gig-night interior draw budget (ROUND12: ≤350 incl. the crowd)
if night['draws'] <= GIG_DRAWS_MAX:
OK(f"gig-night interior budget: {night['draws']} draws ≤ {GIG_DRAWS_MAX} (tris {night['tris']:,})")
else:
FAIL(f"gig-night interior budget: {night['draws']} draws > {GIG_DRAWS_MAX} (tris {night['tris']:,})")
# no giants on the stage/crowd rigs (R10 gate, inherited by the gig path)
rigs = [f for f in night['figs'] if f['kind'] == 'rig']
bad = [f for f in rigs if not stature_ok(f) or f['crown'] >= night['H']] # [R16 #3] seated exemption
if not rigs:
WARN(f"no-giants: crew spawned as placeholders (no fleet) — rig check n/a ({len(night['figs'])} figs)")
elif bad:
FAIL(f"no-giants: {len(bad)} gig rig(s) out of [1.4,2.0]m stature or through the {night['H']}m ceiling: {bad[:3]}")
else:
lo = min(f['stature'] for f in rigs); hi = max(f['stature'] for f in rigs)
hc = max(f['crown'] for f in rigs)
OK(f"no-giants: {len(rigs)} band+crowd rigs human-sized ({lo:.2f}{hi:.2f} m stature; "
f"tallest crown {hc:.2f} m under the {night['H']} m ceiling)")
if errs: FAIL(f"gig night: {len(errs)} console error(s); first: {errs[0][:140]}")
else: OK('gig night: 0 console errors')
finally:
b.close()
# 2b) THE DISTRICT (R13): every playing venue fires — its live bed resolves to a REAL manifest bar for
# its genre (debt #1, now across pub/band_room/rsl), its crowd is capped at its OWN watch points, and it
# is a 4-piece. Plus the queue ≤ cap / drains-by-done probe (WARN-skips until Lane D wires the seam).
music_keys = set()
try:
mani = json.loads((ROOT / 'web' / 'assets' / 'manifest.json').read_text())
music_keys = set((mani.get('audio', {}).get('music', {}) or {}).keys())
except Exception as e:
WARN(f"district: could not read manifest music keys ({e}) — gigKey-resolves check is JS-only")
b, pg, errs = new_page(p)
try:
boot(pg, '')
pg.keyboard.press('Space')
district = pg.evaluate("""async () => {
const P = window.PROCITY, D = window.DBG, THREE = P.THREE, v = new THREE.Vector3();
D.setSegment(5); // NIGHT — every playing venue is 'on'
const ids = P.gigs.venueShopIds || [];
const out = [];
for (const id of ids) {
const shop = (P.plan.shops || []).find(s => s.id === id);
if (!P.gigs.onOf(id)) { out.push({ id, kind: shop && shop.venueKind, dark: true }); continue; }
D.enterShop(id);
await new Promise(r => setTimeout(r, 450));
const room = P.interiorMode.current, info = P.interiorMode.crewInfo;
const mem = P.interiorMode.crew ? P.interiorMode.crew.members : [];
const figs = mem.map(m => {
let crown = 0; m.actor.fig.updateWorldMatrix(true, true);
(m.actor.inner || m.actor.fig).traverse(o => { if (o.isBone && /head/i.test(o.name)) {
o.getWorldPosition(v); crown = Math.max(crown, v.y); } });
return { part: m.part, kind: m.kind, stature: +(crown - (m.base.y || 0)).toFixed(3),
seated: !!(m.actor.fig.userData && m.actor.fig.userData.procitySeated) }; // [R16 #3]
});
P.renderer.info.reset(); P.renderer.render(P.interiorMode.scene, P.camera);
out.push({ id, kind: shop && shop.venueKind, genreKey: shop && shop.genreKey,
gigKey: (room && room.audio && room.audio.gigKey) || null,
band: info ? info.band : 0, crowd: info ? info.crowd : 0,
watchPoints: room ? room.watchPoints.length : 0, figs });
D.exitShop(); await new Promise(r => setTimeout(r, 200));
}
return { ids, out };
}""")
playing = [d for d in district['out'] if not d.get('dark')]
OK(f"district: {len(district['ids'])} venue(s) — {len(playing)} playing tonight, "
f"{len(district['out']) - len(playing)} dark (reads true to the seeded week)")
genres_seen = set()
for d in playing:
tag = f"{d['kind']} #{d['id']}"
want = f"gig-{d['genreKey']}"
if d['gigKey'] == want and (not music_keys or want in music_keys):
OK(f" {tag}: live bed '{d['gigKey']}' resolves to a real manifest bar (genre {d['genreKey']})")
genres_seen.add(d['genreKey'])
else:
FAIL(f" {tag}: gigKey '{d['gigKey']}' != '{want}' or not in manifest.music {sorted(music_keys)}")
if 0 < d['crowd'] <= d['watchPoints']:
OK(f" {tag}: crowd {d['crowd']}{d['watchPoints']} watch points")
else:
FAIL(f" {tag}: crowd {d['crowd']} vs {d['watchPoints']} watch points (must be >0 and ≤ cap)")
if d['band'] == 4: OK(f" {tag}: 4-piece band")
else: FAIL(f" {tag}: expected a 4-piece, got {d['band']}")
rigs = [f for f in d['figs'] if f['kind'] == 'rig']
bad = [f for f in rigs if not stature_ok(f)] # [R16 #3] seated exemption
if rigs and bad: FAIL(f" {tag}: {len(bad)} giant rig(s) by stature: {bad[:2]}")
if len(genres_seen) >= 2:
OK(f"district: {len(genres_seen)} distinct genres verified live ({', '.join(sorted(genres_seen))})")
# queue ≤ cap and drains by 'done' — Lane D's outdoor line (charter item). D shipped the VenueQueue
# class and left the street-side lifecycle to F, which wires it in the shell (spawn at doors, drive
# each frame, disposeAll at done); read via P.queueCountOf(id). The line is a seeded short 26.
q = pg.evaluate("""async () => {
const P = window.PROCITY, D = window.DBG;
if (typeof P.queueCountOf !== 'function') return { wired: false };
const id = P.gigs.venueShopIds[0];
D.setSegment(4); await new Promise(r => setTimeout(r, 700)); // DUSK — doors open, the line forms
const atDoors = P.queueCountOf(id) | 0;
D.setSegment(0); await new Promise(r => setTimeout(r, 700)); // DAWN — closing time ('done'), line gone
const atDone = P.queueCountOf(id) | 0;
return { wired: true, atDoors, atDone };
}""")
QUEUE_CAP = 6
if not q.get('wired'):
FAIL("queue: P.queueCountOf(id) not exposed — F's shell wiring missing")
elif 0 < q['atDoors'] <= QUEUE_CAP and q['atDone'] == 0:
OK(f"queue: {q['atDoors']} punters in line at doors (≤ cap {QUEUE_CAP}), drained to 0 by 'done'")
else:
FAIL(f"queue: at doors {q['atDoors']} (want 1..{QUEUE_CAP}), at done {q['atDone']} (want 0)")
if errs: FAIL(f"district: {len(errs)} console error(s); first: {errs[0][:140]}")
else: OK('district: 0 console errors across all venues')
finally:
b.close()
# 2c) STREET BUDGET at the busiest venue block (R13 law: ≤300 draws / ≤200k tris with the lit frontage,
# town-wide posters and the queue zone all in). Park on the footpath in front of the primary venue at
# NIGHT with the gig on, let the block stream in, and sample the settled composer frame.
b, pg, errs = new_page(p)
try:
boot(pg, '')
st = pg.evaluate("""async () => {
const P = window.PROCITY, D = window.DBG;
D.setSegment(5); // NIGHT — frontage lit, posters up
const id = P.gigs.venueShopIds[0], shop = (P.plan.shops || []).find(s => s.id === id);
const lot = shop && (P.plan.lots || []).find(l => l.id === shop.lot);
if (lot) {
const fx = Math.sin(lot.ry || 0), fz = Math.cos(lot.ry || 0);
D.teleport(lot.x + fx * 10, lot.z + fz * 10, Math.atan2(-fx, -fz)); // 10 m out, facing the venue
}
await new Promise(r => setTimeout(r, 700)); // chunks/frontage/posters/pools settle
let draws = Infinity, tris = 0; // take the settled floor (ignore stream spikes)
for (let i = 0; i < 12; i++) {
await new Promise(r => requestAnimationFrame(r));
if (P.renderer.info.render.calls < draws) { draws = P.renderer.info.render.calls; tris = P.renderer.info.render.triangles; }
}
return { draws, tris, venue: shop && shop.name };
}""")
if st['draws'] <= STREET_DRAWS_MAX and st['tris'] <= STREET_TRIS_MAX:
OK(f"street budget @ venue block ({st['venue']}): {st['draws']} draws ≤ {STREET_DRAWS_MAX}, {st['tris']:,} tris ≤ {STREET_TRIS_MAX:,}")
else:
FAIL(f"street budget @ venue block ({st['venue']}): {st['draws']} draws / {st['tris']:,} tris over ({STREET_DRAWS_MAX} / {STREET_TRIS_MAX:,})")
if errs: FAIL(f"street budget: {len(errs)} console error(s); first: {errs[0][:140]}")
finally:
b.close()
# 3) cover ruling, PAID night (seed 20261990 → $10): debit once · no re-charge on re-entry · broke = knockback
b, pg, errs = new_page(p)
try:
boot(pg, '')
paid = pg.evaluate("""async () => {
const P = window.PROCITY, D = window.DBG;
const id = P.gigs.venueShopIds[0]; // R15: no scalar alias — the primary venue by id
D.setSegment(5);
const cover = P.gigs.coverOf(id);
if (!cover) return { skip: true, cover };
const before = P.wallet.cash();
D.enterShop(id); await new Promise(r => setTimeout(r, 350));
const inRoom1 = !!P.interiorMode.current, afterPay = P.wallet.cash();
D.exitShop(); await new Promise(r => setTimeout(r, 250));
const mid = P.wallet.cash();
D.enterShop(id); await new Promise(r => setTimeout(r, 350));
const inRoom2 = !!P.interiorMode.current, afterReentry = P.wallet.cash();
D.exitShop(); await new Promise(r => setTimeout(r, 250));
// now go broke and try again on the SAME night (stamp already paid → still free), then roll the
// night over (MORNING clears the stamp) and try broke → must be knocked back at the door.
P.wallet.buy({ title: 'qa: drain', price: P.wallet.cash() });
D.setSegment(1); D.setSegment(5); // NIGHT → MORNING → NIGHT = a new night, cover due
const brokeCash = P.wallet.cash();
D.enterShop(id); await new Promise(r => setTimeout(r, 350));
const inRoomBroke = !!P.interiorMode.current;
return { cover, before, afterPay, inRoom1, mid, afterReentry, inRoom2, brokeCash, inRoomBroke,
inv: P.wallet.inventory().map(i => i.title) };
}""")
if paid.get('skip'):
WARN(f"cover: seed {SEED} tonight is free (cover 0) — paid path not exercised here")
else:
if paid['inRoom1'] and paid['afterPay'] == paid['before'] - paid['cover']:
OK(f"cover: ${paid['cover']} debited once at the door (${paid['before']} → ${paid['afterPay']}), entry granted")
else:
FAIL(f"cover: wrong debit/entry on a paid night: {paid}")
if paid['inRoom2'] and paid['afterReentry'] == paid['mid']:
OK(f"cover: re-entry the same night is FREE (no re-charge, still ${paid['afterReentry']})")
else:
FAIL(f"cover: re-entry re-charged or was refused: {paid}")
if not paid['inRoomBroke'] and paid['brokeCash'] < paid['cover']:
OK(f"cover: broke (${paid['brokeCash']}) on a NEW night → polite knockback, no entry")
else:
FAIL(f"cover: broke player got in (or the night never rolled): {paid}")
if errs: FAIL(f"cover: {len(errs)} console error(s); first: {errs[0][:140]}")
finally:
b.close()
# 4) cover ruling, FREE night (seed 1234 → cover 0): walk straight in, wallet untouched
b, pg, errs = new_page(p)
try:
pg.goto(f'{HOST}/index.html?seed={FREE_GIG_SEED}&dbg=1')
pg.wait_for_function("window.DBG && window.DBG.ready === true", timeout=20000)
pg.evaluate("() => { const o=document.getElementById('pc-start'); if(o) o.style.display='none'; }")
free = pg.evaluate("""async () => {
const P = window.PROCITY, D = window.DBG;
const id = P.gigs.venueShopIds[0]; // R15: no scalar alias — the primary venue by id
D.setSegment(5);
const cover = P.gigs.coverOf(id), before = P.wallet.cash();
D.enterShop(id); await new Promise(r => setTimeout(r, 350));
return { cover, before, after: P.wallet.cash(), inRoom: !!P.interiorMode.current, band: P.gigs.bandNameOf(id) };
}""")
if free['cover'] == 0 and free['inRoom'] and free['after'] == free['before']:
OK(f"cover: free night (seed {FREE_GIG_SEED}, {free['band']}) → walked straight in, wallet untouched (${free['after']})")
else:
FAIL(f"cover: free-night entry wrong: {free}")
if errs: FAIL(f"free night: {len(errs)} console error(s); first: {errs[0][:140]}")
finally:
b.close()
# 5) ?noassets=1 + ?gigs=1 — still silent-and-fine: zero audio/band-file fetches, gig still happens
b, pg, errs = new_page(p)
reqs = []
pg.on('request', lambda r: reqs.append(r.url)
if ('/assets/audio/' in r.url or r.url.endswith('.ogg') or r.url.endswith('.m4a')
or 'custom_bands' in r.url or r.url.endswith('.glb')) else None)
try:
boot(pg, 'noassets=1')
pg.keyboard.press('Space')
na = pg.evaluate("""async () => {
const P = window.PROCITY, D = window.DBG;
const id = P.gigs.venueShopIds[0]; // R15: no scalar alias — the primary venue by id
D.setSegment(5);
D.enterShop(id); await new Promise(r => setTimeout(r, 450));
const info = P.interiorMode.crewInfo;
return { state: P.gigs.stateOf(id), inRoom: !!P.interiorMode.current, band: info && info.band,
crowd: info && info.crowd, gigs: (P.plan.gigs || []).length,
bandName: P.gigs.bandNameOf(id), audio: P.audio && P.audio.state.manifest };
}""")
if na['inRoom'] and na['band'] == 4 and na['crowd'] > 0:
OK(f"?noassets=1&gigs=1: the gig still happens (4-piece band, crowd {na['crowd']}, placeholders)")
else:
FAIL(f"?noassets=1&gigs=1: no gig without assets: {na}")
if len(reqs) == 0:
OK(f"?noassets=1&gigs=1: 0 audio/GLB/band-file fetches (manifest={na['audio']}, bands from the generator)")
else:
FAIL(f"?noassets=1&gigs=1: {len(reqs)} asset fetch(es) leaked; first {reqs[0]}")
if errs: FAIL(f"?noassets=1&gigs=1: {len(errs)} console error(s); first: {errs[0][:140]}")
else: OK('?noassets=1&gigs=1: 0 console errors (silent-and-happy)')
finally:
b.close()
GODVERSE_TOWN = 'redhill_godverse'
GODVERSE_SHOP = 3962749 # Monster Robot Party, 147 Musgrave Rd, Red Hill QLD — the real shop
GODVERSE_BASE = f'assets/stock_godverse/{GODVERSE_SHOP}/'
def smoke_real_crate(p):
"""R24 — THE REAL CRATE, gated so it cannot pass without touching its subject (the vacuous-gate law).
R23's #7a would have gone green on "real covers render" while the room held the generic v2 pack and
stock_godverse/ was never fetched once. R24's brief then specified id equality — which ALSO cannot
discriminate, because both packs number items positionally (rec_0000…), so all 120 of the atlas's ids
are generic-pack ids too. Presence proves nothing; slot numbers prove nothing. This smoke asserts the
two things a wrong pack cannot fake:
1. TITLE equality — the room's pack titles are the atlas's (0 of them exist in the generic pack).
2. TEXTURE provenance — the URL the GPU samples IS the shop's atlas file.
Plus the rungs either side: a keyed shop with no atlas falls SOFT to parody, and a plain town is
untouched. Subject absent (no town/atlas) ⇒ explicit SKIP with the reason, never a quiet pass."""
head(f'SMOKE: the real crate (R24 — {GODVERSE_TOWN} · Monster Robot Party · per-shop atlas identity)')
b, pg, errs = new_page(p)
missing = [] # atlas probes that 404 — the fail-soft path, counted not hand-waved
pg.on('response', lambda r: missing.append(r.url) if (r.status == 404 and 'stock_godverse/' in r.url) else None)
try:
pg.goto(f'{HOST}/assets/towns/{GODVERSE_TOWN}.json')
if 'godverse+osm' not in pg.content():
print(f'· SKIP: {GODVERSE_TOWN} cache absent or not godverse+osm — subject absent, nothing asserted (Lane G/E)')
return
boot(pg, f'plansrc=osm&town={GODVERSE_TOWN}&stock=real&dig=1')
res = pg.evaluate("""async ({shop, base}) => {
const D = window.DBG, P = window.PROCITY;
const idxR = await fetch(base + 'stock_record_index.json');
if (!idxR.ok) return { skip: 'atlas absent for ' + shop };
const atlas = new Set(((await idxR.json()).items || []).map(i => i.title));
const town = new Set((((await (await fetch('assets/models/stock_record_index.json')).json()).items) || [])
.map(i => i.title));
const entered = D.enterShop(shop);
await new Promise(r => setTimeout(r, 1400));
const si = D.stockInfo();
if (!si || si.error) return { fail: 'no room: ' + JSON.stringify(si) };
// a keyed shop with NO atlas must fall soft to parody, not inherit this crate
const other = (P.plan.shops || []).find(s => s.godverseShopId && s.godverseShopId !== shop);
let soft = null;
// 'g:<id>' — NOT the bare number: plan ids and godverse ids share a numeric space, so a
// bare id can silently enter a different shop and pass this assertion for the wrong reason.
if (other) { D.exitShop(); await new Promise(r => setTimeout(r, 300));
D.enterShop('g:' + other.godverseShopId); await new Promise(r => setTimeout(r, 900));
const o = D.stockInfo();
soft = { name: o.name, gid: o.godverseShopId, want: other.godverseShopId,
packItems: o.packItems }; }
return {
entered, base: si.base, name: si.name, gid: si.godverseShopId,
inAtlas: si.packTitles.filter(t => atlas.has(t)).length,
inTown: si.packTitles.filter(t => town.has(t)).length,
packItems: si.packItems,
fromAtlasTex: si.texUrls.filter(u => u.includes(base)).length,
fromTownTex: si.texUrls.filter(u => u.includes('assets/models/stock_')).length,
soft,
};
}""", {'shop': GODVERSE_SHOP, 'base': GODVERSE_BASE})
if res.get('skip'):
print(f"· SKIP: {res['skip']} — subject absent, nothing asserted (Lane G)"); return
if res.get('fail'):
FAIL(f"the crate: {res['fail']}"); return
print(f" room: {res['name']} (godverse {res['gid']}) → base {res['base']}")
OK(f"resolves its OWN base ({res['base']})") if res['base'] == GODVERSE_BASE else FAIL(f"base is {res['base']}, expected {GODVERSE_BASE}")
OK(f"TITLE equality: {res['inAtlas']}/{res['packItems']} titles are the shop's own atlas") \
if (res['packItems'] > 0 and res['inAtlas'] == res['packItems']) \
else FAIL(f"TITLE equality: only {res['inAtlas']}/{res['packItems']} titles are the shop's atlas")
OK(f"TITLE equality: 0/{res['packItems']} titles from the generic pack — the crate is not the v2 pack") \
if res['inTown'] == 0 \
else FAIL(f"TITLE equality: {res['inTown']}/{res['packItems']} titles came from the GENERIC pack")
OK(f"TEXTURE provenance: {res['fromAtlasTex']} tex from the shop's atlas, {res['fromTownTex']} generic — the pixels' own file") \
if (res['fromAtlasTex'] > 0 and res['fromTownTex'] == 0) \
else FAIL(f"TEXTURE provenance: {res['fromAtlasTex']} atlas / {res['fromTownTex']} generic — the GPU is sampling the wrong file")
if res.get('soft') is not None:
sf = res['soft']
# the subject must BE a keyed shop, or this assertion proves nothing (it passed on an
# unkeyed cafe first time round — a vacuous pass inside the anti-vacuous-pass gate)
if sf.get('gid') != sf.get('want'):
FAIL(f"soft fall: entered the WRONG shop — wanted godverse {sf.get('want')}, got {sf.get('gid')} (\"{sf['name']}\")")
elif sf['packItems'] == 0:
OK(f"soft fall: keyed-but-atlas-less \"{sf['name']}\" (godverse {sf['gid']}) → parody, not this crate")
else:
FAIL(f"soft fall BROKEN: \"{sf['name']}\" inherited {sf['packItems']} items")
# Console errors: the ONLY tolerated ones are the atlas probes that 404 — that IS the fail-soft
# path (a keyed shop with no atlas → null → parody), and it is counted and named here, never
# waved through. Any other error fails. The 404 noise itself is a real cost of probing blind;
# the fix is an atlas index in G's namespace so we never ask for what isn't there (F, R24 §24).
probes = len(missing)
if probes and len(errs) == probes:
print(f' · {probes} atlas-probe 404(s) — the fail-soft path, expected; 0 other console errors')
print(f' (cost of probing blind; fix = an atlas index so we never probe an absent shop)')
elif errs:
FAIL(f'the crate: {len(errs)} console error(s), {probes} explained as atlas probes; first: {errs[0][:120]}')
else:
print(' · 0 page errors')
finally:
b.close()
def smoke_continuity(p):
"""R15 identity continuity (ledger #3): the ped who queued/surged into a venue turns up in its crowd.
Exercises F's real relay end-to-end — VenueQueue.admitOne() → onAdmit → citizens.recordVenueEntry →
tonightRoster(id) → GigCrew.spawn({roster}) → the front crowd slots carry those identities. Asserts the
seam on identities (crewInfo.fromRoster + member.pedIndex ∈ tonightRoster), the invariant crowd ⊇
roster ∩ cap, and that an EMPTY roster injects nobody (D's byte-identical law, proven here on the shell
side; D's sim soak proves the sim side). Also ledger #5 — reports SETTLED tris (after the async
instrument GLBs land), not the pre-load number C caught the R13 smoke reporting."""
head('SMOKE: continuity (R15 — the ped you followed is in the crowd · empty-roster clean · settled tris)')
if not gigs_landed(p):
print('· gigs=1 not landed (no plan.gigs) — skipping (Lane A)')
return
b, pg, errs = new_page(p)
try:
boot(pg, '')
pg.keyboard.press('Space')
res = pg.evaluate("""async () => {
const P = window.PROCITY, D = window.DBG;
const id = P.gigs.venueShopIds[0];
const rosterOf = () => (P.citizens.tonightRoster(id) || []).map(e => e.pedIndex);
// 1) EMPTY-ROSTER PATH — enter before any admit; crew must inject only what's in the roster
// (usually 0). This is the shell side of D's "empty roster = byte-identical to R13" law.
D.setSegment(5); // NIGHT — the gig is on
const rosterAtEmpty = rosterOf();
D.enterShop(id); await new Promise(r => setTimeout(r, 550));
const ei = P.interiorMode.crewInfo;
const emptyFromRoster = ei ? ei.fromRoster : -1, emptyCrowd = ei ? ei.crowd : 0;
D.exitShop(); await new Promise(r => setTimeout(r, 250));
// 2) POPULATED PATH — let the street loop spawn the queue, then admit a few EXPLICITLY (rAF
// auto-drain is throttled in automation, per B's note). Each admit fires onAdmit → recordVenueEntry.
for (let i = 0; i < 8; i++) await new Promise(r => requestAnimationFrame(r));
const q = P.venueQueues && P.venueQueues.get(id);
const admitted = [];
if (q) for (let i = 0; i < 3 && q.count() > 0; i++) { const e = q.admitOne(); if (e) admitted.push(e.pedIndex); }
const roster = rosterOf();
D.enterShop(id); await new Promise(r => setTimeout(r, 700)); // build + crew spawn
const info = P.interiorMode.crewInfo, room = P.interiorMode.current;
const crowd = P.interiorMode.crew ? P.interiorMode.crew.members.filter(m => m.part === 'crowd') : [];
const rosterCrowd = crowd.filter(m => m.fromRoster).map(m => m.pedIndex);
// 3) SETTLED TRIS (ledger #5) — immediately after build vs after the instrument GLBs + rig atlas land
P.renderer.info.reset(); P.renderer.render(P.interiorMode.scene, P.camera);
const trisImmediate = P.renderer.info.render.triangles, draws = P.renderer.info.render.calls;
await new Promise(r => setTimeout(r, 1600));
P.renderer.info.reset(); P.renderer.render(P.interiorMode.scene, P.camera);
const trisSettled = P.renderer.info.render.triangles, drawsSettled = P.renderer.info.render.calls;
return { rosterAtEmpty, emptyFromRoster, emptyCrowd, admitted, roster, rosterCrowd,
fromRoster: info ? info.fromRoster : -1, crowd: info ? info.crowd : 0,
watchPoints: room ? room.watchPoints.length : 0,
trisImmediate, draws, trisSettled, drawsSettled };
}""")
# empty-roster path: crew injects exactly what's in the roster at that moment (0 unless surge beat us)
want_empty = min(len(res['rosterAtEmpty']), res['emptyCrowd'])
if res['emptyFromRoster'] == want_empty:
OK(f"empty roster: crew injected {res['emptyFromRoster']} from a {len(res['rosterAtEmpty'])}-entry roster "
f"(crowd {res['emptyCrowd']} — no phantom continuity, R13-identical path)")
else:
FAIL(f"empty roster: fromRoster {res['emptyFromRoster']} != roster∩cap {want_empty} (roster leaked)")
# the queue admits were recorded into tonightRoster (the onAdmit → recordVenueEntry relay)
roster, admitted, rosterCrowd = res['roster'], res['admitted'], res['rosterCrowd']
if admitted and all(a in roster for a in admitted):
OK(f"relay: {len(admitted)} queue admits → tonightRoster ({admitted} ⊆ roster of {len(roster)})")
else:
FAIL(f"relay: queue admits {admitted} not all in tonightRoster {roster} (onAdmit→recordVenueEntry broke)")
# THE SEAM — crowd ⊇ roster ∩ cap, and every roster-crowd member's pedIndex is genuinely in the roster
cap = res['crowd']
want = min(len(roster), cap)
subset_ok = set(rosterCrowd).issubset(set(roster))
if res['fromRoster'] == want and want > 0 and subset_ok and set(roster[:cap]).issubset(set(rosterCrowd)):
OK(f"continuity seam: {res['fromRoster']} of {cap} crowd came from the roster "
f"(crowd ⊇ roster∩cap ✓, all pedIndex ∈ tonightRoster ✓) — the ped you followed is in the crowd")
else:
FAIL(f"continuity seam: fromRoster {res['fromRoster']} vs roster∩cap {want}; rosterCrowd {rosterCrowd} "
f"vs roster {roster} (subset={subset_ok})")
# ledger #5 — settled-tris honesty (report both; interior gate is draws, which stays low)
OK(f"settled tris: {res['trisImmediate']:,} pre-load → {res['trisSettled']:,} settled "
f"(instrument GLBs; draws {res['draws']}{res['drawsSettled']}{GIG_DRAWS_MAX} gate)")
if errs: FAIL(f"continuity: {len(errs)} console error(s); first: {errs[0][:140]}")
else: OK('continuity: 0 console errors')
finally:
b.close()
def main():
srv = ensure_server()
try:
with sync_playwright() as p:
flags = list(KNOWN_FLAGS)
if plansrc_landed(p):
flags.append('plansrc=osm'); print("· plansrc=osm detected as landed — included")
else:
print("· plansrc=osm not landed (Lane A) — skipping that flag")
classic_regression(p) # [R16] the frozen v2 covenant moved here: ?classic=1 → byte-identical
default_boot_gate(p) # [R16] the flip: the no-flag boot is the full experience (all four on)
interior_budget(p)
for fl in flags:
smoke(p, fl.split('=')[0], fl)
smoke(p, 'ALL-ON combo', '&'.join(flags), is_combo=True)
smoke_stock(p) # STRICT (r8) — ?stock=real real record sleeves
smoke_weather(p) # STRICT (r8) — ?weather=rain
smoke_buy(p) # STRICT (r9) — buy-v0
smoke_patronage(p) # STRICT (r9) — peds visit open shops
smoke_booktoy(p) # STRICT (r9) — book/toy real stock
smoke_presence(p) # new (warn) — interior presence (browsers)
smoke_shelfbuy(p) # new (warn) — buy-anywhere (book/toy shelves)
smoke_tram(p) # new (warn) — tram (auto-skips if not landed)
smoke_audio(p) # R11 — audio house-law (silent-happy · pre-gesture · mute · noassets · leak)
smoke_gigs(p) # R12/13 — the district gig layer (schedule · band · crowd · budget · cover · noassets)
smoke_continuity(p) # R15 — identity continuity (the ped you followed is in the crowd · settled tris)
smoke_real_crate(p) # R24 — THE REAL CRATE (per-shop atlas identity · soft fall · no-op off the path)
finally:
if srv: srv.terminate()
head('VERDICT')
print(json.dumps({'fails': len(fails), 'warns': len(warns)}, indent=0))
if fails:
print(f"\033[31m● flags_check RED\033[0m — {len(fails)} failure(s), {len(warns)} warning(s).")
for f in fails: print(' FAIL:', f)
sys.exit(1)
print(f"\033[32m● flags_check GREEN\033[0m — {len(warns)} warning(s).")
for w in warns: print(' warn:', w)
sys.exit(0)
if __name__ == '__main__':
main()