Cast modules: summons and capture-class casts
Every summon and every capture-class cast - Seru capture, the capture item, the boss cinematic specials - runs its choreography from its own small overlay program, a cast module. There are 64, one per special. The battle overlay pages one into a side window of RAM and re-enters it every frame until it reports done. A module owns the cast's phase machine, its camera, its particle spawns, its voice cue and its damage constants, so anything that edits or ports a special attack ends up here.
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_801F1ED4through table0x801CF4EC, rowid - 0x81- Capture dispatch
FUN_801F2160through table0x801CF56C, row = the spell record's sub-id- Stager table
0x801F6734, 64 words, called by move-VM opcode0x20- 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 pooloverlay-images::cast_effect_pool
| PROT | Class | Reached by | What it is |
|---|---|---|---|
0903..=0913 | player Seru magic | action ids 0x81..=0x8B | the eleven base Seru, Gimard to Nova |
0914..=0934 | evolved summons and item casts | ids 0x8C..=0xA0 | the high summons; one entry is a null stub with no tick arm |
0935..=0966 | capture class | spell sub-id 0x00..=0x1F | monster 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.
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
| Region | Contents |
|---|---|
| head | the jump table of one switch, 0 to 256 words; the first function's prologue is the first word past it |
| tick | the phase machine |
| stager | the spawn switch |
| tail | data: 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.
| Phase | What the arm does | Exits |
|---|---|---|
0 | first tick: typically the opening camera cut | advances to 1 |
1..=N | stage clips, spawn records, move the camera, roll damage, write stats | holds while its gate is closed; then +1, or a literal store that skips arms |
| in-range terminal arm | spends the countdown, tests the settle state, clears busy; stores no phase | tick returns 0 |
| past the bound | the default arm: nothing | returns busy forever |
0xFF | the Done latch of chain-dispatched bodies | tick 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 at0x801C9370- Victim
actor_table[caster+0x1DD]- Staging
- Store a clip id to
+0x1DAand bump+0x1DC; victim reactions come from the victim's own reaction map - Counts
ctx[+0]is the party count andctx[+1]the monster count - two bounds over disjoint halves of the table- Seats
- Party
0..2, monsters3..7whatever the party size; the summoned creature takes seat7 - 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 shape | Cap | Compare | Can kill | Negative roll |
|---|---|---|---|---|
| A (most sites) | live HP | unsigned | yes | reads as huge, rewritten to the whole bar: kills |
| B (two stagers) | HP - 1 | signed | no, leaves 1 HP | passes unclamped: the subtract raises HP |
| C (seven tick sites) | HP - 1 | unsigned | no | rewritten 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.
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.