# Animation System Audit

Phase A audit for `feature/animation-system-complete`.

Date: 2026-04-28.

## Summary

The animation core is much stronger than the feature surface around it.
`AnimationSequencer` already handles cyclic state changes, transition links,
negative-speed retail remaps, mid-cycle speed changes, frame hooks, and
PosFrame root-motion accumulation. `GameWindow.OnLiveMotionUpdated` already
routes `InterpretedMotionState.Commands[]` through `PlayAction`, which means
server-broadcast NPC/monster/emote/action overlays are likely to animate when
the server emits motion commands.

The remaining gap is mostly orchestration:

- local combat/spell/item-use commands build wire packets but do not yet drive
  the local visible action immediately;
- several combat/spell/emote packet surfaces need conformance fixes before
  animation triggers can be trusted: combat mode enum values, split
  melee/missile attack builders, `CombatCommenceAttack`, `AttackDone`,
  damage/death notification parsers, `MagicSchool` enum order, and outbound
  emote/soul-emote builders;
- combat/spell/item-use game events populate state/chat but do not yet map to
  animation overlays for attacker/defender/caster;
- style changes are handled as simple `SetCycle(style, motion)` swaps, not the
  full ACE `MotionTable.GetObjectSequence` multi-link style transition chain;
- held posture/emote commands need a small command resolver and tests around
  one-shot-vs-persistent routing;
- death needs explicit `Sanctuary` action -> `Dead/Fallen` persistence rather
  than relying on chat/health side effects.

## Evidence Sources

Named retail decomp:

- `CMotionTable::is_allowed` at `0x005226C0`
- `CMotionTable::get_link` at `0x00522710`
- `CSequence::update_internal` at `0x005255D0`
- `CMotionInterp::adjust_motion` at `0x00528010`
- `CMotionInterp::charge_jump` at `0x005281C0`
- `CMotionInterp::get_jump_v_z` at `0x00527AA0`
- `CMotionInterp::jump` at `0x00528780`
- `CMotionInterp::apply_current_movement` at `0x00528870`
- `CMotionInterp::HitGround` at `0x00528AC0`
- `CMotionInterp::LeaveGround` at `0x00528B00`
- `CMotionInterp::DoMotion` at `0x00528D20`
- `CMotionInterp::DoInterpretedMotion` at `0x00528360`
- `ClientCombatSystem::HandleCommenceAttackEvent` at `0x0056AD20`
- `ClientCombatSystem::SetCombatMode` at `0x0056BE30`
- `ClientCombatSystem::StartAttackRequest` at `0x0056C040`
- `ClientCombatSystem::EndAttackRequest` at `0x0056C0E0`
- `ClientCombatSystem::StartPowerBarBuild` at `0x0056ADB0`
- `ClientCombatSystem::GetPowerBarLevel` at `0x0056ADE0`
- `ClientCombatSystem::ExecuteAttack` at `0x0056BB70`
- `ClientCombatSystem::HandleDefenderNotificationEvent` at `0x0056C920`
- `ClientCombatSystem::HandleEvasionDefenderNotificationEvent` at `0x0056C620`
- `ClientCombatSystem::HandlePlayerDeathEvent` at `0x0056C320`
- `ClientCombatSystem::HandleAttackerNotificationEvent` at `0x0056B420`
- `ClientCombatSystem::HandleAttackDoneEvent` at `0x0056C500`
- `CM_Combat::Event_ChangeCombatMode` at `0x006A9A70`
- `CM_Combat::Event_TargetedMeleeAttack` at `0x006A9C10`
- `CM_Combat::Event_TargetedMissileAttack` at `0x006A9D60`
- `AttackHook::Execute` at `0x00526B70`
- `gmSpellcastingUI::Cast` at `0x004C6050`
- `ClientMagicSystem::CastSpell` at `0x00568040`
- `ClientMagicSystem::FreeHandsAndCastSpell` at `0x00566EF0`
- `ClientMagicSystem::GetAppropriateSpellFormula` at `0x00567D50`
- `CM_Magic::Event_CastUntargetedSpell` at `0x006A3150`
- `CM_Magic::Event_CastTargetedSpell` at `0x006A3040`
- `ItemHolder::UseObject` at `0x00588A80`
- `CM_Inventory::Event_UseEvent` at `0x006AC3B0`
- `CM_Inventory::Event_UseWithTargetEvent` at `0x006AC480`
- `CM_Item::DispatchUI_UseDone` at `0x006A8510`
- `CommandInterpreter::PlayerIsDead` at `0x006B3D70`
- `SmartBox::HandlePlayScriptID` at `0x00452020`
- `CM_Physics::DispatchSB_PlayScriptID` at `0x006ACC40`
- `CM_Physics::DispatchSB_PlayScriptType` at `0x006AC6E0`
- `ClientCommunicationSystem::DoEmote` at `0x00578AD0`
- `ClientCommunicationSystem::Pose` at `0x00580480`
- `ClientCommunicationSystem::Handle_Communication__HearEmote` at
  `0x0057CBE0`
- `ClientCommunicationSystem::Handle_Communication__HearSoulEmote` at
  `0x0057D020`
- `ChatPoseTable::InqChatPoseCommand` at `0x00570AD0`
- `ChatEmoteData::Pack` at `0x004FCE80`

Cross-reference material:

- `docs/research/deepdives/r03-motion-animation.md`
- `C:\Users\erikn\source\repos\acdream\references\ACE\Source\ACE.Server\Physics\Animation\MotionTable.cs`
- `C:\Users\erikn\source\repos\acdream\references\ACE\Source\ACE.Server\Physics\Animation\MotionInterp.cs`
- `C:\Users\erikn\source\repos\acdream\references\ACE\Source\ACE.Entity\Enum\MotionCommand.cs`
- `C:\Users\erikn\source\repos\acdream\references\holtburger\apps\holtburger-cli\src\pages\game\combat.rs`
- `C:\Users\erikn\source\repos\acdream\references\holtburger\crates\holtburger-core\src\client\messages.rs`
- `C:\Users\erikn\source\repos\acdream\references\holtburger\crates\holtburger-protocol\src\messages\movement\types.rs`

The clean worktree intentionally does not contain `references/`; it was read
read-only from the original checkout path above.

## Current Code Surface

Core animation:

- `src/AcDream.Core/Physics/AnimationSequencer.cs`
  - `SetCycle(style, motion, speedMod, skipTransitionLink)` handles cyclic
    state changes and transition links.
  - `PlayAction(motionCommand, speedMod)` handles Action, Modifier, and
    ChatEmote one-shots through Links/Modifiers lookup.
  - `Advance(dt)` emits pending hooks and accumulates PosFrame deltas.
  - Missing: full style-transition chain, durable modifier list, action queue
    accounting, and a public command-resolution facade that callers can test
    without `GameWindow`.
- `src/AcDream.Core/Physics/MotionInterpreter.cs`
  - Handles locomotion, jump, leave-ground/hit-ground, and basic contact
    guards.
  - Missing: full retail `MotionState`, action list, modifier list, hold-key
    run application, combat-state guards, and `move_to_interpreted_state`.
- `src/AcDream.Core/Physics/MotionCommandResolver.cs`
  - Reconstructs full 32-bit commands from 16-bit wire values.

App integration:

- `src/AcDream.App/Input/PlayerMovementController.cs`
  - Local walk/run/strafe/turn/jump driver. It does not own combat/spell/item
    action animation.
- `src/AcDream.App/Rendering/GameWindow.cs`
  - `OnLiveMotionUpdated` is the main inbound motion/action router.
  - `OnLiveVectorUpdated` seeds airborne jump arcs and Falling cycles.
  - `OnLivePositionUpdated` snaps positions and lands airborne remotes.
  - `TickAnimations` advances sequencers and drains hooks.
  - `UpdatePlayerAnimation` drives the local movement cycle.
  - Missing: typed animation coordinator for combat/spell/use/death/emote
    events; too much command mapping still lives inline.

Wire/state:

- `src/AcDream.Core.Net/Messages/AttackTargetRequest.cs`: outbound attack
  request exists, but currently combines melee and missile into one layout;
  retail/ACE/holtburger use distinct `0x0008` melee and `0x000A` missile
  payloads.
- `src/AcDream.Core.Net/Messages/CastSpellRequest.cs`: outbound spell request
  exists.
- `src/AcDream.Core.Net/Messages/CharacterActions.cs`: combat mode request
  exists, but the combat-mode enum must be corrected to retail values
  `NonCombat=1`, `Melee=2`, `Missile=4`, `Magic=8`.
- `src/AcDream.Core.Net/Messages/InteractRequests.cs`: use/use-with-target
  request exists.
- `src/AcDream.Core.Net/GameEventWiring.cs`: combat, spell, item, chat events
  route into state classes.
- Missing: public `WorldSession.SendAttack/SendCast/SendUse/ChangeCombatMode`
  wrappers and animation-side subscriptions.

## Retail Command Catalogue To Use

From ACE `MotionCommand.cs` + `r03-motion-animation.md`:

- Locomotion substates: Ready `0x41000003`, WalkForward `0x45000005`,
  WalkBackward `0x45000006`, RunForward `0x44000007`, TurnRight
  `0x6500000D`, TurnLeft `0x6500000E`, SideStepRight `0x6500000F`,
  SideStepLeft `0x65000010`, Falling `0x40000015`.
- Held/posture substates: Crouch `0x41000012`, Sitting `0x41000013`,
  Sleeping `0x41000014`, Dead `0x40000011`, Fallen `0x40000008`.
- Item/use substates: Reload `0x40000016`, Unload `0x40000017`, Pickup
  `0x40000018`, StoreInBackpack `0x40000019`, Eat `0x4000001A`, Drink
  `0x4000001B`, Reading `0x4000001C`.
- Spell substates/actions: CastSpell `0x400000D3`, MagicBlast
  `0x4000002B`, MagicSelfHead `0x4000002C`, MagicSelfHeart `0x4000002D`,
  MagicBonus..MagicPenalty `0x4000002E..0x40000034`, MagicTransfer
  `0x40000035`, MagicEnchantItem `0x40000037`, MagicPortal `0x40000038`,
  MagicPray `0x40000039`, MagicPowerUp01..10 `0x1000006F..0x10000078`,
  MagicPowerUp01Purple..10Purple `0x1000012B..0x10000134`.
- Combat actions: Sanctuary `0x10000057`, ThrustMed/Low/High
  `0x10000058..0x1000005A`, SlashHigh/Med/Low `0x1000005B..0x1000005D`,
  BackhandHigh/Med/Low `0x1000005E..0x10000060`, Shoot `0x10000061`,
  AttackHigh/Med/Low1..6 `0x10000062..0x1000006A` and
  `0x10000186..0x1000018E`, MissileAttack1..3 `0x100000D0..0x100000D2`,
  SpecialAttack1..3 `0x100000CD..0x100000CF`, dual-wield/offhand ranges
  `0x10000173..0x1000019A`.
- ChatEmote actions: Wave `0x13000087`, BowDeep `0x1300007D`, Laugh
  `0x13000080`, Point `0x13000084`, Salute `0x1300008A`, Kneel
  `0x13000092`, HaveASeat `0x13000152`, DrudgeDance `0x13000151`, plus
  the full `0x1200/0x1300` ranges in `r03`.
- Persistent emote states: `0x430000EA..0x430000FD`, SnowAngelState
  `0x43000118`, CurtseyState `0x4300011A`, AFKState `0x4300011B`,
  MeditateState `0x4300011C`, SitState `0x4300013A`,
  SitCrossleggedState `0x4300013B`, SitBackState `0x4300013C`,
  PossumState `0x43000142`, HaveASeatState `0x43000145`. ACE's enum is a
  useful alias catalog but has a shifted range for some late chat-emote states;
  named-retail values win when hard-coding constants.

## Category Audit

### 1. Own Player Movement

Status: mostly working.

Evidence: retail `CMotionInterp` jump/grounding symbols listed above; ACE
`MotionInterp.cs` for `adjust_motion`, `apply_current_movement`, `HitGround`,
and `LeaveGround`.

acdream locations: `PlayerMovementController`, `MotionInterpreter`,
`UpdatePlayerAnimation`, `OnLiveVectorUpdated`, `OnLivePositionUpdated`.

Gaps:

- held postures exist as retail commands but are not driven by a general
  posture/action API;
- `MotionInterpreter` does not yet own full `MotionState`, so non-locomotion
  commands cannot be uniformly tested there;
- mounted/swimming need dat/retail verification before any implementation.

Tests to add:

- posture state `SetCycle` tests for Crouch/Sitting/Sleeping;
- `motion_allows_jump` conformance for item/spell/aim/posture ranges;
- local action does not stomp Falling while airborne.

### 2. Other Players' Movement

Status: partially working after the K-fix series.

Evidence: `UpdateMotion` handling in `OnLiveMotionUpdated`; retail
`CMotionInterp::DoInterpretedMotion` and `apply_current_movement`; ACE
`MotionInterp.apply_current_movement`.

acdream locations: `RemoteMotion`, `OnLiveMotionUpdated`,
`OnLiveVectorUpdated`, `OnLivePositionUpdated`, `TickAnimations`.

Gaps:

- remote action overlays only happen when the server includes
  `InterpretedMotionState.Commands[]`; combat/spell game events do not yet
  synthesize overlays when the wire omits motion commands;
- no test fixture exercises `OnLiveMotionUpdated` command-list routing outside
  `GameWindow`;
- root-motion deltas are accumulated but not applied to remote body transforms.

Tests to add:

- command-list `Wave` -> `PlayAction` routing through a new coordinator;
- airborne remote ignores mid-arc locomotion cycle swaps but still updates
  interpreted movement;
- landing swaps Falling back to current interpreted command.

### 3. NPC Movement

Status: likely works for UpdateMotion-driven locomotion and simple gestures;
not verified.

Evidence: retail MotionTable/InterpretedMotionState path; ACE
`MotionTable.GetObjectSequence` and `MotionInterp.move_to_interpreted_state`.

acdream locations: `CreateObject.ParseServerMotionState`,
`OnLiveMotionUpdated`, `TickAnimations`.

Gaps:

- no NPC-specific live test checklist;
- no retained action/modifier list, so repeated scripted gestures are
  fire-and-forget overlays only;
- no head-look/threat-pose state beyond whatever arrives as motion commands.

Tests to add:

- synthetic NPC `UpdateMotion` with `Commands=[Wave, Ready]` plays one-shot
  then returns to Ready;
- style-default fallback for creature motion tables.

### 4. Monster Movement

Status: locomotion probably works when `MotionTableId` and UpdateMotion are
present; special attacks are unknown.

Evidence: ACE MotionTable supports monster actions such as HeadThrow,
FistSlam, BreatheFlame, SpinAttack, Bite, SpecialAttack1..3.

acdream locations: same as NPC movement; `AnimationHookRouter` for VFX/audio
side effects.

Gaps:

- attack action overlays for monsters depend on server motion command lists;
- no mapping from combat events to visible monster attack/hit reactions;
- no exotic creature spot-checks.

Tests to add:

- `PlayAction(BreatheFlame)` resolves from Links/Modifiers when synthetic data
  provides it;
- Attack hooks fire exactly once for a synthetic monster action.

### 5. Combat Actions

Status: wire codecs and combat state exist; visual action orchestration is
missing for local and event-driven paths.

Evidence:

- retail `ClientCombatSystem::StartPowerBarBuild`,
  `ClientCombatSystem::GetPowerBarLevel`, `ClientCombatSystem::ExecuteAttack`,
  `HandleCommenceAttackEvent`, `HandleAttackerNotificationEvent`,
  `HandleAttackDoneEvent`;
- ACE `MotionTable.GetAttackFrames` scans Attack hooks and is the canonical
  hit-frame source;
- holtburger combat UI tracks `AttackCommenced`, `AttackDone`, victim,
  attacker, defender, evasion, and killed feedback as runtime state.

acdream locations:

- `AttackTargetRequest` exists but no `WorldSession.SendAttack` wrapper was
  found;
- `CombatState` emits `DamageTaken`, `DamageDealtAccepted`, evasion,
  `AttackDone`, and `KillLanded`;
- `GameEventWiring` registers combat event parsers;
- `AnimationSequencer.PlayAction` can play the swing once the command is known.

Gaps:

- combat-mode enum values are currently non-retail for missile/magic;
- melee/missile attack request builders need to be split to retail layouts:
  `0x0008 targetGuid, attackHeight, power` and
  `0x000A targetGuid, attackHeight, accuracy`;
- `CombatCommenceAttack (0x01B8)` is enumerated but not parsed/wired;
- `AttackDone (0x01A7)` and attacker/defender/death notification parsers need
  ACE/holtburger fixtures before downstream animation can trust them;
- `CombatState` has no `CurrentMode`, no attack sequence active flag, no
  selected target, and no power-bar state;
- no local predictive swing on attack request;
- hit reactions (Twitch/Stagger/Tipped/FallDown) are not mapped from defender
  notifications;
- style changes for draw/sheath do not run the full style-transition chain.

Tests to add:

- parse/wire `CombatCommenceAttack`;
- `CombatAnimationCoordinator` maps height/power/style to attack command;
- defender hit quadrant maps to a stable flinch command;
- `AttackHook` dispatch is one-shot.

### 6. Spell Casting

Status: outbound cast packets and spellbook/enchantment state exist; visible
cast-stage animation is missing.

Evidence:

- retail `ClientMagicSystem::CastSpell` and `FreeHandsAndCastSpell`;
- `gmSpellcastingUI::Cast` calls `ClientMagicSystem::CastSpell`;
- outbound cast actions are `0x0048` untargeted (`spellId`) and `0x004A`
  targeted (`targetGuid`, `spellId`);
- retail/ACE school order is `War=1`, `Life=2`, `Item=3`, `Creature=4`,
  `Void=5`;
- MotionCommand spell catalogue above;
- `GameEventWiring` wires spellbook/enchantment updates but not casting
  animation.

acdream locations:

- `CastSpellRequest` targeted/untargeted builders;
- `Spellbook`, `SpellTable`, `GameEventWiring` spell handlers;
- `AnimationHookRouter` already routes hooks to audio/VFX sinks.

Gaps:

- no cast coordinator reading server `UpdateMotion`, spellcasting chat,
  `PlayScript.Fizzle`, `UseDone`, and errors into one local cast timeline;
- no fizzle/interruption animation mapping from `PlayScript.Fizzle = 0x51`
  (ACE sends speed `0.5`) and `WeenieError`;
- no recoil/release state;
- no local immediate cast animation on request.
- `MagicSchool` enum currently needs conformance against the retail/ACE order.

Tests to add:

- spell school/effect classifier maps to MagicBlast/MagicSelf/MagicPortal;
- fizzle error maps to a one-shot action or recovery state once retail
  command is confirmed;
- cast request triggers local action overlay without waiting for enchantment.

### 7. Emotes

Status: inbound text parsers and chat display exist; motion command-list
emotes likely animate if server emits them. Slash-command-to-emote wire and
text-event-to-animation are missing.

Evidence:

- retail `ClientCommunicationSystem::DoEmote`, `HelpEmote`,
  `DoEmoteList`, `InitializeEmoteInputActionHash`;
- retail `ClientCommunicationSystem::Pose` looks up a token in
  `ChatPoseTable`, issues the motion command locally, then sends SoulEmote;
- `ChatEmoteData::Pack`;
- ACE MotionCommand ChatEmote range.

acdream locations:

- `EmoteText` and `SoulEmote` top-level parsers;
- `ChatLog.OnEmote` / `OnSoulEmote`;
- `GameWindow.OnLiveMotionUpdated` command-list `PlayAction` route.

Gaps:

- no outbound `Communication_Emote (0x01DF)` or
  `Communication_SoulEmote (0x01E1)` GameAction builder found;
- `MoveToState` currently writes zero command-list entries, so the client
  cannot yet send pose/emote commands in the retail motion-state path;
- `ChatInputParser` has no `/em`, `/emote`, `/me`, `/sit`, `/kneel`,
  `/sleep`, or `/lie` parsing;
- `EmoteText`/`SoulEmote` text events do not carry an emote id, so they
  should not be used as the primary animation source unless retail proves a
  deterministic text -> command mapping;
- held postures need `SetCycle`, not `PlayAction`.

Tests to add:

- `MotionCommandResolver` reconstructs representative ChatEmotes;
- command-list Wave routes to `PlayAction`;
- persistent Sit/Meditate routes to `SetCycle`.

### 8. Death Animations

Status: death chat and killer notifications exist; pose transition is missing.

Evidence:

- retail `CommandInterpreter::PlayerIsDead` checks forward command
  `0x40000011`;
- MotionCommand `Sanctuary = 0x10000057` is an action and must not be used as
  the persistent death state;
- MotionCommand `Dead = 0x40000011` and `Fallen = 0x40000008` are persistent
  states;
- `PlayerKilled` top-level message and `KillerNotification (0x01AD)` are
  parsed/wired.

acdream locations:

- `PlayerKilled`, `ChatLog.OnPlayerKilled`;
- `CombatState.OnKillerNotification`;
- `MotionCommand.Dead` currently incorrectly comments `0x10000057` in
  `MotionInterpreter`; this should be split into `Sanctuary` action and
  `Dead` substate before death work.

Gaps:

- no explicit death animation coordinator;
- no hit-direction-aware fall;
- no dead-pose persistence or respawn reset.

Tests to add:

- death event plays Sanctuary then persists Dead/Fallen;
- movement is blocked while Dead/Fallen;
- respawn/reset returns to Ready.

### 9. Item-Use Animations

Status: outbound use builders exist; local visible use animations are missing.

Evidence:

- retail `ItemHolder::UseObject`;
- MotionCommand item/use states: Pickup, StoreInBackpack, Eat, Drink,
  Reading, HouseRecall, LifestoneRecall.

acdream locations:

- `InteractRequests.BuildUse` / `BuildUseWithTarget`;
- `ItemRepository`, appraise/use-done event enum.

Gaps:

- no `WorldSession.SendUse` wrapper found;
- `UseDone (0x01C7)` is enumerated but not parsed/wired;
- `0xF754 PlayScriptId` is wired, but target anchoring and speed handling need
  audit; `0xF755 PlayScriptType` is not wired;
- no item-class-to-motion mapping.

Tests to add:

- potion use maps to Drink;
- food maps to Eat;
- scroll/book maps to Reading;
- recall spell/item maps to recall command once retail source is confirmed.

### 10. Mounting / Dismounting

Status: not implemented; likely not relevant to retail AC character movement.

Evidence: `r03` lists `Graze` as a monster-only/mount-like stance, but no
player mount feature has been verified in retail references in this audit.

Action: defer until a server/content feature requires it. Do not invent
mounting behavior.

### 11. Floating-Point / Polish

Status: partially implemented.

Evidence:

- `AnimationSequencer.MultiplyCyclicFramerate` exists and is tested;
- `LocalAnimationSpeed` exists in `MovementResult`;
- PosFrame deltas are accumulated in `AnimationSequencer`.

Gaps:

- root-motion deltas are not composed into entity/body transforms;
- remote animation speed scaling is tied to ForwardSpeed/SidestepSpeed/TurnSpeed
  only when UpdateMotion carries them;
- style-transition and modifier physics combination are incomplete.

Tests to add:

- same-motion/different-speed rescale remains green;
- root-motion delta is consumed by an integration coordinator;
- modifiers combine velocity/omega instead of replacing base cycle physics.

## Implementation Order

1. Extract an `AnimationCommandRouter` in Core/App-adjacent code that owns
   `SetCycle` vs `PlayAction` routing for full 32-bit commands. Move the
   command-list logic out of `GameWindow.OnLiveMotionUpdated` into tests.
2. Add missing MotionCommand constants and fix the `Dead`/`Sanctuary`
   distinction.
3. Fix combat wire conformance first: combat-mode enum, split attack builders,
   `CombatCommenceAttack`, `AttackDone`, damage/evasion/death notification
   parsers, and fixtures from ACE/holtburger.
4. Wire `CombatState.CurrentMode` and `WorldSession.SendAttack/ChangeCombatMode`;
   trigger local and remote swing overlays through the router.
5. Add spell-cast event/state wiring: `WorldSession.SendCast`, school enum
   conformance, `UpdateMotion` cast actions, spellcasting chat,
   `PlayScript.Fizzle`, `UseDone`, and errors.
6. Add outbound emote/soul-emote builders, MoveToState command-list emission,
   chat parser aliases, and posture routing.
7. Add item-use wrappers, `UseDone`, script target anchoring, and
   item-class-to-motion mapping.
8. Add death coordinator and respawn reset.
9. Port full ACE style-transition/modifier/action queue semantics into a
   `MotionTableWalker` or equivalent, replacing `SetCycle` special cases only
   after the category tests cover current behavior.
10. Apply/consume root motion where retail expects it; leave purely decorative
   PosFrames un-applied when decomp/ACE proves they should not move the body.

## Visual Sign-Off Points

The agent can build, test, and live-launch autonomously, but these require
user visual confirmation before claiming complete:

- local attack swing + defender flinch;
- local spell windup -> release/fizzle;
- local `/wave` and persistent sit/lie/kneel/sleep;
- local death pose and respawn recovery;
- potion drink/eat/read animations;
- remote observer view for all of the above.
