The model separates authoritative combat from game-feel presentation. A cue may predict or dramatize an effect, but only confirmed deterministic state advances damage, meter, position, round, or replay truth.
This is the product-side view of the moment-to-moment fight: what a player actually does with the sticks and buttons, what defensive vocabulary the game hands them, and why a hit feels like Mortal Kombat in one match and like Street Fighter 6 in the next. The companion architecture page, ../architecture/combat-system-gas-frame-data-and-determinism.md, explains how the engine stays frame-deterministic enough to roll back and re-simulate; this page stays on the player's side of the screen and inventories the offensive systems, the defensive options library, and the game-feel layer as features, while pointing at the exact C++ and balance data that back each one.
The throughline is V2's whole reason for existing: per-ruleset feel from one
shared combat core. A single fighter actor can be re-skinned into a 2D
motion-input duelist, a Tekken juggle machine, a Soul Calibur weapon master, an
MMA grappler, or a WWE worker — not by swapping engines, but by changing which
attributes a move touches, which defensive options the ruleset enables, and
which hitstop/shake curve plays on impact. Almost all of this is real, tested
Unreal C++ under V2/ue/Source/V2Combat and V2/ue/Source/V2Gameplay, tuned by
CSVs under V2/balance/feel/ and V2/balance/data/. For the full mode
taxonomy, roster, and the rest of the scope this slots into, start at the
feature hub: ../V2_features.md.
What ships, honestly#
The combat logic is substantive and broadly tested. The offensive and
defensive resolution lives in UV2CombatResolver
(V2Combat/Public/V2CombatResolver.h, an 823-line UBlueprintFunctionLibrary)
plus a family of stateful UActorComponents — UV2ComboCounterComponent,
UV2JuggleStateComponent, UV2BlockstringStateComponent — and the real
per-call math sits in V2Combat/Private/V2CombatTypes.cpp, which is ~24,300
lines of domain logic (the matching header is ~15,800). Game feel is a real,
CSV-driven catalog. The suite under V2/ue/Source/V2Tests carries 407
*.spec.cpp automation specs, including an 8,300-line
Automation/Combat.Module.spec.cpp that exercises the resolver evaluators and
combat components directly, plus dedicated Combat/GameFeelTuning.spec.cpp and
Gameplay/CameraShake.spec.cpp suites.
Three honest qualifications. First, the resolver's evaluators are pure
deterministic functions — each public Evaluate* call forwards to a
Request.Evaluate() that checks windows and returns a result with a fail-loud
reason tag. They compute the right answer; wiring them into live abilities,
input, and animation is the GAS + sim-world job covered in the architecture
companion. Second, the defensive options are opt-in per ruleset: every
config struct (FV2CombatParryRulesetConfig, …) defaults to bEnabled = false,
so the library exists in full but which family a given ruleset turns on is
data, not a guarantee that all of them are live at once. Third,
V2/balance/data/frame-data.csv is a 4-move sample that demonstrates the
column schema, not a shipped frame database — the authoritative per-move numbers
live in UV2MoveFrameData data assets. And as the architecture page flags, the
editor-time validator that would forbid a designer from shipping a move with
no property data is planned, not enforced. There are no .uasset binaries in
the repo; this is a logic-and-data skeleton.
Offensive systems — the layered attack vocabulary#
V2 combat is layered, and the layers activate per ruleset. A player in an MK match never touches the 8-way-run weapon layer; a Soul Calibur player never sees the Drive gauge. One actor carries all of them; the ruleset decides which are armed.
The universal spine every move shares#
Underneath every layer is a move contract. Each move is a UV2MoveFrameData
asset (V2Combat/Public/V2MoveFrameData.h, ~1,300 lines) wrapping a small
honest FV2FrameData of StartupFrames / ActiveFrames / RecoveryFrames /
OnHitAdvantage / OnBlockAdvantage plus juggle scaling. The sample
frame-data.csv shows the shape a designer reads — e.g. Heavy Launcher at 18
startup / 3 active / 29 recovery, +38 on hit, −14 on block, 92 damage — the
classic "huge reward, deeply punishable" launcher profile, versus a
Cross-Up Kick at 11/4/17, −3 on block (barely minus, a safe pressure tool).
That on-block number is the entire mind-game economy of a fighting game
expressed as one integer.
Every move also carries an attack-property bitmask,
EV2CombatAttackProperty, with exactly the eight bits the genre needs — Low,
Mid, High, Overhead, Crossup, AntiAir, OTG, Unblockable — so the
defender's block check, the crossup/anti-air logic, and the training-mode
overlays all read the same source of truth. Hits resolve through a deterministic
sweep (UV2CombatResolver::ResolveFixedFrameSweep) and a trade resolver
(ResolveTradeEvents) whose EV2CombatTradeResolutionKind distinguishes a
clean SimultaneousDamage trade from a CounterHitPriority win or a
GuardImpactClash — the difference between "you both got hit," "you got
counter-hit for extra damage," and "your weapons rang off each other."
Strikes, juggles, and the combo economy#
The headline offensive feature is the combo, and V2 models it as an economy,
not a fixed string. UV2ComboCounterComponent tracks the live hit count and,
crucially, starter proration: RegisterProratedCollisionEvent scales the
whole combo by the move that started it (EV2CombatStarterProrationProfile), so
a light-starter combo deals less than the same route off a launcher — the reason
a "confirm" matters more than raw button mashing. It also knows the difference
between techable and untechable hits and resets the counter for the right reason
(EV2ComboCounterResetReason: Tech, WakeUp, BlockedHit,
DamageScalingReset).
The juggle layer — Tekken culture — lives in UV2JuggleStateComponent. It
registers a launcher, caps the air string (MaxJuggleHits default 12), and
applies per-ruleset juggle damage scaling (EvaluateJuggleDamageScale,
keyed on EV2ShaktiRulesetId), so the same number of air hits bleeds value
differently in Tekken than in MK. It owns the tornado/screw state
(RegisterTornado, one use by default), wall splat (single by default, up
to 3 in rulesets that allow multi-wall-splat via
DoesRulesetAllowMultiWallSplat), and stage transitions that move the fight
to a new floor. Tekken's launcher kinematics are real numbers on the move asset
— TekkenLaunchImpulse = (160, 0, 520), a 12-hit juggle cap, 0.92 starter
scaling.
Pressure that doesn't combo is the blockstring, modelled by
UV2BlockstringStateComponent: it accumulates blockstun, tracks the gap between
blocked hits, and reports IsTrueBlockstring() — i.e. whether the defender is
genuinely locked down or has a frame to escape. That single boolean is what
separates "respect this string" from "mash a reversal here."
Supers, finishers, and counter-hit drama#
The spectacle layer is real too.
UV2CombatResolver::BuildDefaultSuperMoveGrammar seeds the cinematic super
catalog — SF Critical Art (Level 3), MK X-Ray, MK Fatal Blow,
Tekken Rage Art, SC Critical Edge — each entry naming its cinematic
sequence and slow-mo cue, and EvaluateSuperMoveActivation gates activation on
the meter rules. These pair with the 21 typed GAS ability subclasses on the
gameplay side (the nine UV2_Ability_* ruleset super variants — including the
MK Fatal Blow's real "only under 30% health" gate) detailed in the architecture
companion. Counter-hit and "big moment" emphasis are first-class evaluators:
EvaluateCounterHit, MK's EvaluateKrushingBlow (a hidden-condition alt-hit
with its own cinematic freeze), EvaluateWakeupAction for okizeme, and
EvaluateComebackState for the low-HP rally. Tekken's full kit — Heat
activation and Heat moves, Rage activation/Rage Art, Ki Charge, Power Crush
armor, and stance switching — each has a dedicated resolver entry. Beyond
striking, the same resolver carries the grapple/MMA/wrestling offensive
surfaces (takedowns, ground-position transitions, ground-and-pound, submissions,
pins, kickouts, object usage, and stipulation logic) and the Def Jam
environmental finishers, Blazin' meter, and crowd momentum — each a real
Evaluate* function, not a placeholder.
The defensive options library#
V2's standout system is that it doesn't ship one defense; it ships the genre's
entire defensive vocabulary as a toggleable library, so each match plays in
the defensive grammar of the family it's emulating. Every option is a real
evaluator on UV2CombatResolver whose request struct enforces a timing window
and returns a fail-loud reason tag — these are not stubs, they compute
correctness and honestly reject bad input.
One resolver, many defenses#
| Defensive option (lineage) | Resolver entry | What the player gets |
|---|---|---|
| Parry (3rd Strike) | EvaluateParry |
Time the block on the hit-frame: no chip, attacker stunned, defender gains frames + meter |
| Just Defend (KOF / Garou) | EvaluateJustDefend |
Perfect-frame block for 0 chip |
| Instant Block (Tekken) | EvaluateInstantBlock |
Release-and-repress guard on first contact → reduced blockstun |
| Faultless Defense (Guilty Gear) | EvaluateFaultlessDefense |
Meter-cost air/ground guard with extra pushback and chip mitigation |
| Push Block / Advancing Guard (MvC) | EvaluatePushBlock |
Shove the attacker out of pressure at meter cost |
| Burst (Guilty Gear) | EvaluateBurst |
One-shot (or burst-meter) panic button out of a combo |
| Counter Assault / Alpha Counter | EvaluateCounterAssault |
Special out of blockstun to reverse the turn |
| Guard Cancel (KOF) | EvaluateGuardCancel |
Cancel block into a roll/blowback at meter cost |
| Negative Penalty (KOF) | EvaluateNegativePenalty |
Anti-turtle inactivity penalty |
| Hold / Strike / Throw triangle (DOA) | EvaluateHoldStrikeThrow |
RPS reversal layer (also drives WWE clinch) |
| Super-armor / invuln frames | EvaluateSuperDefenseFrames |
Armored or invincible windows on reversals |
| Guard Impact (Soul Calibur) | EvaluateSoulCaliburGuardImpact |
Repel / Deflect / Resist weapon deflection |
| Reversal Edge (SCVI) | EvaluateSoulCaliburReversalEdge |
Cinematic RPS clash at meter cost |
The EvaluateParry implementation is representative of the whole library: it
clamps the parry window (default 6 frames), computes the input-lead frames
against the incoming hit, and only on a IsInputWithinParryWindow() success
returns bParried = true, bNegatesChipDamage = true, the attacker's stun
(default 18 frames), the defender's frame advantage (default +12), and a
meter reward (default 10) — otherwise it returns one of a dozen precise
rejection tags (Parry.Rejected.NotInWindow, …RulesetDisabled, …). Instant
Block similarly verifies the release-then-repress ordering happened on first
contact before granting reduced blockstun; Burst checks one-shot availability
or burst-meter cost before it fires. The standard block path is split into its
own trio — EvaluateBlock, EvaluateBlockChipDamage, EvaluateBlockPushback —
so chip (which, per the architecture page, converts to SF6-style recoverable
white health) and pushback are tunable independently of the block decision.
Throws, techs, and per-ruleset toggling#
The throw game is a defensive surface too: EvaluateThrowAttempt,
EvaluateThrowTech, EvaluateCounterThrow, and EvaluateCrouchTech give the
defender the genre's full anti-grab toolkit. SF6's Drive system rounds out the
defensive menu on the gameplay side — EV2DriveGaugeAction carries
DriveImpact, DriveParry, DriveRush, ODSpecial, and DriveReversal with
real per-action costs (DriveReversalCost = 2.0), and the punishing Burnout
state is modelled by FV2DriveBurnoutStateResult (doubled
BurnoutChipDamageScalar).
What ties the library to the "per-ruleset feel" promise is that
UV2CombatRulesetData carries a config struct for every option —
ParryConfig, JustDefendConfig, InstantBlockConfig,
FaultlessDefenseConfig, PushBlockConfig, BurstConfig,
CounterAssaultConfig, GuardCancelConfig, NegativePenaltyConfig,
HoldStrikeThrowConfig — each with its own enable flag, window timings, costs,
and per-fighter overrides (FV2CombatParryFighterWindow). A ruleset enables
exactly the defenses that belong to its lineage; the rest stay dormant. Which
rulesets light up which options, and how that meshes with tag-team assists and
the Kameo partner system, is covered in
./rulesets-tag-and-kameo.md.
Game feel — hitstop, shake, slow-mo, tint#
A correct hit that feels limp is a failed feature. V2 centralizes game feel into
one tunable catalog — UV2CombatRulesetData::BuildDefaultGameFeelCatalog()
returns an FV2GameFeelCatalog (id GameFeel.Catalog.V2) — so every ruleset
can hit differently from one shared system, and every value is a CSV a designer
edits without a recompile.
Hitstop and hitlag — the "weight" of a hit#
Hitstop is the brief freeze on impact that sells force. The catalog's
HitstopPolicy splits attacker and defender hitstop, adds a defender-only
hitlag window, a counter-hit bonus (2–3 frames), a configurable block
hitstop (default 4), and a crush/punish bonus — all authored per ruleset and
per damage tier in V2/balance/feel/hitstop_curves.csv. The shipped rows make
the identity differences concrete: MK.Heavy freezes 8 attacker / 10 defender
/ 8 hitlag with a 6-frame Sakkin, MK.Launcher goes to 9 / 12 / 9, while
SF6.Heavy stays conservative at 6 / 7 / 5 and SC.WeaponClash rings at
7 / 9 / 6 — MK's "juicy and heavy," SF6's "crisp and quick," Soul Calibur's
"steel-on-steel." Two columns matter for netplay: every row sets
rollback_budget_excluded = true and resim_cosmetic_suppressed = true, which
is why a heavy hit's freeze never blows the rollback budget and never
re-triggers during re-simulation (the architecture page traces this through the
sim world's if (HitstopFrames > 0) --HitstopFrames; else ++AnimationFrame;).
Camera shake, slow-mo, freeze, and tint#
V2/balance/feel/camera_shake_curves.csv defines 9 shake curves scaling
from Small (base 0.20, reduced-motion ceiling 0.12) through Heavy,
BoneShatter, WeaponClash, and Explosion up to KO (1.00 / 0.35) and
Fatality (1.20 / 0.35) — and GameFeelTuning.spec.cpp asserts the
ordering must hold (Fatality stronger than KO) and that every reduced-motion
ceiling sits below its base intensity, so the accessibility clamp can never be
louder than the default. At runtime UV2CameraShakeLibrary folds Perlin /
spring / directional-impact patterns into one evaluation clamped to a 50-unit /
10-degree maximum.
slowmo_freeze_curves.csv tunes the dramatic pauses: a round-clinching KO
slow-mo at 48 frames with a camera ride, a finisher freeze-frame at 18
frames with zoom and tint, Soul Calibur's Reversal-Edge clash at 18, MK's
mid-combo launcher emphasis at 10, and ruleset-specific KO treatments (WWE
broadcast KO at 54, UFC's realistic flash KO at 30) — every row exposing
an accessibility_opt_out. Screen-state tints (counter-hit flash, low-HP
vignette, comeback aura, and power-state tints for Devil / Soul Charge / Heat /
Blazin' / Burnout) and the photo-mode tone curves round out the visual layer.
Tunability and accessibility#
The catalog's FV2GameFeelTunabilitySpec points at the exact CSV paths
(V2/balance/feel/hitstop_curves.csv, …/camera_shake_curves.csv,
…/slowmo_freeze_curves.csv, …/photo_mode_tone_curves.csv) and names an
editor utility widget (EUW_GameFeelMovePreview) for previewing feel per move —
and the spec adversarially rejects a catalog whose tunability path doesn't
point at the canonical balance CSVs. Because the reduced-motion ceilings are the
same data the accessibility surfaces read, "tone down screen shake" is not a
separate code path; it's a clamp baked into the shared feel system. The full
accessibility surface is in the UI/HUD/accessibility feature page set.
Control schemes and the accessibility ramp#
The same combat core serves both the execution purist and the newcomer. V2 ships
Classic (full motion-input fidelity), Modern (assisted single-button
specials, ranked-eligible), Dynamic (fully assisted, ranked-locked), and
Easy combo/Fatality options. The balance tradeoff is real data, not a
slogan: V2/balance/data/modern-vs-classic-balance.csv pins Modern's default
damage scalar at 0.80 against Classic's 1.00, and already records a
per-fighter tuning exception — Fighter.King Modern bumped to 0.82 because
simplified grappler routes underperform Classic beyond the launch parity band.
That's the control-scheme balance loop expressed as a tracked, designer-owned
tuning row.
How it connects#
Combat is the hub the rest of V2 reads. The frame data, attack properties, and input-buffer windows feed the animation and motion-matching layer; the deterministic hit resolution and the tag-team/assist handshakes feed netplay; the combo, juggle, and blockstring components plus the golden-replay corpus feed the training, trials, and replay surfaces in ./mode-catalog-training-and-replay.md; and the per-ruleset feel definitions, defensive toggles, tag mechanics, and Kameo partner system are inventoried in ./rulesets-tag-and-kameo.md. For the engine-side treatment of every system named here — the GAS authority/sim-world split, the attribute sets, the gameplay-effect appliers, the determinism guardrails, and the state-hash desync signal — read the architecture companion, ../architecture/combat-system-gas-frame-data-and-determinism.md.
Related#
- The feature hub: ../V2_features.md
- Rulesets, Tag & Kameo — per-ruleset feel, tag-team/assist mechanics, and the MK1-style Kameo partner option
- Mode Catalog, Training & Replay — where these systems are practiced, recorded, and played back
- Architecture companion: ../architecture/combat-system-gas-frame-data-and-determinism.md — GAS, frame data, game feel, and determinism on the engine side