Move the complete update/render construction graph into a typed Phase-8 owner, explicitly carry the content dependencies it consumes, and publish both roots through one exact lease. Extract lifecycle resource sampling and frame-owned late bindings so partial startup and shutdown withdraw the same generation without window callbacks. Co-authored-by: Codex <codex@openai.com>
40 KiB
GameWindow Slice 8 — composition and lifecycle shell
Status: Active 2026-07-22.
Parent program: docs/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
- A — freeze construction/callback/shutdown order and delete proven dead or test-facade residue.
- B — make native-window callback intake an explicit reversible owner and define host-quiescence failure semantics.
- C — extract live-session host/reset/binding callbacks and verify the embedded skill formula against named retail.
- D — extract retail world-environment/day/weather behavior.
- E — extract two-phase raw pointer/camera input, focus, and framebuffer resize behind symmetric named subscriptions.
- F — extract the sole gameplay input-action router plus focused combat and diagnostic command owners.
- G — extract two-phase persisted settings/display/quality ownership.
- H — give terrain atlas, sky shader, retained
UiHost, and both frame roots explicit single ownership and transfer seams. - I — group
OnLoadinto small ordered, fakeable composition phases with transactional partial-acquisition rollback. I.1–I.8b are complete; I.8c terminal session start is active. Detailed plan:2026-07-22-gamewindow-slice-8-checkpoint-i-ordered-composition.md. - J — move the exact retryable shutdown manifest to a focused lifetime owner and prove all partial-load/reentrant/retry paths.
- K — in a separate #232 commit, add canonical owner snapshots to every soak checkpoint without weakening the process-memory guard.
- L — corrected-diff reviews, focused and full Release gates, connected lifecycle/soak, framebuffer comparison, documentation, memory, and final visual handoff.
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:
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
GameEntityaggregation, 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:
- before
Window.Create, load persisted Display and resolve VSync/MSAA because these are native-context attributes; - create the window and bind
DisplayFramePacingControllerimmediately; - register Load, Update, main Render, pacing Render, Closing, FocusChanged, Move pacing, StateChanged pacing, and FramebufferResize in that order;
- in
OnLoad, acquire GL and input from the native window; - compose camera/DAT/effects/audio, persisted settings, optional devtools, world/render resources, retained UI, live presentation, streaming, session, teleport, then update/render roots;
- call
LiveSessionController.Startlast, 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:
- pointer press/release transitions;
- scroll;
- combat press/release transitions;
- Press/DoubleClick gate;
- retained UI semantic actions;
- selection interactions;
- pressed movement/autorun;
- 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:
- 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;
- 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;
- withdraw both update and render frame graphs through their shared slot;
- retire session/UI dependents;
- clear the live runtime while teardown callbacks remain alive;
- retire live dependents;
- wait for submitted GPU work;
- retire private/render frontends;
- retire shared textures;
- retire the mesh adapter;
- retire remaining render borrowers, including terrain renderer;
- retire separately owned borrowed assets, including terrain atlas and sky shader, only after their respective renderer stage converges;
- retire remaining render owners and frame-flight ownership;
- close DAT mappings;
- dispose the already-detached input context;
- dispose GL last;
- 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:
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:
CameraPointerInputControllerowns 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:AttachRawruns after dispatcher/source wiring and before retainedUiHostwiring;BindGameplayFramefills a focused deferred slot afterGameplayInputFrameControllerexists. It consumes canonicalLocalPlayerModeState,ChaseCameraInputState, input capture, and camera owners.GameplayInputActionRouteris the only gameplay subscriber toInputDispatcher.Firedand owns the frozen priority graph above. Its pointer and scroll edges call typed operations onCameraPointerInputController; the pointer owner does not independently subscribe toFired. 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, andLiveSessionResetManifest. It resolves the current session throughLiveSessionController; it does not mirror it. WorldEnvironmentControllerowns 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:
- host input/camera;
- content/effects/audio;
- persisted settings/devtools;
- world/render resources;
- interactions/retained UI;
- live presentation/landblock publishers;
- streaming/session/local player/teleport;
- update/render roots;
- 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;
TerrainAtlasresidency/textures are explicitly released after submitted work and before GL;- the dedicated sky
Shaderis explicitly disposed; - a published
UiHostis disposed even ifRetailUiRuntime.Mountfails; - 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
ResourceShutdownTransactionTestsfor 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
Runevent 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.Startlast; 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 intoWorldEnvironmentController. - 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
RuntimeSettingsControllerbefore 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
RetailUiRuntimeLeasebefore 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
GameFrameGraphSlotand withdraw the slot after session convergence. - Pin portal fallback/transfer unchanged and test every normal/partial prefix for no leak or double delete.
- Make
Shaderconstruction andTerrainAtlas.Buildinternally 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
OnLoadbody 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.
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.
K — canonical soak checkpoints (#232, separate commit)
- Extract
WorldLifecycleResourceSnapshotSourcefrom 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 andRenderFrameDiagnosticsController.Publishhas published the current frame, a post-diagnostics checkpoint phase drains requests using that same immutableRenderFrameOutcomeplus 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
checkpointcommands, 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.
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.
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 nativeWindowCallbackTargetsbinding is the intentional host-boundary exception; OnLoadis ordered composition,OnUpdate/OnRenderremain one handoff, andOnClosing/Disposedelegate 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.