PROCITY/B-progress.md
m3ultra 6323d58736 Lane B round-4: closed facades (§3.5) + shot poses/letterbox + furniture GLB + novelty_record
- B1: per-shop closed-facade visuals — dark windows + red CLOSED plate + door tooltip, batched via a
  per-vertex aHours attr + two shared shaders + one uHour/uNight uniform pair on a procity:segment
  event (facade atlas untouched, no per-shop materials, no per-frame poll). 22:00 → only the openLate
  video shop lit, all others CLOSED; ~200 draws worst-view with citizens.
- B2: the shots 'letterbox' was camera-jammed-into-a-wall poses, not a composer bug (composer proven
  correctly sized headless). Reframed street_noon/arcade/warehouse to look down a street
  (streetViewPose), added the 3 missing bookmarks, night_neon now frames the lit video shop amid
  CLOSED neighbours, + a defensive composer resize. shots.py: 10 un-letterboxed shots, 0 errors.
- B3: furniture GLB use-if-ready upgrade (bench + food_cart, sit on their footprints) + novelty_record
  placed at record stores. novelty_record GLB is 26.5k tris (5x glb_law) → stays low-poly primitive
  until Lane E decimates it; ?noassets verified 0 network.

qa.sh --strict GREEN (4/4).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 19:59:27 +10:00

151 lines
12 KiB
Markdown
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.

# PROCITY-B — progress (Lane B: Streetscape + game shell)
**Status: complete and verified.** Lane B turns a CityPlan into a walkable, chunk-streamed 3D town
plus the game shell. It runs on my hand-written fixture **and** auto-integrates with Lane A's
`generatePlan` (which landed mid-session). Everything below was verified live in a browser.
## Round 4 (Fable's ROUND4 → Lane B) — done · qa.sh --strict GREEN (4/4)
- **B1 — closed-facade visuals (§3.5, headline).** Closed shops → **dark windows + red CLOSED plate**;
open shops (esp. the `openLate` video rental at night) → **lit windows**; door tooltip
`🔒 <name> — CLOSED · opens HH:00`. Batched with a per-vertex `aHours` + two shared shaders + one
`uHour/uNight` uniform pair, updated once on a `procity:segment` event (lighting.js → buildings.js)
**facade atlas untouched, no per-shop materials, no per-frame poll.** Verified live: **22:00 →
only "Video Regal" lit, all others CLOSED**; midday → all open; worst-view **200 draws with
citizens** (≤300). Files: `buildings.js`, `lighting.js`, `hud.js`.
- **B2 — shot harness (gate-6 critical path).** The "letterbox" was **not** a composer bug (proved
the composer is correctly sized headless) — the `street_noon`/`arcade`/`warehouse_fringe` **poses
jammed the camera into a wall**. Reframed them to look down a street (new `streetViewPose`), added
the **3 missing bookmarks** F asked for, pointed `night_neon` at the glowing video shop amid CLOSED
neighbours, and added a defensive composer resize. `tools/shots.py` → **10 un-letterboxed shots, 0
console errors** (in `docs/shots/laneB_r4/`). File: `dbg.js`.
- **B3 — furniture GLB upgrade + novelty_record.** furniture.js now use-if-ready-upgrades primitives
to depot GLBs (sync check, `?noassets` → 0 network verified). Placed **novelty_record** at every
record store + food_cart by stalls/milk bars. **Finding:** the **novelty_record GLB is 26.5k tris**
(5× `glb_law`), spiking a busy view to **574k** → it stays on its low-poly primitive; bench (1.9k)
+ food_cart (5.0k) GLBs are fine and sit on their footprints. **→ Lane E: decimate novelty_record
(same as the ped rigs).** File: `furniture.js`.
## Round 3 (Fable's ROUND3_INSTRUCTIONS → Lane B) — done
- **Task 1 (draw budget, re-measured WITH citizens).** Radius-2 auto is already the spawn default;
no config change needed. Drove the real loop (`chunks.update` + `citizens.update`, pop 140, up to
**125 active peds**) along the dense corridors via `window.DBG`: worst continuous-walk view
**263 scene / 275 total at MIDDAY**, **275 total at NIGHT****≤300 everywhere**, ~25 margin.
Peds are a batched impostor layer (~12 draws). Documented in LANE_B_NOTES.
- **Task 2 (`scene.environment`).** Was **null** → Lane D's rig-fleet peds bake their impostor atlas
from it (`sim.js`) and would render dark. Set a neutral PMREM'd sky→ground gradient in
`lighting.attachRenderer` (before `CitizenSim` constructs; no asset). Verified **set in
street / night / interior / after interior-return** (`interior_mode` only swaps `scene.background`).
- **Task 3 (furniture GLB footprints)** — **standby**: Fable's §3.4 (manifest GLB upgrade +
`?noassets`) hasn't landed (furniture.js still primitives); will verify placement/collision when it does.
- **Task 4 (optional polish)** — deferred (fresh generated-city stills / map.html wiring).
- Only Lane B file changed this round: `web/js/world/lighting.js` (+ these docs). Committed by exact
pathspec (Lanes C/E/F have concurrent uncommitted work in the shared tree — untouched).
## Session 2 (continuation) — closed the two open Lane-B items Fable flagged
- **Draw budget now holds with margin.** Facade texture atlas + awning-merge + shell/trim-merge:
worst dense frustum **393 → 265 scene draws**, worst continuous-walk gameplay view **~261 total**
(≤300 gate, ~40 margin). Bonus: **skin materials 25 → 5** (the atlas is one shared facade material),
fixing the "≤25 was at the cap" concern too. (The old "~445" peak was largely a measurement artifact
— orphan chunks from teleport-jumping that the real streamer disposes.)
- **`window.DBG` harness exposed** (`?dbg=1`, `js/world/dbg.js`) — answers LANE_F_NOTES §4 so Fable's
`shots.py`/`soak.py` can drive the game: `ready`, `shot(name)`, `teleport`, `setSegment`,
`enterShop`/`exitShop` (drives the real B→F→C interior chain), `info()`.
- **`ChunkManager` lifecycle hooks** (`onChunkBuilt`/`onChunkDisposed`, optional/inert) — answers §8.
- Verified: fixture ("Yarraville Junction", 26 shops) still builds clean with all 3 awning colours;
shadow path (`?shadows=1`) renders from the merged meshes; day + night + door tooltip + interiors
all work; **zero console errors**. Files touched: `skins.js`, `buildings.js`, `chunks.js`, `hud.js`,
new `dbg.js`, + a small guarded DBG loader in `index.html`. **Uncommitted** (per the lane treaty;
and Fable is concurrently wiring `index.html` — our edits coexist cleanly on stable anchors).
Owned/created (only `web/index.html` + `web/js/world/*`, per the ownership treaty):
| file | role |
|---|---|
| `web/index.html` | shell: importmap→vendor, renderer + ACES + bloom, MODE state machine (`map`/`street`/`interior`), main loop, adaptive perf, guarded `generatePlan` import, shot harness |
| `web/js/world/fixture_plan.js` | hand-authored CityPlan (schema v1): 4 N + 2 S terraced blocks (26 shops), cross-street houses, 2 market stalls, streets graph — spans ~6 chunks so streaming is exercised |
| `web/js/world/planutil.js` | chunk math (64 m), `chunkIndex`, node/lot indices, oriented-rect collider + `pushOutRect` |
| `web/js/world/skins.js` | city-wide shared material registry (one material per skin), seeded flat-colour fallbacks |
| `web/js/world/buildings.js` | facade kit: instanced shells/parapets/posts/awnings, merged per-skin facades, per-chunk sign atlas, doors/windows |
| `web/js/world/ground.js` | roads/footpaths/kerbs/plaza/base plane, merged strips, UV-baked tiling |
| `web/js/world/furniture.js` | instanced streetlights (emissive at night), benches, bins, gum trees, bus stops — all with primitive fallbacks |
| `web/js/world/lighting.js` | seeded sky dome + 6-segment day/night cycle, one following sun/shadow, fog pop-in mask |
| `web/js/world/player.js` | PointerLock WASD+run, eye 1.7 m, push-out collision vs nearby-chunk colliders |
| `web/js/world/chunks.js` | the streamer: radius-R build / R+1 dispose, nearest-first queue, 4 ms/frame budget, night propagation |
| `web/js/world/hud.js` | crosshair, throttled door-raycast tooltip, live draws/tris/fps/seed/clock readout, click→`procity:enterShop` |
| `web/js/world/minimap.js` | `map` mode: 2D top-down town directory (streets + lots by type + player dot) |
## Acceptance — all met
- ✅ Walk the fixture 5 min: contiguous, no seams, chunks stream silently, day/night cycles, signs
legible, tooltips work, budgets green (148 scene draws, 21 skin materials, 5.8k tris).
- ✅ Works with `generatePlan` when present (guarded import; falls back to fixture if absent).
- ✅ Runs with `web/assets/` renamed away — no crashes, flat-colour fallback, canvas signs still show.
- ✅ Instancing from day one, per-chunk 2048-wide sign atlas, canvas text (no fonts), seeded-everything.
- ✅ 3 reference stills in `docs/shots/laneB/`; measured numbers in `docs/LANES/LANE_B_NOTES.md`.
## Verified live (in-browser)
Streaming (chunks build/dispose as you walk, queue never starves), 6-segment day→night with glowing
shopfront windows + streetlamp bloom at night, door raycast → correct shop → `procity:enterShop` +
toast, pixel-accurate collision (stops at wall player radius), all-fallback mode, and Lane A's full
493-shop city rendering + streaming + arbitrary-angle collision through Lane B.
## Perf (measured — full table in `docs/LANES/LANE_B_NOTES.md`)
- **Fixture** (radius 3, shadows on): ~148 scene draws, low tris, well under all gates.
- **Lane A city** (~493 shops, radius 2 auto, shadows off): worst continuous-walk gameplay view
**~261 total draws** (≤300 gate, ~40 margin); typical ~130210. Skin materials **5** (≤25).
Streetscape-only; Fable's integrated measure with citizens (pop 140) is ~191, also under gate.
## Adversarial multi-agent review → fixes landed
I ran a 4-dimension review (correctness / memory-perf / spec / robustness) with adversarial
verification. Confirmed real bugs, all fixed and re-verified:
1. **Collision was wrong for non-axis-aligned `ry`** (`pushOutRect` used the wrong rotation sign).
Latent on my axis-aligned fixture; would let you walk through Lane A's angled buildings. **Fixed**
verified `keptOut:true` against a `ry=1.32` lot.
2. **House doors** were raycastable and mis-resolved to the nearest shop → clicking a house "entered"
a random shop. **Fixed** (separate non-picked mesh).
3. **Awning skin** used a signed shift → undefined skin for ~⅓ of shops. **Fixed** (`>>>`).
4. **Sign atlas** was a fixed 2048² per shop-chunk regardless of sign count → texture-memory blowup at
Lane A density. **Fixed** (sized to actual signs, mipmaps off).
5. **Furniture**: numeric edge ids broke the seed hash (all edges identical); offset furniture could
land in an unbuilt chunk and vanish. **Both fixed**.
6. **`takeShots()`** didn't restore the pre-shot mode. **Fixed**.
## ⚠️ Things Fable should know (cross-lane)
- **Lane A landed and is now the shipped default.** `index.html` prefers `generatePlan(seed)` over the
fixture. Seed 20261990 → "Boolarra Heads", ~493 shops. The fixture remains the fallback + schema ref.
- **A "Lane F integration fix" already appeared in `skins.js`** (`facadeMat` now strips a `.jpg`/`facade-`
prefix). Lane A stores `shop.facadeSkin` as a **full filename** (`"facade-stucco-pink.jpg"`), not the
bare skin key the CITY_SPEC table implies. That normalize is what makes Lane A's facades load. Kept.
- **Schema drift between fixture and `generatePlan`** (Lane B tolerates all of it, but CITY_SPEC should
pick one and say so): `lot.id`/`lot.block` are strings in the fixture, **numbers** from Lane A;
`lot.frontEdge` is `'N'/'S'` in the fixture, a **number** from Lane A; `shop.facadeSkin` is a bare key
in the fixture, a **filename** from Lane A; `lot.ry` is axis-aligned in the fixture, **arbitrary
radians** from Lane A. Recommend Fable freeze these in CITY_SPEC so Lane C/D/E don't each guess.
- **≤25 skin-materials gate — RESOLVED (session 2).** Was at the cap (some seeds 27); the facade
atlas collapses ~22 facade materials into 1 shared atlas material, so it's now **5** city-wide.
- **Perf gate on the full city — RESOLVED (session 2).** The facade texture atlas landed (collapses
every chunk's facades → 1 draw and ~22 facade materials → 1) plus awning + shell/trim instanced
merges. Worst gameplay view is now ~261 total (≤300). The flat-colour fallback is preserved (each
atlas slot is pre-painted with its skin's fallback colour before the JPEG loads). See LANE_B_NOTES.
## Suggested next instructions (your call)
1. Freeze the CityPlan schema in CITY_SPEC (the drift list above) — unblocks C/D/E. **Still open.**
2. ~~Decide the facade-atlas~~**done (session 2): Lane B implemented it.** Draw + material gates now green.
3. ~~Interiors hand-off~~**done: Fable wired `enterShop → interiorMode → buildInterior` in the shell;**
verified via `DBG.enterShop`. Lane B's seam (`procity:enterShop`/`procity:exitShop`) is intact.
4. Run Lane F's `shots.py`/`soak.py` against `window.DBG` now that it exists (bookmarks are seed-derived).
5. Optional: capture fresh `docs/shots/laneB/` stills of the generated city via `DBG.shot(...)`; wire
`map` mode into Lane A's `map.html` (currently my lightweight minimap).
## Run
`cd web && python3 -m http.server 8130` → http://localhost:8130 . (The app needs no server-side logic;
the P-key shot harness uses browser downloads.)