acdream/docs/plans/2026-07-21-gamewindow-slice-5-landblock-presentation.md
Erik db3ca59fd0 docs(architecture): record landblock cutover
Mark Slice 5G complete, record the 44 percent GameWindow reduction and origin-lifetime barrier, and advance the active ledger to deterministic and connected closeout.
2026-07-21 22:50:38 +02:00

451 lines
23 KiB
Markdown

# 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
- [x] A — freeze production order and introduce explicit build/publication
seams plus structural guards.
- A1 landed in `4f965d06`: accepted results now enter one staged
`LandblockPresentationPipeline` transaction; `GpuWorldState` returns an
exact spatial-publication receipt before retryable render-pin/script
activation; retries preserve FIFO suffixes, pending-live merges, and
promotion identity across policy/generation changes. Three corrected-diff
reviews are clean. Release gate: 6,955 passed / 5 skipped.
- A2 landed in `090b0354`: every production load now captures a specified
immutable origin (including valid map origin `(0,0)`) and carries it through
worker construction, priority/supersession, completion, Far normalization,
and publication. Origin-less or mismatched request-aware work fails before
it can publish. Three corrected-diff reviews are clean. Release gate: 6,965
passed / 5 skipped.
- [x] B — capture one immutable world-origin snapshot per load and extract the
serialized DAT-backed `LandblockBuildFactory`.
- Landed in `5af101b2`: `LandblockBuildFactory` now owns the complete
serialized worker-side DAT transaction, immutable height-table snapshot,
stab/scenery/interior hydration, complete EnvCell build, and physics-DAT
bundle. `GameWindow` only constructs the owner and passes its `Build`
operation to the streamer; it no longer contains any build helper body.
Hermetic tests pin Far/Near/Promote shape, zero-submesh visibility cells,
missing-record behavior, the exact supplied DAT gate, non-interleaved full
Near transactions, exception recovery, and immutable inputs. The installed
Holtburg gate independently derives visibility completeness from
`LandBlockInfo.NumCells`. Three corrected-diff reviews are clean. Release
gate: 6,979 passed / 5 skipped; `GameWindow.cs` is 9,573 lines.
- [x] C — extract terrain, visibility, EnvCell, building, and render-pinning
publication.
- Landed in `1029f837`: `LandblockRenderPublisher` owns captured-origin
terrain publication, complete visibility-cell transactions, AABBs,
building registries, EnvCell commit/preparation, and matching retirement
operations. Invalid EnvCell identities fail before any presentation
mutation; visibility-only cells and zero-submesh transactions remain
first-class. Tests pin building-before-EnvCell order, duplicate and failed
completion behavior, foreign receipts, post-pin preparation, real
publish-to-retire balance, and hidden AABB ownership. Three corrected-diff
reviews are clean. Release gate: 6,988 passed / 5 skipped;
`GameWindow.cs` is 9,490 lines.
- [x] D — extract physics/cache/collision publication and cross-landblock
reflood ownership.
- Landed in `3613d393`: `LandblockPhysicsPublisher` now owns captured-origin
terrain/cell/portal/building physics, cached GfxObj shapes, mutually
exclusive multipart BSP or Setup fallback collision, exact static-owner
replacement, final reflood, demotion, and full removal. Every Setup shape
shares the retail logical entity ID; building shells remain exclusive to
the per-landcell building channel. Reapply retires stale cell/building/UCG
state and omitted static owners by landblock prefix, rebinds `CurrCell` to
the replacement object, and preserves adjacent owners even when collision
footprints cross the seam. Three corrected-diff reviews are clean. Release
gate: 7,008 passed / 5 skipped; zero-warning build; `GameWindow.cs` is
9,043 lines.
- [x] E — extract static lighting, translucency, default-script, and plugin
presentation; compose the small `LandblockPresentationPipeline`.
- Landed in `ea0da8c8`: render, physics, and DAT-static publishers now
preflight immutable receipts before mutation and retain exact per-stage
progress through retry. `LandblockStaticPresentationPublisher` owns static
lights, translucency lifetime, and plugin projection; `GpuWorldState` and
`EntityScriptActivator` explicitly rebind same-ID static snapshots without
replaying Setup defaults or resetting animation/light/translucency state,
while omitted IDs take full teardown. Source changes and duplicate IDs fail
before presentation mutation, missing DefaultAnimation never claims an
owner, and fallible reconciliation precedes pending-map consumption. Three
corrected-diff reviews are clean. Release gate: 7,026 passed / 5 skipped;
`GameWindow.cs` is temporarily 9,073 lines because production wiring lands
before checkpoint G deletes the old callback body.
- [x] F — move retryable demotion/full-retirement presentation under the
pipeline without changing `LandblockRetirementCoordinator`'s ledger.
- Landed in `ea7ffbc1`: the pipeline now owns a focused
`LandblockPresentationRetirementOwner` and its exact detach-first ledger;
`GameWindow` no longer owns a coordinator or retirement callback body.
Full and Near retirements preserve their distinct terrain/physics/live
owner matrices, successful stages and entity cursors never replay, and
reentrant Begin/Advance requests serialize without mutating an active
ticket enumeration. A committed visibility-observer failure retains the
detach receipt, completes presentation teardown, drains deferred work,
then rethrows. Constructor identity guards enforce one world-state,
publisher, lighting, and translucency graph, while explicit concrete and
legacy controller overloads make ignored presentation callbacks
impossible. Three corrected-diff reviews are clean. Release gate: 7,039
passed / 5 skipped; build green with 17 pre-existing test-project warnings;
`GameWindow.cs` is 9,011 lines.
- [x] 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 without changing
the live call graph. The typed apply-diagnostics snapshot moves with its
concrete render owner in C instead of creating a temporary A-stage owner.
- 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.
**Complete in `c79d0a49`.** Production constructs one concrete pipeline;
legacy callback constructors are internal test seams and structural tests keep
them out of the composition root. The old GameWindow apply methods and their
building/diagnostic scratch state are deleted. A retained
`StreamingOriginRecenterCoordinator` now joins complete old-window
presentation retirement to teleport recenter and session cancellation before
the shared origin may be reused. Reviews closed full-window overlap, radius
reconfiguration, sealed-dungeon bootstrap, committed-callback retry,
reentrancy, fast-relogin, and ordinary-logout gaps. Three final reviews are
clean; the zero-warning Release build and 7,052 tests pass with five skips.
`GameWindow.cs` is 8,793 raw lines, 1,508 below the Slice-5 baseline and 6,930
(44%) below the campaign baseline.
### H — closeout
**Active.** No new landblock ownership moves are planned in H; it proves the
landed graph under deterministic and connected lifecycle churn, reconciles
divergence pointers/documentation, and records the final Slice-5 structural
measurement before Slice 6 begins.
- 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.