This repository has been archived on 2026-09-16. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Syplheed-Reborn/docs/re/structures/isl-bytecode.md
Sylpheed RE agent 7b445916bc re: ISL bytecode encoding decoded; phase-end call sites located in Stage02
Read the encoding off the interpreter rather than guessing: instruction is a
big-endian u32 whose LOW byte is the opcode (25 of them, table 0x822635FC),
byte[2] is the instruction length -- every handler advances the pc by it -- and
bytes[0..1] are operand kinds. Op 12 is a jump whose operand is relative to the
code base [phase+232], which settles that offsets are code-base-relative for
this opcode. Op 19 is the built-in call: id in word@+4, and word@+8 is a
monotonically increasing STATEMENT id (0x245, 0x248, 0x24A, ...).

Confirmed by disassembling Stage02.ssb: the stream decodes cleanly from the code
base and routines terminate on ret exactly where expected.

Scanning the code region on the call encoding: 2846 call sites, 73 of the 147
built-ins used. The phase-control ones are located -- built-in 6 (end phase) at
12 sites, 62 at 3, 39 (mark last phase) at 8 -- so a phase has several exit
paths, as a mission with win and lose branches should.

New tool tools/re-capture/isl.py with --calls and --to (resync-into-target,
needed because instructions are variable-length so you cannot walk backwards).

Not settled: the 147 built-ins are uncharacterised, so this is structure without
meaning -- we can see THAT a phase ends, not WHAT was tested.
2026-08-25 12:21:44 +00:00

4.8 KiB
Raw Blame History

The ISL script bytecode — instruction encoding decoded

Status: the encoding, the 25-opcode table and the call form, read off the interpreter and confirmed by disassembling Stage02.ssb; 🟡 most opcode semantics are named only by their handler; the 147 built-ins are not yet characterised.

Follows mission-script-ssb (where the scripts live) and mission-phase-advance (why they matter). Tool: tools/re-capture/isl.py.

Encoding

ScriptPhase::Update (sub_82263408) fetches one big-endian u32 per instruction:

0x822635D4  lwz    r11,0(r31)     ; the instruction word
0x822635D8  clrlwi r4,r11,24      ; OPCODE = the LOW byte
0x822635DC  cmplwi 0x18           ; 25 opcodes
0x822635FC  jump table, 25 absolute VAs

Every handler advances the pc with lbz r11,2(r31); add r31,r11,r31, so:

byte 0 1 2 3
meaning operand kind A operand kind B instruction length in bytes opcode

Operand words follow. The common form is 12 bytes (opcode word + two operands). Operand kinds go through resolvers with their own 4-entry table (sub_82271D40 for integers, cmplwi 0x3), so there are 4 operand kinds.

The opcode table

op handler what the handler does
0 82263660 integer assign — resolve rvalue (82271D40, kind byte[0], word@+8), resolve lvalue (82272030, kind byte[1], word@+4), stw
1 8226369C float assign — same shape with 82271F10/82272120 and stfd
2,4,6,8 822636D0 822713E8 (a compare/branch family; four opcodes share one handler)
3,5,7,9 822636E4 822714D0 (the sibling family)
10 822636F8 82271598
11 8226370C 822716E0
12 82263720 JUMPr31 = [phase+232] + word@+4
1318 82263738 82271830, 822718C8, 82271960, 822719F8, 82271AC8, 82271B60
19 822637B0 CALL BUILT-INsub_82272220
20 82263874 li r29,1 then the suspend path — yield / return
21 822637C4 sub_82175C20(phase+44, phase+168)
22 822637E4 sub_82274BA0(phase+64, phase+184)
23,24 82263804 82271C30, 82271CB8

Handler return codes drive the outer loop at 0x82263828: 0 continue, 1 suspend, 2/3 other exits.

Jump operands are code-base-relative

Op 12 adds its operand to [phase+232], the code base — i.e. the .ssb header's code offset (0x24 in every file). That settles, for this opcode, the question mission-script-ssb.md left open about whether offsets are file- or code-base-relative.

The call form, and a statement counter

sub_82272220 reads the built-in id from word@+4 (cmplwi 0x92 → 147 built-ins, table 0x8227226C) and stores word@+8 into [phase+200].

That second word turns out to be a monotonically increasing statement id0x245, 0x248, 0x24A, 0x24B, 0x24C, 0x24D, 0x24E, 0x252… along a routine. It is a source-position counter, presumably for the script's own error traces.

It decodes — Stage 02

Disassembling from the code base runs cleanly, and routines terminate on ret (op 20) exactly where expected. Data in data/isl-stage02.txt.

Scanning the whole code region for the call encoding:

2846 call sites, 73 distinct built-ins used (of 147)
most used: 11 (×372), 69 (×255), 1 (×216), 64 (×213), 30 (×179), 20 (×167)

The phase-control built-ins, located:

built-in meaning sites in Stage02
6 end phase ([ScriptPhase+196] = 1) 12
62 force-end, skipping the end event 3
39 mark last phase ([phase+300] = 2) 8
40 [phase+300] = 1 4

Twelve end-phase sites across three phases — so a phase has several exit paths, which is what a mission with win and lose branches should look like.

Argument passing is visible in the disassembly: pairs of set.i k=01,02 <0> <value> / set.i k=02,03 <slot> <0> stage arguments into slots, then call. Floats are staged the same way — e.g. 40080000 = 3.0 immediately before several calls.

What this does not settle

  • The 147 built-ins are uncharacterised. Without them the disassembly is structure without meaning: we can see that a phase ends here, not what was tested. That is the remaining step to per-phase clear conditions.
  • Opcodes 211 and 1318 are named only by handler address. The four-way sharing (2/4/6/8 and 3/5/7/9) suggests the handler re-reads the opcode to pick a comparison or a type, but that is not yet read.
  • Operand kinds (4 of them) are not decoded — the k=01,02 / k=02,03 pairs are recorded literally.
  • The mission-level stream at +0x24 of a .ssb — as opposed to this ISL stream — is still only partly read.