acdream/docs/plans/2026-07-22-gamewindow-slice-8-checkpoint-h-resource-ownership.md
Erik c87b15303d refactor(lifetime): own render resource prefixes
Give terrain, sky, retained UI, portal preparation, and the update/render frame pair explicit single owners. Make shader, texture, text, bindless, and GL construction prefixes checked and retryable so partial failure cannot lose or replay resource ownership.

Co-authored-by: Codex <codex@openai.com>
2026-07-22 14:42:14 +02:00

11 KiB

GameWindow Slice 8 — Checkpoint H Resource Ownership Plan

Objective

Replace the remaining implicit, constructor-local, and nullable-field resource ownership in GameWindow with explicit single-owner transactions. This is a behavior-preserving lifetime cutover: draw/update order, portal presentation, retained UI behavior, settings behavior, and the frozen shutdown order do not change.

Checkpoint H does not move the complete shutdown manifest; that is Checkpoint J. It supplies the typed roots and retry semantics that J will consume.

Frozen invariants

  1. Every GL/kernel-owning object is published to exactly one owner immediately after successful construction.
  2. Renderers borrow the terrain atlas and dedicated sky shader. They never dispose those resources. The renderer borrower retires before the sole resource owner releases the borrowed atlas/shader.
  3. A leaf factory that throws before returning must release every GL name it created. Outer phase/lifetime rollback cannot recover a name that was never published.
  4. Bindless residency is a resource prefix: partial handle acquisition rolls back, successful non-residency is not replayed after a later failure, and textures are not deleted while a resident handle remains.
  5. Retained UI input is quiesced before the live-session barrier and physically detached afterward, independently of runtime/host disposal.
  6. Exactly one retained-UI ownership chain exists. Before runtime construction the lease owns the UiHost; after a runtime object is published the lease retains that exact partial/full runtime and delegates final disposal to it. Constructor or initialization failure never leaves a host in a local only.
  7. Update and render roots publish atomically as one frame graph. Silk callbacks resolve only through the slot; an empty/withdrawn slot is safely inert.
  8. Portal tunnel ownership starts in one fallback slot. Transfer clears the fallback only after the complete local-teleport controller factory returns; any earlier failure leaves the same tunnel reachable by shutdown.
  9. Successful disposal is never replayed. Failed disposal remains retryable. Terminal abandonment is explicit and retains references for native/process fallback rather than silently losing ownership.

Typed owners

OwnedResourceSlot<T> and GameRenderResourceLifetime

  • OwnedResourceSlot<T> acquires through a factory, rejects duplicate publication, exposes a typed borrower, and clears ownership only after Dispose succeeds.
  • GameRenderResourceLifetime contains separate slots for TerrainAtlas and the dedicated sky Shader.
  • GameWindow acquires both through this owner. Shutdown releases the sky renderer and terrain renderer first, then the two resource slots, then GL.

RetailUiRuntimeLease

  • The lease exists before OnLoad and acquires the UiHost through a factory.
  • Runtime construction and initialization are separate. The runtime is stored in the lease before initialization begins.
  • Initialization failure attempts immediate cleanup. Successful cleanup rethrows the original initialization error; cleanup failure reports both and leaves the exact runtime in the lease for lifetime retry.
  • The lease exposes idempotent QuiesceInput and retryable DeactivateInput operations against the retained host.
  • If no runtime was constructed, lease disposal directly disposes the host. If a runtime exists, runtime disposal remains the one path that disposes the host. There are never two host disposers.
  • Explicit terminal abandonment is allowed only after a failed disposal pass; it makes later disposal inert while retaining the unresolved references for the native/process fallback handled by Checkpoint J.

GameFrameGraphSlot

  • IGameUpdateFrameRoot and IGameRenderFrameRoot are the narrow frame seams.
  • UpdateFrameOrchestrator and RenderFrameOrchestrator implement those seams.
  • Publish(update, render) is atomic and one-shot until withdrawal.
  • Tick/Render are inert before publication and after withdrawal.
  • Shutdown withdraws the pair only after live-session convergence and before any frame borrower retires.

TransferableResourceSlot<T>

  • Acquire owns one fallback resource.
  • Transfer(factory) retains fallback ownership while the complete destination factory runs and clears it only after that factory returns successfully.
  • Failed acquisition, resource preparation, destination construction, binding, and normal shutdown prefixes are covered without double disposal.

Exception-safe GL leaves

Shader program construction

ShaderProgramConstruction uses a narrow IShaderProgramBuildApi. It tracks the vertex shader, fragment shader, and linked program as soon as each name is returned. Compile, attach, link, detach, or cleanup failure attempts every remaining rollback operation. A clean rollback preserves the original failure; rollback failures are aggregated with it. Shader.Dispose becomes idempotent and retryable.

Terrain atlas construction and residency

GlTextureConstructionTransaction tracks every texture name allocated by terrain, alpha, and fallback paths. TerrainAtlas.Build, BuildAlphaAtlas, and BuildFallback allocate only through that transaction. The transaction commits only after the complete TerrainAtlas object exists; failure rolls back all names in reverse acquisition order and reports cleanup failures with the original error.

BindlessTexturePair owns the terrain/alpha residency pair. Partial acquisition releases the first handle. Release attempts both handles, remembers successful substeps, and is retryable. TerrainAtlas.Dispose releases residency first, then independently deletes both texture names through the existing retryable shutdown transaction.

Production integration order

  1. Construct render-resource lifetime, UI lease, frame slot, and portal fallback slot before Window.Create/OnLoad acquisitions.
  2. Acquire the atlas through GameRenderResourceLifetime; construct TerrainModernRenderer as a borrower.
  3. Acquire UiHost through RetailUiRuntimeLease, wire input/assets, construct and publish RetailUiRuntime before initialization, then expose host/runtime only as borrower aliases.
  4. Acquire/prepare portal presentation in the fallback slot.
  5. Acquire the dedicated sky shader through the render-resource lifetime, then construct SkyRenderer as a borrower.
  6. Transfer the portal fallback only through the complete LocalPlayerTeleportController factory.
  7. Construct update/render roots as locals and publish them atomically through GameFrameGraphSlot.
  8. Update/render callbacks resolve only through the slot.
  9. Shutdown quiesces/deactivates UI through the lease, withdraws frame roots, disposes the UI lease and renderer borrowers, releases atlas/sky roots, then reaches GL.

Automated gates

  • owned-resource acquire/borrow/release, duplicate acquire, transient failure, retry, and no replay;
  • UI host acquisition, runtime constructor failure, Initialize failure with successful cleanup, transient cleanup then lifetime retry, persistent cleanup then explicit abandonment, separate input quiescence/deactivation, and exact host disposal count;
  • frame pair atomic publication, duplicate publication, update/render routing, withdrawal, pre-publication/post-withdrawal silence, and republish policy;
  • portal fallback acquisition/preparation/transfer failures plus successful transfer and every shutdown prefix, with exact one-resource disposal;
  • vertex/fragment compile failure, program creation/link failure, and cleanup failure after every created shader/program name;
  • terrain/alpha/fallback texture allocation and upload failure after every created name, reverse rollback, cleanup aggregation, and committed-name non-deletion;
  • bindless first/second acquisition failure, release partial failure/retry, and no deletion before residency release;
  • source boundary proves direct frame-root fields, constructor-local atlas/sky ownership, direct UI host/runtime ownership, and nullable portal fallback are removed from GameWindow;
  • focused App tests, production App Release build, solution Release build, and complete Release suite.

Review and commit gate

The existing architecture/integration, retail-conformance, and adversarial reviewers inspect the complete tracked and untracked diff independently. Every confirmed finding is corrected and all three repeat until clean. Architecture, roadmap, milestones, issues, memory, CLAUDE.md, and AGENTS.md are reconciled in the same bisectable Checkpoint H commit.

Implementation result — 2026-07-22

Checkpoint H landed the planned ownership graph without changing the accepted update/render order or gameplay behavior:

  • GameRenderResourceLifetime owns the terrain atlas and dedicated sky shader; renderers borrow them and retire first.
  • RetailUiRuntimeLease owns Host/runtime construction, initialization, input cutoff, retryable disposal, and explicit terminal abandonment as one chain.
  • GameFrameGraphSlot publishes update/render roots atomically and makes empty or withdrawn callbacks inert.
  • TransferableResourceSlot.AcquirePrepared publishes the portal presentation before mesh preparation, retries the same partial resource, and refuses transfer until preparation has committed.
  • GL name creation records the returned name before post-command validation. Shader/program, texture, text-renderer, buffer/VAO, bindless-residency, and texture-binding operations advance ownership only after an always-on checked GL commit boundary.
  • Failed construction cleanup is retained by GlConstructionCleanupLedger; exact pending names and binding-restore obligations remain retryable, while successful cleanup stages never replay.

Four corrected-diff cycles closed issues found by the architecture, retail, and adversarial reviewers: partial bindless mutation, lost GL rollback names, constructor-local text resources, reentrant UI ownership, portal preparation, unchecked production GL calls, leaked texture binding, and the bindless post- commit verification gap. All three final reviews are clean.

Automated acceptance:

  • focused Checkpoint H ownership/lifetime gate: 61 passed;
  • App Release suite: 3,236 passed / 3 intentional skips;
  • production App Release build: zero warnings and zero errors;
  • complete solution Release suite: 7,606 passed / 5 intentional skips; solution build has zero errors and the 17 existing test-project warnings tracked by #228;
  • GameWindow.cs: 3,689 raw lines / 162 fields / 37 methods, versus 15,723 / 278 / 205 at the campaign baseline.

No AC-specific algorithm or intended retail behavior changed, so Checkpoint H adds no retail-divergence row. Checkpoint I is the next active unit.