From 40012510640a560242b0951bd054089cdec34d36 Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 21 Jul 2026 19:33:07 +0200 Subject: [PATCH] docs(plan): define GameWindow slice 5 --- docs/architecture/code-structure.md | 4 + ...mewindow-slice-5-landblock-presentation.md | 362 ++++++++++++++++++ 2 files changed, 366 insertions(+) create mode 100644 docs/plans/2026-07-21-gamewindow-slice-5-landblock-presentation.md diff --git a/docs/architecture/code-structure.md b/docs/architecture/code-structure.md index a9ddf3cd..c32b9a1b 100644 --- a/docs/architecture/code-structure.md +++ b/docs/architecture/code-structure.md @@ -546,6 +546,10 @@ Slice 5 is active next. #### Slice 5 — extract landblock presentation +Detailed execution ledger: +[`docs/plans/2026-07-21-gamewindow-slice-5-landblock-presentation.md`](../plans/2026-07-21-gamewindow-slice-5-landblock-presentation.md) +(active). + Create `LandblockPresentationPipeline` around the existing immutable `LandblockBuild` transaction. It owns DAT build context, scenery/EnvCell construction, mesh/collision/light publication, demotion/full-retirement, and diff --git a/docs/plans/2026-07-21-gamewindow-slice-5-landblock-presentation.md b/docs/plans/2026-07-21-gamewindow-slice-5-landblock-presentation.md new file mode 100644 index 00000000..e274be97 --- /dev/null +++ b/docs/plans/2026-07-21-gamewindow-slice-5-landblock-presentation.md @@ -0,0 +1,362 @@ +# GameWindow Slice 5 — landblock presentation + +**Status:** Active 2026-07-21. +**Parent program:** [`docs/architecture/code-structure.md`](../architecture/code-structure.md), Slice 5. +**Baseline:** `2b10e91e`; `GameWindow.cs` is 10,301 lines, 267 fields, and +163 methods. Release baseline: 6,940 passed / 5 skipped. The connected R6 +baseline passed seven destinations/revisits, three movement/jump/combat +exercises, and graceful close in 310 seconds. +**Behavior rule:** Preserve the accepted two-tier streaming quality, immutable +per-job EnvCell transaction, render-readiness barrier, collision/light/script/ +plugin lifetime, and retryable detach-first retirement. This is an ownership +move. Any proven defect needed to make that boundary safe gets its own oracle, +test, and bisectable commit rather than being hidden in a mechanical move. + +## Progress ledger + +- [ ] A — freeze production order and introduce explicit build/publication + seams plus structural guards. +- [ ] B — capture one immutable world-origin snapshot per load and extract the + serialized DAT-backed `LandblockBuildFactory`. +- [ ] C — extract terrain, visibility, EnvCell, building, and render-pinning + publication. +- [ ] D — extract physics/cache/collision publication and cross-landblock + reflood ownership. +- [ ] E — extract static lighting, translucency, default-script, and plugin + presentation; compose the small `LandblockPresentationPipeline`. +- [ ] F — move retryable demotion/full-retirement presentation under the + pipeline without changing `LandblockRetirementCoordinator`'s ledger. +- [ ] G — cut `StreamingController` and `GameWindow` directly to the pipeline; + delete old bodies, scratch state, and callback facades. +- [ ] H — three-agent corrected-diff review, full Release suite, deterministic + lifecycle gate, seven-destination resource soak, documentation, and durable + memory closeout. + +Every checked checkpoint is committed as a bisectable architectural unit. +Documentation records the exact commit and accepted test count as work lands. + +## 1. Outcome and non-goals + +At slice exit, `GameWindow` only composes and retains the landblock presentation +owner. It no longer builds a landblock from DAT records, hydrates scenery or +interiors, constructs a physics DAT bundle, publishes terrain/cells/collision/ +lights/plugins, or advances presentation retirement. + +The final owner graph is deliberately not a replacement god object: + +```text +StreamingController (tier, region, generation, priority, budget) + │ accepted Loaded / Promoted / Unloaded result + ▼ +LandblockPresentationPipeline (transaction order only) + ├── LandblockBuildFactory worker-only DAT transaction + ├── LandblockRenderPublisher terrain/cells/EnvCell/building/render pins + ├── LandblockPhysicsPublisher terrain/cell/portal/static collision + ├── LandblockStaticPresentation light/translucency/script/plugin projection + └── LandblockRetirementCoordinator detach-first retry ledger + │ + ▼ +GpuWorldState (spatial buckets, pending live projections, tier, AABB) +``` + +`StreamingController` remains the sole desired-tier and stale-generation +authority. `GpuWorldState` remains the sole spatial-bucket and retained-live- +projection owner. `LandblockStreamer` remains the sole worker queue. Existing +renderers, physics/cache registries, lighting, scripts, plugin state, and +`WorldRevealCoordinator` remain independent owners. + +This slice does not change Near/Far radii, mesh or texture quality, world view +distance, portal visibility, GPU frame fences, the draw graph, live-entity +identity, or the update/render phase order. It does not re-port algorithms +already inventoried under `Rendering/Wb` or introduce another DAT reader. + +## 2. Retail, WorldBuilder, and shipped oracles + +The extraction preserves the following retail order and already-shipped +adaptations: + +- `LScape::grab_visible_cells @ 0x00504EC0`: initialize buildings and gather + visible cells before static/dynamic activation; +- `CLandBlock::init_buildings @ 0x0052FD80`; +- `CLandBlock::grab_visible_cells @ 0x0052F460`; +- `CLandBlock::init_static_objs @ 0x00530A40` and + `CLandBlock::init_dyn_objs @ 0x0052F3B0`; +- `CLandBlock::destroy_static_objects @ 0x0052FA50`, + `CLandBlock::Destroy @ 0x0052FAA0`, and + `CLandBlock::release_all @ 0x0052FCF0` for static/building/cell teardown; +- `CObjCell::init_objects @ 0x0052B420` → + `CPhysicsObj::recalc_cross_cells @ 0x00515A30`; acdream's per-landblock + `RefloodLandblock` remains the registered asynchronous adaptation; +- `PView::InitCell @ 0x005A4B70` and + `CEnvCell::find_visible_child_cell @ 0x0052DC50`; +- `CBuildingObj::find_building_collisions @ 0x006B5300`; +- `CPhysicsObj::add_shadows_to_cells @ 0x00514AE0`; +- `SmartBox::UseTime @ 0x00455410`; acdream's shared reveal coordinator is the + asynchronous equivalent of retail's synchronous cell readiness; +- [`worldbuilder-inventory.md`](../architecture/worldbuilder-inventory.md), + especially the one-job/one-complete-EnvCell-transaction and sole-DAT-reader + rules; +- the current render and two-tier streaming digests, including logical + withdrawal before fence-delayed GPU reuse and the prohibition on reducing + view distance as an optimization; +- [`2026-07-13-envcell-landblock-transaction.md`](../research/2026-07-13-envcell-landblock-transaction.md) + and + [`2026-07-18-retail-texture-resource-lifetime-pseudocode.md`](../research/2026-07-18-retail-texture-resource-lifetime-pseudocode.md). + +The named retail decomp is the oracle for lifecycle ordering. The extracted +WorldBuilder code is the oracle for already-ported terrain, scenery, EnvCell, +mesh, and texture mechanics. ACE and the retained WorldBuilder reference tree +are cross-checks, not new dependencies. + +## 3. Load-bearing invariants + +### 3.1 Worker build + +1. One `Load` captures landblock ID, job kind, generation, and the accepted + world-origin center on the update thread. The worker never reads the + update-thread-confined `LiveWorldOriginState`. +2. All DAT reads for one build execute under the existing single-reader gate. + The worker performs CPU work only: no GL call, renderer/physics publication, + registry mutation, or shared pending cell bag. +3. Far reads the heightmap only and emits no static entities, EnvCells, or + physics bundle. Near/Promote emits hydrated stabs, procedural scenery, + interiors, a complete `PhysicsDatBundle`, and one immutable + `EnvCellLandblockBuild`. +4. Every valid EnvCell is present in the visibility transaction even when it + has zero drawable shell submeshes. Geometry IDs retain their current + deterministic arithmetic. +5. Missing records keep the current skip/null semantics. A failed build has no + externally visible partial publication. + +### 3.2 Publication + +For an accepted Near load or promotion, the transaction order remains: + +1. publish terrain and rebase it to the captured origin; +2. commit the complete cell-visibility snapshot and AABB; +3. populate physics data and publish terrain/cells/portals/buildings; +4. commit building registries and the EnvCell renderer transaction; +5. publish static BSP/cylinder collision, lights, translucency, and DAT-static + plugin snapshots; reflood the landblock; +6. publish/merge the canonical spatial bucket in `GpuWorldState`, acquiring + ordinary and synthetic EnvCell render IDs and activating static defaults; +7. only after the pins exist, replay schema-aware EnvCell mesh preparation; +8. only after spatial publication, restore retained live projections. + +Render readiness means Near tier plus real uploaded ownership for every +required ordinary and synthetic EnvCell render ID. Dictionary publication is +not readiness. Duplicate Near completion is a no-op; stale Far cannot erase a +Near layer. Promotion-before-base and the accepted Near-completion-while-Far- +desired synthesis remain `StreamingController` policy. + +### 3.3 Demotion and retirement + +1. `GpuWorldState.DetachNearLayer` or `DetachLandblock` commits first. No + presentation cleanup failure can resurrect spatial membership. +2. Near→Far keeps terrain and all live projections, but removes DAT statics, + static scripts/classification, lights/translucency/plugins, indoor physics, + cell visibility, building registries, and EnvCells. +3. Full retirement additionally releases terrain and full landblock physics. + Persistent player projection is rescued and still-live server projections + are parked for rebucketing with the same identity. +4. The retryable order is exactly: mesh references → static scripts → + classification → entity lights → entity translucency → DAT-static plugin + projection → terrain for Full only → physics remove/demote → cell visibility + → building registry → EnvCell renderer. +5. Successful stages and entity cursors never replay. Same-ID replacement is + fenced until every required stage converges, including across controller or + radius reconfiguration. +6. GPU range/layer reuse remains fence-delayed by `GpuFrameFlightController`. + +## 4. Architecture and interfaces + +### 4.1 Immutable build request + +Introduce a request equivalent to: + +```csharp +public readonly record struct LandblockBuildRequest( + uint LandblockId, + LandblockStreamJobKind Kind, + ulong Generation, + int OriginCenterX, + int OriginCenterY); +``` + +The generation remains for matching and diagnostics; the builder never uses it +to decide residency. Results carry the same origin snapshot so apply cannot +combine worker geometry from one center with render-thread placement from a +later center. + +### 4.2 DAT build factory + +`LandblockBuildFactory` receives `IDatReaderWriter`, the one shared DAT gate, +the immutable height table, and worker-safe mesh/physics-cache collaborators. +Its public surface is one operation: + +```csharp +LandblockBuild? Build(in LandblockBuildRequest request); +``` + +The extracted `BuildPhysicsDatBundle`, stab hydration, scenery generation, +interior/EnvCell construction, and terrain sampling remain private focused +helpers. No method accepts `GameWindow`, `LiveWorldOriginState`, GL objects, or +presentation registries. + +### 4.3 Focused publishers + +`LandblockRenderPublisher` owns terrain slot publication, AABB, cell +visibility, building registries, EnvCell commit, static render pin/preparation, +and a typed immutable diagnostics snapshot. + +`LandblockPhysicsPublisher` owns cache population, terrain/cell/portal/building +physics, static multipart BSP and exclusive Setup-cylinder/sphere fallback, +demotion/full removal, and final cross-cell reflood. + +`LandblockStaticPresentationPublisher` owns idempotent static light and +translucency replacement, static script activation/removal at the existing +spatial registration boundary, and DAT-static plugin spawn/removal projection. + +The pipeline calls concrete publishers in the fixed order. Publish/retire +interfaces expose no DAT reader. Diagnostics move with the owner and the Debug +UI reads a snapshot rather than `GameWindow` scratch counters. + +### 4.4 Policy cutover + +`StreamingController` keeps result validation and calls the pipeline only after +generation/tier/duplicate/retirement-fence checks. It must not gain DAT, GL, +physics, plugin, light, or live-GUID state. `GpuWorldState` keeps its buckets, +pending Near/render-ID parking, and live rescue. Existing hidden presentation +side effects may be made explicit only when tests prove identical ordering and +balance. + +`GameWindow` retains construction/wiring plus one pipeline field. Structural +tests forbid these methods from returning: + +```text +BuildLandblockForStreaming +BuildLandblockForStreamingLocked +BuildPhysicsDatBundle +BuildSceneryEntitiesForStreaming +BuildInteriorEntitiesForStreaming +EnsureEnvCellMeshesAfterPin +ApplyLoadedTerrain +ApplyLoadedTerrainLocked +AdvanceLandblockPresentationRetirement +``` + +## 5. Checkpoint execution + +### A — characterization and seams + +- Add owner-call trace tests around the current production order and payload + multiplicity. +- Introduce immutable build/publication request/result seams and diagnostics + snapshots without changing the live call graph. +- Add dependency/reflection guards: no origin-state read from worker, no DAT in + publish/retire interfaces, no residency/generation map in the pipeline. + +### B — origin snapshot and build factory + +- Capture origin with every enqueued load; preserve it through priority queue, + completion, promotion, and Far synthesis. +- Move the complete serialized DAT build verbatim into + `LandblockBuildFactory`; keep `DatCollection` access behind + `IDatReaderWriter` and the shared gate. +- Test Far/Near payload shape, repeat-build determinism, EnvCell completeness, + physics-DAT closure, missing records, lock serialization, and zero DAT reads + during publication. + +### C — render publication + +- Extract terrain upload/rebase, CellVisibility, AABB, building registry, + EnvCell renderer commit, render-ID pin/preparation, and diagnostics. +- Test visibility-only cells, zero-submesh EnvCells, promotion-before-base, + real render readiness, reapply idempotency, and release balance. + +### D — physics publication + +- Extract physics cache commit, terrain/cell/portal/building setup, static + multipart BSP/cylinder registration, reflood, demote, and full removal. +- Use two adjacent landblocks to prove one retirement cannot erase a neighbor's + seam collision. Pin Far terrain-only, Near reapply, promotion, demotion, and + full unload symmetry. + +### E — static presentation and transaction coordinator + +- Extract static lights/translucency/plugin projection and make existing static + script ownership explicit without replaying defaults. +- Compose the small `LandblockPresentationPipeline`; add exact order, + multiplicity, rollback/blocked-publication, and owner-balance tests. + +### F — retirement + +- Move production retirement callbacks under the pipeline while keeping + `LandblockRetirementCoordinator` as the exact retry ledger. +- Inject failure before/after each boundary; prove only unfinished suffixes and + exact entity cursors resume. Test Near and Full requirements separately. + +### G — direct cutover and cleanup + +- Route `LandblockStreamer` build and `StreamingController` apply/retire calls + directly to the new owners. +- Delete the old GameWindow bodies, callback facades, building-registry and + apply-diagnostic scratch state that now have focused owners. +- Add structural source tests and remeasure GameWindow lines/fields/methods. + +### H — closeout + +- Run focused App/Core/Net tests after each checkpoint. +- After each code checkpoint, run three independent read-only reviews: retail/ + WorldBuilder conformance, architecture/integration, and adversarial failure + analysis. Fix confirmed findings and repeat review until clean. +- Run `dotnet build AcDream.slnx -c Release` and the full Release test suite. +- Run deterministic first-login/portal lifecycle automation and the connected + seven-destination resource soak. Verify retirement/preparation queues drain, + residence matches the region, resource counts remain bounded, and graceful + close succeeds. +- Update the divergence register pointers in IA-14, AD-2, AD-22, and any other + row whose cited ownership moved. Keep AD-6, AD-24, AP-31, and TS-52 unless a + separate retail mechanism actually lands. +- Update architecture, milestones, roadmap, issues if needed, synchronized + `AGENTS.md`/`CLAUDE.md`, and durable decomposition/render memory. + +## 6. Acceptance matrix + +Automated acceptance includes: + +- Far heightmap-only and complete immutable Near/Promote payloads; +- deterministic static/scenery/interior IDs, bounds, offsets, cells, and shell + geometry IDs across repeated builds; +- single-reader DAT gate and no publish-time DAT access; +- exact owner-call sequence for load, promotion, Far synthesis, demotion, full + unload, replacement, and failure retry; +- stale/duplicate completions invoke no presentation participant; +- complete render pins and real readiness before reveal; +- static lights/translucency/scripts/plugins and mesh references remain + balanced through load → reapply → demote → promote → full unload; +- adjacent-landblock collision survives one neighbor's retirement; +- first login with unknown origin starts no build, and stale placeholder/old- + center completions cannot publish or satisfy reveal; +- reflection/source gates keep moved methods and scratch ownership out of + `GameWindow` and keep policy/state out of the pipeline. + +Connected acceptance includes fresh login, outdoor travel, world-edge empty +blocks, dungeon travel, same-location revisit, turn sampling at every stop, +movement/jump/combat exercises, portal reveal, and graceful close. No view- +distance or quality reduction is an acceptable way to pass the resource gate. + +## 7. Known risks and explicit traps + +- A worker must not read a later live origin than the job it is building. +- A Near promotion may complete before its Far base; parking behavior and + required render IDs must survive unchanged. +- Duplicate Near/ForceReload must not stack lights, scripts, plugin events, + meshes, collision, or EnvCells. +- Demotion must never retire live entities or terrain. +- Publication must not be mistaken for uploaded render readiness. +- Failed retirement must survive controller/radius replacement. +- Do not move `WbDrawDispatcher` or any draw-graph body; that is Slice 7. +- Do not reintroduce worker-side `RegisterCell`, global pending renderer lists, + legacy rendering, or another DAT reader. +- Do not reduce Near/Far radii, texture detail, or mesh quality.