# Animation Pipeline

V4 is one game that has to animate like six. A tactical-FPS operator leaning out
of cover, a social-stealth assassin dragging a body, a real-time-tactics
specialist holding a squad pose, a Wukong-class action-RPG hero chaining a combo
into a transformation morph, an RTS unit cycling work-and-march loops, and a 2D
run-and-gun pilot flipping through Paper2D frames — all of them are meant to
ride the **same modern UE5 animation stack**: AnimBlueprints with per-cell
sub-states, Motion Matching for traversal-heavy areas, IK retargeting through
one IKRig per body archetype, Motion Warping for contact alignment, and the
MetaHuman pipeline for principal faces. The organizing idea mirrors V4's GAS
layer ([./gas-layout.md](./gas-layout.md)): **share one skeleton and one master
AnimBlueprint per body family, then specialize with cell-specific slots and
state machines** so a costume or a verb is an addition, not a fork.

What actually lives in the repo today is the **contract and validation layer**
for that stack, plus two genuinely runtime helpers. The `V4Animation` module is
small and honest: a set of `USTRUCT` specs, two data-asset manifests that build
and self-validate the required AnimBlueprints / motion-matching datasets /
IKRigs / retargeters, a real hit-reaction-and-wake-up **chooser table** that
runs its lookups in C++, and a **motion-warping handler** that calls straight
into Unreal's MotionWarping plugin. Unlike V2's fighting-game animation chapter
— which was pure config behind unlinked plugins — V4's module **links the real
animation plugins** and **makes a real plugin call**, but the heavy authored
content (compiled AnimBlueprints, pose-search databases, IKRig assets, montages,
MetaHuman heads) is **not yet present as binary `.uasset`**. This page is part
of the **Combat, Character & Input Systems** group; the section hub is
[../V4_ARCHITECTURE.md](../V4_ARCHITECTURE.md).

## What ships, honestly

The `V4Animation` module is **real C++ that builds and is tested**, but it is a
specification-and-runtime-helpers layer, not the authored animation graph. Five
honest qualifications, so the rest of the page reads at face value:

1. **The module links the real animation plugins.** `V4Animation.Build.cs:9-22`
   adds `AnimGraphRuntime`, `PoseSearch`, `Mover`, `Chooser`, and
   `MotionWarping` to its public dependencies — alongside `Core`, `CoreUObject`,
   `Engine`, and `V4Core`. This is a step beyond V2, whose animation module
   linked none of them. But linking is not calling: only one of those plugins
   (`MotionWarping`) is actually invoked from C++ today.

2. **Two of the four classes are validated config; two are runtime.**
   `UV4AnimBlueprintManifest` and `UV4AnimationPipelineManifest` are
   `UDataAsset`s that _describe_ the required AnimBlueprints and the
   motion-matching / IKRig / retarget / MetaHuman pipeline and then _validate_
   that description. `UV4AnimationChooserTable` and `UV4MotionWarpingHandler` do
   real work at runtime — a data-driven decision and a real plugin call,
   respectively.

3. **There are zero binary `.uasset` animation assets.** `V4/ue/Content` holds
   **no** `.uasset` files at all; the animation content directory
   (`V4/ue/Content/V4Animation`) instead ships **47 `*.uasset.v4asset.json`
   descriptors** across eight subdirectories (`AnimationBlueprints`, `Choosers`,
   `IKRig`, `MetaHuman`, `Mocap`, `MotionMatching`, `Retargeters`, `Takedowns`).
   These JSON files mirror — and slightly extend — the C++ specs (the
   `ABP_Operator_Master` descriptor lists a `Takedown` slot the C++ default does
   not), but no skeleton, AnimBlueprint, pose-search database, IKRig, or montage
   binary exists for the soft names to resolve to.

4. **The animation classes are not yet wired into gameplay.** Outside the module
   itself, the _only_ consumer of `UV4AnimationChooserTable`,
   `UV4MotionWarpingHandler`, `UV4AnimationPipelineManifest`, and
   `UV4AnimBlueprintManifest` is the automation spec
   (`V4Tests/Private/V4AnimationTests/AnimBPCompileSpec.cpp`). No ability, pawn,
   or combat component instantiates them in the live path yet. They are
   **shippable units awaiting an integration call site.**

5. **There is no AnimNotifyState bridge, and no montages.** V2 turned montage
   timelines into combat data through real `UAnimNotifyState` subclasses (hitbox
   / armor / cancel-window). V4 has **none** of those in `V4Animation`, and **no
   montage `.uasset`** anywhere. The frame-accurate combat windows the GAS verbs
   depend on — dodge i-frames, the parry window — are computed in **combat** C++
   components in `V4ActionRPG`, not driven by animation notifies (see
   [How the pipeline connects](#how-the-pipeline-connects-and-where-it-does-not-yet)).

Everything below is real where it cites a function and a line; where it names an
authored asset or a plugin runtime, that is the specified-but-pending part.

## The module surface

`V4Animation` is four public classes over `V4AnimationTypes.h`, which defines
the shared enums and structs. The enums are the vocabulary the rest of the
module keys on: `EV4MotionWarpContext` (`Breach / Throw / Finisher`,
`V4AnimationTypes.h:8`), `EV4HitReactionType`
(`Light / Heavy / Knockdown / Death`, `:16`), and `EV4WakeupType`
(`BackRise / RollLeft / RollRight / KipUp`, `:25`). The cell identity itself is
shared from core — `EV4RulesetCell` (`V4Core/Public/V4CellState.h:7`:
`None, Tactical, Stealth, Tactics, ActionRPG, RTS, Arcade`) — so the
motion-matching datasets can be keyed by the same join key the GAS layer uses.

```mermaid
flowchart TD
    GAS["GAS verb<br/>(UGA_* picks a montage/state)"] --> ABP
    INPUT["EnhancedInput + FMotionParser"] --> ABP
    ABP["Master AnimBlueprint per body archetype<br/>state machines + montage slots"]
    ABP --> SRC{Locomotion source?}
    SRC -->|traversal-heavy cell| MM["Motion Matching dataset<br/>pose history 8, trajectory 600 ms"]
    SRC -->|FPS / RTS / standard| RM["Root-motion AnimBP graph"]
    MM --> POSE["Local pose"]
    RM --> POSE
    POSE --> IK["IKRig retarget<br/>(one rig per archetype)"]
    IK --> FINAL["Final pose to mesh"]
    GAS -. hit / wake-up .-> CHOOSE["UV4AnimationChooserTable<br/>ChooseHitReaction / ChooseWakeup"]
    CHOOSE -.-> ABP
    GAS -. breach / throw / finisher .-> WARP["UV4MotionWarpingHandler<br/>AddOrUpdateWarpTargetFromTransform"]
    WARP -.-> FINAL
```

The dashed edges are the two runtime helpers; the solid path (AnimBP → source →
retarget → mesh) is the authored graph that today exists as JSON descriptors and
validated specs rather than compiled assets.

## AnimBlueprint layout — masters, slots, and state machines

The AnimBlueprint design is captured by `FV4AnimBlueprintSpec`
(`V4AnimationTypes.h:94`): an `AssetName`, a list of `RequiredSlots` (montage
slot names), a list of `RequiredStateMachines`, and a `bUsesFlipbooks` flag for
the 2D exception. `UV4AnimBlueprintManifest::LoadDefaultSpecs`
(`V4AnimBlueprintManifest.cpp:32`) builds **ten** master AnimBlueprints — more
than the architecture monolith's six, because it also covers hostiles and
vehicles:

| AnimBlueprint                | Required slots                      | State machines                       |
| ---------------------------- | ----------------------------------- | ------------------------------------ |
| `ABP_Operator_Master`        | Cover, ADS, Crouch, Prone, BodyDrag | Locomotion, Weapon, Injured          |
| `ABP_RTSUnit_Master`         | FactionOverride                     | UnitLocomotion, Work                 |
| `ABP_RTST_Specialist_Master` | SquadPose, CommandPose              | SpecialistLocomotion, TacticalAction |
| `ABP_ARPGHero_Master`        | Combo, Parry, Dodge, Transformation | CombatCombo, TransformationMorph     |
| `ABP_NPC_Civilian`           | Panic, DropAndCover                 | Idle, PanicRun                       |
| `ABP_2DOperator`             | Flipbook, Weapon                    | ContraCellFlipbook _(flipbooks)_     |
| `ABP_NPC_Hostile_FPS`        | Combat, Suppression, Vault          | HostileFPSLocomotion, WeaponResponse |
| `ABP_NPC_Hostile_RTST`       | Patrol, Alert, ConeAim              | RTSTPatrol, HostileTacticalAction    |
| `ABP_Vehicle_Driver`         | VehicleSeat, Steering, ExitVehicle  | DriverPose, VehicleTransition        |
| `ABP_Vehicle_Gunner`         | DoorGunner, AimOffset, Reload       | GunnerPose, MountedWeapon            |

The pattern is the cell-sharing thesis made concrete: each entry is a **master
graph for one body/role family**, and the cell verbs hang off it as named
**montage slots** (cover-lean, ADS, body-drag, the ARPG combo/parry/dodge/morph)
layered over a small set of **state machines** (always a locomotion machine plus
one or two role machines). `ABP_2DOperator` is the deliberate outlier — its sole
state machine is `ContraCellFlipbook` and it is the only spec with
`bUsesFlipbooks = true`, encoding that the Contra cell is Paper2D rather than
skeletal.

`ValidateRequiredSpecs` (`:78`) is a genuine self-check, not a tautology: it
constructs a fresh required manifest, then for every required spec confirms the
actual manifest contains it, contains _all_ of its required slots and state
machines (`ContainsAllNames`, `:5`), and matches the flipbook flag exactly. A
manifest that dropped the `BodyDrag` slot or flipped a flipbook flag fails. The
automation spec asserts the count is `10` and spot-checks that
`ABP_Operator_Master` carries `BodyDrag` and `ABP_Vehicle_Gunner` carries
`DoorGunner` (`AnimBPCompileSpec.cpp:27-51`).

## Blending — motion matching, root motion, and shared skeletons

`UV4AnimationPipelineManifest` (`V4AnimationPipelineManifest.h:9`) is the larger
data asset, aggregating the motion-matching datasets, IKRigs, retargeters, and
MetaHuman pipeline that produce a blended pose. `LoadDefaultPipeline`
(`V4AnimationPipelineManifest.cpp:86`) populates them with domain-tuned
defaults.

**Motion matching.** Six `FV4MotionMatchingDatasetSpec`
(`V4AnimationTypes.h:112`) datasets, one per playable cell, each carrying the
architecture's promised parameters: `PoseHistoryFrames = 8`,
`TrajectoryMilliseconds = 600`, and four **distance-field cost weights** —
`StanceCost`, `VelocityCost`, `AccelerationCost`, `FacingCost` — plus the
`BakedArchetypes` the dataset is trained for. The defaults are not uniform: the
Stealth Hitman-sandbox dataset raises `StanceCost` to `1.25` and `FacingCost` to
`1.0` (stealth cares about stance and which way you face), while the Wukong
open-area ARPG dataset raises `VelocityCost` to `1.15` (`:93-120`). The intent
matches the monolith: **motion matching is reserved for traversal-heavy cells**
(Hitman sandbox, Wukong open areas); FPS and RTS use the standard root-motion
AnimBP path, which is why the diagram branches on locomotion source.

**IK retargeting and shared skeletons.** Five `FV4IKRigSpec`
(`V4AnimationTypes.h:145`) entries cover the body archetypes — Adult Male, Adult
Female, Adolescent, Animal Quadruped, Animal Biped — each declaring its IK
**chains**. Bipeds share a six-chain set
(`Root, Spine, LeftArm, RightArm, LeftLeg, RightLeg`, `V4BipedChains()` at
`:8`); the quadruped swaps in four legs (`V4QuadrupedChains()` at `:20`). Two
`FV4RetargeterSpec` (`:160`) entries retarget `MetaHuman → Operator` and
`Mixamo → Operator`, both onto the shared six biped chains. This is the
**one-skeleton-per-archetype** rule: there is no per-operator skeleton, costume
and cosmetic differences ride on the shared rig, and animation authored against
MetaHuman or Mixamo is retargeted in.

**MetaHuman pipeline.** `FV4MetaHumanPipelineSpec` (`V4AnimationTypes.h:178`) is
the most config-shaped piece — five fields, four booleans plus a
`LipSyncProvider` name. `LoadDefaultPipeline` sets all four configured and names
the provider `OVRMetaSounds` (`:133-137`). These are capability flags (Creator
workflow, Animator facial mocap, Live Link Face, lip-sync runtime) declaring
_intent_, not a wired runtime — there is no Live Link or MetaHuman runtime call
in this module.

`ValidatePipeline` (`:140`) is the substantive part. For each of the six
required cells it finds the dataset, then asserts `PoseHistoryFrames == 8`, the
trajectory is within tolerance of `600 ms`, **every distance-field cost is
strictly positive**, and at least one baked archetype is present. For each
archetype it confirms an IKRig exists _and contains every required chain_; for
each retarget source it confirms a `→ Operator` retargeter with the full biped
chain set; and it fails if any MetaHuman flag is unset or the provider is
`None`. The automation spec drives this end-to-end and pins the headline numbers
— `6` datasets, `5` IKRigs, `2` retargeters, the Wukong dataset baked for `Hero`
at depth `8` and `600 ms`, and the `OVRMetaSounds` provider
(`AnimBPCompileSpec.cpp:60-88`).

## The runtime decision: hit-reaction and wake-up choosers

`UV4AnimationChooserTable` (`V4AnimationChooserTable.h:9`) is where the module
stops describing and starts deciding. It models UE5's Chooser-table concept as a
small data-driven matcher with two real lookup functions.

`LoadDefaultRows` (`V4AnimationChooserTable.cpp:3`) seeds two row tables.
Hit-reaction rows are `{MinDamage, MaxDamage, bRequiresHeadshot, Reaction}`: a
headshot row (`0–999`, headshot-only → `Death`) first, then `0–20 → Light`,
`20–60 → Heavy`, `60–99 → Knockdown`. Wake-up rows are
`{MinDownTime, MaxDownTime, bRequiresThreatNearby, Wakeup}`: `0–2 s → BackRise`,
a threat-only `2–5 s → RollLeft`, `2–5 s → RollRight`, `5 s+ → KipUp`.

`ChooseHitReaction(Damage, bHeadshot)` (`:18`) walks rows in order, **skipping
any row whose `bRequiresHeadshot` is unmet**, and returns the first whose
half-open damage band contains the hit; it falls back to `Light` if nothing
matches. Because the headshot row is first and spans the whole range, a 5-damage
headshot returns `Death`, while a 35-damage body shot skips that row and lands
in `Heavy`. `ChooseWakeup(DownTime, bThreatNearby)` (`:36`) is the same shape
gated on threat: 3 seconds down _with_ a nearby threat returns `RollLeft`; the
same 3 seconds _without_ a threat skips that row and returns `RollRight`. The
automation spec asserts exactly those four outcomes
(`AnimBPCompileSpec.cpp:98-101`) — and they would fail against a hardcoded
return, because two of them differ only by the boolean flag. This is the genuine
selection logic an AnimBlueprint's hit-reaction and get-up state machines would
call into; today the call site is the test.

## Motion warping — the one real plugin call

`UV4MotionWarpingHandler` (`V4MotionWarpingHandler.h:11`) is a
`BlueprintSpawnableComponent` and the only place V4 reaches into an animation
plugin runtime. `FV4MotionWarpAlignment` (`V4AnimationTypes.h:34`) describes a
warp request: a context (`Breach / Throw / Finisher`), a `TargetName`, a target
`FTransform`, translation and rotation offsets, and a `bMatchRotation` flag.

`ApplyWarpAlignment` (`V4MotionWarpingHandler.cpp:11`) stores the request,
lazily finds-or-creates a real `UMotionWarpingComponent` on the owning actor
(`GetOrCreateMotionWarpingComponent`, `:49` — `FindComponentByClass`, else
`NewObject` + `AddInstanceComponent` + `RegisterComponent`), and then calls the
**engine plugin API**
`UMotionWarpingComponent::AddOrUpdateWarpTargetFromTransform`
(`MotionWarpingComponent.h:155` in the UE5.5 MotionWarping plugin) with the
computed transform. That is a genuine integration: a breach, a throw, or a
finisher can register a warp target the engine's motion-warping anim node
consumes, so root motion bends to align the operator's hands to a door or the
hero's strike to a boss.

The transform math is in `CalculateAlignedTransform` (`:27`) and is
independently testable without an owner: it starts from the alignment's target
transform, adds the translation offset, and either concatenates the rotation
offset (when `bMatchRotation`) or preserves the source rotation. The automation
spec exercises exactly this — a breach target at `(100,0,0)` yaw `90°` plus a
`(10,0,0)` offset and a `90°` rotation offset yields location X `110.0` and yaw
`180.0` (`AnimBPCompileSpec.cpp:110-118`) — concrete arithmetic that pins the
offset and rotation-compose behavior.

## How cells share and specialize

The animation pipeline is the same shared-spine / narrow-specialization split as
the GAS layer:

- **Shared:** one master AnimBlueprint per body/role family (not per character),
  one IKRig per body archetype with a fixed chain set, the
  `MetaHuman → Operator` and `Mixamo → Operator` retargeters onto a single
  `Operator` target skeleton, the `8`-frame / `600 ms` motion-matching contract,
  and the two runtime helpers (chooser table, warp handler) that are
  cell-agnostic.
- **Specialized per cell:** which montage slots and state machines the master
  graph exposes (cover/ADS for Tactical, body-drag for Stealth, combo/parry/
  dodge/morph for ARPG, faction-override for RTS, flipbooks for Arcade), and
  which motion-matching dataset (with its own cost weights and baked archetypes)
  a traversal-heavy cell loads. Adding a seventh cell means a new master spec,
  its slots/state-machines, optionally a motion-matching dataset, and a
  `ValidatePipeline` arm — with no change to the shared skeletons or helpers.

Because there is exactly one skeleton per archetype and costumes ride on top, a
new outfit or a new operator is an asset addition, never a rig fork — the same
"mutually exclusive at run-time, shared by construction" property the GAS layer
enforces for attribute sets.

## How the pipeline connects (and where it does not yet)

Animation sits **downstream of GAS and input**. A GAS verb
([./gas-layout.md](./gas-layout.md)) decides _what_ a character is doing; the
AnimBlueprint expresses it as a pose. Input feeds the same chain: EnhancedInput
IMCs and the reused `V4Input::FMotionParser` (a 16-frame directional recognizer,
`V4Input/Public/V4MotionParser.h:10`) drive the ARPG combo verbs that the
`ABP_ARPGHero_Master` `Combo`/`Parry`/`Dodge` slots animate — detailed in
[./input-pipeline.md](./input-pipeline.md).

The honest seam is the frame windows. The gas-layout page notes abilities "drive
the dodge/parry/posture frame windows"; those windows are computed today in
**combat** components — `UV4DodgeComponent` (`InvincibilityFrames = 9`,
`IsInvincibleAtFrame`) and `UV4ParryComponent` (`ParryWindowFrames = 6`,
`IsFrameInsideParryWindow`) in `V4ActionRPG` — **not** by animation notify
states, of which `V4Animation` ships none. So the integer-frame combat truth and
the visual animation are presently parallel tracks: the C++ windows are real and
tested, and the montages that would visualize them are not authored. Wiring the
chooser table and warp handler into those verbs, and authoring the AnimBlueprint
and montage `.uasset` the specs describe, is the remaining integration work.

The cloth, hair, facial-performance, and lip-sync _presentation_ of these poses
— the MetaHuman runtime side the `FV4MetaHumanPipelineSpec` flags stand in for —
belongs to the presentation layer, not this module; see
[./presentation-pipelines.md](./presentation-pipelines.md).

## Related

- [GAS Layout](./gas-layout.md) — the verbs that pick montages and the
  combat-side dodge/parry/posture frame windows animation visualizes
- [Input Pipeline](./input-pipeline.md) — EnhancedInput IMCs and the
  `FMotionParser` that feed the ARPG combo/parry/dodge animation slots
- [Presentation Pipelines](./presentation-pipelines.md) — cloth, hair, facial
  performance, and lip-sync that dress the poses this module produces
- The section hub: [../V4_ARCHITECTURE.md](../V4_ARCHITECTURE.md)
