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
FV2AbilityFrameWindowofStartupFrames/ActiveFrames(min 1) /RecoveryFrames, withGetTotalCommitmentFrames()and aHasValidFrameWindow()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)(aBlueprintNativeEvent), andIsInputBufferWindowSatisfied(FV2InputBufferRing&, CurrentFrame). - Cancel windows.
OpenCancelWindow(CancelTag, FrameNumber)/CloseCancelWindow(...)broadcasting both a dynamicFV2CancelWindowChangedSignatureand a native delegate, plusIsCancelWindowOpenandGetCancelWindowOpenedFrame— 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.
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:
- Consume input. Apply each
FV2RollbackFrameInputto its fighter (ApplyInputToFighter), tracking who advanced toward the opponent (for negative-penalty logic). - Tick state. Per fighter: decrement
HitstopFrames, and only advance the animation frame when hitstop is zero — the literalif (HitstopFrames > 0) --HitstopFrames; else ++AnimationFrame;. This is why hitstop "freezes" the move and why frozen frames don't accrue rollback work. - 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. - Advance the deterministic RNG and refresh state hash. Each fighter's
RngCursoris stepped throughNextDeterministicRng, thenRefreshStateHash(). - Increment the frame, emit one present event tagged with the
EV2RollbackSideEffectPolicy, and return a tick result carryingComputeStateHash().
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, androllback_budget_excluded = truein every CSV row), so heavy hits never blow the resimulation budget. - Hit-once enforcement survives rollback. The
HitOnceRegistryis 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→ onlyPostMatch) cannot be escaped sideways. - Catalog validation is adversarial.
FV2GameFeelCatalog::IsValidCatalogrejects a catalog with a missing Fatality camera shake and rejects a tunability path that doesn't point at the canonicalV2/balance/feel/*.csv— both are explicit negative cases inGameFeelTuning.spec.cpp. - The structs guard themselves. Frame windows (
IsValidWindow), hitbox configs (IsValidHitboxComponentConfig), drive/critical-art/MK-stamina requests (each with anIsValidRequest(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/Randomnode; 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.
Related#
- V2 Product Promise — why one engine must cover eight rulesets
- Glossary and Module Topology —
V2Gameplay/V2Combat/V2Netcodein the module map - Animation and Input Pipeline, Rollback Netcode and Tag-Team, Game Modes, Training, and Replay
- Presentation, A/V, and Signature Content, UI, HUD, VR/AR, and Accessibility
- Telemetry, Performance, Testing, and Release Gates, Online Backbone and Competitive Integrity
- The section hub: ../V2_ARCHITECTURE.md