PROCITY/tools/qa/tour_v5.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

201 lines
11 KiB
Python

#!/usr/bin/env python3
"""PROCITY v5.0 RELEASE TOUR — THE LIVE CRATE (Lane F, R27 ledger #5)
The crate walk, in the order you'd actually do it: Musgrave Road → Monster Robot Party's frontage (B's
stocked-shopfront mark) → inside, riffling ITS OWN records → a mint shop, captioned honestly → the
`?classic=1` control that still boots the exact v2 town.
Posing: the street frames go through F's R21 cluster primitive (reused from tour_shots.py). Per-EDGE shop
counts are ~1 on a real graph, so "the busiest edge" poses you at bare bitumen — the Fitzroy trap, twice
learned. The interior frames pose themselves: the room builds you a metre inside its own door.
Honesty rules baked in, not bolted on:
· the mint frame says MINT in its caption. A minted crate is plausible, not real, and a tour is exactly
where that distinction gets quietly lost.
· the real frame's caption carries the shop's actual name and address, because it IS one.
· nothing here boots ?live=1. A tour frame must be reproducible by anyone who clones this repo, and
tier 2 needs a POS none of them have. The live tier is proven by the gate, which owns its own server;
the tour shows tier 1, which is what ships in the box.
"""
import json, subprocess, sys, time, pathlib
ROOT = pathlib.Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT / 'tools' / 'qa'))
PORT = 8131
HOST = f'http://127.0.0.1:{PORT}'
SEED = 20261990
OUTDIR = ROOT / 'docs' / 'shots' / 'release_v5'
SHOP = 3962749
# name, query, segment, mode, caption
V5_TOUR = [
('musgrave_road', f'plansrc=osm&town=redhill_godverse&stock=real&seed={SEED}', 3, f'frontage:{SHOP}',
"the frontage — Monster Robot Party on Musgrave Road, Red Hill, Brisbane. It is on this road in the "
"game because it is on this road"),
('the_real_crate', f'plansrc=osm&town=redhill_godverse&stock=real&dig=1&seed={SEED}', 3, f'shop:{SHOP}',
"THE REAL CRATE — Monster Robot Party (147 Musgrave Rd): 120 records off its own till, real covers, "
"real titles, real prices. Sell one in the shop and it leaves this crate"),
('a_mint_crate', f'plansrc=osm&town=redhill_godverse&stock=real&dig=1&seed={SEED}', 3, 'mint',
"a MINT crate — plausible, NOT real: a seeded sample standing in for a shop whose till we don't hold. "
"Same tier, same offline guarantee, different provenance — and the atlas says so in its own index"),
('classic_covenant', f'classic=1&seed={SEED}', 2, 'street',
"?classic=1 — the exact v2 town, byte-identical forever (0x5f76e76). Five epochs, one covenant"),
]
SHOT_JS = r"""
async ({ mode, seg, shop }) => {
const P = window.PROCITY, D = window.DBG, T = P.THREE;
D.setSegment(seg);
const plan = P.plan, shops = plan.shops || [];
if (mode.startsWith('shop:') || mode === 'mint') {
let target;
if (mode === 'mint') {
const man = await (await fetch('assets/stock_godverse/index.json')).json();
const mint = new Set((man.shops||[]).filter(s => s.sourcing === 'mint').map(s => s.godverseShopId));
const byId = new Map((man.shops||[]).map(s => [s.godverseShopId, s]));
target = shops.find(s => mint.has(s.godverseShopId) &&
(byId.get(s.godverseShopId).types||[]).includes(s.type));
} else {
target = shops.find(s => s.godverseShopId === shop);
}
if (!target) return { err: 'no target shop for ' + mode };
D.enterShop('g:' + target.godverseShopId);
await new Promise(r => setTimeout(r, 1500));
const si = D.stockInfo();
// pose: stand back from the spawn looking into the room, so the crates are in frame
const im = P.interiorMode, sp = im.current.spawn;
P.camera.position.set(sp.x, 1.6, sp.z + 0.2);
P.camera.rotation.set(0, sp.ry || 0, 0);
P.camera.updateMatrixWorld();
// Beauty shot: hide EVERY overlay, not just the street HUD. R21 taught this on the street frames
// and F only fixed it there — the first v5 interior frame came back with "press Esc to leave"
// across the top of the money shot. Same lesson, second surface.
// Hide EVERY DOM overlay rather than guessing ids — the interior banner is created at runtime, so a
// hardcoded selector list is exactly how the HUD sneaks back into the next money shot.
for (const el of document.body.children) if (el.tagName !== 'CANVAS') el.style.display = 'none';
for (let i = 0; i < 3; i++) await new Promise(r => requestAnimationFrame(r));
const info = D.info();
return { name: si.name, gid: si.godverseShopId, items: si.packItems, base: si.base,
shops: shops.length, edges: (plan.streets.edges||[]).length,
draws: info.drawCalls, tris: info.tris };
}
// ── street ──
// [R27w5] The frontage frame now goes through B's `poseForShopFront` — B's helper, B's geometry, B's
// shop. F re-derived this three times and produced three photographs of empty road; the fourth attempt
// would have been the same mistake with more arithmetic. The lane that owns the frontage owns the pose.
// (And the empty road was never the pose at all — the shops faced backwards. Fixed by A, wave 5.)
const nById = new Map(plan.streets.nodes.map(n => [n.id, n]));
const lots = (plan.lots || []).filter(l => l.use === 'shop' || l.use === 'anchor');
let hub = -1, best = null;
if (mode.startsWith('frontage:')) {
const r = D.poseForShopFront('g:' + mode.split(':')[1], 8);
if (!r || r.error) return { err: 'poseForShopFront: ' + JSON.stringify(r) };
// poseForShopFront RETURNS a pose; it does not apply one. F called it, error-checked it, and threw
// the result away — so the camera never moved and the frame was the spawn paddock. A helper that
// hands you a value is not a helper that does the thing.
P.camera.position.set(r.pos[0], r.pos[1], r.pos[2]);
P.camera.lookAt(r.look[0], r.look[1], r.look[2]);
P.camera.updateMatrixWorld();
const sh = shops.find(x => x.godverseShopId === +mode.split(':')[1]);
const lot = sh ? (plan.lots || []).find(l => l.id === sh.lot) : null;
hub = lot ? lots.filter(o => Math.hypot(o.x - lot.x, o.z - lot.z) < 60).length : -1;
} else {
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));
for (const el of document.body.children) if (el.tagName !== 'CANVAS') el.style.display = 'none';
for (let i = 0; i < 3; i++) await new Promise(r => requestAnimationFrame(r));
const info = D.info();
return { name: plan.name, shops: shops.length, edges: (plan.streets.edges || []).length,
hub, draws: info.drawCalls, tris: info.tris };
}
"""
def ensure_server():
import socket
s = socket.socket()
try:
s.connect(('127.0.0.1', PORT)); s.close(); return None
except Exception:
pass
srv = subprocess.Popen([sys.executable, '-m', 'http.server', str(PORT)],
cwd=str(ROOT / 'web'), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
time.sleep(1.2)
return srv
def main():
try:
from playwright.sync_api import sync_playwright
except ImportError:
sys.exit('playwright not installed')
print(f"\033[1mv5.0 RELEASE TOUR — THE LIVE CRATE\033[0m ({len(V5_TOUR)} frames → docs/shots/release_v5/)")
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, mode, cap in V5_TOUR:
pg = b.new_page(viewport={'width': 1280, 'height': 720})
errs = []
pg.on('pageerror', lambda e: errs.append(str(e)))
pg.on('console', lambda m: errs.append(m.text) if m.type == 'error' else None)
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, {'mode': mode, 'seg': seg, 'shop': SHOP})
if r.get('err'):
print(f" \033[31m✗\033[0m {name}: {r['err']}"); errs_total += 1; pg.close(); continue
pg.screenshot(path=str(OUTDIR / f'{name}.png'))
extra = (f" · {r['items']} records from {r['base']}" if r.get('items')
else (f" · retail heart: {r['hub']} shops/60m" if r.get('hub') else ''))
caption = (f"{cap} · {r['name']}{extra} · {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" \033[31m{len(errs)} page error(s)\033[0m: {errs[0][:110]}")
pg.close()
b.close()
finally:
if srv:
srv.terminate()
html = ["<!doctype html><meta charset=utf-8><title>PROCITY v5.0 — THE LIVE CRATE</title>",
"<style>body{background:#111;color:#ddd;font:15px/1.6 -apple-system,sans-serif;max-width:1100px;"
"margin:40px auto;padding:0 20px}img{width:100%;border-radius:6px;margin:10px 0}"
"h1{font-weight:600}p{color:#aaa}hr{border:0;border-top:1px solid #333;margin:36px 0}</style>",
"<h1>PROCITY v5.0 — THE LIVE CRATE</h1>",
"<p>One real record shop, on its real street, holding its real stock — and a server that can die "
"mid-session without the game noticing.</p>"]
for s in shots:
html.append(f"<hr><h3>{s['name']}</h3><img src='{s['file']}'><p>{s['caption']}</p>")
(OUTDIR / 'contact.html').write_text('\n'.join(html))
(OUTDIR / 'shots.json').write_text(json.dumps(shots, indent=1))
print(f"\n{OUTDIR}/contact.html ({len(shots)} frames, {errs_total} page error(s))")
return 0 if (len(shots) == len(V5_TOUR) and errs_total == 0) else 1
if __name__ == '__main__':
sys.exit(main())