isl-builtins.md carried the trigger queue at phase+272 a long way -- layout, live
count at +272+20, the generic appender sub_8226EAB8 -- and stopped at "what actually
appends a node is still unidentified", proposing a gdb watchpoint on a running
emulator. It is a static question after all.
sub_8226EAB8 is a shared helper with 16 callers, so it cannot be identified by name.
But the corpus records that the trigger container EMBEDS its inner list at +12, so the
appender must hand it container+12. Only two of the sixteen callers do, and one sets
its base 34 instructions earlier:
8226A22C addi r29, r24, 272 ; r24 = the ScriptPhase
8226A2B4 addi r3, r29, 12 ; -> phase + 272 + 12
bl 0x8226EAB8
phase+272+12 is exactly the inner list whose count the corpus watched at +272+20.
sub_8226A0D8 is ScriptPhase vtable SLOT 28, which is what BUILT-IN 25's stub
tail-calls, and there are ZERO real `bl 0x8226A0D8` instructions in the image -- it is
reached only through the vtable. So the queue is populated by the SCRIPT, retiring
the residual "the engine moves records into the phase's queue each frame" reading that
isl-builtins.md had already flagged as unsupported by its own call site. Built-in 25's
arguments agree with two independently measured tables: UNIT_ARG (a unit at local[4])
and SYM1_SLOTS[12] (a symtab-1 name at local[12]).
REFUTED in the same pass, with a control: built-in 25 does NOT carry the coroutine
entry that a trigger fires, which would have seeded the 389 routines reachable from no
static entry. Taking each staged operand as a code offset from the phase base, 0 of 8
land on an instruction boundary, against 38.7% for an arbitrary 4-aligned offset --
below chance, not above it. And the scale is wrong regardless: built-in 25 has only
2 call sites in Stage 02.
So what starts the unreachable routines is still open, and it is not this. A trigger
node's own layout and its consumer are unread, and built-in 25 stays unnamed.
82 lines
3.3 KiB
Markdown
82 lines
3.3 KiB
Markdown
# ✅ What appends to the trigger queue — built-in 25, not the engine
|
|
|
|
[`structures/isl-builtins.md`](structures/isl-builtins.md) took the trigger queue
|
|
at `phase+272` a long way — its layout, its live count at `+272+20`, and the
|
|
generic appender `sub_8226EAB8` — and stopped at: *"What actually appends a node
|
|
is still unidentified."* Its suggested next step was a **gdb watchpoint** on a
|
|
running emulator.
|
|
|
|
It is a static question after all.
|
|
|
|
## The search that works
|
|
|
|
`sub_8226EAB8` is a shared list helper with 16 callers, so it cannot be
|
|
identified by name. But the corpus records that the trigger container **embeds
|
|
its inner list at `+12`**, so the caller must hand it `container + 12`. Two of
|
|
the 16 do:
|
|
|
|
```
|
|
0x8226E208 addi r3, r30, 12
|
|
0x8226A2B4 addi r3, r29, 12
|
|
```
|
|
|
|
and in the second, `r29` is set 34 instructions earlier:
|
|
|
|
```
|
|
8226A22C addi r29, r24, 272 ; r24 = the ScriptPhase
|
|
8226A2B4 addi r3, r29, 12 ; -> phase + 272 + 12
|
|
bl 0x8226EAB8 ; the generic append
|
|
```
|
|
|
|
`phase + 272 + 12` is exactly the inner list whose count the corpus watched at
|
|
`phase + 272 + 20`. **`sub_8226A0D8` is the appender.**
|
|
|
|
## 🔑 And it is a BUILT-IN, so the script appends its own triggers
|
|
|
|
`sub_8226A0D8` is **ScriptPhase vtable slot 28**, and slot 28 is what
|
|
**built-in 25**'s stub tail-calls. Its entry is the standard built-in shape —
|
|
`r3` = the phase, `r4` = the `local[]` base, `[phase+324]` indexed by `local[4]`.
|
|
|
|
**There are ZERO real `bl 0x8226A0D8` instructions in the image.** It is reached
|
|
only through the vtable, i.e. only as a built-in. So the queue is populated by
|
|
the **script**, not by the engine — which retires the residual "the engine moves
|
|
records into the phase's queue each frame" reading that `isl-builtins.md` already
|
|
flagged as unsupported by its call site.
|
|
|
|
Built-in 25's argument shape agrees with the corpus's independently measured
|
|
tables: it is in `UNIT_ARG` (a unit at `local[4]`) and in `SYM1_SLOTS[12]` (a
|
|
symbol-table-1 name — a route, message or objective — at `local[12]`).
|
|
|
|
## 🔴 REFUTED: this does not explain the unreachable routines
|
|
|
|
The attractive next step was that built-in 25 carries the coroutine entry a
|
|
trigger fires, which would have seeded the 389 routines reachable from no static
|
|
entry. It does not.
|
|
|
|
At Stage 02's built-in 25 sites, taking each staged operand as a code offset from
|
|
the phase base:
|
|
|
|
| staged slot | values | landing on an instruction boundary |
|
|
|---|---|---|
|
|
| 0 | 2 | **0 — 0 %** |
|
|
| 4 | 2 | **0 — 0 %** |
|
|
| 8 | 2 | **0 — 0 %** |
|
|
| 12 | 2 | **0 — 0 %** |
|
|
| *control:* an arbitrary 4-aligned offset | — | **38.7 %** |
|
|
|
|
Nothing scores even at chance. And the scale is wrong anyway: built-in 25 has
|
|
**only 2 call sites in Stage 02** — far too few to account for ~15 % of the code
|
|
being unreachable.
|
|
|
|
So the trigger queue is now identified, but **what starts the unreachable
|
|
routines remains open**, and it is not this.
|
|
|
|
## 🟡 Not settled
|
|
|
|
* **What a trigger node contains**, and what consumes it. The append is located;
|
|
the node's own layout is not read.
|
|
* **The other `+12` caller**, `0x8226E208` in `sub_8226E160`, appends to some
|
|
other embedded list — not shown to be the trigger queue.
|
|
* **Built-in 25 is still unnamed.** "Appends to the trigger container" is what it
|
|
does mechanically; what the trigger *means* needs the consumer.
|