Final Fantasy VII
Square's 1997 PlayStation game, read through Xeeynamo's ff7-decomp, a matching decompilation of the North American Disc 1.
On this page
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
| Mechanic | Kind | How |
|---|---|---|
| Damage formula | Defense as a fraction | a base from attack and level, times power/16 and (512 − defense)/512, then 3,841–4,096 over 4,096 |
| Hit chance | Hit rate against evade | the 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 hit | Weapon and stats | (level + luck − target level)/4 plus the weapon's critical %, against 1–100; doubles physical damage |
| Limit Break | From damage taken | 300 × damage over max HP, times 256 over a per-character divisor, from enemy hits only |
Every claim about this game, by concept (4)
References
- ^In the North American release,
BattleSetTmpDmgAsPhysicaltakes the attacker's physical attack S (physAttackafter 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 whendamageFlagsbit $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'sbackDamageMult/8 when the unnamedunk234bit 0 is set, quarters it under Frog, applies the Sadness, multi-target, and Barrier modifiers, and sets it to 0 under Mini. Last,BattleAddRndModifierAndZeroCheckmultiplies 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'sCalcDmgModalso 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 disassemblyrevisedHow 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 North American release,
BattleRollPhysicalHithits 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 (unk234bit 0,unk218bit 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.BattleGetRnd164scales 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 disassemblyHow 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 North American release,
BattleRollCriticalHitis 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) ofdamageFlags.BattleSetTmpDmgAsPhysicaldoubles the damage when that bit is set, and sets it itself for an attack whoseattackFlagsbit $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 disassemblyHow 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 North American release, when an enemy's action takes HP from a party member,
BattleApplyDamageAndChargeLimitGaugeadds ⌊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 (turnFlagsbit 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-decompHow 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.
Sources
Code
- Xeeynamo and ff7-decomp contributors. 2026. “ff7-decomp: a matching decompilation of Final Fantasy VII for the PlayStation (Xeeynamo/ff7-decomp).” Commit fc71ccc. Xeeynamo/ff7-decomp @ fc71ccc · licence, all uses
- everything8215. 2026. “Final Fantasy VI Disassembly.” Commit 8130132. everything8215/ff6 @ 8130132 · licence, all uses