# GameWindow Slice 2 — live animation presentation ownership **Status:** Complete 2026-07-21. Automated and three-agent review gates pass; connected visual regression gate is carried to the final campaign gate. **Parent program:** [`docs/architecture/code-structure.md`](../architecture/code-structure.md), Slice 2. **Baseline:** `9ad8113c`; `GameWindow.cs` is 14,912 lines, 278 fields, and 191 methods before this slice. **Result:** `GameWindow.cs` is 14,546 lines, 277 fields, and 190 methods. The slice also closed stale-schedule ABA across replacement, appearance rebinding, static rebind, and borrowed sequencer buffers. Forty-one focused Release tests and the full 6,575-pass / 5-skip Release suite are green. **Behavior rule:** This is an ownership extraction over the accepted R6 object frame plus one named-retail conformance correction: short AnimFrames retain the prior/rest pose of trailing CPartArray parts. It must not otherwise change sequence time, root motion, collision, manager order, hook semantics, part transforms, render eligibility, or effect attachment. ## 1. Outcome `GameWindow` stops owning final animated-part presentation. A focused `LiveEntityAnimationPresenter` consumes the already-completed scheduler output and publishes the exact same draw and effect state: - final drawable `MeshRef` transforms; - indexed rigid part poses and availability; - `EntityEffectPoseRegistry` publication; - live static-animation frame handoff; - legacy animation interpolation state; - one-time `MotionDone` binding; - presentation-specific sequence/part diagnostics. The shipped owners remain unchanged: - `LiveEntityAnimationScheduler` owns ordinary-object clocks, PartArray advance, root motion, one movement owner, control-hook capture, and the retail manager tail; - `RetailStaticAnimatingObjectScheduler` owns the separate retail `static_animating_objects` time/workset and its deferred process-hooks tail; - `EquippedChildRenderController` owns equipped-child composition; - `AnimationHookFrameQueue` owns incarnation-scoped semantic/presentation-hook delivery; - `LiveEntityRuntime` remains the only identity, incarnation, component, and spatial-lifetime owner. `GameWindow.AdvanceLiveObjectRuntimeCore` retains only the explicit frame-order calls between these owners. ## 2. Retail and project oracles No new AC algorithm is introduced. The extraction is pinned to: - [`2026-07-19-r6-update-object-order-pseudocode.md`](../research/2026-07-19-r6-update-object-order-pseudocode.md) — `UpdatePositionInternal`, `process_hooks`, and manager-tail ordering. - [`2026-07-19-r6-complete-root-frame-pseudocode.md`](../research/2026-07-19-r6-complete-root-frame-pseudocode.md) — complete root `Frame`, ordinary/static worksets, part/effect publication. - [`memory/project_animation_runtime.md`](../../memory/project_animation_runtime.md) — appearance-update and description-before-enter-world lifetime invariants. - `CPhysicsObj::UpdatePositionInternal @ 0x00512C30`. - `CPhysicsObj::UpdateObjectInternal @ 0x005156B0`. - `CPhysicsObj::process_hooks @ 0x00511550`. - `CPhysicsObj::animate_static_object @ 0x00513DF0`. - `CPartArray::Update @ 0x00517DB0`. - `CPartArray::UpdateParts @ 0x005190F0`. - `CPartArray::AnimationDone @ 0x00517D30`. - `CPhysicsObj::MotionDone @ 0x0050FDB0`. - `Frame::combine @ 0x005122E0`. ACE/DatReaderWriter and the installed retail DAT remain secondary fixtures for motion-table/animation schema interpretation. Named retail is authoritative for ordering and transform semantics. ## 3. Fixed frame-order contract One ordinary live-object frame remains: ```text local player pre-network object phase selection outbound boundary publish PhysicsScript clock LiveEntityAnimationScheduler.Tick RetailStaticAnimatingObjectScheduler.Tick LiveEntityAnimationPresenter.Present EquippedChildRenderController.Tick RetailStaticAnimatingObjectScheduler.ProcessHooks translucency advance AnimationHookFrameQueue.Drain effect-root refresh attached-emitter refresh live-light refresh particle visibility particle simulation PhysicsScript simulation ``` The presenter advances no clock and drains no hook. It only consumes poses produced earlier in the same frame. Static `process_hooks` remains after live parts and equipped children, as retail's `animate_static_object` requires. ## 4. Ownership and interfaces ### 4.1 Animation component state Move `GameWindow.AnimatedEntity` and `GameWindow.AnimatedPartTemplate` to top-level App rendering types: ```text LiveEntityAnimationState LiveAnimationPartTemplate ``` `LiveEntityAnimationState` remains the exact component stored in `LiveEntityRecord.AnimationRuntime`. It owns no GUID map and no lifetime. It retains: - the exact `WorldEntity`, Setup, Animation, and optional sequencer; - legacy low/high/current frame and framerate; - ObjScale, part template, and part availability; - prepared local-player pose and complete root-frame scratch; - reusable `MeshRefs`, effect-part-pose, and diagnostics scratch. Appearance updates mutate this same component and preserve sequencer/playback state. Logical replacement creates a new component. ### 4.2 Presentation context Define a narrow App interface equivalent to: ```csharp interface ILiveAnimationPresentationContext { uint LocalPlayerGuid { get; } MotionInterpreter? ResolveMotionInterpreter(LiveEntityRecord record); } ``` `GameWindow` implements only these constant-time lookups. The presenter cannot reach windowing, GL, networking, input, streaming, physics, or arbitrary `GameWindow` methods. ### 4.3 Presenter Use an API equivalent to: ```csharp void Present( IReadOnlyDictionary schedules); void PrepareAnimation( LiveEntityRecord record, LiveEntityAnimationState state); ``` The presenter receives: - canonical `LiveEntityRuntime` provider for exact-record validation; - `EntityEffectPoseRegistry`; - `ILiveAnimationPresentationContext`; - a late-bound `ILiveStaticPartFrameSource` (the static scheduler is created after the presenter); - startup-resolved hidden-part diagnostic index; - typed animation diagnostic flags. The presenter owns its own reusable `LiveEntityRecord` snapshot. It does not borrow `LiveEntityAnimationRuntimeView`'s reusable enumerator snapshot across the synchronous `EffectPoseChanged` callback boundary. Nested enumeration by a subscriber therefore cannot invalidate the outer presentation pass. Each schedule carries the exact `LiveEntityRecord`, `WorldEntity`, animation component, `ObjectClockEpoch`, and projection-mutation version which produced it. The presenter validates all of those identities before consuming ordinary or static frames and again before each externally observable publication. A dictionary key/local ID is an index only; it is never proof of incarnation. The static prepared-frame source is queried late, only after this validation. Its take operation receives the exact record, entity, animation component, object-clock epoch, and projection/residency versions; it validates those tokens before clearing and returning prepared frames. It keeps no GUID map, no animation clock, no packet/session reference, and no render-backend object. ### 4.4 Diagnostics Move the surviving `SEQSTATE`, `CURRNODE`, `PARTSDIAG`, and `MOTIONDONE` gates behind one typed animation diagnostic owner. Environment variables are read once; the presenter does not perform per-frame environment lookups. Throttle state follows the exact animation component lifetime instead of living on the remote-motion component. ## 5. Transform invariants For every part index, preserve these two deliberately different transforms. ### Drawable visual transform ```text Setup DefaultScale × animated part orientation × animated part translation × entity ObjScale ``` This is the existing renderer/hydration contract. It scales geometry and the part's translated placement. ### Rigid effect/attachment transform ```text animated part orientation × translation(animated origin × entity ObjScale) ``` Retail `CPartArray::UpdateParts` scales the part-frame origin only. Setup DefaultScale is `gfxobj_scale`, not rigid part-frame state, so particles, lights, attachments, and multipart collision must never inherit it. Additional invariants: - Sequencer frames win when present. Retail updates only `min(CPartArray.num_parts, AnimFrame.num_parts)`; missing trailing frames retain both their previous drawable visual transform and their previous indexed rigid/effect pose (the two distinct initial rest transforms on first presentation) instead of snapping either channel to identity. - Legacy animation interpolates origin linearly and orientation spherically, clamps interpolation to `[0,1]`, and wraps over the inclusive frame span. - Live static prepared frames override the ordinary schedule for that owner. - `ComposeParts == false` changes no mesh or indexed pose. - Non-drawable/debug-hidden parts remain in indexed effect poses but not drawable `MeshRef`s. - `PartAvailability` is published unchanged and controls effect part lookup. - Mesh identity, surface overrides, and list reuse remain unchanged. - Entity mesh/indexed-part state is installed before effect-pose publication. ## 6. MotionDone invariants - Bind before any sequencer can complete; repeated calls are idempotent. - The scheduler validates the exact owner before invoking `PrepareAnimation`. - Resolve the player/remote `MotionInterpreter` at callback time because that consumer may be created after the animation component. - Never capture a legitimate null interpreter permanently. - Never complete a motion from pose publication; `AnimationHookFrameQueue` remains the semantic `AnimationDone` boundary. - Logical removal/replacement stops the old component from advancing; no presenter-owned callback queue survives it. - The callback revalidates the stable captured record, entity, and animation component before resolving a same-GUID interpreter. It deliberately does not capture object-clock/projection epochs: the same retail CPartArray and completion consumer survive legitimate rebucketing and leave/re-enter. GUID reuse still cannot deliver an old completion to a replacement. - `MOTIONDONE` diagnostics observe but never alter queue state. ## 7. Execution sequence ### Commit 0 — plan and baseline - Land this plan. - Record exact baseline metrics and current frame order. - Register no new divergence; this slice moves an accepted mechanism. ### Commit 1 — extract component types and pure presenter - Move animation component/template types out of `GameWindow`. - Add the presentation context and typed diagnostics owner. - Implement `LiveEntityAnimationPresenter` by moving the current composition body line-for-line, retaining the baseline trailing-part fallback initially. - Add focused tests before runtime cutover. ### Commit 2 — cut over runtime ownership - Construct the presenter beside the scheduler. - Route scheduler preparation, local-player pre-advance, spawn-time binding, and final presentation through it. - Delete `TickAnimations`, `EnsureMotionDoneBinding`, nested animation types, and presentation-only remote diagnostic fields from `GameWindow`. - Keep child/static-hook/effect ordering visible in `AdvanceLiveObjectRuntimeCore`. ### Commit 3 — retail trailing-part conformance - Record the `CPartArray::UpdateParts` minimum-count/retain behavior in the R6 pseudocode note. - Preserve both visual and rigid prior/rest trailing transforms. - Add first-presentation and later-shorter-frame conformance tests plus ordinary/live-static/DAT-static parity coverage. - Update the divergence register in this commit if the audited register contains a corresponding row; introduce no silent behavior delta. ### Commit 4 — review, gate, and ledger close - Run retail-conformance, architecture/integration, and adversarial read-only reviews against the complete slice. - Fix every confirmed finding and repeat review until clean. - Run focused animation, scheduler, static, effect, projectile, remote-motion, and local-player tests. - Run Release solution build and full Release suite. - Record new `GameWindow` line/field/method counts. - Update code structure, roadmap, milestones, issues if any, durable animation memory, `AGENTS.md`, and `CLAUDE.md`. ## 8. Automated acceptance matrix ### Composition - Sequencer pose composes every part and preserves surface overrides. - Missing sequencer trailing parts retain their previous/rest indexed pose. - Legacy current/next frame interpolation and inclusive wrapping are exact. - Legacy hidden/single-frame owners compose only when scheduled. - Static prepared frames override ordinary frames exactly once. - `ComposeParts == false` preserves prior mesh/pose state. ### Visual versus rigid transforms - Setup nonuniform DefaultScale affects drawable geometry only. - ObjScale affects drawable geometry and translated placement. - Rigid effect poses scale only the translated origin. - Nondrawable and debug-hidden parts remain available to effects. - Unavailable parts remain indexed but fail effect-part lookup. ### Lifetime and ordering - A removed/replaced animation component is skipped by the runtime-view snapshot and cannot publish into its replacement. - A schedule prepared for incarnation A is rejected if A is withdrawn, replaced, or reprojected before presentation, including same-local-ID ABA. - Reentrant deletion/replacement during pose publication prevents every later write for the stale owner; no borrowed scratch buffer crosses a callback. - Effect-pose publication sees the entity's already-installed indexed parts. - Same-frame animation hooks drain only after current parts and children. - Static process-hooks runs only after static parts and children are current. - Presentation allocates no per-frame mesh/effect lists in steady state. ### Motion completion - Binding is idempotent. - A consumer created after binding receives completion. - Player and remote resolution select the correct interpreter. - Missing consumer is a no-op. - Diagnostics do not duplicate `MotionDone`. - A legitimate rebucket/object-clock rebase preserves the binding. - Delete plus GUID/local-ID reuse rejects completion from the old component. ### Cross-owner order and reconciliation The presenter-level portions below are covered by Slice 2. The full instrumented owner-order trace and non-advancing spatial-reconcile assertion remain assigned to Slice 6, where the update sequence becomes one testable orchestrator instead of a `GameWindow` method. Slice 2 preserves that order but does not claim an end-to-end orchestrator test prematurely. - One instrumented object-frame test pins presenter, children, static hooks, fades, ordinary hook drain, effect roots, emitters, lights, particles, and scripts. A PES-created particle first simulates on the following frame. - Authoritative spatial reconciliation performs only root/effect refresh, child composition, emitter refresh, and light refresh; it advances no animation, hook, fade, particle, script, or object clock. - Appearance shrink from three parts to one preserves playback while removing stale MeshRefs, indexed poses, availability, and effect-registry entries. - Ordinary, live-static handoff, and DAT-static composition share conformance fixtures for scale, sparse availability, and surface overrides. - An equipped child consumes the just-published parent indexed pose in the same frame, including ObjScale and nested attachment composition. - An animated projectile's trail/light anchor observes the same-quantum root and part pose, never the previous frame. `EquippedChildRenderController` callback-mutation hardening is an adjacent Slice-6 frame-orchestration concern. Slice 2 does not claim it: the known reentrancy case is recorded as a follow-up unless a focused test demonstrates the existing traversal is already safe. - A nested animation-runtime enumeration triggered by `EffectPoseChanged` cannot overwrite or truncate the presenter's private outer snapshot. ## 9. Connected gate Run one normal capped Release client and verify behavior remains unchanged: 1. local idle, walk, run, strafe, turn, jump, combat, and stop transitions; 2. observe a remote player walk/run/turn without pose stalls or blips; 3. open/close a door and kill a creature; static/reactive final poses persist; 4. equip/unequip armor and switch melee/bow/wand without animation loss; 5. fire an arrow and cast a projectile spell; trails remain attached to the current missile/hand pose; 6. cast a protection and recall/portal; body effects follow current parts; 7. portal/relog and confirm no old pose or completion reaches a replacement. Any failure returns to the owning commit. No fallback pose cache, delayed retry, grace period, or GUID-only suppression is acceptable. ## 10. Subagent policy One primary agent performs all edits. Read-only agents independently audit retail conformance, architecture/lifetime, and adversarial tests before the plan and after every implementation correction. No subagent edits the shared slice concurrently.