Author: HobbitDur

  1. Informations & stats
    1. Stat values
    2. Abilities
      1. AnimationSequenceID
    3. Renzokuken data
    4. Draw Mug Drop
    5. Elemental resistance
    6. Status resistance
    7. Rate value
    8. Byte 246: Camera category
    9. Byte 255: Devour category
    10. Function/address reference

Informations & stats

Offset Length Description
0 24 bytes Monster name in FF8 String
24 4 bytes HP values
28 4 bytes Str values
32 4 bytes Vit values
36 4 bytes Mag values
40 4 bytes Spr values
44 4 bytes Spd values
48 4 bytes Eva values
52 16*4 bytes Abilities, low level
116 16*4 bytes Abilities, med level
180 16*4 bytes Abilities, high level
244 1 byte Med level start
245 1 byte High level start
246 1 byte Camera category — a small integer selecting how camera sequences frame this monster. See details below
247 1 byte [LSB] Zombie / Fly / unused / LvUp-Down Immunity / HP Hidden / Auto-Reflect / Auto-Shell / Auto-Protect [MSB]. Bit 0x04 (third from LSB) is never read by the loader — it has no effect (setMonsterInfoFromDatInfoSection at 0x48BBD0 is the only reader of this byte and tests every other bit)
248 3 bytes Card (drop/mod/rare mod)
251 3 bytes Devour (low/med/high)
254 1 byte [LSB] IncreaseSurpriseRNG / DecreaseSurpriseRNG / SurpriseAttackImmunity / IncreaseChanceEscape / DecreaseChanceEscape / unused / Gravity Immunity / Always obtains card [MSB]
255 1 byte DevourCategory. Consumed by the Devour code, which returns it as the “damage” of the devour hit — technically real but invisible in practice, so likely vestigial.
256 2 bytes Extra EXP
258 2 bytes EXP
260 8 bytes Draw (low)
268 8 bytes Draw (med)
276 8 bytes Draw (high)
284 8 bytes Mug(low)
292 8 bytes Mug(med)
300 8 bytes Mug(high)
308 8 bytes Drop (low)
316 8 bytes Drop (med)
324 8 bytes Drop (high)
332 1 byte Mug rate
333 1 byte Drop rate
334 1 byte Padding (0x00)
335 1 byte APs
336 16 bytes Renzokuken data
352 8 bytes Elemental resistance (Fire, Ice, Thunder, Earth, Poison, Wind, Water, Holy)
360 20 bytes Status resistance (Death, Poison, Petrify, Darkness, Silence, Berserk, Zombie, Sleep,
Haste, Slow, Stop, Regen, Reflect, Doom, Slow Petrify, Float, Confuse, Drain, Expulsion, VIT0 (unused by the game))

Stat values

Each stat (bytes 24–51) is four bytes, A B C D in file order, turned into the stat at the monster’s level L. Every division truncates toward zero.

Stat Value at level L
HP A×L²/20 + A×L + 10×B + 100×C×L + 1000×D
Str, Mag (C + L×A/10 + L/B − (L²/D)/2) / 4
Vit, Spr, Spd, Eva C + L×A + L/B − L/D

The L² term of Str and Mag is subtracted: with a small D it overtakes the rest, and the stat falls as the level rises. Str to Eva are then capped at 255, but only from above: the result is stored in a byte, so a negative value wraps around (−3 becomes 253). A steep enough Str/Mag curve at a high level therefore turns into a very high stat. B and D are divisors with no zero check: a 0 there crashes the game.

In battle, Str, Vit, Mag, Spr, Spd and Eva are also multiplied by the monster’s AI stat variable divided by 10 (10 by default, so ×1), which its AI script can change.

Abilities

Each ability entry is four bytes. The low, medium, and high level-tier lists each contain 16 entries, starting at section-relative offsets 0x34, 0x74, and 0xB4. The normal AI USE instruction selects an entry from the active tier; its type determines how to interpret the ability ID (for example, type 2 indexes magic data and type 8 indexes enemy attacks). See Battle Cameras for the full ability-to-action-to-camera path.

Abilities are composed of:

Length Description
1 byte AbilityTypeID
1 byte AnimationSequenceID
2 byte AbilityID

AnimationSequenceID

Refers to the actor’s sequence in Animation sequences, not a camera-animation index. The queued action calls this field animationSequenceId (formerly unk3). For monster Magic in the relevant opening-camera branch, the engine tests whether this ID is 11 and the actor’s saved base sequence is 1, then selects camera 0 or 1 from the monster’s section 6. Other command types can take different branches; see the selection rule and command scope.

Renzokuken data

The data is 8*2 bytes, each 2 bytes corresponding to an ID on the Special Action list

Draw Mug Drop

Each section is composed of 8 bytes corresponding to 4 id (magic for draw, Item for mug & drop) and 4 quantity. Quantity is not used for draw (always 0 but no impact on game when changing it)

Length Description
1 byte ID 1
1 byte Quantity 1
1 byte ID 2
1 byte Quantity 2
1 byte ID 3
1 byte Quantity 3
1 byte ID 4
1 byte Quantity 4

Elemental resistance

The % def value follows the formula: value% = 900 - value_hex * 10

and to revert back: value_hex = floor((900 - value%) / 10))

value% is the damage the monster takes from that element:

  • 100 is normal damage.
  • 200 is double damage (a weakness).
  • 0 is no damage (immune).
  • Below 0 the monster absorbs the element: the damage heals it and shows as a green number.

When the monster loads, the game keeps elemDef = value_hex * 10. Magic and GF damage of an elemental attack is then multiplied by (900 - elemDef) / 100, which is value% / 100.

  • An attack with several elements uses only the first one set, in the order Fire, Ice, Thunder, Earth, Poison, Wind, Water, Holy.
  • Holy against a Zombie target always counts as 200%.
  • Weakness has no cap.
  • A physical attack uses the attack’s element percentage as well: damage + damage * element_percent / 100 * (value% - 100) / 100.

Examples from vanilla: Snow Lion has Fire 250 and Ice -100 (weak to fire, absorbs ice), Bomb has Fire -100 and Ice 300.

Status resistance

Each byte is copied as-is into the monster’s battle status resistance (FF8BattleMentalStatus) when it loads. Byte n covers these statuses, in this order:

Bytes Statuses
0-6 Death, Poison, Petrify, Darkness, Silence, Berserk, Zombie
7-11 Sleep, Haste, Slow, Stop, Regen
12-18 Reflect, Doom, Slow Petrify, Float, Confuse, Drain, Expulsion
19 VIT0 - never read: the resistance to VIT0 stays at 100

Every status the list does not name (Protect, Shell, Aura, Curse, Invincible…) gets 100.

The % def value follows the formula: value% = value_hex - 100

and to revert back: value_hex = value% + 100

value_hex is the resistance itself:

  • 100 (0%) is neutral.
  • Each point above 100 lowers the chance of inflicting that status by 1%.
  • 200 or more (100% or more) is immune. Only a status attack accuracy of 255 still lands, because it skips the resistance check.
  • Below 100 is more vulnerable than neutral. Retail monsters go down to 80 (-20%).

The chance of a status attack landing is: chance = accuracy + attacker_stat/4 - target_stat/4 - value_hex

The stats are STR and VIT for a physical attack, and MAG and SPR for magic and GFs. Vit0 on the target makes its stat count as 0. The chance must be above 0. An accuracy of 250-254 then lands; any other accuracy lands when GetRandomInt() <= 255 * chance / 100.

Rate value

The mug rate (byte 332) and drop rate (byte 333) are raw bytes (0–255), not stored as a percentage. The engine compares them directly against a uniform battle random number in the range 0–255 (GetRandomInt), so the true probability is out of 256, with a +1 from the <= comparison.

Drop — ComputeProbabilityGetItemMug, rolled on kill inside applyDamageAndHandleDeath:

the drop succeeds when  GetRandomInt() <= DropRate      // GetRandomInt() ∈ [0, 255]
=> P(drop) = (DropRate + 1) / 256

There is no zero-guard on the drop, so DropRate = 0 still yields a 1/256 (≈0.39%) chance, and DropRate = 255 is a guaranteed drop.

Mug — getMugObjectIdAndQuantity, used by the steal/Mug path:

if MugRate == 0            => never steal
the mug succeeds when  GetRandomInt() <= MugRate + attacker_SPD / 2
=> P(mug) = (MugRate + min(attacker_SPD/2, ...) + 1) / 256

Unlike the drop, the mug has a hard MugRate == 0 => never guard and adds half the attacker’s SPD to the threshold, so a faster stealer succeeds more often.

A convenient approximation for display is % ≈ (value_hex + 1) / 256 * 100 (e.g. 128 ≈ 50.4%, 255 = 100%). The older value_hex * 100 / 255 formula is close but slightly off (wrong denominator, and it ignores the +1).

The battle module also has generic probability helpers used by the auto-summon / escape / card rolls — isRandomProbaNumDen255 and isRandomNumeratorDenominator — which compute 255 * numerator / denominator and succeed when that value >= GetRandomInt(). This is the same 0–255 convention (a roll quoted as “32/255” means a threshold of 32 against the 0–255 random).

Byte 246: Camera category

This byte is a small per-monster integer that tells the battle camera how to frame the monster (roughly, a size/distance class — it is what makes the camera pull back further for large enemies when a spell is cast on them, etc.). It is not a bitfield in the usual sense; it is read once per entity at battle start and converted into a camera value on the entity, which the camera-sequence VM then reads back.

How the engine uses it:

  1. At battle start, initAnimationSequenceAtStartBattle reads this byte through getMonsterCameraCategory and converts it into the entity’s cameraDataRelated field using the mapping below. Playable characters (com id < 0x10) always get cameraDataRelated = 0; only monsters use the byte.
  2. During a fight, the camera-sequence VM can read a target’s cameraDataRelated back via C3 special variable 0x15 (21), letting a camera sequence branch on the target’s category to pick a framing.

Conversion from byte 246 to cameraDataRelated:

Byte 246 value cameraDataRelated Notes
bit 0x8 set 5 Not used by any vanilla monster
bit 0x4 set (and not 0x8) 4 i.e. value 4
3 0 3 − value for the remaining low values
2 1  
1 2  
0 3 Default / closest framing

Vanilla monsters only use values 0–4, so the effective camera value ranges 0–4 (the 0x8 → 5 branch exists but is never triggered by shipped data).

Byte 255: Devour category

It is an integer, a per-monster category consumed by the Devour system:

What the engine does with it (PC version):

  1. When a Devour command resolves, computeDevour picks the devour effect ID from bytes 251–253 (by level tier). If the ID is 255 (Immune) the devour fails immediately. Otherwise it stores the effect ID (DEVOUR_EFFECT_ID) and this byte (DEVOUR_CATEGORY_OR_SWAP_COM_ID) into a pending-devour block.
  2. Battle_DamageGettingRelated’s ATTACK_TYPE_DEVOUR case then rolls the devour: it succeeds only if the devourer’s current HP ≥ the target’s current HP, with probability (attacker_hp − target_hp) / attacker_hp (so devouring gets more reliable the more the target is weakened — a previously undocumented formula). On success the target gets the Eject status (this is how Devour removes a monster without death rewards) and the devour message is printed; on failure the miss flag is set and the byte is overwritten with 8.
  3. Either way, the function returns this byte as the “damage” value of the devour hit. So on PC its gameplay effect is real but essentially invisible: a successful devour deals 0–7 damage to a monster that is being ejected anyway, and on a failure the miss flag suppresses the 8. In practice you cannot see it in game — which is why it stayed unidentified so long. The categorized values strongly suggest it originally had a richer purpose (flavor text or per-category outcome).
  4. Side note: the effect ID meanwhile drives everything visible — statuses and heal/damage quantities from the kernel devour table, and the Str/Vit/Mag/Spr/Spd/HP permanent bonuses via relatedToDevour.

The data itself is a deliberate classification. No name for the categories survives in the engine or the reference data — the names in the Name column below are proposed based on which monsters use each value, not something extracted from code. Monster lists come from scanning all vanilla c0m files (144–199 are garbage filler):

Value Name Monsters Reading
0 DEVOUR_CATEGORY_PRIME_BEAST Thrustaevis, Mesmerize, Blue Dragon, Adamantoise, Chimera, Behemoth, T-Rexaur, Ruby Dragon, Cactuar, Tonberry, Torama Notably includes every stat-bonus devour (Str/Vit/Mag/HP +1)
1 DEVOUR_CATEGORY_STANDARD SAM08G, Snow Lion, Caterchipillar, Fastitocalon, Hexadragon, Armadodo, Turtapod, Wendigo, Gayla, Death Claw, Grand Mantis, Grendel, PuPu, Lefty, Righty, Vysage Mostly Full HP Recovery devours
2 DEVOUR_CATEGORY_SMALL_CRITTER Glacial Eye, Buel, Red Bat, Blitz, Fastitocalon-F, Jelleye, Imp, Anacondaur, Cockatrice, Tri-Face, Bomb, Abyss Worm Small / flying / venomous critters
3 DEVOUR_CATEGORY_PLANT Grat, Ochu, Malboro, Funguar Plants
4 DEVOUR_CATEGORY_AMORPHOUS Blobra, Gesper, Blood Soul Amorphous / spirits
5 DEVOUR_CATEGORY_UNDEAD Forbidden Undead
6 DEVOUR_CATEGORY_PEST Geezard, Bite Bug Small pest group
7 DEVOUR_CATEGORY_MACHINE GIM52A, GIM47N, Elastoid, Iron Giant, Belhelmel Machines / inorganic
8 DEVOUR_CATEGORY_INEDIBLE Every boss/story monster (c0m061+) and early bosses Bosses / story monsters. Matches the engine forcing this value on a failed devour; these monsters are all Devour-immune anyway, so it’s never even read for them

Note the consistency: DEVOUR_CATEGORY_INEDIBLE (8) is the failure code the engine itself writes, and it is exactly the value stored on every non-devourable monster. Modders can effectively treat this byte as free space (its only vanilla effect is 0–7 phantom damage on a successful devour), or repurpose the already-wired data path: monster byte 255 → DEVOUR_CATEGORY_OR_SWAP_COM_ID → returned as the devour hit’s damage.

Function/address reference

For readers following along in IDA / a disassembler. Names above are the ones set in the shared IDA database.

Name Address Role
Stat_ComputeMonsterStats 0x48C1C0 Monster battle stats: each curve’s value × the AI stat variable / 10
Stat_ComputeMonsterStatCurve 0x48C3F0 Str/Vit/Mag/Spr/Spd/Eva at a level from the four curve bytes (formulas above)
Stat_ComputeMonsterMaxHP 0x48C500 Max HP at a level from the HP curve bytes
CapTo255 0x495930 Caps a stat at 255 from above only; the byte store then wraps a negative value
initAnimationSequenceAtStartBattle 0x5027D0 Per-entity battle-start init; converts byte 246 into the entity’s cameraDataRelated
getMonsterCameraCategory 0x48B9F0 Reads byte 246 (camera_category) from a monster’s info section
CameraSeq_ReadSpecialVar_C3 (renamed from a3ParamAnimSeqForCamera in the community IDB) 0x509640 Camera-sequence VM special-variable reader; C3 var 0x15 returns a target’s cameraDataRelated
ComputeProbabilityGetItemMug 0x486650 On-kill item drop: reads byte 333 (DropRate), drop succeeds when GetRandomInt() <= DropRate
getMugObjectIdAndQuantity 0x4867C0 Mug/steal: reads byte 332 (MugRate); 0 = never, else succeeds when GetRandomInt() <= MugRate + attacker_SPD/2
GetRandomInt 0x48F020 Battle random byte generator, returns a value in [0, 255]
setMonsterInfoFromDatInfoSection 0x48BBD0 Copies the 20 status resistance bytes into the battle slot’s FF8BattleMentalStatus (byte 19 is skipped); other statuses get 100
Battle_ApplyStatusWithResistRoll 0x48F9F0 Status attack roll: immune when the resistance is >= 200 (cmp 0C8h), else the chance formula above
Damage_ComputeMagicAndGF 0x491AD0 Magic/GF damage: multiplied by (900 - elemDef) / 100 for the attack’s first element
Damage_ApplyPhysicalModifiers 0x48F600 Physical damage: + damage * element_percent * (800 - elemDef) / 10000
computeDevour 0x48FC60 Picks the devour effect ID by level tier, stores it and byte 255 into the pending-devour block
Battle_DamageGettingRelated 0x4922B0 Resolves attack-type-specific damage; ATTACK_TYPE_DEVOUR case at 0x4926BC rolls the devour and returns byte 255 as the hit damage
relatedToDevour 0x492220 Applies the devour effect’s permanent Str/Vit/Mag/Spr/Spd/HP bonuses from the kernel devour table
isRandomProbaNumDen255 0x48F0F0 Generic probability helper: succeeds if 255*numerator/denominator >= GetRandomInt() (verified IDA function)
isRandomNumeratorDenominator 0x48F0C0 Generic probability helper, same convention as above (verified IDA function)
DEVOUR_EFFECT_ID 0x1D28E27 Pending devour effect ID (from bytes 251–253)
DEVOUR_CATEGORY_OR_SWAP_COM_ID 0x1D28E28 Pending devour category (byte 255); reused as a scratch com-id by the unrelated party-swap flow