PROCITY/tools/qa.sh
m3ultra 293a82c5e8 R42 THE LOOK: five lane-runs — the pub is dressed, the decks read, and the town is cast
John looked at R41's proof shots and named three faults. All three are fixed, at zero draw
cost, and every lane corrected somebody's numbers on the way — including its own.

E-LOOK (§42.1+§42.2a) — THE PREMISE WAS WRONG. "The pub props have no texture" (my brief,
quoting E's own R41 note) is false: all 37 carry COLOR_0 and three.js renders it. The colour
was BLEACHED — jukebox at saturation 0.02, 15 assets under 0.05; what the source library
called colour was baked AO. So the fix was a palette imposed on colour already present, in
place: 46 assets, draw delta +0 and tri delta +0 on every one, proved the honest way — the
concatenated POSITION/NORMAL/TEXCOORD/index bytes are sha1-identical to R41, so footprints
and origins provably did not move. (A first-pass bbox check ignored node rotations and
"found" 3 phantom violations; thrown out for the byte comparison.) The banquette was 1990s
office teal and went burgundy; the beer umbrella was white and went brewery green.
THE DECK, diagnosed before being touched: 7 greys, chassis 0.05 luminance on a 0.033 bench =
1.21:1, and the one high-contrast feature was INSIDE-OUT (white ring around a dark disc; a
turntable is a dark record on a light platter). Now Technics silver at 6.74:1, black vinyl,
red start/stop. Node contract re-read from the shipped GLB: 4/4 + 1/1 PASS. Filed to R43,
not fixed: deck_1200_rigged has NO PLINTH (bottom 43mm of 119mm lost to R41 join damage).

E-CAST (§42.3) — 19 of 24 ship. Rejected: comical_luchador_01 (Mixamo Ch43, purple mask
reading EL CHUPACABRA, identified from the file's own material name), a 3-heads-tall cartoon
child, a robed wuxia elder, a WW2 infantryman — and dj_phrtt_01, AN UNCLOTHED BODY whose only
texture is a bare-skin atlas, which was in the DEFAULT crowd and live on the depot. It is the
R41 deny-list's failure class through a different door: an innocent name from another
pipeline. Only rendering all 24 and LOOKING found it.
Ruling 2 (prefer CC0) was REFUSED ON MEASUREMENT: Quaternius is 25 nodes / 23 joints and ZERO
of 64 canonical tracks bind — it would have loaded silently and never animated. Replacements
came from Mixamo's Ch## pool, filtered to the contract-clean 65-bone family (candidates ran
65..99 bones; named characters carry facial/hair bones).
THE LICENCE GAP WAS UNREAD, NOT MISSING: every source FBX carries ApplicationVendor "Mixamo,
Inc." internally. Adobe royalty-free, no attribution owed. B's amber rows are now real.

B (§42.5) — MY ASSIGNED FIX WAS USELESS AND B PROVED IT. document.fonts.ready shipped, then
measured: the atlas canvases are already byte-identical across boots, the game ships NO
webfonts (document.fonts.size === 0), and ready fires 50ms BEFORE the first fillText. The
real cause of unreproducible bookmarks is the wall-clock CROWD — 983 changed px of 921,600,
ALL in the pedestrian band; 0 px with the crowd out. New DBG.freeze() takes stable bookmarks
2/5 -> 5/5. Also found: ?pop=0 boots 140 citizens (0 is falsy against the default).
THE BENCH: D's filing correct on both counts. Front-vs-road 90.0deg -> 0.0deg, min=med=max
over 60/60 instances; the streetlight arm now reaches 1.05m over the road where it reached
0.00. Both used a base yaw that pointed +Z down the street while their own comments claimed
otherwise — the side term was already right, which is why it survived 41 rounds of reading.
Also: on any box with CDN access 9 of 60 benches are the depot GLB, 90deg out from the house
law — normalised at the geometry, once, at load. benchStops(plan) exported as one truth.

C (§42.2b) — 122 vs 123 WAS THE CAMERA ASPECT, and nobody was wrong. Draw counts are
post-frustum-cull and the sweep camera inherits the window aspect: 1.25->116, 1.60->122 (C's
harness), 1.778->123 (F's), 2.37->124. Pinned MEASURE_ASPECT=16/9 with a 1.25 control.
E's "keep the bench dark" advice was measured and declined with reason (deck-vs-bench was
already 4.73:1, over threshold). "Reads as a black box" is a claim about pixels, so C measured
pixels: near-black in the booth crop 57.5% -> 26.7%, distinct colours 228 -> 389. Bench
rebuilt 3->7 boxes with an open record bay; headphones on the mixer; faceDoor aims booths at
the player's entry — alignment within 45deg went 49% -> 92%. Two bugs found by looking: a
fitting could be planted INSIDE A WALL (0.77m of a 0.86m half-depth in plaster), and every
booth part sat 25mm sunk into its own worktop.

D (§42.4) — five swaps IN PLACE at the same index (pickRig indexes these arrays and pedIndex
is in the identity signature and the impostor atlas; removal would renumber the town).
Identity signature pre- vs post-swap: 0 differing lines of 150 — who each citizen is did not
move, only which body they wear. dj_phrtt_01 verified gone AT THE WIRE, not from source: a
fresh default boot fetches 32 ped GLBs, none retired.
THE CONTROL ARM FOUND A 41-ROUND-OLD BUG: the ped being RETIRED, comical_boy_01, binds only
3,132 of 3,460 tracks — six thumb bones lack the mixamorig prefix. It had been silently
dropping six tracks per clip since it shipped. Testing the thing being thrown away found it.
Imported B's benchStops mid-round: sitters now 0.0deg to the road, signatures byte-identical
across the rewire. Corrected F's filing: the browse smoke was flaky-by-construction (a 16-25s
race against a live sim), not deterministically red.

F (§42.6) — qa.sh --strict 24 passed / 0 failed / 0 warn / 0 skipped. New Gate 11: period law
(0/5 banned; CONTROL: R41's roster trips all five) and roster licence (24/24, 0 unverified;
CONTROL: an amber row turns 20 red). r41_shots.py promoted warn -> strict.
R41'S BYTE-HASH BAN IS REVERSED AND ITS STATED REASON WITHDRAWN AS WRONG. A gate may pin a
hash under five in-run assertions (classic+flags boot, freeze().stillLive all-false, a second
freeze removing 0 draws, settle until DBG.info() stops changing, hash with the dbg panel
hidden): 5/5 bookmarks now pin to one sha256 over 3 fresh boots. Two findings got it there —
the chunk streamer (a fixed settle is a bet on load) and the fps readout, which with
everything else frozen was the last thing moving at 53-62 px.
?pop=0 fixed (Number.isFinite) — a no-op for 35 rounds. Three red flags_check gates fixed.

Street: noon 282 · NIGHT 291/300 — the nine-draw margin INTACT, every R41 row reproducing to
the digit. Interior at pinned 16:9: 123 @ dept/auto, margin 227. selfcheck 157,647/157,647,
0x5f76e76 unmoved. No tag — John's playtest rules the epoch.

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

342 lines
23 KiB
Bash
Executable File
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 bash
# PROCITY Lane F — the QA gate runner (CITY_SPEC / LANE_F acceptance).
#
# tools/qa.sh run every gate whose inputs have landed; skip the rest
# tools/qa.sh --strict also FAIL on any still-pending lane deliverable (use at v1 sign-off)
#
# Design: F runs before AE finish. A gate whose target file does not exist yet is reported
# SKIPPED (yellow), not failed — so this is safe to run continuously as lanes land. Once every
# lane is in, `--strict` turns the skips into hard failures: that is the v1 readiness gate.
#
# Gates:
# 1. scaffold_check.mjs — scaffold + PRNG determinism law + lane-readiness matrix (F-owned)
# 2. citygen selfcheck — node web/js/citygen/selfcheck.js (Lane A ships it)
# 3. manifest validator — python3 pipeline/validate_manifest.py (Lane E ships it)
# 8. the R39 (v9 wave 1) gates — tools/qa/r39_address.mjs · r39_transmission.py · r39_runtime.py
# 9. the R40 gates — tools/qa/door_footpath_check.mjs · r40_lane_b.py · r40_playtest.py
# 10. the R41 (THE LIBRARY LANDS) gates — pipeline/clips_verify.py · tools/qa/r41_postures.mjs ·
# r41_denylist.mjs · r41_citizens.py · r41_shots.py · r41_integration.py
# 11. the R42 (PROVE THE LOOK) gates — tools/qa/r42_roster.mjs (period law · roster licence) ·
# r42_cast.py · r42_shots.py · r42_budget.py · r42_repro.py. --tour adds the proof tour.
#
# Browser-driven gates (determinism-PNG, 10-min soak, budget-HUD, ?noassets run) need the running
# game; they live in tools/soak.md + tools/shots.md and run once Lane B's index.html lands.
set -uo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT"
STRICT=0
[ "${1:-}" = "--strict" ] && STRICT=1
pass=0; failn=0; skip=0
c_red=$'\033[31m'; c_grn=$'\033[32m'; c_yel=$'\033[33m'; c_bold=$'\033[1m'; c_off=$'\033[0m'
hr() { printf '%s\n' "────────────────────────────────────────────────────────"; }
run_gate() { # run_gate "name" cmd...
local name="$1"; shift
printf '%s▶ %s%s\n' "$c_bold" "$name" "$c_off"
if "$@"; then printf '%s ✓ PASS%s %s\n\n' "$c_grn" "$c_off" "$name"; pass=$((pass+1));
else printf '%s ✗ FAIL%s %s\n\n' "$c_red" "$c_off" "$name"; failn=$((failn+1)); fi
}
skip_gate() { # skip_gate "name" "reason"
printf '%s▶ %s%s\n' "$c_bold" "$1" "$c_off"
printf '%s ⊘ SKIP%s %s\n\n' "$c_yel" "$c_off" "$2"
skip=$((skip+1))
}
warnn=0
soft_skip() { # non-blocking skip for a warn-level gate — does NOT trip --strict readiness
printf '%s▶ %s%s\n' "$c_bold" "$1" "$c_off"
printf '%s ⊘ SKIP%s %s (warn-level, non-blocking)\n\n' "$c_yel" "$c_off" "$2"
}
warn_gate() { # warn_gate "name" cmd... — runs, but a failure is a WARN not a FAIL (round-6 policy)
local name="$1"; shift
printf '%s▶ %s%s (warn-level)\n' "$c_bold" "$name" "$c_off"
if "$@"; then printf '%s ✓ PASS%s %s\n\n' "$c_grn" "$c_off" "$name"; pass=$((pass+1));
else printf '%s ! WARN%s %s (non-blocking this round — strict in r7)\n\n' "$c_yel" "$c_off" "$name"; warnn=$((warnn+1)); fi
}
printf '%sPROCITY QA GATES%s (%s)\n' "$c_bold" "$c_off" "$([ $STRICT = 1 ] && echo strict || echo lenient)"
hr
# ── Gate 1: scaffold + determinism + readiness (always) ──────────────────────
run_gate "scaffold_check (scaffold · PRNG determinism · readiness matrix)" \
node tools/qa/scaffold_check.mjs
# ── Gate 1b: cross-lane consistency (plan ↔ registry ↔ assets ↔ manifest) ────
# Self-skips (exit 0) until Lane A's plan.js + registry.js exist.
run_gate "consistency (plan ↔ registry ↔ assets ↔ manifest)" \
node tools/qa/consistency_check.mjs
# ── Gate 2: Lane A citygen selfcheck ─────────────────────────────────────────
if [ -f web/js/citygen/selfcheck.js ]; then
run_gate "citygen selfcheck (determinism · <100ms · lots/shops integrity)" \
node web/js/citygen/selfcheck.js
else
skip_gate "citygen selfcheck" "web/js/citygen/selfcheck.js not landed yet (Lane A)"
fi
# ── Gate 3: Lane E manifest validator ────────────────────────────────────────
# [Lane F R3] --depot: the 16 GLBs are published + live on the 3GOD depot (Fable, 2026-07-14), so the
# gate now REQUIRES them reachable (was a soft local-only check). Override the endpoint with GOD3_DEPOT
# (tailnet path) if digalot.fyi is unreachable; the validator falls back to the manifest's depot field.
if [ -f pipeline/validate_manifest.py ]; then
run_gate "manifest validator (files exist · footprints sane · GLBs live on depot · JSON parses)" \
python3 pipeline/validate_manifest.py --depot
else
skip_gate "manifest validator" "pipeline/validate_manifest.py not landed yet (Lane E)"
fi
# ── Gate 4 (warn-level, round 6): v2 flag enforcement harness ────────────────
# Flags-off regression (prime-law) + per-flag + all-on-combo smokes. Needs the Playwright venv +
# a browser (self-contained server), auto-skips where absent. STRICT as of R7 (F2): a harness FAIL
# now fails qa (flags-off regression + the four existing flags + all-on combo). New flags (stock,
# weather) report at warn inside the harness, so they never trip this gate until they graduate.
# Skip explicitly with --no-flags (fast determinism-only runs).
FLAGS_SKIP=0; for a in "$@"; do [ "$a" = "--no-flags" ] && FLAGS_SKIP=1; done
if [ "$FLAGS_SKIP" = 1 ]; then
soft_skip "v2 flags harness" "--no-flags"
elif [ -x tools/.venv/bin/python ] && tools/.venv/bin/python -c "import playwright" 2>/dev/null; then
run_gate "flags harness (classic regression · default-boot gate · per-flag · smokes · STRICT · R16 flip)" \
tools/.venv/bin/python tools/flags_check.py
else
soft_skip "v2 flags harness" "Playwright venv absent (python3 -m venv tools/.venv && …/pip install playwright)"
fi
# ── Gate 5 (Lane D, round 10): interior figure-scale regression guard ────────
# Enters a sample of open shops and asserts every interior rig figure's crown is human-sized
# [1.4,2.0] m AND under the room ceiling — so the R9 giant blocker (buildFigure normalised off
# head-above-hips → ~2× too tall) can never pass a gate again. run_gate ⇒ a giant FAILS qa.
# Needs the Playwright venv (auto-skips where absent, like the flags harness). Wired per ROUND10 §D.
SCALE_SKIP=0; for a in "$@"; do [ "$a" = "--no-scale" ] && SCALE_SKIP=1; done
if [ "$SCALE_SKIP" = 1 ]; then
soft_skip "interior figure-scale gate" "--no-scale"
elif [ -x tools/.venv/bin/python ] && tools/.venv/bin/python -c "import playwright" 2>/dev/null; then
run_gate "interior figure-scale (no giants: crown ∈ [1.4,2.0] m, under ceiling · Lane D R10)" \
tools/.venv/bin/python tools/qa/interior_scale_check.py
else
soft_skip "interior figure-scale gate" "Playwright venv absent (python3 -m venv tools/.venv && …/pip install playwright)"
fi
# ── Gate 6 (Lane F, round 15): full-week gig soak — OPT-IN (--soak), NON-STRICT ─
# The v3.0 release soak: 7 nights × every playing venue in one context — latch state per plan.gigs,
# cover stamp per venue per night, roster clears per night, leak geo/tex to a warmed baseline across
# ≥20 enter/exit cycles. Slow (~90s) + diagnostic, so it is OPT-IN and warn-level (never blocks a tag);
# the core 6 gates stay a fast strict run. Wired per ROUND15 §Lane F.3.
SOAK_RUN=0; for a in "$@"; do [ "$a" = "--soak" ] && SOAK_RUN=1; done
if [ "$SOAK_RUN" = 1 ]; then
if [ -x tools/.venv/bin/python ] && tools/.venv/bin/python -c "import playwright" 2>/dev/null; then
warn_gate "full-week gig soak (7 nights · cover/roster per night · leak-free · Lane F R15)" \
tools/.venv/bin/python tools/qa/week_soak.py
else
soft_skip "full-week gig soak" "Playwright venv absent"
fi
fi
# ── Gate 7 (Lane F, round 17): town-matrix smoke — OPT-IN (--matrix), NON-STRICT ─
# The v4 real-map scout evidence: boot / determinism / budget / district / noassets across the full town
# matrix — synthetic hero seeds + the 3 osm fixtures + Lane E's 5 real AU town caches. One town × gate
# table. Slow (~20 boots) + diagnostic, so OPT-IN + warn-level. Wired per ROUND17 §Lane F (ledger #7).
MATRIX_RUN=0; for a in "$@"; do [ "$a" = "--matrix" ] && MATRIX_RUN=1; done
if [ "$MATRIX_RUN" = 1 ]; then
if [ -x tools/.venv/bin/python ] && tools/.venv/bin/python -c "import playwright" 2>/dev/null; then
warn_gate "town-matrix (synthetic + fixtures + real caches × 5 gates · Lane F R17)" \
tools/.venv/bin/python tools/qa/town_matrix.py
else
soft_skip "town-matrix" "Playwright venv absent"
fi
fi
# ── Gate 8 (Lane F, round 39): the v9 wave-1 gates ───────────────────────────
# 39.1 THE ADDRESS LAYER (node, no browser) · 39.4 THE TRANSMISSION PRE-PASS (validator control +
# the interior draw sweep against a fabricated glass asset) · 39.2 THE FOG / THE SIGN / THE RUMMAGE
# BIN (browser). Each ships its falsifiability control in the same run — see LANE_F_NOTES §39.
# --no-r39 skips the browser half on a fast determinism-only run; the node half always runs.
run_gate "R39 address layer (>=97% corpus · degrade-to-district · the wrong-shift red arm)" \
node tools/qa/r39_address.mjs
R39_SKIP=0; for a in "$@"; do [ "$a" = "--no-r39" ] && R39_SKIP=1; done
if [ "$R39_SKIP" = 1 ]; then
soft_skip "R39 transmission + runtime gates" "--no-r39"
elif [ -x tools/.venv/bin/python ] && tools/.venv/bin/python -c "import playwright" 2>/dev/null; then
run_gate "R39 transmission pre-pass (validator hard-fail control · interior sweep vs a glass asset)" \
tools/.venv/bin/python tools/qa/r39_transmission.py --validator
run_gate "R39 runtime (THE SIGN · THE FOG both arms · THE RUMMAGE BIN, contents unarmed)" \
tools/.venv/bin/python tools/qa/r39_runtime.py
else
soft_skip "R39 transmission + runtime gates" "Playwright venv absent"
fi
# ── Gate 9 (Lane F, round 40): the R40 gates ─────────────────────────────────
# [Lane F R40] Three gates, wired per each lane's §40 ask:
# • D §40.5 — door→footpath (node, ~2s, zero deps): walkable-front ≥95% on all four towns, the
# back-point control non-vacuous, the kerb clamp FIX-ONLY. rc 0/1.
# • B §40.3 — the ?r= budget law (default 300 / diagnostic 420 + warn, measured stepwalk ≤ own
# ceiling), classic's town selector (RULED R40), fog signage both arms. Own no-store server.
# • F §40.6 — the playtest harness: off-boot byte-identical (no fetch, no DOM, no storage), the
# F8 note round-trip (drop → export → schema-validate → reload persists → corrupt-reject), and
# ?bugtour=1 advancing through every stop with zero console errors.
run_gate "R40 door→footpath (D §40.5: walkable ≥95% · back-point control · fix-only clamp)" \
node tools/qa/door_footpath_check.mjs
R40_SKIP=0; for a in "$@"; do [ "$a" = "--no-r40" ] && R40_SKIP=1; done
if [ "$R40_SKIP" = 1 ]; then
soft_skip "R40 browser gates (budget law · selector ruling · playtest harness)" "--no-r40"
elif [ -x tools/.venv/bin/python ] && tools/.venv/bin/python -c "import playwright" 2>/dev/null; then
run_gate "R40 ?r= budget law + classic selector + fog signage (B §40.3, both arms each)" \
tools/.venv/bin/python tools/qa/r40_lane_b.py
run_gate "R40 playtest harness (F §40.6: off byte-identical · F8 round-trip · bugtour clean)" \
tools/.venv/bin/python tools/qa/r40_playtest.py
else
soft_skip "R40 browser gates (budget law · selector ruling · playtest harness)" "Playwright venv absent"
fi
# ── Gate 10 (Lane F, round 41): THE LIBRARY LANDS ────────────────────────────
# [Lane F R41 §41.6] Six gates, wired per each lane's §41 ask. The node/stdlib half always runs
# (fast, zero deps); the browser half rides the Playwright venv like every other class here.
#
# • E §41.1 — pipeline/clips_verify.py: CLIP-MANIFEST INTEGRITY. Every clip id in
# web/assets/motion_manifest.json resolves to a named animation in its group GLB, sizes match,
# all six groups are zero-draw and Draco-free, all six carry the identical 66-node skeleton,
# every channel targets a real node, every declared duration equals the keyframes in the bytes.
# Ships with the library, so a renamed clip or a re-packed group fails HERE, not in the game.
# • D §41.3 — r41_postures.mjs: the posture table resolves, is deterministic (two module
# instances byte-equal), stream-isolated from the 12 pre-R41 keys, spread across all 10 idles,
# and loop-safe. Five controls in the same run.
# • F §41.6 — r41_denylist.mjs: RULING 3 AS REPO LAW. Nothing under web/ may reference OR CONTAIN
# a banned name — paths AND bytes, binaries included (a GLB carries its source node names). The
# control plants a real banned reference, asserts RED, removes it, asserts GREEN: this gate can
# never go quietly vacuous.
# • D §41.3 — r41_citizens.py: 7 browser arms (boot ledger · lazy loading · determinism incl. a
# 2 s forced clip stall · the draw table both arms · ?noassets/?classic · bench binding + its
# 2 m-offset control · leak · gig widening).
# • D §41.3 — r41_shots.py: the acceptance shots, camera chosen by measurement + occlusion
# raycast, every figure measured for stature (the R10 no-giants line). Written to a scratch
# outdir by default so a gate run never rewrites the round's committed shots; set
# PROCITY_SHOT_OUT=docs/shots/laneD to regenerate them deliberately. WARN-LEVEL — see the
# measured reason at the call site (its browse arm can lose its browser to the sim mid-scan).
# • F §41.6 — r41_integration.py: the seams F wired and the budgets F answers for — the wardrobe
# both arms (ON resolves the pack, OFF is the control and is what ships default), the interior
# ≤350 law on F'S OWN sweep plus the PHANTOM control that reproduces the pre-R41 instrument bug,
# ?noassets=1 clean over a three-shop walk, ?clips=0 / ?classic=1 carrying no R41 cargo, and the
# deny-list checked again from the network side.
if [ -f pipeline/clips_verify.py ]; then
run_gate "R41 clip-manifest integrity (E §41.1: 46 clips resolve · zero-draw · one skeleton · durations in the bytes)" \
python3 pipeline/clips_verify.py
else
skip_gate "R41 clip-manifest integrity" "pipeline/clips_verify.py not landed yet (Lane E)"
fi
run_gate "R41 postures (D §41.3: manifest resolution · determinism · stream isolation · pool spread · loop safety)" \
node tools/qa/r41_postures.mjs
run_gate "R41 deny-list (F §41.6 / ruling 3: web/ clean by path AND bytes · plant-and-remove control)" \
node tools/qa/r41_denylist.mjs
R41_SKIP=0; for a in "$@"; do [ "$a" = "--no-r41" ] && R41_SKIP=1; done
if [ "$R41_SKIP" = 1 ]; then
soft_skip "R41 browser gates (citizens · acceptance shots · integration)" "--no-r41"
elif [ -x tools/.venv/bin/python ] && tools/.venv/bin/python -c "import playwright" 2>/dev/null; then
run_gate "R41 citizens (D §41.3: boot ledger · lazy · determinism under a 2s stall · bench binding + control · leak)" \
tools/.venv/bin/python tools/qa/r41_citizens.py
# [Lane F R42 §42.6] PROMOTED FROM warn_gate TO run_gate. R41 wired this warn-level because its
# BROWSE arm scanned up to 8 shops for browsers, kept the best, then RE-ENTERED the winner 16-25 s
# later — and a patronage occupant is free to walk out in the meantime, so the re-entry could spawn
# 0 browsers and the arm asserted on an empty room. F filed it as deterministically red; D measured
# the filing before fixing it (§42.4) and found it was FLAKY BY CONSTRUCTION, not always red — which
# is the property that cannot go strict. D's fix removes the window rather than widening it: the
# frame, the measurements and the JPEG are now taken INSIDE the winning room the moment a candidate
# beats the incumbent, and the harness never leaves. The residual failure ("no occupied shop has
# browse points with browsers at all") is real, deterministic and worth failing on. So it is strict.
run_gate "R41 acceptance shots (D §41.3 + §42.4: measured camera · shot in place · R10 stature line per figure)" \
tools/.venv/bin/python tools/qa/r41_shots.py --outdir "${PROCITY_SHOT_OUT:-${TMPDIR:-/tmp}/procity-r41-shots}"
run_gate "R41 integration (F §41.6: wardrobe both arms · interior ≤350 on F's sweep + phantom control · noassets · clip gates · deny-list live)" \
tools/.venv/bin/python tools/qa/r41_integration.py
else
soft_skip "R41 browser gates (citizens · acceptance shots · integration)" "Playwright venv absent"
fi
# ── Gate 11 (Lane F, round 42): PROVE THE LOOK ───────────────────────────────
# [Lane F R42 §42.6] R42 is a LOOK round and its two new laws are about WHAT SHIPS IN A BODY, so the
# two cheapest gates are node/stdlib and always run. Each ships its control demonstrated.
#
# • F §42.6 / ruling 1 — r42_roster.mjs --gate period: NO BANNED-GENRE BODY SHIPS. PROCITY is 1990s
# Australia; the R42 audit retired a purple luchador, a cartoon child, a wuxia elder, a WW2
# infantryman and — the one nobody had looked at in six rounds — an UNCLOTHED body that was in the
# default crowd. The gate reads `PED_NAMES` in rigs.js (the only list the game loads from), not a
# directory listing: the five files are still on disk by design, because deleting a name mid-wave
# renumbers every pickRig index. Five arms: the roster, the five replacements at their own slots,
# the pool shape 17/5/2, no banned name on any LOADABLE surface, and a QUARANTINE arm — every ped
# GLB on disk must be shipped, a base clip, or explicitly recorded as withdrawn in credits.json,
# so a sixth un-vetted body cannot appear silently. CONTROL: the R41 roster is replayed through
# the same predicates and must trip all five.
# • F §42.6 / ruling 3 — r42_roster.mjs --gate licence: ROSTER LICENCE COMPLETENESS. Every shipped
# ped resolves to a credits.json row with a real licence; RED on any `unverified`. CONTROL: the
# same predicate on a synthetic doc with R41's amber row restored must turn 20 peds red.
# • D §42.4 — r42_cast.py: the recast, wired. Contract from the GLB bytes over all 54 clips, the
# identity signature byte-identical across the swap, draw-neutrality measured as the crowd's own
# difference (render, hide citizens.group, render), animation, and the impostor seam.
# • D §42.4 — r42_shots.py: the street + pub crowd frames, every figure resolved to its body.
# Scratch outdir like the R41 shot gate, so a routine run never rewrites the round's shots.
# • F §42.6 — r42_budget.py: the interior ≤350 law with THE INSTRUMENT PINNED. C proved in §42.2b
# that the 122-vs-123 argument was the CAMERA ASPECT (a fixed vertical fov inheriting the window's
# aspect), so this sweep sets `camera.aspect = 16/9` explicitly and ships two controls: the same
# sweep at 1.25 must read C's published 116, and the pinned sweep run in a 1280x1024 window must
# return the SAME number as in 1280x720 — which is the actual claim that the gate can no longer
# drift when someone edits an unrelated `new_page`.
# • F §42.6 — r42_repro.py: THE BYTE-HASH GATE, and the R41 ruling reversed on measurement. See
# F-progress §42.6 for the ruling itself. Pins one sha256 per bookmark across 3 fresh frozen
# boots, asserts `DBG.freeze().stillLive` is all-false and that the second freeze removes nothing,
# and carries an unfrozen CONTROL that must still be able to fail.
run_gate "R42 period law (F §42.6 / ruling 1: no banned-genre body ships · quarantine arm · R41-roster control)" \
node tools/qa/r42_roster.mjs --gate period
run_gate "R42 roster licence (F §42.6 / ruling 3: every shipped ped has a real credits row · unverified control)" \
node tools/qa/r42_roster.mjs --gate licence
R42_SKIP=0; for a in "$@"; do [ "$a" = "--no-r42" ] && R42_SKIP=1; done
if [ "$R42_SKIP" = 1 ]; then
soft_skip "R42 browser gates (cast · shots · interior budget · byte-hash pin)" "--no-r42"
elif [ -x tools/.venv/bin/python ] && tools/.venv/bin/python -c "import playwright" 2>/dev/null; then
run_gate "R42 cast wired (D §42.4: contract from the bytes · identity byte-identical · crowd-difference draws · impostor seam)" \
tools/.venv/bin/python tools/qa/r42_cast.py --outdir "${PROCITY_SHOT_OUT:-${TMPDIR:-/tmp}/procity-r42-shots}"
run_gate "R42 cast shots (D §42.4: street + pub crowd, every figure resolved to its ped GLB)" \
tools/.venv/bin/python tools/qa/r42_shots.py --outdir "${PROCITY_SHOT_OUT:-${TMPDIR:-/tmp}/procity-r42-shots}"
run_gate "R42 interior budget (F §42.6: ≤350 with the aspect PINNED 16:9 + the 1.25 and viewport controls)" \
tools/.venv/bin/python tools/qa/r42_budget.py
run_gate "R42 byte-hash pin (F §42.6: 1 sha256 per bookmark over 3 frozen boots · stillLive all-false · unfrozen control)" \
tools/.venv/bin/python tools/qa/r42_repro.py
else
soft_skip "R42 browser gates (cast · shots · interior budget · byte-hash pin)" "Playwright venv absent"
fi
# ── Gate 11b (Lane F, round 42): THE PROOF TOUR — OPT-IN (--tour), NON-STRICT ─
# [Lane F R42 §42.6] The round's DELIVERABLE (this round is judged by eye), not a regression gate:
# six frames re-shot on the integrated tree and composited against the COMMITTED R41 originals, each
# humanoid frame carrying its R10 human-sized line and its CAST line. It is opt-in for two measured
# reasons: it costs ~8 minutes of browser time (the street arm HOLDS until the live crowd offers a
# frame with three posture states in it — that dwell is the method, not slack), and pointed at its
# real outdir it REWRITES the round's committed shots. `--tour` runs it to a scratch outdir;
# PROCITY_SHOT_OUT=docs/shots/laneF_r42 regenerates the deliverable deliberately.
TOUR_RUN=0; for a in "$@"; do [ "$a" = "--tour" ] && TOUR_RUN=1; done
if [ "$TOUR_RUN" = 1 ]; then
if [ -x tools/.venv/bin/python ] && tools/.venv/bin/python -c "import playwright" 2>/dev/null; then
warn_gate "R42 proof tour (F §42.6: the paired before/after deliverable · R10 + CAST lines)" \
tools/.venv/bin/python tools/qa/r42_tour.py --outdir "${PROCITY_SHOT_OUT:-${TMPDIR:-/tmp}/procity-r42-tour}"
else
soft_skip "R42 proof tour" "Playwright venv absent"
fi
fi
# ── Summary ──────────────────────────────────────────────────────────────────
hr
printf '%sSUMMARY%s %s%d passed%s · %s%d failed%s · %s%d warn%s · %s%d skipped%s\n' \
"$c_bold" "$c_off" "$c_grn" "$pass" "$c_off" "$c_red" "$failn" "$c_off" "$c_yel" "$warnn" "$c_off" "$c_yel" "$skip" "$c_off"
if [ "$failn" -gt 0 ]; then
printf '%s● QA RED%s — fix the failing gate(s) above.\n' "$c_red" "$c_off"; exit 1
fi
if [ "$STRICT" = 1 ] && [ "$skip" -gt 0 ]; then
printf '%s● QA NOT READY%s — %d gate(s) still pending; not v1 yet.\n' "$c_yel" "$c_off" "$skip"; exit 2
fi
printf '%s● QA GREEN%s — every landed gate passed.\n' "$c_grn" "$c_off"; exit 0