At a glance

Where
SCUS_942.54, RAM DAT_800754C8; the name pointers are read through the shifted base DAT_800754D0 (= stats base + 8)
Size
190 records (ids 0x00..=0xBD), 12 bytes each. Ends exactly where the description-pointer table 0x80075DB0 begins
Parser
legaia_asset::spell_names (SPELL_COUNT = 190); CLI asset spell-names <SCUS> [--json]; engine mirror engine-core::retail_magic
Confidence
Confirmed - readers traced in the battle and menu overlays; the curated magic chart joins byte-exact on MP
Used by
Battle-action SM (cast + banner), field menu (descriptions), spell-cost slider, Magic viewer

Record layout

Record id sits at base + id × 12. Every record has the same shape:

class sub target MP desc padding (zero) name pointer u8 u8 u8 u8 u8 3 bytes u32 → C string +0 +1 +2 +3 +4 +5 +8 +12 stats base DAT_800754C8 reads +0..+4 name base DAT_800754D0 reads +8
One 12-byte record. The stats reader and the name reader use two base pointers 8 bytes apart over the same rows.
OffsetSizeFieldMeaning
+0u8cast class0x32 player summon, 0x14 plain cast, 0x63 ('c') capture-class module (below)
+1u8sub-indexWithin the class; for capture-class records the module index (PROT 935 + sub)
+2u8target shapeSide / scope bits (below)
+3u8MP costDeducted from the caster when the cast starts
+4u8description indexInto the pause-menu description pointer array; 0 = none
+53paddingZero on every record - there is no power byte hiding here
+8u32name_ptrDisplay-name C string, prefixed by a colour-control code (0xCE, element colour, space)
The extent is 190, and nothing enforces it

The 12-byte walk does not stop at record 0xBD; only the reader bounds it. A tool that sweeps ids up to 255 walks straight into the neighbouring tables, and a write there corrupts the description pointers and the arts-name table on a patched disc. SPELL_COUNT is 190 (reading past the extent).

Id ranges

IdsContents
0x00..=0x24Internal enemy-attack tiers - MP / target populated, empty name, no description
0x25..=0x7FNamed monster attacks (Fire Breath 0x25, Tail Fire 0x27, …) and capture-class spells
0x80Boundary entry (MP and description both 0)
0x81..=0x95Player Seru magic, Gimard … Gilium. The first 11 (0x81..=0x8B) are the engine-pinned block retail_magic::SERU_MAGIC
0x9A..=0xA0Ra-Seru summons - Palma, Mule, Horn, Jedo, Meta, Terra, Ozma (the hidden eighth, Juggernaut, sits outside this run)
0xA1..=0xBDBoss cinematic casts and late capture-class spells (Dead End Crisis 0xA1 … Jugger Power 0xBA)

Player Seru-magic block (0x81..=0x8B)

MP and target are byte-exact from the executable; element comes from the curated game-data chart, which matches on every MP value.

IdNameElementMPTarget
0x81Gimardfire10one enemy
0x82Theederthunder24one enemy
0x83Veralight6one ally
0x84Gizamwater28all enemies
0x85Nightodark13one enemy
0x86Zenoirfire36one enemy
0x87Vigurothunder64all enemies
0x88Swordiewind32one enemy
0x89Orblight18all allies
0x8AFreedwater40all enemies
0x8BNovawind48one enemy

The whole player block shares class = 0x32, sub = 0: the table cannot tell Gimard from Nova, which is the first sign that damage lives elsewhere. Which Seru teaches which id is wired in engine-core::seru_learning::SeruRegistry::retail.

Target shape (+2)

Two independent bits: 0x02 = ally side (clear = enemy side), 0x20 = every target on that side (clear = single).

ValueShape
0x44one enemy
0x64all enemies
0x06one ally
0x26all allies
0x04one enemy (the internal enemy-attack tiers)

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

History: the "bit 0x40 = enemies" reading

Equivalent for the four player-block values but wrong for the internal enemy-attack tiers, whose byte is 0x04: bit 0x40 clear yet enemy-targeting. The 0x02-bit reading classifies them correctly. The curated chart's one target error (Mushura / "Crazy Driver" = single enemy) was corrected from this byte.

Cast classes (+0)

The class byte chooses which band of the battle-action state machine runs the cast:

+0ClassWhat happens
0x32Player summonSummon band; pages a per-summon overlay (PROT 903..) that spawns the Ra-Seru body and applies damage
0x14Plain castOrdinary magic band with the caster's animation (Tail Fire, Astral Wave, …)
0x63Capture-classPages the per-spell cast module PROT 935 + sub and starts its voice cue. Covers Seru capture, the Amulet, and the boss cinematic casts; the module carries its own baked damage constants

Because the sub-index is static table data, the identity of every capture-class module is readable from the executable alone (spell_names::capture_class_records). The 32 modules (PROT 935..966) are shared: a multi-spell module dispatches per spell id at its head.

Capture-class module index (PROT 935..966)
PROTSpellsPROTSpells
9350x4A Earthquake9510x36 Chaos Flare; 0x5B Scythe Wind
9360x4B Hyper Crush9520x5C Bloody Horns; 0xB8 Astral Slash
9370x4C Hyper Lightning9530x5D Terio Punch; 0x5E Bull Charge
9380x4E Chaos Breath; 0xB7 Mystic Circle9540x5F Fatal Decision
9390x4F Spore Gas9550x60 White Shield; 0x6E Kiss of Death; 0x6F Melt Spray; 0x70 Terror Scream; 0x72 Power Charge; 0x73 Void Accessories
9400x3C Glare; 0x50 Divide; 0xAC Mystic Shield; 0xAE Clone9560x71 Water Hazard; 0x75 Paralyzing Wave
9410x51 Steal; 0xB9 Stone Circle9570x76 Death Game; 0x77 Thunder Storm
9420x52 Power Up; 0xAA Dark Typhoon9580x79 Blazing Slash
9430x40 Curse; 0xB5 Lapis Wave9590x7A Megaton Press
9440x37 Guilty Cross; 0x53 Curse All9600x7B Plasma Strike; 0xA6 Neo Star Slash
9450x54 Water Column; 0xBA Jugger Power9610xA1 Dead End Crisis; 0xB4 Final Crisis
9460x55 Call Wave; 0x56 Big Wave9620xA2 Blade Breath; 0xA3 Thunder Needle; 0xA4 Gigaton Press; 0xA5 Ultra Charge
9470x57 V-Windhash; 0xA7 Neo Windhash9630xB3 Genocidal Cannon
9480x58 Cross Beam9640xAF Element Change; 0xB0 Rogue Wind; 0xB1 Rogue Thunder; 0xB2 Rogue Flame
9490x59 Water Crystals9650xB6 Doomsday
9500x5A Rolling Flare; 0xAB Shadow Break9660xAD Evil Seru Magic

The sub-id space covers 935..=966 with no orphan slots. The status-only modules (940, 954, 955) are exactly the cells with no damage spells. An older attribution of 965 as a sibling of the battle-tutorial overlay was an entry-size over-read (do-not-re-walk).

How an enemy's cast lands in the same id space

An enemy is named exactly like a party caster. Two selection mechanisms both end in the same byte on the live actor, and the banner reads the row that byte names:

1
Record array
The AI picker reads a global spell id from the monster record's three-slot magic-attack array.
monster_archive
2
Special-cast switch
The picker's tail runs a hardcoded switch on the monster's formation id - about 30 boss and species cases gated on HP fraction, MP, round counter, RNG cadence and a not-charmed check.
engine-vm
3
Queue
The chosen id is written to the actor's action byte.
battle-action
4
Banner
The action SM reads this table at that id - the same row a party cast would use.
spell_names

The monster archive's own local action ids (0x0C..=0x1F) only gate action cost - they are not this table's id space. Two spells, Curse 0x40 and Curse All 0x53, are sourced by neither mechanism and are casterless in retail.

Notable special-cast switch cases
Formation idCasts queuedTrigger
0x4B ZetoCall Wave 0x55 → Big Wave 0x56 via a chain cell40% + MP ≥ 100
0x8B XainBull Charge 0x5E → Terio Punch 0x5D; Bloody Horns 0x5C from the record arrayrand % 3
0xA2..0xA4 Gi / Che / Lu Delilasspell = formation id − 0x29 (Blazing Slash / Megaton Press / Plasma Strike)every third round
0xA6 Sim-Seru GazaNeo Star Slash 0xA6odd rounds, MP ≥ 200
0xA8 RogueElement Change 0xAF, then Rogue Wind / Thunder / Flameelement-cycling counter
0xB3 Songi (Seru-Kai)Genocidal Cannon 0xB3two-turn charge
0xB4..0xB6 Cort formsEvil Seru Magic / Mystic Circle / Mystic Shield; Ultra Charge → Final Crisis, Doomsday; the Dead End Crisis ladderround-scripted
species bandsSteal, Power Up, White Shield, Rolling Flare, Power Charge, Void Accessories, Paralyzing Wave, Death Game, Thunder Storm, Stone Circle, Chaos Breath, Jugger Power, Lapis Wave, …HP-fraction / cadence gates

The three Delilas jump-table slots share one arm that computes the spell id arithmetically from the formation id; the whole mapping is the single instruction word 0x2442FFD7 (addiu v0,v0,-0x29) at PROT 0898 file offset 0x1CFFC. Two id spaces collide on 0xB5 (spell Lapis Wave, formation Cort); the branch at 0x801E6D04 is a formation test, not a spell test - do-not-re-walk.

Descriptions and names

The pause menu's spell info window reads the +4 byte and indexes a flat pointer array at 0x80075DB0; index 0 means no description. Player-block strings are a title line, a line break, then an effect line. Parser SpellEntry::desc.

Each name string is followed by NUL bytes up to the next word boundary - that padding is the whole edit budget for a rename, and it is word alignment, not a fixed slot (a 13-byte name has three spare bytes, a 15-byte name one). Names nest - a text search for one finds the longer name that contains it - so the only safe way to rewrite one is through its record's pointer (spell_names::name_field).

Reading past the extent

Both neighbouring tables start on exact multiples of the 12-byte stride, so an over-read lands on whole words:

IdsWhat the walk lands in
0xBE..=0xD4the description-pointer table
0xD5..the arts-name table (20-byte stride, name pointer at +0xC)

Whenever the arts index is 1 mod 3, the arts name pointer and a phantom spell record's name pointer are the same four bytes (spell 0xD7 = arts record 1, Burning Flare). A rename through either coordinate is one write. asset spell-names sweeps 256 ids for inspection, so the aliased rows show above 0xD4; the patcher stops at 190.

History: "+4 is an animation id"

The +4 byte was once recorded as an animation id. The summon animation is dispatched by spell_id - 0x81, and the menu overlay's info window pins +4 as the description index.

Where the damage comes from

The table carries no power field. The cast state reads only MP, the class byte and the name pointer; the static attack-vs-defence kernel is melee / arts only. Magnitude is computed at cast time by whichever overlay runs the spell:

  • Damage summons (PROT 0904 / 0912 / 0914 and one arm of 0915) roll rand % (summon AGL + 1) + summon HP + caster AGL × 2 against a defender roll, then apply element affinity, status weakening and the 9999 cap - the kernels on the battle-formulas page, ported in engine-vm::battle_formulas.
  • Heal summons (PROT 0903 / 0905 / 0910 / 0911 / 0913) compute (power_byte << 5) + 0xE0, with the power byte read from the caster's character record.
  • Capture-class casts carry baked constants in their cast module.
  • The per-move power scalar that does exist - the move-power table - feeds the arts / physical branch and shares this id space (0x25..=0x74 are the named monster attacks).

The base_power figures in engine-core::retail_magic are MP-scaled placeholders until the summon roll runs in a live battle context.

History: the summon jump-table hypothesis

"The magnitude is in the summon jump table" is falsified: the table at 0x801F69D8 resolves into the resident render overlay - staggered entry points into one per-frame routine that blends animation banks and emits GPU packets, with no multiply and no HP write. The 0xC5-entry substitution table at DAT_80075EC4, once mistaken for a spell-name source, is the Tactical Arts name table. See do-not-re-walk.

How we know

FunctionAddressWhat it provesDump
Battle-action SM, cast states 0x28 / 0x3CFUN_801E295C (PROT 0898)MP at +3, class at +0, name via DAT_800754D0 + id*0xC; no power readoverlay_battle_action_801e295c.txt
Enemy AI pickerFUN_801E9FD4 (PROT 0898)Record array + formation switch both write the actor action byte; Delilas arm at 0x801EB7C0overlay_battle_action_801e9fd4.txt
Menu spell info windowFUN_801D2E74 (PROT 0899)+4 indexes the description pointers at 0x80075DB0overlay_menu_801d2e74.txt
Melee kernelFUN_801EC3E4Returns early unless the queued action is a physical id - magic never reaches itoverlay_battle_action_801ec3e4.txt
Summon damage kernelsFUN_801DD0AC / FUN_801DD864 / FUN_801DDB30Summon magnitude is battle-state-derivedoverlay_battle_action_801dd0ac.txt + siblings
Overlay pagerFUN_8003EC70Capture-class sub + 0x28 and summon id - 0x79 resolve to PROT 935.. / 903..8003ec70.txt
Disc-gated testsspell_catalog_disc, magic_vs_discAll 11 pinned targets + MP byte-exact; curated chart joins on MP for 21 Seru + 7 Ra-Serucrates/engine-core, crates/gamedata

Source of record: docs/formats/spell-table.md.

See also