Effect VM (battle effect cluster)
The runtime that drives battle-spawn effects — spell casts, item-use animations, hit sparks. Implemented as a per-slot state machine rather than a clean bytecode dispatcher: there's no central switch on a per-slot opcode byte, and state transitions are inlined throughout 600+ instructions of the per-frame walker.
How it works
When you cast a fireball in battle, three things happen visually: the spell-cast pose plays on the caster, a sequence of sprites animates between caster and target (the "fireball flying" frames), and a hit explosion plays on the target. Internally that's one effect script doing all three — one entry in efect.dat describing the whole thing as a sequence of frame batches.
The effect VM is what runs that script. It owns a fixed pool of "master slots" (32 of them, one per simultaneous effect) and "child slots" (128 of them, one per active sprite within an effect). Every frame, the per-frame walker iterates the master slots and pushes each one through its state machine: spawn the next batch of children, update positions, decrement timers, retire finished sprites, advance to the next state.
Unlike the field VM or the move VM, this isn't a clean bytecode dispatcher with a switch table. It's a state machine where the "next state" tokens live inside the on-disc effect script, and the walker reads them inline. Producing a clean opcode table from it would mean extracting the per-state transition logic by hand (~10–20 cases). The cleaner port path is to model the walker as a state-machine class and accept its decompile shape rather than insisting on an opcode table.
The on-disc input is the runtime 2-pack wrapper at PROT entry 873 (data\battle\efect.dat): pack0 holds 14 sprite-anim records, pack1 holds 33 effect-ID scripts indexed by the public effect ID.
Where it lives
All three functions are in the battle overlay (0898_xxx_dat):
| Function | Span | Role |
|---|---|---|
0x801DE914 | 0x138 | Init / pack-fixup. Called from FUN_800520F0 case 0xE with (id=0x1000, param=0xA00). |
0x801DFDF8 | 0x290 | Public spawn-effect API: (byte effect_id, short* world_pos, ushort angle). |
0x801E0088 | 0x970 | Per-frame walker (update + render). 600+ instructions of inlined state transitions. |
How it dispatches
Each 28-byte master slot carries:
(state, counter, ?, sub_state, pos_x, pos_y, pos_z, data_ptr)
The walker reads *data_ptr as the next-state token, but state transitions are inlined throughout FUN_801E0088. To produce a clean-room opcode table you'd need to extract per-state-byte transition logic by hand. The 32-master / 128-child slot pool, the spawn API, and the per-frame walker are all well-understood — the port itself is straightforward; the only question is whether to label the format an "opcode table" or a "state machine".
Pool layout
One contiguous 5008-byte block at _DAT_8007BD30:
+0x000 16 bytes table-head record set by init
+0x010 4096 bytes 128 × 32-byte child slots — per-sprite render state
+0x1010 896 bytes 32 × 28-byte master slots — per-effect-instance state
+0x1390 1968 bytes (unused / future expansion)
32 max simultaneous effects × ~4 sprites avg = 128-child sprite pool.
Side-band streaming-effect handler
- Function
0x801F17F8- Called from
FUN_800520F0case0xFF- Buffer size per slot
0x10800= 67584 bytes
Streams two specific runtime-only files via FUN_800558FC:
| File | PROT | Trigger |
|---|---|---|
data\battle\summon.dat | 0x37F | Selected when _DAT_8007BD24[0x26B] & 0x80 != 0 |
data\battle\readef.dat | 0x380 | Opposite branch |
Format unverified; may share the 2-pack layout but not yet confirmed.
Effect-ID → human effect name mapping
Effect IDs are anonymous; no string table maps id → "fireball / thunder / heal". To name effects, trace call sites of FUN_801DFDF8 in damage / battle-action code (in the town/level-up overlays). Each caller passes a literal byte for effect_id; correlate with the action that triggered it (a Tactical Arts move, an item use, a spell cast).