# Animation Runtime Crib ## Current live presentation ownership (2026-07-21) `LiveEntityAnimationScheduler` owns ordinary-object time, PartArray advance, root motion, and the retail manager tail. `RetailStaticAnimatingObjectScheduler` owns the separate retail static-object workset. `LiveEntityAnimationPresenter` is the sole consumer that composes final drawable part transforms, publishes rigid part/effect poses, and binds `MotionDone`. `GameWindow` wires these owners but no longer contains the presentation body. The frame handoff is identity- and lifetime-bound. A schedule carries the exact `LiveEntityRecord`, `WorldEntity`, animation state, sequencer, object-clock epoch, projection mutation version, and presentation revision. Both ordinary and static schedulers copy the sequencer's borrowed PartArray view into their own buffers before invoking callbacks. Presentation is non-reentrant because it consumes legacy elapsed time and static prepared frames exactly once. Appearance rebinding increments `PresentationRevision`. That rejects a pose prepared against the old part template without replacing the live animation component. For a static owner, rejecting only that stale visual pose must retain the same sequencer's pending `process_hooks` tail so `MotionDone` is not lost; owner, sequencer, residency, projection, or clock mismatches still invalidate the entire handoff. Retail `CPartArray::UpdateParts` updates only the authored AnimFrame prefix. When a frame contains fewer parts than the Setup, trailing parts keep their previous pose (or their rest pose on first presentation) in both channels: - visual: `DefaultScale * rotation * translation(origin) * ObjScale`; - rigid/effect: `rotation * translation(origin * ObjScale)`. Canonical per-frame order remains scheduler tick, static tick, live presenter, equipped-child update, static `process_hooks`, deferred hook drain, attached effects/lights, particles, and draw. The end-to-end order assertion belongs to Slice 6's extracted update orchestrator; do not reintroduce local ordering into the presenter. ## Live entity lifecycle invariant `CreateObject` creates a live entity and a real `DeleteObject` destroys it. `ObjDescEvent (0xF625)` is neither: it changes appearance on the existing object. For an appearance update, preserve: - `WorldEntity` object identity and local `Id` - `AnimatedEntity`, `AnimationSequencer`, frame/range/rate state - movement-controller and animation-manager bindings - physics host, collision registration, dead reckoning, and selection state Replace only resolved `MeshRefs`, palette override, part overrides, and visual bounds, then invalidate the entity-classification cache. The paperdoll may clone the updated appearance separately. Retail oracle chain: `SmartBox::HandleObjDescEvent` → `SmartBox::UpdateVisualDesc` → `ACCObjectMaint::SetVisualDesc` → `CPhysicsObj::DoObjDescChangesFromDefault` → `CPartArray::DoObjDescChanges`. Regression history: treating `0xF625` as despawn/respawn severed the player controller from the active animation state, so unequipping armor froze character animation and made animation-dependent door interaction appear frozen. Fixed as issue #203 on 2026-07-11 and user-gated in a Release client. ## CreateObject enter-world invariant The wire motion is applied while the new physics object is still detached, and only then is the object placed in the world: `ACCObjectMaint::CreateObject` → `CPhysicsObj::set_description` (`0x00514F40`) → apply MovementData → `CPhysicsObj::enter_world` (`0x00516170`) → `CPartArray::HandleEnterWorld` (`0x00517D70`) → `MotionTableManager::HandleEnterWorld` (`0x0051BDD0`). The final call strips every description-time transition link and drains the manager queue. This is not optional cleanup. A corpse CreateObject carries `NonCombat + Dead`, while a door carries `NonCombat + On/Off`; both come from the same wire MovementData path. Issue #204 exposed a structural trap: the persistent Dead rest pose can be static, so corpses use the same static/reactive registration branch as doors. That branch had been data-driven at its gate but door-hardcoded in its initializer, which discarded Dead and left Ready playing. Every spawn branch must call the shared wire-driven description-then-enter-world initializer, keyed by data and never by object name/type. 2026-07-12.