mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-07 13:43:51 +00:00
Replace the 2,200-line monolithic AnimationController (goto-driven, single class, untestable) with a composed FSM architecture per refactor.md. New subsystem (src/rendering/animation/ — 16 headers, 10 sources): - CharacterAnimator: FSM composer implementing ICharacterAnimator - LocomotionFSM: idle/walk/run/sprint/jump/swim/strafe - CombatFSM: melee/ranged/spell cast/stun/hit reaction/charge - ActivityFSM: emote/loot/sit-down/sitting/sit-up - MountFSM: idle/run/flight/taxi/fidget/rear-up (per-instance RNG) - AnimCapabilitySet + AnimCapabilityProbe: probe once at model load, eliminate per-frame hasAnimation() linear search - AnimationManager: registry of CharacterAnimator by GUID - EmoteRegistry: DBC-backed emote command → animId singleton - FootstepDriver, SfxStateDriver: extracted from AnimationController animation_ids.hpp/.cpp moved to animation/ subdirectory (452 named constants); all include paths updated. AnimationController retained as thin adapter (~400 LOC): collects FrameInput, delegates to CharacterAnimator, applies AnimOutput. Priority order: Mount > Stun > HitReaction > Spell > Charge > Melee/Ranged > CombatIdle > Emote > Loot > Sit > Locomotion. STAY_IN_STATE policy when all FSMs return valid=false. Bugs fixed: - Remove static mt19937 in mount fidget (shared state across all mounted units) — replaced with per-instance seeded RNG - Remove goto from mounted animation branch (skipped init) - Remove per-frame hasAnimation() calls (now one probe at load) - Fix VK_INDEX_TYPE_UINT16 → UINT32 in shadow pass Tests (4 new suites, all ASAN+UBSan clean): - test_locomotion_fsm: 167 assertions - test_combat_fsm: 125 cases - test_activity_fsm: 112 cases - test_anim_capability: 56 cases docs/ANIMATION_SYSTEM.md added (architecture reference).
4.9 KiB
4.9 KiB
Animation System
Unified, FSM-based animation system for all characters (players, NPCs, companions).
Every character uses the same CharacterAnimator — there is no separate NPC/Mob animator.
Architecture
AnimationController (thin adapter — bridges Renderer ↔ CharacterAnimator)
└─ CharacterAnimator (FSM composer — implements ICharacterAnimator)
├─ CombatFSM (stun, hit reaction, spell cast, melee, ranged, charge)
├─ ActivityFSM (emote, loot, sit/stand/kneel/sleep)
├─ LocomotionFSM (idle, walk, run, sprint, jump, swim, strafe)
└─ MountFSM (mount idle, mount run, flight)
AnimationManager (registry of CharacterAnimator instances by ID)
AnimCapabilitySet (probed once per model — cached resolved anim IDs)
AnimCapabilityProbe (queries which animations a model supports)
Priority Resolution
CharacterAnimator::resolveAnimation() runs every frame. The first FSM to
return a valid AnimOutput wins:
- Mount — if mounted, return
MOUNT(overrides everything) - Combat — stun > hit reaction > spell > charge > melee/ranged > combat idle
- Activity — emote > loot > sit/stand transitions
- Locomotion — run/walk/sprint/jump/swim/strafe/idle
If no FSM produces a valid output, the last animation continues (STAY policy).
Overlay Layer
After resolution, applyOverlays() substitutes stealth animation variants
(stealth idle, stealth walk, stealth run) without changing sub-FSM state.
File Map
Headers (include/rendering/animation/)
| File | Purpose |
|---|---|
i_animator.hpp |
Base interface: onEvent(), update() |
i_character_animator.hpp |
20 virtual methods (combat, spells, emotes, mounts, etc.) |
character_animator.hpp |
FSM composer — the single animator class |
locomotion_fsm.hpp |
Movement states: idle, walk, run, sprint, jump, swim |
combat_fsm.hpp |
Combat states: melee, ranged, spell cast, stun, hit reaction |
activity_fsm.hpp |
Activity states: emote, loot, sit/stand/kneel |
mount_fsm.hpp |
Mount states: idle, run, flight, taxi |
anim_capability_set.hpp |
Probed capability flags + resolved animation IDs |
anim_capability_probe.hpp |
Probes a model for available animations |
anim_event.hpp |
AnimEvent enum (MOVE_START, MOVE_STOP, JUMP, etc.) |
animation_manager.hpp |
Central registry of CharacterAnimator instances |
weapon_type.hpp |
WeaponLoadout, RangedWeaponType enums |
emote_registry.hpp |
Emote name → animation ID lookup |
footstep_driver.hpp |
Footstep sound event driver |
sfx_state_driver.hpp |
State-transition SFX (jump, land, swim enter/exit) |
i_anim_renderer.hpp |
Interface for renderer animation queries |
Sources (src/rendering/animation/)
| File | Purpose |
|---|---|
character_animator.cpp |
ICharacterAnimator implementation + priority resolver |
locomotion_fsm.cpp |
Locomotion state transitions + resolve logic |
combat_fsm.cpp |
Combat state transitions + resolve logic |
activity_fsm.cpp |
Activity state transitions + resolve logic |
mount_fsm.cpp |
Mount state transitions + resolve logic |
anim_capability_probe.cpp |
Model animation probing |
animation_manager.cpp |
Registry CRUD + bulk update |
emote_registry.cpp |
Emote database |
footstep_driver.cpp |
Footstep timing logic |
sfx_state_driver.cpp |
SFX transition detection |
Controller (include/rendering/animation_controller.hpp + src/rendering/animation_controller.cpp)
Thin adapter that:
- Collects per-frame input from camera/renderer →
CharacterAnimator::FrameInput - Forwards state changes (combat, emote, spell, mount, etc.) →
CharacterAnimator - Reads
AnimOutput→ applies viaCharacterRenderer - Owns footstep and SFX drivers
Key Types
AnimEvent— discrete events:MOVE_START,MOVE_STOP,JUMP,LAND,MOUNT,DISMOUNT, etc.AnimOutput— result of FSM resolution:{animId, loop, valid}.valid=falsemeans STAY.AnimCapabilitySet— probed once per model load. Caches resolved IDs and capability flags.CharacterAnimator::FrameInput— per-frame input struct (movement flags, timers, animation state queries).
Adding a New Animation State
- Decide which FSM owns the state (combat, activity, locomotion, or mount).
- Add the state enum to the FSM's
Stateenum. - Add transitions in the FSM's
resolve()method. - Add resolved ID fields to
AnimCapabilitySetif the animation needs model probing. - If the state needs external triggering, add a method to
ICharacterAnimatorand implement inCharacterAnimator.
Tests
Each FSM has its own test file in tests/:
test_locomotion_fsm.cpptest_combat_fsm.cpptest_activity_fsm.cpptest_anim_capability.cpp
Run all tests:
cd build && ctest --output-on-failure