On this page
  1. Related
  2. Mechanics
  3. References
  4. Sources

Final Fantasy VII is Square’s 1997 PlayStation role-playing game. The code read here is Xeeynamo’s ff7-decomp, a matching decompilation that builds the North American Disc 1; its battle formulas are in the battle overlay’s battle.c.

Physical damage grows with the product of level and attack, and defense removes a share of it out of 512, where Final Fantasy VI works out of 2561. The hit chance counts a party attacker’s dexterity twice, once directly and once through its own Df%2. A critical reads luck and the two levels, where Final Fantasy VI’s is a flat 1 in 323. Enemy hits fill the Limit gauge by a share of max HP4.

Mechanics

MechanicKindHow
Damage formulaDefense as a fractiona base from attack and level, times power/16 and (512 − defense)/512, then 3,841–4,096 over 4,096
Hit chanceHit rate against evadethe attack's hit % and a quarter of dexterity, plus the attacker's Df% less the target's, against 1–100; luck can force a hit, or for a party member a dodge
Critical hitWeapon and stats(level + luck − target level)/4 plus the weapon's critical %, against 1–100; doubles physical damage
Limit BreakFrom damage taken300 × damage over max HP, times 256 over a per-character divisor, from enemy hits only
Every claim about this game, by concept (4)
Critical hitIn the North American release, BattleRollCriticalHit is entry 2 of the hit-formula table, so it runs only for an attack whose hit-formula script includes it, and only if the hit roll didn't mark a miss. Its chance is (the action's level + the attacker's luck − the target's level)/4, plus the weapon's critical % for a party member, or 255 under Lucky Girl; a 1–100 roll at or under the chance sets bit 1 ($2) of damageFlags. BattleSetTmpDmgAsPhysical doubles the damage when that bit is set, and sets it itself for an attack whose attackFlags bit $2000 is clear. So a chance c of 0–99 crits about c times in 99 (computed for evenly spread 16-bit values). Final Fantasy VI crits on a random byte under 8, 1 in 32, whatever the stats, and a critical adds 2 to the damage multiplier, which its code comments as damage ×2.Code
Damage formulaIn the North American release, BattleSetTmpDmgAsPhysical takes the attacker's physical attack S (physAttack after stat multipliers) and the action's level L (characterLevel) and computes (⌊L·S/32⌋ · ⌊(L + S)/32⌋ + S) × (512 − the target's defense) × the attack's power, over 8,192: a base of S + ⌊L·S/32⌋·⌊(L + S)/32⌋, scaled by power/16 and by (512 − defense)/512 (computed). It then doubles the damage when damageFlags bit $2 is set, the bit a critical sets and that this same function also sets for any attack whose attackFlags bit $2000 is clear (ff7/critical), multiplies it by 1.5 under Berserk, for a short-range attack or an enemy's Attack command halves it once if the target or the attacker is in the back row (other attacks ignore rows), halves it for a defending target, multiplies it by the target's backDamageMult/8 when the unnamed unk234 bit 0 is set, quarters it under Frog, applies the Sadness, multi-target, and Barrier modifiers, and sets it to 0 under Mini. Last, BattleAddRndModifierAndZeroCheck multiplies by 3,841 plus a random byte, over 4,096 (93.8–100%, computed), and raises 0 to 1, so a Mini attacker deals 1. Final Fantasy VI's CalcDmgMod also lets defense remove a share, but as (255 − Defense)/256, with a variance of 224–255 over 256 applied before it.Code
Hit chanceIn the North American release, BattleRollPhysicalHit hits when a roll of 1–100 (BattleGetRnd164) is under the hit chance: the attack's hit % (targetAttackPercent) plus a quarter of the attacker's dexterity, plus the attacker's Df%, less the target's Df%, cut by 30% under Fury, and raised to at least 1, which never hits. A party member's Df% is a quarter of its dexterity plus its physical evade and an enemy's is its physical evade alone, so a party attacker's dexterity counts twice. The chance is instead 255 against a target under Death, Sleep, Confusion, Stop, Petrify, Manipulate, or Paralysis, for the Death, Nullify, and Absorb affinities and one unnamed affinity flag, and for two flags the code leaves unnamed (unk234 bit 0, unk218 bit 5). Before the roll, a 0–99 roll under the attacker's luck/4 makes a sure hit; failing that, when an enemy attacks a party member and the same roll is under the target's luck/4, the attack misses, even at 255. BattleGetRnd164 scales a 16-bit random number by 99/65,535 and adds 1, so it gives 100 only for $FFFF, and a chance h of 2–100 hits about (h − 1) times in 99 (computed for evenly spread 16-bit values). Final Fantasy VI instead scales the hit rate by the target's stored evade (magic evade, through a bug) and rolls 0–99 against it.Code
Limit BreakIn the North American release, when an enemy's action takes HP from a party member, BattleApplyDamageAndChargeLimitGauge adds ⌊300 × damage / max HP⌋ × 256 over the character's HP divisor to the Limit gauge, with 512 in place of 256 under Fury and 128 under Sadness, and caps the gauge at 255; the damage counts in full, even past the HP the character had left. It adds nothing for healing, MP damage, a character whose limit level is 4 or more, or a turn flag the code leaves unnamed (turnFlags bit 3). The divisor is read from KERNEL.BIN per character and limit level (hpDivisor), so the code alone doesn't give its values.Code

References

  1. ^In the North American release, BattleSetTmpDmgAsPhysical takes the attacker's physical attack S (physAttack after stat multipliers) and the action's level L (characterLevel) and computes (⌊L·S/32⌋ · ⌊(L + S)/32⌋ + S) × (512 − the target's defense) × the attack's power, over 8,192: a base of S + ⌊L·S/32⌋·⌊(L + S)/32⌋, scaled by power/16 and by (512 − defense)/512 (computed). It then doubles the damage when damageFlags bit $2 is set, the bit a critical sets and that this same function also sets for any attack whose attackFlags bit $2000 is clear (ff7/critical), multiplies it by 1.5 under Berserk, for a short-range attack or an enemy's Attack command halves it once if the target or the attacker is in the back row (other attacks ignore rows), halves it for a defending target, multiplies it by the target's backDamageMult/8 when the unnamed unk234 bit 0 is set, quarters it under Frog, applies the Sadness, multi-target, and Barrier modifiers, and sets it to 0 under Mini. Last, BattleAddRndModifierAndZeroCheck multiplies by 3,841 plus a random byte, over 4,096 (93.8–100%, computed), and raises 0 to 1, so a Mini attacker deals 1. Final Fantasy VI's CalcDmgMod also lets defense remove a share, but as (255 − Defense)/256, with a variance of 224–255 over 256 applied before it.CodeXeeynamo/ff7-decomp · everything8215, FF6 disassemblyrevised
    How we know
    4491    if (g_CurrentAction->attackFlags & 4) {
    ⋮
    4494    } else {
    4495        attack = g_BattleState.combatant[g_CurrentAction->actorId].physAttack;
    4496        g_CurrentAction->attackStat = BattleApplyStatMult(g_CurrentAction->actorId, attack, STAT_MULT_PHYS_ATTACK);
    
    src/battle/battle.c lines 4491–4497 at fc71ccc · upstream ↗
    4569void BattleSetTmpDmgAsPhysical(void) {
    ⋮
    4583    stat = g_CurrentAction->attackStat;
    4584    level = g_CurrentAction->characterLevel;
    4585    mulTerm = (level * stat) / 32;
    4586    sumTerm = (level + stat) / 32;
    4587    raw = ((mulTerm * sumTerm) + stat) * (0x200 - g_CurrentAction->targetDefense) * g_CurrentAction->power;
    4588    damage = raw / 0x2000;
    4589    if (g_CurrentAction->damageFlags & 2) {
    4590        damage *= 2;
    ⋮
    4592    if (g_CurrentAction->attackerStatus & STATUS_BERSERK) {
    4593        damage *= 3;
    4594        damage >>= 1;
    ⋮
    
    src/battle/battle.c lines 4569–4623 at fc71ccc · upstream ↗
    4552// scale arg0 by a fixed-point random variance factor (~93.77%..100%), then
    4553// clamp the result to a minimum of 1
    4554static s32 BattleAddRndModifierAndZeroCheck(s32 arg0) {
    ⋮
    4559    temp_s0 = ((s32)(var_v0 * (SysGetRandomByteFromTable() + 0xF01))) >> 0xC;
    4560    var_v0 = temp_s0;
    4561    if (temp_s0 == 0) {
    4562        var_v0 = 1;
    ⋮
    4564    return var_v0;
    
    src/battle/battle.c lines 4552–4565 at fc71ccc · upstream ↗
    19u8 SysGetRandomByteFromTable(void) { return g_KernRndTable[g_KernRndTablePos[g_KernRndTableIndex]++]; }
    
    src/main/btlrand.c line 19 at fc71ccc · upstream ↗
    153; [ calculate damage modification ]
    ⋮
    155; random variance, defense stat, shell/safe, defending, row, morph, friendly fire
    ⋮
    157CalcDmgMod:
    ⋮
    165        jsr     Rand
    166        ora     #$e0        ; random variance (240..255)
    167        sta     $e8
    168        jsr     MultDmg
    ⋮
    178        lda     near wTargetProp2::MagicDefense,y
    179        bcc     @0cd4
    180        lda     near wTargetProp2::Defense,y
    181@0cd4:  inc
    ⋮
    190        dec
    ⋮
    
    src/battle/calc_dmg.asm lines 153–193 at 8130132 · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-22.

    Revised after a check by a second AI model, 2026-10-22.

    In the claim ledger

  2. ^In the North American release, BattleRollPhysicalHit hits when a roll of 1–100 (BattleGetRnd164) is under the hit chance: the attack's hit % (targetAttackPercent) plus a quarter of the attacker's dexterity, plus the attacker's Df%, less the target's Df%, cut by 30% under Fury, and raised to at least 1, which never hits. A party member's Df% is a quarter of its dexterity plus its physical evade and an enemy's is its physical evade alone, so a party attacker's dexterity counts twice. The chance is instead 255 against a target under Death, Sleep, Confusion, Stop, Petrify, Manipulate, or Paralysis, for the Death, Nullify, and Absorb affinities and one unnamed affinity flag, and for two flags the code leaves unnamed (unk234 bit 0, unk218 bit 5). Before the roll, a 0–99 roll under the attacker's luck/4 makes a sure hit; failing that, when an enemy attacks a party member and the same roll is under the target's luck/4, the attack misses, even at 255. BattleGetRnd164 scales a 16-bit random number by 99/65,535 and adds 1, so it gives 100 only for $FFFF, and a chance h of 2–100 hits about (h − 1) times in 99 (computed for evenly spread 16-bit values). Final Fantasy VI instead scales the hit rate by the target's stored evade (magic evade, through a bug) and rolls 0–99 against it.CodeXeeynamo/ff7-decomp · everything8215, FF6 disassembly
    How we know
    5702void BattleRollPhysicalHit(void) {
    ⋮
    5711    const s32 vulnerableStatusMask = STATUS_DEATH | STATUS_SLEEP | STATUS_CONFU | STATUS_STOP | STATUS_PETRIFY |
    5712                                     STATUS_MANIPULATE | STATUS_PARALYSIS;
    ⋮
    5716    hitChance = -1;
    5717    if (g_CurrentAction->unk234 & 1) {
    5718        hitChance = 255;
    ⋮
    5721    // Auto-hit on element Death-weakness, Auto-Hit weakness, Immune or Absorb
    5722    if (g_CurrentAction->affinityFlags & (AFFINITY_DEATH | AFFINITY_UNK2 | AFFINITY_NULLIFY | AFFINITY_ABSORB)) {
    5723        hitChance = 255;
    ⋮
    5737        hitChance = 255;
    ⋮
    5740    if (g_CurrentAction->unk218 & 0x20) {
    5741        hitChance = 255;
    ⋮
    
    src/battle/battle.c lines 5702–5780 at fc71ccc · upstream ↗
    5920s32 BattleCalcDefPercent(s32 arg0) {
    5921    s32 evade;
    ⋮
    5923    if (arg0 < START_ENEMY) {
    5924        evade = g_BattleState.combatant[arg0].dexterity / 4 + g_BattleState.combatant[arg0].physEvade;
    5925    } else {
    5926        evade = g_BattleState.combatant[arg0].physEvade;
    ⋮
    5928    return BattleApplyStatMult(arg0, evade, STAT_MULT_PHYS_EVADE);
    
    src/battle/battle.c lines 5920–5929 at fc71ccc · upstream ↗
    6008static s32 BattleApplyFuryHitChanceReduction(s32 hitChance) {
    6009    if ((hitChance < 0xFF) && (g_CurrentAction->attackerStatus & STATUS_FURY)) {
    6010        hitChance -= (hitChance * 3) / 10;
    ⋮
    6012    return hitChance;
    
    src/battle/battle.c lines 6008–6013 at fc71ccc · upstream ↗
    6667u16 BattleGetRndU16(void) { return SysRandomTwoBytes(); }
    ⋮
    6669// scale a 16-bit value into the range 1..100
    6670static s32 BattleGetRnd164(void) { return (((BattleGetRndU16() & 0xFFFF) * 0x63) / 0xFFFF) + 1; }
    
    src/battle/battle.c lines 6667–6670 at fc71ccc · upstream ↗
    19u8 SysGetRandomByteFromTable(void) { return g_KernRndTable[g_KernRndTablePos[g_KernRndTableIndex]++]; }
    ⋮
    21u8 SysGetRandomByteRange(s32 upperBound) { return (SysGetRandomByteFromTable() * upperBound) >> 8; }
    
    src/main/btlrand.c lines 19–21 at fc71ccc · upstream ↗
    166.else
    167        lda     near wTargetProp2::Evade,y
    168        bcs     @2347
    169        lda     near wTargetProp2::MagicEvade,y
    170@2347:  pha
    171        bcc     @2388
    172.endif
    ⋮
    185        lda     $ee
    ⋮
    195        lda     $ee
    ⋮
    200        sta     $ee
    201@2388:  pla
    202        xba
    ⋮
    
    src/battle/check_hit.asm lines 166–210 at 8130132 · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-22.

    Checked by a second AI model, 2026-10-22.

    In the claim ledger

  3. ^In the North American release, BattleRollCriticalHit is entry 2 of the hit-formula table, so it runs only for an attack whose hit-formula script includes it, and only if the hit roll didn't mark a miss. Its chance is (the action's level + the attacker's luck − the target's level)/4, plus the weapon's critical % for a party member, or 255 under Lucky Girl; a 1–100 roll at or under the chance sets bit 1 ($2) of damageFlags. BattleSetTmpDmgAsPhysical doubles the damage when that bit is set, and sets it itself for an attack whose attackFlags bit $2000 is clear. So a chance c of 0–99 crits about c times in 99 (computed for evenly spread 16-bit values). Final Fantasy VI crits on a random byte under 8, 1 in 32, whatever the stats, and a critical adds 2 to the damage multiplier, which its code comments as damage ×2.CodeXeeynamo/ff7-decomp · everything8215, FF6 disassembly
    How we know
    5825static void BattleRollCriticalHit(void) {
    ⋮
    5833    if (!(g_CurrentAction->unk218 & 1)) {
    5834        critChance = 0xFF;
    5835        if (!(g_CurrentAction->attackerStatus & STATUS_LUCKY_GIRL)) {
    5836            v = (g_CurrentAction->characterLevel + g_BattleState.combatant[attacker].luck) -
    5837                g_BattleState.combatant[target].level;
    5838            critChance = v / 4;
    5839            if (attacker < NUM_PARTY) {
    5840                critChance += g_BattleWork.setup[attacker].criticalHitChance;
    ⋮
    5843        if (critChance >= BattleGetRnd164()) {
    5844            g_CurrentAction->damageFlags |= 2;
    
    src/battle/battle.c lines 5825–5847 at fc71ccc · upstream ↗
    346            setup->criticalHitChance = character->weapon.criticalPercent;
    
    src/battle/batini.c line 346 at fc71ccc · upstream ↗
    5044const BattleFormula g_BattleHitFormulaJmpTbl[] = {
    5045    /* 0x0 */ BattleRollPhysicalHit,
    5046    /* 0x1 */ BattleRollMagicalHit,
    5047    /* 0x2 */ BattleRollCriticalHit,
    
    src/battle/battle.c lines 5044–5053 at fc71ccc · upstream ↗
    4443void BattleDmgFormulaRun(void) {
    4444    BattleFormula fnDamageFormula = g_BattleDmgFormulaJmpTbl[g_CurrentAction->dmgFormulaId];
    4445    s32 i = g_BattleHitFormulaOffs[g_CurrentAction->hitFormulaId];
    4446    s32 op;
    ⋮
    4448    while ((op = g_BattleHitFormulaOpcodeStream[i++]) != HIT_OPCODE_DELIM) {
    4449        g_BattleHitFormulaJmpTbl[op]();
    
    src/battle/battle.c lines 4443–4451 at fc71ccc · upstream ↗
    4580    if (!(g_CurrentAction->attackFlags & 0x2000)) {
    4581        g_CurrentAction->damageFlags |= 2;
    ⋮
    4589    if (g_CurrentAction->damageFlags & 2) {
    4590        damage *= 2;
    
    src/battle/battle.c lines 4580–4591 at fc71ccc · upstream ↗
    6669// scale a 16-bit value into the range 1..100
    6670static s32 BattleGetRnd164(void) { return (((BattleGetRndU16() & 0xFFFF) * 0x63) / 0xFFFF) + 1; }
    
    src/battle/battle.c lines 6669–6670 at fc71ccc · upstream ↗
    333@33f2:  lda     #$02
    334        bit     zb3
    335        beq     @340c                   ; auto-crit
    336        bit     zb2
    337        bne     @3414
    338        bit     zba
    339        beq     @3414
    340        jsr     Rand
    341        cmp     #$08
    342        bcs     @3414       ; 31/32 chance to branch
    343        lda     near w7e3eb0 + 25
    344        beq     @3414       ; branch if there are no targets
    ⋮
    
    src/battle/attack.asm lines 332–348 at 8130132 · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-22.

    Checked by a second AI model, 2026-10-22.

    In the claim ledger

  4. ^In the North American release, when an enemy's action takes HP from a party member, BattleApplyDamageAndChargeLimitGauge adds ⌊300 × damage / max HP⌋ × 256 over the character's HP divisor to the Limit gauge, with 512 in place of 256 under Fury and 128 under Sadness, and caps the gauge at 255; the damage counts in full, even past the HP the character had left. It adds nothing for healing, MP damage, a character whose limit level is 4 or more, or a turn flag the code leaves unnamed (turnFlags bit 3). The divisor is read from KERNEL.BIN per character and limit level (hpDivisor), so the code alone doesn't give its values.CodeXeeynamo/ff7-decomp
    How we know
    4252void BattleApplyDamageAndChargeLimitGauge(void) {
    4253    BattleUnit* target = &g_BattleState.combatant[g_CurrentAction->targetId];
    4254    s32 isHeal = g_CurrentAction->damageFlags & 1;
    ⋮
    4261        return;
    ⋮
    4265    if (g_CurrentAction->damageFlags & 4) {
    4266        oldValue = target->curMP;
    4267        target->curMP = SysAddSubWithMaxMinBound(oldValue, g_CurrentAction->tmpDamage, target->maxMP, isHeal);
    4268        return;
    ⋮
    4281    // only party members charge their limit gauge, and only from enemy attacks
    4282    if (g_CurrentAction->targetId >= NUM_PARTY || g_CurrentAction->actorId < START_ENEMY || isHeal) {
    4283        return;
    ⋮
    4286    if (g_CurrentAction->partyWork->limitLevel >= 4 || g_BattleWork.turn[g_CurrentAction->targetId].turnFlags & 8) {
    ⋮
    
    src/battle/battle.c lines 4252–4311 at fc71ccc · upstream ↗
    388                    party->limitBreakHPDivisor = D_80082268[charId].hpDivisor[limitLevel];
    
    src/battle/batini.c line 388 at fc71ccc · upstream ↗
    786// Per-character record of KERNEL.BIN section 3 (battle and growth data), 0x38 bytes
    787// each, nine of them from 0x80082268. The limit commands and HP divisors the
    788// limit-break code reads are the third and last fields.
    ⋮
    797    /* 0x28 */ s32 hpDivisor[4]; // by limit level
    
    include/game.h lines 786–797 at fc71ccc · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-22.

    Checked by a second AI model, 2026-10-22.

    In the claim ledger

Sources

Code