Steal-item table Confirmed
What can you steal from each enemy, and how often does it work? With the Evil God Icon accessory equipped, the answer is a static two-byte-per-monster lookup baked into SCUS_942.54, the game's main executable on the disc: steal chance (percent) and stolen item id. The surprise is where it lives - in the executable, not in the monster archive alongside the rest of a monster's battle data. The monster record's reward block carries only gold / exp / drop, so every attempt to find the steal item by scanning monster records was structurally doomed. Second trap: the two bytes are stored chance-first, the reverse of the drop pair in the monster record.
At a glance
- Magic
- none - static data in
SCUS_942.54 - Where
DAT_80077828(file offset0x68028), 2-byte stride, 1-based monster id; not in the PROT 867 monster record- Parser
legaia_game_tables::steal_table::StealTable; randomizerlegaia_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 offset0x68028inSCUS_942.54)- Index
DAT_80077828 + monster_id*2- 1-based monster id, the monster archive's id space; entry0is a reserved sentinel
The item id is in the item-name table's id space; 0 = none.
+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.