19 KiB
GameWindow Slice 5 — landblock presentation
Status: Active 2026-07-21.
Parent program: docs/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.
- A1 landed in
4f965d06: accepted results now enter one stagedLandblockPresentationPipelinetransaction;GpuWorldStatereturns 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.
- A1 landed in
- B — capture one immutable world-origin snapshot per load and extract the
serialized DAT-backed
LandblockBuildFactory.- Landed in
5af101b2:LandblockBuildFactorynow owns the complete serialized worker-side DAT transaction, immutable height-table snapshot, stab/scenery/interior hydration, complete EnvCell build, and physics-DAT bundle.GameWindowonly constructs the owner and passes itsBuildoperation 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 fromLandBlockInfo.NumCells. Three corrected-diff reviews are clean. Release gate: 6,979 passed / 5 skipped;GameWindow.csis 9,573 lines.
- Landed in
- 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
StreamingControllerandGameWindowdirectly 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:
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 @ 0x00530A40andCLandBlock::init_dyn_objs @ 0x0052F3B0;CLandBlock::destroy_static_objects @ 0x0052FA50,CLandBlock::Destroy @ 0x0052FAA0, andCLandBlock::release_all @ 0x0052FCF0for static/building/cell teardown;CObjCell::init_objects @ 0x0052B420→CPhysicsObj::recalc_cross_cells @ 0x00515A30; acdream's per-landblockRefloodLandblockremains the registered asynchronous adaptation;PView::InitCell @ 0x005A4B70andCEnvCell::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, 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.mdand2026-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
- One
Loadcaptures landblock ID, job kind, generation, and the accepted world-origin center on the update thread. The worker never reads the update-thread-confinedLiveWorldOriginState. - 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.
- 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 immutableEnvCellLandblockBuild. - Every valid EnvCell is present in the visibility transaction even when it has zero drawable shell submeshes. Geometry IDs retain their current deterministic arithmetic.
- 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:
- publish terrain and rebase it to the captured origin;
- commit the complete cell-visibility snapshot and AABB;
- populate physics data and publish terrain/cells/portals/buildings;
- commit building registries and the EnvCell renderer transaction;
- publish static BSP/cylinder collision, lights, translucency, and DAT-static plugin snapshots; reflood the landblock;
- publish/merge the canonical spatial bucket in
GpuWorldState, acquiring ordinary and synthetic EnvCell render IDs and activating static defaults; - only after the pins exist, replay schema-aware EnvCell mesh preparation;
- 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
GpuWorldState.DetachNearLayerorDetachLandblockcommits first. No presentation cleanup failure can resurrect spatial membership.- 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.
- 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.
- 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.
- Successful stages and entity cursors never replay. Same-ID replacement is fenced until every required stage converges, including across controller or radius reconfiguration.
- GPU range/layer reuse remains fence-delayed by
GpuFrameFlightController.
4. Architecture and interfaces
4.1 Immutable build request
Introduce a request equivalent to:
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:
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:
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; keepDatCollectionaccess behindIDatReaderWriterand 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
LandblockRetirementCoordinatoras 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
LandblockStreamerbuild andStreamingControllerapply/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 Releaseand 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
GameWindowand 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
WbDrawDispatcheror 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.