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

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](../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](./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](./glossary-and-module-topology.md) for the
module map and [./v2-product-promise.md](./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](./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 `FV2HitboxNotifyWindow`s, each with a
`StartFrame`/`EndFrame`, a `HitPriority`, an array of `FV2HitboxShapePrimitive`s
(`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`.

```mermaid
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](./game-modes-training-and-replay.md) and
[./open-world-coop-and-special-modes.md](./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](./ui-hud-vr-ar-and-accessibility.md) — and
the broader audio/visual presentation of impacts is covered in
[./presentation-av-and-signature-content.md](./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](./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](./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](./animation-and-input-pipeline.md)); the
deterministic sim and the tag-team/assist handshakes feed netplay
([./rollback-netcode-and-tag-team.md](./rollback-netcode-and-tag-team.md)); the
hit-confirm trainer specs and golden replays feed training and replay
([./game-modes-training-and-replay.md](./game-modes-training-and-replay.md));
the `HitConfirmed` payload and shake catalog feed presentation
([./presentation-av-and-signature-content.md](./presentation-av-and-signature-content.md))
and accessibility
([./ui-hud-vr-ar-and-accessibility.md](./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](./online-backbone-and-competitive-integrity.md),
[./security-compliance-and-sister-monorepo-integration.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](./racing-and-vehicle-architecture.md)),
and the meters it tracks surface in live-ops and progression
([./live-ops-store-progression-and-community.md](./live-ops-store-progression-and-community.md),
[./esports-companion-and-ai-services.md](./esports-companion-and-ai-services.md)).
For where these modules sit in the build, see
[./build-cook-assets-data-and-production.md](./build-cook-assets-data-and-production.md)
and [./glossary-and-module-topology.md](./glossary-and-module-topology.md).

## Related

- [V2 Product Promise](./v2-product-promise.md) — why one engine must cover
  eight rulesets
- [Glossary and Module Topology](./glossary-and-module-topology.md) —
  `V2Gameplay` / `V2Combat` / `V2Netcode` in the module map
- [Animation and Input Pipeline](./animation-and-input-pipeline.md),
  [Rollback Netcode and Tag-Team](./rollback-netcode-and-tag-team.md),
  [Game Modes, Training, and Replay](./game-modes-training-and-replay.md)
- [Presentation, A/V, and Signature Content](./presentation-av-and-signature-content.md),
  [UI, HUD, VR/AR, and Accessibility](./ui-hud-vr-ar-and-accessibility.md)
- [Telemetry, Performance, Testing, and Release Gates](./telemetry-performance-testing-and-release-gates.md),
  [Online Backbone and Competitive Integrity](./online-backbone-and-competitive-integrity.md)
- The section hub: [../V2_ARCHITECTURE.md](../V2_ARCHITECTURE.md)
