At a glance

Magic
none - static data in SCUS_942.54
Where
DAT_80077828 (file offset 0x68028), 2-byte stride, 1-based monster id; not in the PROT 867 monster record
Parser
legaia_game_tables::steal_table::StealTable; randomizer legaia_patcher::steal
Confidence
Confirmed - pinned from a live steal capture and byte-exact against the complete published steal table
Used by
Enemy table (steal column), Randomizer, ROM patcher

Field order is [chance, item] - the reverse of the drop pair in the monster record. Read it item-first and every chance shifts by one monster.

Table base + record layout

(DAT_80077828 is a Ghidra label - the disassembler's name for the fixed RAM address the table occupies. Static data: the address maps directly to a file offset in SCUS_942.54 on the disc.)

Base
DAT_80077828 (file offset 0x68028 in SCUS_942.54)
Index
DAT_80077828 + monster_id*2 - 1-based monster id, the monster archive's id space; entry 0 is a reserved sentinel

The item id is in the item-name table's id space; 0 = none.

steal table (SCUS) chance % item id +0 +1 drop pair (monster record) item id chance % ← reversed order
Two bytes per monster, chance first - the reverse of the drop pair (+0x48 item / +0x49 chance) in the monster record.

Field order is chance-then-item - the reverse of the drop fields in the monster record (+0x48 item / +0x49 chance). Reading the table with the record's item-first order shifts every chance by one monster. A chance of 0 (or item 0) means the enemy can't be stolen from; a raw steal id resolves to a name the same way a drop id does (0x8A → Incense).

How we know

Pinned from a live player-steal RAM capture (Evil God Icon equipped): the Skeleton (monster id 13) entry at 0x80077842 is 1e 8a = 30% Incense, exactly the steal the in-battle banner shows. Base, stride, and the [chance, item] order are confirmed byte-exact against the complete published steal table - item and chance columns - across every resolvable monster id, zero mismatches. The table is static rodata, resident in RAM identical to the file bytes.

Why the monster record has no steal field

An exhaustive offset scan of the decoded monster record (extraction entry 0867 / raw TOC index 0x365 - a numbered entry of PROT.DAT, the disc's single big archive - the battle_data monster archive), correlated against ground-truth steal data, finds no steal field there: the reward block at +0x44..+0x49 holds only gold / exp / drop. Every record-only search for the steal item was structurally doomed - the data was never in the archive.

When a steal happens

The steal is not a command and does not happen on the hit. The battle animation commit FUN_8004AD80 rolls it as a slain monster finishes its knockdown fall, and only for the first monster each attacking action fells: a one-shot latch is set before the killer is even looked at, so a kill by someone without the Evil God Icon spends that action's attempt. The battle state machine re-arms the latch at the end of every attack chain. The killer must have used Attack and wear the icon; the roll is rand() % 100 < chance, doubled when any living party member has the Bronze Book's Items Up; and a stack already at 99 drops the steal silently. The caption goes up in the full-width bottom bar, composed from a template in the executable plus the item name.

The same arm also returns loot. An enemy's Steal (PROT 0941) parks what it took in a per-seat band at 0x801C8FE0; when that thief dies, the arm hands the item back to the bag instead of rolling a steal, with its own "recovered" caption.

In the port

legaia_game_tables::steal_table::StealTable::from_scus resolves the table from a SCUS_942.54 image (PSX-EXE t_addr → file-offset map, identical to the item-name table resolver). entry(monster_id) returns the [chance, item] pair; steal_item(monster_id) returns the item only when the entry is stealable. CLI: asset steal-table <SCUS> [--all] [--json], with the item id joined to its name.

The randomizer (legaia_patcher::steal) edits this table on a user-supplied disc to reassign steal items. The steal roll and the thief's return run in legaia_engine_battle::battle_steal.

Watching it in the retail game

An emulator probe confirms the roll, the doubling and the latch exactly as read from the disassembly.

What the capture showed

An emulator probe that breaks on every step of that branch, in a real battle with the Evil God Icon's passive bit written in, shows the roll is one BIOS rand() taken % 100 and compared strictly against the table's chance: a 29 steals at 30%, a 30 does not. The Bronze Book's Items Up passive doubles the threshold to 60, exactly.

The one-roll latch that stops the death animation from rolling again every frame is re-armed at the end of every attack chain (the battle state machine clears it when a strike loop hands over to its recovery wait), so each attacking action gets a roll on the first monster it fells - not one roll per battle. A plain byte search for writes to that latch misses the clear, because the instruction reaches it through a base register that already carries part of the offset.

See also