PROCITY/tools/qa/tour_shots.py
m3ultra b2d29ba112 Lane F R27 (v5.0): the covenant re-pinned, the frame that took five tries, QA GREEN
qa.sh --strict --matrix: 7 passed / 0 failed / 0 warn. selfcheck ALL GREEN 156,352/156,352 ·
MATRIX GREEN 10 towns x 7 gates · scaffold_check GREEN (F's re-pins took) · flags harness green,
carrying the kill-the-server gate and the beta crate gate.

F RE-PINNED ITS OWN FILES per A's handoff (A doesn't edit other lanes' files, correctly):
GOLDEN_HASH 0x3fa36874 -> 0x5f76e76 · GIG_GOLDEN 0xb1d48ea1 -> 0xec7a2d39 · melbourne · katoomba
· the captions. AND A CAUGHT A LIVE HAZARD IN F'S GATE: hash_ok carried `or ('3fa36874' in
r.stdout)` — a bare-string OR-clause, dead the moment the covenant moved and worse than dead,
because it would go green off a SENTENCE ABOUT the old covenant appearing in stdout. Deleted.
The check derives from GOLDEN_HASH or it doesn't exist.

JOHN'S RULING IS THE EPOCH'S BEST LINE: the covenant is anti-drift machinery, not a defect
preservative. 0x3fa36874 did its job for five epochs — it never drifted. It just was never right,
and no hash can catch what was wrong in the first commit. A golden proves NOTHING CHANGED; it
cannot prove anything is CORRECT. Every gate we own was green over a world of back walls, and the
only thing that ever saw it was a human standing in the street looking at a wall.

THE FRONTAGE FRAME TOOK FIVE ATTEMPTS, FOUR OF THEM F'S FAULT:
  1-3. cluster pose (the town's heart isn't this shop) / lot-anchored facing along the edge /
       lot-normal facing — three photographs of empty road. Also never winnable: the shops faced
       backwards the whole time. F was posing a camera at a wall and tuning the geometry.
  4.   B's helper — and F THREW THE RESULT AWAY. poseForShopFront RETURNS a pose; it does not
       apply one. F called it, error-checked it, and never touched the camera, so the frame was
       the spawn paddock — and F's error-check PASSED, because the call succeeded perfectly. The
       vacuous check one last time, in the last frame of the epoch.
  5.   Applied it -> MONSTER ROBOT PARTY. Sign, door, awning, a ped walking in off Musgrave Road.

The measurement that would have found the facade bug in five minutes — compare the two
conventions against each other — is the one nobody ran for nine rounds, F included, right up to
the vacuous 72/72 F nearly filed as "B is wrong".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 16:48:22 +10:00

154 lines
8.4 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 — the v4.0 RELEASE TOUR (round 22 · the epoch close · ledger #4). One deterministic run.
The v4 thesis in five frames, into docs/shots/release_v4/ (+ contact.html). The towns ARE the product:
• fremantle_hero — the golden-hour hero: a real shopfront corridor with the density crowd on it
• fitzroy_mecca — the mecca (160 shops, 2× anything absorbed before this epoch)
• toowoomba_thintail — the thin tail (12 shops): proof the floor still reads as a town
• synthetic_control — the procedural town, UNCHANGED: the control the whole epoch was measured against
• classic_covenant — `?classic=1`: the v2 covenant made visible, byte-identical forever (0x5f76e76)
Posing: every real-town frame resolves through Lane B's `DBG` retail-heart anchor / `clusterPose` — the
primitive that came out of F's R21 finding (per-EDGE shop counts are ~1 on a real graph, so "densest edge"
is the wrong heuristic; you must project the densest SPATIAL cluster onto its front edge and stand on the
road). B's R22 bookmark fix resolves through the same anchor, so the tour and the bookmarks agree by
construction. The HUD is hidden for every frame (F's R21 ruling corollary — the selector ships, but it
doesn't belong in a beauty shot).
Run: tools/.venv/bin/python tools/qa/tour_shots.py [--seed N]
Needs the Playwright venv + Lane B's ?dbg=1 hook. Writes full-frame 1280x720 PNGs.
"""
import sys, time, socket, subprocess, pathlib
ROOT = pathlib.Path(__file__).resolve().parent.parent.parent
PORT = 8130
HOST = f'http://127.0.0.1:{PORT}'
SEED = 20261990
OUTDIR = ROOT / 'docs' / 'shots' / 'release_v4'
if '--seed' in sys.argv:
SEED = int(sys.argv[sys.argv.index('--seed') + 1])
# (file, query, segment, cluster-pose?, caption-prefix). seg 3 = late-afternoon gold; seg 2 = midday.
# cluster=True → F's spatial-cluster pose (real towns); cluster=False → the bookmark (synthetic is one dense
# cluster already, so street_noon frames it correctly).
V4_TOUR = [
('fremantle_hero', 'plansrc=osm&town=fremantle_real', 3, True, 'the hero — Fremantle, real streets, golden hour'),
('fitzroy_mecca', 'plansrc=osm&town=fitzroy_real', 3, True, 'the mecca — Fitzroy, the densest town in the pack'),
('toowoomba_thintail', 'plansrc=osm&town=toowoomba_real', 2, True, 'the thin tail — Toowoomba: the floor does NOT hold here, and this is the honest frame (12 shops over a 1,459-edge graph = 0.008 shops/edge vs the heroes\' 0.060.08; it reads as road, not town). Filed for v4.x'),
('synthetic_control', f'seed={SEED}', 2, False, 'the control — the procedural town, unchanged'),
('classic_covenant', 'classic=1', 2, False, '?classic=1 — the v2 covenant, byte-identical forever'),
]
# THE POSING PRIMITIVE (F's R21 finding). On a real graph, per-EDGE shop counts are ~1 — so "the edge with
# the most shops" frames an empty arterial (it did, first run: the Fitzroy mecca came out as bare bitumen).
# The town's retail heart is a SPATIAL cluster: find the shop lot with the most neighbours inside 60 m,
# project it onto its own front edge, and stand ON the road backed off along it, looking down the strip.
# Lane B's R22 bookmark fix anchors on the same retail heart — this is that primitive, applied to the tour.
SHOT_JS = r"""
async ({ seg, cluster }) => {
const P = window.PROCITY, D = window.DBG;
D.setSegment(seg);
let hub = -1;
if (!cluster) { D.shot('street_noon'); }
else {
const plan = P.plan, nById = new Map(plan.streets.nodes.map(n => [n.id, n]));
const lots = (plan.lots || []).filter(l => l.use === 'shop' || l.use === 'anchor');
let best = null;
for (const l of lots) { let c = 0; for (const o of lots) if (Math.hypot(o.x - l.x, o.z - l.z) < 60) c++;
if (c > hub) { hub = c; best = l; } }
if (best) {
const e = (plan.streets.edges || []).find(x => x.id === best.frontEdge) || plan.streets.edges[0];
const a = nById.get(e.a), b2 = nById.get(e.b);
const dx = b2.x - a.x, dz = b2.z - a.z, L = Math.hypot(dx, dz) || 1, ux = dx / L, uz = dz / L;
let t = (best.x - a.x) * ux + (best.z - a.z) * uz;
t = Math.max(6, Math.min(L - 6, t));
const back = Math.min(26, t);
D.teleport(a.x + ux * (t - back), a.z + uz * (t - back), Math.atan2(ux, uz));
} else { D.shot('street_noon'); }
}
await new Promise(r => setTimeout(r, 1800)); // crowd + chunks + skins + GLBs settle
const hud = document.getElementById('pc-hud'); if (hud) hud.style.display = 'none'; // beauty shot
for (let i = 0; i < 3; i++) await new Promise(r => requestAnimationFrame(r));
const i = D.info();
return { name: P.plan.name, shops: (P.plan.shops || []).length, hub,
edges: (P.plan.streets.edges || []).length, draws: i.drawCalls, tris: i.tris };
}
"""
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
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}')
def contact_html(shots):
cards = ''.join(
f'<figure><img src="{s["file"]}" alt="{s["name"]}" loading="lazy">'
f'<figcaption><b>{s["name"]}</b><br>{s["caption"]}</figcaption></figure>' for s in shots)
return (
'<!doctype html><meta charset="utf-8"><title>PROCITY v4.0 — the release tour</title>'
'<style>body{background:#14110c;color:#f4efe6;font:14px/1.4 -apple-system,Segoe UI,Roboto,sans-serif;margin:24px}'
'.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(460px,1fr));gap:18px}'
'figure{margin:0;background:#1d1810;border-radius:10px;overflow:hidden}img{width:100%;display:block}'
'figcaption{padding:9px 13px;color:#cdc4b2}b{color:#ffd9a0}</style>'
f'<h1>PROCITY v4.0 — THE REAL MAP <small>· 23 real Australian towns · seed {SEED} · settled</small></h1>'
f'<div class="grid">{cards}</div>')
def main():
try:
from playwright.sync_api import sync_playwright
except ImportError:
sys.exit('playwright not installed — tools/.venv/bin/python -m playwright install chromium')
print(f"\033[1mv4.0 RELEASE TOUR\033[0m ({len(V4_TOUR)} frames → docs/shots/release_v4/)")
OUTDIR.mkdir(parents=True, exist_ok=True)
srv = ensure_server()
shots, errs_total = [], 0
try:
with sync_playwright() as p:
b = p.chromium.launch()
for name, query, seg, cluster, cap in V4_TOUR:
pg = b.new_page(viewport={'width': 1280, 'height': 720})
errs = []
pg.on('pageerror', lambda e: errs.append(str(e)))
pg.goto(f'{HOST}/index.html?{query}&dbg=1')
pg.wait_for_function('window.PROCITY && window.PROCITY.plan && window.DBG && window.DBG.ready', timeout=25000)
pg.evaluate("() => { const o=document.getElementById('pc-start'); if(o) o.style.display='none'; }")
try:
pg.wait_for_function('() => !window.PROCITY.fleet || window.PROCITY.fleet.ready', timeout=15000)
except Exception:
pass
r = pg.evaluate(SHOT_JS, {'seg': seg, 'cluster': cluster})
pg.screenshot(path=str(OUTDIR / f'{name}.png'))
caption = f"{cap} · {r['name']} · {r['shops']} shops / {r['edges']} edges" + (f" · retail heart: {r['hub']} shops/60m" if r.get('hub', -1) > 0 else '') + f" · {r['draws']} draws · {r['tris']:,} tris"
shots.append({'name': name, 'file': f'{name}.png', 'caption': caption})
print(f" \033[32m●\033[0m {name}: {caption}")
errs_total += len(errs)
if errs: print(f" ! {len(errs)} page error(s): {errs[0][:90]}")
pg.close()
b.close()
finally:
if srv: srv.terminate()
(OUTDIR / 'contact.html').write_text(contact_html(shots))
print(f"\n\033[1m{len(shots)} frames\033[0m → docs/shots/release_v4/ (+ contact.html) · {errs_total} page errors")
sys.exit(1 if errs_total or len(shots) < len(V4_TOUR) else 0)
if __name__ == '__main__':
main()