The kind-0 condition tester sub_8226DAF8 resolves a symbol as well, but from
payload+4 rather than +28:
8226DB14 rlwinm r10, r5, 2, 0, 29 ; r5 = payload+4, x4
8226DB24 lwz r11, 244(r30) ; [phase+244] = symbol table 1
8226DB3C lwzx r10, r10, r11
8226DB50 addi r4, r11, 4 ; -> the name string
Both appenders write local[12] into whichever slot their own path reads -- built-in 19
into +4, built-in 25 into +4 and +28 -- so the same operand is the symbol either way,
and it resolves cleanly:
built-in 19 local[12] -> symtab 1 79 / 79 all symbol type 1
built-in 25 local[12] -> symtab 1 25 / 25 all symbol type 1
isl-builtins.md records symtab-1 type 1 as the Route_* names, and the samples agree:
Route_ADN106_p1F, Route_ADN1xxe1_p1F, Route_ADS101_p1F, Route_TCN004_p1S.
So both built-ins register a trigger on a UNIT reaching a ROUTE -- payload+0 is the
unit, local[12] the route. They differ only in the payoff: built-in 19 additionally
carries a handler routine (local[32] -> payload+28, kind 0 -> spawn) while built-in 25
carries none (kind 1 -> no spawn). That is a third independent agreement with
isl.py's SYM1_SLOTS, which lists slot 12 for both and was derived from operand ranges
alone.
It also joins up with the closed REMAINING OB work, where the counter was measured to
rise at a squadron's route arrival time -- route triggers are the mechanism that would
do that. Recorded as a connection, not a demonstration: nothing here traces a trigger
to that counter.
All artefacts regenerate byte-identical; documentation only.
Still unread: what the conditions actually COMPARE -- only sub_8226DAF8's first ~34
instructions are read, up to the name lookup, and sub_8226DC80 not at all -- and
payload+8, a computed value passed to both testers. A waypoint index would fit it,
which is exactly why it is not being called one.
Read straight off the drain, immediately after the pop:
8226D7AC lwz r11, 92(r31) ; payload+24
8226D7B8 cmpli cr6, 0, r11, 0x1
8226D7C4 bc 12, lt, 0x8226D89C ; +24 == 0 -> the SPAWN path
8226D7C8 bc 4, eq, 0x8226DA44 ; +24 > 1 -> bail
; +24 == 1 -> fall through, a NON-spawn path
So the field I recorded last iteration as "kind-tag shaped, but the drain was not
shown branching on it" does branch on it, three ways.
+24 = 0, written by built-in 19: condition sub_8226DAF8, then bl 0x822737C8 --
+28 is a code offset, matching the 79/79-vs-27.8%-control measurement.
+24 = 1, written by built-in 25: condition sub_8226DC80, no spawn, and the path
resolves +28 through [phase+244] -- symbol table 1:
8226D834 lwz r10, 112(r31) ; payload+28
8226D838 lwz r11, 244(r29) ; symtab 1
8226D848 lwzx r10, r10, r11
And that resolution checks out: over all 25 call sites disc-wide, built-in 25's
local[12] resolves to a symbol-table-1 entry 25 times out of 25, and every name is a
route -- Route_ADS101_p1F, Route_ADT107_p1F, Route_TCN004_p1S, Route_ADT202A_p2S.
The 1-12 values that looked like a puzzle two iterations ago are simply small symbol
indices. This independently confirms isl.py's SYM1_SLOTS, which lists slot 12 for
built-in 25 and was derived from operand ranges alone.
So there are two kinds of trigger: built-in 19 registers one whose payload is a
HANDLER ROUTINE, built-in 25 one whose payload is a ROUTE.
All artefacts regenerate byte-identical; documentation only.
Unread: both condition testers (sub_8226DAF8, sub_8226DC80), what the kind-1 path
does with the route past sub_8217FA08 / sub_8218CC88 / sub_8225FEA0, and the drain's
second spawn site at 0x8226DA10.
Resolves the puzzle the previous commit left open: payload+28 is a 1-12 index, yet
the drain hands it to a spawner that wants a code offset. The cause is that built-in
25 is not the only thing that appends a trigger node.
Searching for functions that write a double at +16 AND a word at +28 of the same
register finds exactly two: sub_8226A0D8 (built-in 25, vtable slot 28) which fills the
payload inline, and sub_8226E160, a free-list recycling variant whose sole caller
sub_82269ED0 is vtable slot 68 = BUILT-IN 19.
Built-in 19 passes local[4] -> node+0 (the unit), local[12] -> +4, local[24] -> +16 as
a double, and local[32] -> +28. That last one is the field the drain spawns from, and
it is a genuine code offset:
over all 79 call sites disc-wide
land on the instruction stream 79 / 79 = 100.0%
control, random 4-aligned offsets 27.8%
values 12164 .. 111080, 73 distinct
Large, distinct, and every one resolves against a control that resolves barely a
quarter of the time. So built-in 19 registers a trigger whose handler is a routine,
and the drain's `base + payload+28` spawn is that handler. The two builders simply
put different things in the same slot.
Observed alongside: built-in 25 writes +24 = 1 and built-in 19 writes +24 = 0, which
is kind-tag shaped -- but the drain was not shown branching on it, so that is recorded
as an observation and not a reading.
All artefacts regenerate byte-identical; documentation only.
Still open: whether +24 selects between the node kinds, and this STILL does not
explain the unreached code -- 0 of the 79 handler offsets are unreached run-starts.
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.