Fighting Game · Architecture

Combat System: GAS, Frame Data, Game Feel & Determinism

A focused page within the Fighting Game Architecture documentation. The full map and every sibling page live in the Architecture hub.

11sections17 minread1diagram3tables

On this page

V2's combat core is the part of the project that has to be provably correct, not just plausible: one engine that emulates the feel of Mortal Kombat, Street Fighter, Tekken, Soul Calibur, UFC, WWE, Def Jam, and King of Fighters, while staying frame-deterministic enough to roll back and re-simulate cleanly for netplay. It does that by splitting the problem in two. The authored, designer- facing model lives on Unreal's Gameplay Ability System (GAS) inside the V2Gameplay module — attribute sets, abilities, gameplay-effect calculations, and a match-state machine — and the runtime hit geometry lives in V2Combat (hitboxes, collision authority, and the per-move frame-data asset). Underneath both sits a third thing that most fighting games never expose: a standalone, integer-frame, side-effect-free simulator, FV2SimWorld, in V2Netcode, that the rollback path drives instead of the live actor graph. Combat is therefore not one codebase but a layered one, and the layers exist precisely so that "looks right" (GAS, montages, camera) and "is deterministic" (the sim world, state hashes, golden replays) can be developed and verified independently.

This is also why combat in V2 is almost entirely Unreal C++, not a TypeScript contract library. Unlike the V1 domains, there is no libs/contracts/src/combat package; the source of truth is the headers under V2/ue/Source/V2Gameplay, V2/ue/Source/V2Combat, and V2/ue/Source/V2Netcode, the gameplay-tag table in V2/ue/Config/Tags/V2.GameplayTags.ini, and the balance CSVs under V2/balance/feel/. This page is the architecture-side companion for the "Deterministic Combat Core" set; the section hub is ../V2_ARCHITECTURE.md.

What ships, honestly#

The combat logic is real and broadly tested. There are 407 *.spec.cpp automation specs under V2/ue/Source/V2Tests, including dedicated suites for each attribute set (VitalsAttributeSet, CombatAttributeSet, MMAAttributeSet, MovementAttributeSet, WeaponAttributeSet, WrestlingAttributeSet), ability lifecycle (AbilityLifecycle.spec.cpp), attribute replication (AttributeReplication.spec.cpp), effect math (GameplayEffectCalculations.spec.cpp), the match-state machine (MatchState.spec.cpp), hitstop/shake/slow-mo (GameFeelTuning.spec.cpp, CameraShake.spec.cpp), and — most importantly for determinism — a 200-replay golden corpus regression (GoldenReplayCorpus.spec.cpp). The GAS classes, the frame-data data asset, the hitbox/collision-authority components, the match state machine, the game-feel catalog, and the deterministic FV2SimWorld all exist as substantive C++ with domain-specific math, not placeholders.

Three honest qualifications. First, the determinism guardrails described in the monolith — a "strict-FP build flag," "deterministic cook order," and an "Editor Validator" that forbids Time / World Delta Seconds / Random / Now Blueprint nodes — are not enforced in code. Grepping every *.Build.cs, *.Target.cs, and the V2Editor module finds no strict-FP flag and no determinism validator. Determinism is achieved by construction (the sim world ticks integer frames, never reads wall-clock, and uses an inline deterministic RNG) and verified by the golden-replay regression, but nothing automatically stops a designer from dropping a forbidden node into a Blueprint. Treat the guardrails as planned. Second, the monolith's gameplay-tag taxonomy over-claims: it lists Cancel.*, Weapon.*, Submission.*, Position.*, Stipulation.*, and Stage.Hotspot.* as root families, but the authored tag table has only four roots (State, Action, Style, Rules). Cancels are Action.Cancel.*, weapon styles are Style.Weapon.*, and submission/position/ stipulation/stage-hotspot tags do not exist in the native tag table at all. Third, the GAS gameplay-effect calculations are deliberately thin: proration, counter-hit, and juggle scaling are computed in V2Combat (UV2MoveFrameData + FV2SimWorld), not inside the gameplay effects. The sections below say where each claim is backed and where it is not.

The two worlds: GAS authority vs. deterministic sim#

Every fighter actor carries a UV2AbilitySystemComponent (V2Gameplay/Public/V2AbilitySystemComponent.h), a subclass of Unreal's UAbilitySystemComponent. Crucially, it does not rely on real time: it holds an integer SimulationFrame advanced explicitly via SetSimulationFrame() / AdvanceSimulationFrame(), and it queues input as a FGameplayTag (QueueInputTag / ConsumeQueuedInputTag) rather than reading hardware each tick. That is the seam that lets the same component run under two replication models, captured by EV2FighterMovementReplicationMode { Rollback, ClientServer } (V2GameplayTypes.h). In ClientServer mode the ASC is the authority — SetServerAuthoritativeAbilityMode, ShouldServerAuthorizeAbilityActivation(InputTag, ClientPredictionFrame), and BuildServerAuthoritativeAbilityRequest implement host-authoritative activation with client prediction. In Rollback mode the live ASC is bypassed for hit resolution and the deterministic FV2SimWorld decides instead — "shared deterministic simulator decides; no per-hit server arbitration," which the code backs through FV2SimWorld::ResolveRollbackHits. Each ability also declares its own EV2AbilityNetworkMode { LocalPredicted, ServerOnly, ServerInitiated, ClientCosmetic, DeterministicRollback }, so cosmetic abilities and rollback-critical abilities are tagged distinctly at authoring time. The tag-team and assist plumbing also lives on the ASC: a replicated FV2TagTeamAnimationHandshake state machine (EV2TagTeamAnimationHandshakePhase: TagOutRequested → TagOutMontageStarted → TagInMontageStarted → Completed, or Rejected) and a replicated FV2AssistCallCooldown (default CooldownFrames = 360). Those connect out to ./rollback-netcode-and-tag-team.md.

The GAS layer#

Gameplay tags — the corrected taxonomy#

V2/ue/Config/Tags/V2.GameplayTags.ini defines 113 tags under four roots:

Root Count Notable sub-families
State 53 State.Stance.* (32, incl. Tekken.Hwoarang.Flamingo*, Tekken.Lei.{Crane,Dragon,...}, DefJam.*, Invincible.*), State.Status.* (14), State.Emotion.* (7)
Action 27 Action.{Light,Medium,Heavy,Special,Super,Throw,Taunt,StanceSwitch,Counter}, Action.Cancel.{Special,Super,X-Ray,Drive,Heat}, Action.Throw.Tech.*, Action.Tag.{In,Out}, Action.Assist.Call
Style 20 Style.Weapon.* (9), plus Style.{Boxing,Judo,Karate,Kickboxing,LuchaLibre,MartialArts,MuayThai,Sambo,Streetfighting,Submissions,Wrestling}
Rules 9 Rules.Mode.{MK,SF,Tekken,WWE,UFC,SoulCalibur,DefJam,TagTeam}

The cancel buffers and ability filters are typed against these: the special- and super-cancel buffer requests in V2MoveFrameData.h carry meta = (Categories = "Action.Special") and "Action.Super", and invincibility frames are keyed under State.Stance.Invincible. The monolith's Cancel.*, Weapon.*, Submission.*, Position.*, Stipulation.*, and Stage.Hotspot.* families are either re-rooted (the first two) or simply absent from the authored table (the last four). The TagRegistry.spec.cpp automation test validates the native table loads.

Attribute sets — six of them, and a Vitals superset#

The canonical attribute sets live under V2/ue/Source/V2Gameplay/Public/Attributes/: UV2_AttrSet_Vitals, UV2_AttrSet_Combat, UV2_AttrSet_MMA, UV2_AttrSet_Movement, UV2_AttrSet_Weapon, and UV2_AttrSet_Wrestling, replicated through a shared V2AttributeReplicationComponent. (Two older single-file sets, V2AttributeSetVitals.h and V2AttributeSetCombat.h, also exist in the parent Public/ directory; the Attributes/-folder UV2_AttrSet_* types are the current ones the calculations target.)

UV2_AttrSet_Vitals is richer than the monolith's list. It replicates 23 FGameplayAttributeData fields — Health/MaxHealth, RecoverableHealth (SF6-style white health), Stun/MaxStun, Stamina/MaxStamina, MKStamina/MaxMKStamina, Hype/MaxHype, SuperMeter/MaxSuperMeter, Drive/MaxDrive, Heat/MaxHeat, Armor, Resolve, Block, Stunmeter, and a legacy Meter/MaxMeter pair — each with a ReplicatedUsing = OnRep_* handler. The set overrides PreAttributeChange, PostAttributeChange, and PostGameplayEffectExecute, and a private ClampCurrentVitals() keeps current values inside their max paired attribute. This single set spans six different meter economies (SF Drive, Tekken Heat, MK Stamina/X-Ray, generic Super, Hype, Stun) so that one fighter actor can be re-skinned to any ruleset by which attributes a move actually touches — which is exactly the cross-game ambition the core exists to serve. See ./glossary-and-module-topology.md for the module map and ./v2-product-promise.md for why the multi-ruleset framing drives these data shapes.

Abilities — a typed family of 21 subclasses#

UV2_GameplayAbility (V2GameplayAbility.h) is the base, extending Unreal's UGameplayAbility with the machinery a fighting game needs:

  • Frame windows. A FV2AbilityFrameWindow of StartupFrames / ActiveFrames (min 1) / RecoveryFrames, with GetTotalCommitmentFrames() and a HasValidFrameWindow() guard — frame data is a first-class property of the ability, not just the animation.
  • Input buffering. InputBufferWindowFrames (default 6, matching the monolith's default buffer), ShouldConsumeInputBuffer(QueuedInputTag, CurrentFrame) (a BlueprintNativeEvent), and IsInputBufferWindowSatisfied(FV2InputBufferRing&, CurrentFrame).
  • Cancel windows. OpenCancelWindow(CancelTag, FrameNumber) / CloseCancelWindow(...) broadcasting both a dynamic FV2CancelWindowChangedSignature and a native delegate, plus IsCancelWindowOpen and GetCancelWindowOpenedFrame — the data that the combo/training systems read.
  • Tag filtering, network mode, and replay policy via FV2AbilityTagFilterRules (required/blocked source & target tag containers), EV2AbilityNetworkMode, and a replay-strip policy (bCosmeticOnlyAbility, bStripCosmeticOnlyInReplays, ShouldRunDuringReplay(bIsReplayPlayback)).

UV2_Ability_TypedBase adds an FV2AbilityClassDescriptor keyed on EV2AbilityCombatFamily (Strike, Throw, GroundedSpecial, AirSpecial, Projectile, AssistCall, Super, GrappleClinch, GrappleTakedown, GrapplePin, GrappleSubmission, CinematicFinisher). From it descend 21 concrete UV2_Ability_* subclasses: the six basic families, AssistCall (default DefaultCooldownFrames = 360), Super, nine ruleset super variants (SFDriveImpact, SFCriticalArtLevel1/2/3, TekkenRageArt, MKXRay, MKFatalBlow, SCCriticalEdge, DJBlazin), the four grapple abilities, and Cinematic_Finisher. UV2_Ability_Super carries two real config blocks: ConfigureSuperMeterProfile(...) (super-bar cost, invincible- reversal frames, cinematic preset) and ConfigureMKStaminaProfile(...) (X-Ray meter gate, RequiresFatalBlowHealthGate, FatalBlowHealthThresholdPct default 0.30) — so an MK Fatal Blow's "only under 30% health" rule is encoded as a real gate, not a comment.

Gameplay effects and the thin-applier nuance#

V2GameplayEffects.h defines only three thin UGameplayEffect subclasses (UV2GameplayEffect_Damage, _Blockstun, _MeterGain). The monolith's longer GE_Damage_Strike / GE_Stun_Delta / GE_Drive_Cost list is conceptual: the actual per-family math lives in Calculations/V2_GameplayEffectCalculations.h as paired UV2_MMC_* magnitude calculations and UV2_ExecCalc_* execution calculations, all resolving a SetByCaller magnitude. The base UV2_MMC_GameplayEffectFamilyBase reads a named SetByCallerDataName with a DefaultMagnitude fallback; the concrete execs apply it as attribute modifiers:

Calculation SetByCaller key Default Effect
StrikeDamage Data.V2.StrikeDamage 10.0 -Health
ThrowDamage Data.V2.ThrowDamage 14.0 -Health
ChipDamage Data.V2.ChipDamage 3.0 -Health and +RecoverableHealth (chip converts to white HP)
StunDelta Data.V2.StunDelta 20.0 +Stun
DriveCost Data.V2.DriveCost 1.0 -Drive
HeatGain Data.V2.HeatGain 20.0 +Heat
HypeGain Data.V2.HypeGain 15.0 +Hype
LimbDamage Data.V2.LimbDamage 10.0 +LimbDamageTorso (Wrestling set)
StaminaDrain Data.V2.StaminaDrain 15.0 -Stamina and -LimbStaminaTorso (MMA set)
TakedownSuccess Data.V2.TakedownSuccess 0.55 +TransitionSkill (MMA set)

The takeaway is architectural: gameplay effects in V2 are appliers, fed a magnitude that the combat layer already computed. They do not, themselves, multiply by MeleeDamageMul, CounterHitMul, or JuggleScaling; that scaling is resolved upstream from the move's frame data and the running combo state (next section). The ChipDamage exec is the one that bakes in real ruleset behaviour by hand — subtracting health and crediting the same amount to recoverable health in one pass — which is why SF6 chip recovery "just works" through the standard GAS path.

Frame data — the move contract#

UV2MoveFrameData (V2Combat/Public/V2MoveFrameData.h, ~1,300 lines) is the per-move UPrimaryDataAsset that every other system reads. At its heart is a small, honest FV2FrameData struct — StartupFrames, ActiveFrames (min 1), RecoveryFrames, OnHitAdvantage, OnBlockAdvantage, GapToFollowup, and a JuggleScalingProfile array — but the asset wraps that in the full grammar of a cross-ruleset fighter. It carries, among many others: Tekken launcher kinematics (TekkenLaunchImpulse = (160, 0, 520), TekkenLauncherMaxJuggleHits = 12, TekkenJuggleStarterScaling = 0.92), Tekken Heat (TekkenHeatDamageMultiplier, TekkenHeatSmash), Tekken Rage Art (damage 85, TekkenRageArtCinematicFreezeFrames = 45), Power Crush armor windows, Ki Charge, stance switching, kara-cancel (KaraCancelWindowFrames = 2), ground tech and wake-up option-selects, MK get-up attacks, starter proration (EV2CombatStarterProrationProfile), counter-hit scaling (CounterHitDamageMultiplier = 1.25), block chip overrides, armor and invincibility frames keyed by FV2MoveFrameRange, projectile class / durability / interaction mask, and the hitbox/hurtbox keyframe sequences. The adaptive-trigger and haptic curves (FV2AdaptiveTriggerResistanceProfile, FV2MoveHapticCurve, and the UV2HapticCurveLibraryData asset) also live here, which is how a DualSense trigger or Switch 2 HD-rumble pulse is bound to a specific impact frame. Because every field has a ClampMin/ClampMax and the asset exposes validation (HasCompleteFrameData(), EvaluateBlockChipDamage(...)), a move is a checkable contract, not a bag of magic numbers. The animation binding side of this — montage references, hit-confirm windows — is detailed in ./animation-and-input-pipeline.md.

Hitbox authority and the hit-confirmed payload#

Two V2Combat components turn frame data into actual collisions.

UV2_HitboxComponent owns a list of FV2HitboxNotifyWindows, each with a StartFrame/EndFrame, a HitPriority, an array of FV2HitboxShapePrimitives (Box / Capsule / Sphere, default box half-extent (25, 18, 18)), and per-frame FV2HitboxFrameKeyframe overrides that can re-aim the shape, swap the damage payload, or change the hit-reaction tag mid-active-window. It resolves active boxes at an integer frame (BuildResolvedHitboxesAtFrame(FrameNumber)), and when a collision lands it builds the game-feel payload, FV2HitConfirmedEvent, broadcast on OnHitConfirmed. That struct is the single hand-off from "did it hit" to "make it feel good," carrying attacker/defender combat IDs and fighter names, SourceMoveId, FrameNumber, Damage, the HitReactionTag, bCounterHit, MK Krushing-Blow fields (bKrushingBlowTriggered, KrushingBlowCinematicFreezeFrames, KrushingBlowCinematicSequenceId), ResolvedHitstunFrames, and — feeding the camera and rumble systems directly — HitstopAttackerFrames, HitstopDefenderFrames, and HitlagDefenderFrames.

UV2CollisionAuthorityComponent is the deterministic sweep authority. It holds the active UV2MoveFrameData and an integer LocalMoveFrame, collects active hit/hurtboxes, and resolves collisions against another component (ResolveCollisionsAgainst(Defender)). Double-hit prevention is a real TSet<int64> HitOnceRegistry keyed by MakeHitRegistryKey(Event) and gated by RegisterHitIfAllowed(Event) / ResetHitRegistry(). And — the detail that makes it rollback-safe — it can snapshot and restore its entire state (BuildRollbackSnapshot() / ApplyRollbackSnapshot(...)), so a re-simulated frame re-derives the same hits rather than replaying stale ones.

Match-state machine#

FV2_MatchState (V2Gameplay/Public/Match/V2_MatchState.h) is a value-type state machine over EV2MatchPhase { Uninitialized, Intro, RoundStart, Live, RoundEnd, MatchEnd, FinisherWindow, PostMatch } (plus hidden Fight / MatchComplete aliases). The legal transitions are a real table — FV2_MatchState::CanTransition — not free-form: any illegal call routes through RejectTransition and is recorded on LastTransition with bAccepted = false.

stateDiagram-v2 [*] --> Intro Intro --> RoundStart RoundStart --> Live Live --> RoundEnd RoundEnd --> RoundStart : another round RoundEnd --> FinisherWindow RoundEnd --> MatchEnd RoundEnd --> PostMatch FinisherWindow --> MatchEnd FinisherWindow --> PostMatch MatchEnd --> PostMatch PostMatch --> Intro : rematch PostMatch --> [*]

Per-mode behaviour is injected, not hard-coded into the phase enum. EV2MatchModeStateNodeType enumerates eight injectable nodes — MKFinisherWindow, WWEPin, WWESubmission, UFCRoundEndScorecard, SCRingOutTermination, DJEnvironmentFinisherPrompt, SFKOReplay, TekkenRageActivationLockout — each a FV2MatchModeStateNode with entry/exit phases, a DurationFrames, and a bBlocksFighterInput flag. CanAcceptFighterInput() therefore answers honestly during, say, a WWE pin mini-game or a UFC scorecard interlude. The UV2MatchStateMachineComponent wraps the value type for actors, replicates it over a dedicated channel (FV2MatchStateReplicationPacket, IsDedicatedMatchStateChannel()), and exposes BuildRollbackSnapshot()/ApplyRollbackSnapshot() — the snapshot carries a DeterministicHash and a bPresentationSideEffectsStripped flag so that re-applying match state during a rollback never re-fires intros or KO replays. Mode-specific flow is expanded in ./game-modes-training-and-replay.md and ./open-world-coop-and-special-modes.md.

Game feel: hitstop, shake, slow-mo, tint#

Game feel is centralized so that each ruleset can hit differently from one shared system. UV2CombatRulesetData::BuildDefaultGameFeelCatalog() returns an FV2GameFeelCatalog (id GameFeel.Catalog.V2) whose HitstopPolicy encodes the rules the monolith describes: attacker/defender hitstop split, a defender-only hitlag window, a counter-hit hitstop bonus (CounterHitBonusMinFrames = 2, Max = 3), block hitstop, bRollbackBudgetExcludesHitstopFrames, and bSuppressCosmeticSideEffectsDuringResim. The catalog ships 9 camera-shake curves with reduced-motion clamps, and GameFeelTuning.spec.cpp asserts the real ordering — Fatality shake is stronger than KO, and every reduced-motion ceiling is below its base intensity.

These numbers are not hard-coded in C++ alone; they are tunable CSVs under V2/balance/feel/, and the test verifies the catalog points at the exact paths. Concrete shipped values:

hitstop_curves.csv attacker defender hitlag counter-hit block rollback-excluded
MK.Small 3 4 3 2 3 true
MK.Heavy 8 10 8 3 5 true
MK.Launcher 9 12 9 3 5 true
SF6.Heavy 6 7 5 2 4 true
SC.WeaponClash 7 9 6 3 4 true

camera_shake_curves.csv runs from small (base 0.20 / reduced-motion 0.12) up to KO (1.00 / 0.35) and Fatality (1.20 / 0.35); slowmo_freeze_curves.csv pins KO slow-mo at 48 frames and finisher freeze at 18. The runtime that consumes shake events is UV2CameraShakeLibrary (V2Gameplay/Public/V2CameraShake.h), a function library whose EvaluateCameraShake(Config, State, Events) folds Perlin / spring / directional-impact patterns (EV2CameraShakePattern) into one FV2CameraShakeEvaluation, clamped to MaxLocationOffset = 50 and MaxRotationOffsetDegrees = 10. The reduced-motion ceilings are the same data that the accessibility surfaces read — see ./ui-hud-vr-ar-and-accessibility.md — and the broader audio/visual presentation of impacts is covered in ./presentation-av-and-signature-content.md.

Determinism and the per-frame loop#

The rollback path does not tick the live actor graph. It drives FV2SimWorld (V2Netcode/Public/V2RollbackSimWorld.h), a plain C++ class with no UObject dependencies, separated from FV2PresentWorld exactly as the monolith describes ("FV2_SimWorld deterministic vs. FV2_PresentWorld interpolated"). The present world holds FixedStepSeconds = 1.0f / 60.0f and is the only side that consumes a display DeltaSeconds; the sim world counts integer frames and nothing else.

FV2SimWorld::StepFrame(FrameInputs, SideEffectPolicy) runs a fixed per-tick order:

  1. Consume input. Apply each FV2RollbackFrameInput to its fighter (ApplyInputToFighter), tracking who advanced toward the opponent (for negative-penalty logic).
  2. Tick state. Per fighter: decrement HitstopFrames, and only advance the animation frame when hitstop is zero — the literal if (HitstopFrames > 0) --HitstopFrames; else ++AnimationFrame;. This is why hitstop "freezes" the move and why frozen frames don't accrue rollback work.
  3. Advance per-mode mechanic timers — air-juggle frames, Tekken Heat, Soul Calibur soul charge / critical-edge invuln / reversal-edge prompt, Ki Charge, Power Crush armor, and stance entry/recovery/duration — each as integer counters, then EvaluateTekkenRageForFighter.
  4. Advance the deterministic RNG and refresh state hash. Each fighter's RngCursor is stepped through NextDeterministicRng, then RefreshStateHash().
  5. Increment the frame, emit one present event tagged with the EV2RollbackSideEffectPolicy, and return a tick result carrying ComputeStateHash().

The RNG is honest about what it is: an inline linear-congruential generator, Current * 1664525u + 1013904223u (the classic Numerical Recipes constants), seeded per fighter from the match seed. The monolith's phrasing — "use FRandomStream seeded from match seed" — is the right principle but not the literal implementation; there is no FRandomStream in the sim path. Cosmetic suppression during resimulation is the EV2RollbackSideEffectPolicy { Emit, SuppressCosmetics, TelemetryOnly } enum: any non-Emit policy sets bAudioMuted, bParticlesSuppressed, bPresentationSuppressed, and bSimulationOnly on the emitted FV2PresentFrameEvent, so a re-simulated frame computes the same state without re-triggering sound or VFX. ComputeStateHash() folds the match seed, current frame, and each fighter's StateHash into one uint32 — the desync signal that the netcode and telemetry layers watch (./telemetry-performance-testing-and-release-gates.md).

Determinism is then proven, not asserted, by GoldenReplayCorpus.spec.cpp: a corpus of 200 deterministic input-stream replays is persisted as JSON manifests (schema v2.tests.goldenReplay.simCorpus.v1) under Content/V2/Tests/GoldenReplays/Corpus/, recording ComputeStateHash every 10 frames plus a final hash. The regression test re-runs each scenario through a fresh FV2SimWorld and compares hashes; the generator even self-verifies in-process ("the same inputs must reproduce the same hashes or the corpus would be noise") and refuses to write a manifest for any scenario that is not deterministic. The full netcode architecture — prediction, transport, rollback session — is in ./rollback-netcode-and-tag-team.md.

Edge cases and failure modes#

  • Hitstop is doubly special. It both gates animation advance (step 2 above) and is excluded from the rollback budget (bRollbackBudgetExcludesHitstopFrames, and rollback_budget_excluded = true in every CSV row), so heavy hits never blow the resimulation budget.
  • Hit-once enforcement survives rollback. The HitOnceRegistry is part of the collision authority's snapshot, so a rolled-back-and-replayed active frame cannot double-register a hit it already landed.
  • Match transitions fail loud. Illegal phase changes are rejected and recorded with bAccepted = false; terminal phases (MatchEnd → only PostMatch) cannot be escaped sideways.
  • Catalog validation is adversarial. FV2GameFeelCatalog::IsValidCatalog rejects a catalog with a missing Fatality camera shake and rejects a tunability path that doesn't point at the canonical V2/balance/feel/*.csv — both are explicit negative cases in GameFeelTuning.spec.cpp.
  • The structs guard themselves. Frame windows (IsValidWindow), hitbox configs (IsValidHitboxComponentConfig), drive/critical-art/MK-stamina requests (each with an IsValidRequest(FString* OutFailureReason)), and frame data (HasCompleteFrameData) all validate before use, returning a human-readable failure reason rather than silently degrading.
  • The un-enforced seam. As flagged up top, nothing automated prevents a designer from introducing nondeterminism via a Blueprint Now/Random node; the golden corpus catches a regression after the fact, but the editor-time validator is not built yet.

How it connects#

Combat is the hub the rest of V2 reads. Frame data and the input buffer feed the animation and motion-matching layer (./animation-and-input-pipeline.md); the deterministic sim and the tag-team/assist handshakes feed netplay (./rollback-netcode-and-tag-team.md); the hit-confirm trainer specs and golden replays feed training and replay (./game-modes-training-and-replay.md); the HitConfirmed payload and shake catalog feed presentation (./presentation-av-and-signature-content.md) and accessibility (./ui-hud-vr-ar-and-accessibility.md); and the state-hash desync signal feeds the online backbone and competitive integrity (./online-backbone-and-competitive-integrity.md, ./security-compliance-and-sister-monorepo-integration.md). The same GAS/attribute foundation is reused, with different attribute sets and ticks, by the vehicle and racing stacks (./racing-and-vehicle-architecture.md), and the meters it tracks surface in live-ops and progression (./live-ops-store-progression-and-community.md, ./esports-companion-and-ai-services.md). For where these modules sit in the build, see ./build-cook-assets-data-and-production.md and ./glossary-and-module-topology.md.