re: the sufficient side -- each phase exit now names the condition that FIRES it
Dominance said a phase cannot end unless X. A port also needs "once X holds, it must end", and that is a must-reach set: nodes from which END_PHASE is unavoidable, as a least fixpoint where n qualifies when it has successors and ALL of them qualify. The conservatism is deliberate and is the honest answer: a loop never enters the set, because a poll loop reaches its exit only if the polled predicate eventually becomes true, which is a liveness property rather than a graph one. A dominating condition is a TRIGGER when the successor it takes on being satisfied lies in that set. Over all 28 stages: 732 dominating conditions, 234 triggers (31.97%). isl_report.py phase-guards now tags every line precond / TRIGGER. The split lands where it should. Stage 02's phase-1 objective exit is six preconditions -- player alive, TCN004 destroyed, t <= 210, ADT102/ADT107/ADT113 destroyed -- and exactly ONE trigger: hp_pct_test(ADN101, 0) != 1. Destroying ADN101 is what fires the phase. That is a sentence a port can implement. Per-exit distribution over 172 reachable exits: 89 have exactly one trigger, 42 have none, 41 have several. The 42 with none are not a failure -- they are the exits no branch fires; Stage 02's 0x006260 ends on read_freg(0) < 1200, a timeout, and time passing is not a property of the graph, so declining to call it a trigger is correct. Recorded as a heuristic rather than a rule: "the first trigger is the point of no return" holds for 33 of the 41 multi-trigger exits, with 8 counterexamples where a precondition appears after a trigger. The likely cause is that the listing is ordered by file offset, which is not execution order -- coroutines and jumps let a lower offset run later. Not asserted. calls, phase-ends and conditions all regenerate byte-identical; the two phase-guards artefacts change only by gaining the tags.
This commit is contained in:
@@ -1498,7 +1498,20 @@ premise was wrong.**
|
||||
`unit_hp_pct(TCN001, Character_Player_Test) != 0`** (the player alive); phase 1's
|
||||
objective exit additionally requires `hp_pct_test(ADT102/ADT107/ADT113, 0) != 1`,
|
||||
phase 3's requires `ADT301`/`ADT302`, and `read_freg(0)` gates at 210/300/1200.
|
||||
🟡 Dominance gives NECESSARY not sufficient conditions. 🟡 2 of 15 exits are
|
||||
~~🟡 Dominance gives NECESSARY not sufficient conditions.~~ ✅ **(2026-08-27)
|
||||
THE SUFFICIENT SIDE IS DONE.** `isl_cfg.must_reach_exit()` = a least fixpoint of
|
||||
nodes from which `END_PHASE` is unavoidable (n qualifies when it has successors
|
||||
and ALL qualify — conservative, so a loop never enters, which is correct because
|
||||
a poll loop's exit depends on a LIVENESS property). A dominating condition is a
|
||||
**TRIGGER** when its satisfying successor is in that set. **732 dominating
|
||||
conditions, 234 triggers (31.97 %)**, and the artefacts now tag every line
|
||||
`precond` / `TRIGGER`. Stage 02 phase 1's objective exit = 6 preconditions plus
|
||||
ONE trigger, `hp_pct_test(ADN101, 0) != 1` — destroying ADN101 is what fires it.
|
||||
Per-exit: **89 of 172 have exactly one trigger**, 42 have none (those are the
|
||||
timeout exits — time passing is not a graph property, so declining to call it a
|
||||
trigger is correct), 41 have several. 🟡 "the FIRST trigger is the point of no
|
||||
return" holds **33/41**, not universally — the listing is ordered by file offset,
|
||||
which is not execution order — so it is recorded as a heuristic, not a rule. 🟡 2 of 15 exits are
|
||||
reachable from NO static entry — consistent with the trigger queue at `phase+272`.
|
||||
✅ **(2026-08-27) EXTENDED TO ALL 28 STAGES** — `data/isl-phase-guards-all.txt`
|
||||
(`isl_report.py <dir> phase-guards`). **177 exits, only 5 (2.8 %) unreachable**;
|
||||
|
||||
Reference in New Issue
Block a user