Files
Sylpheed/docs/re/structures/isl-trigger-node.md
Sylpheed RE agent bee8d96394 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.
2026-08-27 07:30:41 +00:00

98 lines
4.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ✅ 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.