Skip to content

The expression command

Opcode 0x14 is by far the most-used command in the game: 46,347 of the 386,348 command records in the loose event corpus, about 12%.

It is a general read-modify-write across most mutable script state. About 90% of uses are a plain copy.

Command_14 (0x002E9FA0) reads a control word, evaluates two operand words into two temporary integers, applies an operation selected by the low three bits of the control word, and optionally stores the result.

control word
operand word 0 ; left-hand side / destination
operand word 1 ; right-hand side / value

The low three bits of the control word select the operation confirmed:

Low bits Operation
0 copy — take the right-hand value
1 add
2 subtract
3 multiply
4 divide — divide by zero produces 0
5 modulo — divide by zero produces 0
6 bitwise AND
7 bitwise OR

Divide and modulo do not fault on a zero divisor; they yield 0. Any decoder or emulator has to reproduce that, because scripts rely on it.

3. Control bits 8–11 are a source domain, not a destination

Section titled “3. Control bits 8–11 are a source domain, not a destination”

An earlier reading of this command treated bits 8–11 as “where the result is stored”. That is wrong. Grouping every use in the corpus by that field and looking at the shape of the right-hand operand settles it — the field selects which domain the operand is read from, and the value shapes line up with that reading rather than with a destination.

The destination is chosen by the type nibble on operand word 0.

Each operand carries a type nibble. Read-side sources proven by trace:

  • config/event flags, through CRadiApp::GetConfigEventFlag
  • script event values, through CRadiScript::GetEventValueForScript
  • character-property dispatch tables, after GetAbstractionCharacterNumber
  • config-property dispatch tables
  • item-property dispatch tables, after GetAbstractionItemNumber
  • stand-position vectors, after GetAbstractionStandPosNumber
  • literal immediate values

Write-side destinations proven by trace:

  • config/event flags, through CRadiApp::SetConfigEventFlag
  • script event values, through CRadiScript::SetEventValueForScript
  • character-property dispatch tables
  • config-property dispatch tables
  • item-property dispatch tables
  • SCR_DATA float arrays at +0x60 and +0x70, for the special ids 0x3F3 and 0x3F5

These come from direct trace of Command_14 confirmed.

5.1 Type 0 — config/event flag, both sides

Section titled “5.1 Type 0 — config/event flag, both sides”

Read at 0x002EA0A8, write at 0x002EA3D8.

bits 0-15 first flag id
bits 16-23 extra bit count
bits 24-31 ignored by the engine

count = ((word >> 16) & 0xFF) + 1 consecutive flags hold a multi-bit integer, LSB first. Reads assemble the bits with GetConfigEventFlag(flag + i); writes scatter them with SetConfigEventFlag(flag + i, bit).

This is how a script stores a small number in a run of boolean flags.

5.2 Type 1 — script event value, write side

Section titled “5.2 Type 1 — script event value, write side”

At 0x002EA430: only the low 16 bits of the operand word reach SetEventValueForScript. High bits are ignored.

5.3 Type 5 read — stand-position component

Section titled “5.3 Type 5 read — stand-position component”

At 0x002EA288. The low 16 bits resolve through GetAbstractionStandPosNumber; byte 3 indexes the returned position vector (0/1/2 = x/y/z), and the float component is truncated to an integer.

5.4 Type 5 write — script-local float slots

Section titled “5.4 Type 5 write — script-local float slots”

At 0x002EA590. The low 16 bits select the slot id, and byte 3 is the component index:

Slot id Effect
0x3F3 Stores the integer result as a float at SCR_DATA + 0x60 + component*4
0x3F5 Converts the result from degrees to radians (* pi / 180) and stores at SCR_DATA + 0x70 + component*4
anything else Stores nothing

The 0x3F5 conversion is the only place in the format where a stored value is transformed on the way in rather than being written verbatim.

The __ptmf_scall in Command_14 dispatches through per-domain property tables, recovered by emulating their static initialisers confirmed:

Table Address Initialiser Entries Symbols
CRadiApp::pfnParamTbl 0x003B0E30 __sinit_r_baseconfig.cpp 0x003A5450+0x284 14 SystemParaCode_000..013
CCharacterManager::pfnParamTbl 0x003B0FE0 __sinit_r_charamanager2.cpp 0x003A56E0+0x1104 75 CharaParaCode_000..074
CItemManager::pfnParamTbl 0x0035BC80 — — not yet recovered

Character entries 4, 5 and 73 all point at the same stub (0x002A0FD0), so those property codes are unused — a useful validity check on any decoded operand.

See also property 0x1D, which scripts use as general-purpose per-character storage the engine never reads.

Combinations outside the layouts above are preserved verbatim as a control word plus two operand words. They re-encode byte-exactly; what they mean is simply not established yet.