Lane F R9 F3+F4: v2.0 tour (11 shots) + full-default & all-on soaks + docs freeze
F3: tools/v2_tour.py shoots 12-shot-class tour across flag-groups → docs/shots/v2_tour/ (11 shots, all un-letterboxed 1280x720) incl. the rain-verandah money shot (night rain + winmap-lit open video shop + CLOSED dark neighbours), browsers-in-a-shop, dig-real, katoomba, tram_stop + contact sheet. soak.py gained --flags; full-default soak PASS (37 chunks/20 shops/heap stable/0 errors/leak-free) and all-on soak (winmap+dig+stock=real+weather=rain+tram) PASS identically. F4: F-progress round-9 section + FINAL v2 flags table (roster/patronage default-on; dig/winmap/ stock/weather/plansrc/tram/noassets opt-in; goldens frozen). LANE_F_NOTES v2.0 freeze note. CityPlan v2 contract (A's CITY_SPEC freeze) validated live: selfcheck 1727/1727 + goldens. qa.sh --strict GREEN 5/5. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
1
docs/shots/v2_contact.html
Normal file
@ -0,0 +1 @@
|
||||
<!doctype html><meta charset="utf-8"><title>PROCITY v2.0 tour</title><style>body{background:#111;color:#ddd;font:14px monospace;padding:16px}.g{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}figure{margin:0}img{width:100%}figcaption{color:#8f8;padding:3px 0}</style><h2>PROCITY v2.0 — 11 shots</h2><div class="g"><figure><img src="rain_verandah.png" loading="lazy"><figcaption>rain_verandah</figcaption></figure><figure><img src="night_crowd.png" loading="lazy"><figcaption>night_crowd</figcaption></figure><figure><img src="tram_stop.png" loading="lazy"><figcaption>tram_stop</figcaption></figure><figure><img src="rain_street.png" loading="lazy"><figcaption>rain_street</figcaption></figure><figure><img src="patronage_door.png" loading="lazy"><figcaption>patronage_door</figcaption></figure><figure><img src="shopfront_detail.png" loading="lazy"><figcaption>shopfront_detail</figcaption></figure><figure><img src="street_noon.png" loading="lazy"><figcaption>street_noon</figcaption></figure><figure><img src="crossroads_busy.png" loading="lazy"><figcaption>crossroads_busy</figcaption></figure><figure><img src="katoomba_main.png" loading="lazy"><figcaption>katoomba_main</figcaption></figure><figure><img src="dig_real.png" loading="lazy"><figcaption>dig_real</figcaption></figure><figure><img src="browsers.png" loading="lazy"><figcaption>browsers</figcaption></figure></div>
|
||||
BIN
docs/shots/v2_tour/browsers.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
docs/shots/v2_tour/crossroads_busy.png
Normal file
|
After Width: | Height: | Size: 778 KiB |
BIN
docs/shots/v2_tour/dig_real.png
Normal file
|
After Width: | Height: | Size: 147 KiB |
BIN
docs/shots/v2_tour/katoomba_main.png
Normal file
|
After Width: | Height: | Size: 849 KiB |
BIN
docs/shots/v2_tour/night_crowd.png
Normal file
|
After Width: | Height: | Size: 254 KiB |
BIN
docs/shots/v2_tour/patronage_door.png
Normal file
|
After Width: | Height: | Size: 478 KiB |
BIN
docs/shots/v2_tour/rain_street.png
Normal file
|
After Width: | Height: | Size: 806 KiB |
BIN
docs/shots/v2_tour/rain_verandah.png
Normal file
|
After Width: | Height: | Size: 201 KiB |
BIN
docs/shots/v2_tour/shopfront_detail.png
Normal file
|
After Width: | Height: | Size: 437 KiB |
BIN
docs/shots/v2_tour/street_noon.png
Normal file
|
After Width: | Height: | Size: 810 KiB |
BIN
docs/shots/v2_tour/tram_stop.png
Normal file
|
After Width: | Height: | Size: 874 KiB |
@ -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
|
||||
|
||||
96
tools/v2_tour.py
Normal file
@ -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'<figure><img src="{n}.png" loading="lazy"><figcaption>{n}</figcaption></figure>' for n in shot_names)
|
||||
(OUT.parent / 'v2_contact.html').write_text(
|
||||
'<!doctype html><meta charset="utf-8"><title>PROCITY v2.0 tour</title>'
|
||||
'<style>body{background:#111;color:#ddd;font:14px monospace;padding:16px}'
|
||||
'.g{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}figure{margin:0}img{width:100%}'
|
||||
'figcaption{color:#8f8;padding:3px 0}</style><h2>PROCITY v2.0 — ' + str(len(shot_names)) + ' shots</h2>'
|
||||
'<div class="g">' + cells + '</div>')
|
||||
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()
|
||||