ShadowFlare does not hardcode every conversation, quest, and town event in the main executable. Scenario behavior is split between data and engine code:
Scenario.Mct map, music, actors, entry points, and basic actor settings
Scenario.Scs flags, messages, status triggers, sentences, and commands
RKC_RPG_SCRIPT binary script container and lookup boundary
ShadowFlare.exe interpreter, operand domains, command behavior, and native actions
That boundary matters to the reconstruction. A dialogue line, quest branch, or
actor instruction that already exists in an SCS file should stay in the SCS
file. The portable executable should decode and interpret it, not copy it into
WorldScene as new C++ logic.
This document records what we currently know. It will grow along with the interpreter.
Scenario.Scs is the game's only known general-purpose scenario bytecode, but
it is not the only data that controls behavior. The retail files divide that
work into four layers:
| Layer | Retail data/code | Responsibility |
|---|---|---|
| Scenario script | One Scenario.Scs in each of the 209 scenario directories |
Dialogue, quest branches, flags, messages, rewards, services, spawning, and scenario events |
| AI action data | System\Game\Parameter\Control.aid |
Reusable actor and enemy behavior choices, grouped by event and guarded by conditions |
| Scenario setup | The matching Scenario.Mct |
Map, music, entities, positions, movement areas, entry points, appearance, and initial actor settings |
| Native game logic | ShadowFlare.exe and the gameplay DLLs |
Evaluating conditions, executing script opcodes and AI actions, movement, combat, rendering, audio, and other engine services |
The distinction is important. MCT values and AID records are data-driven, but they are not SCS sentences and do not run through the scenario interpreter. Conversely, an NPC conversation or quest branch found in SCS should not be recreated as an AI action or hardcoded state machine.
The game ships one global AI database at
System\Game\Parameter\Control.aid. Scenario MCT headers name that controller
file, and actors select one of its behavior lists. The reconstructed
RKC_RPG_AICONTROL DLL proves the following binary organization:
RKC_AIDATA v001 + 0x1a
64 behavior lists
18 event buckets per list
Each behavior list contains:
- a variable-length name;
- a walk-point speed in version 1;
- eighteen event buckets;
- zero or more action candidates in each event bucket.
Each action candidate stores an action number, a 36-byte parameter block, and
a 24-byte condition block. The file therefore describes which native action
may be selected and supplies its tuning values. It does not contain SCS-style
messages, sentences, typed operands, nested calls, or arbitrary opcodes.
Recognizable embedded names include behavior families such as HITandAWAY,
GUARD, and MAGIC.
The executable contains the evaluator and the action implementations. The
evaluator at 0x0045c9f0:
- selects an event bucket from the actor's behavior list;
- rejects candidates outside their inclusive life-percentage or target-range
conditions, with
-1meaning an open end; - clears the retained candidates whenever it finds a new highest priority, but still inserts later lower-priority candidates at the front of the temporary list;
- performs a weighted random selection while traversing that temporary list, which is the reverse of file order;
- falls back to event zero when events 1 through 10, 16, or 17 select nothing;
- copies the selected action number and complete action record into the actor;
- dispatches that number to native movement, attack, guard, spell, or other action code.
The unusual third step is retail behavior, not a simplified priority rule. The shipped file contains 33 places where a lower priority follows a higher one in the same event, so preserving it can affect real selections. Parameter zero is priority and parameter two is selection weight. Condition zero enables the inclusive percentage test in conditions one and two; condition three enables the target-distance query using conditions four and five. Timing, movement, and the remaining values still need consumer traces, so they remain raw rather than receiving speculative names.
The portable executable now has a separate RKC_RPG_AICONTROL static library
which owns AID decoding and lookup. The shipped catalog resolves all 18,788
MCT enemy references against its 64 exact names and keeps all 1,338 candidates
under their original event buckets. An executable-owned evaluator reproduces
the proven filtering, candidate-list quirk, weighted draw, and event-zero
fallback without putting runtime policy into the data library.
It is deliberately not connected to live enemies yet. Enemy life fields,
target querying, and the native action dispatcher are reconstructed behind
that boundary; selected-action storage and presentation completion still need
to land alongside live movement before evaluation can safely alter an actor.
AI lists, probabilities, and condition values must come from Control.aid,
not from NPC-specific C++ branches.
The first two native action handlers are reconstructed behind that live
boundary. Action zero at 0x0045c350 holds the normal idle presentation,
uses parameter one as its duration, reports event 11 while waiting, and
returns to event zero at the inclusive duration boundary. Action one at
0x0045c3c0 alternates a bounded patrol and pause: parameter one limits the
whole action, parameter three is movement speed, parameters four and five are
movement and idle update counts, and parameter six selects the walk chart.
While it remains active it reports event 12, then returns to event one.
The patrol destination is chosen independently on both axes from the inclusive spawn-relative MCT rectangle. Effective speed is parameter three times the enemy's MCT speed scale divided by 1,000. A zero movement duration still enters and immediately leaves the walk presentation but does not choose a destination or consume random state. The shipped database contains 61 wait actions and 92 patrol actions, including six of those zero-duration patrols. This controller remains dormant until its movement and presentation requests have complete live consumers.
The destination selector below the dispatcher is a shared native game service, not another script interpreter. Its seven modes cover fixed points, bounded patrol, player or scenario-actor approach and retreat, and rectangle-edge projection. Target modes use judgement-bound distance, retain their old destination until the authored refresh interval, and preserve both percentage and angle random draws. It only chooses the next destination; collision and stepping stay in the movement controller.
Target acquisition is shared too. The ranged query checks the four player
slots first and only searches companion character numbers 16000000 through
16000003 when no player qualifies. Each group chooses the first actor at the
nearest judgement-bound distance, and -1 leaves a distance end open. The
default query has its own retail activity rules but keeps the same
player-before-companion priority. Both return one typed target record used by
the evaluator and dispatcher; scripts and individual enemies do not maintain
parallel target lists.
Actions two through seven are the two three-variant animated action families.
Their native handlers map them directly to presentation actions one through
six, clear the old presentation, and reset the action counter on entry. The
presentation routine owns targeting and effects, then returns the matching
event number when its animation finishes. Action eight is different: it
resets its action counter but deliberately keeps the presentation already in
progress. The shipped Control.aid does not select actions four or eight, but
their executable paths are still preserved. None of these actions drive a
live enemy until their presentation-side behavior is reconstructed.
Actions nine and ten move relative to a target. Nine retreats to a bounds distance of 10,000 and uses event 14 while active; ten approaches to contact and uses event 15. Both return their own action number as the completion event, whether the authored duration expires, no eligible target exists, or walking stops. Parameter three is scaled movement speed, parameter seven is target-refresh cadence, and parameter eight is random-turn chance. Player targets and scenario-actor targets use separate native movement modes.
Action eleven walks toward the cached walk point at the AI list's
WalkPointSpeed, stops within 150, and holds event minus one for counters zero
through 90 before returning to event zero. It also completes when walking
stops. No shipped AID record selects action eleven; the executable can force
that path itself. The portable dispatcher now covers all native actions zero
through eleven, but stays off the live actor until its emitted movement and
presentation requests have complete runtime consumers.
Several other binary files are essential to gameplay but should not be routed through either interpreter:
Scenario.Mctis declarative scenario and entity setup.Table.Tbdis a general parameter-table database.Item.Ibncontains item definitions and resource selections..Map,.Gnd, and.Oblhold map, terrain, placement, and collision data..Lstfiles are map-pattern asset lists.
These formats can drive a large amount of behavior without being executable scripts. The reconstruction should preserve that data ownership instead of turning the values into constants.
An SCS file starts with the 16-byte signature:
ScenaScriptV000\0
The rest is a sequence of counted blocks. Strings in the message block are stored with every byte bitwise inverted and are decoded while loading.
The portable decoder currently reads:
- temporary flag definitions;
- network flag definitions;
- messages;
- status triggers;
- sentences;
- the command and operand records belonging to each sentence.
The decoder checks every count and byte range and requires the complete file to be consumed. It therefore rejects truncated files, implausible sizes, and unrecognized trailing data instead of quietly accepting a partial script. Sentence and message references are checked when they are executed.
Remote Town's Scenario.Scs contains:
| Block | Count |
|---|---|
| Temporary flags | 66 |
| Network flags | 0 |
| Messages | 61 |
| Status triggers | 23 |
| Sentences | 220 |
| Commands | 608 |
A status record connects a game event to a sentence. Its useful fields are:
- network/local state;
- status kind;
- character number;
- sentence number.
For people loaded from a scenario MCT, the executable derives the script
character number as 12000000 + local people ID. Ostare is local person zero,
so clicking him looks up character 12000000, status kind 0, and enters
sentence 4. Nothing in the portable world layer needs to know his name,
message number, or sentence number.
Kind 3 is an overlap trigger. The scenario update at 0x004305d0 resolves
the status character to its live MCT entity and passes the entity and local
player rectangles to 0x00414350. Touching edges count as an overlap. The
sentence runs even when the entity's ordinary visible, pointer, and judgement
channels are disabled, which is how invisible map exits work.
Kind 5 is a periodic scenario update. Remote Town has five such records.
Four keep the town companion actors in sync with the local player's saved
companion type. The player-owned dog is disabled and the other three are
enabled. Each status is an independent callback, so one unsupported periodic
branch must not prevent the later records from updating their actors.
The remaining record is the world-teleporter activation loop. Opcode 34
measures the judgement-rectangle distance from the local hero to script object
10000202. A zero result means the rectangles overlap. That branch writes
1 to operand type 10, using the matching Table 40 row as its operand value,
and therefore permanently adds the location to the transport list. The same
shape appears throughout the scenario scripts for all 51 transport rows.
The rest of that loop is presentation. While overlapping, it raises temporary
flag 1000039 by 50 per update, plays sample 80 once through latch
1000040, draws message 1000060 (Remote Town) above object 10000200,
and writes the rising value to objects 10000203 and 10000204. Leaving
subtracts 50 per update, resets the sound latch, removes the label, and asks
the executable to close transport service zero. The label and both fades are
therefore SCS-authored behavior, not properties hardcoded onto a teleporter
class.
The portable implementation lives in
src/SF_EXE/libs/RKC_RPG_SCRIPT/. It is a separate static library with two
parts:
ScriptDataowns the decoded, immutable SCS data and its lookup helpers;Interpreterowns execution state, temporary flags, nested sentence frames, message waits, and the actor callback attached to an open message.
The interpreter deliberately does not include world, renderer, audio, UI, or save-game headers. It asks the executable for those services through small hooks:
- read or write an external operand domain;
- perform a native actor/game command;
- answer a typed query about game-owned state;
- measure the local hero's judgement distance from a script character;
- present a decoded message.
This keeps the old DLL boundary visible without pretending that the original DLL contained the whole game. It also means the interpreter can be tested with the retail SCS file without creating a window.
Sentence calls use an explicit frame stack. Opcode 2 presents its message but
does not stop the current sentence: the remaining immediate assignments and
native actions run first, matching the executable. Once that work has
finished, the interpreter reports a message wait. Confirming an actor message
starts status kind 1 for the message's character, which may present another
message and attach the next callback. Unknown opcodes return an error with
their opcode and sentence context; they are never treated as successful
no-ops.
The retail opcode switch begins at 0x00430f80 and covers opcode values
0x00 through 0x4b. Commands are made portable as real interactions and
services exercise them; unknown values still fail loudly.
| Opcode | Retail address | Current meaning |
|---|---|---|
| 0 | 0x00431005 |
Compare two evaluated operands and call a sentence when true |
| 1 | 0x004310a2 |
Evaluate an operand and assign it to another operand |
| 2 | 0x00431294 |
Present a message, finish immediate sentence work, then wait |
| 4 | 0x00432296 |
Mark every equipped, backpack, and belt item as identified |
| 5 | 0x0043222b |
Request one of the executable's numbered vendor inventories |
| 6 | 0x004321e8 |
Rebuild a numbered vendor inventory from a Table 32 stock profile |
| 7 | 0x0043244d |
Fully restore the local hero and a living owned companion |
| 8 | 0x004324cf |
Fully restore the local hero's mana |
| 9 | 0x0043234a |
Repair one equipped item group, or the non-equipped backpack group for selector -1 |
| 10 | 0x00431ca1 |
Ask the world to create an item at evaluated coordinates |
| 11 | 0x00431ac5 |
Add an evaluated value to a writable operand |
| 12 | 0x00431b0c |
Subtract an evaluated value from a writable operand |
| 13 | 0x00431b53 |
Multiply a writable operand by an evaluated value with 32-bit wrapping |
| 14 | 0x00431b9b |
Divide a writable operand by an evaluated signed divisor |
| 15 | 0x00431bef |
Store the signed remainder from an evaluated divisor |
| 16 | 0x00417260 |
Ask the world to play an authored sample, optionally range-limited at its evaluated position |
| 17 | 0x00432162 |
Queue travel to an evaluated scenario and entry |
| 18 | 0x00431efa |
Stop a PEOPLE actor and enter its interaction state |
| 19 | 0x00431f72 |
Native actor action which releases Ostare's interaction |
| 20 | 0x00431fc9 |
Start an evaluated PEOPLE animation action with one-shot or repeated frame bounds |
| 21 | 0x00432094 |
Turn a PEOPLE actor toward an evaluated target when its MCT flag allows it |
| 22 | opcode switch | Enable all three state channels for a scenario entity |
| 23 | opcode switch | Disable all three state channels for a scenario entity |
| 24 | 0x00417550 |
Ask the world to create authored loot at evaluated coordinates |
| 25 | 0x004326c9 |
Reactivate an inactive scenario-enemy slot at an evaluated position and direction |
| 26 | 0x00432b02 |
Draw one evaluated decimal value above a player or scenario actor |
| 27 | 0x00432d05 |
Draw an actor-anchored script message with evaluated offsets, color, and backing opacity |
| 28 | 0x00433022 |
Run the target character's status-kind-six sentence inline when one exists |
| 29 | 0x00433056 |
Send the network-client form of an unlock-switch notification; a single-player run has no local mutation |
| 30 | 0x0043309b |
Build a combat packet and submit an authored effect from an explicit projected origin |
| 31 | 0x00432762 |
Search an inclusive enemy-character range for the first registered active entry and write its absolute character number, or -1 |
| 32 | 0x004327c9 |
Search an inclusive enemy-character range for the first registered inactive entry and write its absolute character number, or -1 |
| 33 | 0x0043288d |
Find the nearest living local player inside an evaluated distance range and write that slot and world position |
| 34 | 0x004337b5 |
Measure the judgement-bound distance from the local hero to a script character and write the result |
| 35 | 0x00432831 |
Convert an evaluated world vector to the executable's truncated direction in degrees |
| 36 | 0x0043332d |
Submit a packetless one-pass visual at an evaluated world position |
| 37 | 0x004334da |
Request the transport service selected by the command argument |
| 38 | 0x00433544 |
Close the matching script-opened transport service |
| 39 | 0x00431c43 |
Write a random integer between two evaluated inclusive bounds |
| 40 | 0x00433409 |
Submit a packetless one-pass visual attached to an evaluated player or scenario actor |
| 41 | 0x004335ac |
Toggle an executable-owned item service; zero selects Warehouse/Special Item and nonzero selects Giant Warehouse |
| 42 | opcode switch | Write the local player's current and maximum life to two operands |
| 43 | opcode switch | Write the local player's current and maximum mana to two operands |
| 44 | 0x00433692 |
Write the local player's saved companion type to an operand |
| 45 | 0x004336a9 |
Switch the local player's owned companion to an evaluated Table 60 row |
| 46 | 0x004336e0 |
Write an evaluated draw strength to a type-zero scenario object |
| 48 | 0x00433868 |
Select a quest notice and set its counter to 600 |
| 49 | 0x0043389b |
Retain one raw scenario message in the executable's map-caption buffer |
| 50 | 0x004321cb |
Write the current scenario-entry value to an operand |
| 51 | 0x00432fed |
Install the twenty evaluated integer substitutions used by later message %d fields |
| 52 | 0x004310d7 |
Write the repair price for an evaluated equipment/backpack selector to an operand |
| 53 | 0x00433923 |
Write the local player's total Gold to an operand |
| 54 | 0x00433940 |
Spend an evaluated amount of Gold from the backpack owner |
| 55 | 0x0043397d |
Write whether any equipped, backpack, or belt item is unidentified |
| 56 | 0x00433a78 |
Override one scenario entity's effective visible, pointer, and judgement states |
| 57 | 0x00433b1f |
Write the local player's stored gender value without remapping it |
| 58 | 0x00433b33 |
Search the four automatic-item pages, backpack, and active equipment, then write zero or one |
| 59 | 0x00433ced |
Remove the first matching item from that same retail owner order |
| 60 | 0x00433edf |
Refresh the local player's one-update UnlockSW presentation marker |
| 61 | 0x00433f16 |
Write the local player's level to an operand |
| 62 | 0x00433f29 |
Update a quest's state and trigger its update/completion cue |
| 63 | opcode switch | Write the local player's current and maximum optional condition to two operands |
| 64 | 0x00434001 |
Open an authored full-screen Epilogue or VisualNN presentation |
| 65 | 0x0043403e |
Refresh the colored falling-streak emitter with evaluated RGB and count values |
| 66 | 0x00433682 |
Write the current local-player slot number |
| 67 | 0x004340e7 |
Mark one spell as permanently learned in the player's saved magic owner |
| 68 | 0x004342de |
Award a percentage of the current level's experience threshold and run the ordinary level-up path |
| 69 | 0x0043412b |
Write whether one spell has the exact learned availability state |
| 70 | 0x00434186 |
Map the local player's saved job to the occupation-menu selection and write it |
| 71 | 0x004341da |
Change the local player's saved job from an evaluated occupation-menu selection |
| 73 | 0x004343b0 |
Close the ordinary gameplay panels and request the executable-owned Blackjack service |
| 74 | 0x00434412 |
Write the most recent Blackjack result to an operand |
| 75 | 0x0043443c |
Create a table-backed item and place it in its authored automatic-item page and cell when absent |
Opcode 0 stores its comparison selector as a raw operand. The selectors seen in the executable are:
| Selector | Test |
|---|---|
| 0 | equal |
| 1 | not equal |
| 2 | greater than |
| 3 | less than |
Opcodes 49 and 50 are part of map initialization rather than conversation UI.
The first copies the selected SCS message verbatim into the retail buffer at
0x0048d5f8 and clears its companion value at 0x0048d5f4. No code which
reads that buffer has been found in the executable, so the portable runtime
retains the ID and text for fidelity and inspection but does not invent a
visible area-name banner. Opcode 50 writes the entry value installed by the
scenario loader. Dusty Ruins uses it to choose B1F or B2F, including when
an authored transition changes floors without changing the scenario ID.
Retail installs the local player and entry before running scenario status kind
7. It runs that status after both a changed-map load and a same-scenario
relocation. The portable transaction now follows that order, which also lets
initialization scripts safely query player level before building vendor stock.
Opcodes 57 and 66 expose two different parts of that installed player. The
handler at 0x00433b1f resolves the current player and copies runtime field
+0x28, which is the saved gender at record offset +0x18; it keeps retail's
raw 0 = female, 1 = male representation. The shorter handler at
0x00433682 calls 0x00434cd0 and copies the player-list current-slot field
at +0x08, so multiplayer-aware scripts receive the actual local slot from
zero through three rather than a made-up character number.
The shipped catalog contains ten opcode-57/66 pairs in ten scenarios. Every
command has one writable temporary-flag operand, and every sentence which
uses one query uses the other exactly once. Dusty Ruins (00010000) is the
first visible example: entry zero initializes a 200-update line, opcode 66
anchors it to the local hero, and opcode 57 selects message 1000004 for a
woman or 1000003 for a man. The ordinary status-kind-5 update, opcode 27
label owner, player record, and scenario slot remain separate owners; the
script library only performs the two queries and writes their destinations.
Opcode 56 evaluates a character number followed by visible, pointer, and
judgement values. The handler at 0x00433a78 finds the live entity and writes
an enabled flag plus those three overrides at runtime offsets +0xfc through
+0x108. It does not rewrite the three ordinary script variables. Type-zero
object drawing at 0x0045ddd0 uses the effective visible value, while
0x0045e080 uses the effective pointer and judgement values. Near Remote Town
uses the command every update to swap objects 1030 and 1031 according to saved
script flag 71. Every shipped opcode-56 target is a type-zero object, though
the portable state owner keeps the same override available to every scenario
entity class.
Opcode 39 evaluates its lower bound first and its upper bound second, takes
exactly one value from the executable's shared Visual C++ random stream, and
writes lower + rand() % (upper - lower + 1) to its third operand. Both ends
are therefore possible. The shipped scripts contain 611 calls across 55
scenarios, and every call has exactly three operands. Of those, 285 choose
between zero and one and 41 choose from 20 through 40; spawn setup uses the
remaining calls with script-calculated upper bounds. The portable
script library asks its host for the next random value, keeping the DLL
boundary free of world ownership while still sharing the world's retail
random sequence.
Opcode 33 resolves its first operand through the scenario-character registry,
then searches the four local-player slots in numerical order. A candidate must
be active, alive, and in the same scenario. Distance comes from the shared
judgement-rectangle measurement; -1 leaves either end open and every other
bound is inclusive. The nearest candidate wins and the earlier slot wins a
tie. Success writes the player slot followed by world X and Y. A valid source
with no candidate writes only -1 to the slot, leaving both coordinates
alone; a missing source leaves all three outputs alone. The portable runtime
currently owns one local player, but the script-library boundary already
returns the complete target record so multiplayer can extend the world query
without changing the interpreter.
Opcode 35 evaluates X and Y, calculates atan2(-Y, X), multiplies by the
executable's slightly truncated 57.29579143313326 degrees-per-radian
constant, and truncates toward zero. It does not normalize negative angles.
The shipped data contains 126 opcode-33 calls in 25 scenarios and 80
opcode-35 calls in 17 scenarios. Six target queries and one direction query
deliberately put a literal in the last output position, which the ordinary
operand writer ignores just as it does for other non-writable operands.
Opcodes 13, 14, and 15 continue the writable arithmetic group started by add
and subtract. All three evaluate the destination value before the right-hand
operand and write back through the common operand owner. Multiply keeps the
low 32 bits of the x86 imul. Divide and remainder use signed idiv, so the
quotient truncates toward zero and the remainder keeps the dividend's sign.
A zero divisor returns successfully without changing the destination. The
retail scripts contain 67 multiplies, 126 divides, and 195 remainders across
34, 45, and 27 scenarios respectively; every destination is a temporary flag.
Opcode 30 is the script-facing form of the executable's normal effect request,
not a new scenario-actor class. All 411 shipped calls have fourteen operands,
spread across 33 scenarios. The first two supply an origin, operand three is a
direction in degrees, and operand seven projects that origin along the retail
sine/cosine path. The remaining values fill the effect number, speed, height,
direction and selected words of the ordinary 77-word combat packet. One
shared random draw selects packet presentation 21000..21003 for nonzero
effects or 21007..21009 for effect zero. Near Remote Town's first periodic
spawn sentence uses the position of object 10055000, submits effect two,
then follows it with a separately authored positional sound. The script
library only evaluates the fourteen operands; the world owns packet
construction, the random stream, and the effect runtime.
Opcode 36 is the lighter packetless version used for placed scenery effects. Its seven operands are effect number, world X, world Y, display height, direction, judgement right, and judgement bottom. A negative direction becomes direction eight. The request has owner kind zero, an explicit origin, zero left/top bounds, no combat packet, and the same common constructor value 200. The one-pass owner adds one to the supplied lower-right coordinates and uses that result for all four edges of its point judgement rectangle.
The shipped scripts contain 353 calls across 26 scenarios. Effects 20007 and 20008 occur 34 times each and select OPTION resources 11000005 and 11000006; their omitted presentation values evaluate to height zero, direction eight, and zero-sized bounds. Effect 20009 occurs 285 times, selects resource 11000007, uses height 150, and supplies directions one, three, five, or seven. Near Remote Town's sentence 18 is the first direct fixture, and its periodic status creates the six live visuals authored for that update.
Opcode 40 is the actor-attached form. Its two operands are the effect number and source character. Player slots zero through three use owner kind one; every other resolved scenario character uses owner kind four. The executable copies the source judgement rectangle into the common request, leaves the origin implicit so the one-pass owner resolves the actor position, uses direction eight, and carries no combat packet. A missing source is a successful no-op.
All 54 shipped calls have two literal operands and occur across 45 scenarios. Eight use effect 20010/resource 11000008, while the other 46 use effect 20018/resource 10000020. The portable interpreter only evaluates and forwards the pair. Actor lookup, geometry, effect resources, and presentation stay in the world owner.
Opcodes 18 and 21 are separate operations. Opcode 18 addresses a PEOPLE actor, stops its current walk, and enters interaction state without changing its facing. Opcode 21 evaluates an actor and a target. Target zero is the local player; a nonzero value resolves another scenario actor. The actor turns only when its PEOPLE-tail scripted-turning flag is enabled. This is enabled for Ostare, Syria, and the four Remote Town animals, but disabled for Malse.
Opcode 17 evaluates a scenario ID and entry value, writes them to the
executable's pending-travel fields, enables the travel request, and clears the
optional explicit-position selector. Remote Town's invisible object zero has
status kind 3; its sentence 219 supplies {1, 0}. Walking through the south
gate therefore loads scenario 1, Near the Remote Town, at entry key zero.
That outdoor scenario uses the same mechanism in reverse: object zero supplies
{0, 0} and returns to Remote Town's entry key zero.
The C99 interpreter forwards those two values without loading anything. The
world's status-kind-three pass resolves each authored entity, performs the
same inclusive judgement-rectangle overlap, and publishes one fixed travel
request after the sentence completes. Same-scenario entries relocate through
the active MCT. A cross-scenario request reaches runtime/gameplay_runtime.c,
which rewinds only screen-scoped memory, loads the new scenario and entry, and
rebinds the preserved player and progress owners. A native regression crosses
the shipped trigger in both directions.
Opcodes 37 and 41 show why native commands remain hooks. Remote Town object
200 has script character 10000200; its status-zero sentence calls opcode 37
with argument zero, which asks the executable to open the transport panel.
The named Warehouse is character 10000300 and calls opcode 41 with argument
zero, which toggles the same Special Item owner opened by X. The interpreter
does not include panel, input, camera, item, or transport headers. It only
evaluates the command and sends the typed request across its native-command
hook.
Opcode 27 takes eight operands: actor, X offset, Y offset, message ID, red,
green, blue, and black-backing opacity. The executable projects the actor,
measures Shift-JIS text on a 6-by-12 grid, horizontally centers it, and
bottom-aligns it at the evaluated offset. It submits a black rectangle with a
three-pixel margin, a black text pass at +1,+1, and then the colored text.
Remote Town uses {10000200, 0, -160, 1000060, 224, 224, 224, 1000}.
Opcode 46 evaluates an object character and strength, then writes the live
type-zero draw-strength field used by both static and animated rendering.
Remote Town feeds its 0-to-1000 pulse into objects 10000203 and 10000204;
an opacity of zero also stops their hidden CAF update. Opcode 38 is the paired
service cleanup. It closes transport only when its argument matches the
script-opened selector, leaving an independently open right-side inventory
and its camera layout untouched.
The C99 runtime now follows that split directly. The interpreter forwards 16,
17, 27, 37, and 38 through its native hook. The world queues opcode 16's sound,
keeps opcode 27's fixed actor-relative label values, and records service
requests. A stable label revision avoids redrawing an unchanged label every
30 Hz update. The screen-side service controller opens or closes UI state,
and the selected same-scenario row is resolved through the MCT by
game/world_transport.c. Label measurement and composition, panel artwork,
paging, hover, and click consumption stay in ui/; no script opcode includes
a UI header or knows a screen rectangle.
The one shipped nonzero opcode-41 call is scenario 99000013, sentence 10.
Its object 10000900 is named Giant Warehouse in the Tower of Ordeal 12F
MCT and passes argument one. The executable uses that value to select a
different character-owned service with ten 9-by-10 pages. This remains a
native request: the script chooses the service, while item ownership, page
input, rendering, and persistence stay outside the interpreter.
Opcodes 58, 59, and 75 expose a different item owner. Category-four
Item.Ibn records can carry a page number and fixed grid cell. A non-negative
page routes the item into one of four private player collections instead of
the backpack; these are not the X Warehouse or any Giant Warehouse page.
Opcode 75 creates the ordinary retail instance, refuses a duplicate in its
authored page, and inserts it at that fixed cell. Opcode 58 searches all four
pages first, then the backpack, active main hand, body, active off hand, head,
legs, and four accessories. Opcode 59 removes in precisely that order and
refreshes the player profile when it removes equipped gear. The belt,
alternate weapon set, Warehouse, and Giant Warehouse are deliberately not
part of either command.
The shipped scripts use these commands for story objects such as Malse's Gem and Syria's Spirit Stone, the later orb and card sets, companion stones, and ordinary category-three or equipment checks. This is why the interpreter only evaluates operands and returns the query result: item construction, owner order, equipment refresh, and persistence remain executable hooks.
Opcode 68 is the matching scripted reward path. Its argument is a percentage,
not a raw experience amount. The executable reads Table 13 at level - 1,
multiplies that threshold by the evaluated percentage with a signed 64-bit
intermediate, divides by 100, and adds the result to player-record experience.
Crossing the threshold invokes the same growth, full life/mana restoration,
900-update notice, and samples used by combat experience. Scenario 04900001
sentence 30 demonstrates the real sequence: opcode 75 grants the Spirit Stone,
opcode 68 grants 50 percent, and opcode 16 plays the authored reward sample.
Opcodes 67 and 69 use the saved magic owner directly. Opcode 67 writes the
exact learned value 3 to the evaluated spell slot at player +0x1440.
Opcode 69 compares that same stored value with 3 and writes zero or one to
its second operand. It deliberately ignores the runtime-only All Spells debug
override. Shipped scenario 04100000 uses opcode 69 to branch on a reward
spell and later opcode 67 to grant it; the normal magic save block preserves
the result.
Opcodes 5 and 6 follow the same boundary. Scenario status kind 7 initializes
vendor inventory zero with opcode 6 and stock profile zero (or profile 22 for
the alternate global state). Malse later refreshes that same owner with
profile 8, and his Trade callback opens it with opcode 5. The script owns
all of those gates and profile numbers; no Malse or Red Goblin condition is
duplicated in C++.
The executable's stock builder at 0x00430c10 expands Table 32 into Table 33
entries. Fixed entries name a category and definition directly. Random entries
filter the decoded Item.Ibn definitions by category, episode, level range,
variant flags, and loot weight before creating a normal rolled item instance.
Table 33's start column and row are the beginning of a row-major search in the
9-by-10 merchant grid, not a decorative position. The portable vendor owner
keeps that numbered inventory and placement logic separate from the player
backpack.
Opcode 10 evaluates six operands: category, definition ID, world X, world Y,
minimum quantity, and maximum quantity. Ordinary items create one record at
the requested point. Category 4, definition 0 is the executable's money
case: it chooses an inclusive quantity, splits values larger than 10,000 into
stacks, and places them around the point at radius 200 with an angle step of
roughly π/10. Ostare supplies the fixed range 200–200, so his opening quest
creates one stack at the first point on that circle.
Opcodes 11 and 12 use operand zero as both the first input and the destination. They are what turn Ostare's live actor coordinates into the four drop positions. Opcode 19 addresses the actor again and ends the interaction after the final bubble closes. Its behavior for other actor types still needs more examples before the command gets a narrower name.
Opcode 61 is much narrower. The retail handler gets the local player, reads
the level field at offset 0x34, and passes it to the common operand writer.
The portable interpreter asks its host for
ValueQuery::local_player_level, so player data stays game-owned rather than
being copied into the script library.
Opcodes 71 and 70 do the same for occupation changes. The saved runtime job
values are 16 for Mercenary, 6 for Warrior, 5 for Hunter, and 9 for
Wizard or Witch. Opcode 71 maps those to menu values zero through three;
opcode 70 accepts only selections one through three and writes Warrior,
Hunter, or spellcaster respectively. Invalid selections leave the record
unchanged. Scenario 03900003 contains the one shipped query/change pair:
its authored service remembers the current selection, changes the job after
the player's choice, and plays the accompanying sound through opcode 16.
Opcode 72 has no operands. Its three shipped call sites (01000000 sentence
212, 02100000 sentence 357, and 03900002 sentence 102) all sit behind an
equipment-color conversation. The interpreter asks the world for that
service; the UI owner then snapshots the equipped weapon, shield, and body
color fields before opening the panel. This keeps the script library unaware
of item records, rendering, and input.
Opcodes 73 and 74 form a complete asynchronous service boundary. Opcode 73
has no operands. It closes the ordinary gameplay panels and asks the
executable to run Blackjack; cards, input, timing, audio, and drawing do not
belong to the interpreter. When the result display closes, the executable
runs scenario status kind 8. The following sentence uses opcode 74 to write
the saved result: zero is a draw, one is a player win, and two is a dealer
win.
The shipped paths are Tower of Ordeal scenarios 99000018 and 99000023.
Scenario 99000018 starts the game in sentence 22 and handles the result in
sentence 31. Scenario 99000023 continues through sentence 35. The portable
tests load the original SCS and exercise that launch/result pair, so these
sentence numbers and branches remain scenario data rather than C++ rules.
Opcodes 22 and 23 take a script character number and write one or zero to all
three of its live entity-state keys. Opcode 44 reads player-record offset
0x140, which is the currently owned companion type, through a typed host
query. Remote Town combines those commands with the play-mode operand to keep
the selected companion from also appearing as a clickable town NPC.
Opcode 45 is the matching mutation. It evaluates one Table 60 row, stores the
active dog's level and experience in the player's per-companion arrays, then
loads the selected row's saved values and clears its defeated countdown.
0x00450500 destroys and recreates character 16000000 + player slot at the
hero with full life, so a swap does not reuse the town PEOPLE actor or retain
the previous dog's presentation state. The six shipped calls cover companion
types zero through five across scenarios 00000000, 01000000, and
03900005. Remote Town's four Swap Dogs choices are therefore ordinary SCS
branches; the portable world does not contain a name-based companion menu.
Opcode 62 evaluates a quest ID, a new state, and a network-notification flag.
Ordinary updates write the new state and issue cue 0x41. State 2 is the
completion path: the executable latches completion once, requires the old
state to be 1, writes state 2, and issues cue 0x42. Its optional server
broadcast is deliberately left at the world hook because the current scenario
is single-player. The portable QuestState owns these executable-level
values; they do not live in the DLL-derived interpreter.
Opcode 48 evaluates one quest ID, stores it as the selected quest notice, and
sets the adjacent counter to 600. Syria's first new-game conversation
executes opcode 62 with {0, 1, 0} and then opcode 48 with {0}. The
gameplay interface reads the title from Table 41, wraps it in the retail
Shift-JIS corner brackets, and temporarily draws it just above the lower HUD.
The exact title rectangle is clickable and opens the Mission List. Opcode 62
also plays retail sample 65 for an ordinary quest update and sample 66 for a
valid first completion. While any quest remains in state one, the same retail
function draws StatusIcon.njp pattern zero at (616, 360). Its
616..639 by 368..383 shortcut stays after the timed title has faded and
also opens the Mission List.
Syria's later status-zero branch reads quest zero directly. When it is already active, opcodes 42, 43, and 63 compare the player's life, mana, and optional condition pairs before selecting the normal healing or blessing text; it does not offer quest zero again.
The wounded branch continues through the authored callback rather than a
conversation special case. Opcode 19 first releases Syria, opcode 20 starts
PEOPLE action four as a one-shot, opcodes 7 and 8 restore the party, and opcode
16 plays the sample stored in temporary flag 1000061. Action four maps to
Syria's CAF chart three. The PEOPLE owner presents frame zero first, keeps the
last frame for one update, and then returns to idle chart zero. Opcode 7 copies
the hero's live derived maximum life into current life and does the same for
the owned companion only while that companion is alive; it deliberately does
not revive a defeated companion. Opcode 8 independently copies the live
derived maximum mana into current mana.
Opcode 20 evaluates six operands: actor, action, repeat selector, restart
frame, end frame, and one trailing value retained by the native call. PEOPLE
actions four through nineteen map to CAF charts three through eighteen. A
repeat selector of -1 plays once. Other values repeat, using the authored
restart and end frames; -1 means frame zero or the chart's last frame. This
is kept in the PEOPLE action controller, not in the interpreter, because CAF
ownership and actor update timing belong to the world.
The ordinary Mission List does not store another hand-written copy of this
information. 0x0040cea0 reads the state array written by opcode 62, takes all
48 titles from Table.Tbd table 41, and reads mission n's description from
table 700+n. State zero is absent from the list, state one is unfinished,
and state two is cleared. In the portable code, QuestState still owns the
script values while MissionCatalog owns the table text; the screen only
combines those two sources for display.
Opcode 2 keeps presentation mode and selection state separate. Operand one is
the writable selection result and operand three is the initially selected
zero-based option. A non-negative initial option and paired ~ runs identify
the actual choice step. The four Remote Town companions use presentation mode
one, while Malse's service menu uses mode zero. The
executable's message layout removes those markers, records the enclosed line
and columns for hit testing, and writes the chosen range number before
entering the actor's status-kind-one callback. Messages with initial option
-1 are chained informational speech instead: they have no selectable
ranges and write -1 to operand one when they close. The portable interpreter
and speech-bubble layout preserve that split.
The native Remote Town fixture walks to Gravity, opens his retail message,
checks the initial red QUIT selection, moves the red highlight to
Check Status, and opens the generated status speech before closing it through
Gravity's authored status-one release branch. A second interaction hits the
rendered QUIT range and writes option three. Unselected ranges use the retail
gray, and leaving all ranges keeps the most recent selection. This covers the
actual world-to-render-to-interpreter path rather than only testing the marker
parser by itself.
Opcode 3 is the companion status message. Its first operand is a constant
companion type and its second is the writable close result. The handler at
0x0043167d reads that type's saved level, rebuilds the profile from Table 60
and table 800 + type, and opens the resulting text through the same ordinary
actor speech owner as opcode 2. Closing the bubble writes -1 to the result
and enters status kind one; the shipped companion sentences have already set
their branch flag to two, so that callback releases the actor.
There are only six opcode-3 calls in the shipped scripts, one for every
companion type across three scenarios. The text uses the active companion's
level and experience fields but the requested type's table-backed profile.
The cap is min(player level / 3 + 2, 35), producing Experience Limit at a
non-final cap and Experience Max at 35. The executable also labels magical
hit rate as M Defense and physical defense as M Evasion Rate; the portable
formatter keeps that retail display bug.
Harley's Explanation choice exercises the other mode-one path. Choosing
option one shows 1000057 (“You found me finally.”), ordinary acknowledgement
advances to 1000058, and acknowledging that second bubble reaches native
actor command 19 and releases Harley. Both the interpreter fixture and the
live WorldScene fixture cover this complete chain.
The two scripted type-zero services use the same status route. Their MCT
visibility and pointer flags decide whether they can be selected, the common
159-unit judgement distance controls the approach, and only then does status
kind zero run. This keeps Warehouse and transport behavior out of
WorldScene: the world owns selection and relocation, while the runtime owns
the panels and their input.
Each command operand has a type and a value. The executable's operand reader
starts at 0x004346b0; its corresponding writer starts at 0x00434920.
The currently understood domains are:
| Type | Meaning |
|---|---|
| 0, 1, 2 | Raw immediate value |
| 3 | Scenario-enemy lifecycle by local enemy number |
| 4 | Scenario temporary flag |
| 5 | Network/state domain |
| 6 | Script character's current world X |
| 7 | Script character's current world Y |
| 8 | Current play mode (0 single player, 1 client, 2 server) |
| 9 | Whether the idle local player is within interaction range of a displayed script character |
| 10 | Persistent transport flags (Table 40 rows) |
| 11 | Persistent script and conversation flags |
| 12 | Persistent quest state |
| 13 | Giant Warehouse page-unlock values |
Type 5 includes three confirmed live scenario-entity ranges. A key beginning
at 100000000 controls visibility, 300000000 controls pointer selection,
and 200000000 controls judgement/collision. The remainder is the entity's
script character number. Type-zero MCT objects use 10000000 + local ID;
PEOPLE use 12000000 + local ID.
Several additional types address broader game state. They will be named only when an exercised retail path gives us enough evidence.
Type 3 is the direct form of the scenario-enemy registry used by opcodes 31
and 32. Retail adds 14000000 to the operand value, looks up that exact MCT
enemy slot, and returns its lifecycle value. A missing entry returns -1, not
zero. All 160 shipped type-three operands are reads in opcode-0 comparisons;
none are assignment destinations.
Type 9 is the gate used by the shipped unlock-switch interactions. Retail
first requires the local player to belong to the active scenario and be in
normal idle action one. It then finds the requested character in the live
object-display registry and compares the two judgement rectangles against the
player's 159-unit interaction range. Missing, hidden, busy, or distant actors
return zero. All 60 shipped uses are the second operand of opcode 1, spread
across 22 scenarios and the seven 10041000..10041006 switch actors.
Temporary flags are owned directly by the interpreter and initialized from the
SCS definitions. Persistent and game-owned domains are callbacks because their
lifetime belongs to scenario state, save data, the player, or another portable
DLL boundary. Retail writes types 12, 10, and 11 as three counted arrays
immediately after the owned-item stream, in that order. The reconstruction now
restores and rewrites those arrays through their real owners. This includes
Ostare's type-11 flag at index 4, so his opening conversation and starter drop
do not repeat after a save/load. Type 13 addresses the ten Giant Warehouse
page-unlock values which retail saves beside the ten page containers. All ten
shipped operands use indices two through nine; Berini's Sacred Wing reward,
for example, writes one to index two and unlocks Giant Warehouse III. The
portable operand callback now reads and writes that saved owner directly.
The first end-to-end slice is Ostare's opening Remote Town interaction:
Scenario.Mctcreates Ostare as local person zero.- A click on his rendered actor derives script character
12000000. - Status kind
0resolves to sentence4. - The interpreter follows its nested comparisons and sentence calls.
- Opcode 18 stops Ostare's wandering; opcode 21 separately turns him toward the player because his MCT flag allows it.
- Message
1000000is decoded from the retail SCS and shown. - Return or another click invokes Ostare's status-kind-one sentence.
- Four more callbacks show messages
1000001through1000004. - The third callback reads Ostare's live X/Y position and creates four ground items through opcode 10.
- Closing the final bubble runs opcode 19 and restores world control.
The visible text begins with Ostare introducing himself as commander of the area. That text is read at runtime from the original data; it is not present in the OpenShadowFlare source.
Clicking Ostare again follows the next real SCS branch. Sentence six uses
opcode 61 to read the new character's level, selects the under-level-five
path, and shows message 1000005. Its status-kind-one callback then shows
message 1000006, resets the temporary dialogue state, and releases the actor.
The interpreter retains the earlier persistent assignment, so this is a
continuation of the same script state rather than a hand-written interaction.
The same table-driven actor path now loads all seven Remote Town people
records rather than stopping after Ostare. Malse's new-game status runs its
real two-message branch (1000019 and 1000020) and releases him through
opcode 19. The longer Malse quest dialogue is not forced: the retail SCS only
selects it after the Red Goblin progression state reaches the required value.
Once quest zero is complete, the same authored branch introduces Malse as a
merchant, advances its persistent script flags, and then exposes message
1000013. Choosing Trade reaches sentence 113 and opcode 5. The resulting
left merchant panel and right inventory remain live together: stock can be
carried into the backpack or equipment slots, player items can be sold back,
gold is debited only after a purchase lands in an owned container, and Escape
returns an unfinished purchase to its original merchant owner. Purchase hover
text uses Price; owned-item hover text keeps Sale Price.
That introduction also offers mission one through message 1000024; the
quest ID is not attached to Malse in C++. Black Hammer in West Ruins scenario
00000004 owns loot row 6. Its zero attempt count expands to the active-player
count, every one of its ten slots selects Table 31 row 400, and that row fixes
the result to category 4, definition 99000000. Picking up the gem therefore
sends it to automatic-item page zero rather than the backpack. On the return
visit, sentence 37 finds it through opcode 58. Retail finishes the rest of
that sentence immediately after opening message 1000028: opcode 59 removes
the gem and opcode 62 completes mission one and queues sample 66 before the
bubble is dismissed. The remaining three callbacks show messages 1000029
through 1000031 and release Malse. Quest state, Malse's progress flags, and
the removed automatic item all survive the ordinary save path.
Malse's Identify Items choice is script-owned as well. Opcode 55 first scans
the five ordinary equipment slots, four accessory slots, backpack, and belt.
When there is something unknown, opcode 51 inserts the authored flat price of
100 into message 1000017; NO starts selected. Confirming YES uses opcode
53 to check the backpack's Gold, shows message 1000015 when the player is
short, or spends exactly 100 through opcode 54 before opcode 4 identifies all
of those owners. Their raw instance words are updated with the live flag, so a
normal save keeps the result. If everything is already known, the script shows
message 1000018 without opening another panel or charging Gold. This is a
merchant service distinct from the Identify spell, which selects one backpack
item and trains the spell.
Repair Items continues through sentences 117 and 75 rather than opening a
separate panel. Opcode 52 queries selectors zero through four for Arms, Head
Armor, Body Armor, Shield, and Leg Armor; selector -1 covers non-equipped
items in the backpack. The script sums the first five values for All Equipped,
inserts all seven prices into message 1000014, and starts on QUIT.
The groups come from executable ownership, not NPC-specific code. Arms include
both active and alternate main-hand pointers, while Shield includes both
off-hand pointers. The other three selectors address their one equipped slot.
Non-Equipped scans only category-zero weapons and category-one armor in the
backpack; belt and accessory items are not repairable. A zero price shows It has been repaired, insufficient Gold reuses message 1000015, and a
successful choice runs opcode 9 before opcode 54 spends exactly the displayed
price. All Equipped invokes opcode 9 once for each of the five groups. Every
branch returns to the newly priced Repair menu until QUIT releases Malse.
Syria's new-game status follows messages 1000040 and 1000041. Its callback
starts quest zero with opcode 62 and selects that quest's notice with opcode
48 before waiting for the second bubble to close. Message events carry the
script character number, so the renderer can anchor Syria's bubble even
though this particular branch does not run the explicit actor-facing command
used by Ostare and Malse. Dialogue text, actor IDs, branches, and quest IDs
continue to come from the retail SCS.
The corresponding completion is authored on the outdoor map rather than in a
hard-coded enemy-name check. Red Goblin character 14010000 has status kind
4 at sentence 12. After its death animation and fade finish, the retail
enemy owner invokes that status; its opcode 62 command changes quest zero from
state one to state two and emits the completion cue.
Enemy groups have a second script-facing state which is separate from their
three visible, pointer, and judgement channels. Opcodes 31 and 32 evaluate an
inclusive pair of absolute 14000000-series character numbers and scan the
scenario enemy registry in ascending order. Opcode 31 returns the first
registered entry whose lifecycle value is one; opcode 32 returns the first
whose value is zero. IDs which are not present in the current MCT are skipped,
including for opcode 32, and either command writes -1 when no entry matches.
That lifecycle value follows the enemy owner rather than HP alone. Retail sets
it to one when an enemy is activated and leaves it set while a zero-life enemy
plays its death chart and 120-update fade. The death owner clears it only when
that presentation expires, immediately before the matching status-kind-four
callback. The portable world exposes the same lifecycle through a narrow
interpreter hook, so group-clear and later encounter scripts do not need to
know about EnemyActor or duplicate combat state.
Episode 1's Dusty Ruins assignment is the first complete group-clear path.
After quest zero is complete, Ostare still waits until the saved hero reaches
level 30. His Remote Town branch then shows messages 1000007 through
1000009, starts mission three, selects its 600-update notice, and queues
sample 65. In Dusty Ruins scenario 00010004, a periodic sentence scans
enemy characters 14000000 through 14000007 with opcode 31. Each defeated
Garam Goblin remains active until its death chart and fade expire, so the
mission cannot complete early. Once the scan returns -1, the script applies
its three object-state commands, plays its positioned sample, and completes
mission three with sample 66. Back in town, Ostare creates the Table 30 row-4
reward before message 1000011, sets persistent flag two, follows with the
Cold Svalt message 1000012, and never creates that reward again after a
save/load round trip.
Syria's neighboring Spirit Stone mission is separate from her healing path.
Once mission three is active, message 1000044 starts mission two and its
notice. Stone Spike in continued Dusty Ruins scenario 00010005 owns Table
30 row 23; all ten choices lead through Table 31 row 401 to fixed category 4,
definition 99000001. This is Syria's page-zero item at cell (1,0), not the
different page-two item with the same display name used by a later reward.
On return, message 1000045 is followed immediately by opcodes 59 and 62, so
the stone is removed and sample 66 is queued before acknowledgement. Its
callback opens message 1000046 and creates the fixed category-2 definition
1100001 reward. Saving the completed state keeps the stone absent and sends
later visits back through Syria's ordinary recovery branch.
Completing Dusty Ruins also unlocks two independent Remote Town callbacks.
Malse requires mission three complete, Ostare's reward latch set, and his own
flag eight clear. Messages 1000025, 1000026, and 1000027 run across the
status-zero/status-one callback chain; only the last one calls opcode 10 for
category two, definition 1100000. Syria checks the same completed mission
and Ostare latch but owns flag seven instead. Message 1000042 sets that
latch, and callback message 1000043 creates definition 1100002. Both
commands use the NPC position plus the authored 200-unit offset and -1
spread values, so they remain ordinary airborne ground items with the
category-two sample 93 landing sound. Saving flags seven and eight prevents
the gifts from being produced again.
Ostare's Cold Svalt direction is backed by ordinary status-kind-three map
edges. The Episode 1 route is scenario 1 (Near the Remote Town) object 6 to
scenario 3 entry 1, scenario 3 object 0 to scenario 5 entry 0, and scenario 5
object 1 to scenario 6 entry 1. In Wasteland of Pillars, object 3 enters
sentence 9. The sentence compares mission three with completed state two and
only then reaches sentence 10's opcode 17 call for scenario 1000001, entry
zero. The incomplete branch contains no travel command, so touching the same
edge before clearing Dusty Ruins correctly does nothing. Scenario 1000001
is the enemy-occupied Cold Svalt Town map; it is distinct from the recovered
town in scenario 1000000.
The next edge and first town mission stay script-owned as well. Object 2 in
occupied scenario 1000001 calls opcode 17 for inhabited Cold Svalt scenario
1000000, entry zero. Alex's clear flag 11 starts messages 1000000..1000006
through his status-zero/status-one chain and saves the first-visit latch.
Rosanna owns flag 15: her first visit runs messages 1000047..1000049, and
the next runs 1000050..1000051, sets mission four active, selects its notice,
and queues sample 65. Wild Ice in the occupied outskirts owns loot row 56 and
therefore the fixed category-four definition 99000002, stored on automatic
page zero at (2,0). On return, Rosanna's message 1000053 is followed by
opcode 59 and opcode 62 before acknowledgement, so the ruby is removed and
sample 66 is queued immediately. Callback message 1000054 creates category
two definition 1100003; later visits use message 1000055 without repeating
the item.
With mission four complete, Alex's next status chain runs messages
1000009..1000012 and sets mission six active. Cold Ruins bottom-floor
scenario 1020002 periodically scans enemy characters 14000000..14000006
with opcode 31. The two Frost Golems, four Knight Frost Goblins, and King Frost
Goblin must all finish their death presentations before the scan is empty.
Only then does the script hide object 10011000, show 10011001 and
10011002, play its positioned sound, and complete mission six with sample
66. Alex's completed branch opens message 1000014 and creates category four,
definition zero with quantity 2,000: the normal Gold ground item and sample 85
landing sound. Its callback opens 1000015, starts mission seven, and queues
sample 65; later visits use 1000016, so saving and loading cannot repeat the
Gold or the handoff.
The Purgatory assignment uses the same generic pieces on different shipped
maps. Vaporous Forest scenario 1000002 object 2 calls opcode 17 for scenario
1030000, entry zero. After traversing that map, object 1 calls opcode 17 for
scenario 1030002, also at entry zero. Its periodic sentence scans character
numbers 14000000..14000006: three Arc Shamans and four Arc Thunder Bats.
When all seven death presentations expire, the script hides object 10011000,
shows 10011001 and 10011002, plays the room sound, and completes mission
seven with sample 66. Alex's 1000017 branch creates category four definition
zero with quantity 4,000, followed by sample 85 on landing. Callback messages
1000018..1000020 start mission eight and sample 65; saved active state uses
1000021 and does not create the Gold again.
Mission eight begins at Hanged Men's Forest scenario 1000003, whose object
1 enters Remains scenario 1040000. Object 1 there enters 1040001, and
object 1 in that inner map reaches clear room 1040002. The room scans enemy
characters 14000000..14000006: two Earth Golems, two King Earth Goblins,
and three Arc Goblin Shamans. After every lifecycle clears, it swaps objects
10011000..10011002 with sample 34, swaps 10021000..10021001 with sample
31, runs Table 30 loot row 63 at the second door, and completes mission eight
with sample 66. Alex's message 1000022 creates 6,000 Gold; callbacks
1000023..1000024 start mission nine and sample 65. Saved active state uses
1000025, so neither Gold nor the handoff repeats.
Mission nine is a map-discovery assignment, not another group clear. Object 5
in Remains scenario 1040002 calls opcode 17 for Sea of Trees scenario
1000004, entry one. Object 0 there enters Immortal Remains scenario
1050000, entry zero. The destination's status-kind-seven initialization
calls opcode 62 immediately, completing mission nine and queuing sample 66 as
the map loads. Back in town, Alex's messages 1000026..1000028 start mission
ten and sample 65. There is deliberately no Gold or item command in this
handoff; saved active mission ten uses message 1000029.
Mission ten continues through object one in Immortal Remains scenarios
1050000 and 1050001, then reaches the seven-Gargoyle room in 1050002.
Its four ordinary Gargoyles use loot row 55 with a 50-percent Gold branch of
200..300; the three magic variants use the same loot row with guaranteed
600..800 Gold. The periodic sentence scans characters 14000000..14000006
and waits until all seven death presentations have expired. It then hides
object 10011000, shows 10011001 and 10011002, plays positioned sample
34, and completes mission ten with sample 66.
Alex's completed branch opens message 1000030, sets the episode-state flag
to two, and creates category-four definition zero with quantity 10,000. Its
normal landing path plays sample 85. Callback message 1000031 explains the
Tower of Ordeal, and the following callback runs opcode 64 with value zero,
which is the Episode 1 Epilogue presenter. After it closes, flag value two
sends later visits to message 1000033 and sets the separate one-time flag
71, pointing the player toward Mining Town without creating the reward again.
Both flags and the completed mission survive the retail save extension.
Flag 71 also owns the first Episode 2 road. Near Remote Town scenario 1
runs its status-kind-five sentence every update: value zero shows character
10001030 and hides 10001031, while value one reverses them with opcode 56.
Object four only reaches its opcode-17 transition to scenario 2999999, entry
zero, while the flag is one. This keeps the route in script data rather than a
world-scene special case.
Scenario 2999999 is titled Caravan. Its object-one edge enters scenario
2000000, whose object-one edge enters 2000001; object one there enters
2100000, entry zero. Both intermediate scenarios are titled Forest and
select music track one. Caravan's object-two branch shows visual one and
changes flag 71 from zero to two, but that branch is not taken by the normal
post-Epilogue route, which arrives with value one.
Mining Town is titled Kanfore, Mining Town. Its initialization builds vendor
inventory zero from Table 6, inventory one from Table 23, and inventory two
from Table 32, alongside 14 PEOPLE actors including actor 100, Beboba. Object
zero returns to scenario 2000001, entry one. A shipped-data regression now
keeps this whole chain, the no-visual branch, vendor initialization, and the
saved flag/scenario/entry state under the generic interpreter and world
owners.
Kyle, PEOPLE actor zero, owns the first Episode 2 assignment. His first status
branch sets saved flag 23 and runs messages 1000002..1000008; their callback
uses opcode 62 to set mission 11 active and opcode 48 to publish its notice.
Table 41 names it Destroy thieves staying SE of Kanfore. Reopening the
conversation while it is active uses message 1000009 rather than replaying
the briefing.
Mining Town object one enters Forest of Four Leaves scenario 2100001, entry
zero. Object one there enters Forest of Claws scenario 2100002, entry zero.
That map's periodic statuses use opcode 31 to scan two separate ranges. Once
characters 14010000..14010002 finish their death presentation, opcodes 23
hide gate objects 10000700 and 10000701, and positional opcode 16 requests
sample 81. The second scan waits for 14020000..14020002 and completes
mission 11 through opcode 62. All six actors are Oak Knights with Table 30 row
85 and a ten-percent Gold branch.
Kyle's completed branch opens message 1000010 and calls opcode 10 with the
temporary value 20,000 at both quantity bounds. The common Gold constructor
therefore makes two stacks of 10,000, each with its normal landing sample 85.
Messages 1000011..1000012 then start mission 12, whose Table 41 title is
Head for the Mining Tunnel of Yugunos. Later visits use 1000013. Mission
11 completion, mission 12 activation, flag 23, and the no-repeat branch all
survive the retail save owner.
Mission 12 does not immediately open the route to the Mining Tunnel. Mining
Town object one reaches Forest of Four Leaves scenario 2100001, and object
three there leads to Cross Agora scenario 2100004. Garshwin is PEOPLE actor
zero. His default 1000002 text is replaced before presentation when mission
12 is active, so the player sees the 1000003 and 1000004 refusal bubbles.
The same status sets saved flag 24.
Cross Agora object three only runs opcode 17 for Fanann scenario 2200000
when mission 14 is complete. With mission 12 active and mission 14 untouched,
walking into its authored judgement rectangle is a no-op. Back in Mining Town,
Kyle's flag-24 branch runs messages 1000020..1000029, leaves mission 12
active, and uses opcodes 62 and 48 to start and publish mission 13. Table 41
names it Meet with the Wizard Kirushutat. Once that mission is active,
Kyle's ordinary return text is 1000013; flag 24 and both mission states
survive saving and loading.
The route to Kirushutat remains entirely scenario-authored. Cross Agora
object one enters 2100005, whose MCT title is spelled Forest of Sprits in
the shipped data. Object one there enters 2110000, Tower of the Wizard.
The tower contains all ten floors in one scenario; its stair objects move
between entries rather than loading separate maps. Entry 18 reaches the top
floor near PEOPLE actor zero, Kirushutat.
Kirushutat's mission-13 status selects sentence 31. It shows message
1000012, writes temporary state 100, and opcode 62 marks mission 13 complete.
The message callbacks continue through 1000027; the final callback uses
opcode 62 to start mission 14 and opcode 48 to publish its notice. Table 41
names that mission Take back the Seal Crystal. A saved return while mission
14 is active takes message 1000028, so the briefing and its side effects do
not repeat. Native coverage exercises the real route and this complete
handoff through the generic scenario interpreter.
Mission 14 uses the same automatic-item and script-query path as the earlier
Episode 1 keepsakes. Cross Agora object two enters 2100006, Forest of Knight's Misery; object one there enters 2120000, Fort of Thieves. Enemy
65 is the special Oak Warrior with Table 30 row 76. Its guaranteed Table 31
row 403 result is category four, definition 99000003, the Seal Crystal.
Item.Ibn places it at page zero, cell (3,0) in the automatic-item owner.
While mission 14 is active, Kirushutat's sentence 28 uses opcode 58 to search
for category four, definition 99000003. The present branch runs opcode 59,
opens messages 1000029..1000031, and sets mission 14 to state two; the usual
completion sample is queued by opcode 62. The item is gone before the first
return bubble is displayed. A later visit without completed mission 17 uses
message 1000032, so neither the crystal nor the mission cue repeats. Cross
Agora object three can then execute its guarded opcode 17 and enter 2200000,
Fanann, Village of Elves.
Fanann's scenario initialization uses opcode 6 to fill vendor owners zero,
one, and two from Tables 7, 24, and 33. PEOPLE actor zero is Lytle. With saved
flag 41 still zero, his status shows message 1000002; the message resource's
continuations present 1000003 and 1000004, and the status writes flag 41
to one. This does not start a new mission: mission 12 remains active while
mission 14 remains complete. On a later visit, the flag-41 branch selects
message 1000006, so the Yugunos directions are not replayed.
The route remains script-owned as well. Fanann object one runs opcode 17 for
scenario 2200001, entry zero, which the shipped MCT names Butterfly Hill.
Butterfly Hill object one then runs opcode 17 for scenario 2200003, entry
zero, Dragon Road. A native save/load regression follows the opened Cross
Agora gate, initializes the village services, consumes Lytle's exact message
chain, proves the return branch, and walks both exits through the general
interpreter without a town-specific runtime case.
Dragon Road object two continues with opcode 17 to scenario 2210000, entry
zero, Mining Tunnel of Yugunos, B1F. B1F object one enters scenario
2210001, entry zero, Mining Tunnel of Yugunos, B2F. The B2F protection is
not the ordinary floor exit: object two has its own kind-three status. Its
first contact writes saved flag 38, and while saved flag 40 is zero it runs
opcode 17 for the current scenario at entry two, pushing the player away from
the protected section. Mission 12 remains active and mission 15 remains
untouched at this point.
The ordinary return route stays independent of that rejection. B2F object zero enters B1F at entry one, and B1F object zero enters Dragon Road at entry two. Native coverage walks both inward edges, touches the real protection rectangle, confirms the same-scenario relocation, walks both return edges, and round-trips flag 38 plus the Dragon Road entry through the retail save owner.
The later mine route uses the same general pieces. Kirarru starts mission 15
after the first blockade report; the Black Wing's inactive enemy slot
completes it, and her return conversation saves flag 40 so B2F can enter B3F.
The B3F and B5F switches control ordinary script entities, while B5F object
800 saves flag 39. Kirarru's 1000058..1000068 warning advances that flag to
two. Lytle then starts mission 16, whose route ends at the Power Supply Fort.
The named Crimson Sword's inactive slot completes it, and Lytle's return
creates 40,000 Gold before starting mission 17.
Mission 17 also needs no executable-side quest special case. Kirarru's
1000070..1000072 chain prepares the seal. B5F object one checks the active
quest before entering scenario 2210004. That scenario's periodic sentence
uses opcode 31 to read Ancient Dragon character 14010000; only lifecycle
-1, after its full death presentation, reaches opcode 62 and completes the
quest. Object zero returns to the previous B5F entry. Lytle's completed branch
shows 1000018 and writes flag 41 to two, while Kirarru uses 1000073.
Shipped-data coverage follows this whole chain and saves the completed state.
The aftermath also remains script-owned. Lytle's 1000018..1000025 chain
writes flag 41 to two, then uses 1000026..1000027 while mission 12 remains
active. Kyle's corresponding Kanfore branch sees mission 17 complete, uses
opcode 62 {12,2,0} to close mission 12 without a notice, and creates 40,000
Gold through opcode 10 before messages 1000014..1000017. A later visit uses
1000018..1000019 and does not recreate the reward.
Back in Fanann, completed mission 12 selects 1000028..1000029, writes flag
41 to four, and assigns one to type-10 operand 25. That is the persistent
transport domain: Table 40 row 25 names South Camp of Yugunos and maps it to
scenario 3900000, entry 50. The saved repeat is 1000030. Native coverage
round-trips the quest, flag, and transport owners and follows the enabled row
into the real South Camp scenario.
South Camp starts with another script-owned one-time event. Its first periodic
pass sees saved flag 104 at zero, calls opcode 64 with value three, and writes
the flag to one. That is the real Visual03 briefing; it freezes gameplay
until acknowledged and does not turn ordinary map loading into a story page.
Jeel then uses saved flag 74 to keep his 1000002..1000004 introduction
separate from the assignment. The following 1000005..1000006 conversation
starts mission 20, Sweep vicinity of S. Camp of Yugunos., while 1000007
is its active reminder.
The route is made from ordinary overlap triggers. South Camp object zero
enters East Antalusia (3000507), and that map's object one enters
The Foot of Mt. Tedoron (3000407). Its mission sentence scans only enemy
characters 14020000..14020001 with opcode 31. Those are MCT enemies 20000
and 20001, Flame Warrior and Dread Warrior; the hundreds of other enemies on
the map are not part of the clear condition. When both slots finish their
death presentation and become inactive, opcode 62 {20,2,1} completes the
mission. Jeel's return branch plays sample 64, awards 50 percent of the
current level threshold through opcode 68, advances flag 74 to two, and runs
1000008..1000011. Later visits use 1000012 and leave the next request to
Morris. Shipped-data coverage follows the briefing, both conversations, both
map edges, exact enemy range, completion cue, reward, handoff, and no-repeat
branch without adding a South Camp case to the interpreter.
Morris continues that same data-owned story through saved flags 77, 79, and
80. His separate 1000027 remark is followed on the next visit by
1000028..1000033; the latter chain starts mission 21, Get the sacred relic, Sacred Wing. East Antalusia object three enters Tower of Nazzle 1F, where
Bishop Edgar first refuses entry and writes flag 79. Morris then responds with
1000035..1000037, advances flag 77 to three, and opens East Antalusia object
two into the Town of Antalusia. Berini's 1000002..1000008 response writes
flag 80, after which Edgar's 1000002..1000004 branch advances flag 79 and
opens the tower stair.
The tower climb remains entirely authored. Objects one and zero link floors
one through five in opposite directions. Floors 2F, 3F, and 4F use opcode 31
over enemy ranges 14000000..14000024, 14000000..14000024, and
14000000..14000028; only after all 25, 25, and 29 slots respectively become
inactive do opcodes 22 and 23 swap each closed gate for its open state. On 5F,
MCT enemy zero is a Dark Golem with Table 30 loot row 154. That row has a
100-percent fixed profile for category four definition 99000005, Sacred
Wing, whose automatic-item location is page zero at cell (5,0).
Berini's flag-80 branch uses opcode 58 to find that exact item and opcode 59
to remove it. Opcode 62 completes mission 21, opcode 68 awards 50 percent of
the current level threshold, sample 64 plays with the reward, and
1000010..1000016 grants Giant Warehouse III through type-13 index two.
Mission state, all three conversation flags, the removed relic, experience,
and the warehouse unlock survive saving; the next 1000017 branch does not
repeat the reward. Native coverage follows both directions through every map
edge and the complete gate sequence.
Opcode 25 is the other half of that lifecycle. Its four evaluated operands are absolute enemy character number, world X, world Y, and direction. The native owner refuses to mutate an already-living enemy, but the script command still returns successfully. An inactive slot is moved without changing its authored spawn rectangle, restored to maximum life, reset to idle action 7, made fully opaque, and cleared of old AI, movement, reaction, damage-attribution, and death state. The slot is not recreated: expired MCT enemies stay in their original vector position so later script lookups keep stable identities.
Opcode 28 evaluates one character number and runs that character's status-kind six sentence inline through the normal frame stack. Nested calls carry the target character context, and returning restores the caller's context. A target without kind six is a successful no-op. The shipped scripts contain 181 calls across 32 scenarios, and every target has a matching kind-six status.
Scenario 04000003 shows how the pieces fit. Its periodic sentence uses a
type-three read for controller enemy 14030000, opcode 32 to choose an expired
slot from 14030001 through 14030005, and opcode 28 to create effects 20007
and 20008 plus positional sample 27. Forty updates later, opcode 25 activates
the chosen slot at object 10030000 facing direction 7. Native coverage runs
this shipped controller end to end and proves the expired slot remains present,
shows the wave effect, and returns at full life.
This is intentionally a narrow vertical slice. The messages use the
actor-anchored retail speech frame from Hukidasi.njp: its size comes from
the Shift-JIS-aware 6-by-12 Font01.njp text layout, and the tail follows
Ostare's live screen position. Ground-item state is created faithfully. The
executable-owned Item.Ibn loader keeps the inventory icon fields separate
from the ground resource and CAF chart fields. The four drops load their real
Character/ITEM animation and SDW shadow and share the normal depth-sorted
world pass with actors and scenery. Their CAF-selected palettes and
Item.Ibn RGB strengths also reproduce the default ground colors. They now
participate in opaque-pixel pointer selection and the common interaction-range
approach too. A successful pickup moves the concrete category, definition,
and quantity into PlayerInventory; that owner now feeds the live 9-by-4
inventory panel and retail save stream.
Switch progress is a script-owned interaction, not a hard-coded map feature.
The status-zero sentence first uses operand type 9 to confirm that the idle
hero has actually reached the displayed switch actor. The authored arithmetic
then computes the progress value before opcode 26 draws it as centered decimal
text at the actor, with a one-pixel black shadow and no rectangle behind it.
Its remaining operands provide the world offsets and RGB strengths.
Opcode 60 refreshes the player's transient switch marker for that update.
0x00434ef0 draws Player/Common/UnlockSW as chart zero, direction eight,
using the player's action counter and full RGB strengths. The player update
clears the marker before each status-kind-five pass, so the periodic script
must keep refreshing it while the switch sequence is active.
Opcode 29 is the multiplayer counterpart. Its handler sends packet 0x22
with event kind six only when the executable is a network client. The shipped
scripts select it behind a play-mode branch; local single player reaches
opcode 28 instead. The portable single-player host therefore accepts opcode
29 without inventing local state.
This family appears consistently in the shipped data: opcodes 26 and 60 each have 60 calls across 22 scenarios, while opcode 29 has 61 calls across 23. The scenario audit fixes their operand shapes and actor distribution so later switches keep using the same general interpreter and world presentation.
Opcode 64 is the script owner behind the presenter at 0x00417bd0. Its one
evaluated value selects Waiting.njp pattern 4 for value zero or
Visual%02d.njp for a nonzero value. The page starts fully dark, reaches full
strength over 120 presented frames, and does not accept an advance until frame
300. Return, Escape, and the primary mouse button all use the same advance
path. While a page owns the screen, retail freezes the current player action
in place rather than cancelling it; that action resumes when the presentation
ends. A resource with more than one pattern starts its next page at counter
one; the final acknowledgement releases the resource and returns control to
the world.
The shipped scripts call opcode 64 seven times across six scenarios, using
each value from zero through six exactly once. Visual02 is the only shipped
two-page resource. These are story, briefing, and selection pages, not map
loading screens, so normal scenario transitions still show only the
crossed-swords loading artwork while work is actually pending.
Opcode 65 is a separate screen-space particle command. The first three
operands are RGB bytes and the fourth is the number of new streaks for that
update. Each streak consumes five values from the executable's shared random
stream for its X origin, speed, short line length, opacity, and angle. It
starts at Y -30, moves down with the retail trigonometric projection, draws
through the DIB-style line path, and expires when its leading point reaches Y
479. The command only refreshes one frame's spawn request; periodic status
kind five calls are what keep rain, snow, or colored ambience going.
There are 22 shipped opcode-65 calls across 21 scenarios. Twenty request five literal streaks. The other two calculate a temporary count from distance. The audited colors are eight red, twelve white, one pale red, and one blue. The interpreter evaluates those operands, while the world owns particles and GAPI owns line drawing.
Each new script slice should start with a real retail interaction:
- identify the status trigger and starting sentence;
- trace every sentence and opcode that the path can reach;
- compare the command handlers and operand accessors with the executable;
- implement only the missing general behavior, in the correct boundary;
- preserve waits and asynchronous actor actions;
- test the path with the original SCS data;
- update this document with the new evidence.
Script-owned dialogue, quest branches, rewards, actor IDs, and service behavior
must not be copied into WorldScene, a renderer, or a state class. Native game
services should be small general hooks, and reusable behavior derived from a
DLL must remain under its matching SF_EXE/libs/ directory.
The same rule applies to AI: actor decision tables belong to the future
portable RKC_RPG_AICONTROL boundary, while their native action handlers
belong to the relevant executable-owned actor or combat system. MCT-owned
placement and movement bounds should remain scenario data.
The next useful script work should keep following real scenario interactions and combat rather than adding commands in isolation. That should naturally reveal more of:
- persistent flag initialization and save-game restoration;
- message control bytes, portraits, speaker metadata, and alternate message frame modes;
- keyboard movement between message choices;
- waits for movement and animation completion;
- later warps, shops, rewards, and quest-log actions;
- remaining operand domains and the rest of the opcode switch;
- multiplayer and network flag behavior.
The goal is not merely to make Remote Town look scripted. It is to recover a general interpreter that can eventually run all of Remote Town, then every scenario in the original game from its own data.