This repository has been archived on 2026-09-16. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Syplheed-Reborn/docs/re/structures/isl-conditions.md
Sylpheed RE agent 1ac9aa1d7d re: fix the wrong ISL conditions -- the cause was a coroutine boundary, not the filter
The listing showed end_coroutine as the left-hand side of 34 comparisons disc-wide.
That is impossible -- it returns no value a script can test -- so it was the bug
reporting itself.

The recorded fix ("set special[0] only for built-ins that write [phase+164]") is
REFUTED.  end_coroutine's handler 0x82272624 is `addi r11,r0,1 ; addi r3,r0,3 ;
stw r11,164(r31)` -- it DOES write [phase+164], so that filter would have kept it.
Reading the handler before writing the filter is what caught this.

The real cause: end_coroutine returns 3, which DESTROYS the thread.  Execution does
not continue past it, so the instructions following it in the flat stream belong to
a different routine and every tracked value is stale.  The linear walk that makes
the decode possible is exactly what walks across that boundary.

A/B over all 28 stages, 7563 sites, resetting the tracker at end_coroutine:
  sites whose operands change            34  (0.45%)
  LHS = end_coroutine, before -> after   34 -> 0
  left as an explicit unknown            34  (0.45%)

The two counts being equal is the result: the leak was confined to exactly the sites
that displayed the impossible value, so the other 7529 conditions were never
affected.  Those 34 now print "<unknown: reached after a coroutine boundary>".

Not done, and said so: their RHS is still exact and the LHS is recoverable by seeding
the tracker at coroutine entries, whose targets are staged slot 0 of start_coroutine.

data/isl-stage02-conditions.txt regenerated; calls and phase-ends both byte-identical.
2026-08-27 05:36:02 +00:00

131 lines
5.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ✅ Every condition site, with its comparand — the clear conditions are readable
This is what the whole ISL chain was for. `data/isl-stage02-conditions.txt` has
existed for a long time as a **stale artefact with no generator** —
`isl_report.py`'s own docstring says so. It has one now, and the conditions are
resolved rather than printed as `special[N]`.
## ✅ The deque ops are an expression stack
`isl-bytecode.md` names ops 21–24 `push.i`/`push.f`/`pop.i`/`pop.f` over deques
at `phase+44` / `phase+64`. What they are *used for* is the missing piece:
```
set.i special[0] = 1
set.i special[1] = special[0]
push.i ; save the comparand
set.i local[0] = 1
set.i local[4] = 0x49
call unit_state(ADT308) ; result -> special[0] (clobbers it)
pop.i ; restore comparand -> special[1]
cmp.i special[0], special[1]
beq -> 0xFEB4
```
A textbook stack-machine lowering: **push the left operand, evaluate the right,
pop, compare.** Tracking the stack through the decode is therefore enough to
recover what every site actually tests.
### The evidence it is a stack, not something else
| check | result |
|---|---|
| `push` vs `pop` across all 28 stages | **1877 vs 1877** |
| files where the deque underflows or ends unbalanced | **0 of 28** |
| Stage 02 `pop.i` sites immediately followed by `cmp.i` | **319 / 319** |
| ops immediately preceding a `pop.i` | `call` ×313, `cmp.a` ×6 |
Perfect balance with zero underflow across 28 independent files is not something
a wrong model produces, and `pop.i` → `cmp.i` at 319/319 makes `pop.i` a reliable
marker for a condition site.
## ✅ Result — disc-wide
`isl.conditions()` walks the linear stream tracking `special[]`, `local[]` and
the stack, then reports each compare with its branch:
| | all 28 stages |
|---|---|
| condition sites | **7 563** |
| **either side left unresolved as `special[N]`** | **0 — 0.0 %** |
| LHS is a resolved built-in call | 6 292 — 83.2 % |
| RHS is a plain number | 7 544 — 99.7 % |
**Most-tested predicates disc-wide:** `hp_pct_test` 1955, `unit_state` 1257,
`unit_relation` 796, `dist_lt` 450, `request_script_message` 425,
`unit_alive` 413, `read_freg` 187.
And they read as conditions:
```
0x032F4 ph1 if unit_alive(TCN105) != 1 -> 0x3388
0x04D70 ph1 if unit_hp_pct(TCN001, Character_Player_Test) != 0 -> 0x51FC
0x2C4E0 ph3 if hp_pct_test(ADT308, 0) != 1 -> 0x2CF8C
0x30790 ph3 if dist_lt(ADT308, TCN000, 15000) != 1 -> 0x307B0
0x33FA0 ph3 if unit_state(ADT308) != 2 -> 0x34030
0x349E0 ph3 if unit_state(ADT308) == 1 -> 0x34A00
```
`dist_lt(ADT308, TCN000, 15000)` — with the world unit established as 1 metre,
that is a **15 km** proximity test.
## ✅ FIXED — and the cause was not what I expected
The first version of this listing showed **`end_coroutine`** as the left-hand
side of 34 comparisons disc-wide (15 in Stage 02). That is impossible —
`end_coroutine` returns no value a script can test — and an impossible output is
the bug reporting itself.
### 🔴 The obvious fix is REFUTED
The plan recorded here was *"set `special[0]` only for built-ins that write
`[phase+164]`"*. That would not have worked: `end_coroutine`'s handler
`0x82272624` is
```
addi r11, r0, 1
addi r3, r0, 3
stw r11, 164(r31) ; it DOES write [phase+164]
```
so the filter would have kept it. Checking the handler before writing the filter
is what caught this.
### ✅ The real cause: a coroutine boundary
`end_coroutine` returns **3**, which *destroys the thread*. Execution does not
continue past it — so the instructions that follow it in the **flat** stream
belong to a **different routine**, and every value the tracker was carrying is
stale. The linear walk that makes this decode possible at all is precisely what
walks across that boundary.
Resetting the tracker at `end_coroutine`:
| A/B over all 28 stages, 7563 sites | |
|---|---|
| sites whose operands change | **34 — 0.45 %** |
| LHS `end_coroutine` before → after | **34 → 0** |
| left as an explicit unknown afterwards | **34 — 0.45 %** |
**The two counts are equal, so the leak was confined to exactly the sites that
displayed the impossible value** — the other 7 529 conditions were never
affected. Those 34 now print
`<unknown: reached after a coroutine boundary>` rather than a wrong answer.
🟡 **They are recoverable but not recovered.** The right-hand side of each is
still exact; only the left is lost. Resolving them means seeding the tracker at
each coroutine **entry** rather than walking in from the previous routine, and
the entries are available — `start_coroutine`'s target is staged slot 0. Not
done.
## 🟡 Not settled
* The **35 unnamed built-ins** still print as `builtinN` — `builtin103` (115
sites), `builtin105` (117) and `builtin16` (132) are the highest-traffic
unknowns, and each is now a vtable-slot lookup away.
* **Which condition guards each `END_PHASE`.** Every site is readable, but
linking a condition to the phase exit it eventually reaches needs the control
flow between them, which this listing does not follow.
* Only Stage 02's artefact is committed; the other 27 generate from the same
command but are not in the tree.