90 lines
5.7 KiB
Markdown
90 lines
5.7 KiB
Markdown
# LANE B — STREETSCAPE (chunked 3D town + game shell)
|
||
|
||
> **Prompt for Opus 4.8.** You are working in `/Users/jing/Documents/PROCITY`. Read
|
||
> `docs/CITY_SPEC.md` and `docs/RESEARCH.md` first, then skim the two source patterns you are
|
||
> generalizing: `90sDJsim/web/world/index.html` `buildStrip()` (~line 740) and
|
||
> `thriftgod/web/index.html` `buildStreet()` (1441–1515). You own `web/index.html` and
|
||
> `web/js/world/*`. Do not touch other lanes' files. Plain ES modules, vendored three.js via
|
||
> importmap (`web/vendor/` — copy the importmap style from 90sDJsim's index.html), no npm,
|
||
> no build. All randomness via `core/prng.js`. Commit in small, described steps on `main`.
|
||
|
||
## Mission
|
||
|
||
Turn a CityPlan into a walkable, contiguous, chunk-streamed 3D town at 60fps, plus the game
|
||
shell (mode state machine, controls, day/night, HUD). This is the lane where PROCITY stops
|
||
being two separate street-strip games and becomes a *city*.
|
||
|
||
**Don't wait for Lane A**: start by hand-writing `web/js/world/fixture_plan.js` — a small
|
||
hard-coded CityPlan (2 blocks of main street, one cross street, ~14 shops, valid per the
|
||
CITY_SPEC schema). Build everything against the schema; swap in `generatePlan()` when Lane A
|
||
lands. If the schema needs a field Lane A didn't foresee, propose it in CITY_SPEC in the same
|
||
commit.
|
||
|
||
## Architecture
|
||
|
||
- `web/index.html` — shell only: importmap, canvas, HUD divs, mode state machine
|
||
(`map | street | interior`), main loop. Keep it thin; logic lives in modules.
|
||
- `web/js/world/chunks.js` — the streamer. `ChunkManager(plan, scene)`: on player move,
|
||
ensure chunks within radius R (default 3) exist, dispose beyond R+1. Each chunk = one Group
|
||
built from `chunkIndex(plan)` data. **Build budget 4ms/frame** — queue chunk builds, one per
|
||
frame or via `requestIdleCallback`. Dispose = remove + `geometry.dispose()` for non-shared
|
||
geometry + return instanced slots.
|
||
- `web/js/world/buildings.js` — the facade kit:
|
||
- Shells: **InstancedMesh per chunk** (one box geometry, per-instance matrix + color).
|
||
Parapet/verandah posts/awning boxes likewise instanced.
|
||
- Facade planes: shops sharing a skin share a material — group facade planes per skin into
|
||
merged geometry (`BufferGeometryUtils.mergeGeometries`, vendored) per chunk. ≤25 skin
|
||
materials city-wide.
|
||
- Sign band: per-chunk **2048px canvas atlas** — draw every sign text once, UV-map planes into
|
||
it. One material per chunk for all signage. Steal `textPlane` proportions from the parents
|
||
but do NOT create per-sign canvases.
|
||
- Doors: plane + `userData.place = {kind:'door', shopId}` for the raycaster. Windows: dark
|
||
tinted planes, emissive warm at night.
|
||
- Verandahs/awnings over footpath on main street (the classic AU posted verandah — boxes +
|
||
posts, instanced), `tex-awning-*.jpg` skins.
|
||
- `web/js/world/ground.js` — roads/footpaths/kerbs as merged strips along street edges
|
||
(`ground-asphalt*`, `ground-footpath*`, `ground-grass` for yards, `ground-brickpave` for the
|
||
arcade + market square). UV along edge length. Intersections: simple quad fill, don't
|
||
over-engineer curb geometry.
|
||
- `web/js/world/furniture.js` — instanced streetlights, benches, bins, gum trees (cross-plane
|
||
billboard trees are fine), bus stops; placed seeded along footpaths. GLB props may load from
|
||
the 3GOD depot (promise-cached, placeholder box fallback — pattern in RESEARCH) but every
|
||
furniture type MUST have a primitive fallback.
|
||
- `web/js/world/lighting.js` — sky dome (`sky-*.jpg` picked by seed, BackSide sphere), 6-segment
|
||
day cycle ported from 90sDJsim `applyLighting`: sun direction/intensity, ambient, streetlights
|
||
+ window emissives at night. ONE directional shadow map (2048) following the player, or none —
|
||
measure. ACES tonemapping, bloom optional at threshold 0.9 (vendored postprocessing exists).
|
||
- `web/js/world/player.js` — PointerLockControls walk (WASD + shift-run), eye 1.7m. Collision:
|
||
push-out against building AABBs of the current + neighbour chunks and a "stay off private
|
||
yards" rule; streets/footpaths/square all freely walkable. No navmesh — rectangles are enough.
|
||
- `web/js/world/hud.js` — crosshair, shop-name tooltip via throttled raycast (every 6th frame,
|
||
far=6), time-of-day widget, FPS/draw-call debug readout (`renderer.info`), seed display.
|
||
- Door interaction: raycast hit + click → dispatch `window.dispatchEvent(new CustomEvent(
|
||
'procity:enterShop', {detail:{shopId}}))` and pause street mode. Lane C listens for it; until
|
||
Lane C lands, show a "🚪 {name} — interiors coming" toast and keep walking. Interior returns
|
||
via `procity:exitShop`.
|
||
|
||
## Performance gates (hard)
|
||
|
||
- ≤300 draw calls, ≤200k tris in street view (HUD shows both, live).
|
||
- No per-frame allocation storms; reuse vectors/matrices.
|
||
- Chunk in/out causes no visible hitch (verify with the HUD frame-time graph).
|
||
- `setPixelRatio(min(dpr,2))`; textures ≤1024 (skins already are).
|
||
|
||
## Deliverables
|
||
|
||
1. `web/index.html` + all `web/js/world/*` modules above.
|
||
2. `web/js/world/fixture_plan.js` (works standalone before Lane A).
|
||
3. `shot()` bookmark harness: press `P` → downloads canvas PNG from 3 fixed cameras; save
|
||
references into `docs/shots/laneB/`.
|
||
4. A `docs/LANES/LANE_B_NOTES.md` with the measured numbers (draw calls, tris, chunk build ms).
|
||
|
||
## Acceptance
|
||
|
||
- `cd web && python3 -m http.server 8130` → walk the fixture town for 5 minutes: contiguous,
|
||
no seams, chunks stream silently, day/night cycles, signs legible, tooltips work, budget
|
||
numbers green on an M-series MacBook.
|
||
- Works with `?seed=N` once Lane A's `generatePlan` is importable (guard the import so the page
|
||
still runs on fixture data if citygen is absent).
|
||
- The page loads and runs with `web/assets/` renamed away (all-fallback mode) — no crashes.
|