re: the kind-1 trigger condition is the same test with a pre-filter -- mechanism read end to end

sub_8226DC80 turns out to be sub_8226DAF8's twin.  Diffing them instruction by
instruction, 33 of the first 86 differ, and every difference before index 65 is a
register rename or a branch target.  Both:

  * resolve the route name (sub_8217FA08 -> sub_823012D8), leaving the route point in
    [r31+112/120/128];
  * index [phase+324] by payload+0 and require rec+100 != 0;
  * copy the two 3-double vectors out of the unit record.

They part at index 65.  Kind 0 calls vtable slot 60 immediately.  Kind 1 instead
computes an inline point-to-point distance first -- three fsub against the route
point, fmul plus two fmadd, fsqrt -- and rejects when it exceeds f31, the same radius:

    8226DDC4  fsqrt f0, f0
    8226DDC8  fcmpu cr6, f0, f31
    8226DDCC  bc    4, gt, return-0
    8226DDE8  lwz   r11, 60(r11)      ; then the SAME slot 60
    8226DDF0  bcctrl

So both trigger kinds run the identical point-to-segment test; kind 1 only adds a
cheap early-out against the same radius, and per the drain it does not spawn.

Diffing the two functions rather than reading the second one cost one query and made
the relationship obvious -- the same move that resolved the six ISL branch handlers.

All artefacts regenerate byte-identical; documentation only.

What remains on triggers is peripheral: which unit-record fields A and B are, slot 60
past the t < 0 rejection, payload+8, and the kind-1 path in the drain.
This commit is contained in:
Sylpheed RE agent
2026-08-27 08:14:24 +00:00
parent a3c8257c64
commit 70df1c3a1b
2 changed files with 50 additions and 2 deletions

View File

@@ -248,13 +248,47 @@ two positions; a previous/current pair would make this the standard
frame-rate-robust waypoint test, and that is exactly the kind of tidy reading
this corpus makes itself prove. Not proven here.
## ✅ The kind-1 condition is the SAME test with a cheap pre-filter
`sub_8226DC80` turns out to be `sub_8226DAF8`'s twin. Diffing them instruction
by instruction: of the first 86, 33 differ — and every difference before index 65
is a register rename or a branch target. Both do exactly the same thing up to
that point:
* resolve the route name (`sub_8217FA08``sub_823012D8`), leaving the route
point in `[r31+112/120/128]`;
* index `[phase+324]` by `payload+0`, require `rec+100 != 0`;
* copy the two 3-double vectors out of the unit record.
Then they part:
| | kind 0 — `sub_8226DAF8` | kind 1 — `sub_8226DC80` |
|---|---|---|
| next | calls vtable slot 60 straight away | **inline distance first** |
| | | `fsub` ×3 against the route point, `fmul` + 2 `fmadd`, `fsqrt` |
| | | `fcmpu` vs `f31`; **if > the radius, return 0** |
| then | — | **calls the same vtable slot 60** |
```
8226DDC4 fsqrt f0, f0
8226DDC8 fcmpu cr6, f0, f31 ; f31 = the node's double, the radius
8226DDCC bc 4, gt, return-0 ; too far -> reject without the segment test
8226DDD0 lwz r11, 0(r30)
8226DDE8 lwz r11, 60(r11) ; the SAME slot 60
8226DDF0 bcctrl
```
**So both trigger kinds run the identical point-to-segment test.** Kind 1 adds a
point-to-point early-out against the same radius and, per the drain, does not
spawn. The trigger mechanism is now read end to end.
## 🟡 Not settled
* ~~What the drain actually spawns.~~ ✅ Resolved above: built-in 19's nodes carry
a real routine offset at `+28`.
* ~~Whether `+24` selects between the two node kinds~~ ✅ It does — see above.
* ~~What the kind-0 condition tests~~ ✅ Read above. **`sub_8226DC80` (kind 1) is
still unread entirely.**
* ~~What the kind-0 condition tests~~ ✅ Read. ~~`sub_8226DC80` (kind 1)~~ ✅ Read —
the same test with an inline distance pre-filter.
* **Which unit-record fields A and B are** — `rec+64/72/80` vs `rec+32/40/48`.
* **The rest of slot 60** past the `t < 0` rejection (a second constant at
`0x820B0000+25192` is loaded next).