re: trigger queue layout at phase+272, and a readable pending count

Chasing what makes the phase-1 condition re-evaluate, since the polls do not run
continuously.

Two method corrections: searching the VM range for '272(rN)' mostly returns
VTABLE slot offsets -- 0x82273174 lwz r11,272(r11) is followed by mtctr/bctrl,
a virtual call through slot 68, not an access to the phase field. And
[phase+272] is not a pointer to a queue but an EMBEDDED container: vt2
(sub_82265DD0) is 'addi r3,r3,272 ; b 0x8226E3B8', passing phase+272 as this.

Layout from the push/pop pair (sub_8226E3B8 from built-in 100, sub_8226E220
called every frame from sub_8226D740): +12 list head, +16 current node, +20
element count (zero = empty, tested first by the pop), +24 scratch. The pop
returns the record through out-parameters read from node+8: three u32s, a
double at +16, another u32 at +24 -- matching the six pointers sub_8226D740
passes in.

The actionable part is [phase+272+20], a live pending-trigger count readable
from /dev/shm. Watching it alongside [ScriptMission+40] should show when the
engine hands the script an event, which is when condition coroutines start --
the thing every phase experiment so far has been blind to.

Layout is from disassembly only; not yet verified live.
This commit is contained in:
Sylpheed RE agent
2026-08-25 17:54:34 +00:00
parent 75ff2878f7
commit 0b83bf84bc

View File

@@ -230,3 +230,44 @@ facts — the *identifications* (which built-in does what) have held up well.
slots, by simulating the constant/stack dataflow of `sub_822FE040`.
* Names here are from handler behaviour, not from symbols; `isl.py` prints a bare
`builtinN` for anything unread rather than guessing.
## ✅ The trigger queue at `phase+272` — layout, and a readable pending count
Chasing what makes the phase-1 condition re-evaluate (the polls do **not** run
continuously — see [script-runtime-probe](../script-runtime-probe.md)).
**Two method corrections first**, because both nearly sent me the wrong way:
* Searching the VM's address range for `272(rN)` returns mostly **vtable slot
offsets**, not accesses to the phase field. `0x82273174 lwz r11,272(r11)` is
followed by `mtctr; bctrl` — it is a virtual call through slot 68, nothing to
do with `[phase+272]`.
* `[phase+272]` is **not a pointer to a queue** — it is an **embedded container**.
`vt2` (`sub_82265DD0`) is literally `addi r3,r3,272 ; b 0x8226E3B8`, i.e. it
passes `phase+272` as `this` into the push.
### Container layout, from the push/pop pair
`sub_8226E3B8` (push, reached from built-in **100**) and `sub_8226E220` (pop,
called every frame from `sub_8226D740`):
| offset in the container | meaning |
|---|---|
| `+12` | list head/sentinel (`addi r31, r30, 12`) |
| `+16` | current node pointer |
| `+20` | **element count — zero means empty**; the pop tests it first and returns 0 |
| `+24` | scratch: the popped node is stashed here |
The pop hands the record out through **out-parameters**, reading from
`node+8`: `+0`, `+4`, `+8` as `u32`s, `+16` as a `double`, `+24` as another
`u32` — which matches `sub_8226D740` passing six pointers into local slots.
### 🎯 `[phase + 272 + 20]` is a live "pending triggers" counter
That is the useful part: a single `u32` that says how many triggers are queued,
readable from `/dev/shm` with no debugger. Watching it alongside
`[ScriptMission+40]` should show **when** the engine hands the script an event —
which is exactly the moment the condition coroutines get started, and the thing
every phase experiment so far has been blind to.
**Not yet verified live.** The layout above is read off the disassembly only.