Table base + record layout

A note on naming: DAT_80xxxxxx is a Ghidra label (Ghidra is the disassembler this project uses) for a fixed RAM address - here, where the table sits once the PlayStation loads the executable. Because the data is static, the same bytes can be read straight out of the SCUS_942.54 file on the disc; the parser translates the RAM address to a file offset. "Stride" is the size of one record - step that many bytes to reach the next spell.

The table is viewed through two interleaved base pointers over the same records: DAT_800754C8 (the stats base, read for MP + class + target) and DAT_800754D0 (the same base shifted +8, read for the name pointer). Both step 0xC per spell id, so record id sits at DAT_800754C8 + id*0xC.

FieldValue
Stats baseDAT_800754C8
Name-pointer baseDAT_800754D0 (= stats base +8)
Index formDAT_800754C8 + spell_id*0xC
Stride0xC bytes
OffsetTypeField
+0u8class byte - 'c' (0x63) marks a capture-class spell
+1u8sub-index within the class
+2u8target shape (below)
+3u8MP cost
+4u8animation id
+5..+8-padding (zero)
+8u32name_ptr - pointer to the display-name C-string (leading 0xCE colour-control prefix from MES, the game's text markup, before the ASCII name)

Target shape (+2)

Two independent bits over a side/scope pair: bit 0x02 = ally side (clear = enemy side), bit 0x20 = all targets on that side (clear = single).

ValueShape
0x44one enemy
0x64all enemies
0x06one ally
0x26all allies

One documented exception: the revive Ra-Seru Horn ("Resurrector", id 0x9C) carries an enemy-side 0x24 byte even though it revives all allies - the summon projects toward the enemy field and the revive is special-cased by spell id. Decoded by legaia_asset::spell_names::SpellEntry::target_shape.

Id ranges

IdsContents
0x00..=0x24internal enemy-attack tiers - MP / element / target populated, empty name pointers
0x25..=0x7Fnamed monster attacks (Fire Breath 0x25, Tail Fire 0x27, …) + capture-class spells ('c' at +0)
0x81..=0x95the 21 player Seru-magic spells (Gimard 0x81 … Gilium); the engine-pinned block retail_magic::SERU_MAGIC covers 0x81..=0x8B, anim ids 0x25..=0x2F
0x9A..=0xA0Ra-Seru summons - Palma, Mule, Horn, Jedo, Meta, Terra, Ozma (the hidden 8th, Juggernaut, is outside this contiguous named region)

An enemy cast is named exactly like a party cast: the AI spell picker FUN_801E9FD4 (a Ghidra-traced function in the battle overlay - a chunk of code the game loads into RAM on demand; extraction entry 0898 / raw TOC index 0x384) reads a global spell id from the monster record's magic-attack array at +0x21..=+0x23, writes it into the live battle actor at +0x1DF, and the battle-action SM prints &DAT_800754D0 + id*0xC. The monster archive's local +0x4C entry ids (0x0C..=0x1F) only gate action cost - they are not this table's id space.

No per-spell power scalar

The table carries no magic-power field - bytes +5..+8 are zero on every record, and the whole player Seru block shares class = 0x32, sub = 0, so the table cannot even distinguish Gimard from Nova. Summon magnitude is battle-state-derived, not static data:

  • Damage summons roll via the shared kernel FUN_801dd0ac - rand % (summon_AGL + 1) + summon_HP + caster_AGL×2 against a defender roll, then element-affinity / status-weaken / magic-power scaling in FUN_801dd864. Ported as pure kernels in legaia_engine_vm::battle_formulas.
  • Heal summons compute (power_byte << 5) + 0xE0 inline, with power_byte fetched from the caster's character record (spell-id match at +0x705, power byte at +0x729).
  • The genuine per-move power scalar that does exist - the 26-byte-stride table at overlay VA 0x801F4F5C - feeds the arts/physical branch and shares this table's id space; see the move-power table.

Parser + engine consumption

legaia_asset::spell_names resolves the table from a SCUS_942.54 image (PSX-EXE t_addr → file-offset map); CLI asset spell-names <SCUS> [--json]. The engine sources the player Seru-magic catalog's MP + target from the user's executable via legaia_engine_core::retail_magic::seru_magic_catalog_from_scus, falling back to the pinned retail_seru_magic_catalog on disc-free builds. The legaia-gamedata magic_vs_disc oracle joins the curated magic chart to this table by name: MP byte-exact for all 21 Seru + 7 Ra-Seru joins, target shapes agreeing everywhere except the explicit Horn exception.

Superseded readings

Superseded readings
  • The earlier "bit 0x40 = enemies" target reading is equivalent for the four player-block values but misclassifies the internal enemy-attack tiers (byte 0x04: 0x40 clear yet enemy-targeting). The 0x02-bit / low-nibble reading is correct.
  • The "spell magnitude is in the summon jump table" hypothesis is falsified: the JT at 0x801F69D8 resolves into the resident render overlay (extraction entry 0900) and is animation/rendering only - no multiply, no HP write, no power read. The magnitude is applied by the paired stager overlays (extraction entries 0903..0915).
  • The 0xC5 MES substitution table at DAT_80075EC4, once mistaken for a spell-name source, is the Tactical Arts name table (see art records) - per-character art names, no spells.

See also