acdream/docs/plans/2026-07-21-gamewindow-slice-5-landblock-presentation.md
Erik 801d8a189c docs(architecture): record pipeline-owned retirement
Close Slice 5F in the execution ledger, update the milestone and roadmap measurements, and advance the active checkpoint to direct cutover and dead-facade cleanup.

Co-authored-by: Codex <codex@openai.com>
2026-07-21 22:09:16 +02:00

22 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 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.
  • 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.
  • 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.
  • 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.
  • 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.
  • 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.
  • 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:

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 @ 0x0052B420CPhysicsObj::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, 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 and 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:

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; 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.