re: the per-phase clear conditions, by dominance over the ISL CFG

Closes the backlog's "which condition guards each END_PHASE".  With the CFG from the
previous commit this is a graph query, not new machinery.

The obvious query is WRONG for this language, and I implemented it first: "one
successor reaches END_PHASE and the other does not" finds 1/62/1 guards across Stage
02's three phases, and the 1s are both the same read_freg(0) < 1200 timeout -- every
objective test missed.  The cause is the dominant idiom: a POLL LOOP's loop-back
branch also reaches the exit, one iteration later, so neither successor discriminates.
The asymmetric 1/62/1 is what exposed it; a uniform number would have read as
plausible.

Dominance has no such blind spot: a condition dominates an exit when every path from
an entry passes through it, so it is NECESSARY for the phase to end that way, and a
poll loop's test dominates its own exit by construction.  Iterative dominators
converge in 3 passes over 15670/18739 instructions (83.6%).

Result for Stage 02 -- every exit in all three phases is dominated by
unit_hp_pct(TCN001, Character_Player_Test) != 0, the player's ship being alive, which
falls out rather than being assumed.  Beyond that, phase 1's objective exit requires
hp_pct_test on ADT102, ADT107 and ADT113; phase 3's requires ADT301 and ADT302;
read_freg(0) gates at 210 / 300 and times out at 1200; random(3) and random(5)
dominate only the exits that pick one of several closing lines.

Two of the 15 exits are reachable from NO static entry, both FORCE_END_PHASE.  That
agrees with the independently measured 389 unreachable routines: they are started from
the trigger queue at phase+272, by data rather than code.

Practical note recorded: the first dominator run was OOM-killed -- 6743 nodes each
holding a Python set of up to 6743 elements.  Integer bitmasks run in seconds.

Not settled, and said so: dominance gives necessary, not sufficient, conditions; only
Stage 02's artefact is committed; one listed condition is still an unresolved
<unknown>; read_freg's units are inferred from the gate values, not read.

calls, phase-ends and conditions all regenerate byte-identical.
This commit is contained in:
Sylpheed RE agent
2026-08-27 06:12:41 +00:00
parent 5ea9e38b35
commit 4f95b98813
6 changed files with 387 additions and 3 deletions

View File

@@ -0,0 +1,102 @@
# Stage02.ssb — what each phase exit requires
Generated by `tools/re-capture/isl_report.py phase-guards`.
A condition is listed when it DOMINATES the exit: every path from an
entry to that `END_PHASE` passes through it, so it is NECESSARY for the
phase to end that way. Reachability alone is the wrong query here —
in a poll loop both successors reach the exit.
CFG reached 15670 of 18739 instructions (83.6%).
## phase 1 — END_PHASE at 0x0051E4: 2 necessary condition(s)
0x004D70 unit_hp_pct(TCN001, Character_Player_Test) != 0
0x005110 random(3) == 0
## phase 1 — END_PHASE at 0x005828: 3 necessary condition(s)
0x004D70 unit_hp_pct(TCN001, Character_Player_Test) != 0
0x00523C hp_pct_test(TCN004, 0) != 1
0x00572C random(5) == 0
## phase 1 — END_PHASE at 0x006010: 7 necessary condition(s)
0x004D70 unit_hp_pct(TCN001, Character_Player_Test) != 0
0x00523C hp_pct_test(TCN004, 0) != 1
0x005888 read_freg(0) <= 210
0x0058DC hp_pct_test(ADT102, 0) != 1
0x005930 hp_pct_test(ADT107, 0) != 1
0x005984 hp_pct_test(ADT113, 0) != 1
0x005B3C hp_pct_test(ADN101, 0) != 1
## phase 1 — END_PHASE at 0x006260: 4 necessary condition(s)
0x004D70 unit_hp_pct(TCN001, Character_Player_Test) != 0
0x00523C hp_pct_test(TCN004, 0) != 1
0x005888 read_freg(0) <= 210
0x006070 read_freg(0) < 1200
## phase 1 — FORCE_END_PHASE at 0x01482C: UNREACHABLE from any static entry
(started from the trigger queue at `phase+272`, by data not code)
## phase 2 — END_PHASE at 0x019640: 2 necessary condition(s)
0x0191CC unit_hp_pct(TCN001, Character_Player_Test) != 0
0x01956C random(3) == 0
## phase 2 — END_PHASE at 0x019934: 2 necessary condition(s)
0x0191CC unit_hp_pct(TCN001, Character_Player_Test) != 0
0x019698 hp_pct_test(TCT206, 0) != 1
## phase 2 — END_PHASE at 0x01AC44: 9 necessary condition(s)
0x0191CC unit_hp_pct(TCN001, Character_Player_Test) != 0
0x019698 hp_pct_test(TCT206, 0) != 1
0x0199C8 builtin7(TCT206, 1, Route_TCT206_p2S, 4294967295, 500) == 1
0x019A8C <unknown> <= 240
0x01A1E4 global[4] == 0
0x01A2B0 global[4] != 1
0x01A924 hp_pct_test(TCT206, 89.9) != 0
0x01AA38 global[104] != 0
0x01AAF8 global[4] == 0
## phase 2 — FORCE_END_PHASE at 0x0249F0: 2 necessary condition(s)
0x0247E4 hp_pct_test(ADN202, 0) != 1
0x024848 unit_state(TCT206) != 1
## phase 3 — END_PHASE at 0x02B96C: 2 necessary condition(s)
0x02B4F8 unit_hp_pct(TCN001, Character_Player_Test) != 0
0x02B898 random(3) == 0
## phase 3 — END_PHASE at 0x02BFB0: 3 necessary condition(s)
0x02B4F8 unit_hp_pct(TCN001, Character_Player_Test) != 0
0x02B9C4 hp_pct_test(TCN004, 0) != 1
0x02BEB4 random(5) == 0
## phase 3 — END_PHASE at 0x02C1E0: 3 necessary condition(s)
0x02B4F8 unit_hp_pct(TCN001, Character_Player_Test) != 0
0x02B9C4 hp_pct_test(TCN004, 0) != 1
0x02BFC8 global[112] < 4
## phase 3 — END_PHASE at 0x02CF74: 16 necessary condition(s)
0x02B4F8 unit_hp_pct(TCN001, Character_Player_Test) != 0
0x02B9C4 hp_pct_test(TCN004, 0) != 1
0x02BFC8 global[112] < 4
0x02C240 read_freg(0) <= 300
0x02C294 hp_pct_test(ADT301, 0) != 1
0x02C2E8 hp_pct_test(ADT302, 0) != 1
0x02C33C hp_pct_test(ADT303, 0) != 1
0x02C390 hp_pct_test(ADT304, 0) != 1
0x02C3E4 hp_pct_test(ADT305, 0) != 1
0x02C438 hp_pct_test(ADT306, 0) != 1
0x02C48C hp_pct_test(ADT307, 0) != 1
0x02C4E0 hp_pct_test(ADT308, 0) != 1
0x02C5C4 global[112] != 0
0x02CC0C global[112] != 0
0x02CDC8 global[4] == 0
0x02CE9C global[112] != 0
## phase 3 — END_PHASE at 0x02D1DC: 5 necessary condition(s)
0x02B4F8 unit_hp_pct(TCN001, Character_Player_Test) != 0
0x02B9C4 hp_pct_test(TCN004, 0) != 1
0x02BFC8 global[112] < 4
0x02C240 read_freg(0) <= 300
0x02CFD4 read_freg(0) < 1200
## phase 3 — FORCE_END_PHASE at 0x034A10: UNREACHABLE from any static entry
(started from the trigger queue at `phase+272`, by data not code)