minimap.js drew every lot in the plan from the first frame (493 shops on the synthetic, one M press, against 5,330 m / 4.8 game-days of walking to earn it). It now draws what has been walked past AND FRAMES ITSELF TO IT — a shop lot goes 0.83x1.11 px -> 29.4x39.2 px on bowral_real, 35.3x. ZERO DRAWS, measured as an A/B: two port-isolated no-store servers, HEAD vs treatment, the same 15 stations x 4 yaws compared as JSON before either sweep runs. Draw calls IDENTICAL on 60/60 samples at noon and at night; with the gig layer off both sides (the venue queue's async rigs were the only noise) NIGHT is identical in draws AND triangles. Map mode cannot cost a draw structurally: the frame loop never calls composer.render() in the map branch. - discovery.js (new): a 25 m PROXIMITY PROBE, not onChunkBuilt (R=2 => the outermost live chunk corner is 181 m out), on a plan-derived spatial hash — 1.09-1.37 us a probe, every 6th street frame = every 0.46 m at WALK. Plus a side test: you must be in FRONT of the shopfront. Control with a red arm: walking the synthetic's two service laneways, radius-only reveals 93 shops, the shipped probe 59 — 34 refused through the back wall; on the arcade it refuses 0 of 17. - save.js: optional `known` block under the delta law, keyed by TOWN (re-derived: 23 caches x 3 seeds, shop/edge ids and lot geometry all identical; the synthetic alone keys @<seed>). Bounded by the plan (max 493 shops / 2,593 edges) at 8,192, 64 towns FIFO, ints only. Whole corpus fully learned = 137,547 B (2.6% of quota); 482 B after a 500 m outing. Eight reject arms demonstrated. - classic BY CONSTRUCTION: createMinimap(plan, null) is the pre-R39 map and the file has no flag test at all. ?classic=1 / ?fog=0 / ?game=0 map canvases are hash-identical to HEAD's. - Lane A's address layer consumed (getTownCache + createAddresses): street names along walked streets, deduped; the synthetic gets district labels from the same line. No town-type branch. FILED, NOT FIXED: index.html:384 (Lane D patronage door points) and :450 (the R32 spawn) use (-sin ry, -cos ry) — the BACK of the building. buildings.js:410/583-597, dbg.js:53 and minimap.js all use local +Z. Measured against each lot's own frontEdge the +sin point is 0.00 m from the kerb and the shipped one 15.25 m the wrong side, 493/493 and 72/72 shops. At the shipped spawn 0 of the shops within 25 m are in front of you on all four towns tested; at the corrected one, all of them. Two characters, but it moves the default boot's opening pose mid-round — Fable's call. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
390 lines
32 KiB
Markdown
390 lines
32 KiB
Markdown
# 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 39 (v9 Layer 2 — THE FOG, item 39.2) — **zero draws, and the map is finally readable**
|
||
|
||
Full detail + every number in LANE_B_NOTES §39. `minimap.js` drew all 493 synthetic shops from the
|
||
first frame; it now draws what has been walked past and **frames itself to it**. New
|
||
`web/js/world/discovery.js`, `minimap.js` rewritten, `save.js` gains the ledger, the shell wires it.
|
||
|
||
- **ZERO DRAWS, measured as an A/B** (two port-isolated no-store servers, HEAD vs treatment, same 15
|
||
stations × 4 yaws computed from the plan and compared before either sweep runs, sim + tram hidden
|
||
identically): **draw calls identical on 60 of 60 samples at noon and at night.** And map mode cannot
|
||
cost a draw *structurally* — the frame loop never calls `composer.render()` in the map branch, so the
|
||
counter reads the same number open (131) as closed (131).
|
||
- **Legibility.** A shop lot goes from **0.83×1.11 px to 29.4×39.2 px on bowral_real — 35.3×**
|
||
(launceston 27.6×, castlemaine 21.3×, katoomba 20.8×, adelaide 16.9×, fitzroy 11.1×, synthetic 4.0×
|
||
at 21×41). Reproduces Fable's binding table to the digit. The frame is **the shops you have stood in
|
||
front of + where you stand** — framing on walked *edges* was my own first cut and it blew the frame to
|
||
340 m at spawn, because one synthetic edge is 400 m long.
|
||
- **The rule: 25 m of the shopfront AND in front of it**, probed off a plan-derived spatial hash every
|
||
6th street frame (**0.46 m of travel at WALK**), never off `onChunkBuilt` (R=2 ⇒ 128 m). **1.09–1.37
|
||
µs a probe** — 0.0014% of a frame. Falsifiability control with a red arm: walking the synthetic's two
|
||
service laneways, radius-only reveals 93 shops, the shipped probe 59 — **34 refused through the back
|
||
wall**; on the arcade it refuses 0 of 17.
|
||
- **The save.** Optional `known` block, keyed by TOWN (I re-derived the seed-invariance: 23 caches ×
|
||
3 seeds, shop ids / edge ids / lot geometry all identical; synthetic alone keys `@<seed>`). Bounded
|
||
by the plan (max 493 shops, 2,593 edges) with a 8,192 cap, 64 towns FIFO, ints only. **Worst case
|
||
160 KB for the entire corpus fully learned; ~300 B after a 500 m outing.** Eight reject arms
|
||
demonstrated, live state untouched on reject, foreign-town fog does not leak.
|
||
- **Classic by construction:** `createMinimap(plan, null)` is the pre-R39 map and the file has **no flag
|
||
test**. `?classic=1` / `?fog=0` / `?game=0` map canvases are **hash-identical to HEAD's**.
|
||
- **Lane A's address layer consumed** — street names along walked streets, deduped by name; the
|
||
synthetic gets district labels from the same line. No town-type branch anywhere.
|
||
- **FILED, NOT FIXED: `index.html`'s front normal is inverted** at `:384` (Lane D patronage door points)
|
||
and `:450` (the R32 spawn). `buildings.js`, `dbg.js` and `minimap.js` all use local +Z; measured, the
|
||
shell's point sits **15.25 m behind the facade** and **0 of the shops within 25 m of the shipped spawn
|
||
are in front of the player** on all four towns tested. Two characters, but it moves the default boot's
|
||
opening pose mid-round — Fable's call.
|
||
|
||
## Round 38 (v8 WAVE 1 — INHABITATION) — all four runtime items, **+1 walked draw for the lot**
|
||
|
||
Full detail + every number in LANE_B_NOTES §38. Measured as a true A/B: two port-isolated no-store
|
||
servers (`:8472` = `git archive HEAD`, `:8471` = treatment, assets symlinked so E's mid-round wave is
|
||
identical on both), 102 walked 8 m spine steps at yaw 0, each frame fully streamed, rendered through
|
||
the shell's own composer. **The citizen sim + tram are hidden identically on both sides** — with them
|
||
live the same step read control 346 vs treatment 232, because peds spawn on wall-clock time.
|
||
|
||
| walked spine, sim quiet | HEAD | R38 | delta |
|
||
|---|---|---|---|
|
||
| worst draws | 224 | **225** | **+1** |
|
||
| worst tris | 89,502 | **93,298** | **+3,796** (of ~75k spare — the cheap resource, spent) |
|
||
| sum draws over 102 frames | 16,676 | **16,663** | **−13** |
|
||
|
||
Isolated at a fixed pose: **washing +1 draw / +512 tris · magpie +1 draw / +182 tris · every yard box
|
||
prop +0 draws.** `?yards=0` reproduces HEAD **exactly** (224 / 89,502 / 16,676).
|
||
|
||
- **§1.2 the character vector** — `character.js`, 23 towns, ground palette + sky bias + ambience mix.
|
||
**+0 draws, +0 tris.** Classic is byte-identical **by construction, not by flag**: `townCharacter(null)`
|
||
returns the v1 literals, and a null tint keeps the *same material cache key*, so classic gets the same
|
||
object. Default-boot ground hashes identical to HEAD mesh-for-mesh; `?classic=1` hashes unmoved from
|
||
R37. Sky feeds the **base** `skyName`, never `setSky` (a one-way latch that would have killed R32's
|
||
dawn dome on all 23 towns). The vacuous arm is **per-town** — `newtown_godverse`/`redhill_godverse`
|
||
carry `state: ''`, so a per-corpus arm would have dropped exactly those two silently. **21 distinct
|
||
palettes across 23 towns.** `gravel` and `reddust` were not unwired but *unwireable* — their `use`
|
||
values named no slot `ground.js` had; making the slot addressable fixed it at the root. E's three new
|
||
grounds landed mid-round and the upgrade was a name change in one table.
|
||
- **§1.4 backyard Australia** — inside `boxMats`, **+0 draws**. 145 houses inset off their boundaries so
|
||
there is a yard to put things in; **36 `yard` lots that had rendered as blank shopfront sheds since v1**
|
||
are now fenced blocks with a hoist, a shed and often a tank. Collision byte-identical.
|
||
- **§1.4's washing** — **ONE town-wide InstancedMesh, +1 draw at any N**, cap 256, proximity-first off
|
||
the R37 chunk seam. Per-chunk would have been +1 × 31 live chunks = 4× the whole margin. **0 on every
|
||
real town** (no hoists ⇒ no mesh) and **0 at night** (it comes in). I **wrote the inverted wind mode**
|
||
rather than dropping it: the shipped top-weighted weight moves a hem **0.10 cm calm / 0.67 cm gusty**;
|
||
`pegged` gives **4.5 cm / 31 cm**, a ~52× gain, in the right place. The `canopy` string is byte-for-byte
|
||
unchanged.
|
||
- **§1.3 the magpie** — **+1 draw on screen, exactly 0 otherwise; 182 tris of 900.** Territory scales
|
||
with the graph: **6 on 6.78 km synthetic → 157 on bendigo's 188.6 km / 2,593 edges, nearest 120 m.**
|
||
Latch is runtime-only: it sets, the day roll clears it, **localStorage gains no key** and the save file
|
||
never mentions it. `swoopPos()` is exported and pure. The hat stays cut.
|
||
- **§1.1 the audio layer** — **six new mixer layers, zero draws, zero tris**, seeded anchors, distance
|
||
falloff, and **a `StereoPannerNode` per layer** so direction is real (measured spread −0.71…+0.85)
|
||
instead of a limitation written down. Day-of-week derived locally. All six anchored on the default
|
||
boot; **3 of 6 report `no-anchor` on real towns** rather than being faked. The falsifiability control
|
||
— one manifest key renamed away on a third port — makes that source report **`no-entry`, 0 plays,
|
||
never `audible`**, five others unchanged.
|
||
|
||
**Two defects my own green numbers could not see, both caught by a screenshot.** The washing shipped at
|
||
256 instances / +1 draw / cap respected **and was invisible behind its own back fence** (per-instance peg
|
||
height + a fence taller than the hoist); the magpie perched **on thin air** over the road. R37's lesson,
|
||
relearned at full price. Territories now snap to `furniture.js`'s own streetlight rule.
|
||
|
||
**Lane E's props, adjudicated:** hoist + aerial **permanently primitive** (E's spike killed them
|
||
outright, and primitive is also the +0-draw mechanism) · `paling_fence` at 1,121 tris × **905 runs =
|
||
1.01 M triangles** — no · `magpie` GLB **held for the tint**: it currently reads as a crow, and a magpie
|
||
seen for 1.2 s in peripheral vision *is* its white bar. My 182-tri bird already carries it.
|
||
|
||
## Round 37 (v8 WAVE 0 §0.1 + §0.4) — THE KERB IS FOOTPATH, at +0 draws · commit `47db478`
|
||
|
||
John's ruling landed as geometry. `ground.js` narrows the road quad to the carriageway and extends
|
||
the **existing** merged `footGeos` class inward to meet it, consuming Lane A's `vergeBand(e)[0]`.
|
||
- **+0 draws, proven not asserted**: identical draw count at every one of 102 walked spine steps
|
||
(worst frame **276 both**, sum **20,081 both**) and on `katoomba_real` (worst 100 both).
|
||
**Tris went DOWN** — −8 synthetic, −288 katoomba. The charter's "~2k tris" was an estimate against
|
||
a class that already existed; extending a quad re-parameterises it. Honest number, stated loudly.
|
||
- **14 of 14 gig posters were standing in painted bitumen. 0 are now.** No poster moved; the ground did.
|
||
- Surfaces verified by **raycast**: `main`@24 road 0–5 / footpath 5.25–15.25 · `side`@12 (72% of the
|
||
corpus) road 0–3 / footpath 3.25–9.5 · **`lane` degenerate `[4,4]` ⇒ pre-ruling geometry** ·
|
||
**`arcade` all footpath, no road quad, no kerbs** · **residential keeps its grass nature strip**.
|
||
The lane case branches on `outer <= inner`, never on a band literal (Fable's mid-round correction).
|
||
- **A defect my own numbers could not see:** the first cut painted every intersection shut (a 12.5 m
|
||
footpath crosses the perpendicular *carriageway*, where a 3.5 m one only clipped a corner). All
|
||
numbers were green; the screenshot was not. Fixed by putting the carriageway on top under the flag.
|
||
- **`?verge=0` ships permanently** and is EXACT: ground vertex+UV hashes identical to the HEAD build,
|
||
and the same detector goes red on the default boot.
|
||
- **`?classic=1` is FENCED, not amended** (R31 dig-flip pattern): identical ground geometry AND
|
||
identical ground-only rendered pixels at three poses. → Fable's call if classic should be amended.
|
||
- **§0.4** — the chunk seam was never connectable: on HEAD `!!chunks.ctx === false`, so a consumer
|
||
holding only `PROCITY.chunks` had nowhere to attach. Now `chunks.onChunkBuilt(fn) → unsubscribe()`
|
||
(many consumers) + `chunks.ctx` (the documented single slot). 136 built / 144 disposed over a walk,
|
||
payload + key shape verified, throwing subscriber survives, and **zero behaviour change with no
|
||
subscriber**: a 102-step per-frame walk hashes `ceb1ec66` on both builds. `sim.js` untouched.
|
||
|
||
## Round 30 (v7.0-alpha ledger #4) — THE COLLECTION UI + SLEEP surface · qa GREEN 6/6
|
||
|
||
The crate panel + game bar in `hud.js` (my DOM-overlay patterns), on F's §30.1 `PROCITY.game`
|
||
contract — and verified against F's REAL committed core (`207bcff` landed mid-round; my test
|
||
snapshot diffed byte-identical to it — **nothing stubbed**, unlike C's wave-1 run).
|
||
- **Game bar** (bottom-right): `day N · $cash` · `CRATE n` (count in the standing HUD; opens the
|
||
panel, `C` toggles) · `☾ SLEEP` → `game.sleep()`, enabled any time (alpha).
|
||
- **Crate panel**: cover thumb (pack-atlas CSS crop off the entry's sku — per-shop godverse base or
|
||
town-wide; parody = type glyph by design), title/artist, `paid $p · where · day d`, newest first,
|
||
header totals. 50-row rebuild 0.9 ms; 0 draws (DOM-only).
|
||
- **Construction-gated on the game object itself** (lazy, R19-selector style): `?classic=1`/`?game=0`
|
||
⇒ zero game DOM, zero listeners, help line byte-identical, 0 console errors (measured, clean tabs).
|
||
`?noassets` ⇒ 0 stock fetches, placeholders stand.
|
||
- **SLEEP proven end-to-end**: day 1→2, weekNight 0→1, wake DAWN seg 0, `procity-save/1` on disk,
|
||
next boot loads it and the UI reads it. **venue.js §30.4 filing fixed as filed** — compat
|
||
`venueShopId` is now a live weekNight-keyed getter (116 → 490 across a sleep, measured).
|
||
- Full measurement table + honest notes: LANE_B_NOTES §30. Verified on a port-isolated no-store
|
||
:8151 snapshot server (not :8130/:8144 — Lane F was mid-flight in the shared tree).
|
||
|
||
## Round 9 (Fable's ROUND9 → Lane B) — v2 tour bookmarks + tram (`?tram=1`) · qa GREEN
|
||
|
||
Ship-v2.0 round. Two deliverables, both v2-law clean, `qa.sh --strict` GREEN, flags-off byte-identical.
|
||
|
||
- **8 v2 tour bookmarks** in `js/world/dbg.js` (16 total): `rain_verandah` (hero — open VIDEO shop w/
|
||
winmap parallax rooms, CLOSED dark neighbours, night rain), `patronage_door`, `dig_real`,
|
||
`katoomba_main`, `tram_stop`, `rain_street`, `night_crowd`, `shopfront_detail`. All un-letterboxed,
|
||
113–188 draws, 0 errors. `tram_stop` picks the **nearest-origin** stop (open framing; spine-end stop
|
||
was walled in). F shoots these — best hero combo `?winmap=1&weather=rain&dbg=1` seg 5.
|
||
- **Tram** (`js/world/tram.js`, `?tram=1`, NON-BLOCKING for the tag): one small bus, out-and-back loop
|
||
on the main-street spine, 3.5 s door-dwell at each `busShelterStops` stop, ping-pong at the ends.
|
||
**Deterministic** (verified byte-identical across fresh instances). **Exactly 2 draws** (verified via
|
||
`renderer.info` delta). Composes w/ weather+night (headlights 0.15→1.6 via `lighting.isNight()`).
|
||
"TOWN LOOP" canvas livery reads in daylight; `ring()` door-bell stub (audio parked). Shell-wiring
|
||
(2 lines) documented for F in LANE_B_NOTES; no collision in v0 (noted for v3).
|
||
- **Rain polish** (weather.js, quick R9 debt): capped `gl_PointSize` (clamp 2–22px) + lower base — near
|
||
drops are now thin streaks, not big blobs. Re-verified: rain smoke still GREEN, money-shot re-shot.
|
||
- Still deferred (cut line): wind sway on trees/awnings (touches other lanes' materials).
|
||
|
||
## Round 8 (Fable's ROUND8 → Lane B) — weather (`?weather=1`), carried from R7 · qa GREEN
|
||
|
||
New `js/world/weather.js` + `lighting.setSky()`. Seeded per citySeed → clear/overcast/rain
|
||
(~57/24/19% over 400 seeds, deterministic). **Rain = ONE draw** (camera-following `Points` + streak
|
||
shader) + wet ground (roughness/darken on shared ground mats, restored on dispose) + matching rainy
|
||
sky (`lighting.setSky`). Fully procedural (0 depot fetch). Worst view **141 draws** (≤300); composes
|
||
with `?winmap`+night — **rain outside a lit interior-mapped window** shot saved. Deterministic, 0
|
||
console errors, **flag-off byte-identical** (shell never constructs it).
|
||
- **Exposed the `window.PROCITY.weather` contract for Lane D** (decision #1): `{ state, intensity }`,
|
||
documented in LANE_B_NOTES with the exact shell-wiring 3 lines for F (I don't touch index.html).
|
||
`?weather=1` seeded (default town rolls clear); `?weather=rain|overcast|clear` forces a state.
|
||
- Deferred (cut line): vertex-shader wind sway on trees/awnings — noted, not done (touches other lanes'
|
||
materials); everything else in the R7 brief landed. Tram stretch not attempted.
|
||
|
||
## Round 6 (Fable's ROUND6 → Lane B) — placed the orphaned street props · qa GREEN
|
||
|
||
Wired the two published-but-unconsumed street GLBs (`street_bin_01`, `bus_shelter_01`) via the
|
||
use-if-ready furniture path (fail-soft; `?noassets` → primitives, 0 network). Files: `furniture.js`,
|
||
`chunks.js` (collider merge).
|
||
- **Bins**: sparse (~1/block, `s += 78 m`, seeded), decoupled from benches — country town not a mall.
|
||
- **Bus shelters on the street graph** (future tram/bus stops): deterministic rule = midpoint of every
|
||
MAIN edge with `hashEdge(id)%3===0`, road-side footpath, long axis along the edge, open front to road
|
||
→ **1–3/town** (2 in seed 20261990). **Solid collider** (furniture now returns colliders → `chunks`
|
||
merges into `getColliders`); walk-into-it is blocked. **Exposed `busShelterStops(plan)` → the stop
|
||
list contract** for whoever builds the vehicle loop.
|
||
- **Perf**: worst view WITH rig-fleet citizens **94k tris / 148 draws** (≤200k / ≤300, big margin);
|
||
deterministic; all-flags-on composes clean (0 errors). Flagged the 3k-tri `street_bin` GLB to Lane E as
|
||
a decimation candidate (bounded by the sparse cadence for now). Shot: `docs/shots/laneB/bus_shelter.png`.
|
||
|
||
## Round 5 (Fable's ROUND5 → Lane B) — v2 window trick, behind `?winmap=1` · qa GREEN (4/4)
|
||
|
||
- **Parallax interior-mapping window glass** (V2_IDEAS "Vuntra window trick"), behind **`?winmap=1`
|
||
default-off.** One shared `ShaderMaterial` on the existing window geometry does single-cube interior
|
||
mapping — each fragment rays into a virtual room box and shades back wall + seeded shelves + side
|
||
walls + floor/ceiling. **Zero extra geometry, 1 window draw/chunk, per-shop variation via attributes
|
||
(aTangent/aTint/aSeed), not per-shop materials.** Seeded from `shop.seed`.
|
||
- **§3.5 held:** night → open shop's room glows warm, closed shop's room dark (reuses the closed-facade
|
||
`uHour/uNight/aHours`). Screenshots: `docs/shots/laneB/winmap_{day,night_open_vs_closed,night_closed}.png`.
|
||
- **v2 prime law held & proven:** flag-OFF byte-identical to v1 (windows `MeshStandardMaterial`, no
|
||
winmap attrs); flag-ON worst view **122–168 draws** (≤300); shader **100% procedural — 0 fetch delta**
|
||
vs `?noassets` baseline; 0 console errors; `GOLDEN.hash` untouched. `?winmap` is self-read in
|
||
buildings.js → **no index.html seam for F** (just a flags-table row). File: `buildings.js`.
|
||
|
||
## 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 12 (v3.0-alpha, VENUES & GIGS, `?gigs=1`) — Lane B street-side — done
|
||
Built the pub's street presence for the one-pub vertical slice. Dependencies (A venue+`plan.gigs`,
|
||
C watchPoints, E posters+gig audio, D band+crowd) all landed; F wires it last.
|
||
- **`web/js/world/venue.js`** (new, B-owned; shell/F constructs under `?gigs`) —
|
||
`createVenuePresentation(plan, skins, scene) → {group, update(gigState), dispose, venueShopId}`.
|
||
**Posters:** E's 4 poster skins at each `plan.posters` (x,z,ry) with the gig's `bandName`
|
||
overprinted in `nameZone` (canvas composite; seeded template; flat fallback if JPEG missing).
|
||
Verified "THE WOMBATTS" over the band-photo art. **Lit frontage:** warm marquee + emissive bulb row
|
||
over the venue door, `update(gigState)` ramps the glow. Verified: "The Thornbury Hotel" lit at night.
|
||
- **Muffled-gig spill** (extended `audio.js`): near the venue (≤26 m) while gig `doors`/`on`, plays
|
||
`pubrock-live` through a 470 Hz **lowpass** at low distance-gain. Reads `window.PROCITY.gigs` (F)
|
||
with a clock+`plan.gigs` fallback. Verified: gain ~0.14 muffled at night, fades off far/day.
|
||
- **Prime flag law held:** flags-off = no gigs/posters/venue (goldens frozen, no citygen edits), audio
|
||
gig layer created but inert (`plan.gigs` absent → skipped). Byte-identical boot.
|
||
- **Stretch deferred:** streetlamp-pool night-readability on the venue block (touches shared furniture
|
||
rendering) — flagged as next polish. **Note to A:** main-street poster positions land mid-road on
|
||
intersection nodes — a verge/pole offset would seat them better.
|
||
- Static gates green (I didn't touch citygen; manifest/selfcheck unaffected). Files: `web/js/world/
|
||
venue.js` (new) + `web/js/world/audio.js` (spill extension) + docs. **F wiring documented in LANE_B_NOTES.**
|
||
|
||
## Round 11 (AUDIO) — Lane B WebAudio engine — done
|
||
Built **`web/js/world/audio.js`** (B-owned) + a guarded create-call in the shell. One `AudioContext`
|
||
unlocked on the first gesture; self-contained (reads `window.PROCITY`, self-ticks); consumes Lane E's
|
||
`manifest.audio` (23 assets, 8.7 MB, ≤25 MB) with a 3× fetch retry.
|
||
- **Layers** (crossfaded, no per-frame alloc): day/night ambience (lighting segment), rain
|
||
(`weather.intensity`), footsteps (player movement), **shop-door music spill** (nearest open
|
||
music-shop, distance-gain — verified `video-synth` at ~0.08 by Video Barn's door), tram rumble+bell
|
||
(`?tram`), SFX (doorbell/door-open on `procity:enterShop`).
|
||
- **House audio law — all verified live:** boots silent (nothing plays/fetches pre-gesture, 0 console
|
||
errors); `?mute=1` → silent surface (exists for smokes, 0 fetches); `?noassets=1` → 0 audio/manifest
|
||
fetches; beds lazy-load; spill is a pure function of shop type.
|
||
- **For Lane F:** `window.PROCITY.audio` exposes `playInterior({musicKey,toneKey})`/`stopInterior()`
|
||
(street beds auto-duck while inside), `playSfx`, `setMasterGain`, `mute`, and a `state` diag surface.
|
||
- **For Lane C:** confirmed `room.audio = { musicKey, toneKey }` on `buildInterior` is the right shape
|
||
(musicKey → `manifest.music`, toneKey → `manifest.ambience`).
|
||
- Gates green (manifest validator 0 errors incl. audio 23; selfcheck 1727/1727). `qa.sh --strict`
|
||
hangs on a headless Playwright gate — ran the static gates directly. Files: `web/js/world/audio.js`
|
||
(new) + a small `[Lane B R11 audio]` loader in `web/index.html` + these docs.
|
||
|
||
## 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 (~1–2 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 ~130–210. 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.)
|