docs(N.4) Task 15: mark Week 2 complete + Adjustment 3 (FPS regression cause)

Week 2 ships: LandblockSpawnAdapter routes atlas-tier GfxObjs to WB
ref counts (Task 11/12), pending-spawn list integration verified
(Task 14), WbMeshAdapter.Tick drains the pipeline queues per frame
(added per Adjustment 3, fixes a real memory leak).

Task 13 (memory budget verification) is deferred: stress-test
revealed the FPS drop with flag-on isn't the queue leak we thought
— it's the dual-pipeline cost (background workers + duplicate GL
upload + duplicate I/O + legacy renderer still doing the same atlas
work). The savings only materialize in Task 22 when the dispatcher
short-circuits the legacy upload for atlas-tier content. Plan
Adjustment 3 documents this; no fix needed before Week 4 since
default-off is byte-identical to pre-N.4.

Next: Task 16 (Week 3) — AnimatedEntityState + per-instance path.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-05-08 14:33:19 +02:00
parent bf53cb4fce
commit 36f7a601c4

View file

@ -66,23 +66,28 @@ This plan is the **execution source of truth** for N.4. It is updated as tasks l
Status: **Living document — work in progress, started 2026-05-08.**
**Progress (2026-05-08):** Week 1 ✅ COMPLETE. Tasks 1-10 shipped. Foundation types + WbMeshAdapter constructed against real WB pipeline (`OpenGLGraphicsDevice` + `DefaultDatReaderWriter` + `ObjectMeshManager`). `InstancedMeshRenderer.EnsureUploaded` routes through the adapter under `ACDREAM_USE_WB_FOUNDATION=1`; sentinel entry marks "this gfxObj lives in WB now" and the draw loop skips sentinel entries (Task 22's `WbDrawDispatcher` will draw them eventually). Conformance tests pin `GfxObjMesh.Build` + `SetupMesh.Flatten` behavior. Build green, 901 tests pass, 8 pre-existing failures only (unchanged from main).
**Progress (2026-05-08):** Weeks 1 + 2 ✅ COMPLETE. WB pipeline running flag-on (constructed + ref-counted + per-frame Tick draining its queues). Three architectural adjustments documented: 1 (DefaultDatReaderWriter discovery, no bridge needed), 2 (renderer is tier-blind; routing belongs in spawn callbacks), 3 (FPS regression root-caused as dual-pipeline cost; Task 22's dispatcher will allow the legacy-renderer short-circuit). Build green, 912 tests pass, 8 pre-existing failures only.
**Next: Task 11** — `LandblockSpawnAdapter` (streaming-loader hook for ref-count lifecycle).
**Next: Task 16** (Week 3) — `AnimatedEntityState` type + per-instance customization path.
| Task | Status | Commit |
|---|---|---|
| 1 — WbFoundationFlag scaffold | ✅ | `81b5ed8` |
| 2 — AcSurfaceMetadata + Table | ✅ | `46deed6` |
| 3 — Mesh-extraction conformance | ✅ | `ed73fc5` |
| 4 — Setup-flatten conformance | ✅ | `ed73fc5` (combined with #3) |
| 4 — Setup-flatten conformance | ✅ | `ed73fc5` |
| 5 — WbMeshAdapter stub + IWbMeshAdapter | ✅ | (post-`ed73fc5`) |
| 6 — WbDatReaderAdapter | ✅ OBSOLETED | `502c3a8` |
| 6 — WbDatReaderAdapter | ✅ OBSOLETED (Adj. 1) | `502c3a8` |
| 7 — GameWindow wiring under flag | ✅ | `502c3a8` |
| 8 — CLAUDE.md pointer | ✅ | `506b86b` (preemptive) |
| 9 — Real WB pipeline + InstancedMeshRenderer routing | ✅ | `4ad7a98` |
| 10 — Week 1 wrap-up | ✅ | (this commit) |
| 1115 — Week 2: streaming integration | pending | — |
| 9 — Real WB pipeline + InstancedMeshRenderer routing | ✅ partial / Adj. 2 reverted | `4ad7a98` + `4f318bc` |
| 10 — Week 1 wrap-up | ✅ | `c49c6ed` |
| 11 — LandblockSpawnAdapter | ✅ | `669768d` |
| 12 — Wire into GpuWorldState | ✅ | `931a690` |
| 13 — Memory budget verification | ✅ deferred to Task 22 (Adj. 3) | — |
| 14 — Pending-spawn integration test | ✅ | `f4f0101` |
| Tick — drain WB pipeline queues | ✅ added per Adj. 3 | `bf53cb4` |
| 15 — Week 2 wrap-up | ✅ | (this commit) |
| 1621 — Week 3: per-instance + animation | pending | — |
| 2228 — Week 4: draw dispatcher + ship | pending | — |
@ -878,6 +883,58 @@ construction in `WbMeshAdapter` (verified working under flag-off).
flag-off visually identical." Routing arrives in Week 2 (Task 11) at
the correct layer. Smoke verification is now: flag-on === flag-off.
---
### Adjustment 3 (2026-05-08): flag-on FPS regression — root-caused, deferred to Task 22
**Discovered during Task 13 stress test** (radius 7, flag-on). Visible
FPS drop + rising frame latency vs flag-off baseline. Initial guess
was the staged-upload queue leaking memory; we shipped
`WbMeshAdapter.Tick()` (commit `bf53cb4`) to drain
`_meshManager.StagedMeshData` + `_graphicsDevice._glThreadQueue` per
frame. Result: leak fixed, but **FPS unchanged**.
**Real cause: dual-pipeline cost.** Flag-on runs both rendering
pipelines in parallel without yet collecting any savings:
1. **Background workers (4-wide).** `ObjectMeshManager` spins up
`MaxParallelLoads = 4` worker threads decoding GfxObj polygons,
building texture atlases, encoding batches. Contends with the
render thread for CPU cores.
2. **Duplicate GL upload.** `Tick()` calls `UploadMeshData` per
staged mesh, creating VAO/VBO/IBO + atlas texture uploads. Real
per-call GL state churn on the render thread.
3. **Duplicate I/O.** `DefaultDatReaderWriter` opens its own dat file
handles and rebuilds its own index cache (~50-100 MB) alongside
our existing `DatCollection`. Memory bandwidth + GC churn.
4. **Legacy renderer keeps doing the same work.** Per Adjustment 2,
`InstancedMeshRenderer` is tier-blind — it still uploads VAO /
VBO / IBO for the same atlas-tier content WB is also building.
**We literally double the prep cost** for every atlas-tier GfxObj.
The savings from WB's atlas batching only materialize when **Task 22
(`WbDrawDispatcher`) lands and the legacy renderer can short-circuit
its upload for atlas-tier content**. At that point WB owns atlas-tier
draw and `InstancedMeshRenderer` skips its own upload + draw work
for those entities. Until then, flag-on pays both costs.
**Decision: do not fix now.** Plan Risk #5 explicitly anticipated this:
> Performance regression during integration of week 1's "atlas for
> static scenery, old path for everything else" mixed state.
> Mitigation: keep the feature gate `ACDREAM_USE_WB_FOUNDATION=1`
> during weeks 1-3; default-off until week 4 visual verification.
Default-off (the user's daily experience) is byte-identical to
pre-N.4. Flag-on is dev-only until Week 4. Task 22 must wire the
legacy-renderer short-circuit for atlas-tier content as part of
landing the dispatcher; the cost cannot be amortized any earlier
without violating Adjustment 2's tier-blind-renderer principle.
`Tick()` stays — it fixed a real memory leak and is required
infrastructure for Task 22 anyway. We just paid for it without
seeing FPS recovery yet.
### Task 6 (original — kept for history)
**Files:**