re: recover the ISL interpreter's command table — 57 opcodes, committed

sub_822FE040 fills 1023 eight-byte slots at table+32 with a default and then
overwrites individual ones; slot = (N - 32) / 8 from each std r9, N(r31).
Symbolically executing it yields 57 populated slots, matching the count the
corpus recorded, now with the full opcode -> handler map committed as
docs/re/data/isl-command-table.txt and regenerable from
tools/re-capture/isl_cmdtab.py.

Nine opcodes point at 0x82391BA8, which is `li r3,1 ; blr` -- accept and
discard. 768, 769, 774, 775, 776, 791, 792, 793 and 805 are dead in this
build, which is why the built-ins posting them do nothing.

Opcodes 800-802's entries are thunks 8 bytes apart into 60-instruction
handlers that differ in exactly two words: a descriptor offset and a unit
message id.

  800  builtin 26        0xED0802DE
  801  builtin 28        0xED0803DE
  802  builtin 29, 101   0xED0804DE

That fixes the id format as 0xED08 nn DE, and the ids known from other work
fit it: opcode 514 -> 00DE, 803 -> 07DE, 999 -> 0FDE.

Stopped one link short of the semantics, and saying so: the pump's arm for
0xED0802DE does not apply an effect. It walks the unit's child list at
[unit+320]/[unit+324] and REBROADCASTS to each child as 0xED0902DE. So 0xED08
is the to-unit family and 0xED09 the to-child one, and the terminal effect is
further on. 26/28/29 remain unnamed.

The command table is the reusable part -- it answers "what does this opcode
reach" for every future built-in question, not just this family.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
This commit is contained in:
Sylpheed RE agent
2026-08-26 01:44:49 +00:00
parent 8d1c9d1c77
commit 1707b6b74a
3 changed files with 154 additions and 0 deletions

View File

@@ -0,0 +1,69 @@
# The ISL interpreter's command table — opcode -> handler
#
# Recovered by symbolically executing `sub_822FE040`, which fills 1023 eight-byte
# slots at `table+32` with a default and then overwrites individual ones. Slot
# index = (N - 32) / 8 from each `std r9, N(r31)`.
#
# Regenerate with `tools/re-capture/isl_cmdtab.py`.
#
# `82391BA8` is `li r3,1 ; blr` — an accept-and-discard stub. The opcodes bound
# to it are dead in this build: 768, 769, 774, 775, 776, 791, 792, 793, 805.
#
populated slots: 57
opcode 256 -> 822FE8D0
opcode 512 -> 822FE8D8
opcode 513 -> 822FE9D8
opcode 514 -> 822FE9C0
opcode 515 -> 822FE9C8
opcode 516 -> 822FE9D0
opcode 517 -> 822FE8E0
opcode 518 -> 822FE8E8
opcode 519 -> 822FE9A0
opcode 520 -> 822FF4C8
opcode 768 -> 82391BA8
opcode 769 -> 82391BA8
opcode 772 -> 822FEDE0
opcode 774 -> 82391BA8
opcode 775 -> 82391BA8
opcode 776 -> 82391BA8
opcode 784 -> 822FEE28
opcode 785 -> 822FEF00
opcode 786 -> 822FEE38
opcode 787 -> 822FEE30
opcode 788 -> 822FEFB8
opcode 789 -> 822FF060
opcode 790 -> 822FF110
opcode 791 -> 82391BA8
opcode 792 -> 82391BA8
opcode 793 -> 82391BA8
opcode 800 -> 822FF118
opcode 801 -> 822FF120
opcode 802 -> 822FF128
opcode 803 -> 822FF130
opcode 804 -> 822FF1B8
opcode 805 -> 82391BA8
opcode 806 -> 822FF218
opcode 807 -> 822FF220
opcode 808 -> 822FF138
opcode 816 -> 822FECC8
opcode 817 -> 822FED10
opcode 896 -> 822FF228
opcode 897 -> 822FF280
opcode 992 -> 822FF2D8
opcode 993 -> 822FEDB0
opcode 994 -> 822FEDC8
opcode 995 -> 822FF330
opcode 996 -> 822FF3B8
opcode 997 -> 822FF468
opcode 998 -> 822FF4A8
opcode 999 -> 822FF4C0
opcode 1008 -> 822FE9A8
opcode 1009 -> 822FEA80
opcode 1010 -> 822FEB10
opcode 1011 -> 822FEB40
opcode 1012 -> 822FEBB0
opcode 1013 -> 822FEBB8
opcode 1014 -> 822FEBD0
opcode 1015 -> 822FEC20
opcode 1016 -> 822FEB70
opcode 1017 -> 822FEB98

View File

@@ -140,6 +140,39 @@ and spread checks but its maximum *exceeds* the table — flag indices run 0..31
against symbol tables as small as 40 — so it is excluded, and the disassembler
now declines to resolve it rather than printing an invented name.
### ✅ The interpreter's command table, recovered
`sub_822FE040` fills 1023 eight-byte slots at `table+32` with a default and then
overwrites individual ones; slot = `(N - 32) / 8` from each `std r9, N(r31)`.
Symbolically executing it yields **57 populated slots** — the full opcode →
handler map, committed as [`data/isl-command-table.txt`](../data/isl-command-table.txt)
and regenerable with `tools/re-capture/isl_cmdtab.py`.
Nine opcodes point at `0x82391BA8`, which is `li r3,1 ; blr` — accept-and-discard.
**768, 769, 774, 775, 776, 791, 792, 793 and 805 are dead in this build**, which
is why the built-ins that post them do nothing.
### ✅ Opcodes 800802 send unit messages `0xED08 nn DE`
Their table entries are thunks 8 bytes apart into handlers `0x823008C8`,
`0x823009B8`, `0x82300AA8` — each 60 instructions, and **differing in exactly two
words**: a descriptor offset and the message id.
| opcode | built-in | unit message |
|---|---|---|
| 800 | 26 | **`0xED0802DE`** |
| 801 | **28** | **`0xED0803DE`** |
| 802 | **29**, 101 | **`0xED0804DE`** |
That fixes the id format as `0xED08 nn DE`, and the ids already known from other
work fit it: opcode 514 → `00DE`, 803 → `07DE`, 999 → `0FDE`.
🟡 The pump's arm for `0xED0802DE` does **not** apply an effect — it walks the
unit's child list at `[unit+320]`/`[unit+324]` and **rebroadcasts** to each child
as `0xED0902DE`. So `0xED08…` is the to-unit family and `0xED09…` the to-child
one, and the terminal effect is one link further on. ❌ Not followed; 26/28/29
remain unnamed.
### 🟡 Built-ins 26 / 28 / 29 are one family — and `damage_unit` looks mis-named
Method-diffing put the structure beyond doubt but did not reach the semantics.