From 43edabfe56b8a6a21626832b3bef4a27f22fe742 Mon Sep 17 00:00:00 2001 From: m3ultra Date: Wed, 15 Jul 2026 00:51:51 +1000 Subject: [PATCH] =?UTF-8?q?Lane=20D=20round-7:=20validate=20ped-merge=20?= =?UTF-8?q?=E2=86=92=20GO=20for=20roster=20flip=20+=20full-density=20defau?= =?UTF-8?q?lts=20+=20escape=20hatch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lane E's R7 merge (447188a) made each ped 1 mesh/1 material (~1 draw/near-rig, fleet 116->24 at the cap) — the gate my R6 memo named. Validated + handed F the GO. Merge validated: bind+animate/no-T-pose, skinning/skeleton preserved, silhouettes+atlas materials crisp (hi-vis/suit/kid), impostor bake clean, identity variety + determinism intact. GPU memory dropped (geom 205->160, tex 141->90). Flip numbers (full density, perChunk 16, in-shell): worst street view 241 draws <=300 (was 356 unmerged), 65,899 tris <=200k, 17 near-rigs; 30-chunk soak (726 builds/disposes) leak-free, 0 GPU delta, heap stable. => GO. D2: enableStream default perChunk 8->16 (full density, the merge affords it); near-cap stays 24. D3: test page + harness default to stream now; ?roster=v1 escape hatch restores the fixed roster (verified streamMode false + determinism). No-flag boot -> streamMode true + determinism. F's web/index.html flip wiring (invert to default-on + ?roster=v1 escape) documented in LANE_D_NOTES; in-shell no-flag-URL boot completes when F lands F1. Only web/js/citizens/sim.js + web/citizens_test.html changed (+ NOTES/progress). Co-Authored-By: Claude Opus 4.8 --- D-progress.md | 24 ++++++++++++++++++ docs/LANES/LANE_D_NOTES.md | 52 ++++++++++++++++++++++++++++++++++++++ web/citizens_test.html | 4 ++- web/js/citizens/sim.js | 9 +++---- 4 files changed, 83 insertions(+), 6 deletions(-) diff --git a/D-progress.md b/D-progress.md index 2351c18..664d22c 100644 --- a/D-progress.md +++ b/D-progress.md @@ -1,5 +1,29 @@ # PROCITY-D — progress (Lane D · Citizens) +## Round 7 (the flip) — MERGE VALIDATED, GO given to F, defaults + escape hatch ready. qa GREEN. + +Validated Lane E's ped-merge (the gate my R6 memo named) and handed F the **GO** for the roster flip. +Full go/no-go numbers + F's two-line wiring at the top of `LANE_D_NOTES.md`. + +- **Merge validated (D1)**: all 19 peds now **1 mesh / 1 material** (was 8/2) → ~1 draw/near-rig + (fleet 116→24 at the cap); bind+animate/no-T-pose, silhouettes+atlas materials crisp (hi-vis/suit/ + kid), impostor bake clean, identity variety + determinism preserved. GPU memory dropped too. +- **The flip numbers (full density, perChunk 16)**: worst street view **241 draws ≤300** (was 356 + unmerged), **65,899 tris ≤200k**, 17 near-rigs; 30-chunk soak (726 builds/disposes) **leak-free, 0 + GPU delta**, heap stable. → **GO.** +- **Full-density defaults (D2)**: `enableStream` default flipped 8→**16** (the merge affords it); + near-cap stays 24 (now 24 draws not 168). +- **Post-flip + escape hatch (D3)**: test page + shell now default to stream; **`?roster=v1` restores + the fixed roster** (verified: streamMode false, determinism ✓). No-flag boot → streamMode true, + determinism ✓. F's `web/index.html` flip wiring (invert to default-on + `?roster=v1` escape) + documented; the in-shell no-flag-URL boot completes when F lands F1 (roster proven running in-shell + at the shipping default via the in-shell soak above). + +Only `web/js/citizens/sim.js` (perChunk default) + `web/citizens_test.html` (default-on + escape +hatch) changed. Committed atomically. + +--- + ## Round 6 (harden streamed roster toward default-on) — DONE. qa GREEN. Flag still default-off. Hardened `?roster=stream` and wrote the **default-on readiness memo** (top of `LANE_D_NOTES.md`) so diff --git a/docs/LANES/LANE_D_NOTES.md b/docs/LANES/LANE_D_NOTES.md index 75e5c22..b4a7f2d 100644 --- a/docs/LANES/LANE_D_NOTES.md +++ b/docs/LANES/LANE_D_NOTES.md @@ -6,6 +6,58 @@ _rotOnly/head-bone normalize/upgradeStreetPeople). Measurements on the M3 Ultra --- +## ROUND 7 — MERGE VALIDATED · **GO for the roster flip** (→ Lane F: flip on this) · escape hatch ready + +**Verdict: GO. Lane E's R7 ped-merge closed the one gap the R6 memo named. Full-density streamed +roster (perChunk 16) is now under budget in a real town. Flip it default-on.** +`qa.sh --strict` GREEN. All numbers in-shell (real town "Boolarra Heads", seed 20261990). + +### Merge validation (E1 — `pipeline/merge_ped.py`, `447188a`) ✅ +- **Every one of the 19 peds is now 1 mesh / 1 material** (was 8 meshes / 2 materials) → **~1 draw per + near-rig**; fleet **116→24 draws at the 24-cap** (E's own count: 92→19). Bonus: GPU memory dropped + too (geometries 205→160, textures 141→90 in-shell). +- Rigs **bind + animate** (walk/idle) — mixer 0.2–0.3 ms; **no T-pose**. Skinning/skeleton preserved + (65 joints), clips retarget unchanged. +- **Silhouettes + atlas materials intact** — hi-vis worker (vest/helmet/boots), suit, business, comical + kid all read crisp, no atlas seams/melting; **identity variety preserved** (`pickRig` unchanged). +- **Impostor bake clean** (19 subjects, 16×5 atlas); **determinism holds** (507 chunk-keyed identities + re-derive from seed). + +### The flip numbers (full density, `perChunk 16` — no reduced-density concession) +| metric | pre-merge (R6) | **post-merge (R7)** | budget | +|---|---|---|---| +| worst continuous-walk street view **draws** | 356 ✗ | **241** ✅ | ≤300 | +| worst view **tris** | ~66k | **65,899** ✅ | ≤200k | +| worst-view near-rigs | (throttled to 8) | **17** (full) | cap 24 | +| draws / near-rig | ~7 | **~1** | — | +| 30-chunk soak (3 walks = 726 builds/disposes) | leak-free | **leak-free, 0 GPU delta** | baseline | +| heap over soak | 50→77→54 | 78→63 (GC, stable) | stable | + +### Full-density shipping defaults (set this round) +`enableStream` default is now **`perChunk 16`, `radius 2`** (was 8). Near-cap stays 24 (now 24 draws, +not 168). `NIGHT_LIVELY_FLOOR 0.5` for the open-late block (R6). No other knobs change. + +### → Lane F: the flip wiring in `web/index.html` (F owns the shell — decision #1, flip on my word) +Invert the R5/R6 flag: **stream is the default; `?roster=v1` is the escape hatch.** +```js +const rosterV1 = params.get('roster') === 'v1'; // escape hatch → the old fixed roster +const citizens = new CitizenSim({ renderer, scene, camera, citySeed: plan.citySeed, + graph: plan.streets, fleet, + chunkStream: rosterV1 ? null : { radius: 2 } }); // default-on +``` +Hours-aware (optional, recommended): after construction, `citizens.setNightLivelyChunks(keys)` where +`keys` = the openLate shop's lot→chunk + 1-ring (`sim.chunkKeyAt(x,z)`; openLate = `hours[1] >= 22`). +Everything else (`setTimeOfDay`/`setExposure`/`update`/`setPaused`) is unchanged. The +hook-driven window (`onChunkBuilt`/`onChunkDisposed` → `sim.onChunkBuilt/onChunkDisposed`) is still the +preferred precise option; poll works with zero wiring. + +**Verified in my harness (test page + runtime-enabled shell):** no-flag boot → `streamMode:true`, +perChunk 16, determinism ✓; `?roster=v1` → `streamMode:false`, fixed roster, determinism ✓. The +in-shell no-flag-URL boot completes once F lands the two-line flip above — the roster itself is proven +to run correctly in the shell at the shipping default (the soak + flip numbers above are in-shell). + +--- + ## ROUND 6 — streamed-roster "default-on readiness" memo (→ Fable + Lane F, for the R7 flip call) **Verdict: functionally ready (deterministic, leak-free, hours-aware, composes) — but full-density diff --git a/web/citizens_test.html b/web/citizens_test.html index 3dc9837..c8d2315 100644 --- a/web/citizens_test.html +++ b/web/citizens_test.html @@ -83,7 +83,9 @@ import { KeeperManager } from './js/citizens/keepers.js'; const qs = new URLSearchParams(location.search); const SEED = (parseInt(qs.get('seed'), 10) || 20261990) >>> 0; const NOASSETS = qs.get('noassets') === '1'; -const STREAM = qs.get('roster') === 'stream'; // v2: chunk-streamed roster (default off = v1) +// R7 flip: the chunk-streamed roster is now the DEFAULT. `?roster=v1` is the escape hatch back to the +// fixed v1 roster; `?roster=stream` is still accepted (explicit). Mirrors the shell wiring F lands in F1. +const STREAM = qs.get('roster') !== 'v1'; // ---------- renderer / scene ---------- const app = document.getElementById('app'); diff --git a/web/js/citizens/sim.js b/web/js/citizens/sim.js index b7ecbc5..bb41326 100644 --- a/web/js/citizens/sim.js +++ b/web/js/citizens/sim.js @@ -147,11 +147,10 @@ export class CitizenSim { // downstream — LOD tiers, the 24 near-cap, mixer stagger, rig pool, impostor layer — is already // per-citizen and chunk-agnostic and runs GLOBAL across live chunks, unchanged. - // perChunk default 8 keeps the worst continuous-walk street view ≤300 draws in a real town TODAY. - // Ceiling cause: each decimated ped is 8 sub-meshes but only 2 materials → ~7 draws/near-rig, so a - // dense street of near-rigs blows the 300 budget. The clean unlock (LANE_D_NOTES R6 memo) is merging - // each ped's sub-meshes by material (8→2) — then perChunk can go back to ~16 with headroom. - enableStream({ radius = 2, perChunk = 8 } = {}) { + // perChunk default 16 = full density. Affordable since Lane E's R7 merge made each ped ONE draw + // (was 8 sub-meshes / ~7 draws): the worst continuous-walk street view now sits well under the 300 + // budget even with a dense street of near-rigs. This is the shipping default for the R7 roster flip. + enableStream({ radius = 2, perChunk = 16 } = {}) { if (this.streamMode) return; this.roster.forEach(c => this._releaseActor(c)); this.roster.length = 0; // tear down the v1 roster if (!this.chunkEdges) this._buildChunkIndex();