acdream/docs/plans/2026-07-22-gamewindow-slice-8-composition-lifecycle.md
Erik 5c955c36ec docs(architecture): record GameWindow automated closeout
Reconcile the architecture, roadmap, milestones, issue ledger, session instructions, and durable memory with the 1,622-line native shell, canonical soak evidence, connected framebuffer comparison, and sole remaining user visual gate.

Co-authored-by: Codex <codex@openai.com>
2026-07-22 20:52:25 +02:00

792 lines
44 KiB
Markdown

# GameWindow Slice 8 — composition and lifecycle shell
**Status:** Automated implementation and closeout complete 2026-07-22; final
user visual matrix pending.
**Parent program:** [`docs/architecture/code-structure.md`](../architecture/code-structure.md), Slice 8.
**Baseline:** `96f8bfcf`; `GameWindow.cs` is 4,666 raw lines, 196 fields, and
70 methods. The Release suite passes 7,341 tests / 5 fixture or environment
skips. The connected lifecycle/reconnect and synchronized nine-stop soak gates
pass.
**Behavior rule:** This is the final behavior-preserving ownership slice. It
must not change accepted input priority, session/reset behavior, update/render
order, world presentation, quality settings, or retail gameplay behavior. It
may fix resource and callback lifetime defects proven by this slice's ownership
audit.
## Progress ledger
- [x] A — freeze construction/callback/shutdown order and delete proven dead or
test-facade residue.
- [x] B — make native-window callback intake an explicit reversible owner and
define host-quiescence failure semantics.
- [x] C — extract live-session host/reset/binding callbacks and verify the
embedded skill formula against named retail.
- [x] D — extract retail world-environment/day/weather behavior.
- [x] E — extract two-phase raw pointer/camera input, focus, and framebuffer
resize behind symmetric named subscriptions.
- [x] F — extract the sole gameplay input-action router plus focused combat and
diagnostic command owners.
- [x] G — extract two-phase persisted settings/display/quality ownership.
- [x] H — give terrain atlas, sky shader, retained `UiHost`, and both frame roots
explicit single ownership and transfer seams.
- [x] I — group `OnLoad` into small ordered, fakeable composition phases with
transactional partial-acquisition rollback. Production and failure tests now
invoke the same pipeline and carry exact typed prior results. Detailed plan:
[`2026-07-22-gamewindow-slice-8-checkpoint-i-ordered-composition.md`](2026-07-22-gamewindow-slice-8-checkpoint-i-ordered-composition.md).
- [x] J — move the exact retryable shutdown manifest to a focused lifetime
owner and prove all partial-load/reentrant/retry paths. Detailed plan:
[`2026-07-22-gamewindow-slice-8-checkpoint-j-lifetime-shutdown.md`](2026-07-22-gamewindow-slice-8-checkpoint-j-lifetime-shutdown.md).
- [x] K — in separate #232 commits, add canonical owner snapshots to every soak
checkpoint without weakening the process-memory guard. Detailed plan:
[`2026-07-22-gamewindow-slice-8-checkpoint-k-canonical-soak.md`](2026-07-22-gamewindow-slice-8-checkpoint-k-canonical-soak.md).
- [ ] L — corrected-diff reviews, focused and full Release gates, connected
lifecycle/soak, framebuffer comparison, documentation, and memory are
complete; only the final user visual handoff remains. Detailed plan:
[`2026-07-22-gamewindow-slice-8-checkpoint-l-closeout.md`](2026-07-22-gamewindow-slice-8-checkpoint-l-closeout.md).
Checkpoint K leaves `GameWindow.cs` at 1,622 raw lines. The focused lifetime
owner holds the typed shutdown roots, exact 18-stage dependency manifest,
retry/no-replay state, structured soft-cleanup failures, hard-barrier fallback,
and native-window-last release. Deferred same-frame checkpoint capture and two
fresh-process nine-stop routes close #232 without weakening the residency
guard. App Release passes 3,451 tests / 3 skips and the complete Release suite
passes 7,823 / 5 skips. All automated Checkpoint-L gates are complete.
Each checked checkpoint lands as a bisectable commit. A checkpoint is not done
while a new class delegates a substantial body back into `GameWindow`, stores
`GameWindow`, or replaces 196 fields with one runtime/service-locator bag.
## 1. Outcome and non-goals
At slice exit `GameWindow` is the native construction shell:
```text
GameWindow
├── creates Window + GL + input context
├── invokes ordered composition functions
├── forwards tiny Silk callbacks to typed owners
├── starts LiveSessionController last
└── asks GameWindowLifetime to close synchronously
```
The shell may retain injected Core/UI game-state roots whose replacement would
be the separately deferred `GameEntity`/client-state aggregation migration. It
must not retain AC algorithms, entity scans, session binding/reset bodies,
settings algorithms, the input switch, anonymous device subscriptions, or the
shutdown operation manifest.
This slice does **not**:
- alter the frozen twelve-phase update graph or ten-phase render graph;
- port a new retail host loop or retire TS-53;
- change input bindings, mouse feel, focus/reset semantics, camera formulas,
resize/DPI behavior, frame pacing, VSync, MSAA, quality, draw distance, or
resource budgets;
- change live-session authority, packet parsing, reset order, the 35-second
graceful F653 confirmation, reconnect, or transport teardown;
- change UI layout, magic, portal presentation, selection, world rendering, or
gameplay behavior;
- fold full `GameEntity` aggregation, headless mode, Linux support, or GPU
particle work into this campaign;
- loosen the soak's working/private-memory threshold.
## 2. Frozen host behavior
No fresh retail research is required for the host boundaries. One audited
exception exists: the embedded skill-formula helper currently cites ACE only,
so Checkpoint C must complete the named-retail pseudocode/cross-reference/test
workflow before moving or claiming that formula.
### 2.1 Startup and callbacks
Preserve this exact order:
1. before `Window.Create`, load persisted Display and resolve VSync/MSAA because
these are native-context attributes;
2. create the window and bind `DisplayFramePacingController` immediately;
3. register Load, Update, main Render, pacing Render, Closing, FocusChanged,
Move pacing, StateChanged pacing, and FramebufferResize in that order;
4. in `OnLoad`, acquire GL and input from the native window;
5. compose camera/DAT/effects/audio, persisted settings, optional devtools,
world/render resources, retained UI, live presentation, streaming, session,
teleport, then update/render roots;
6. call `LiveSessionController.Start` last, including when live mode or
credentials are absent, because its disabled path still performs the
canonical reset.
Main Render remains registered before pacing so the wait occurs after all frame
work and before Silk's swap. Direct raw MouseMove remains ordered after
SilkKeyboard/MouseSource + InputDispatcher subscriptions and before retained
`UiHost` device wiring.
### 2.2 Input, focus, and resize
Preserve the exact input-action priority:
1. pointer press/release transitions;
2. scroll;
3. combat press/release transitions;
4. Press/DoubleClick gate;
5. retained UI semantic actions;
6. selection interactions;
7. pressed movement/autorun;
8. the remaining UI/debug/player/combat/escape commands.
Mouse capture remains three distinct operations:
- focus loss and camera-mode exit use lifecycle end and may publish a final
movement update;
- session reset uses reset semantics and cannot send into the ending session;
- process close retires the session first, then releases presentation without
an active session.
FramebufferResize continues to ignore non-positive sizes, then updates GL
viewport, `ViewportAspectState`, camera aspect, and the forced devtools layout
reset. It must not mutate retained `UiRoot` size; the next UI draw does that.
### 2.3 Session and reset
`LiveSessionController` remains the only session lifecycle owner. The extracted
host adapter may hold typed binding/reset collaborators but no second session,
generation, identity, GUID, routing, or command map.
Preserve `LiveSessionResetManifest` exactly: projection/capture/UI first;
equipped projection before the canonical live runtime; live-runtime convergence
before identity reset; pending effects/hooks/presentation last. The AC skill
formula currently embedded in the window moves only after Checkpoint C adds its
missing named-retail citation and conformance tests (or records the proven
remaining adaptation).
### 2.4 Update and render
`OnUpdate` remains profiler scope plus one immutable
`UpdateFrameOrchestrator.Tick`. `OnRender` takes the existing one window-size
snapshot and calls one `RenderFrameOrchestrator.Render`. PView's
`FramebufferSize` source remains distinct from the logical Window.Size used by
the frame/UI/portal/screenshot path. TS-33 and TS-53 remain registered.
### 2.5 Shutdown
Closing synchronously completes the retryable transaction while the GL context
is current. Direct `Dispose` is the constructed-never-run, Run-failure, and
retry fallback. The native window is disposed only after successful completion
or the explicitly reported last-resort incomplete fallback described below.
The frozen dependency order is:
1. set a no-throw host-quiescence gate and converge live-session shutdown as
independent all-attempted **hard** operations; this guarantees F653 and
transport teardown before dependents;
2. attempt physical detachment of all window/Silk/UI ingress as explicitly
reportable **soft** operations: retry them, record any persistent failure,
but do not block now-safe owner/GPU teardown after quiescence;
3. withdraw **both** update and render frame graphs through their shared slot;
4. retire session/UI dependents;
5. clear the live runtime while teardown callbacks remain alive;
6. retire live dependents;
7. wait for submitted GPU work;
8. retire private/render frontends;
9. retire shared textures;
10. retire the mesh adapter;
11. retire remaining render borrowers, including terrain renderer;
12. retire separately owned borrowed assets, including terrain atlas and sky
shader, only after their respective renderer stage converges;
13. retire remaining render owners and frame-flight ownership;
14. close DAT mappings;
15. dispose the already-detached input context;
16. dispose GL last;
17. return to `GameWindow.Dispose`, which disposes the native window.
The logical quiescence gate makes callbacks inert even when a physical event
unsubscribe reports failure. `ResourceShutdownTransaction` therefore gains an
explicit non-blocking/reportable operation policy for physical detach only.
Those failures remain in the final result and are never swallowed, but they do
not strand unrelated resources. Session convergence and GPU drain remain hard
barriers. Operations within a stage remain independent and all-attempted. Later
stages remain protected until the current stage converges. Successful operations are
never replayed on retry or reentrant completion.
Silk's `Closing` event is not cancellable. `OnClosing` therefore performs the
first synchronous completion attempt and reports an incomplete transaction
without pretending success. The outer `Dispose` retries the same retained
transaction. If a persistent native/driver failure still prevents convergence,
the native window/context is disposed as the explicit last-resort safety net
and the incomplete result is logged with the blocked stage; it is never
reported as a clean shutdown. Tests pin this fallback instead of assuming a
close-cancellation mechanism Silk does not provide.
The lifetime state is explicit: `Active`, `RetryableIncomplete`, clean
`Complete`, `CompleteWithCleanupFailures`, or terminal `AbandonedIncomplete`.
Persistent soft-detach failure followed by otherwise complete teardown produces
`CompleteWithCleanupFailures`; repeated Dispose is inert and returns the same
immutable non-clean report. Hard-barrier retry is allowed only while the native
context is retained. Last-resort native disposal moves to
`AbandonedIncomplete`; every later Dispose is inert and reports the retained
blocked-stage result without touching GL, callbacks, or the already-destroyed
window.
## 3. Architecture and interfaces
### 3.1 Native window intake
Add a focused reversible binding equivalent to:
```csharp
internal sealed class SilkWindowCallbackBinding : IDisposable
{
public static SilkWindowCallbackBinding Create(
IWindow window,
WindowCallbackTargets targets,
DisplayFramePacingController pacing);
public void Attach();
}
```
`WindowCallbackTargets` is a fixed typed set for Load, Update, Render, Closing,
FocusChanged, and FramebufferResize. It is not a general callback list. The
binding is published into its lifetime slot after `Create` and before `Attach`
begins, so even an attach failure plus rollback failure retains a cleanup owner.
Attach treats the current edge as possibly acquired before calling a custom
event add accessor and rolls back in reverse order if registration fails.
The explicit lifecycle state monitor is released around every external event
accessor; external cleanup waits for Attaching/Detaching to converge, while a
gate-owned reentrant cleanup reports typed deferred completion instead of
deadlocking or falsely succeeding. Dispose detaches in reverse registration
order, joins concurrent callers, is idempotent/reentrant-safe, and makes later
window events inert. `GameWindow` clears the retained slot only after terminal
disposal. The separate pacing Render callback remains ordered after main Render.
Every callback enters the shared no-throw host-quiescence monitor, so external
shutdown drains an admitted callback, Closing can stop reentrantly, and a failed
physical unsubscribe cannot re-enter a retired owner.
### 3.2 Input owners
Use two focused owners:
- `CameraPointerInputController` owns named raw MouseMove subscriptions,
pointer position, camera-mode cursor transitions, focus loss, typed scroll
operations, and the existing mode-specific sensitivities. Construction is
deliberately two-phase: `AttachRaw` runs after dispatcher/source wiring and
before retained `UiHost` wiring; `BindGameplayFrame` fills a focused deferred
slot after `GameplayInputFrameController` exists. It consumes canonical
`LocalPlayerModeState`, `ChaseCameraInputState`, input capture, and camera
owners.
- `GameplayInputActionRouter` is the **only** gameplay subscriber to
`InputDispatcher.Fired` and owns the frozen priority graph above. Its pointer
and scroll edges call typed operations on `CameraPointerInputController`; the
pointer owner does not independently subscribe to `Fired`. The router calls existing typed
retained-UI, selection, movement, player-mode, interaction, combat-command,
and diagnostic-command owners.
The window binding, router Fired subscription, pointer raw MouseMove,
Combat/Camera events, dispatcher-to-source links, source-to-Silk links, and
retained-UI-to-Silk links must all be named and reversible. Shutdown's first
stage deactivates them without disposing the input context or the UI owners
needed by session reset. `UiHost` therefore exposes a separate idempotent input
deactivation seam; its later full Dispose owns windows and rendering. Do not
replace these edges with anonymous lambdas retained by a generic subscription
bag.
Every wrapper on those edges—including Silk sources, dispatcher, raw pointer,
retained UiHost, and optional devtools input—checks the same quiescence gate (or
performs its no-throw logical deactivate in stage 1). No device callback can
re-enter during the potentially 35-second live-session close while physical
event removal waits for the following soft-detach stage.
Every multi-event Attach is transactional: if the Nth subscription fails, the
already-attached prefix is removed in reverse order before the exception
escapes. This applies to native window, dispatcher/source/Silk, raw pointer,
Combat/Camera, retained `UiHost`, and optional devtools input bindings.
### 3.3 Session and world environment
- A focused App session host owns the current selection/entered/reset/binding
factories around `LiveSessionLifecycleHost`, `LiveSessionEventRouter`, and
`LiveSessionResetManifest`. It resolves the current session through
`LiveSessionController`; it does not mirror it.
- `WorldEnvironmentController` owns current loaded sky/day state,
`AdminEnvirons`, provider refresh, WorldTime synchronization, and Weather
changes. The existing retail day-group/weather algorithm moves verbatim.
- A named skill-credit resolver owns the formula formerly nested in the session
binding only after Checkpoint C verifies it against named retail; until then
the current ACE-only interpretation is not overclaimed as retail-faithful.
### 3.4 Settings and diagnostics
`RuntimeSettingsController` owns SettingsStore, active toon key, persisted
display/audio/gameplay/chat/character values, quality reapply, and UI
lock/FPS/combat preferences. It is constructed before `Window.Create` and is
the single source for one immutable startup snapshot and Display/VSync/MSAA.
After GL/input sources/input dispatcher/camera/audio exist—but still before
devtools, the world render dispatcher, terrain, retained UI, or streaming—it
applies window/pacing/audio values and resolves the final quality snapshot.
Later factories consume that resolved snapshot. A fixed
typed `RuntimeSettingsTargets` implementation is late-bound only to support
future runtime changes; binding it does not replay startup display or quality
transitions. The controller exposes typed state/commands to existing consumers;
it does not own those renderers, UI trees, or session state.
`RuntimeDiagnosticCommandController` owns command routing for collision wires,
time/weather, sensitivity, and nearby-world dumps. State remains solely in its
canonical owner: time/weather operations call `WorldEnvironmentController`,
sensitivity calls `CameraPointerInputController.AdjustSensitivity`, and world
queries use canonical runtime views. It creates no duplicate state or entity
cache. Diagnostic algorithms do not remain in the window merely because their
input actions originate there.
`FramebufferResizeController` is a separate GL-aware typed target. It owns the
frozen viewport → `ViewportAspectState` → camera aspect → devtools layout-reset
sequence and is not folded into the pointer owner.
### 3.5 Frame-root slots
A focused `GameFrameGraphSlot` is the sole publication point for the current
`UpdateFrameOrchestrator` and `RenderFrameOrchestrator`. The two Silk stubs
resolve through this slot; `GameWindowLifetime` withdraws it after session
convergence and before borrowed owners retire. Clearing a lifetime snapshot is
not withdrawal while a direct window field can still invoke the graph.
### 3.6 Ordered composition
`OnLoad` invokes the narrow production platform phase that owns
`GL.GetApi(_window)` and `_window.CreateInput()`, then invokes small composition
functions at these existing boundaries:
1. host input/camera;
2. content/effects/audio;
3. persisted settings/devtools;
4. world/render resources;
5. interactions/retained UI;
6. live presentation/landblock publishers;
7. streaming/session/local player/teleport;
8. update/render roots;
9. session start.
Each function receives only typed dependencies and may return a small immutable
result needed by the next function. There is no stored mega-context. Stable
owners are copied to locals before retained delegates are constructed so those
delegates cannot capture `GameWindow` accidentally. Construction cycles use
the existing focused deferred slots; they do not use callbacks into the window.
The production ordering lives in a fixed `GameWindowCompositionPipeline` with
typed phase interfaces/factories. Tests invoke that same pipeline with fake
platform/acquisition phases; no test-only clone or source-text assertion stands
in for partial-load execution. A narrow production
`GameWindowPlatformAcquisition` phase is invoked by `OnLoad` and alone calls
`GL.GetApi(window)` and `window.CreateInput()` through injectable factories. It
publishes GL to the lifetime before attempting input, then publishes input
before later phases. Tests execute this exact method with fake factories,
including GL success followed by input failure. Each concrete phase uses a
transactional acquisition scope so failure after any internal acquisition
unwinds the exact prefix.
Every acquired disposable is published to the lifetime owner immediately. A
composer that cannot publish incrementally must unwind its own partial prefix
before throwing. The portal-tunnel transfer remains explicit: publish fallback,
prepare GL resources, transfer to `LocalPlayerTeleportController`, then clear
the fallback before frame graphs are published.
Retained UI uses one `RetailUiRuntimeLease` retained by `GameWindowLifetime`
from Host construction through final teardown. The lease initially owns
`UiHost`, retains a partially constructed `RetailUiRuntime` before Initialize,
and exposes the runtime as a borrower only after Mount succeeds. Constructor
failure leaves Host in the lease; Initialize/cleanup failure leaves the exact
partial runtime in the lease for later retry; success makes lease disposal call
runtime disposal. There is no fallback-to-runtime ownership gap and never two
independent host disposers.
### 3.7 Lifetime owner
`GameWindowLifetime` owns the one `ResourceShutdownTransaction` and typed
teardown-only roots. It does not expose runtime service lookup or gameplay
operations. It exists before `Window.Create`, so window/bootstrap acquisitions
can publish their ownership immediately. Its transaction preserves §2.5 and
fixes these audited gaps:
- update and render orchestrators are both withdrawn;
- `TerrainAtlas` residency/textures are explicitly released after submitted
work and before GL;
- the dedicated sky `Shader` is explicitly disposed;
- a published `UiHost` is disposed even if `RetailUiRuntime.Mount` fails;
- window, dispatcher, pointer, combat, camera, and UI-root callbacks detach
before the owners they target.
## 4. Detailed checkpoint execution
### A — freeze and prune
- Add structural freeze tests for the current startup/session-start, input,
resize, update/render, and shutdown boundaries; later checkpoints replace
these with functional owner tests rather than treating source checks as final
acceptance.
- Extend `ResourceShutdownTransactionTests` for multiple same-stage failures,
failure then explicit retry, empty stages, and reentrant completion.
- Delete only proven dead/test-facade residue: `_capturedMouse`, obsolete
`_streamingRadius`, unused snap constants, `IsPlayerGuid`, `IsDoorName`, and
test-only forwarding helpers. Tests call canonical owners directly.
Result: the exact native attributes/callbacks, input subscription priority,
frame-root/session-start boundary, framebuffer behavior, shutdown stages, and
native-window-last edge are frozen. Dead duplicate state and two test-only
window facades are removed. Three corrected-diff reviews are clean; 56 focused
tests and the complete App suite (2,991 pass / 3 intentional skips) pass.
### B — native window binding and host quiescence
- Implement exact attach/reverse-detach/rollback and the no-throw quiescence
gate.
- Replace direct `Run` event wiring with one owned binding while preserving the
two Render handlers and their order.
- Add post-detach silence, failure-after-Nth-attach, repeated/reentrant Dispose,
callback-during-detach, and persistent physical-detach tests.
Result: one fixed typed binding now owns the exact nine Silk edges, including
the two ordered Render callbacks. Create/publish/Attach and the explicit
lifecycle state preserve cleanup ownership across partial event-accessor
failure, rollback failure, concurrent or reentrant shutdown, and condition
wakeups. The shared host gate drains admitted callbacks and makes failed
physical detach logically inert. Three corrected-diff review loops are clean;
54 focused tests, the App suite (3,027 pass / 3 intentional skips), and the full
Release suite (7,386 pass / 5 intentional skips) pass. No connected gate was
required because this checkpoint changes ownership only and preserves the
frozen callback behavior.
### C — live-session host and skill formula
- Extract selection/entry/reset/binding factories around the existing canonical
session, router, command, and reset owners.
- Before moving the formula, write pseudocode for
`SkillFormula::Calculate @ 0x00591960`, cross-check the DAT field semantics
against ACE plus a second reference, then add conformance tests. If current
behavior differs, register it rather than silently calling the ACE-only
formula retail-faithful inside a structural commit.
- Keep `LiveSessionController.Start` last; both disabled-live and
missing-credential starts must execute the reset path.
Result: `LiveSessionHost` now owns reset-plan construction, exact selection and
entered-world ordering, and create→attach route factories while resolving all
session/command/in-world state through the sole `LiveSessionController`.
Partially attached routes and every individual subscription edge retain failed
cleanup work; successful edges are never replayed, and reset/new generations
remain blocked until teardown converges. Named-retail research corrected the
former ACE-only skill shortcut to exact unsigned `SkillFormula::Calculate @
0x00591960` semantics. Three corrected-diff review loops are clean; focused
session/formula/ledger tests, the App suite (3,048 pass / 3 intentional skips),
Core.Net (548 pass), the Release build, and the full suite (7,408 pass / 5
intentional skips) pass. No connected gate was required for this ownership
checkpoint; the final connected lifecycle gate remains Checkpoint L.
### D — world environment
- Move loaded sky/day state, `RefreshSkyForCurrentDay`, `AdminEnvirons`,
WorldTime synchronization, provider swaps, and Weather changes verbatim into
`WorldEnvironmentController`.
- Preserve existing named-retail citations and registered sound adaptation;
diagnostics call typed commands rather than owning time/weather state.
Result: `WorldEnvironmentController` is now the sole owner of the clock,
loaded sky descriptor, selected day group, Weather state, server time sync,
AdminEnvirons bridge, and time/weather debug cycles. `GameWindow` preserves its
public readonly clock/weather aliases but only composes the owner into render
and session seams. Initialization is explicitly one-shot, and missing GameTime
restores the documented fallback origin instead of inheriting process-global
state. Named-oracle review corrected the day picker to
`SkyDesc::CalcPresentDayGroup @ 0x00500E10` and AdminEnvirons to
`CPlayerSystem::Handle_Admin__Environs @ 0x0055DE20`; TS-54/TS-55 now register
the carried centered-audio and complete fog/ambient/radar gaps. Three
corrected-diff reviews are clean; 17 focused tests, the App suite (3,059 pass /
3 intentional skips), the warning-free Release build, and the full suite
(7,419 pass / 5 intentional skips) pass. No connected gate was required for
this behavior-preserving ownership checkpoint.
### E — pointer, focus, and framebuffer resize
- Make SilkKeyboard/Mouse sources and dispatcher links reversible, then attach
the raw pointer owner in the existing pre-UiHost order.
- Late-bind the gameplay-frame slot without resubscribing or reordering input.
- Extract camera mode, lifecycle focus loss, scroll, and sensitivity state.
- Extract the independent framebuffer resize target and pin logical Window.Size
versus FramebufferSize behavior.
- Require rollback after every Nth event add and silence between every shutdown
stage for raw/retained/devtools device events.
Result: `CameraPointerInputController` now owns raw move, camera-mode cursor,
focus-loss, scroll, and per-mode sensitivity policy; its gameplay-frame edge is
late-bound without resubscription. Silk keyboard/mouse sources,
`InputDispatcher`, retained UI bindings, and the devtools
`QuiescentInputContext` are publish-before-attach, terminal after disposal,
transactional on every side-effecting event add, and retry only the physical
edges that remain pending. Mouse-only hosts retain camera/cursor behavior.
`FramebufferResizeController` is the sole physical framebuffer publisher and
seeds viewport/aspect/camera state from `FramebufferSize`, while logical UI and
frame dimensions remain `Window.Size`. Shutdown now performs no-throw logical
input cutoff, retires the live session, then removes physical callbacks; the
post-session mouse-look release therefore cannot send a final movement packet,
and a bad Silk remove accessor cannot block F653/transport teardown. TS-56
records the carried non-retail camera input scalars. Three corrected-diff review
loops are clean; 66 focused App tests, 9 dispatcher-lifetime tests, 5 Core input
integration tests, the warning-free production Release build, and the complete
Release suite (7,477 pass / 5 intentional skips) pass. `GameWindow` is 4,266
raw lines / 194 fields / 65 methods. No connected gate was required for this
behavior-preserving ownership checkpoint; Checkpoint L retains the final
connected lifecycle and visual gates.
### F — gameplay action and command routing
- Move the frozen action-priority graph into the sole Fired subscriber.
- Extract combat mode command behavior and diagnostic command routing to typed
owners; retained UI, selection, movement, player mode, and item interaction
remain canonical.
- Make Combat, Camera, dispatcher, and retained-root subscriptions named,
transactional, and symmetrically detached.
Result: `GameplayInputActionRouter` is the sole gameplay subscriber to
`InputDispatcher.Fired` and preserves the frozen pointer → scroll → combat →
Press/DoubleClick gate → retained UI → selection → movement → command
priority. Focused command owners now route combat, diagnostics, window/player
mode, item-target mode, and Escape without calling feature bodies on
`GameWindow`. `RetainedUiGameplayBinding` owns the outside-UI item-drop edge;
the combat/diagnostic deferred slots release their targets before session
retirement, and the retained binding detaches transactionally afterward.
Named-retail review
also corrected `ToggleCombatMode @ 0x0056C8C0`: active combat short-circuits to
peace without an equipment lookup, while an incompatible Held object prints
the exact retail notice and sends no mode request. Three corrected-diff review
loops are clean; 54 focused App tests, 20 Core combat tests, the App suite
(3,155 pass / 3 intentional skips), the Release build, and the complete suite
(7,524 pass / 5 intentional skips) pass. `GameWindow` is 4,057 raw lines / 198
fields / 54 methods.
### G — two-phase runtime settings
- Construct `RuntimeSettingsController` before Window.Create and source startup
Display/VSync/MSAA from it.
- After GL/camera/audio and before devtools/world, apply startup
display/pacing/audio and resolve the immutable quality snapshot consumed by
downstream factories.
- Late-bind typed runtime targets without replaying startup transitions, then
move runtime display/quality changes, frame-rate/UI-lock/combat preferences,
and toon-scoped state.
- Preserve restart-required quality behavior and test transitions/previews
without requiring GL.
**Implementation result:** one pre-window `RuntimeSettingsController` now owns
the concrete settings store, the immutable startup snapshot, current Display /
Audio / Gameplay / Chat / Character values, the active toon, resolved quality,
and the optional Settings draft. Startup applies pacing, window state, saved
FOV, and audio in order exactly once; a failed later substep retries without
replaying a successful earlier substep. Complete runtime targets bind only
after their borrowers exist and binding performs no replay. Display saves keep
the persistence → live window → snapshot → quality order; quality applies
alpha-to-coverage → anisotropy → render range → streaming → completion budget.
Combat preference changes merge only their three fields into a live draft, so
unrelated unsaved Gameplay edits survive. Saved FOV now applies without
devtools, correcting the old accidental SettingsVM gate. `/framerate` retains
the named-retail live toggle/notice and keeps acdream's shipped cross-launch
persistence as documented AP-121. UI-lock convergence requires target,
persistence, and Settings baseline publication; a failed substep keeps the
same-value command retryable. `GameWindow` is 3,663 raw lines / 162 fields / 37
methods. Thirty-five focused App settings/boundary tests, all 43
`SettingsVMTests`, the UI Abstractions Release suite (534 pass), and the App
Release suite (3,183 pass / 3 intentional skips) pass. All three independent
corrected-diff reviews are clean; the production App Release build has zero
warnings/errors, and the complete Release suite passes 7,553 tests / 5
intentional skips (17 existing test-project warnings remain tracked by #228).
### H — explicit single resource ownership and frame slots
- Publish TerrainAtlas and the dedicated sky Shader as separate lifetime roots;
renderers remain borrowers and retire before the sole atlas/shader disposal.
- Publish `RetailUiRuntimeLease` before Host/Mount acquisition; add separate
idempotent input deactivation and test constructor failure, Initialize failure
with successful cleanup, transient cleanup followed by lifetime retry,
persistent cleanup followed by terminal abandonment, and exactly one Host
disposal/no unowned Host on every path.
- Publish update/render roots only through `GameFrameGraphSlot` and withdraw the
slot after session convergence.
- Pin portal fallback/transfer unchanged and test every normal/partial prefix
for no leak or double delete.
- Make `Shader` construction and `TerrainAtlas.Build` internally exception-safe;
phase scopes cannot recover GL names created inside a factory that throws.
Inject GL compile/link/texture-allocation failure after every internal name
and prove exact rollback/residency release.
Checkpoint H is complete. `GameRenderResourceLifetime` is the sole terrain-
atlas and dedicated-sky-shader owner; renderers borrow and retire before those
roots. `RetailUiRuntimeLease` retains the Host/runtime chain through partial
construction, input cutoff, retryable disposal, and explicit terminal
abandonment. `GameFrameGraphSlot` publishes update/render roots atomically, and
the portal tunnel now uses one prepare-aware fallback/transfer transaction.
Shader, terrain, text-renderer, buffer/VAO, bindless, and texture-binding
construction/mutation paths use always-on checked GL commit boundaries. A
failed cleanup retains the exact name/handle/binding obligation for retry, and
successful substeps never replay. `GameWindow` is 3,689 raw lines / 162 fields
/ 37 methods. The focused ownership gate passes 61 tests, the App Release suite
passes 3,236 tests / 3 intentional skips, and all three independent final
corrected-diff reviews are clean. The complete Release suite passes 7,606 tests
/ 5 intentional skips; the 17 existing test-project warnings remain tracked by
#228.
### I — ordered production composition
- Split the 2,289-line `OnLoad` body at §3.6 boundaries using the fixed typed
production pipeline, small immutable phase results, and no stored mega-context.
- Remove retained GameWindow captures via stable locals, typed adapters, and
existing deferred slots.
- Run the same pipeline with fake acquisition phases and inject failure after
platform, GL, input, DAT/effects, render resources, retained UI, live/session,
teleport transfer, and frame-root publication. Each concrete phase also
proves transactional rollback of its internal acquisition prefix.
Result: all nine production phases now run through the same executable pipeline
as the prefix/failure oracle. Platform and settings results are explicit typed
inputs wherever consumed; phases reject mismatched instances, all long-lived
owners publish to focused lifetime slots, the frame pair publishes atomically,
and session start is terminal. `GameWindow.cs` is 1,945 raw lines. Three
corrected-diff passes are clean; App Release passes 3,431 tests / 3 skips and
the complete Release suite passes 7,803 tests / 5 skips with the existing 17
#228 warnings. No connected gate was required for the final structural
correction; Checkpoint L retains the connected lifecycle and soak gates.
### J — lifetime cutover and shutdown failure policy
- Move the teardown-only stage records and shutdown manifest out of GameWindow.
- Co-stage no-throw quiescence and live-session convergence as hard operations;
retry/report physical detach as non-blocking operations, withdraw both frame
graphs next, then preserve the remaining frozen order.
- Cover constructed-never-run, Load-never-fired, normal WM_CLOSE, WM_CLOSE then
Dispose, direct/repeated/reentrant Dispose, transient/persistent failures,
session deferred disposal, optional-devtools absence, GPU-drain failure, and
every partial-load prefix.
- Verify acquired owners dispose exactly once, unacquired owners never dispose,
completed operations never replay, and native-window fallback is last and
explicitly reported incomplete when a non-cancellable close cannot converge.
- Verify persistent physical-detach failure still retires downstream owners and
is reported, while persistent session/GPU barriers still protect dependents;
after native fallback the terminal abandoned state makes repeated Dispose
inert.
Result: `GameWindowLifetime` owns the typed root snapshot, 18-stage manifest,
transaction progress, terminal report, and native window. Clean, soft-failure,
hard-failure, native-failure, retry, reentrant, and never-run paths are pinned.
The UI host remains explicitly rooted through failed physical detach until the
native edge is gone; successful finalization releases the completed lifetime
graph, while abandonment retains it. No shutdown stage remains in `GameWindow`,
which is now 1,625 raw lines. App Release passes 3,441 tests / 3 skips and the
complete Release suite passes 7,813 / 5 skips.
### K — canonical soak checkpoints (#232, separate commit)
- Extract `WorldLifecycleResourceSnapshotSource` from the window and reuse the
existing render diagnostics source where fields overlap.
- Change `checkpoint <name>` from an immediate retained-UI-time capture to a
FIFO request with an acknowledgement token. The script runner treats that
token as a command barrier: it executes no later command until the token is
resolved. After private presentation returns and
`RenderFrameDiagnosticsController.Publish` has published the current frame,
a post-diagnostics checkpoint phase drains requests using that same immutable
`RenderFrameOutcome` plus one sample from every canonical source. This avoids
previous-frame diagnostics mixed with current-frame owner counts.
- Mark the token Succeeded only after serialization/write completes. Deferred
capture/write failure marks it Failed, and the runner stops with that exact
error on its next tick. Preserve request sequence/name across the deferred
edge, drain independent producers in FIFO order, and cancel pending tokens
with an explicit shutdown result.
- Store one token against the active script-command index. Repeated pending
ticks poll that token and never enqueue again—even when a render failed before
the post-diagnostics drain. Clear it only after Success, Failure, or
Cancellation is consumed; one command therefore produces one request, write,
and sequence increment.
- Add exactly nine ordered named `checkpoint` commands, parse the JSONL into the
soak report, and assert checkpoint identity/order.
- Gate pending teardown, retirement, staging, and warmup at zero; compare exact
canonical owner/cache counts and bytes at Caul return → Caul plateau; label
authoritative entity/animation population changes as workload warnings.
- Preserve process working/private memory as a separately labelled secondary
residency guard with unchanged thresholds. Require two fresh-process clean
route runs before closing #232.
Result: deferred acknowledged checkpoints now drain after current-frame render
diagnostics, persist the exact frame outcome plus canonical resource owners,
and block the script until both artifacts commit. Seventy-four focused tests,
App Release, the full Release suite, and two 403-second fresh-process routes
pass. Every route produced the exact nine checkpoints, zero pending/staged/
warmup work, no unconfounded canonical owner growth, graceful exit, and the
unchanged process/performance limits. Commits: `bca41487`, `6c5e0604`.
### L — final closeout
- Run three independent corrected-diff reviews: retail conformance,
architecture/integration, and adversarial lifecycle/failure analysis.
- Resolve every confirmed finding and re-review until clean.
- Run focused App/Core/UI tests, Release build, and the complete Release suite.
- Run connected lifecycle/reconnect and two fresh-process synchronized
nine-stop soaks; compare stable framebuffer checkpoints with Slice 7.
- Audit every active divergence row that still points at `GameWindow`; retarget
ownership paths only. Keep TS-53 and other unresolved mechanisms open.
- Update architecture, milestones, roadmap, issues, AGENTS/CLAUDE, and durable
memory with final metrics and exact gates.
Automated result: 293 focused tests and the 7,823/5 complete Release suite
pass. The 314.4-second capped/reconnect lifecycle route passes with six valid
PNGs and graceful exits. Its current deterministic frames preserve Slice 7
commit `9d7df1bf` geometry, UI/paperdoll layering, private viewports,
depth/alpha, and reveal; live entity/particle/vitals/camera timing account for
the accepted differences. All three final corrected-diff reviews are clean.
The user's connected visual matrix is the sole remaining acceptance item.
## 5. Automated acceptance
- exact window callback attach order, reverse detach, rollback, reentrancy, and
post-detach silence;
- transactional rollback after every Nth add for all multi-event bindings, plus
retained/devtools/raw device silence between every shutdown stage;
- exact input subscription/priority order and focus/reset/close semantics;
- exact resize order and non-positive-size rejection;
- disabled-live and missing-credentials startup still perform reset; live
startup remains last;
- PView still reads FramebufferSize while RenderFrameInput, retained UI,
portal/private presentation, and screenshots share one Window.Size snapshot;
- exact session reset trace and current-session borrowing;
- partial-load rollback at every composition checkpoint;
- TerrainAtlas, sky Shader, UiHost, frame graphs, GPU-flight, DAT, input, GL,
and native window have explicit tested lifetimes; terrain/sky renderers remain
borrowers and cannot also dispose the separately owned atlas/shader;
- throwing Shader/TerrainAtlas leaf factories release every internal GL name
and bindless-residency prefix;
- shutdown retry/all-attempted/no-replay semantics remain intact;
- persistent non-cancellable close fallback names the blocked stage and never
reports a clean shutdown;
- completed teardown with soft-detach failures reports immutable
`CompleteWithCleanupFailures`, and repeated Dispose is inert;
- no anonymous MouseMove subscription and no substantial Silk callback body;
- no stored `GameWindow`, broad service locator, or feature-owner callback
facade into the window; the fixed native `WindowCallbackTargets` binding is
the intentional host-boundary exception;
- `OnLoad` is ordered composition, `OnUpdate`/`OnRender` remain one handoff,
and `OnClosing`/`Dispose` delegate to one lifetime owner;
- all canonical soak snapshots are post-diagnostics same-frame atomic,
round-trip, preserve request/name order across the deferred edge and nine-name
order across two fresh processes, and name the exact owner/property on growth;
- checkpoint acknowledgement blocks following script commands, propagates
deferred write failure, and reports shutdown cancellation;
- repeated pending ticks enqueue exactly once and cannot duplicate a checkpoint
after a failed/delayed render;
- complete Release suite, lifecycle gate, soak gate, and framebuffer comparison
pass.
## 6. Final visual handoff
The only user pause is one connected visual smoke gate after all automation is
green:
- first login world bootstrap and radar;
- movement, mouse look/orbit, resize, focus loss/return, combat toggle;
- inventory/skills/spellbook shared panel and retained UI input;
- outdoor, building, dungeon, portal/recall, paperdoll, particles, alpha;
- graceful close and fresh-process reconnect.
After that pass the GameWindow structural campaign is complete. The roadmap
returns to the carried M3 magic/spell-bar/spellbook/component-book and final
two-client portal-out/materialization visual gates before new M4 feature work.