# GAS, Animation & Input

V5 is one open-world narrative game that has to play like seven. A Hong Kong
triad brawler diving into sticky cover, a 1947 detective thumbing a period
sidearm, a 1899 outlaw fanning a revolver, a Witcher coating a silver sword in
specter oil, a sci-fi marine snapping to a zero-G firing stance, a steampunk
gadgeteer, and a cross-cell Mind Palace deducer all run on the **same shared
gameplay foundation** — the same attribute machinery, the same animation
catalog, the same input-context model — and differ only in which **cell** they
are mounted as. That partitioning is the job of the four modules this page
covers: `V5Gameplay` (the Gameplay Ability System layout), `V5Combat` (the
third-person gunplay verbs that ride the GAS attributes), `V5Animation` (the
motion-matching and AnimBP pipeline), and `V5Input` (the Enhanced-Input context
model). The join key that stitches them together is one enum, `EV5Cell`
(`V5Core/Public/V5Types.h:7`):
`Urban, Period, Frontier, Hunter, SciFi, Steampunk, MindPalace` — seven values,
not five, because the DLC Steampunk cell and the Mind Palace meta-layer carry
tags and data even though only five cells ship combat at launch.

Unlike V5's per-cell ruleset plugins, this layer is **cross-cell shared by
construction**: the modules forbid per-cell dependencies and key everything on
`EV5Cell` so a new cell is a new enum arm plus data, not a fork. This page is
part of the **Shared Gameplay Foundations** group; the section hub is
[../V5_ARCHITECTURE.md](../V5_ARCHITECTURE.md).

## What ships, honestly

These four modules are **real, building Unreal C++ with domain-specific math and
genuine automation tests** — not skeletons. But they sit at different distances
from the engine's runtime, and the honest distance matters. Five qualifications,
so the rest of the page reads at face value:

1. **GAS is the most engine-integrated of the four.** `V5Gameplay` links the
   real `GameplayAbilities` and `GameplayTags` modules (`V5Gameplay.Build.cs`),
   ships four concrete `UAttributeSet` subclasses and twenty concrete
   `UGameplayEffect` subclasses with modifiers configured in C++, and the
   `V5.Gameplay.GE.*` specs spawn the engine's real `AAbilitySystemTestPawn`
   into a `UWorld`, apply effects, and assert computed attribute values
   (`V5GameplayEffectTests.cpp:27-97`). These would fail against placeholders.

2. **There is no central GAS pawn or ASC subclass in this layer.** V4 had an
   `AV4Pawn_Operator` that owned a `UV4AbilitySystemComponent`, mounted one
   attribute set per cell, and switched cells by overwriting that pointer. V5
   ships **no equivalent** — `V5Gameplay` provides the attribute sets, the gated
   ability base, and the effects as **reusable building blocks**, and the only
   place an ASC is wired to a set is the test (engine `AAbilitySystemTestPawn` +
   `AddSpawnedAttribute`, `V5GameplayEffectTests.cpp:29-32`). The pawn that
   mounts a set, grants abilities, and flips cells at runtime is an integration
   site this module does not contain.

3. **`V5Gameplay` ships zero concrete abilities.** The architecture lists
   ability families (`V5_Ability_Sign_*`, `V5_Ability_DeadEye`,
   `V5_Ability_Biotic_*`). In the shared module the **only** ability class is
   the abstract `UV5_GameplayAbility_Base` (`V5GameplayAbilityBase.h:9`); the
   cell verbs live in per-cell modules, not here. Treat the families as the
   contract the base enforces, not as classes in this module.

4. **`V5Animation` and `V5Input` link no engine animation/input plugins.** Both
   `V5Animation.Build.cs` and `V5Input.Build.cs` depend on only
   `Core/CoreUObject/Engine/Json/JsonUtilities/V5Core`. They do **not** link
   `PoseSearch`, `MotionWarping`, `Chooser`, or `EnhancedInput`. So their IK
   solvers, motion-match scoring, chooser selection, and IMC-stack swaps are
   honest standalone C++ computations and content-catalog validators — **not**
   calls into UE's Motion Matching / IKRig / Enhanced Input runtimes. The
   `SimulatedLatencyMs` field on the IMC swap result (`V5InputTypes.h:138`) is
   the honest tell: it is an arithmetic estimate, not a measured frame cost.

5. **There are zero binary `.uasset` files in `V5/ue/Content`.** What ships
   instead are large, real **JSON catalogs** the loaders parse and validate —
   the animation catalog alone is
   `Content/V5Animation/Data/animation_catalog.json` at ~390k lines (30,000+
   clips). No compiled AnimBlueprint, pose-search database, IKRig, montage, or
   GameplayEffect `.uasset` exists for the soft names to resolve to.

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

```mermaid
flowchart TD
    CELL["EV5Cell join key<br/>Urban..MindPalace (7)"]
    subgraph INPUT["V5Input (catalog + planner)"]
      ICAT["UV5_Input_Catalog<br/>25+ IMC specs, all 7 cells"]
      IMC["UV5_Input_IMC_Manager<br/>BuildStackForCellAndMode / SwapIMCStack"]
      ICAT --> IMC
    end
    subgraph GAS["V5Gameplay (real GAS)"]
      ASET["UV5_AttributeSet_*<br/>Combat/Honor/Ship/Vehicle (44 attrs)"]
      ABASE["UV5_GameplayAbility_Base<br/>cell-tag gate"]
      GE["UV5_GameplayEffect_*<br/>20 concrete effects"]
      ABASE -->|applies| GE
      GE -->|modifies + clamps| ASET
    end
    subgraph COMBAT["V5Combat (verb rules)"]
      WEAP["UV5_Combat_WeaponBase::FireAtRange"]
      PEN["BulletPenetration / HitReaction / FriendlyFire"]
      WEAP --> PEN
    end
    subgraph ANIM["V5Animation (algorithms + catalog)"]
      MM["MotionMatchingTraversal"]
      IK["7 IK solvers + retargets"]
      CH["ChooserTable / RootMotion"]
    end
    CELL --> INPUT
    CELL --> GAS
    CELL --> COMBAT
    CELL --> ANIM
    IMC -->|fires verbs by cell tag| ABASE
    PEN -->|damage → GE seam| GE
    PEN -->|hit-react class| CH
    WEAP -->|reload montage path| ANIM
```

## GAS layout — attribute sets, a gated base, real effects

### Four attribute sets, 44 replicated fields

Every stat the game tracks lives in one of four `UAttributeSet` subclasses, each
declared with the `V5_ATTRIBUTE_ACCESSORS` macro (`V5AttributeSets.h:8`) that
synthesizes the GAS getter/setter/initter quartet. Unlike V4, these derive
**directly** from `UAttributeSet` and each carries its own clamp logic — there
is no shared abstract base:

| Set                        | Header | Fields | Notable defaults                                                                             |
| -------------------------- | ------ | ------ | -------------------------------------------------------------------------------------------- |
| `UV5_AttributeSet_Combat`  | `:15`  | 14     | Health/Max 100, MovementSpeed 600, CritChance 0.05, CritMultiplier 1.5, Mana 100 (`.cpp:39`) |
| `UV5_AttributeSet_Honor`   | `:112` | 9      | WitcherHumanity 50, all reps 0 (`.cpp:121`)                                                  |
| `UV5_AttributeSet_Ship`    | `:179` | 11     | Hull 1000, Torpedoes 12, Railgun 40, PDC 12000 (`.cpp:189`)                                  |
| `UV5_AttributeSet_Vehicle` | `:258` | 10     | Fuel 100, four tire conditions + engine at 100 (`.cpp:252`)                                  |

That is **44 replicated `FGameplayAttributeData` fields**, each using
`ReplicatedUsing = OnRep_*` with `REPNOTIFY_Always` (`V5_REPLICATE_ATTRIBUTE`,
`V5AttributeSets.cpp:12`). The clamps are domain-specific, not boilerplate.
Combat `PreAttributeChange` (`V5AttributeSets.cpp:76`) floors
Health/Stamina/Toxicity/Mana at zero, holds CritChance to `[0,1]`, and keeps
CritMultiplier/AttackSpeed at a `0.1` minimum so a debuff can never zero out a
multiplier; `PostGameplayEffectExecute` (`:94`) re-clamps current-vs-max pairs
after an instant effect. Honor uses three different clamp bands
(`V5AttributeSets.cpp:148`): `Bounty ≥ 0`, the Paragon/Renegade/WitcherHumanity
meters to `[0,100]`, and the cop/triad/family reputations to a signed
`[-100,100]`. Ship deliberately exempts `ThrustForward`/`ThrustLateral` from the
positive clamp (`:223`) because thrust is a signed vector component. The
`V5.Gameplay.GE.ReplicationMetadata` spec reflection-counts these and pins
`14 / 9 / 11 / 10` (`V5GameplayEffectTests.cpp:128-131`) — a test that fails the
moment an attribute loses its RepNotify.

### The cell-gated ability base

`UV5_GameplayAbility_Base` (`V5GameplayAbilityBase.h:9`) is
`Abstract, Blueprintable` and adds exactly what a multi-cell roster needs. Its
constructor sets `InstancedPerActor` instancing and `LocalPredicted` net
execution (`V5GameplayAbilityBase.cpp:43-45`), so every cell inherits
client-predicted, replicated activation. It carries a `CellContext` (`:17`) and
a resolved `CellTag`; `SetCellContext` (`.cpp:48`) maps the enum to a native tag
— `EV5Cell::Urban → V5.Cell.Urban`, `MindPalace → V5.Layer.MindPalace`
(`ResolveV5CellTag`, `.cpp:7`) — and adds it to `AbilityTags`. The gate lives in
`CanActivateAbility` (`.cpp:58`): after the `Super` check it returns true only
when the cell tag is invalid, the source tags are empty, or
`SourceTags->HasTag(CellTag)` (`.cpp:70`). This is a **source-tag** gate, subtly
different from V4's ASC-active-cell check: a frontier verb activates only when
the activation context is tagged frontier, which keeps a stray tag from firing a
foreign cell's verb.

### Twenty effects with real GAS configuration

This is where V5 departs most from V4. Where V4 used one enum, a resolve table,
and `SetByCaller` scaling, V5 ships **twenty concrete `UGameplayEffect`
subclasses** (`V5GameplayEffects.h:7-165`) whose constructors configure real GAS
modifiers via three helpers (`V5GameplayEffects.cpp:7-40`):

- **Instant damage.** `Damage_Physical` is an `Instant` additive `-20` to Health
  (`.cpp:43`); `Damage_Energy` is `-15` (`.cpp:49`).
- **Periodic bleed.** `Damage_Bleed` (`.cpp:55`) is a `HasDuration` 6 s effect
  with `Period.Value = 1.0`, `bExecutePeriodicEffectOnApplication = true`, and a
  `-3` health tick — a genuine periodic GAS effect, not a flat hit.
- **Stacking buffs.** `ConfigureStackingBuff` (`.cpp:29`) builds a 30 s effect
  with `AggregateByTarget`, `StackLimitCount = 3`,
  `RefreshOnSuccessfulApplication`, `ClearEntireStack` expiry, and
  `bDenyOverflowApplication = true` (`.cpp:21-25`). Stamina regen adds `+10` per
  stack (`.cpp:65`); twelve buffs share this shape.
- **Oil coatings.** `ConfigureOilEffect` (`.cpp:35`) is a 300 s, single-stack
  effect adding a crit-chance and an attack-speed modifier; the specter oil
  grant is `(+0.12 crit, +0.10 attack)` (`.cpp:124`), tuned per monster class.
- **Persistent honor.** `Persistent_HonorMod` (`.cpp:144`) is an `Infinite`
  effect adding `+5` HonorScore and `+2` ParagonScore at once.

The `V5.Gameplay.GE.Stacking` spec applies the stamina buff four times and
asserts the active count caps at `3` and stamina lands at `80` — `50 + 3×10`,
the overflow denied (`V5GameplayEffectTests.cpp:91-97`). The
`V5.Gameplay.GE.Cancellation` spec applies once (stamina `50 → 60`), removes the
handle, and asserts the restore to `50` (`:110-117`). These pin specific
computed values that a hardcoded return could not satisfy.

A companion piece, `UV5_StateMachine` (`V5StateMachine.h:32`), is the narrative
flag store, not a combat machine: `SetFlag/GetFlag/ClearFlag` over
cell-namespaced `FV5NarrativeFlagState` rows carrying a revision and a Unix
timestamp (`:8`), plus `ExportSnapshot`/`ImportSnapshot` for save round-trips,
verified cross-cell at `V5GameplayEffectTests.cpp:143-154`.

The tag side backs all of this. `V5/ue/Config/DefaultGameplayTags.ini` declares
22 native tags under `FastReplication=True` — seven cell roots, the
honor/bounty/paragon/renegade axis roots, and input/save roots. The
`UV5_GameplayTagRegistry` game-instance subsystem (`V5GameplayTagRegistry.h:10`)
requests 17 required tags on `Initialize`, and **fails loud**: missing tags land
in `MissingRequiredTags`, `bReady` goes false, and each absence is logged as an
error (`V5GameplayTagRegistry.cpp:99-106`) rather than silently tolerated.

## The combat verb layer (`V5Combat`)

`V5Combat` is the third-person gunplay that rides those GAS attributes, and it
is **real pure-function C++**, mostly `UBlueprintFunctionLibrary` resolvers over
the data types in `V5CombatTypes.h` (14 weapon classes, 7 cover states, 12
hit-reaction classes, 5 friendly-fire policies). The load-bearing math:

- **Bullet penetration.** `ResolvePenetration` (`V5CombatRules.cpp:3`) computes
  `EffectiveResistance = max(Resistance·(Thickness/10) − ArmorPen, 0)`, blocks
  hard cover above `0.85`, scales damage by `clamp(1 − resistance)`, and counts
  a penetration only when the through-damage clears a 12% floor. The mechanics
  spec drives 40 cm concrete (`bIsHardCover`) and asserts the shot is stopped
  (`V5CombatTests.cpp:191`).
- **Hit reactions.** `ResolveHitReaction` (`.cpp:17`) is a tiered selector:
  headshot ≥ 75 → `KO`; armor-break → `WallSlam`; **Hunter cell** ≥ 55 →
  `KOBleed` (a cell-specific arm); ≥ 90 → `SpinFall`; ≥ 65 → `Knockdown`; ≥ 35 →
  `HeavyStagger`. The spec pins the `90 + headshot → KO` outcome
  (`V5CombatTests.cpp:193`).
- **Friendly fire.** `GetPolicyForCell` (`.cpp:46`) gives each cell its own
  doctrine — Period `Blocked`, Hunter `Allowed`, Sci-Fi `SquadOnly`,
  Urban/Frontier `HonorPenalty` — and `GetReputationPenalty` charges 25 for a
  civilian, 10 for an ally, 35 for a squadmate (`.cpp:79`).

The feel systems are equally concrete. `AdvanceADS`
(`V5CombatAimDownSight.cpp:27`) blends FOV toward the weapon's zoom with
`alpha = clamp(dt·12)`, decays recoil by `1 − dt·7`, and recomputes an accuracy
scalar from movement and residual recoil. `TryAttachToCover`
(`V5CombatCover.cpp:8`) only sticks when the approach faces the cover —
`dot(approach, −normal) ≥ 0.2` and height ≥ 60 cm — and flags vaultability below
145 cm. `UV5_Combat_StanceSubsystem` is a real `UWorldSubsystem`
(`V5CombatStanceSubsystem.h:10`) that gates `ZeroG` to the Sci-Fi cell and
`Horseback` to Frontier (`.cpp:5-18`) and returns per-stance accuracy/recoil/
profile/movement scalars (prone drops profile to `0.32`, `.cpp:48`).
`FireAtRange` (`V5CombatWeaponBase.cpp:11`) ties it together: damage-at-range ×2
for headshots, through the penetration resolver, into the hit-reaction selector,
with a recoil key pulled from the pattern catalog. That catalog is a real
197-weapon roster expanded from `weapon_roster.json` and validated per cell —
Urban 65, Period 40, Frontier 22, Hunter 32, Sci-Fi 38 — with a tuning assertion
that the 1899 revolver kicks more than twice as hard as the sci-fi rifle
(`V5CombatTests.cpp:79-97`). The seam this layer does **not** cross is applying
its computed damage back through a GAS `Damage` effect onto
`UV5_AttributeSet_Combat::Health`; the resolvers return values, and the
integration pawn (qualification 2) would land them.

## Animation pipeline (`V5Animation`)

`V5Animation` is a library of standalone algorithms plus a catalog validator.
The motion-matching contract from the architecture — 0.5 s past trajectory, 1.0
s future intent — is enforced as data: `ValidateAnimationCatalogJson`
(`V5AnimationSystems.cpp:207`) parses the 390k-line catalog and requires schema
version 1, **30,000+ clips across five cells and three traversal modes**, and
**15 pose databases of ≥ 2000 clips each**, rejecting any clip below the quality
bars (`poseCoverage ≥ 0.86`, `qualityScore ≥ 0.82`, `≥ 9` trajectory samples;
`ParseClip`, `:157`). `EvaluateTraversalMatch` (`:334`) scores a request as
`alignment·0.5 + speed·0.3 + window·0.2`, accepts above `0.68`, and returns a
blend time lerped between `0.18` and `0.06` s; `SpeedScoreForMode` (`:169`)
tunes the ideal speed per mode (run 4.8, walk 1.5, crouch 0.9 m/s). The
pose-database quality spec averages every database and asserts mean quality ≥
0.86 and coverage ≥ 0.9 (`V5AnimationTests.cpp:110-133`).

The IK and retarget side is real geometry. Seven solvers
(`V5AnimationSystems.cpp:373-455`) each return an `FV5AnimIKResult` with a
validity flag, effector transform, blend alpha, and chain tag: foot-to-ground,
hand-to-ladder, hand-to-cover (target offset `0.08` m along the normal, valid
within `0.45` m), hand-weapon-grip, head-look-at (yaw/pitch clamped to limits),
and full-body mount keyed by `Cockpit/Airlock/Horse`. Three retargeters build
MetaHuman → quadruped (horse, scale clamped `0.5–2.0`), → wildlife, and → alien
(Sci-Fi-only, `:479`). `ResolveRootMotion` (`:490`) accepts a delta only when
the montage is a server-authoritative cinematic finisher and flags a client
correction past 2 cm — the "root motion is server-authoritative" rule made
testable (`V5AnimationTests.cpp:80-82`). `SelectHitReactVariant` (`:500`) is the
Chooser stand-in: it derives a direction from the impact angle (`back > 110°`,
`left < −35°`, `right > 35°`, else front) and a tier from damage (finisher ≥ 90,
knockdown ≥ 60, heavy ≥ 30), composing a per-cell variant id like
`hitreact.frontier.finisher.back`. This is the genuine selection logic an
AnimBlueprint would call; today the call site is the test, and the engine
Motion-Matching/IKRig/Chooser nodes that would consume these results are
unlinked (qualification 4).

## Input pipeline (`V5Input`)

`V5Input` models Enhanced Input as a catalog and a planner rather than a live
subsystem binding. `ValidateInputContextsJson` (`V5InputSystems.cpp:179`) loads
`input_context_catalog.json` (417 lines) and requires schema version 1, **25+
mapping-context specs spanning all seven cells, all remappable**; the spec
confirms the count and that, e.g., `IMC_SciFi_Pilot` advertises `HOTAS`,
`IMC_Urban_Combat` advertises `Trackball`, and `IMC_Steampunk_GadgetWheel`
targets the Steampunk cell (`V5InputTests.cpp:35-47`). The stack planner is the
core: `BuildStackForCellAndMode` (`:315`) selects the shared Menu/Remapping
contexts always, the Dialogue context when the mode is Dialogue, and the
cell-and-mode match otherwise, then sorts by priority then id (`SortStack`,
`:155`). `SwapIMCStack` (`:336`) diffs the current and target stacks into
`AddedContexts`/`RemovedContexts` and estimates
`SimulatedLatencyMs = 0.25·(added+removed) + 0.08·stackSize` (`:363`), flagging
whether it stays inside the request's budget. The latency spec swaps Urban-Walk
→ Sci-Fi-Pilot, asserts the swap stays under 8 ms, adds `IMC_SciFi_Pilot`, and
keeps `IMC_Shared_Menu` (`V5InputTests.cpp:117-131`). That `SimulatedLatencyMs`
is an estimate, not a measured cost, and no `UEnhancedInputLocalPlayerSubsystem`
is touched.

The rest of the module is honest device support: a dialogue-wheel cursor that
resolves a stick vector to a spoke via `atan2` and `floor(angle/360·spokes)`
(`EvaluateCursor`, `:368`; the spec maps straight-up to spoke 2 of 8), plus
builders for adaptive-controller, HOTAS cockpit (5 axes / 4 buttons),
racing-wheel (valid only for Urban/Frontier, `:421`), trackball, arcade-panel
(6–12 buttons), iPad touch (Mind-Palace-primary drops the virtual stick), and
per-platform VR profiles.

## How cells share and specialize

The four modules repeat one pattern — **one shared mechanism, seven narrow
specializations**, all keyed on `EV5Cell`:

- **Shared:** the four attribute sets and their clamps, the cell-gated ability
  base and its predicted/replicated defaults, the twenty effects, the combat
  resolvers and stance subsystem, the motion-match scorer and IK/retarget
  solvers, the input catalog and stack planner, and the native tag table.
- **Specialized per cell:** which oil/sign/biotic effect a cell grants, which
  friendly-fire doctrine and stance gates apply (ZeroG ⇒ Sci-Fi, Horseback ⇒
  Frontier), which motion-match database and per-cell hit-react table load, and
  which IMC stack a cell-and-mode switch assembles. Adding the Steampunk cell is
  a new enum arm plus catalog rows — no change to the shared spine.

## How it connects

These modules are the seam where everything else attaches. Where `V5Gameplay`,
`V5Combat`, `V5Animation`, and `V5Input` sit in the full module graph, and the
one-way cross-cell dependency rule that keeps them shareable, is laid out in
[./topology-layout-module-split.md](./topology-layout-module-split.md). The
honor and reputation attributes (`UV5_AttributeSet_Honor`) and the friendly-fire
penalties this layer computes feed the morality compass and NPC perception
described in
[./perception-crowd-and-morality.md](./perception-crowd-and-morality.md). The
reload montage paths, hit-react variant ids, dialogue-wheel cursor, and
cinematic-finisher root motion this layer selects are realized by the
presentation systems in
[./audio-vfx-cinematics-ui.md](./audio-vfx-cinematics-ui.md). For these sections
in prose and the full subsystem glossary, return to the hub:
[../V5_ARCHITECTURE.md](../V5_ARCHITECTURE.md).

## Related

- [Topology, Layout & Module Split](./topology-layout-module-split.md) — where
  these modules live and the cross-cell dependency rule
- [Perception, Crowd & Morality](./perception-crowd-and-morality.md) — the honor
  attributes and friendly-fire penalties this layer produces
- [Audio, VFX, Cinematics & UI](./audio-vfx-cinematics-ui.md) — the montages,
  hit-react variants, and finisher root motion this layer selects
- The section hub: [../V5_ARCHITECTURE.md](../V5_ARCHITECTURE.md)
