Location - static overlay data

The table is loaded with the battle-action overlay image (PROT entry 0898), not built per battle: the 0x801F4F5C..0x801F69D8 window is byte-identical across two unrelated battle save states, and the raw PROT 0898 bytes byte-match the in-RAM table.

ThingValue
PROT entry0898 (battle-action overlay)
Table runtime VA0x801F4F5C
Table file offset (in entry)0x26744
Record stride26 bytes (the 26 * index math in FUN_801dd0ac)
id→index map VA0x801F4E63 (0x80 bytes before the table)
id→index map length0x80 (move ids 0x00..=0x7F)

Indexing - power_table[map[move_id]]

The record index is not the battle move id directly. The setup site reads the actor's move id at actor[+0x1df], looks it up in the 128-byte id→index map, and indexes the table with the result. A map byte of 0x00 or 0xFF means "no record". The move id is the same id space as the SCUS spell-name table (spell table, also indexed by actor[+0x1df]), so joining the two labels every record:

  • records 0x10..=0x2b (move ids 0x25..=0x74) are the named monster special attacks (Fire Breath 0x25, Tail Fire 0x27, …) - their special-attack power, separate from the name the spell table carries.
  • records 0x01..=0x0f (move ids 0x04..=0x1f) are the spell table's unnamed internal enemy-attack tiers.

Record 0 is an all-zero unused slot.

Special-attack-only. The map covers 44 special-attack ids (internal tiers 0x04..=0x07/0x12..=0x1F + named monster attacks 0x25..=0x74); the basic-attack / Tactical-Art bands 0x08..=0x11 and 0x16..=0x18 are unmapped. From a live capture, a party member's queued Art (Vahn's Somersault) carries move id 0x0F and an enemy basic attack (Gobu Gobu) 0x09 - both resolve to record 0 (zero power). So a party member's Tactical Art takes its power from the art-record power byte (art data), enemy special attacks roll through this table, and basic attacks use the generic physical path.

Cross-checked against the enemy table. Of the 29 mapped named ids (≥0x25), 28 are exactly the special attacks enemies cast (monster record +0x21..=+0x23) - the records line up with the roster. But the table is a subset: across 186 monsters, 46 distinct attack ids appear and only 28 are here; the other 18 (incl. 0xA7/0xB8, beyond the 0x00..=0x7F map) have no record - the magic / elemental casts, whose damage is caster-state-derived, consistent with this being the physical/arts table. 95 of 186 monsters carry no magic attack at all (basic physical only). The one mapped record with no caster (move id 0x2C) is the unused “Freeze Thunder” enemy spell - a dummied-out attack (TCRF: forcing it crashes with an “Opcode 14 UNK” missing-asset error); its power record (37) survived but its effect lists are empty.

Record layout (26 bytes)

Three battle-action functions consume the record. The damage kernels FUN_801dd0ac / 801f3990 read only +0x00 (at full / half / quarter scale). The setup FUN_801dea50 computes the record address once and stashes the pointer at ctx+0x1014; the per-frame tick FUN_801e09f8 dereferences that held pointer ~25× and reads the residual fields off it - the byte offsets it loads are exactly +0x02,+0x06,+0x08,+0x09,+0x0a,+0x0b,+0x0d,+0x0e,+0x12,+0x16, and never +0x0c.

OffTypeFieldMeaningConfidence
+0x00i16powerDamage roll modulus, used >>0/1/2 at full/half/quarter.Confirmed
+0x02i16strike Y offsetSubtracted from the per-arm Y lane when the hit point is seeded from the target.Inferred
+0x04u16move counterWhole-move timing counter (→ ctx+0x6c6), decremented per frame.Confirmed
+0x06u16phase durationPer-arm phase duration (→ ctx + arm*2 + 0x6c6) at the strike / re-arm transitions.Inferred
+0x08u8homing speedScales the per-frame XY step toward the target; 0x40 - x reseeds the approach counter.Inferred
+0x09u8tracking flagWhen set, the live XY is copied into the spawned effect each frame.Confirmed (read)
+0x0au8impact effectEnum 1..5; stored at actor+0x21f, indexes the 5-entry packed-config table at 0x801f53d4 ((v-1)*4) into actor+0x04 (packed u32 words, not pointers); 3/4/5 branch extra status-proc rolls.Confirmed (read)
+0x0bu8trail texpageTrail / afterimage sprite-page id (GP0 word 0x7700 + id).Confirmed
+0x0cu8designer tag'C'/'E'/'G'/0 annotation on the internal-tier records only; no runtime reader.Unknown (no reader)
+0x0du8sound cueHanded to the UI/voice cue dispatcher FUN_8004fcc8.Confirmed
+0x0eu8list mode0xFF broadcasts the trail to all four arms; otherwise the head of a small effect-id list.Confirmed (read)
+0x12[u8;4]on-contact effectsEffect-id list dispatched on the hit branch (0x00/0xFF-terminated; tables 0x801f6324/0x801f6418).Confirmed
+0x16[u8;4]launch effectsEffect-id list dispatched at the initial-strike transition; same dispatch.Confirmed

Worked example (real disc bytes)

Record 3 (move id 0x06, the third internal-tier attack), from PROT 0898:

power 1500  ctr 0  phase 480  homing 0x20  yoff 250  impact 1  trail 0
sfx 0x4d  list 0x00  tag C  contact[0x27,0x8e,0x8d]  launch[0x28,0x64,0x9d]

A homing physical strike: it approaches at speed 0x20, runs its strike phase for 480 frames, plays impact effect 1 + cue 0x4d, spawns one effect list on launch and a different one on contact, and carries the unused designer tag C.

Effect-id lists (+0x12 / +0x16)

Both lists are up to 4 ids walked until 0x00, dispatched identically by FUN_801e09f8 (the only difference is when they fire - on contact vs at launch). Each list entry indexes the same two tables:

  • 0x01..=0x63 - spawn effect prototype 0x801f6324[id] + play SFX 0x801f6418[id] (when non-zero)
  • 0x64 (100) - fixed screen-flash
  • 0x80-bit set, not 0xFF - FUN_801dfdf0(id & 0x7F)
  • 0xFF / unused 0x65..=0x7F - no effect

0x801f6324 (file 0x27B0C) is a pointer table: each u32 is an overlay VA to a variable-length move-VM scene-graph record (ids 0x27/0x280x801F5BBC/0x801F5BDC). 0x801f6418 (file 0x27C00) is the per-effect SFX byte. Exactly 61 entries (bounded by the SFX table that follows). Parsed by EffectAuxTables; dispatch by EffectListEntry::classify.

For a 0x01..=0x63 entry the dispatch calls FUN_80050ed4(world_pos, src_pos, 0x801f6324[id], 0x1000), which forwards to the shared spawn stager FUN_80021B04: it reads the record's +0x00 model_sel (mesh = DAT_8007C018[model_sel + gp[0x754]]; -1/0x4000/0x4001 are transform-node sentinels), stages an actor with the record as its move-VM buffer (+0x48) and PC at +0x04 (+0x70 = 2), and drives it through the move VM (FUN_80023070). So each record is byte-identical to a summon part record (+0x00 i16 model_sel, +0x02 u16 flags, +0x04 move-VM bytecode) and reuses the same machinery as legaia_asset::summon_overlay. The high-bit (0x80) list bytes route instead to the 2D efect.dat pool (FUN_801dfdf0EffectCatalog). The library helper move_power::parse_effect_proto_records decodes the whole table to records (the engine's World::spawn_move_fx uses it), and all 54 unique records execute cleanly through the ported move VM - seeded as the part-stager does (PC = 2) and run under the same wait-gate / per-frame budget, with no unimplemented opcode (disc-gated move_fx_records_vm_exec_disc; ~20 distinct move-VM ops exercised). One of them, 0x801F5484 (entries 0/0x30/0x31), is the record a live enemy "Fire Tail" part-actor seats (firetail_movefx_liveness).

Open

The whole record is decoded and the auxiliary effect tables are parsed (EffectAuxTables); the 0x801f6324 spawn records are decoded too (summon-format move-VM records, above). The engine surfaces every field as a resolved descriptor (MovePowerCatalog::fx_for_move_idMoveFx). The summon branch of FUN_801dd0ac (attacker slot == 7) does not use this table - a summon's magnitude is caster/summon-state-derived (see spell table and the per-spell-power thread in open RE threads). The model_sel additive base gp[0x754] (global 0x8007BA6C) is party_count + 2 in battle (save-corpus-pinned: 0 when no battle effect-model library is resident, 3 for a 1-member party, 5 for the full 3-member party - it tracks party size, so there is no per-summon base), and model_sel is library-relative: a battle move-FX mesh is DAT_8007C018[model_sel + gp[0x754]] - the resident effect-model library, which the engine registers at a fixed DAT_8007C018[3..=32] (the equivalent 1-member layout). The engine renders the move-FX scene-graph: World::spawn_move_fx parses a move's spawn records, stages them as a SummonScene at that fixed library base, and drives them through the ported move VM (tick_move_fx; play-window H cycles through the renderable moves enumerated by MovePowerCatalog::spawnable_move_ids() - the ids whose effect lists hold a Spawn entry with a resolved prototype) - reusing the summon machinery, so it shares the same interpreted-transform caveat (the exact per-part composition is the open FUN_801F811C/PROT-0900 piece). A spawn also surfaces the move's two presentation fields: the trail texpage (+0x0b0x7700 + id) via World::active_move_fx_trail_texpage(), and the sound cue (+0x0d) via World::take_pending_move_fx_cue(), which the host routes through the ported FUN_8004fcc8 dispatch decode (legaia_engine_audio::classify_cue). The effect-list AltEffect entries (high-bit 0x80 bytes, distinct from the 0x801f6324 scene-graph Spawn entries) now spawn through the 2D efect.dat pool (World::try_spawn_effect / spawn_by_ui_id / FUN_801dfdf0) by their 7-bit id. The 2D afterimage streak draw (FUN_801e1ab0) that consumes the trail texpage is ported as legaia_engine_render::afterimage::build_afterimage_quad - it assembles the jittered semi-transparent POLY_FT4 (per-corner rand wobble, random brightness band, UV/CLUT/texpage layout) from four projected screen corners + the trail id. The camera-coupled corner projection (FUN_800195a8) is ported too - legaia_engine_render::billboard::project_billboard (afterimage call shape afterimage::project_streak_corners: +0x120 Y push, dynamic half-width from the fx-state halfword +0x6c60x200, half-height 0x100). Still open: the live note-on wiring of the resolved cue.

CLI

asset move-power <raw PROT 0898 entry>     # dump every record (power, counters, effects, ...)

See also