# acdream — code structure & extraction sequence **Status:** Living document. Created 2026-05-16; implementation reconciliation completed 2026-07-21; Slices 1–8 and their automated closeout landed by 2026-07-22. The connected visual matrix passed 2026-07-23; the campaign is complete and new M4 subsystems may enter through the extracted owners. **Purpose:** Describe the desired structural state of the App layer, explain the rules we've adopted, and lay out the safe extraction sequence from today's reality (one 15,723-line `GameWindow.cs` at the 2026-07-21 audit) to the target (thin `GameWindow`, small focused collaborators). **Companion to:** [`acdream-architecture.md`](acdream-architecture.md) (the layered architecture) and [`worldbuilder-inventory.md`](worldbuilder-inventory.md) (what we take from WB vs port ourselves). --- ## 1. The structural problem we're solving The layered architecture works: `AcDream.Core` is GL-free, the network layer is wire-compatible, the UI has a stable contract, plugins load. The structural debt is concentrated in **one file**: ``` baseline cf50ee3d 15,723 lines / 278 fields / 205 methods after Slice 1 14,912 lines / 278 fields / 191 methods after Slice 2 14,546 lines / 277 fields / 190 methods after Slice 3 14,310 lines / 274 fields / 190 methods after Slice 4 10,301 lines / 267 fields / 163 methods after Slice 5 closeout 8,811 lines / 247 fields / 153 methods after Slice 6 closeout 7,026 lines / 241 fields / 108 methods after Slice 7 draw-frame cutover 4,666 lines / 196 fields / 70 methods after Slice 8 checkpoint D 4,330 lines / 192 fields / 67 methods after Slice 8 checkpoint E 4,266 lines / 194 fields / 65 methods after Slice 8 checkpoint F 4,057 lines / 198 fields / 54 methods after Slice 8 checkpoint G 3,663 lines / 162 fields / 37 methods after Slice 8 checkpoint I 1,945 lines / startup composition shell after Slice 8 checkpoint J 1,625 lines / focused lifetime shell after Slice 8 checkpoint K 1,622 lines / canonical soak shell ``` At the campaign baseline, `GameWindow` was the single object that: - Owns the GL context, the window, input, and shaders. - Reads ~40 different environment variables across its lifetime. - Composes the shipped `LiveSessionController`/host/router boundary; it no longer owns a parallel session, command bus, subscription lifetime, connect, character-entry, reconnect, or graceful-close body. - Owns the adapters that hydrate canonical Runtime entity records into exact-key App projection sidecars and their animation, collision, rendering, and DAT-backed appearance resources. - Composes the shipped `WorldSelectionQuery` and `SelectionInteractionController`; it no longer owns world-picking, selection intent, Use/PickUp, or auto-walk deferral algorithms. - Drives per-frame render orchestration (sky → terrain → opaque mesh → transparent mesh → particles → debug lines → UI). - Builds and applies streamed landblock presentation (DAT decode, scenery, EnvCells, mesh publication, collision, and retirement) around the shipped `StreamingController` and `GpuWorldState` owners. - Wires up every plugin hook sink, every diagnostic, every panel. The completed slices moved those bodies into focused, tested owners. The rule that governed the campaign still applies: a collaborator is a completed extraction only when it owns the state and behavior body; wrapping a `GameWindow` method in a delegate preserves ordering but remains a partial extraction. The fix is **not** "rewrite `GameWindow` in one pass" — that is a high-risk change. The fix is to **extract one collaborator at a time**, verify behavior is unchanged, ship, and move on. This document defines that sequence. --- ## 2. Code Structure Rules — the discipline Recap of the rules from `CLAUDE.md` with the rationale: ### Rule 1: No new substantial feature bodies in `GameWindow.cs` **Why:** New feature bodies in the native shell would reverse the ownership campaign. Runtime work belongs in focused App owners, with only construction and narrow callback handoffs retained here. **How to apply:** A new feature gets its own class under `src/AcDream.App//` (or deeper in `AcDream.Core` if it's pure logic). `GameWindow` owns a field and a wiring call, nothing more. If you find yourself adding a 200-line method to `GameWindow`, stop and extract. **Exemption:** Trivial wiring that *must* stay in `GameWindow` because it touches GL state during `OnLoad` is acceptable, but should still delegate to a collaborator for the substance. ### Rule 2: `AcDream.Core` must not depend on window / GL / backend projects **Why:** Core is the GL-free, testable layer. The moment Core imports a GL or windowing namespace, we've lost the ability to test it without a graphics context, and the layer split becomes fiction. **How to apply:** Phase O removed both external WorldBuilder/backend project references. The only currently allowed seams are the GL-free helpers owned in our tree under `src/AcDream.Core/Rendering/Wb/`: `TerrainUtils`, `TerrainEntry`, `RegionInfo`, `SceneryHelpers`, and `TextureHelpers`. `ObjectMeshManager` and every GL resource owner remain in App. If Core needs a new capability, define a narrow Core interface and implement it in App; adding a new project reference requires an inventory-doc update explaining why. ### Rule 3: UI panels target `AcDream.UI.Abstractions` only **Why:** This is the one rule that keeps D.2b (the future retail-look backend) viable. Every panel that imports `ImGuiNET` directly is a panel we'd have to rewrite when the backend swaps. **How to apply:** A panel's `using` block must mention `AcDream.UI.Abstractions.*` and nothing from `AcDream.UI.ImGui`. The panel writes against `IPanelRenderer`. The `ImGuiPanelRenderer` translates those calls to ImGui at runtime. Plugin-facing UI follows the same rule. ### Rule 4: Startup env vars enter through `RuntimeOptions` **Why:** Environment variables are global mutable state. Reading them at random call sites means (a) duplicated `Environment.GetEnvironmentVariable` boilerplate, (b) no single place to see "what flags does the client respond to?", (c) impossible to unit-test parsing. **How to apply:** `src/AcDream.App/RuntimeOptions.cs` is the typed options object. `Program.cs` builds it once from args + env and passes it to `GameWindow`. New startup flags add a field to `RuntimeOptions` and a parser in `RuntimeOptions.FromEnvironment`. They don't add `Environment.GetEnvironmentVariable` reads. **Scope:** `RuntimeOptions` is for **startup-time** configuration — things that don't change once the window is up. Runtime diagnostic toggles are Rule 5's domain. ### Rule 5: Runtime diagnostic toggles live in diagnostic owner classes **Why:** Diagnostic flags (`ACDREAM_DUMP_MOTION`, `ACDREAM_PROBE_*`, etc.) need to be both env-readable at startup *and* runtime-toggleable from the DebugPanel. Per-call-site env reads can't be runtime-toggled. **How to apply:** Today's template is `src/AcDream.Core/Physics/PhysicsDiagnostics.cs` — one static class with typed `Probe*` properties read from env vars once at startup, plus runtime setters that the DebugPanel binds. New diagnostic flags follow this shape, not the per-call-site pattern that dominates `GameWindow.cs`. **Cleanup direction:** The dozens of existing `ACDREAM_DUMP_*` reads inside `GameWindow.cs` are tech debt. We do NOT bulk-migrate them as part of this refactor — they're working, they're scattered, and moving them carries risk without a current acceptor. We migrate them opportunistically: when a `GameWindow` extraction lands and a diagnostic moves with it, route it through the new owner's diagnostic class. ### Rule 6: Tests live in the project matching the layer **Why:** Test discoverability + dependency hygiene. A test for a Core class belongs next to other Core tests; a test for an App class belongs in an App test project. Co-locating tests across layers makes the dependency graph dishonest. **How to apply:** One test project per source project that has tests. Today: - `tests/AcDream.Core.Tests/` ← `src/AcDream.Core/` - `tests/AcDream.Core.Net.Tests/` ← `src/AcDream.Core.Net/` - `tests/AcDream.Runtime.Tests/` ← `src/AcDream.Runtime/` - `tests/AcDream.UI.Abstractions.Tests/` ← `src/AcDream.UI.Abstractions/` - `tests/AcDream.App.Tests/` ← `src/AcDream.App/` `tests/AcDream.App.Tests/` now exists and owns App-layer controller, streaming, render-resource lifetime, retained-UI, and `RuntimeOptions` tests. New App tests belong there; do not place GL-free Core behavior in that project merely because App currently wires it. --- ### Render-frame ownership versus recursive reachability The typed `RenderFrameOrchestrator` graph owns frame sequencing and borrows long-lived runtime/presentation owners. The enforceable ownership invariant is that the orchestrator and its immediate phase owners retain neither `GameWindow`, an anonymous callback bag, nor direct window delegates. It is **not** an invariant that recursively walking every borrowed canonical owner can never reach an event subscriber or callback that was composed by `GameWindow`; that claim would be false and would confuse object-graph reachability with frame ownership. Known recursive callback-bearing paths include (this is evidence and design documentation, not an exhaustive allowlist): - `RetainedGameplayUiFrame -> RetailUiRuntime -> RetailUiRuntimeBindings` for retained gameplay UI state and commands. - `PrivatePresentationRenderer -> PaperdollFramePresenter -> RetailPaperdollFrameView/PaperdollInventoryVisibility -> UiElement tree` for private paperdoll visibility and texture publication. This is a second, independent retained-UI entry because `UiElement.Parent` links back to the shared root and sibling controls. - `DevToolsFramePresenter -> DevToolsPanelSet -> panel/ViewModel bindings` for the optional developer UI. - `WorldRenderFrameBuilder -> RuntimeWorldFrameSettingsPreview -> IRuntimeSettingsPreviewSource -> RuntimeSettingsController -> optional SettingsVM` for the live settings draft preview applied before world drawing. - `LocalPlayerPortalViewport -> LocalPlayerTeleportController -> GameplayInputFrameController -> InputDispatcher.Fired -> GameWindow` for the canonical portal/input lifetime and the host's input-action subscription. These are presentation state/command seams, not alternative owners of the window or frame transaction. The screenshot path is deliberately not on this list: viewport width/height now travel in `RenderFrameInput`, so `FrameScreenshotController` no longer stores a window-size callback. New immediate phase owners must still satisfy the direct ownership invariant; recursive shared-owner paths are reviewed against their canonical subsystem lifetime rather than an impossible global no-callback assertion. --- ## 3. Target structure of the App layer The end state — not what we're shipping in one pass, but the shape we're aiming at. ``` src/AcDream.App/ ├── Program.cs # parse args + env → RuntimeOptions, build GameWindow ├── RuntimeOptions.cs # typed startup options (Rule 4) ├── Rendering/ │ ├── GameWindow.cs # thin: GL/window lifecycle + delegates per-frame to RenderFrameOrchestrator │ ├── RenderFrameOrchestrator.cs # GPU-flight boundary + typed world/private/UI render phases │ ├── LiveEntityAnimationScheduler.cs # shipped: ordinary live-object update workset │ ├── LiveEntityAnimationPresenter.cs # final part-pose/mesh/effect composition after scheduler output │ ├── RetailStaticAnimatingObjectScheduler.cs # shipped: separate static-animation workset │ ├── StaticLiveRootCommitter.cs # live static root → pose + collision boundary │ ├── TerrainModernRenderer.cs # (already exists) │ ├── TextureCache.cs # (already exists) │ ├── ParticleRenderer.cs # (already exists) │ ├── Sky/ # (already exists) │ ├── Wb/ # WB seam + EnvCellLandblockBuild transaction │ └── Vfx/ # (already exists) ├── Net/ │ ├── LiveSessionController.cs # owns complete WorldSession connect/enter/logout/reconnect lifecycle │ ├── LiveSessionHost.cs # App reset/selection/entry/route-factory composition over the canonical controller │ ├── LiveSessionLifecycleHost.cs # exact borrowed-session attachment guard │ ├── RetailSkillFormula.cs # named-retail unsigned skill-credit calculation + DAT lookup │ └── LiveSessionEventRouter.cs # typed subscriptions → focused domain handlers ├── Physics/ │ ├── ProjectileController.cs # canonical live-record projectile orchestration │ ├── RemotePhysicsUpdater.cs # ordinary/Hidden remote narrow-tick integration │ ├── LiveEntityOrdinaryPhysicsUpdater.cs # manager-less canonical body Transition path │ ├── LiveEntityNetworkUpdateController.cs # Position/Vector/State/Motion App integration │ ├── LiveEntityInboundAuthorityGate.cs # exact per-channel timestamp/version admission │ ├── LiveEntityMotionRuntimeController.cs # shared host/setup/MoveTo/Sticky runtime policy │ ├── DeferredLiveEntityMotionRuntimeBindings.cs # fail-fast construction-order bridge │ ├── LiveEntityShadowPublisher.cs # authoritative exact-owner/residency collision gate │ ├── RemoteInboundMotionDispatcher.cs # shared animated/headless UpdateMotion funnel │ ├── RemoteTeleportController.cs # loaded/pending teleport placement ownership │ ├── RemoteTeleportHook.cs # ordered retail teleport teardown actions │ └── RemoteTeleportPlacement.cs # collision-seated SetPosition transition commit ├── World/ │ ├── InboundPhysicsStateController.cs # timestamps + accepted spawn snapshots │ ├── LiveEntityRuntime.cs # shipped: logical lifetime + ServerGuid↔entity.Id translation │ ├── LiveEntityHydrationController.cs # spawn/appearance/parent/delete resource integration │ ├── RetailInboundEventDispatcher.cs # update-thread packet/frame FIFO barrier │ ├── RetailLiveFrameCoordinator.cs # shipped: object/network/command/reconcile phase order │ ├── LiveEntityPresentationController.cs # ordered Hidden/NoDraw/effect/collision side effects │ ├── WorldEnvironmentController.cs # one-shot clock/DAT-sky/day/weather/AdminEnvirons owner │ ├── LiveEntityTeardown.cs # failure-isolated multi-owner lifecycle drain │ └── ParentAttachmentState.cs # parent generations + pending ParentEvent relations ├── Interaction/ │ ├── WorldSelectionQuery.cs # read-only picking/classification/description queries │ └── SelectionInteractionController.cs # owns selection intents, Use/PickUp, auto-walk deferral ├── Streaming/ │ ├── LandblockPresentationPipeline.cs # DAT build/apply/retire transaction and resource publication │ ├── StreamingController.cs # typed frame scheduler + reveal destination reservation │ ├── WorldRevealCoordinator.cs # canonical readiness, quiesce, and reservation lifetime │ └── ... # shipped streamer/world-state owners ├── Input/ # (already exists) ├── Audio/ # (already exists) └── Plugins/ # (already exists) ``` What `GameWindow` keeps: - `IWindow` / `GL` / `IInputContext` lifecycle (constructor + `OnLoad` + `Run` + `OnClosing`). - `RuntimeOptions` reference (the typed startup config). - GL resource construction and top-level collaborator composition. Construction is allowed here; feature algorithms and mutable subsystem state are not. - One field per top-level collaborator (`_liveSessionController`, `_liveEntityRuntime`, `_selectionInteraction`, `_streamingPresentation`, `_liveObjectFrame`, `_renderFrameOrchestrator`). - The Silk.NET event-handler stubs that delegate to collaborators. What `GameWindow` loses: - Live connect/enter/logout and event-subscription bodies → completed inside `LiveSessionController` / `LiveSessionEventRouter`. - Live-object hydration adapters and final animated-part presentation → focused world/render owners over exact-key App projection sidecars backed by canonical Runtime records. - `WorldPicker`, target queries, and selection-driven Use/PickUp/auto-walk → `WorldSelectionQuery` + `SelectionInteractionController`. Core `SelectionState` remains the injected session owner. - Landblock DAT build/apply/retirement presentation → `LandblockPresentationPipeline`; `StreamingController` remains the residency scheduler and `GpuWorldState` remains the spatial registry. - Per-frame draw orchestration and its frame-local scratch state → `RenderFrameOrchestrator`. The eventual `GameEntity` aggregation (target state described in `acdream-architecture.md` §"GameEntity: The Unified Entity") happens **after** `RuntimeEntityDirectory` is the single owner of canonical entity state. That boundary is now shipped. App's former parallel presentation dictionaries are bounded by one exact-key projection store rather than spread across `GameWindow`. `RuntimeEntityDirectory` owns the only active server-GUID/incarnation map, Runtime local-ID allocation/reverse lookup, accepted immutable CreateObject snapshot and nine-channel timestamp gates, parent relations, operation versions, and exact teardown tombstones. `RuntimeEntityRecord` is presentation-free. `LiveEntityRuntime` is the App projection/lifecycle host: it creates a `LiveEntityRecord` sidecar only after Runtime local-ID claim and stores it in `LiveEntityProjectionStore` by exact `RuntimeEntityKey`. Hydration, animation, effects, lights, equipped children, render identity, movement observations, liveness, teardown, and spatial worksets borrow that same exact identity. `LiveEntityPresentationController` projects Runtime state transitions into draw, collision, effects, child-NoDraw, and target visibility without becoming a second canonical owner. `Rendering/Vfx/EntityEffectController` owns the focused mixed F754/F755 pending FIFO, effect profiles, typed-table resolution, and readiness markers. A GUID is retained there only as pending wire-delivery metadata; current-incarnation resolution always asks `RuntimeEntityDirectory` and then the exact projection store. `EntityScriptActivator` uses the same Runtime-issued `WorldEntity.Id` as rendering and physics; disjoint static ID allocators fail fast instead of wrapping into another landblock's namespace. All other non-Parent pre-create packet families still need the future general queue tracked by divergence AD-32. The per-frame object scheduler is extracted, but final animated-part presentation is not. `LiveEntityAnimationScheduler` snapshots canonical spatial root records and advances the incarnation-stable object clock, PartArray, hooks, one selected movement owner, and manager tail in retail order. The remaining `GameWindow.TickAnimations` loop still composes part transforms, drawable `MeshRef`s, and effect poses from the scheduler output; moving that body into `LiveEntityAnimationPresenter` is an explicit pending slice. Manager-less bodies delegate their candidate/Transition/cell/shadow commit to `LiveEntityOrdinaryPhysicsUpdater`; retained projectile bodies and remote MovementManagers remain mutually exclusive movement owners. Static animation is deliberately separate: `RetailStaticAnimatingObjectScheduler` owns the `CPhysics::static_animating_objects` workset for DAT and live PhysicsState- Static owners with Setup DefaultAnimation. Both schedulers are wired by `GameWindow`, but neither owns GUID identity or logical resources. The typed animation view fills its reusable snapshot and render-ID set from the exact-key projection workset, avoiding interface-enumerator boxing on both update and render hot paths. `RemoteInboundMotionDispatcher` similarly keeps UpdateMotion protocol behavior outside `GameWindow`: GameWindow resolves the canonical record/body and the optional PartArray sink, while one dispatcher owns retail's interrupt, style, MoveTo/type-0, sticky, and standing-long-jump order. Static root projection is bounded by `StaticLiveRootCommitter`, which synchronizes changed roots to effects and collision without rebuilding zero-omega shadows or resurrecting a Hidden/withdrawn registration. Synchronous network and lifecycle callbacks are bounded by `RetailInboundEventDispatcher`. It owns no wire state and no identity; it only serializes nested live-object operations until the current packet or full object-frame tail completes. State-bearing direct dispatch is allocation-free; only a genuinely nested operation allocates its retained queue wrapper. `LiveEntityRecord` then supplies exact-incarnation and per-channel authority versions at callback boundaries. Position, State, Vector, and Movement remain independent, while a separate velocity version invalidates only an older operation that would overwrite a newer velocity installed by Position, Vector, or Movement. This prevents re-entrant App observers from creating call-stack ordering that retail's update-thread packet FIFO cannot produce. Pose-dependent hook deferral is similarly incarnation-scoped rather than GUID- or local-ID-scoped. `EntityEffectPoseRegistry` publishes a monotonic pose-owner lifetime, and `AnimationHookFrameQueue` captures it before semantic callbacks and rechecks it before each semantic AnimationDone and each routed hook. Static animation retains `process_hooks` until its root, live parts, and children are published; withdrawal invalidates both its prepared pose and pending hook tail. Resolved ordinary motion commits its body/root/contact state before the canonical full-cell setter enters `LiveEntityRuntime.RebucketLiveEntity`. Because that setter may synchronously move the projection to pending or replace the GUID, both `RemotePhysicsUpdater` and `LiveEntityOrdinaryPhysicsUpdater` revalidate the exact incarnation before collision or manager-tail publication. Collision residency itself is projection-owned, not updater-owned: `LiveEntityPresentationController` suspends retained non-projectile shadows on every unavailable-projection edge, restores them on hydration, and reconciles the pending-first case at `OnLiveEntityReady` after collision registration. Local projection and both authoritative remote UpdatePosition tails commit the complete root before rebucketing, then publish collision only through an exact-record/spatial-residency gate. Remote reflood tracks translation, sign-invariant complete orientation, and cell changes so an in-place turn or a same-pose EnvCell crossing cannot leave offset Setup shapes stale. The projectile controller retains its separate body/InWorld/shadow edge owner. During login/portal quiescence, loaded projection residency and gameplay visibility are separate. `LiveEntityRuntime` validates spatial ownership through `GpuWorldState.IsLiveEntityProjectionResident`, allowing destination renderer preparation to converge offscreen. World consumers continue through availability-gated `IsLiveEntityVisible`, so drawing, collision, picking, radar/status targeting, effects, and audio remain closed until reveal. This distinction is part of the Slice E connected gate, not an alternate live-entity lifetime. Remote teleport placement is bounded in `Physics/RemoteTeleportController`, not `GameWindow`: it retains at most one pending request per materialized incarnation, scopes it by the live generation and accepted PositionSequence, and asks `RemoteTeleportPlacement` to collision-seat the current body when the destination projection is available. `GameWindow` supplies lifecycle and shadow-sync callbacks only; canonical identity remains in `RuntimeEntityDirectory`, and App placement retains the exact projection key. Failed hydration restores the captured source and delegates an incarnation-scoped shadow restore to `LiveEntityPresentationController` while that source is unloaded, so Hidden/UnHide and teleport never become competing restore owners. A newer placement transfers that restore into an explicit generation-scoped active-placement state before its rebucket visibility edge even while Hidden. All intervening Hidden/UnHide and projection edges defer to that owner until stable success or rollback completes; only then can it restore, re-defer the source, or hand a Hidden result back for UnHide. The `ILiveEntityRemotePlacementRuntime` seam keeps the complete cell/contact handoff available across same-body runtime-wrapper replacement; replacing the canonical body or dropping the placement contract within one incarnation is rejected even after an operational component clear. `RemoteMotion.Body` is constructor-owned; hydration compares pending/current wrappers directly to the record body rather than trusting wrapper-to-wrapper equality. Binding reads an interface Body getter once and reuses that snapshot. `GpuWorldState` performs remove+place as one spatial rebucket, then commits and serially drains visibility edges; `LiveEntityRuntime` filters delayed duplicates. A rollback inside an observer cannot race the outer destination-visible notification or expose an intermediate false pulse. `LiveEntityTeardown` executes those independent owner callbacks to completion and aggregates failures afterwards, so a throwing effect/plugin sink cannot strand teleport, movement, shadow, light, or GUID-scoped state. --- ## 4. Reconciled ownership ledger — 2026-07-21 This section replaces the original six-step sketch. That sketch correctly identified the target but overstated some extractions and omitted large bodies added during M2/M3. The current audit is based on implementation ownership, not whether a class with the planned name exists. ### 4.1 What counts as extracted An extraction is **complete** only when: 1. the collaborator owns the mutable state and behavior body; 2. `GameWindow` constructs it and delegates through a narrow method; 3. the collaborator does not call back into arbitrary `GameWindow` methods; 4. focused tests exercise the collaborator without constructing a Silk window; 5. the accepted connected/visual behavior is unchanged. A class that stores delegates back to substantial `GameWindow` methods is a useful ordering seam, but its ownership status is **partial**. ### 4.2 Current implementation truth | Area | Status | Current truth | |---|---|---| | Startup options | **Complete** | `RuntimeOptions` owns startup configuration (`eda936dc`). Remaining direct environment reads are legacy runtime diagnostics, not startup configuration. | | Network session | **Complete Runtime ownership** | `RuntimeLiveSessionController` owns the sole `WorldSession` generation and resolve/create/Connect/selection/EnterWorld/Tick/stop/reconnect/disposal transaction. Runtime route owners preserve exact inbound/outbound ordering and retryable teardown. App supplies immutable options, graphical/domain callbacks, and one borrowed inertable UI command projection—no mirrored session or reset plan (`75930787`). | | World environment | **Complete ownership** | `WorldEnvironmentController` solely owns WorldTime, the loaded DAT sky, current day group, Weather, server synchronization, AdminEnvirons bridging, and debug time/weather cycles. `GameWindow` keeps ABI-compatible readonly aliases to the same clock/weather instances and composes one render source plus the two live-session callbacks. TS-54/TS-55 register the remaining centered UI sound and full fog/ambient/radar behavior gaps. | | Live identity/lifetime | **J3 complete** | `RuntimeEntityObjectLifetime` owns the sole `RuntimeEntityDirectory`, live `ClientObjectTable`, direct views, and ordered entity/object stream. The directory owns canonical GUID/incarnation/local-ID identity, accepted snapshots/timestamps, parent state, operation versions, and tombstones. `LiveEntityProjectionStore` owns App graphical sidecars by exact `RuntimeEntityKey`; hydration, presentation components, `GpuWorldState` residence/visibility, and retryable teardown preserve that key without another authority. Exact receipts precede fallible callbacks, re-entrant commits drain synchronously in sequence, and stable reset/disposal must converge the complete ledger to zero (`f46ddb5c`, `420e5eea`, `e937cc36`, `5ef8b537`, `ce3ac310`, `119b7c11`). | | Inbound/object-frame order | **Complete App orchestration** | `UpdateFrameOrchestrator` owns the complete typed host phase graph; `RetailInboundEventDispatcher`, `RetailLiveFrameCoordinator`, `LiveObjectFrameController`, `LiveSpatialPresentationReconciler`, streaming/input/teleport/player-mode/camera owners preserve the accepted order. `GameWindow.OnUpdate` is one profiler-scoped handoff (`e91f3102`). | | World reveal | **Complete** | `WorldRevealCoordinator` owns login/portal readiness, generation quiescence, and the exact streaming destination reservation. Login auto-entry and portal completion close only their active generation; stale completion cannot clear its replacement. Delayed portals retain the authored tunnel and centered retail wait notice rather than force-revealing incomplete content. Quiesced destination projections may become spatially resident for renderer preparation while all availability-gated gameplay consumers stay closed. | | Retained gameplay UI | **Mostly complete feature ownership** | `RetailUiRuntime` and focused panel/controllers own layout and behavior. `GameWindow.OnLoad` still performs substantial service composition, which is allowed until the final composition cleanup. | | Selection/interaction | **J5.1–J5.2 complete; J5.3 active** | Runtime `RuntimeActionState` owns the exact selection, combat, target-mode, and `RuntimeInteractionTransactionState` children. The transaction child owns use/appraisal/pickup identity, the typed ordered FIFO, and exact post-arrival token while borrowing J4's sole inventory busy/request gate. App `WorldSelectionQuery`, movement/transport adapters, lighting, toasts, drag/drop, and pending-slot drawing remain presentation. No App transaction fallback or delegate queue remains (`b298f99f`, `f5f7b417`). | | Landblock presentation | **Complete** | `LandblockBuildFactory` owns the captured-origin DAT transaction; concrete render/physics/DAT-static publishers and `LandblockPresentationPipeline` own typed-meter publication and exact retryable retirement. `StreamingController` owns stable destination/control/unload/Near/Far queues and destination reservation. CPU mesh-cache restaging requires an exact live owner. `StreamingOriginRecenterCoordinator` serializes old-window retirement with teleport/session origin lifetimes. `GameWindow` retains construction and one pipeline field only (`c79d0a49`, closeout `4a205a3e`; Slice E closeout `91e82c3c`). | | Render-frame orchestration | **Complete** | `RenderFrameOrchestrator` owns the GPU-flight, resource, world/PView/shared-alpha, private-presentation, diagnostics, screenshot, and recovery graph. `GameWindow.OnRender` takes one logical window-size snapshot and performs one immutable handoff (`9d7df1bf`). | | Unified `GameEntity` | **Slice J active at J5.3** | Canonical identity, retained-object lifetime, direct views, ordered entity/object deltas, J4 gameplay state, and J5.1–J5.2 action/interaction state share failure-safe Runtime owners while exact graphical sidecars stay in App. J5.3–J7 move combat/magic intent, movement/physics/projectiles, and remaining world authority before one Runtime composition root. | ### 4.3 Revised extraction sequence Every numbered slice is behavior-preserving and independently committed. A slice does not include feature work or opportunistic gameplay fixes. #### Gate 0 — deterministic baseline — COMPLETE The Release suite, connected R6 soak, world-lifecycle screenshots/checkpoints, graceful reconnect, local locomotion/collision/projectile/teleport gate, and two-client portal observer gate form the pre-refactor baseline. Each later slice runs the subset capable of detecting its risk; render/session slices run the complete connected lifecycle gate. #### Slice 1 — finish selection/interaction ownership — COMPLETE 2026-07-21 Detailed execution plan: [`docs/plans/2026-07-21-gamewindow-slice-1-selection-interaction.md`](../plans/2026-07-21-gamewindow-slice-1-selection-interaction.md). Split the old Step 4 into three reviewable commits: 1. `WorldSelectionQuery` receives camera/scene/live-object read seams and owns picking, target classification, selection bounds, closest-target lookup, names, and descriptions. It cannot mutate selection or send packets. 2. `SelectionInteractionController` owns selection intents, double-click Use, Use/PickUp packet requests, range decisions, speculative facing, and the pending auto-walk action. It composes the existing item/combat controllers rather than duplicating their rules. 3. Selection/Use/PickUp/combat-target `InputAction` cases delegate to the new controller; the old methods and fields are deleted from `GameWindow`. Tests cover read-only query classification separately from stateful intents, including direct use, distant auto-walk completion, corpse/container opening, pickup placement, hostile-only targeting, Hidden objects, and same-GUID reuse. The cutover landed in `047a4c83`, `52dbb574`, `e74f2ca9`, `fa8d5232`, `d2bb5af4`, and review-correction commit `5acc3f01`. The final review also ported the shared `ACCWeenieObject::prevRequest` transaction shape across inventory, toolbar, paperdoll, external-container, give, split, merge, and drop routes. Queued actions, pending placements, and responses are bound to exact object incarnations/tokens; optimistic projection, rollback projection, and authoritative response notices are distinct. Measured against `cf50ee3d`, `GameWindow` fell from 15,723 to 14,912 lines and from 205 to 191 methods; field count stayed at 278 because the slice exchanged legacy state for composed owners. Three independent retail, architecture, and adversarial review loops finished clean. Release build passed; 6,558 tests passed and five fixture/conformance tests skipped intentionally. #### Slice 2 — finish live animation presentation — COMPLETE 2026-07-21 Move `TickAnimations`, final rigid/visual part composition, effect-pose publication, and motion-done binding into `LiveEntityAnimationPresenter`. `LiveEntityAnimationScheduler` remains the time/movement owner; the presenter only consumes one scheduler result and publishes the final draw/effect pose. Move the surviving motion diagnostics with this owner instead of adding more environment reads to `GameWindow`. Tests pin legacy and sequencer paths, Hidden/static eligibility, ObjScale versus rigid effect poses, part availability, same-frame hook pose publication, and incarnation replacement. Run the R6 object-frame and projectile/effect suites plus the connected locomotion/projectile gate. Result: `LiveEntityAnimationPresenter` owns final visual/rigid part composition, effect-pose publication, MotionDone binding, and typed diagnostics. Schedules carry exact record/entity/component/epoch/projection/presentation identity and own their authored frame prefix until the next scheduler tick. Appearance and static-owner rebinding invalidate stale work. Named-retail `CPartArray::UpdateParts` short-frame retention now preserves both visual and rigid trailing poses. `GameWindow` lost 366 lines, two methods, and one net field. Three independent retail, architecture, and adversarial review loops finished clean; focused App/Core tests and the complete App suite pass. #### Slice 3 — complete live-session ownership — COMPLETE 2026-07-21 Detailed execution plan: [`docs/plans/2026-07-21-gamewindow-slice-3-live-session.md`](../plans/2026-07-21-gamewindow-slice-3-live-session.md). `LiveSessionController` now owns the complete resolve/create/bind/Connect/ CharacterList/select/EnterWorld/Tick/stop/reconnect/dispose transaction. Exact generation and operation-depth gates prevent synchronous lifecycle callbacks from resurrecting a superseded session. Staged teardown makes commands inert, detaches events, gracefully disposes the exact `WorldSession`, detaches the host, and runs the full reset manifest to convergence before reuse. `LiveSessionLifecycleHost` is the narrow App adapter; the router and command owners are transactional and session-bound. `GameWindow` lost `_liveSession`, the router/command fields, and the old startup/reset/wiring/disposal methods. Both UI stacks resolve `controller.Commands`; direct feature sends borrow `CurrentSession` at call time. Forty-seven controller cases plus host, transport-allocation, and structural gates cover failure/reentrancy/reuse. Three independent retail, architecture, and adversarial reviews finished clean. The Release suite passes 6,714 tests with five intentional skips. The 306-second connected lifecycle gate passed capped login, five travel/revisit checkpoints, exact graceful close, and uncapped fresh-process reconnect; both client processes exited normally. `GameWindow` fell another 236 lines and three fields to 14,310 lines / 274 fields / 190 methods. Slice 4 is next. #### Slice 4 — extract live-entity App integration — COMPLETE 2026-07-21 Detailed execution ledger: [`docs/plans/2026-07-21-gamewindow-slice-4-live-entity-integration.md`](../plans/2026-07-21-gamewindow-slice-4-live-entity-integration.md). This is two owners, not one replacement god object: - `LiveEntityHydrationController` owns CreateObject/ObjDesc/Parent/Pickup/Delete integration, DAT-backed appearance hydration, resource registration, and exact teardown callbacks over `LiveEntityRuntime`. - `LiveEntityNetworkUpdateController` owns App routing for accepted Position/Vector/State/Movement updates into the existing remote physics, motion, projectile, presentation, and teleport controllers. Neither may own a GUID dictionary. Both resolve the current incarnation only through `LiveEntityRuntime` and must preserve the inbound FIFO/authority-token rules. Run the complete live-entity stress suite, R6 connected route, inventory equip/parenting, death/corpse, and portal gates. Checkpoints A and B are complete. `d68c83d1`, `5882b308`, and `fcb66198` established the shared origin, lifecycle, remote-motion, and exact-record physics-host seams. `69a2ca0c` extracted appearance/collision/default-pose and projection-withdrawal mechanics, including exact recursive `leave_world` subtree handling and retained retry ownership. Three independent reviews were clean and the Release suite passes 6,799 tests with five intentional skips. Checkpoints C and D are complete. `d10c5f2d` moved CreateObject, first materialization, origin bootstrap, and landblock recovery into `LiveEntityHydrationController`; `fe551496` moved ObjDesc, Parent, Pickup, child-ready, and exact leave-world/re-entry routing. Appearance and attached subtree recovery now use independent authority transactions and preserve the same logical/runtime identities. Checkpoint E is complete in `f38822c4`: Delete, visibility prune, retryable exact-incarnation component cleanup, and derived remote stop-observation lifetime now have focused owners. Checkpoint F completed in `aa90c646`: accepted Position/Vector/State/Movement and the equal-generation CreateObject tail now route through focused owners. Independent authority/version captures survive reentrant callbacks and GUID reuse; shared motion-runtime policy is one-way bound and no extracted owner keeps a second identity dictionary. ForcePosition serializes and stamps one exact canonical outbound Position. Three corrected-diff reviews, the complete 6,940-test Release suite, and the 310-second seven-destination connected R6 route all passed. `GameWindow` is 10,301 lines / 267 fields / 163 methods. Slice 5 checkpoints A–G are complete. The pipeline owns the serialized worker build, concrete render/physics/static publication, and retryable Near/full retirement. The direct cutover deleted the legacy apply/facade/scratch body and added one retained shared-origin lifetime transaction for teleport, logout, and fast relogin. `GameWindow.cs` was 8,793 raw lines at checkpoint G and no longer owns landblock build/apply/retirement behavior. H then corrected native shutdown ordering so live-session reset converges before the streamer and other reset dependencies are disposed. The final measurement is 8,811 raw lines / 247 fields / 153 methods. The complete Release suite (7,054 pass / 5 skip), the 311-second capped/reconnect lifecycle gate, and the synchronized 394-second nine-stop resource soak all pass (`4a205a3e`). #### Slice 5 — extract landblock presentation — COMPLETE Detailed execution ledger: [`docs/plans/2026-07-21-gamewindow-slice-5-landblock-presentation.md`](../plans/2026-07-21-gamewindow-slice-5-landblock-presentation.md) (complete). Create `LandblockPresentationPipeline` around the existing immutable `LandblockBuild` transaction. It owns DAT build context, scenery/EnvCell construction, mesh/collision/light publication, demotion/full-retirement, and the single-reader DAT lock contract. `StreamingController` continues deciding what is resident; `GpuWorldState` continues owning spatial buckets. Tests pin loaded/pending/demoted/unloaded symmetry, stale build generations, resource pin/release balance, collision footprints across landblock seams, and first-login bootstrap replacement. The deterministic world-lifecycle gate and nine-destination resource soak pass. #### Slice 6 — extract update-frame orchestration — COMPLETE 2026-07-22 Detailed execution ledger: [`docs/plans/2026-07-21-gamewindow-slice-6-update-frame-orchestration.md`](../plans/2026-07-21-gamewindow-slice-6-update-frame-orchestration.md) (complete). After the stateful bodies above have owners, make the update path a real orchestrator instead of delegates back into the window. It preserves the accepted production order—not an overclaimed retail host order—covering teardown/clock/streaming convergence, input, the retail-shaped object → inbound network → command barrier, conditional spatial reconciles, liveness, local teleport, player-mode entry, and camera presentation. The exact twelve-phase graph and registered TS-53 host-order adaptation live in the detailed ledger. `GameWindow.OnUpdate` becomes a short time/input handoff. Frame-order tests and the existing R6 gate must produce the same lifecycle and movement traces before and after extraction. The production cutover is complete in `e91f3102`. `GameWindow.OnUpdate` now contains the profiler scope and one `UpdateFrameOrchestrator.Tick` handoff; streaming, input, the object/network barrier, liveness, teleport, player-mode, and camera phases have typed owners with no substantial callback facade into the window. The complete Release suite passes 7,182 tests with five intentional skips. The 314-second lifecycle/reconnect gate and 394-second synchronized nine-stop resource soak both pass with graceful exits, stable live-owner counts, and a 0.8 ms update-frame p95 at the final Caul checkpoint. `GameWindow` is 7,026 raw lines / 241 fields / 108 methods: 8,697 lines (55.3%) smaller than the campaign baseline. TS-50, TS-51, and TS-53 remain accurately registered; this behavior-preserving extraction introduced no new divergence. #### Slice 7 — extract `RenderFrameOrchestrator` — COMPLETE 2026-07-22 Detailed execution ledger: [`docs/plans/2026-07-22-gamewindow-slice-7-render-frame-orchestration.md`](../plans/2026-07-22-gamewindow-slice-7-render-frame-orchestration.md) (complete). Move the complete draw graph and its reusable frame-local scratch state into a GL-owning App collaborator. Preserve the exact modern pipeline order, clip routing, PView flood, landscape/opaque/shared-alpha flush boundaries, particles, debug draw, paperdoll, retained UI, and frame fences. Do not pass a hundred individual delegates or let the orchestrator reach back into `GameWindow`; inject a small immutable service set plus explicit per-frame input. Automated acceptance uses framebuffer artifacts and render/resource checkpoints. Visual acceptance compares outdoor, building, dungeon, portal exit, translucent lifestone/particles, and UI at the same camera positions. The production draw cutover is complete. `GameWindow.OnRender` now performs one value-only handoff to `RenderFrameOrchestrator`; focused resource, world, private-presentation, and diagnostic owners contain the former frame body and failure recovery. The Release suite passes 7,341 tests with five intentional skips. `GameWindow` is 4,666 raw lines / 196 fields / 70 methods: 11,057 lines (70.3%) smaller than the campaign baseline. The 315.6-second lifecycle and 395.2-second synchronized nine-stop soak pass with code-zero graceful exits; the six stable framebuffer checkpoints preserve the Slice 6 presentation. Issue #232 records the coarse process-residency gate's run-to-run variance so future diagnostics can distinguish canonical owner growth from OS/driver/GC residency without weakening leak detection. #### Slice 8 — composition and shutdown cleanup — AUTOMATED COMPLETE 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. Silk callbacks become narrow calls into the input, update, render, resize, focus, and shutdown owners. Checkpoints E, F, G, and H are complete. `CameraPointerInputController` owns raw pointer, camera cursor/focus/scroll/sensitivity behavior; `FramebufferResizeController` alone publishes physical framebuffer changes. All Silk, dispatcher, retained UI, and optional devtools device edges are reversible, transactional, and terminal after disposal. Logical cutoff precedes live-session retirement; physical detach follows it, so neither copied callbacks nor a failed event remove can interfere with graceful transport teardown. `GameplayInputActionRouter` is now the sole `InputDispatcher.Fired` subscriber and preserves the accepted input-priority graph. Focused combat, diagnostic, and general command owners replace the remaining window command bodies. The retained-root item-drop edge has one reversible owner, while its deferred toolbar targets become inert before session retirement. `GameWindow` now has no settings-store construction, duplicate settings loads, persisted settings mirrors, or display/quality feature bodies. `RuntimeSettingsController` is constructed before `Window.Create`, owns the one immutable startup snapshot and current/toon settings state, applies startup display/pacing/FOV/audio once, then borrows late-bound runtime targets without replay. Saved FOV now applies at startup even when devtools are disabled; this is a deliberate correction of the old accidental SettingsVM gate. The controller also preserves unrelated unsaved Gameplay draft fields when combat preferences change. `GameWindow` is now 3,663 raw lines / 162 fields / 37 methods at G. H adds sole lifetime roots for the terrain atlas and dedicated sky shader, one retained Host/runtime lease, an atomic update/render frame-root slot, and a prepare-aware portal fallback and transfer slot. GL construction and state mutations now use checked commit boundaries with exact retry ownership for failed names, bindless residency, and texture-binding restoration. `GameWindow` is 3,689 raw lines / 162 fields / 37 methods at H. Checkpoint I.1–I.5 now provide the executable nine-phase oracle, platform/host/content/settings phases, and the production world/render phase. `WorldRenderCompositionPhase` owns Region/environment, the mandatory modern-renderer foundation, immutable terrain-worker inputs, and WB/texture/ sampler construction; every Phase-4 GL constructor has retryable prefix ownership. `GameWindow` is 3,522 raw lines after I.5. I.6 moves interaction, retained UI, live presentation, and landblock publication into ordered composition phases. Typed exact-owner sources bridge later session, selection, radar, view-plane, diagnostics, inventory, hydration, and automation owners without callbacks to the window. The old item-use and selection wrapper methods are deleted. I.7 now owns the complete streaming/session/hydration/ local-player/teleport construction phase and every late edge through named exact-owner tokens; the former 432-line inline body and spawn-claim memo are gone. I.8a moves the exact live-session reset/router graph, combat and diagnostic command targets, and sole gameplay input subscriber into Phase 7 before frame publication. I.8b moves the complete update/render construction body into `FrameRootCompositionPhase`, publishes the pair through an exact lease, and gives lifecycle resource sampling a focused source. `GameWindow` is 1,945 raw lines after I.9 corrects production to invoke the same executable pipeline as the failure oracle, carries exact platform/settings results through the typed contract, and keeps live-session start terminal. Checkpoint J then moves the complete shutdown manifest into `GameWindowLifetime`: typed root groups, retry/no-replay progress, structured reportable physical-detach failures, hard session/GPU barriers, and native-window-last release now live outside the host. The shell captures roots once and makes one lifetime call from Closing/Dispose; no shutdown stage remains in it. `GameWindow` is 1,625 raw lines after J. Checkpoint K adds acknowledged same-frame canonical resource checkpoints and leaves the shell at 1,622 lines. The App gate passes 3,451 tests / 3 intentional skips and the complete Release suite passes 7,823 / 5. Two fresh-process nine-stop soaks and the connected lifecycle/reconnect route pass with graceful exits; deterministic framebuffer captures preserve accepted Slice 7 geometry, UI/paperdoll layering, private viewports, depth/alpha, and reveal. The clean solution build retains only the 17 test-project warnings tracked by #228, and all three final corrected-diff reviews are clean. Only the user's connected visual matrix passed 2026-07-23, including first-login radar, movement/combat, shared retained panels, recall/portal presentation, and graceful reconnect. ### 4.4 Exit criteria The campaign is complete when: - `GameWindow` contains no AC gameplay algorithm, entity scan, packet builder, DAT landblock builder, animation-part composer, or draw-graph body; - `OnUpdate`, `OnRender`, `OnInputAction`, and live-session callbacks are short delegation methods; - every extracted owner has App-layer tests and symmetric teardown; - the Release suite and connected lifecycle/resource gates stay green after every slice; - the final local visual matrix passes unchanged. Line count is a progress signal, not the acceptance test. The expected result is below roughly 5,000 lines, but ownership and dependency direction decide completion. Full `GameEntity` type aggregation is evaluated only afterwards as a separate migration; it is not folded into this campaign. --- ## 5. Rules of the road during the extraction 1. **One slice at a time.** Each commit ships one ownership boundary or mechanical call-site cutover. Bundling slices makes failures hard to isolate. 2. **Behavior preservation is the acceptance criterion.** Every slice must build clean, all tests pass, and visual verification at the appropriate accepted milestone scenarios must succeed. We're moving code, not changing it. 3. **No new features during an extraction step.** If you spot a real bug while extracting, file it in `docs/ISSUES.md` and address it in a separate commit (before or after the extraction, not folded into it). 4. **Diagnostic toggle migrations are opportunistic.** When a method moves to a new owner, the diagnostic flag inside it can move to a diagnostic class as part of the same commit. We do not do a bulk diagnostic-cleanup pass. 5. **Update this document when the plan changes.** If a slice turns out to need a different ownership shape than described above, update §4 in the same session you discover the divergence. 6. **No façade-only completion claims.** A delegate from a new class back to a substantial `GameWindow` method is a useful intermediate seam, not a completed extraction. 7. **No duplicate ownership.** New collaborators query `LiveEntityRuntime`, `GpuWorldState`, `SelectionState`, or the relevant existing owner. They do not create replacement GUID, visibility, session, or resource maps. --- ## 6. What this document is **not** - **Not a full rewrite plan.** The point is the *opposite* — small steps, verified at each boundary. - **Not a feature phase.** Following the 2026-07-21 user decision, this behavior-preserving campaign is the structural prerequisite before new M4 subsystem bodies are added. Severe regressions remain fixable in separate commits; ordinary feature work waits. - **Not a substitute for the milestones / roadmap.** Those drive the feature work. This drives the structural work that runs underneath.