re: decode the trigger node -- and withdraw two of my own claims about it

The reader is sub_8226E220, called by the per-frame drain sub_8226D740 as
(phase+272, six out-params).  It pops one node and copies seven payload fields out.
Built-in 25, the appender, writes exactly those seven offsets:

    +0   local[4]  -- the unit          +24  the constant 1
    +4   local[12]                      +28  local[12] again
    +8   computed                       +32  --
    +16  a DOUBLE from local[16]

Neither side was derived from the other, so the agreement is the check.  Container:
+16 head, +20 pending count -- which matches the count isl-builtins.md watched live at
phase+272+20 from a completely different direction -- and +24 a cursor.

WITHDRAWN, from the previous commit: "the drain spawns from [node+112]".  r31 = r1 -
256, the stack frame, so +112 is an output slot and not a node field.

REFUTED, the follow-up hypothesis that the trigger carries the routine offset and so
names the code nothing else starts: payload+28 is local[12], and across all 25 call
sites disc-wide those values are the small integers 1 through 12 -- 1 of 25 (4.0%)
land on the instruction stream against a 16.0% control, and none are unreached
run-starts.  Below chance.  My first version of that test used n=2, Stage 02 only; it
happened to agree, but two samples could not have supported it either way.

One correction in the other direction, to the corpus: isl-builtins.md withdrew
sub_8226E458's link to the trigger queue on the grounds that "the argument is
lwz r4, 324(r29), the unit array, not the trigger container".  That is the SECOND
argument; the first is r26 = phase + 272, set twelve instructions earlier.  The drain
does operate on the container.  What sub_8226E458 does to it remains unread, so only
the argument is corrected, not the conclusion.

All artefacts regenerate byte-identical; documentation only.

Still open: what local[12] indexes; what the drain actually spawns, since [stack+112]
is filled from payload+28 yet the spawner wants a code offset, so some step in that
chain is not what I read; and what starts the ~15% of unreached code.
This commit is contained in:
Sylpheed RE agent
2026-08-27 07:30:41 +00:00
parent a3cb3339bc
commit 9ee32f7940
3 changed files with 123 additions and 5 deletions

View File

@@ -98,6 +98,27 @@ unknown, what evidence exists, and what the first step would be. Move an item in
the trigger queue at `phase+272`; that is the exact over-reach the corpus already
withdrew once, so it is not asserted.
* ✅🔴 **(2026-08-27) THE TRIGGER NODE IS DECODED — and two of my own claims are
withdrawn. [structures/isl-trigger-node](structures/isl-trigger-node.md).**
The reader is `sub_8226E220`, called by the drain `sub_8226D740` as
`(phase+272, …6 out-params)`. **Writer (built-in 25) and reader touch the same
seven payload offsets** — +0 `local[4]` (the unit), +4 and +28 `local[12]`, +8
computed, +16 a **double** from `local[16]`, +24 the constant 1, +32 — neither
derived from the other. Container: +16 head, **+20 pending count** (matching the
count `isl-builtins.md` watched live at `phase+272+20`), +24 cursor.
🔴 **WITHDRAWN: "the drain spawns from `[node+112]`"**`r31 = r1 256`, the
STACK FRAME; `+112` is an output slot.
🔴 **REFUTED: the trigger carries a routine offset.** `payload+28` = `local[12]`,
and across **all 25 sites disc-wide** those are the small integers **1…12**:
**1/25 = 4.0 % land on the instruction stream vs a 16.0 % control**, 0 are
unreached run-starts. ⚠️ My first version of that test was **n=2** (Stage 02
only) — same answer, but unsupportable either way.
**And one correction TO the corpus:** `isl-builtins.md` withdrew
`sub_8226E458`'s link to the trigger queue by reading its SECOND argument
(`[phase+324]`); the FIRST is `r26 = phase + 272`, set twelve instructions
earlier. The drain does operate on the container — what `sub_8226E458` does to
it stays unread. 🟡 What starts the ~15 % of unreached code: still open, not this.
## ✅✅ SOLVED — the mission freeze was a modal sign-in dialog (2026-08-26)
`XamShowSigninUI` opens a modal dialog and `xeXamDispatchDialog` blocks the

View File

@@ -35,7 +35,7 @@ So the record reads `0x1883, base, MAIN_ENTRY, 0, code_end, force_end_handler`.
among the CFG's entry points by another route — so this corrects the *meaning*
of a field without moving any coverage number.
## 🔑 The drain spawns from `[node+112]`
## 🔴 WITHDRAWN: "the drain spawns from `[node+112]`"
The two calls inside `sub_8226D740` both do
@@ -45,10 +45,10 @@ lwz r4, 232(r29) ; the phase's code base
bl 0x822737C8
```
`isl-builtins.md` describes `sub_8226D740` as the per-frame engine→script drain,
and here it is spawning routines from a **code offset carried in the drained
node at `+112`**. That is the first field of a trigger-style node to be located,
and it is the most promising remaining lead on the routines nothing else starts.
**`r31 = r1 - 256`** — it is the **stack frame**, so `+112` is an output slot,
not a node field. See [structures/isl-trigger-node](structures/isl-trigger-node.md),
which reads the node properly and also refutes the follow-up hypothesis that the
trigger carries a routine offset.
## 🟡 Not settled

View File

@@ -0,0 +1,97 @@
# ✅ The trigger node's layout — from a matched writer/reader pair
[isl-trigger-queue](../isl-trigger-queue.md) located the *appender* (built-in 25).
The node's contents are now read, because the **reader** was found too and the two
agree field for field.
## The reader: `sub_8226E220`
`sub_8226D740` — the per-frame engine→script drain — calls it as
```
8226D758 addi r26, r29, 272 ; r29 = the phase, so r26 = the TRIGGER CONTAINER
8226D79C or r3, r26, r26
8226D7A0 bl 0x8226E220 ; (container, &out88, &out84, &out80,
; &out120, &out92, &out112)
```
and it pops one node:
```
8226E228 lwz r10, 20(r11) ; the PENDING COUNT -- return 0 if empty
8226E244 lwz r10, 16(r11) ; the list head
8226E250 stw r10, 24(r11) ; a cursor
8226E254 lwz r11, 8(r10) ; -> the node PAYLOAD
```
## ✅ The layout, confirmed from both ends
| payload | written by built-in 25 | read by `sub_8226E220` | source |
|---|---|---|---|
| **+0** | `stw r26, 0(r30)` | → out88 | `local[4]` — the unit |
| **+4** | `stw r28, 4(r30)` | → out84 | `local[12]` |
| **+8** | `stw r27, 8(r30)` | → out80 | computed |
| **+16** | `stfd f31, 16(r30)` | → out120 (a **double**) | `local[16]` |
| **+24** | `stw r25, 24(r30)` | → out92 | the constant 1 |
| **+28** | `stw r28, 28(r30)` | → out112 | `local[12]` again |
| **+32** | `stw r11, 32(r30)` | → out116 | — |
| +36 | `stw r23, 36(r30)` | *not read here* | — |
Container: **+16** head, **+20** pending count, **+24** cursor. The `+20` matches
the count `isl-builtins.md` watched live at `phase+272+20`, from a completely
different direction.
That the writer and the reader touch the same seven offsets, in the same order,
is the check — neither was derived from the other.
`payload+0` being `local[4]` also agrees with `UNIT_ARG`, which lists built-in 25
and was derived from operand ranges.
## 🔴 Two corrections, both mine
### `[node+112]` does not exist
[isl-coroutine-spawner](../isl-coroutine-spawner.md) recorded the drain's spawns
as taking their offset from `[node+112]`. **`r31 = r1 - 256`** — it is the
**stack frame**, so `+112` is an output slot, not a node field. Withdrawn.
### And the trigger does NOT carry a routine offset
The follow-up hypothesis — that `payload+28` reaches the spawner and so names the
routines nothing else starts — does not survive. `payload+28` is `local[12]`, and
across **all 25 call sites disc-wide** those values are the small integers
**1 … 12**:
| | |
|---|---|
| land on the instruction stream | **1 / 25 = 4.0 %** |
| control, random 4-aligned offsets | 16.0 % |
| are unreached run-starts | **0** |
Below chance. They are indices of some kind, not code offsets.
⚠️ My first version of this test used **n = 2** — Stage 02 only. It happened to
give the same answer, but two samples could not have supported it either way.
## ✅ One correction to the corpus, in the other direction
`isl-builtins.md` withdrew the idea that `sub_8226E458` touches the trigger
queue, reasoning: *"At `0x8226D780` the argument is `lwz r4, 324(r29)`
`[ScriptPhase+324]`, the unit array, not the trigger container."*
The call is `sub_8226E458(r3 = r26, r4 = [phase+324])`, and **`r26` was set to
`phase + 272` twelve instructions earlier**. The withdrawal read the *second*
argument. `sub_8226D740` does operate on the trigger container; what
`sub_8226E458` does *to* it is still unread, so only the argument is corrected
here, not the conclusion.
## 🟡 Not settled
* **What `local[12]` indexes.** Values 112, and `SYM1_SLOTS` lists slot 12 for
built-in 25 (symbol table 1 — routes, messages, objectives), which is
consistent with a small index but was not verified here.
* **What the drain actually spawns.** `[stack+112]` is filled from `payload+28`,
yet the spawner takes a code offset — so some step in that chain is not what I
read. Left open rather than guessed.
* **`sub_8226E458`'s effect** on the container.
* **What starts the ~15 % of unreached code** — still open, and this was not it.