PROCITY/docs/LANES/LANE_D_CITIZENS.md
2026-07-14 10:46:40 +10:00

75 lines
4.8 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.

# 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 ~258393 (`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.551.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 (2570m)**: 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 0200 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.