22 KiB
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,MagicSchoolenum 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 ACEMotionTable.GetObjectSequencemulti-link style transition chain; - held posture/emote commands need a small command resolver and tests around one-shot-vs-persistent routing;
- death needs explicit
Sanctuaryaction ->Dead/Fallenpersistence rather than relying on chat/health side effects.
Evidence Sources
Named retail decomp:
CMotionTable::is_allowedat0x005226C0CMotionTable::get_linkat0x00522710CSequence::update_internalat0x005255D0CMotionInterp::adjust_motionat0x00528010CMotionInterp::charge_jumpat0x005281C0CMotionInterp::get_jump_v_zat0x00527AA0CMotionInterp::jumpat0x00528780CMotionInterp::apply_current_movementat0x00528870CMotionInterp::HitGroundat0x00528AC0CMotionInterp::LeaveGroundat0x00528B00CMotionInterp::DoMotionat0x00528D20CMotionInterp::DoInterpretedMotionat0x00528360ClientCombatSystem::HandleCommenceAttackEventat0x0056AD20ClientCombatSystem::SetCombatModeat0x0056BE30ClientCombatSystem::StartAttackRequestat0x0056C040ClientCombatSystem::EndAttackRequestat0x0056C0E0ClientCombatSystem::StartPowerBarBuildat0x0056ADB0ClientCombatSystem::GetPowerBarLevelat0x0056ADE0ClientCombatSystem::ExecuteAttackat0x0056BB70ClientCombatSystem::HandleDefenderNotificationEventat0x0056C920ClientCombatSystem::HandleEvasionDefenderNotificationEventat0x0056C620ClientCombatSystem::HandlePlayerDeathEventat0x0056C320ClientCombatSystem::HandleAttackerNotificationEventat0x0056B420ClientCombatSystem::HandleAttackDoneEventat0x0056C500CM_Combat::Event_ChangeCombatModeat0x006A9A70CM_Combat::Event_TargetedMeleeAttackat0x006A9C10CM_Combat::Event_TargetedMissileAttackat0x006A9D60AttackHook::Executeat0x00526B70gmSpellcastingUI::Castat0x004C6050ClientMagicSystem::CastSpellat0x00568040ClientMagicSystem::FreeHandsAndCastSpellat0x00566EF0ClientMagicSystem::GetAppropriateSpellFormulaat0x00567D50CM_Magic::Event_CastUntargetedSpellat0x006A3150CM_Magic::Event_CastTargetedSpellat0x006A3040ItemHolder::UseObjectat0x00588A80CM_Inventory::Event_UseEventat0x006AC3B0CM_Inventory::Event_UseWithTargetEventat0x006AC480CM_Item::DispatchUI_UseDoneat0x006A8510CommandInterpreter::PlayerIsDeadat0x006B3D70SmartBox::HandlePlayScriptIDat0x00452020CM_Physics::DispatchSB_PlayScriptIDat0x006ACC40CM_Physics::DispatchSB_PlayScriptTypeat0x006AC6E0ClientCommunicationSystem::DoEmoteat0x00578AD0ClientCommunicationSystem::Poseat0x00580480ClientCommunicationSystem::Handle_Communication__HearEmoteat0x0057CBE0ClientCommunicationSystem::Handle_Communication__HearSoulEmoteat0x0057D020ChatPoseTable::InqChatPoseCommandat0x00570AD0ChatEmoteData::Packat0x004FCE80
Cross-reference material:
docs/research/deepdives/r03-motion-animation.mdC:\Users\erikn\source\repos\acdream\references\ACE\Source\ACE.Server\Physics\Animation\MotionTable.csC:\Users\erikn\source\repos\acdream\references\ACE\Source\ACE.Server\Physics\Animation\MotionInterp.csC:\Users\erikn\source\repos\acdream\references\ACE\Source\ACE.Entity\Enum\MotionCommand.csC:\Users\erikn\source\repos\acdream\references\holtburger\apps\holtburger-cli\src\pages\game\combat.rsC:\Users\erikn\source\repos\acdream\references\holtburger\crates\holtburger-core\src\client\messages.rsC:\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.csSetCycle(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, andmove_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.csOnLiveMotionUpdatedis the main inbound motion/action router.OnLiveVectorUpdatedseeds airborne jump arcs and Falling cycles.OnLivePositionUpdatedsnaps positions and lands airborne remotes.TickAnimationsadvances sequencers and drains hooks.UpdatePlayerAnimationdrives 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 distinct0x0008melee and0x000Amissile 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 valuesNonCombat=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/ChangeCombatModewrappers and animation-side subscriptions.
Retail Command Catalogue To Use
From ACE MotionCommand.cs + r03-motion-animation.md:
- Locomotion substates: Ready
0x41000003, WalkForward0x45000005, WalkBackward0x45000006, RunForward0x44000007, TurnRight0x6500000D, TurnLeft0x6500000E, SideStepRight0x6500000F, SideStepLeft0x65000010, Falling0x40000015. - Held/posture substates: Crouch
0x41000012, Sitting0x41000013, Sleeping0x41000014, Dead0x40000011, Fallen0x40000008. - Item/use substates: Reload
0x40000016, Unload0x40000017, Pickup0x40000018, StoreInBackpack0x40000019, Eat0x4000001A, Drink0x4000001B, Reading0x4000001C. - Spell substates/actions: CastSpell
0x400000D3, MagicBlast0x4000002B, MagicSelfHead0x4000002C, MagicSelfHeart0x4000002D, MagicBonus..MagicPenalty0x4000002E..0x40000034, MagicTransfer0x40000035, MagicEnchantItem0x40000037, MagicPortal0x40000038, MagicPray0x40000039, MagicPowerUp01..100x1000006F..0x10000078, MagicPowerUp01Purple..10Purple0x1000012B..0x10000134. - Combat actions: Sanctuary
0x10000057, ThrustMed/Low/High0x10000058..0x1000005A, SlashHigh/Med/Low0x1000005B..0x1000005D, BackhandHigh/Med/Low0x1000005E..0x10000060, Shoot0x10000061, AttackHigh/Med/Low1..60x10000062..0x1000006Aand0x10000186..0x1000018E, MissileAttack1..30x100000D0..0x100000D2, SpecialAttack1..30x100000CD..0x100000CF, dual-wield/offhand ranges0x10000173..0x1000019A. - ChatEmote actions: Wave
0x13000087, BowDeep0x1300007D, Laugh0x13000080, Point0x13000084, Salute0x1300008A, Kneel0x13000092, HaveASeat0x13000152, DrudgeDance0x13000151, plus the full0x1200/0x1300ranges inr03. - Persistent emote states:
0x430000EA..0x430000FD, SnowAngelState0x43000118, CurtseyState0x4300011A, AFKState0x4300011B, MeditateState0x4300011C, SitState0x4300013A, SitCrossleggedState0x4300013B, SitBackState0x4300013C, PossumState0x43000142, HaveASeatState0x43000145. 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;
MotionInterpreterdoes not yet own fullMotionState, so non-locomotion commands cannot be uniformly tested there;- mounted/swimming need dat/retail verification before any implementation.
Tests to add:
- posture state
SetCycletests for Crouch/Sitting/Sleeping; motion_allows_jumpconformance 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
OnLiveMotionUpdatedcommand-list routing outsideGameWindow; - root-motion deltas are accumulated but not applied to remote body transforms.
Tests to add:
- command-list
Wave->PlayActionrouting 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
UpdateMotionwithCommands=[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.GetAttackFramesscans 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:
AttackTargetRequestexists but noWorldSession.SendAttackwrapper was found;CombatStateemitsDamageTaken,DamageDealtAccepted, evasion,AttackDone, andKillLanded;GameEventWiringregisters combat event parsers;AnimationSequencer.PlayActioncan 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, powerand0x000A 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;CombatStatehas noCurrentMode, 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; CombatAnimationCoordinatormaps height/power/style to attack command;- defender hit quadrant maps to a stable flinch command;
AttackHookdispatch is one-shot.
6. Spell Casting
Status: outbound cast packets and spellbook/enchantment state exist; visible cast-stage animation is missing.
Evidence:
- retail
ClientMagicSystem::CastSpellandFreeHandsAndCastSpell; gmSpellcastingUI::CastcallsClientMagicSystem::CastSpell;- outbound cast actions are
0x0048untargeted (spellId) and0x004Atargeted (targetGuid,spellId); - retail/ACE school order is
War=1,Life=2,Item=3,Creature=4,Void=5; - MotionCommand spell catalogue above;
GameEventWiringwires spellbook/enchantment updates but not casting animation.
acdream locations:
CastSpellRequesttargeted/untargeted builders;Spellbook,SpellTable,GameEventWiringspell handlers;AnimationHookRouteralready 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 speed0.5) andWeenieError; - no recoil/release state;
- no local immediate cast animation on request.
MagicSchoolenum 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::Poselooks up a token inChatPoseTable, issues the motion command locally, then sends SoulEmote; ChatEmoteData::Pack;- ACE MotionCommand ChatEmote range.
acdream locations:
EmoteTextandSoulEmotetop-level parsers;ChatLog.OnEmote/OnSoulEmote;GameWindow.OnLiveMotionUpdatedcommand-listPlayActionroute.
Gaps:
- no outbound
Communication_Emote (0x01DF)orCommunication_SoulEmote (0x01E1)GameAction builder found; MoveToStatecurrently writes zero command-list entries, so the client cannot yet send pose/emote commands in the retail motion-state path;ChatInputParserhas no/em,/emote,/me,/sit,/kneel,/sleep, or/lieparsing;EmoteText/SoulEmotetext 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, notPlayAction.
Tests to add:
MotionCommandResolverreconstructs 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::PlayerIsDeadchecks forward command0x40000011; - MotionCommand
Sanctuary = 0x10000057is an action and must not be used as the persistent death state; - MotionCommand
Dead = 0x40000011andFallen = 0x40000008are persistent states; PlayerKilledtop-level message andKillerNotification (0x01AD)are parsed/wired.
acdream locations:
PlayerKilled,ChatLog.OnPlayerKilled;CombatState.OnKillerNotification;MotionCommand.Deadcurrently incorrectly comments0x10000057inMotionInterpreter; this should be split intoSanctuaryaction andDeadsubstate 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.SendUsewrapper found; UseDone (0x01C7)is enumerated but not parsed/wired;0xF754 PlayScriptIdis wired, but target anchoring and speed handling need audit;0xF755 PlayScriptTypeis 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.MultiplyCyclicFramerateexists and is tested;LocalAnimationSpeedexists inMovementResult;- 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
- Extract an
AnimationCommandRouterin Core/App-adjacent code that ownsSetCyclevsPlayActionrouting for full 32-bit commands. Move the command-list logic out ofGameWindow.OnLiveMotionUpdatedinto tests. - Add missing MotionCommand constants and fix the
Dead/Sanctuarydistinction. - Fix combat wire conformance first: combat-mode enum, split attack builders,
CombatCommenceAttack,AttackDone, damage/evasion/death notification parsers, and fixtures from ACE/holtburger. - Wire
CombatState.CurrentModeandWorldSession.SendAttack/ChangeCombatMode; trigger local and remote swing overlays through the router. - Add spell-cast event/state wiring:
WorldSession.SendCast, school enum conformance,UpdateMotioncast actions, spellcasting chat,PlayScript.Fizzle,UseDone, and errors. - Add outbound emote/soul-emote builders, MoveToState command-list emission, chat parser aliases, and posture routing.
- Add item-use wrappers,
UseDone, script target anchoring, and item-class-to-motion mapping. - Add death coordinator and respawn reset.
- Port full ACE style-transition/modifier/action queue semantics into a
MotionTableWalkeror equivalent, replacingSetCyclespecial cases only after the category tests cover current behavior. - 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
/waveand persistent sit/lie/kneel/sleep; - local death pose and respawn recovery;
- potion drink/eat/read animations;
- remote observer view for all of the above.