docs(architecture): plan GameWindow lifecycle shell

Freeze host ordering, resource ownership, callback quiescence, partial-startup rollback, and canonical soak evidence as twelve reviewed, bisectable Slice 8 checkpoints.

Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-22 09:14:24 +02:00
parent 96f8bfcfa8
commit 20ebf217e2
3 changed files with 615 additions and 0 deletions

View file

@ -676,6 +676,9 @@ residency without weakening leak detection.
#### Slice 8 — composition and shutdown cleanup — ACTIVE
Detailed execution ledger:
[`docs/plans/2026-07-22-gamewindow-slice-8-composition-lifecycle.md`](../plans/2026-07-22-gamewindow-slice-8-composition-lifecycle.md).
Keep GL/window construction in `GameWindow.OnLoad`, but group creation into
small composition functions and delete feature state left behind by prior
slices. `OnClosing` delegates to the existing retryable shutdown transaction.

View file

@ -0,0 +1,610 @@
# GameWindow Slice 8 — composition and lifecycle shell
**Status:** Active 2026-07-22.
**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
- [ ] 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 `OnLoad` into small ordered, fakeable composition phases with
transactional partial-acquisition rollback.
- [ ] 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:
```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 Attach(
IWindow window,
WindowCallbackTargets targets,
DisplayFramePacingController pacing);
}
```
`WindowCallbackTargets` is a fixed typed set for Load, Update, Render, Closing,
FocusChanged, and FramebufferResize. It is not a general callback list. Attach
rolls back in reverse order if any registration fails; Dispose detaches in
reverse registration order, is idempotent/reentrant-safe, and makes later
window events inert. The separate pacing Render callback remains ordered after
main Render. Every callback first checks the shared no-throw host-quiescence
gate, so 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.
### 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.
### 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.
### 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.
### 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.
### 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.
### 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.
### 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.
### 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.
### 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 `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.
### 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 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.