# GameWindow Slice 3 — complete live-session ownership **Status:** Active 2026-07-21. **Parent program:** [`docs/architecture/code-structure.md`](../architecture/code-structure.md), Slice 3. **Baseline:** `9a150e24`; `GameWindow.cs` is 14,546 lines, 277 fields, and 190 methods before this slice. **Behavior rule:** Preserve the accepted connect, EnterWorld, inbound dispatch, chat/command, and retail graceful-close behavior while correcting the named-retail character-list, unattended-selection, canonical-account, and disconnect-state gaps discovered by the slice audit. This slice changes App ownership and fixes proven session-lifetime defects; it does not add a retained character-selection UI or redesign the transport. ## 1. Outcome `LiveSessionController` becomes the sole App owner of one exact `WorldSession` lifetime: - endpoint resolution and session construction; - event routing before the first network receive; - Connect → CharacterList validation → first-character selection → EnterWorld; - active command-bus publication; - per-frame Tick; - graceful stop, replacement/reconnect, and disposal; - session-generation and reentrant-operation gates; - failure convergence back to a clean offline state. `LiveSessionEventRouter` owns typed session subscriptions and exact unsubscription. It routes into focused entity/environment/chat/vital and Core state handlers but owns no entity map, renderer, UI model, or gameplay state. `LiveSessionResetPlan` owns the ordered, failure-isolated reset transaction but does not become a second state owner. At slice exit, `GameWindow` retains one `_liveSessionController` field plus narrow composition/lifecycle callbacks. `_liveSession`, `_commandBus`, `_combatChatTranslator`, `TryStartLiveSession`, `ClearInboundEntityState`, and `WireLiveSessionEvents` are removed. Every outbound caller resolves the current borrowed session from the controller at call time; no feature caches it. ## 2. Retail and shipped oracles The accepted transport behavior remains pinned to: - [`2026-07-20-retail-graceful-logout-pseudocode.md`](../research/2026-07-20-retail-graceful-logout-pseudocode.md); - `Proto_UI::LogOffCharacter @ 0x00546A20`; - `CharacterSet::UnPack @ 0x004FE340`; - `gmCharacterManagementUI::EnterGame @ 0x004ED440`; - `CPlayerSystem::LogOnCharacter @ 0x0055F890`; - `CPlayerSystem::RequestLogOff @ 0x00562DD0`; - inbound login-message dispatch `@ 0x0055C963`; - `CPlayerSystem::ExecuteLogOff @ 0x0055D780`; - `ClientNet::ExitWorldDisconnect @ 0x00541E00`; - `ClientNet::LogOffServer @ 0x00543EF0`; - `SharedNet::SendOptionalHeader @ 0x00543160`; - ACE `CharacterHandler.CharacterLogOff` and `Session.SendFinalLogOffMessages`; - Holtburger's connect/PlayerCreate/LoginComplete flow as a protocol cross-check, not as the logout oracle where its shortcut differs from retail; - [`2026-07-20-automated-world-lifecycle-gate.md`](2026-07-20-automated-world-lifecycle-gate.md), whose accepted connected trace proves F653 confirmation, transport disconnect, endpoint release, and fresh-process reconnect. The controller must preserve these fixed facts: 1. Complete routing is installed before `Connect`; Connect can synchronously publish initial server time before character selection. 2. Retail's CharacterList contains separate active and deleted collections. EnterGame requires a selected GUID in the active collection whose `GetGreyedOutFor` result is not positive. acdream's unattended auto-entry is an explicit adaptation: select the first active, non-greyed character and reject an empty/all-greyed list. A future retained character-select screen is separate M4/UI work. 3. `WorldSession.EnterWorld` retains its shipped request → ServerReady → CharacterEnterWorld order. F657 uses `CharacterList.AccountName`, the canonical server-returned spelling, rather than the startup username. 4. Closing an in-world session sends the eight-byte `[0xF653, active GUID]` request, drains without App world callbacks until the server's exact four-byte opcode-only F653 confirmation, sends transport Disconnect, and only then releases the socket. Once ConnectRequest negotiation has supplied receiver id/iteration, character-select, entering-world, and failed post-negotiation sessions also send transport Disconnect before socket release. `WorldSession.Dispose` remains the one wire owner. 5. The existing close-only adaptation in divergence row AD-44 remains: acdream exits instead of returning to a retained character-select connection. 6. The registered fixed ConnectResponse delay (UN-6) and initial LoginComplete-readiness gap (TS-28) remain explicit Core.Net follow-ups. They are not copied into, disguised by, or claimed by the App controller. No new automatic network retry or logout shortcut is introduced. Core.Net changes stay surgical: retail CharacterSet parsing/selection data, exact F653 confirmation, and negotiated-state Disconnect symmetry. The accepted packet codec, handshake loops, confirmation wait, and receive-thread design are not rewritten. ## 3. Proven baseline defects closed by this slice The pre-slice audit found real lifetime defects, not merely style debt: - repeated startup can overwrite an active controller/session without stopping the exact prior lifetime; - no-character and failure paths can leave a `LiveCommandBus` whose delegates capture a disposed session; - the no-character path leaks `CombatChatTranslator` subscriptions; - anonymous event wiring cannot be explicitly detached; - `GameEventDispatcher.Unregister(type)` cannot safely distinguish an old registration from a replacement; - the reset boundary leaves player identity, Core character state, Turbine rooms/cookie, shortcuts/components, combat/item mana/social state, motion metadata, and player-mode references from the prior session; - one reset-stage failure can prevent later network/effect cleanup; - a synchronous handler can request stop/reconnect while `Tick`, Connect, or EnterWorld is still unwinding. Each is fixed at its owner. No suppression flag, delay, retry loop, or stale-GUID filter is acceptable. ## 4. Architecture and interfaces ### 4.1 Controller state `LiveSessionController` exposes: ```csharp LiveSessionStartResult Start(RuntimeOptions options, ILiveSessionLifecycleHost host); LiveSessionStartResult Reconnect(RuntimeOptions options, ILiveSessionLifecycleHost host); void Stop(); void Tick(); WorldSession? CurrentSession { get; } // borrowed; never cache ICommandBus Commands { get; } // NullCommandBus outside active InWorld bool IsInWorld { get; } ulong SessionGeneration { get; } ``` The controller uses an injected internal operations seam for deterministic tests (resolve, create, connect, select, enter, tick, dispose). Production operations call the real `WorldSession`; tests do not open UDP sockets. This is an App orchestration seam, not a duplicate protocol implementation. Every start attempt and accepted session has a monotonically increasing generation. Lifecycle calls made synchronously from a host/event callback mark the current attempt non-accepting and defer stop/reconnect until the outer operation unwinds. The outer attempt rechecks its generation after every callback and blocking network phase, so it cannot publish an old session after reentrant replacement. Replacement order is fixed: 1. disable outbound commands and mark the router non-accepting; 2. detach exact subscriptions and session-scoped translators; 3. gracefully dispose the exact old `WorldSession`; 4. detach the borrowed session from the host; 5. execute the complete reset plan to convergence; 6. create and bind the new session transactionally; 7. Connect, validate/select, EnterWorld; 8. publish active commands only after success. ### 4.2 Lifecycle host `ILiveSessionLifecycleHost` is a narrow composition boundary, not a context bag. It has focused lifecycle calls only: - bind one exact session and return an owned `LiveSessionBinding`; - reset session-scoped App state; - report connecting/connected status; - apply the selected character identity; - apply post-EnterWorld UI/settings state; - detach the exact session. Entity, environment, and effect packets continue through focused sink interfaces. Later Slice 4 replaces the GameWindow entity sink with the two planned live-entity integration controllers without changing the session controller. ### 4.3 Exact subscription ownership `LiveSessionEventRouter` stores named delegates against one exact event source, sets `Accepting = false` before teardown, and unsubscribes in reverse order. Construction is transactional: a partially created router unwinds every prior registration. `ObjectTableWiring.Wire`, `CombatStateWiring.Wire`, and `GameEventWiring.WireAll` return idempotent owned registrations. The GameEvent dispatcher gains registration tokens that restore/remove only when their exact handler is still current. Nested registration A → B is safe when A disposes first: disposing B skips the retired A node and restores the session-owned predecessor (not a dead callback). The router groups dependencies by focused responsibility: - live entity/physics/effect sink; - environment/time sink; - Core object/combat/spell/player/social state bridge; - chat/vital bridge; - command router. There is no `GameWindowContext`, GUID table, renderer registry, or generic service locator. ### 4.4 Command lifetime `LiveSessionCommandRouter` owns one `LiveCommandBus`, the server/chat routing, and the exact session captured by its handlers. The controller exposes this bus only while that session is the active in-world generation. Before stop or replacement it returns `NullCommandBus`, so a retained panel can never send via the displaced socket. Turbine channel resolution and labels move with the chat command/event owner. `ClientCommandController` remains the retail command behavior owner; GameWindow supplies its existing focused bindings during composition. ### 4.5 Reset convergence `LiveSessionResetPlan` is an ordered list of named owner-reset operations. It runs every stage even when one fails, aggregates errors, and forbids a new session from starting until all stages converge. The plan clears: - mouse-look, player mode/cameras, auto-entry, and teleport/reveal transit; - session origin/counters and selected-character identity; - equipped/external-container projections; - object, spell, magic, combat, item-mana, local-player, friends, squelch, Turbine-chat, shortcut, and desired-component state; - interaction/selection and selection-presentation state; - liveness and canonical live entities (including GpuWorldState persistence); - remote teleport, pending F754/F755 effects, and deferred animation hooks. `LocalPlayerState.Clear`, `TurbineChatState.Reset`, and `SquelchState.Clear` are added to their real state owners. Chat history is intentionally retained across an in-process reconnect, matching the existing UI behavior, but its local-player GUID resets to zero. Vitals GUID, diagnostic player GUID, run/jump skills, character options, motion table, movement truth/shadow, active toon, shortcuts, and desired components reset to pre-login values. ## 5. Implementation sequence ### Commit A0 — retail lifecycle conformance corrections - Write one consolidated pseudocode note for `CharacterSet::UnPack`, `gmCharacterManagementUI::EnterGame`, `CPlayerSystem::LogOnCharacter`, and negotiated logout/disconnect ordering. - Parse active and deleted CharacterList collections with cursor/truncation tests and preserve status/account/slot/boolean fields. - Add the unattended first-active-non-greyed selector and canonical-account EnterWorld input. - Require an exact four-byte F653 confirmation and send transport Disconnect for every negotiated session state. - Keep UN-6, TS-28, and AD-44 explicitly registered and unchanged. ### Commit A — owned subscription primitives - Add nested-safe owned registrations to `GameEventDispatcher`. - Make ObjectTable, CombatState, and GameEvent wiring return disposables. - Add exact teardown, nested replacement, reverse disposal, and idempotence tests in Core.Net. ### Commit B — session event and command routers - Add focused live-session event source/sink contracts. - Route every current direct WorldSession event exactly once. - Move chat/Turbine/vital routing and the live command bus out of GameWindow. - Own `CombatChatTranslator` and all wiring registrations transactionally. - Test post-dispose silence, partial-construction unwind, nested events, and router A/B replacement. ### Commit C — convergent session reset - Add missing reset APIs to Core state owners. - Add `LiveSessionResetPlan` with named, failure-isolated stages. - Compose the complete current session manifest and test mutated A → reset → clean B state, including one throwing stage. ### Commit D — complete lifecycle controller - Move resolve/create/Connect/CharacterList/selection/EnterWorld/stop/reconnect into `LiveSessionController`. - Add generation and operation-depth gates. - Add typed start results for Disabled, MissingCredentials, NoCharacters, Connected, Deferred, and Failed. - Test ordering, duplicate/reentrant start/stop/reconnect, Connect and EnterWorld failures, tick generation, and exact-once cleanup. ### Commit E — GameWindow cutover - Implement the narrow host and packet sink interfaces. - Replace all direct session field reads with borrowed controller resolution. - Replace both UI command-bus providers with `controller.Commands`. - Delete the three old lifecycle/wiring methods and the displaced fields. - Preserve OnLoad, frame Tick, and shutdown stage order. ### Commit F — review corrections and documentation - Run three independent read-only reviews: retail conformance, architecture/lifetime, and adversarial tests. - Fix every confirmed issue and repeat review until clean. - Run focused App/Core.Net tests, Release build, and the full Release suite. - Run the existing connected login/command/chat/portal/graceful-close/ fresh-process-reconnect gate when local ACE is available. - Update code structure, roadmap, milestones, issues/divergence if needed, durable session memory, `AGENTS.md`, and `CLAUDE.md`. ## 6. Automated acceptance matrix ### Controller - Routing is complete before Connect's synchronous server-time callback. - First active non-greyed CharacterList entry, GUID, name, canonical account, and active-list index are exact; deleted/greyed entries are rejected. - Empty list, Connect failure, EnterWorld failure, and binding failure leave no active session/router/bus and clean exactly once. - Duplicate `Start` is idempotent and cannot disturb a healthy active scope; explicit `Reconnect` gracefully stops A before B state is reset/wired. - Stop/reconnect requested during bind, Connect, selected-character callback, EnterWorld, or Tick cannot resurrect the outer generation. - Tick reaches only the exact current session; stopped/disposed Tick is a no-op. - Stop and Dispose are idempotent. ### Router and command bus - Every direct session event routes once while accepting. - Dispose is reverse-order, idempotent, and post-dispose events are ignored. - Partial construction unwinds prior registrations. - GameEvent A/B nested ownership restores the nearest live predecessor. - Old command delegates are unreachable before old-session disposal and cannot send after replacement. - Domain-handler exceptions follow an explicit policy without corrupting subscription ownership. ### Reset - Every named stage is attempted once even when another throws. - Failure blocks construction of session B and reports all stage errors. - Player identity, Vitals/Chat identity, diagnostic GUID, selected-character metadata, Core state, social/chat-room state, projections, live runtime, pending effects, and hooks all return to pre-login values. - Chat history is retained but no old own-GUID classification remains. - Live runtime convergence removes the old persistent player classification. ### Integration - `GameWindow` has no `_liveSession`, `_commandBus`, or `_combatChatTranslator` field. - `TryStartLiveSession`, `ClearInboundEntityState`, and `WireLiveSessionEvents` no longer exist. - No substantial session callback body remains in `GameWindow`. - WorldSession retains the 8-byte request → exact 4-byte confirmation → Disconnect order and extends Disconnect symmetry to every negotiated state. - Release build and full suite are green. ## 7. Connected gate Run a normal capped Release client against local ACE and prove: 1. login selects the same first character and reveals a complete world; 2. system chat, say/tell/channel/server commands, vitals, combat state, and shortcuts/components still update; 3. one portal/recall completes with the same reveal lifecycle; 4. native close logs the F653 request/confirmation and transport Disconnect; 5. ACE releases the exact endpoint; 6. a fresh process reconnects cleanly with no old character state, command route, entities, effects, or persistent GUIDs. The gate is behavioral and resource-oriented. It does not create a new visual approval pause; final campaign visual acceptance remains after Slice 8. ## 8. Review policy One primary agent performs edits. Read-only agents independently audit retail conformance, architecture/lifetime, and adversarial tests after each ownership commit. Confirmed findings are fixed at their root and re-reviewed. No reviewer edits the shared worktree, and no callback facade back into a substantial old GameWindow body counts as extraction completion.