diff --git a/F-progress.md b/F-progress.md index 7b46e85..d98eb55 100644 --- a/F-progress.md +++ b/F-progress.md @@ -4,6 +4,37 @@ --- +## Round 9 (the v2.0 ship round) — DONE ✅ (v2.0 tagged) + +All lanes landed (C browse points + buy-anywhere, D browser rigs, A CITY_SPEC freeze + closing-time +ruling, E VRAM audit + half-res book/toy atlases, B tram + tour bookmarks). F wired the one cross-lane +seam, graduated the harness, shot the tour, soaked, and tagged the epoch. + +| task | status | +|---|---| +| **F1 — interior presence (C→D→F)** | ✅ `interior_mode` reads `occupancyOf(shop.id)` (closure to `citizens`) and spawns browser rigs at C's `room.browsePoints`, one per patronage occupant, **cap 3**; freed by `exit()`'s `keepers.disposeAll`. Door points carry `shopId`. Verified: enter occupied "Second Time Redfern" → 1 browser == `min(occ,3)`, disposed clean, 0 errors. | +| **also wired** | ✅ **Tram** (`?tram=1`, `createTram`+update). **Buy-anywhere** (C2): E at a book-spine/toy-box `buyMesh` buys over the wallet (`collapseBuyItem`+toast); preload book+toy packs. Verified: bought a book spine, cash 191→177. | +| **F2 — smokes to strict** | ✅ buy-v0 / patronage / book-toy / katoomba **graduated warn→STRICT**. New warn smokes: interior-presence, shelf-buy, tram. Fixed the book-stock check to count `isStock OR buyMesh` (C2 tags spines `buyMesh`). `qa.sh --strict` GREEN 5/5, harness 0 fails / 0 warns. | +| **F3 — tour + soaks** | ✅ `tools/v2_tour.py` → **11 shots** (`docs/shots/v2_tour/`, un-letterboxed) incl. the **rain-verandah money shot** (rain + winmap-lit open video shop + CLOSED dark neighbours at night) + browsers + dig-real + katoomba + tram_stop + contact sheet. **Both soaks PASS** (full-default + all-on: 37 chunks, 20 shops, heap stable, 0 errors, leak-free). | +| **F4 — docs freeze + tag v2.0** | ✅ Flags table finalized (below); **`v2.0` tagged** on a green tree. | +| **Marshal** | C1 (the round's SPOF) landed early; all lanes committed; no slips. | + +### v2.0 flags — FINAL +| flag | owner | status | default | +|---|---|---|---| +| roster | D | **graduated → DEFAULT-ON** (R7 flip); `?roster=v1` escape hatch | on | +| patronage | D | default-on (`?patronage=0` off) | on | +| `?dig=1` | C | opt-in (crate riffle) | off | +| `?winmap=1` | B | opt-in (parallax window glass) | off | +| `?stock=real` | C/E | opt-in (real GODVERSE covers/spines/boxes + buy-anywhere) | off | +| `?weather=1` | B | opt-in (seeded rain/overcast/clear; `?weather=rain` forces) | off | +| `?plansrc=osm[&town=]` | A | opt-in (real-OSM towns: melbourne, katoomba) | off (synthetic) | +| `?tram=1` | B | opt-in (spine tram loop) | off | +| `?noassets=1` | — | opt-in (primitive-only, zero-network) | off | +Determinism goldens frozen: synthetic `0x3fa36874` · osm/melbourne `0x34cfdec0` · osm/katoomba `0x0f652510`. + +--- + ## Round 8 (catch-up + the town comes alive → v2.0-rc) — DONE ✅ All lanes landed (E packs, B weather, A 2nd town, C buy, D patronage). F wired the shell seams, diff --git a/docs/LANES/LANE_F_NOTES.md b/docs/LANES/LANE_F_NOTES.md index e54190f..7d2595f 100644 --- a/docs/LANES/LANE_F_NOTES.md +++ b/docs/LANES/LANE_F_NOTES.md @@ -9,6 +9,12 @@ Status of this file: **living**. Update the ✅/⏳ markers as wiring lands. Sna readiness lives in [`F-progress.md`](../../F-progress.md); run `node tools/qa/scaffold_check.mjs` for the live matrix. +> **v2.0 FROZEN (round 9).** Nine rounds: seed → walkable town → weather, a shopping crowd, +> follow-a-ped-into-a-shop presence, and real stock you can buy. Current gate reality + the +> final v2 flags table live in [`F-progress.md`](../../F-progress.md); `qa.sh --strict` GREEN 5/5 +> (scaffold+determinism · consistency · citygen selfcheck · manifest --depot · v2 flags harness STRICT). +> CityPlan v2 contract frozen by Lane A in CITY_SPEC; validated live by selfcheck (1727/1727) + goldens. + --- ## 0. Ground truth / environment diff --git a/docs/shots/v2_contact.html b/docs/shots/v2_contact.html new file mode 100644 index 0000000..d638d7d --- /dev/null +++ b/docs/shots/v2_contact.html @@ -0,0 +1 @@ +PROCITY v2.0 tour

PROCITY v2.0 — 11 shots

rain_verandah
night_crowd
tram_stop
rain_street
patronage_door
shopfront_detail
street_noon
crossroads_busy
katoomba_main
dig_real
browsers
\ No newline at end of file diff --git a/docs/shots/v2_tour/browsers.png b/docs/shots/v2_tour/browsers.png new file mode 100644 index 0000000..68f8ac6 Binary files /dev/null and b/docs/shots/v2_tour/browsers.png differ diff --git a/docs/shots/v2_tour/crossroads_busy.png b/docs/shots/v2_tour/crossroads_busy.png new file mode 100644 index 0000000..43cb8c3 Binary files /dev/null and b/docs/shots/v2_tour/crossroads_busy.png differ diff --git a/docs/shots/v2_tour/dig_real.png b/docs/shots/v2_tour/dig_real.png new file mode 100644 index 0000000..019a434 Binary files /dev/null and b/docs/shots/v2_tour/dig_real.png differ diff --git a/docs/shots/v2_tour/katoomba_main.png b/docs/shots/v2_tour/katoomba_main.png new file mode 100644 index 0000000..c67a90c Binary files /dev/null and b/docs/shots/v2_tour/katoomba_main.png differ diff --git a/docs/shots/v2_tour/night_crowd.png b/docs/shots/v2_tour/night_crowd.png new file mode 100644 index 0000000..eed74cf Binary files /dev/null and b/docs/shots/v2_tour/night_crowd.png differ diff --git a/docs/shots/v2_tour/patronage_door.png b/docs/shots/v2_tour/patronage_door.png new file mode 100644 index 0000000..160a910 Binary files /dev/null and b/docs/shots/v2_tour/patronage_door.png differ diff --git a/docs/shots/v2_tour/rain_street.png b/docs/shots/v2_tour/rain_street.png new file mode 100644 index 0000000..17c092f Binary files /dev/null and b/docs/shots/v2_tour/rain_street.png differ diff --git a/docs/shots/v2_tour/rain_verandah.png b/docs/shots/v2_tour/rain_verandah.png new file mode 100644 index 0000000..614e904 Binary files /dev/null and b/docs/shots/v2_tour/rain_verandah.png differ diff --git a/docs/shots/v2_tour/shopfront_detail.png b/docs/shots/v2_tour/shopfront_detail.png new file mode 100644 index 0000000..d5829ff Binary files /dev/null and b/docs/shots/v2_tour/shopfront_detail.png differ diff --git a/docs/shots/v2_tour/street_noon.png b/docs/shots/v2_tour/street_noon.png new file mode 100644 index 0000000..e7bd285 Binary files /dev/null and b/docs/shots/v2_tour/street_noon.png differ diff --git a/docs/shots/v2_tour/tram_stop.png b/docs/shots/v2_tour/tram_stop.png new file mode 100644 index 0000000..93b83c4 Binary files /dev/null and b/docs/shots/v2_tour/tram_stop.png differ diff --git a/tools/soak.py b/tools/soak.py index 8399406..c91b8b3 100644 --- a/tools/soak.py +++ b/tools/soak.py @@ -42,7 +42,9 @@ def main(): seed = flag('--seed') minutes = float(flag('--minutes', 10)) noassets = '--noassets' in sys.argv - qs = '?dbg=1' + (f'&seed={seed}' if seed else '') + ('&noassets=1' if noassets else '') + extra = flag('--flags') # arbitrary query flags, e.g. --flags 'winmap=1&dig=1&stock=real&weather=rain&tram=1' + qs = '?dbg=1' + (f'&seed={seed}' if seed else '') + ('&noassets=1' if noassets else '') \ + + (f'&{extra}' if isinstance(extra, str) else '') url = HOST + '/' + qs fails, warns, chunks_seen, shops_entered, peak_draws, peak_tris = [], [], set(), 0, 0, 0 diff --git a/tools/v2_tour.py b/tools/v2_tour.py new file mode 100644 index 0000000..8d898c6 --- /dev/null +++ b/tools/v2_tour.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 +"""PROCITY Lane F — v2.0 tour: 12 beauty shots across flag-groups → docs/shots/v2_tour/ + contact sheet. +Boots each flag-combo once, shoots its bookmarks (Lane B dbg.js poses) + interior shots. Un-letterboxed +1280x720 (dbg.js syncSize). Run: cd web && python3 -m http.server 8130 then tools/.venv/bin/python tools/v2_tour.py +""" +import sys, pathlib +from playwright.sync_api import sync_playwright + +ROOT = pathlib.Path(__file__).resolve().parent.parent +OUT = ROOT / 'docs' / 'shots' / 'v2_tour' +OUT.mkdir(parents=True, exist_ok=True) +HOST = 'http://127.0.0.1:8130' + +# flag-combo -> [street bookmarks] (each dbg.js bookmark sets its own pose + day segment) +GROUPS = [ + ('winmap=1&weather=rain&tram=1&stock=real&pop=200', + ['rain_verandah', 'night_crowd', 'tram_stop', 'rain_street', 'patronage_door', 'shopfront_detail', + 'street_noon', 'crossroads_busy']), + ('plansrc=osm&town=katoomba&pop=180', ['katoomba_main']), +] +# interior shots: flags, then enter + optional dig +INTERIORS = [ + ('dig_real', 'dig=1&stock=real', 'record', True), # enter record shop + open the dig (real covers) + ('browsers', 'stock=real&pop=200', 'occupied', False), # enter an occupied shop (browsers present) +] + +def boot(pg, q): + pg.goto(f'{HOST}/?dbg=1&{q}') + 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 main(): + shot_names, errs = [], [] + with sync_playwright() as p: + b = p.chromium.launch() + pg = b.new_page(viewport={'width': 1280, 'height': 720}) + pg.on('console', lambda m: errs.append(m.text) if m.type == 'error' else None) + for flags, marks in GROUPS: + boot(pg, flags) + for name in marks: + try: + pg.evaluate("(n) => window.DBG.shot(n)", name) + pg.wait_for_timeout(1100) + pg.evaluate("(n) => window.DBG.shot(n)", name) # settle bloom/segment + pg.wait_for_timeout(300) + pg.screenshot(path=str(OUT / f'{name}.png')) + shot_names.append(name); print('shot', name) + except Exception as e: + print('MISS', name, str(e)[:80]) + for name, flags, kind, do_dig in INTERIORS: + boot(pg, flags) + ok = pg.evaluate("""(kind) => { + const P=window.PROCITY, D=window.DBG; D.setSegment(2); + let s=null; + if(kind==='occupied'){ + 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); + for(const sh of plan.shops){ const o=P.citizens.occupancyOf(sh.id); if(o&&o.count>0){s=sh;break;} } + if(!s) s=plan.shops.find(x=>x.type==='opshop'&&P.isOpen(x)); + } else { s=(P.plan.shops||[]).find(x=>x.type===kind && P.isOpen(x)); } + if(!s) return false; D.enterShop(s.id); return true; + }""", kind) + if not ok: + print('MISS', name, '(no shop)'); continue + if do_dig: + # aim at a bin + open the dig for the real-cover shot + pg.evaluate("""() => { const P=window.PROCITY; const bins=[]; + P.interiorMode.current.group.traverse(o=>{if(o.userData&&o.userData.kind==='bin')bins.push(o)}); + if(bins.length){ 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(); + window.dispatchEvent(new KeyboardEvent('keydown',{code:'KeyE'})); } }""") + pg.wait_for_timeout(900) + pg.screenshot(path=str(OUT / f'{name}.png')) + shot_names.append(name); print('shot', name) + b.close() + + # contact sheet + cells = ''.join(f'
{n}
' for n in shot_names) + (OUT.parent / 'v2_contact.html').write_text( + 'PROCITY v2.0 tour' + '

PROCITY v2.0 — ' + str(len(shot_names)) + ' shots

' + '
' + cells + '
') + print(f'\n{len(shot_names)} shots → docs/shots/v2_tour/ · contact: docs/shots/v2_contact.html') + if errs: + print(f'⚠ {len(errs)} console error(s); first: {errs[0][:120]}') + +if __name__ == '__main__': + main()