75 lines
4.8 KiB
Markdown
75 lines
4.8 KiB
Markdown
# LANE D — CITIZENS (populate the town)
|
||
|
||
> **Prompt for Opus 4.8.** You are working in `/Users/jing/Documents/PROCITY`. Read
|
||
> `docs/CITY_SPEC.md` and `docs/RESEARCH.md` first, then the source you are porting:
|
||
> `90sDJsim/web/world/index.html` lines ~258–393 (`loadRig`, `spawnRig`, `_canon`, `_rotOnly`,
|
||
> head-bone height normalization, `upgradeStreetPeople`) — this stack is proven, port it
|
||
> faithfully. You own `web/js/citizens/*`, `web/citizens_test.html`, and `web/models/*`.
|
||
> Do not touch other lanes' files. Plain ES modules, three.js from `web/vendor/`, randomness
|
||
> via `core/prng.js`. Commit in small, described steps on `main` (GLBs are committed — keep
|
||
> the set curated, not hoarded).
|
||
|
||
## Mission
|
||
|
||
Streets that feel lived-in and shops with keepers, at city scale, on budget. Rigged characters
|
||
near, cheap impostors far, deterministic per-citizen identity (same seed → same person walks
|
||
the same beat), all built on the **house fleet rule**: shared base meshes + one canonical clip
|
||
bank, never per-character rigs.
|
||
|
||
## Assets (copy, never edit locally)
|
||
|
||
- Copy the ped fleet from `90sDJsim/web/world/models/peds/` (19 rigged GLBs + clip-only
|
||
`walk.glb` / `idle.glb`) into `web/models/peds/`. Byte-identical copies — the canonical
|
||
source stays upstream (house law; see `asset-pipeline` conventions).
|
||
- Canonical hero rigs if needed: `~/Documents/character_kit/` (female + male). Clip bank:
|
||
`~/Documents/mixamo-fetch/out/` (34 clips — sit, lean, look-around, phone etc. may exist;
|
||
check before wanting new ones; **never re-download an existing clip**). New clips need John
|
||
to run the mixamo-fetch flow — file wishes in `docs/LANES/LANE_D_NOTES.md`, don't block.
|
||
- Licensing: 🟢 web-ok only. No ActorCore.
|
||
|
||
## Architecture
|
||
|
||
- `web/js/citizens/rigs.js` — the ported stack: promise-cached `loadRig`; `spawnRig()` with
|
||
SkeletonUtils.clone, `mixamorig\d+` canonicalization (one walk clip drives all 19 peds),
|
||
`_rotOnly` position-track stripping, head-bone height normalization (seeded 1.55–1.95m),
|
||
feet planted by min bone Y, random clip start offsets. Primitive-figure placeholders that
|
||
hot-swap when GLBs arrive (`upgradeStreetPeople` pattern) — the town is populated from
|
||
frame one.
|
||
- `web/js/citizens/sim.js` — the citizen simulation (Vuntra's layered idea, hamlet-scale):
|
||
- Deterministic roster per chunk: `rng(citySeed,'citizen',chunkKey+i)` → identity (ped index,
|
||
height, walk speed, home/goal habits). Density by district (main street busy at midday,
|
||
residential sparse, market square clumps).
|
||
- **Near tier (<25m)**: full rig + mixer, walking footpath lanes (offset polylines along
|
||
street edges from the CityPlan graph), turning at nodes with seeded choice, idle/loiter
|
||
stops at shop windows and benches (bench-sit if a sit clip exists, else stand-loiter).
|
||
- **Mid tier (25–70m)**: no mixers ticking — instanced/billboard impostors. Generate impostor
|
||
sprites at runtime: render each ped GLB once to a small offscreen RT from 4 yaw angles →
|
||
one sprite atlas → InstancedMesh quads picking the nearest angle. (This keeps impostors
|
||
automatically in sync with the fleet; cache the atlas in-session.)
|
||
- **Far**: culled entirely. Promotion/demotion by distance with hysteresis so nobody pops
|
||
while you watch (swap only when off-screen or beyond a fade).
|
||
- Time-of-day modulation from the shell's day segment (event or global): morning trickle,
|
||
lunch rush, night = near-empty streets + the odd wanderer.
|
||
- `web/js/citizens/keepers.js` — shopkeepers: one keeper per open shop, standing at the counter
|
||
slot (Lane C exposes counter position in its `places` — coordinate via the event payload;
|
||
until Lane C lands, test with a mock). Idle clip + a "greet" head-turn toward the player if a
|
||
clip allows. Keepers are spawned when an interior builds, disposed with it.
|
||
- Budgets: ≤ 24 rigged actives, ≤ 4 mixers updated per frame beyond the nearest 8 (stagger
|
||
updates), impostor instancing = 1 draw call per atlas. All mixers pause when tab hidden.
|
||
|
||
## Test page (`web/citizens_test.html`)
|
||
|
||
Standalone: a fixture street loop (hard-code a small street graph — do NOT import Lane A/B
|
||
files; keep the fixture local), spawn slider 0–200 citizens, tier-visualization toggle
|
||
(colour by tier), live HUD (rigged count, mixer ms, draw calls, fps), time-of-day slider,
|
||
determinism check button (respawn roster, assert identical identities).
|
||
|
||
## Acceptance
|
||
|
||
- 200-citizen fixture scene holds 60fps on an M-series MacBook; mixer update <2ms/frame.
|
||
- No T-pose ever visible (placeholder → rig swap is seamless); no giants/ants (height
|
||
normalization verified across all 19 peds); one shared walk clip animates every ped.
|
||
- Same seed → same crowd, twice (assert).
|
||
- Screenshots of near/mid tiers + a busy main-street shot into `docs/shots/laneD/`.
|
||
- `docs/LANES/LANE_D_NOTES.md`: measured budgets + clip wishlist for the mixamo-fetch run.
|