At a glance

Form
[4C, op0, operands…]; the high nibble of op0 selects the dispatcher
Outer table
Sixteen entries at 0x801CEE60, inside the field VM FUN_801DE840 (field overlay, PROT 0897)
Inner tables
Most arms index a second table on the low nibble; every sub-op has its own byte width
Disassembler
legaia_scene_formats::field_disasm carries the widths for all sixteen nibbles
Confidence
Confirmed - arm addresses and widths read from the disassembly; shipped usage checked by the disc-wide opcode census

The sixteen nibbles

4C op0 … → op0 >> 4 0: Party leaderleader change 1: Globals + tintclear colour, actor clone 2: Pad rotationcamera octant 3: Ambient gatecamera-zone arms 4: Write or rampctx slots, globals 5: Five sub-opsmodel, move, TAKE_ITEM 6: VRAM movesCLUT-cell write 7: Wall paintcollision grid rects 8: Multi-purposeheal, tint, shake, face-turn 9: Floor ladderheight rungs, oscillators A: Flag skipskip on a flag bit B: Error printerno valid sub-op C: Small writescamera focus, counters D: Party + spawntimed flags, STP bits E: Scene miscballoon, FMV, coins F: Error printeronly 4C FF passes
Highlighted nibbles are the ones a port or a scene editor meets first. Nibbles B and F are not handlers at all.

Three exits recur across the arms:

  • Advance - the arm adds its width to the script cursor and returns. The add is often in a branch delay slot, which the decompiled C hides.
  • Halt at PC - the arm returns the cursor unchanged, so the op re-runs next frame. This is how a sub-op waits.
  • Halt-acquire - the idiom for taking over another actor: the arm writes the target's payload pointer, sets its halt bit 0x400, then advances the caller; if the target is already held, the caller halts at PC instead.

Nibble 3: the ambient gate and the camera-zone arms

4C 30 and 4C 31 raise and clear one word, _DAT_8007B854. It is the ambient-particle master gate, not an input lock. Its six references disc-wide are two clears in the executable, the field render pass that stages the particle table, the ambient particle emitter, and these two stores - none of them is pad state. A field script decides, per scene, whether ambience emits at all.

Four arms are the script's only grip on the field camera's parameter block:

OpWhat it does
4C 38camera-zone query and load at the player's tile
4C 39the same query, a floor-height sample, then falls into the snap below
4C 3Athe player's heading takes the arrival facing a door set; every scene's entry script issues it once
4C 3Dwalk-region attribute refresh at the player's tile - not a camera load
4C 3Ecamera snap and focus clamp; its table entry points inside 4C 39's arm

Nibble 7: wall paint

[4C, 0x7s, col0, row0, col1, row1 (, mask)] edits the walkability grid the locomotion collision check reads - one byte per 128-unit tile, whose high nibble is four sub-cell wall bits.

SubEffectWidth
0clear walls (&= 0x0F)6
1block all (|= 0xF0)6
2clear the mask's wall bits7
3set the mask's wall bits7

The row bounds carry an extra +1 the column bounds do not. The painted rectangle is columns [col0, col1 + 1) and rows [row0 + 1, row1 + 2). These edits layer over the disc-streamed base grid and are commonly gated behind story-flag tests: story-conditional terrain, such as a gate that opens after a boss.

Nibble 8: the multi-purpose dispatcher

OpWhat it does
4C 80child-actor allocator
4C 81actor draw tint, with a blend target and a tick count
4C 82 slotfull HP / MP restore of one party slot
4C 83rectangular tile fill
4C 84screen-shake amplitude
4C 85 / 8E / 8Fhalt-acquire on a target, which turns it to face someone
4C 86 / 87install / retire the reflection controller
4C 89the dialog pager's automatic press
4C 8B / 8Djumps on an actor search

There is no inn opcode. 4C 82 takes a literal slot, not “every active member”, and it is the primitive every inn, rest and infirmary script is built on; the charge is a separate gold gate and debit, so the price is per-scene script data.

The halt-acquire is not a freeze. The bytes it hands the target are a face-target leg of the walk kernel: the target turns toward a named actor over a frame budget, and the leg's last frame releases it. Cutscene records issue over a thousand of these - “one character turns to another” is exactly this op. Aimed at the player, the arm also holds the caller, so a cutscene waits on the player's turn.

Nibble 9: the floor-height ladder, not a fade

The scene keeps a 16-entry ladder of floor elevations: the one ground height is interpolated from, and the one added to every placed object's height. Nibble 9 writes it.

  • 4C 9E installs all sixteen rungs, and also the copy the follow camera samples - so an installed ladder moves the camera.
  • 4C 90 sets one rung oscillating (live rungs only, so the camera does not follow).
  • 4C 9F retires every oscillator.

The organic Seru interior's undulating floor is the clean example: its entry script installs a linear ramp, then issues 4C 90 over consecutive rungs with a rising phase - a travelling wave across the ladder. Two of the three oscillator sub-ops are dead code: 4C 91 and 4C 92 land on phases whose loop never terminates, and no shipped scene uses them.

What a moving ladder moves

Three readers see the live rungs, and placed objects are not among them:

  • The ground pass takes each ground cell’s four corner tiers through the ladder every frame, so the ground deforms per vertex - the pulsing path and the flesh floors.
  • The decoration sweep places each terrain or decoration cell at its floor term once a frame, so a whole mesh rides up and down. It skips placed-object records.
  • The floor sampler, so the player walks the same shape and rides it while standing.

A placed object’s Y is written once, when it is spawned (at scene init or by the window sweep’s re-plan), as its rung plus its own offset; its actor template sets neither height-arm bit that would make the per-frame driver re-sample the floor. When a scene’s system script lifts rungs after spawn, the objects on those rungs stay where they were placed.

Nibbles 4 and 5: ramps, held heights and the seat

Nibble 4 is one pattern, [4C, 4s, value:u16, ticks:u16]: with ticks zero the slot s is written at once; otherwise a scheduled actor ramps it from its current value over ticks frames and the script never waits on it. The slots cover the actor’s speed and draw scale, its held height, its position halves, its drawn heading, the floor ladder, three globals and an audio duck (a fraction of the configured level, which the port runs as its BGM duck). Sub 5 is the one 11-byte form.

Held height. Sub 2 writes the actor’s height slot +0x8E. An actor with flag bit 0x20000000 takes Y = -(+0x8E) every frame in the per-actor tick’s height arm instead of the ground sample, and the arm sits ahead of both floor arms, so the held height follows the actor wherever it moves. A placed object is drawn at its actor, and the height is absolute rather than an offset from its map seat - this is how a script lifts or drops scenery (a collapsing dungeon’s boulders tween down to the stairs) and how a walking boss can stay at a fixed altitude while its motion stream walks it. The port runs the arm for its placement NPCs (World::tick_field_npc_heights), and both play hosts draw from it.

Sub 1 of nibble 5: the seat binds its move clip

[4C, 51, x, z, depth, move_id] is a seat, not a walk: no walk kernel is armed. On the actor the op resolves (the executing context, or a cross-context target), the arm writes four things in order:

1 · Party-bank bitdepth bit 7 → +0x10 2 · Anim controldrop hold, clamp, reverse 3 · Seattile centre, compass heading 4 · Clipmove_id binds and loops Playerclip base, bound now Anyone elsere-bind, restart
The clip binds on every path that reaches the op - the install pre-run, a cutscene’s cross-context poke and a live channel alike. A party member is seated with a depth byte in 0x8_, so its clip id resolves through the party locomotion bank; any other depth plays a scene-bank clip.

Two shipped consequences: a monster seated beside its tree on its pre-fight arm loops its tree-eating clip instead of its header clip, and party members seated at a bridge get their walk clips before the compass legs walk them up it. The port writes the bit, the mask and the clip slot on every NPC arm (FieldHostImpl::run_to_tile) and raises the clip cue on the cross-context arm; one difference remains - the port’s cue skips a re-request of the clip already playing, where retail’s restarts it.

Nibbles C and D: camera, counters, timers

OpWhat it does
4C C0seats the actor's clip at a random frame - not a cancel. The festival crowd in town0d issues it for every dancer, so each starts its loop at its own phase rather than in lockstep
4C C1fog-region enable reset
4C C3script-table teleport and spawn-section re-run
4C C4 x zcamera-zone query at an explicit tile: frames a shot from a region the player is not standing in
4C C5 / C6wait until a system flag is set / cleared. The flag is flipped by a move-VM part the record itself just spawned (ext sub-op 0x1C / 0x1D in the scene's stager record), so the wait lasts as long as that part takes to reach it. Those parts tick in the frame tail, not in the scene tick, so a headless driver that skips the tail leaves the wait to the cutscene timeline’s anti-hang cap, and the record’s tail never runs
4C C7 char artteach a Hyper Art: inserts art into roster slot char's learned list - the masters' lessons
4C CA / CB / CCscript counter set / add / subtract
4C CDwait for the camera glide
4C CEplayer clip override: re-points walk, idle and run at other scene-bank records
4C CF x zscript camera-focus override
4C D0 x ypress-a-button prompt: a blinking cursor at (x, y) holds the script until confirm or cancel is pressed (Tetsu's lessons)
4C D3timed-flag scheduler (14 bytes)
4C D4 / D5set / clear the VRAM semi-transparency bit over a rectangle
4C D8synchronous morph-actor spawn (9 bytes)

The timed-flag scheduler 4C D3 expiry:u16 below:u16 duration:u32 threshold:u32 arms a countdown that lives in the save block. A per-tick consumer sets the expiry flag at zero and the below flag while under the threshold. Retail uses it for the two escape sequences: a collapsing dungeon and the Floating Castle. Stopping the clock does not clear the below flag, which is the root of a traced missing-encounters oddity.

4C D2, D6 and D7 all advance in retail. A VM that halts on them holds a cross-context record forever.

The rest, briefly

  • Nibble 1 - global write, screen tint, the field clear colour, and an actor clone.
  • Nibble 2 - one arm: the camera-octant setter that rotates what the D-pad means.
  • Nibble 5 - besides the seat above: model select (which re-stages the actor), TAKE_ITEM (sub-2 removes an item from the bag; it is not a menu poll), and two dialog polls.
  • Nibble 6 - VRAM image moves and a CLUT-cell write.
  • Nibble A - conditional skip on a flag bit: a set bit steps over the bytes its halfword names, counted from the end of the instruction (the shared exit LAB_801E360C adds the halfword less two), not an absolute target. 4C DC / 4C DE skip the same way when the party holds or wears an item, and so do 4C 8B..8D (spell known, a context word zero, art learned), 4C D1 (a camera glide running) and 4C EB (an actor missing).
  • Nibble E - text balloon, movie trigger, a position-copy teleport, a box-test branch, camera animate and zoom, and the casino coin delta. 4C E0 formats the reading box: a row capacity, or a latch that loads the next page whole (the help pages use twelve-row boxes loaded at once).
  • Nibbles B and F - the error printer. They form a message string and call the message routine, returning the PC unchanged. Only 4C FF passes through. The disc-wide opcode census finds no coherent use of either in any scene.

In the port, and how it is known

The dispatcher lives in legaia_engine_vm::field (step/menu_ctrl/), with one FieldHost hook per sub-cluster implemented over the world by FieldHostImpl. Feature homes include the reflection controller, the actor clone, the morph-weight apply, the casino coin bank, the escape timer, the step-counter reroll and the camera-zone requests.

Every arm address and width on this page comes from the disassembly of the field VM, not the decompiled C - which rendered several arms wrongly: absolute jumps that are relative skips, halts that advance from a branch delay slot, and TAKE_ITEM as a menu poll. Disc usage comes from the field-op census, taken at decoded instruction boundaries. Live captures confirm the reflection controllers and the clear colour.

See also

Every sub-op's body, arm address and width is in docs/subsystems/script-vm-menuctrl.md.