Open-World Narrative · Architecture

GAS, Animation & Input

A focused page within the Open-World Narrative Architecture documentation. The full map and every sibling page live in the Architecture hub.

8sections13 minread1diagram1table

On this page

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.

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 equivalentV5Gameplay 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.

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 8050 + 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. 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. 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. For these sections in prose and the full subsystem glossary, return to the hub: ../V5_ARCHITECTURE.md.