At a glance

Module band
PROT entries 0903..=0966, 64 images
Load base
Slot B, 0x801F69D8 - shared by all 64, so an address needs its owning entry to mean anything
Pager
FUN_8003EC70(arg); TOC index = arg + 0x381
Summon dispatch
FUN_801F1ED4 through table 0x801CF4EC, row id - 0x81
Capture dispatch
FUN_801F2160 through table 0x801CF56C, row = the spell record's sub-id
Stager table
0x801F6734, 64 words, called by move-VM opcode 0x20
Phase byte
ctx+0x279, a module-local space under the battle state
Port
engine-cast (cast_module_ticks, cast_arm_ticks, seru_magic_ticks, cast_module_camera, cast_fatal_decision); engine-battle-vm (battle_cast_dispatch, battle_cast_cue); spawn pool overlay-images::cast_effect_pool
PROTClassReached byWhat it is
0903..=0913player Seru magicaction ids 0x81..=0x8Bthe eleven base Seru, Gimard to Nova
0914..=0934evolved summons and item castsids 0x8C..=0xA0the high summons; one entry is a null stub with no tick arm
0935..=0966capture classspell sub-id 0x00..=0x1Fmonster specials and boss cinematics

How an action reaches a module

A module is a library, not a program: nothing inside it names its own entry points. Each has two callable routines, a tick and a stager, and three tables in the battle overlay name them - fixed at link time.

Queued action idcaster +0x1DF Summon: row = id - 0x81PROT 903 + row Capture class: row = sub-idPROT 935 + row Pager loads the module into slot B0x801F69D8 Stagertable row → move-VM opcode 0x20 Tick, every framephase machine on ctx+0x279 returns non-zero: busy returns 0: the band exits no timer, no bail-outa gate that never opens parks
The tick is re-entered every frame and the battle advances only when it returns zero. A capture-class row may point at a trampoline inside the module that picks a body by action id; an id the trampoline does not name returns zero and nothing ticks.

While a module is resident, the word at the slot-B base is the module's own first word - a third meaning for an address the corpus maps twice already. It is not an entry address: it is arm 0 of the head jump table where the image has one, and the first instruction where it does not.

Image anatomy

RegionContents
headthe jump table of one switch, 0 to 256 words; the first function's prologue is the first word past it
tickthe phase machine
stagerthe spawn switch
taildata: the spawn and emitter records, plus the module's scratch words

Which switch owns the head table varies - read the bound. In some images the head table is the tick's, in others the stager's, and many head with code and no table. A function in these images is recovered by frame matching: it starts where the stack is lowered and ends at the first return whose delay slot restores the same amount. That recovers 196 framed functions across the band, between one and eight per image. Most images call only the executable and the resident battle overlay, so dumps have to be driven from an address list rather than a call graph.

Some images carry an inherited tail: bytes of the previous, larger module left behind in the window. A call site found there belongs to the neighbour, not to the image being read. The byte-level layout is on the slot-B module layout page.

The module phase machine

A tick body is a dispatcher over its own phase byte. The tick seeds its result with “busy” and returns it, so busy is the default answer; only a terminal arm clears it.

PhaseWhat the arm doesExits
0first tick: typically the opening camera cutadvances to 1
1..=Nstage clips, spawn records, move the camera, roll damage, write statsholds while its gate is closed; then +1, or a literal store that skips arms
in-range terminal armspends the countdown, tests the settle state, clears busy; stores no phasetick returns 0
past the boundthe default arm: nothingreturns busy forever
0xFFthe Done latch of chain-dispatched bodiestick returns 0 after exactly one tick
  • Out of range is busy, not done. A port that advances through a terminal arm walks into the parked state.
  • Two store forms write the phase. Many ticks materialise a pointer to the byte in their prologue and write through that register; a store census has to count both forms.
  • An arm holds on a clip state, a progress halfword, the shared settle loop (no live actor playing anything but the settle clip), or a module countdown seeded from the speed scalar and drained by the frame delta.

Tick ABI

Caster
actor_table[ctx+0x13], with the table at 0x801C9370
Victim
actor_table[caster+0x1DD]
Staging
Store a clip id to +0x1DA and bump +0x1DC; victim reactions come from the victim's own reaction map
Counts
ctx[+0] is the party count and ctx[+1] the monster count - two bounds over disjoint halves of the table
Seats
Party 0..2, monsters 3..7 whatever the party size; the summoned creature takes seat 7
Return
Non-zero = busy, 0 = done

The seat-0 hardcode. In the boss-cinematic exemplars every damage apply site loads actor_table[0] instead of the derived victim. Retail never notices, because a boss cinematic's victim is the party's first seat. Any reuse that points the cast at a monster inherits friendly fire from those sites - the main thing a player-caster port of a module has to change.

Damage

Each hit is one call into a roll wrapper with a power baked into the module image, then an apply: clamp the roll against the victim's HP, accumulate into the damage-popup word, write HP back. A cast never reads the move-power table for its magnitude. The capture-class wrappers differ in the stat they read and in the resist ladder: FUN_801DD4B0 reads the INT word (+0x168) and respects the party's elemental resists, FUN_801DD6B4 reads the ATK word (+0x158) and bypasses them (see battle formulas).

Clamp shapeCapCompareCan killNegative roll
A (most sites)live HPunsignedyesreads as huge, rewritten to the whole bar: kills
B (two stagers)HP - 1signedno, leaves 1 HPpasses unclamped: the subtract raises HP
C (seven tick sites)HP - 1unsignednorewritten to HP - 1

These modules write HP with the paired accumulate convention, so they keep the invariant the HP-readout gate depends on.

The module owns the camera

Neither band's tick state calls a framing case of the action state machine. The camera is the module's, moved with two kernels: a shot - pitch / yaw / roll, translation and a negated focus handed to the same tween builder the battle camera uses - and drifts, values added straight into the pose globals on every pass an arm runs. A module with no camera arms leaves the held pose alone.

What is data and what is code

A signature cast is half data. Its particle layer is a set of records in the module's tail - the summon part-record shape, a model selector followed by move-VM bytecode - each handed to the pool spawner from a hardcoded call site with a constant pointer and a scale literal. Its lift, timing and camera are module instructions. Editing the look of a cast can therefore be a data edit; changing who it hits or when is a code edit.

The colour wash behind a cast

Both cast dispatchers end by calling the battle overlay’s colour wash FUN_801F2410 whenever the module has armed its alpha (ctx[+0x27A]), so the wash draws on every frame the module ticks. It is a backdrop: retail links it into the farthest ordering-table bucket, so the whole scene, blended parts included, draws over it. A mode byte picks one of two looks.

mode 0: bands two opaque POLY_G4 per band, A to B and back 0909, 0918, 0922, 0930, 0931, 0946, 0950 modes 1 and 2: scanlines one-line POLY_FT4 strips, sine-offset per line 0917, 0923, 0928, 0929 (Mule), 0963, 0965 both: alpha-scaled, opaque, drawn behind the whole scene
Band mode shades colour bands; the scanline modes resample texture page 0xA8 (VRAM (512, 0)) through CLUT (0, 488), one 256-texel row per screen line wrapped across the 320-pixel line. Mode 2 mirrors the second strip.

Both modes are ported whole in render-kernels::cast_colour_wash (scanline_prims for the textured one). Both hosts composite screen primitives after their 3D pass, so they draw the wash as a far-plane primitive ahead of the scene instead. The scanline mode is Mule’s underwater backdrop: before it was ported the creature’s whole performance played over black. Two per-line offset arrays the emitter also adds have no writer anywhere on the disc, so the port reads them as zero.

What the port runs

Both hosts run the band through one seam. The spawn pool indexes all 64 entries and parses each image's spawn records off the user's disc. The tick bodies are ported one function per address: dispatch bound, simulation-state writes, the damage step (baked power, wrapper, clamp shape, accumulate, HP write, reaction stage) and the phase advance. Camera directors exist per module; for the summon creatures whose tick bodies are not ported (Gola Gola through Ozma), the director carries the module's whole phase chain - every countdown gate, shot, camera drift, caption and the creature seat - so the band lasts as long as retail's and the outcome lands on the module's hit arm. Aluru's light bands, Slippery's rain, Swordie's split and Cross Beam's beam are drawn. Left out, per function: the other GPU-packet arms, part spawns and the writes to the other seats in the summon-creature modules, and the camera arms and per-arm frame gating of modules with no director.

Most capture-class casts have their whole record set seated at the caster when the band pages the module in. Seven bodies seat theirs on the arm passes that make the calls instead - Evil Seru Magic, Mystic Circle, Ultra Charge, Mystic Shield, Guilty Cross, Call Wave / Big Wave and Final Crisis - and five of them also run the stager their own parts call back into, so Mystic Circle's rays and Ultra Charge's rings appear on retail's clock rather than all at once.

Mule's monster-row visibility rides the same per-arm writes: one arm shows every living monster on each of its passes, two hide the row, a later one shows it again, and the hit arm shows the monsters it strikes. Its early arms also move the camera anchor under the opening cut, so the director re-lands that cut on the moved anchor each pass - without it the row filled the screen where retail frames it small in the distance.

Two pieces of retail timing and colour sit outside the tick bodies. A summon's opening arms can wait on the side-band token, the applier stage ctx[+0x276] that stays up while the cast's slots of summon.dat stream off the disc, about 30 vsyncs a slot. The port's side-band is already in memory, so it keeps the token up for that CD time alone, and Mule's and Horn's opening waits last as long as retail's. And a part can recolour VRAM: Mule's underwater backdrop cycles its colours because one of its parts is a CLUT-cell cycler that captures the backdrop's palette row and swings its hue every frame. Battle parts run that recolour the way field ambient parts do. A module also keeps the handles of the parts and fades it spawns in arrays of its own and later kills them, sometimes a whole array in one loop; the port follows those kills, which is what clears Gilium’s screen after its white-out.

Cort's Mystic Shield is the one module effect retail keeps alive across actions: two records out of the game executable itself, a camera-facing textured sphere and the scroller that animates its texture, seated on Cort by the shield's arm 4 and posed every frame by the actor pass until he is worn to half HP, when a burst replaces it. The port seats, poses and breaks it the same way, in a scene of its own that outlives the cast.

See also

The full write-up - the three tables row by row, the capture trampolines, inherited tails, the routine catalogue, per-module notes, the cast voice cues and measured arm timing - is in docs/subsystems/cast-module.md.