# Animation Pipeline & Input / Motion Parser

This page covers the two engine modules that sit between a player's fingers and
a fighter's body: **`V2Input`**, which turns raw device events into a
frame-stamped input history and recognizes fighting-game motions from it, and
**`V2Animation`**, which describes how those motions and the combat results are
expressed as poses — skeletons, an AnimBP layer stack, hit-reaction tables, IK,
cloth, hair, and facial performance. The dividing line between them is sharp and
worth stating up front, because it is also the line between "shipped, tested
logic" and "specified, art-pending content": the **input/motion side is real,
deterministic C++ that runs in matches**, while the **animation side is largely
a validated configuration layer plus a thin set of real notify-state bridge
classes**, awaiting the authored `.uasset` content and Unreal plugin wiring it
is designed to drive.

Why build it this way? V2's whole combat core is rollback-deterministic (see
[Combat System — GAS, Frame Data & Determinism](./combat-system-gas-frame-data-and-determinism.md)
and [Rollback Netcode & Tag-Team](./rollback-netcode-and-tag-team.md)), and a
motion parser that reads wall-clock time or floating-point analog history would
poison that determinism. So the parser consumes only integer **frame numbers**
and **bitmask** button state from a fixed-capacity ring buffer, and the same
buffer feeds both live ability activation and a byte-exact replay encoder. The
animation module, by contrast, never touches gameplay state — every facial,
cloth, and gore struct is explicitly flagged `bRollbackSafe` / `bGameplayInert`
/ `bCosmeticOnlyRollbackSafe` — so presentation can be as expensive as the
hardware allows without ever desyncing a match. This page is part of the
"Deterministic Combat Core" set; the section hub is
[../V2_ARCHITECTURE.md](../V2_ARCHITECTURE.md).

## What ships, honestly

**Real and tested (runs in matches).** The entire input pipeline is genuine,
deterministic C++ with a 3,239-line automation spec
(`V2/ue/Source/V2Tests/Automation/Input.Module.spec.cpp`). `FV2_MotionParser`
recognizes 21 distinct fighting-game motions from frame history;
`FV2InputBufferRing` is a real ring buffer that derives press/release edges from
held-button masks; `FV2InputReplayEncoder` produces a byte-exact, CRC-hashed
replay stream. This module is a dependency of `V2Gameplay`, `V2UI`, `V2Netcode`,
`V2Persistence`, and `V2Modes`, and `UV2_GameplayAbility` consumes the buffer
directly — so it is wired into the live game, not a stranded library. The three
animation **notify-state** classes (`UV2HitboxNS`, `UV2ArmorNS`,
`UV2CancelWindowNS`) are also real `UAnimNotifyState` subclasses with working
logic that bridges montages into the combat hitbox system.

**Specified and validated, but art-pending.** Everything else in `V2Animation` —
`SK_V2_Fighter_Base` and its archetype variants, `ABP_V2_Fighter_Base`'s layer
stack, the IK retargeters, the six motion-matching databases, the Mover modes,
Chaos Cloth presets, hair grooms, MetaHuman/Live Link facial pipelines — exists
as `USTRUCT` **configuration** with `FSoftObjectPath` references and capability
booleans, plus builders, validators, and resolution logic, all exercised by
`Animation.Module.spec.cpp`. What is _not_ present: `Content/` holds only **8
`.uasset` files**, every one a `DA_RegionVariant_*` data asset, and **zero**
skeletons, anim blueprints, retargeters, pose-search databases, cloth, or groom
binaries — so the soft paths these structs carry (`/Game/V2/Animation/...`) do
not resolve to authored content. Instead, `Content/V2/Animation/` ships **JSON
contract files** (`SK_V2_Fighter_SkeletonCatalog.json`,
`Mover_V2_CustomModes.json`, `ChaosCloth_V2_CostumePresets.json`,
`Facial_V2_CapturePipeline.json`, …) that mirror the C++ structs as
specifications rather than art. And the `V2Animation` module links only
`Core/CoreUObject/Engine/GameplayTags/V2Combat/V2Core` — **not** the Mover,
PoseSearch, ChaosCloth, or MetaHuman runtimes.

**Plugin-ready but not plugin-integrated.** `V2.uproject` _does_ enable the
`PoseSearch`, `Mover`, and `ChaosClothAsset` plugins, so the project is staged
for that runtime — but no C++ in this module calls into them yet. Treat the
animation chapter of the architecture monolith as a build target the config
layer encodes, not as shipped runtime. Where the prose below describes a
struct's resolution logic (chooser-table lookup, retarget tolerance, cloth
weight-class blending) that logic is real and tested; where it names a plugin or
an asset, that is the specified-but-pending part.

## Two modules, one contract boundary

`V2Input` and `V2Animation` are siblings under `V2/ue/Source` (the full module
list is in [Glossary & Module Topology](./glossary-and-module-topology.md)).
They do not depend on each other; they meet at the combat layer. Input produces
an `FV2MotionRecognition`; combat (`V2Gameplay` / `V2Combat`) decides which move
that activates; animation's notify states, riding on the resulting montage, hand
hitbox keyframes back to combat. The flow:

```mermaid
flowchart LR
  DEV[Enhanced Input device event] --> SAMPLE[FV2InputSample\nframe + direction + button masks]
  SAMPLE --> RING[FV2InputBufferRing\n120-frame ring]
  RING --> PARSER[FV2_MotionParser\nRecognizeMotion / RecognizeAllMotions]
  PARSER --> REC[FV2MotionRecognition\nmotion + startFrame + endFrame]
  REC --> GAS[UV2_GameplayAbility\ninput-buffer window gate]
  GAS --> MONTAGE[GAS montage on ABP layer]
  MONTAGE --> NS[UV2HitboxNS / UV2ArmorNS / UV2CancelWindowNS]
  NS --> COMBAT[V2Combat hitbox keyframes]
  RING -. byte stream .-> ENC[FV2InputReplayEncoder\nV2IN / V2RM / V2RF]
  ENC -. deterministic replay .-> RING
```

## The input model: frames, directions, button masks

Everything starts with `FV2InputSample` (`V2InputTypes.h:4412`). It is
deliberately tiny and integer-only: an `int32 FrameNumber`, an
`EV2InputDirection`, and three `uint64` button masks — `ButtonsHeldMask`,
`ButtonsPressedMask`, `ButtonsReleasedMask` — plus a `bFromReplay` flag. Buttons
are the 32-value `EV2InputButton` enum (light/medium/heavy punch and kick,
grapple, block, sidestep, tag, and the ruleset-specific specials: Drive Impact,
Focus Attack, Heat Burst, Rage Art, X-Ray, Blazin', Guard Impact, the four Soul
Calibur commands, WWE/UFC contextual actions, …). `MakeButtonMask` is exactly
`uint64(1) << static_cast<uint8>(Button)` (`V2InputTypes.cpp:6241`), so up to 64
inputs pack into one word and motion-plus-button tests are bitwise-ands.

Directions use **numpad notation** encoded so the value _is_ the numpad key
(`EV2InputDirection`:
`DownBack=1, Down=2, DownForward=3, Back=4, Neutral=5, Forward=6, UpBack=7, Up=8, UpForward=9`,
with `Invalid=0` hidden). This is what makes the motion tables in
`MotionParser.cpp` read like a frame-data notebook.

The ring is `FV2InputBufferRing` (`V2InputBuffer.h`, `V2InputBuffer.cpp`),
default capacity 120 frames (two seconds at 60 Hz). The important detail is
`AddFrameSample`: callers pass only the _held_ mask for a frame, and the buffer
computes the edges by diffing against the previous sample —
`ButtonsPressedMask = Held & ~PreviousHeld`,
`ButtonsReleasedMask = PreviousHeld & ~Held` (`V2InputBuffer.cpp:31`). Press and
release detection is therefore a property of the buffer, not of the caller,
which is why negative-edge and plink recognition can be pure functions of buffer
contents. The buffer also exposes `WasPressedWithin`, `WasReleasedWithin`, and
`WasDirectionHeldFor`, each scanning newest-to-oldest and breaking once it
passes the window — the same windowed-scan shape the parser uses everywhere.

## The motion parser

`FV2_MotionParser` (`V2MotionParser.h`, `MotionParser.cpp`) is a static,
stateless recognizer. Its surface is three calls:

- `RecognizeMotion(Buffer, Motion, Config, bFacingRight, TriggerButton)` — test
  the buffer for one specific `EV2MotionInput`.
- `RecognizeAllMotions(...)` — run a **priority-ordered** sweep of 21 motions
  and return every match.
- `RecognizeJustFrame(Buffer, RequiredFrame, TriggerButton)` — the one
  frame-exact case, checked separately because it is keyed to a specific frame
  rather than a window.

`EV2MotionInput` enumerates 22 motions beyond `None`: the classics (`QCF`,
`QCB`, `DP`, `RDP`, `HCF`, `HCB`, `FullCircle`/360, `DoubleFullCircle`/720,
`ChargeBack`, `ChargeDown`, `NegativeEdge`, `DoubleTapForward`/`Back`/`Down`/
`DownForward`, `Plink`, `JustFrame`) and the execution-heavy techniques
(`TigerKnee`, `KoreanBackdashCancel`, `WaveDash`, `InstantWhileStanding`,
`StepCancelSidewalk`). `RecognizeAllMotions` iterates them
**most-complex-first** — `DoubleFullCircle` before `FullCircle`,
`TigerKnee`/`HCF`/`HCB` before the quarter-circles — so a 720 is never
mis-reported as a 360 and a tiger-knee is never swallowed by the bare QCF inside
it. This ordering is load-bearing, not cosmetic.

### Tunable windows

All timing comes from `FV2InputParserConfig` (`V2InputTypes.h:4364`), and the
defaults are the ones the architecture promises:
`DefaultBufferWindowFrames = 6`, `TightBufferWindowFrames = 3`,
`LenientBufferWindowFrames = 9`. The parser-only windows are
`MotionHistoryFrames = 45` (the master look-back), `ChargeFrames = 60` (a
one-second hold at 60 Hz), `DoubleTapWindowFrames = 12`,
`BackdashCancelWindowFrames = 8`, `CrouchDashWindowFrames = 8`,
`InstantWhileStandingWindowFrames = 2`, `StepCancelWindowFrames = 5`,
`PlinkWindowFrames = 2`, and `JustFrameWindowFrames = 1`. `RecognizeMotion`
widens its collected window adaptively before matching: a 720 looks back
`2 × MotionHistoryFrames`; a charge looks back at least
`ChargeFrames + LenientBufferWindowFrames + 1`; the Korean backdash and wave
dash expand to cover their multi-beat sequences. Collection itself is
`CollectWindow`, which copies only samples within the window and **normalizes
each direction for facing** as it goes.

### Worked example — a quarter-circle-forward + light

Take the canonical buffer from the spec: frame 1 Neutral, frame 2 Down, frame 3
DownForward, frame 4 Forward with Light pressed. `RecognizeMotion(QCF)`:

1. `CollectWindow` gathers all four samples (within 45 frames) and mirror-
   normalizes them — facing right, they pass through unchanged.
2. `MatchDirectionalSequence` first runs `CollapseDirectionChanges`, which drops
   neutrals and de-duplicates repeats, leaving
   `[Down@2, DownForward@3, Forward@4]`.
3. It walks the required sequence `{Down, DownForward, Forward}` using
   `DirectionMatchesStep` (which treats `DownForward` as satisfying a `Forward`
   step, so diagonal sloppiness is forgiven). `StartFrame` latches to 2,
   `EndFrame` to 4.
4. `EndFrame − StartFrame = 2 ≤ MotionHistoryFrames`, so the recognition is
   valid with `EndFrame = 4` — exactly what `Qcf.EndFrame == 4` asserts. The
   light button is then matched in combat against the move's input tag, not by
   the directional parser (except where a motion explicitly carries a trigger,
   below).

`FV2MotionRecognition::IsValid()` is the gate every path funnels through:
`Motion != None && StartFrame != INDEX_NONE && EndFrame != INDEX_NONE && StartFrame <= EndFrame`
(`V2InputTypes.cpp:7745`).

### Charges, circles, and double-taps

- **Charges** (`RecognizeCharge`) scan backward for a release sample in the
  target direction (Forward for `ChargeBack`, Up for `ChargeDown`), then count
  the _contiguous_ held-charge run before it; the run must be `≥ ChargeFrames`.
  The result is flagged `bFromCharge`. The spec builds 60 Back frames then a
  Forward+Light on frame 61 and asserts both validity and the charge flag.
- **Circles** (`RecognizeCircle`) accumulate a 4-bit cardinal mask
  (forward/down/back/up) across collapsed direction changes; one full `0b1111`
  is a 360, two are a 720. Both then route through `AttachTriggerButton`, which
  extends the recognition to the frame a grapple/trigger button lands within
  `DefaultBufferWindowFrames` — so a 360-throw is only "complete" when the
  button confirms.
- **Double-taps** (`RecognizeDoubleTap`) require a **neutral reset between
  taps** (the `bCanRegisterTap` latch), which is how a held direction is
  distinguished from a deliberate double tap — the spec verifies that two
  Forwards _without_ an intervening neutral do **not** register as a dash.

### Execution-heavy techniques

The parser implements the genre's hard inputs as explicit multi-stage state
walks, each emitting a descriptive flag on the recognition:

- **Korean backdash cancel** (`bCancelsBackdashRecovery`) — back-tap, neutral,
  back-tap, crouch-cancel (`DownBack`/`Down`), back-tap, all within nested
  windows. The spec checks `StartFrame == 1`, `EndFrame == 12`, and that
  omitting the crouch cancel fails.
- **Wave dash** (`bChainsCrouchDash`) — the Mishima `f, n, d, df` crouch-dash
  chained out of a forward dash.
- **Instant-while-standing** (`bFromInstantWhileStanding`) — a `d, df` crouch
  dash, a return to neutral, then a trigger button inside the 2-frame IWS
  window.
- **Tiger knee** (`bTigerKneeJumpCancel`) — a `d, df, f, uf` motion with the
  trigger button required on the final up-forward frame.
- **Step-cancel sidewalk**, **plink** (two different buttons within
  `PlinkWindowFrames`, detected by non-overlapping press masks), and **negative
  edge** (a _release_ mask within the buffer window) round out the set.

### Facing normalization

A fighting game flips command meaning when sides switch, and V2 handles this
once, centrally. `V2MirrorDirection` (`V2InputTypes.cpp:8271`) swaps the
back/forward component of every diagonal and cardinal (`Back↔Forward`,
`DownBack↔DownForward`, `UpBack↔UpForward`; pure up/down/neutral unchanged).
`CollectWindow` applies it to every sample when `bFacingRight` is false, so the
recognizers themselves only ever reason in right-facing space. The spec proves
this by feeding a _Back_-side dash to a left-facing fighter and confirming it
recognizes as a forward dash, and the replay test mirrors player two's whole
stream through the same function.

## Per-fighter and per-move buffer windows

The architecture's "default 6f, tight 3f, lenient 9f, configurable per fighter /
per move" is implemented as `FV2InputBufferWindowOverride` plus
`FV2InputParserConfig::ResolveBufferWindowFrames` (`V2InputTypes.cpp:6200`).
Each override carries an optional `FighterId` and `MoveId` and a preset
(`Tight`/`Default`/`Lenient`/`Custom`). Resolution picks the **most specific**
matching override by a `GetSpecificityScore` of
`(FighterId ? 2 : 0) + (MoveId ? 4 : 0)` — so a move-level rule beats a
fighter-level rule beats the global default, and a `Custom` preset yields its
explicit frame count. The spec nails every branch: `Move.Asha.Jab → 3`,
`Move.Asha.Shoryuken → 9`, a fighter-wide `Fighter.King → 9`, a move-only
`JustFrameLink → 3`, and a custom `→ 12`. Validation rejects duplicate
`(fighter|move)` keys and custom presets with a non-positive frame count.

The **control schemes** (`EV2ControlScheme`: Classic / Modern / Dynamic /
EasyCombo) layer on top. The Classic policy _requires_ full motion fidelity —
`RequiresMotionInput(QCF/DP/ChargeBack/JustFrame)` and forbids simplification,
single-button specials, and auto-combos, at `DamageScalar = 1.0`. The Modern
policy exposes single-button specials and **emulates** motions: a
`Down + Special` resolves to an emulated `QCF` with command tag
`Command.Modern.Special.QCF` at `DamageScalar = 0.8`. This is the contract that
lets accessibility-minded schemes coexist with execution mains in the same
ruleset — see
[UI / HUD / VR / AR & Accessibility](./ui-hud-vr-ar-and-accessibility.md).

## Deterministic replay encoder

`FV2InputReplayEncoder` (`V2InputReplayEncoder.h`, `V2InputReplayEncoder.cpp`)
is the "deterministic byte-stream input encoder for replays" the architecture
promises, and it is fully real. It defines three little-endian formats, each
with a four-byte magic and a version word:

- **`V2IN`** — a bare sample stream (`EncodeSamples`/`DecodeSamples`). Each
  sample is a fixed 29 bytes: 4 (frame) + 1 (direction) + 3 × 8 (held/pressed/
  released masks). Decode validates the magic, version, and that the cursor
  lands exactly on the end of the buffer.
- **`V2RM`** — a full match stream (`EncodeMatchStream`): metadata (match,
  ruleset, stage, build/content versions, custom-rules and cosmetic hashes,
  timestamp, RNG seed, total frames) followed by per-player streams. Crucially
  it **sorts player streams by slot then id, and each player's samples by frame
  number**, before writing — so two semantically-identical match streams produce
  byte-identical output. The spec proves it: encoding `{P2, P1}` equals encoding
  `{P1, P2}`, and decode requires 2–16 players and re-validates.
- **`V2RF`** — the on-disk replay file (`EncodeReplayFile`): a rich header
  (platform, region, tick rate, input/initial/final state hashes, compression
  codec, server-authoritative flag), a timestamped input-event stream, keyframe
  - delta state snapshots, and a compressed chunk index with per-chunk CRCs.

Determinism is sealed by `HashEncodedStream`, a `FCrc::MemCrc32` over the bytes;
because the byte layout is canonical, the hash is a stable fingerprint of a
match's inputs. This is the same artifact the replay and competitive-integrity
systems consume — see
[Game Modes — Training & Replay](./game-modes-training-and-replay.md) and
[Online Backbone & Competitive Integrity](./online-backbone-and-competitive-integrity.md).
Decoded samples are tagged `bFromReplay = true` so downstream systems can tell a
replayed frame from a live one.

## How input reaches combat

The buffer is not a parser toy; `UV2_GameplayAbility` reads it directly.
`IsInputBufferWindowSatisfied(InputBuffer, CurrentFrame)`
(`V2GameplayAbility.cpp:173`) returns true only when the latest sample is within
the ability's `InputBufferWindowFrames` of the current simulation frame — the
frame-accurate "did the player press this recently enough to buffer it" check
that makes links and cancels feel correct. The matching tag flow
(`TryConsumeInputBuffer` → `GetQueuedInputTag` → `ShouldConsumeInputBuffer` →
`ConsumeQueuedInputTag`) runs against the ability system's **simulation frame**,
keeping buffered activation inside the deterministic clock the rollback system
re-simulates. The deeper frame-data and cancel-window mechanics live in
[Combat System — GAS, Frame Data & Determinism](./combat-system-gas-frame-data-and-determinism.md).

## The animation configuration layer

`V2Animation` encodes the architecture's skeleton/AnimBP/retarget/cloth/facial
chapter as a tree of `USTRUCT`s aggregated by `UV2AnimationConfigAsset` (a
`UDataAsset` whose `ValidateConfiguration` cross-checks every sub-table). The
builders and validators are exercised end-to-end by `Animation.Module.spec.cpp`.
Read this section as a **specification with working resolution logic** — the
math is real, the assets it points at are not yet authored (see the
honest-status note above).

- **Skeletons & retarget.** `BuildDefaultFighterSkeletonDefinitions` yields 6
  archetypes (Base + Heavyweight, Welterweight, Female, Monster, Child); the
  base is `SK_V2_Fighter_Base`, flagged `bUE5MannequinCompatible` with a
  required-bone list. `FV2IKRetargetProfile` defines 5 base→archetype
  retargeters with **round-trip tolerance checks**: `DoesRoundTripDeltaPass`
  accepts a 1.0 cm joint delta and rejects 1.01 cm — a real numeric gate the
  spec asserts in both directions.
- **AnimBP layer stack.** `FV2AnimBPStateLayerDefinition` describes the six
  ordered layers of `ABP_V2_Fighter_Base` — Locomotion, UpperBodyAction (GAS
  montage slots FullBody/UpperBody/LowerBody/Hands), HitReaction, Stance, IK
  (foot / look-at / opponent-face, the last a no-op in free 3D), Additive
  (emotes/breathing/damage-tics) — with an enforced evaluation order. This is a
  _description of_ the AnimBP graph for validation and tooling, not a compiled
  `UAnimInstance`.
- **Hit-reaction chooser tables.** `FV2AnimChooserTableDefinition` ships four
  tables (HitReaction, Blocking, Parrying, WakeUp), each a **150-row** matrix
  keyed on hit-reaction tag × stance tag × `EV2HitReactionDamageTier`
  (Light/Medium/Heavy/Launcher/Knockdown). `FindRow` performs the real lookup
  the spec checks for, e.g., a Soul-Calibur-weapon-stance wallsplat at Heavy
  tier.
- **Motion matching & Mover.** Six `FV2MotionMatchingConfig` databases (World
  Tour, Tekken Force, Devil Within, Def Jam street, MyCAREER backstage, SC
  Chronicles) carry pose-history (8 samples over 0.6 s), foot-phase contact
  bones, and a trajectory model blending player-input and AI desire. Five
  `FV2MoverModeBinding`s (Match2D/Ring/Cage/Street/ExplorationFree) and three
  `FV2MoverAdoptionPolicy`s gate a Character-Movement fallback behind a
  cert-blocking rollback issue. These map onto the enabled `PoseSearch`/`Mover`
  plugins but are config, not calls.

### Cloth, hair, accessories, facial

`FV2ChaosClothPreset` carries an LOD chain (LOD0 cloth sim → LOD1 baked vertex
animation → LOD2 disabled) and is _resolved_ against
`FV2ChaosClothWeightClassTuning` by `ResolveChaosClothPresetForWeightClass` — a
real blend that, e.g., raises solver damping for heavyweights (the spec asserts
`> 0.70`). Hair splits principal grooms from crowd cards; accessory chains
(necklace/earrings/belt-tail/weapon-trinket) are `bCosmeticOnlyRollbackSafe`
rigid-body chains. Facial covers MetaHuman Animator + Live Link Face (the latter
`bOfflineSolveRequired`, `bRequiresPerformerConsent`, and explicitly **not**
needing runtime Live Link plugins), a 15-entry phoneme→viseme map, six emotion
additive layers driven by `State.Emotion.*` tags, and an audio-driven story-bark
lipsync profile that names the cross-monorepo provider `@psyche/avatar-lipsync`.
MK X-Ray gore (`FV2ChaosFleshGorePreset`) is ruleset-gated
(`bMortalKombatRulesetOnly`), cinematic-gated, and runtime-limited. Every one of
these structs is marked rollback-safe or gameplay-inert; the presentation/AV
story continues in
[Presentation, AV & Signature Content](./presentation-av-and-signature-content.md).

### The real bridge: animation notify states

The one place `V2Animation` does real _runtime_ work today is its notify states,
which depend on `V2Combat` and turn an animation timeline into combat data:

- **`UV2HitboxNS`** — `BuildKeyframeForWindow(Start, End)` produces an
  `FV2CombatBoxKeyframe`, setting `BoxType` to Hitbox or Hurtbox from `bAttack`,
  clamping the frame window, OR-ing in the `Unblockable` attack property when
  `bArmorPiercing`, and calling `Sanitize()`. This is how a montage's active
  frames become the authoritative hit/hurt boxes the combat system reads.
- **`UV2ArmorNS`** — `AbsorbsPayload` / `GetReducedDamage` implement real armor:
  it absorbs while `ArmorHitCount > 0` unless the payload is armor-piercing and
  the armor `bBreaksOnArmorPiercing`, and reduces damage by
  `1 − DamageReductionScalar` (the spec: an 80-damage hit through 0.75 reduction
  yields 20).
- **`UV2CancelWindowNS`** — `CanCancelWithTag(tag, bHitConfirmed, bBlocked)`
  gates special/super/Drive/Heat/X-Ray cancels: it requires an exact tag match,
  then permits on-hit, on-block, or whiff cancels per its flags (whiff off by
  default).

These three are the genuine seam between the (pending) AnimBP/montage content
and the (shipped) combat core.

## Failure modes and edge cases

- **Empty or invalid input.** `RecognizeMotion` returns an invalid recognition
  immediately if the buffer is empty or `Config.IsValidConfig()` fails; every
  recognizer returns a default (invalid) `FV2MotionRecognition` rather than a
  partial one.
- **Ring overflow.** `AddSample` evicts oldest-first to hold `CapacityFrames`;
  motions older than the (adaptively widened) collection window are simply never
  seen, which is the intended "you waited too long" behavior.
- **Held vs. tapped.** Because press/release edges are diffed in the buffer, a
  held direction cannot masquerade as a double-tap, and a held button cannot
  re-trigger negative edge — the edge masks are zero on sustained frames.
- **Replay rejection.** Decoders fail closed on wrong magic, wrong version, an
  out-of-range player count (must be 2–16), a sample count that would overrun
  the buffer, or a trailing-byte mismatch (`Cursor != Bytes.Num()`), so a
  truncated or tampered stream never decodes into a half-valid match.
- **Animation config coherence.**
  `UV2AnimationConfigAsset::ValidateConfiguration` aggregates the per-table
  `HasRequired*` checks; a config missing, say, a weight-class tuning or a
  chooser table fails validation rather than silently shipping an incomplete
  fighter.

## Related

- [Combat System — GAS, Frame Data & Determinism](./combat-system-gas-frame-data-and-determinism.md)
  — what an `FV2MotionRecognition` activates, and the hitbox keyframes notify
  states feed
- [Rollback Netcode & Tag-Team](./rollback-netcode-and-tag-team.md) — why the
  integer-frame, bitmask input model exists
- [Game Modes — Training & Replay](./game-modes-training-and-replay.md) and
  [Online Backbone & Competitive Integrity](./online-backbone-and-competitive-integrity.md)
  — consumers of the `V2RM`/`V2RF` replay byte streams
- [UI / HUD / VR / AR & Accessibility](./ui-hud-vr-ar-and-accessibility.md) —
  control schemes, hold-to-toggle, one-handed, and QTE-alternative input options
- [Presentation, AV & Signature Content](./presentation-av-and-signature-content.md)
  — cloth, hair, gore, and facial performance presentation
- [Build, Cook, Assets, Data & Production](./build-cook-assets-data-and-production.md)
  and
  [Telemetry, Performance, Testing & Release Gates](./telemetry-performance-testing-and-release-gates.md)
  — where the pending animation `.uasset` content and the automation specs fit
- [Glossary & Module Topology](./glossary-and-module-topology.md) and
  [V2 Product Promise](./v2-product-promise.md), under the hub
  [../V2_ARCHITECTURE.md](../V2_ARCHITECTURE.md)
