Lane F R4 finalize: gate table all-6-green + v1 tour shots + interior-shot fix
Gate table (LANE_F_NOTES §4) rewritten to R4 all-green with measured evidence:
1 determinism (selfcheck 1301/1301, fingerprint 0x3fa36874) · 2 soak (37 chunks/20 shops,
heap 38->38MB, 0 errors, leak-free) · 3 budget (draws 138/tris 31k, decimated peds) ·
4 asset-free (heap 13->13MB, 0 net) · 5 selfchecks (qa.sh --strict 4/4, 23 GLBs on depot) ·
6 shots (10 full-frame 1280x720, no letterbox, night closed-facade + real interiors).
shots.py: interior tour shots (record/opshop) now capture via DBG.enterShop(id) directly —
they were passing {type} to enterShop (needs id) then calling shot() (which forces street
mode), so both showed streets. Now they show the themed interiors with keeper + hero props.
Verified by a 6-auditor adversarial v1-readiness workflow -> GO.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ -4,20 +4,19 @@
|
||||
|
||||
---
|
||||
|
||||
## Round 4 (v1-tag round) — IN PROGRESS
|
||||
## Round 4 (v1-tag round) — DONE ✅ (all 6 gates green; v1.0 tagged)
|
||||
|
||||
Executing Fable's `ROUND4_INSTRUCTIONS.md` Lane F tasks. F1+F2 done & committed; F3/F4 gated on
|
||||
other lanes' critical-path work (E1 ped decimation, B2 shot-harness fix); F5 (tag) follows.
|
||||
Executed Fable's `ROUND4_INSTRUCTIONS.md` Lane F tasks. All lanes committed R4; a 6-auditor
|
||||
adversarial v1-readiness workflow returned **GO**; `v1.0` tagged locally on the F finalization commit.
|
||||
|
||||
| task | status |
|
||||
|---|---|
|
||||
| **F1 setExposure wire** | ✅ **DONE** ([`index.html:245`](web/index.html)) — `citizens.setExposure(renderer.toneMappingExposure)` after `setTimeOfDay`. Boots clean, 0 console errors, exposure animates night 1.18↔midday 0.98. Awaiting D3 verify. |
|
||||
| **F2 storeys-checker fix** | ✅ **DONE** — `permittedMax = max>=2 ? min(max+1,3) : max` (Lane A's rule). Consistency **0 warnings** across 6 seeds; single-storey-type-at-2 still trips. Awaiting A1 confirm. |
|
||||
| **F1+F2 commit** | ✅ `9416d0a` (only my 2 files, atomic), ref `lane-f/round4-wireups`. `.gitignore` (Lane E's `.peds_orig/`) left unstaged. |
|
||||
| **F3 gate-3 tris re-measure** | ✅ **PASS (OFFICIAL).** E1 committed decimated peds (`518678d`, 924k→46k tris, ≤3k each); **D2 signed off** (rigs bind/animate, no explosions, silhouettes read, fleet ≈59.6k at the 24-cap). Re-measured busiest node, pop 200, midday, 12-yaw peak, committed peds → **draws 138 ✅ (≤300) · tris ~31k ✅ (≤200k)**. Baseline was 372k. Gate 3 green. |
|
||||
| **F1 follow-up (D3)** | D3 found+fixed a real double-tone-map bug and stubbed `impostor.setExposure` → my F1 call is now a no-op. Will **remove the dead line at finalization, AFTER D commits** the impostor fix (removing before would regress night brightness in the committed tree). |
|
||||
| **F4 gate-6 shots** | 🟡 **PROVISIONALLY PASSING; awaiting B2 commit to finalize.** B2's `dbg.js` (letterbox `syncSize()` + the 3 new poses `crossroads_busy`/`residential_collar`/`warehouse_fringe`) is done on disk (uncommitted). Ran `shots.py v1_tour` → **10 full-frame 1280×720 shots (no letterbox)**, all poses resolve, night emissives + legible signs, decimated peds render human-scale. Re-capture + commit once B commits `dbg.js` (and B1's closed-facade night shot once B1 lands). |
|
||||
| **F5 tag v1.0** | ⏳ pending gates 3+6 finalized against **committed** upstream + D2 sign-off. qa.sh --strict GREEN 4/4 (static gates). Both blocking gates provisionally verified — tag is close. |
|
||||
| **F1 setExposure** | ✅ Wired (`9416d0a`) **then correctly removed** (`aad9baa`): D3 (`a5e4b64`) made the impostor `toneMapped:true`, so `citizens.setExposure()` became a no-op tone-mapped globally via `renderer.toneMappingExposure`. Removed only *after* D committed (removing earlier would regress night brightness). Final state: no dead call. |
|
||||
| **F2 storeys-checker** | ✅ `permittedMax = max>=2 ? min(max+1,3) : max` (Lane A's rule, `9416d0a`). Consistency **0 warnings** across 6 seeds; single-storey-type-at-2 still trips. **A confirmed** (`5b8c806`). |
|
||||
| **F3 gate-3 budget** | ✅ **PASS.** E1 decimated peds (`518678d`, 924k→46k, ≤3k each) + **D2 sign-off** (`a5e4b64`, fleet ≈59.6k at 24-cap). Official measure: busiest node, pop 200, midday, 12-yaw peak → **draws 138 (≤300) · tris ~31k (≤200k)**. Baseline 372k. |
|
||||
| **F4 gate-6 shots** | ✅ **PASS.** Against committed B2 (`6323d58`, `dbg.js` letterbox+poses): `shots.py v1_tour` → **10 full-frame 1280×720, no letterbox**; §3.5 **night closed-facade** (CLOSED plates + lit openLate video "VIDEO REGAL") + real **record/opshop interiors** (keeper + hero props). Fixed `shots.py` so interior shots capture via `enterShop(id)` (were falling back to a street bookmark). |
|
||||
| **F5 tag v1.0** | ✅ **TAGGED** on the F finalization commit (gate table all-green + shots committed). Local only — John pushes. |
|
||||
| **audit** | 6-auditor v1-readiness workflow: 5 GREEN (gates 1/3/5/6 + release hygiene) + 1 procedural "land finalization commit first" → resolved. GO. |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -214,28 +214,29 @@ Wired in the shell (all gated by `NOASSETS = ?noassets=1`):
|
||||
|
||||
---
|
||||
|
||||
## 4. QA gates (all green ⇒ v1). `tools/qa.sh --strict` is the umbrella.
|
||||
## 4. QA gates — ALL GREEN ✅ (v1.0 criterion met, R4). `tools/qa.sh --strict` is the static umbrella.
|
||||
|
||||
| # | gate | how | tool | state (R3) |
|
||||
|---|---|---|---|---|
|
||||
| 1 | **Determinism** | seed → identical plan (deep-equal, golden hash) + identical interior placement per seed | `scaffold_check.mjs` + Lane C soak page | ✅ plan/interior determinism green (map-PNG delta pending shot poses) |
|
||||
| 2 | **Soak** | scripted walk, ≥30 chunks, ≥15 shops; geo/tex back to baseline after each interior; JS heap stable; **0** console errors | `tools/soak.py` | ✅ **PASS** both modes (assets: 20 shops leak-free w/ GLB-cache warmup, heap 98MB stable; noassets: 37 chunks) |
|
||||
| 3 | **Budget** | busiest intersection: ≤300 draws, ≤200k tris (clean single-pass) | browser clean measure | ⚠ **draws PASS (~200)**; **tris FAIL (~279k)** with rig fleet — decimate note for D/E (§9) |
|
||||
| 4 | **Asset-free** | `?noassets=1` full playthrough, zero crashes, **zero network** | `soak.py --noassets` | ✅ **PASS** — 0 GLB/depot/manifest/ped requests, legible, no crash |
|
||||
| 5 | **Selfchecks** | `selfcheck.js` + `validate_manifest.py --depot` green | `tools/qa.sh` | ✅ **GREEN 4/4** (manifest now `--depot`: 16 GLBs live, 0 err) |
|
||||
| 6 | **Shots** | `docs/shots/v1_tour/` (10 beauty shots) + contact sheet | `tools/shots.py` | ◑ harness runs + 10 shots captured; **letterbox + 3 fallback bookmarks = Lane B dbg.js** (§9) |
|
||||
Measured against the fully-committed R4 tree (seed 20261990, "Boolarra Heads"), verified live +
|
||||
by a 6-auditor adversarial v1-readiness workflow (2026-07-14) → **GO**.
|
||||
|
||||
### DBG hook F requests from Lane B (for gates 1–4,6)
|
||||
The house shot/soak harnesses drive the game through a debug object. **Ask Lane B to expose**, on
|
||||
`window.DBG` (guarded, only when `?dbg=1`):
|
||||
- `DBG.ready` → true once first chunks built + assets settled (soak/shots wait on it).
|
||||
- `DBG.shot(name)` → snap camera to a named bookmark (`street_noon`, `arcade`, `market_square`,
|
||||
`milkbar_dusk`, `night_neon`, …); returns after the frame settles.
|
||||
- `DBG.teleport(x,z,ry)` and `DBG.setSegment(seg)` → drive the soak walk + time of day.
|
||||
- `DBG.enterShop(shopId)` / `DBG.exitShop()` → scripted interior visits (shot `record_interior`).
|
||||
- `DBG.info()` → `{drawCalls, tris, fps, heapMB, geometries, textures, chunk, mode}` (budget/soak read this).
|
||||
This mirrors 90sDJsim's `window.DBG.ST` / `window.DBG.shot()` (`90sDJsim/tools/shots.py:31-33`).
|
||||
Until it exists, `shots.py`/`soak.py` degrade to booting the default view and warn.
|
||||
| # | gate | criterion | result (R4) |
|
||||
|---|---|---|---|
|
||||
| 1 | **Determinism** | seed → identical plan (deep-equal + golden fingerprint) + selfcheck | ✅ scaffold_check green; selfcheck **1301/1301**, fingerprint **0x3fa36874** (intended A2 shift, not drift); two node processes → identical `sha256(plan)` per seed; chunkIndex covers every lot |
|
||||
| 2 | **Soak** | walk ≥30 chunks, ≥15 shops; geo/tex baseline after each interior; heap stable; **0** console errors | ✅ **37 chunks, 20 shops, heap 38→38 MB, 0 errors, leak-free** |
|
||||
| 3 | **Budget** | busiest intersection: ≤300 draws, ≤200k tris (settled clean measure) | ✅ **draws 138 · tris ~31k** (baseline 372k; peds decimated 924k→46k ≤3k each, D2 sign-off) |
|
||||
| 4 | **Asset-free** | `?noassets=1` full playthrough, zero crashes, zero network | ✅ **heap 13→13 MB, 0 errors, 0 asset requests** |
|
||||
| 5 | **Selfchecks** | `selfcheck.js` + `validate_manifest.py --depot` green via qa.sh | ✅ **qa.sh --strict 4/4**; manifest `--depot`: **23 GLBs live, 0 err** |
|
||||
| 6 | **Shots** | `docs/shots/v1_tour/` 10 beauty shots + contact sheet | ✅ **10 full-frame 1280×720, no letterbox**, 3 new poses resolve; incl. §3.5 **night closed-facade** (CLOSED plates + lit openLate video) + real **record/opshop interiors** (keeper + hero props) |
|
||||
|
||||
> Note: soak peak-draw/tri *warnings* are transient teleport/impostor-atlas-bake sampling spikes;
|
||||
> gate 3's settled busiest-intersection measure is the authoritative budget check (draws 138 / tris 31k).
|
||||
|
||||
### DBG hook — LANDED (Lane B `web/js/world/dbg.js`, `?dbg=1`)
|
||||
`window.DBG` exposes `ready / shot(name) / teleport(x,z,ry) / setSegment(seg) / enterShop(id) /
|
||||
exitShop() / info()` and drives `tools/shots.py` + `tools/soak.py` (mirrors 90sDJsim's `window.DBG`).
|
||||
Bookmarks: street_noon, arcade, market_square, milkbar_dusk, night_neon, crossroads_busy,
|
||||
residential_collar, warehouse_fringe. **Interior tour shots** (record/opshop) are captured by
|
||||
`enterShop(id)` directly (not via `shot()`, which forces street mode) — see `tools/shots.py`.
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 428 KiB After Width: | Height: | Size: 688 KiB |
|
Before Width: | Height: | Size: 485 KiB After Width: | Height: | Size: 788 KiB |
|
Before Width: | Height: | Size: 620 KiB After Width: | Height: | Size: 625 KiB |
|
Before Width: | Height: | Size: 708 KiB After Width: | Height: | Size: 700 KiB |
|
Before Width: | Height: | Size: 359 KiB After Width: | Height: | Size: 222 KiB |
|
Before Width: | Height: | Size: 485 KiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 485 KiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 485 KiB After Width: | Height: | Size: 696 KiB |
|
Before Width: | Height: | Size: 485 KiB After Width: | Height: | Size: 817 KiB |
|
Before Width: | Height: | Size: 485 KiB After Width: | Height: | Size: 534 KiB |
@ -105,14 +105,25 @@ def main():
|
||||
for name in TOUR:
|
||||
enter = ENTER.get(name)
|
||||
if enter:
|
||||
pg.evaluate("(o) => window.DBG.enterShop(o)", enter)
|
||||
pg.wait_for_timeout(600)
|
||||
pg.evaluate("(n) => window.DBG.shot(n)", name)
|
||||
pg.wait_for_timeout(900) # settle canvas textures, lights, async GLB swaps
|
||||
pg.screenshot(path=str(out / f'{name}.png'))
|
||||
if enter:
|
||||
# Interior shot: DBG.shot() forces street mode + street bookmarks, so it can't frame an
|
||||
# interior. Instead resolve an OPEN shop of the wanted type, enterShop(id) (which switches
|
||||
# to interior mode + poses the camera at the room spawn), and capture that view directly.
|
||||
pg.evaluate("""(t) => {
|
||||
const P = window.PROCITY, D = window.DBG;
|
||||
D.setSegment(2); // midday so the shop is open
|
||||
const shops = P.plan.shops || [];
|
||||
const shop = shops.find(s => s.type === t && (!P.isOpen || P.isOpen(s)))
|
||||
|| shops.find(s => s.type === t);
|
||||
if (shop) D.enterShop(shop.id);
|
||||
}""", enter['type'])
|
||||
pg.wait_for_timeout(900) # interior build + keeper/hero-prop GLBs settle
|
||||
pg.screenshot(path=str(out / f'{name}.png'))
|
||||
pg.evaluate("() => window.DBG.exitShop && window.DBG.exitShop()")
|
||||
pg.wait_for_timeout(300)
|
||||
else:
|
||||
pg.evaluate("(n) => window.DBG.shot(n)", name)
|
||||
pg.wait_for_timeout(900) # settle canvas textures, lights, async GLB swaps
|
||||
pg.screenshot(path=str(out / f'{name}.png'))
|
||||
print('shot', name)
|
||||
b.close()
|
||||
|
||||
|
||||