re: read the ISL branch handlers -- it is a condition-code machine

Closes the backlog item that was the last thing between the flat decode and a
per-phase clear condition, and closes isl-builtins.md's standing "op10 + op13 look
like a switch -- NOT confirmed".

op10 resolves two operands, issues a SIGNED cmp, and writes three condition bits to
a bitset at phase+24: bit 0 = EQ, bit 1 = GT, bit 2 = LT.  op11 is the same machine
for floats via fcmpu.  op13-op18 branch on those bits to [phase+232] + word@+4 --
the same phase-relative target form as the unconditional op12:

  13 bit0 set    beq      16 bits 2 then 0   ble
  14 bit0 clear  bne      18 bits 1 then 0   bge
  15 bit2 set    blt      17 bit1 set        bgt

13/14/15/17 are byte-identical apart from the bit index and the polarity.  All six
relations are present and each appears exactly once; that completeness is the check
that the reading is right, rather than the usage pattern -- which the item
explicitly warned against.

Operand order recorded because it is easy to reverse: LHS = (kind byte[1], word@+4),
RHS = (kind byte[0], word@+8).

Method note in the doc: the jump table at 0x822635FC holds THUNKS, and the handler
is the bl target inside each.  My first pass guessed handler addresses at a fixed
stride, landed mid-function, and produced a 20-line "difference" that was pure
misalignment.

isl.py names the ops; data/isl-stage02.txt is regenerated and every diff line pairs
exactly, only the op-name column changing (op10->cmp.i x5, op13->beq x4,
op14->bne x1).  data/isl-stage02-phase-ends.txt now shows the phase-3 poll loop
reading as one: unit_state(ADT308) -> op23 -> cmp.i -> beq back to 0xFEB4.

Left unnamed on purpose: op23 (0x82271C30) and op21 (0x82175C20).
This commit is contained in:
Sylpheed RE agent
2026-08-27 05:10:50 +00:00
parent f41847701c
commit bad96eb54a
6 changed files with 226 additions and 56 deletions

View File

@@ -1387,9 +1387,20 @@ premise was wrong.**
🟡 **But `END_PHASE`'s call site is the WRONG place to read the condition:**
all 12 Stage-02 `END_PHASE` sites sit in one stereotyped outro
(`wait_cmds_drained → fade_sound(3) → builtin85(3) → wait_s(3) → END_PHASE →
end_coroutine`). ▶️ **Next, and it is now the only thing in the way: read the
end_coroutine`). ~~▶️ **Next, and it is now the only thing in the way: read the
five branch handlers** `op10` (`0x82271598`), `op13` (`0x82271830`), `op14`,
`op21`, `op23`. The condition lives in the `op10`/`op13` poll loop upstream of
`op21`, `op23`.~~**(2026-08-27) DONE for the branches —
[structures/isl-branches](structures/isl-branches.md).** `op10`/`op11` are
signed/float COMPARE, writing three condition bits (0=EQ, 1=GT, 2=LT) to a
bitset at `phase+24`; `op13``op18` are the six relational branches
`beq/bne/blt/ble/bgt/bge` on those bits, targeting `[phase+232] + word@+4`
exactly like `op12`. All six relations present, each once — the completeness is
the check. Handler addresses come from the `bl` inside each dispatcher thunk;
guessing them at a fixed stride lands mid-function. ▶️ **Still open:** `op23`
(`0x82271C30`, takes a built-in's result to `phase+168`) and `op21`
(`0x82175C20`) are characterised but NOT named, and naming the branch does not
yet give each stage's clear condition — that needs the operand chain feeding
each compare. The condition lives in the `op10`/`op13` poll loop upstream of
the outro — e.g. phase 3 polls `unit_state(ADT308)` and branches back to
`0xFEB4` until it passes. Artefact: `data/isl-stage02-phase-ends.txt`.
* 🐛 **(2026-08-25) The nav fix is NOT fully reliable.** `dialog_up.py` works on

View File

@@ -8,12 +8,12 @@ phase code bases: 0xe4 0x14aa8 0x24b4c
15 phase-ending call(s): 12 END_PHASE, 3 FORCE_END_PHASE
## phase 1 — builtin 6 (END_PHASE) at 0x51E4
005124: 01020C0A op10 len=12 k=01,02 00000000 00000001
005130: 0000080D op13 len=8 k=00,00 00004E2C
005138: 01020C0A op10 len=12 k=01,02 00000000 00000002
005144: 0000080D op13 len=8 k=00,00 00004ED4
00514C: 01020C0A op10 len=12 k=01,02 00000000 00000003
005158: 0000080D op13 len=8 k=00,00 00004F7C
005124: 01020C0A cmp.i len=12 k=01,02 00000000 00000001
005130: 0000080D beq len=8 k=00,00 00004E2C
005138: 01020C0A cmp.i len=12 k=01,02 00000000 00000002
005144: 0000080D beq len=8 k=00,00 00004ED4
00514C: 01020C0A cmp.i len=12 k=01,02 00000000 00000003
005158: 0000080D beq len=8 k=00,00 00004F7C
005160: 00000C13 call len=12 k=00,00 00000078 00000248 wait_cmds_drained
00516C: 01021001 set.f len=16 k=01,02 00000000 40080000 00000000 special[0] = imm 3
00517C: 02030C01 set.f len=12 k=02,03 00000000 00000000 local[0] = special[0]
@@ -32,12 +32,12 @@ phase code bases: 0xe4 0x14aa8 0x24b4c
005224: 02030C01 set.f len=12 k=02,03 00000008 00000000 local[8] = special[0]
## phase 1 — builtin 6 (END_PHASE) at 0x5828
005768: 01020C0A op10 len=12 k=01,02 00000000 00000003
005774: 0000080D op13 len=8 k=00,00 00005448
00577C: 01020C0A op10 len=12 k=01,02 00000000 00000004
005788: 0000080D op13 len=8 k=00,00 000054F0
005790: 01020C0A op10 len=12 k=01,02 00000000 00000005
00579C: 0000080D op13 len=8 k=00,00 00005598
005768: 01020C0A cmp.i len=12 k=01,02 00000000 00000003
005774: 0000080D beq len=8 k=00,00 00005448
00577C: 01020C0A cmp.i len=12 k=01,02 00000000 00000004
005788: 0000080D beq len=8 k=00,00 000054F0
005790: 01020C0A cmp.i len=12 k=01,02 00000000 00000005
00579C: 0000080D beq len=8 k=00,00 00005598
0057A4: 00000C13 call len=12 k=00,00 00000078 00000272 wait_cmds_drained
0057B0: 01021001 set.f len=16 k=01,02 00000000 40080000 00000000 special[0] = imm 3
0057C0: 02030C01 set.f len=12 k=02,03 00000000 00000000 local[0] = special[0]
@@ -128,12 +128,12 @@ phase code bases: 0xe4 0x14aa8 0x24b4c
014858: 0000081A op26? len=8 k=00,00 00000000
## phase 2 — builtin 6 (END_PHASE) at 0x19640
019580: 01020C0A op10 len=12 k=01,02 00000000 00000001
01958C: 0000080D op13 len=8 k=00,00 000048C4
019594: 01020C0A op10 len=12 k=01,02 00000000 00000002
0195A0: 0000080D op13 len=8 k=00,00 0000496C
0195A8: 01020C0A op10 len=12 k=01,02 00000000 00000003
0195B4: 0000080D op13 len=8 k=00,00 00004A14
019580: 01020C0A cmp.i len=12 k=01,02 00000000 00000001
01958C: 0000080D beq len=8 k=00,00 000048C4
019594: 01020C0A cmp.i len=12 k=01,02 00000000 00000002
0195A0: 0000080D beq len=8 k=00,00 0000496C
0195A8: 01020C0A cmp.i len=12 k=01,02 00000000 00000003
0195B4: 0000080D beq len=8 k=00,00 00004A14
0195BC: 00000C13 call len=12 k=00,00 00000078 00000204 wait_cmds_drained
0195C8: 01021001 set.f len=16 k=01,02 00000000 40080000 00000000 special[0] = imm 3
0195D8: 02030C01 set.f len=12 k=02,03 00000000 00000000 local[0] = special[0]
@@ -200,7 +200,7 @@ phase code bases: 0xe4 0x14aa8 0x24b4c
01AC74: 01030C00 set.i len=12 k=01,03 00000000 00000001 local[0] = imm 0x1
## phase 2 — builtin 62 (FORCE_END_PHASE) at 0x249F0
024938: 0000080E op14 len=8 k=00,00 0000FF44
024938: 0000080E bne len=8 k=00,00 0000FF44
024940: 01020C00 set.i len=12 k=01,02 00000000 000000F1 special[0] = imm 0xF1
02494C: 02030C00 set.i len=12 k=02,03 00000000 00000000 local[0] = special[0]
024958: 01020C00 set.i len=12 k=01,02 00000000 00000000 special[0] = imm 0x0
@@ -224,12 +224,12 @@ phase code bases: 0xe4 0x14aa8 0x24b4c
024A1C: 0000081A op26? len=8 k=00,00 00000000
## phase 3 — builtin 6 (END_PHASE) at 0x2B96C
02B8AC: 01020C0A op10 len=12 k=01,02 00000000 00000001
02B8B8: 0000080D op13 len=8 k=00,00 00006B4C
02B8C0: 01020C0A op10 len=12 k=01,02 00000000 00000002
02B8CC: 0000080D op13 len=8 k=00,00 00006BF4
02B8D4: 01020C0A op10 len=12 k=01,02 00000000 00000003
02B8E0: 0000080D op13 len=8 k=00,00 00006C9C
02B8AC: 01020C0A cmp.i len=12 k=01,02 00000000 00000001
02B8B8: 0000080D beq len=8 k=00,00 00006B4C
02B8C0: 01020C0A cmp.i len=12 k=01,02 00000000 00000002
02B8CC: 0000080D beq len=8 k=00,00 00006BF4
02B8D4: 01020C0A cmp.i len=12 k=01,02 00000000 00000003
02B8E0: 0000080D beq len=8 k=00,00 00006C9C
02B8E8: 00000C13 call len=12 k=00,00 00000078 00000270 wait_cmds_drained
02B8F4: 01021001 set.f len=16 k=01,02 00000000 40080000 00000000 special[0] = imm 3
02B904: 02030C01 set.f len=12 k=02,03 00000000 00000000 local[0] = special[0]
@@ -248,12 +248,12 @@ phase code bases: 0xe4 0x14aa8 0x24b4c
02B9AC: 02030C01 set.f len=12 k=02,03 00000008 00000000 local[8] = special[0]
## phase 3 — builtin 6 (END_PHASE) at 0x2BFB0
02BEF0: 01020C0A op10 len=12 k=01,02 00000000 00000003
02BEFC: 0000080D op13 len=8 k=00,00 00007168
02BF04: 01020C0A op10 len=12 k=01,02 00000000 00000004
02BF10: 0000080D op13 len=8 k=00,00 00007210
02BF18: 01020C0A op10 len=12 k=01,02 00000000 00000005
02BF24: 0000080D op13 len=8 k=00,00 000072B8
02BEF0: 01020C0A cmp.i len=12 k=01,02 00000000 00000003
02BEFC: 0000080D beq len=8 k=00,00 00007168
02BF04: 01020C0A cmp.i len=12 k=01,02 00000000 00000004
02BF10: 0000080D beq len=8 k=00,00 00007210
02BF18: 01020C0A cmp.i len=12 k=01,02 00000000 00000005
02BF24: 0000080D beq len=8 k=00,00 000072B8
02BF2C: 00000C13 call len=12 k=00,00 00000078 0000029A wait_cmds_drained
02BF38: 01021001 set.f len=16 k=01,02 00000000 40080000 00000000 special[0] = imm 3
02BF48: 02030C01 set.f len=12 k=02,03 00000000 00000000 local[0] = special[0]
@@ -266,8 +266,8 @@ phase code bases: 0xe4 0x14aa8 0x24b4c
02BFA4: 00000C13 call len=12 k=00,00 00000004 0000029E wait_s(3)
02BFB0: 00000C13 call len=12 k=00,00 00000006 0000029F END_PHASE
02BFBC: 00000C13 call len=12 k=00,00 0000000B 000002A0 end_coroutine
02BFC8: 01000C0A op10 len=12 k=01,00 00000070 00000004
02BFD4: 0000080F op15 len=8 k=00,00 000076AC
02BFC8: 01000C0A cmp.i len=12 k=01,00 00000070 00000004
02BFD4: 0000080F blt len=8 k=00,00 000076AC
02BFDC: 01020C00 set.i len=12 k=01,02 00000000 00000000 special[0] = imm 0x0
02BFE8: 02030C00 set.i len=12 k=02,03 00000000 00000000 local[0] = special[0]
@@ -300,8 +300,8 @@ phase code bases: 0xe4 0x14aa8 0x24b4c
02CEC8: 02000415 op21 len=4 k=02,00
02CECC: 00000C13 call len=12 k=00,00 00000063 00000306 builtin99
02CED8: 00020417 op23 len=4 k=00,02
02CEDC: 02020C0A op10 len=12 k=02,02 00000000 00000001
02CEE8: 0000080D op13 len=8 k=00,00 000083B0
02CEDC: 02020C0A cmp.i len=12 k=02,02 00000000 00000001
02CEE8: 0000080D beq len=8 k=00,00 000083B0
02CEF0: 01000C00 set.i len=12 k=01,00 00000074 00000002 global[116] = imm 0x2
02CEFC: 01021001 set.f len=16 k=01,02 00000000 40080000 00000000 special[0] = imm 3
02CF0C: 02030C01 set.f len=12 k=02,03 00000000 00000000 local[0] = special[0]
@@ -344,10 +344,10 @@ phase code bases: 0xe4 0x14aa8 0x24b4c
02D210: 01021001 set.f len=16 k=01,02 00000000 00000000 00000000 special[0] = imm 0
## phase 3 — builtin 62 (FORCE_END_PHASE) at 0x34A10
034974: 0000080D op13 len=8 k=00,00 0000FE3C
034974: 0000080D beq len=8 k=00,00 0000FE3C
03497C: 00000C13 call len=12 k=00,00 0000000B 00000728 end_coroutine
034988: 01000C0A op10 len=12 k=01,00 00000014 00000007
034994: 0000080E op14 len=8 k=00,00 0000FEB4
034988: 01000C0A cmp.i len=12 k=01,00 00000014 00000007
034994: 0000080E bne len=8 k=00,00 0000FEB4
03499C: 01020C00 set.i len=12 k=01,02 00000000 00000001 special[0] = imm 0x1
0349A8: 02020C00 set.i len=12 k=02,02 00000001 00000000 special[1] = special[0]
0349B4: 02000415 op21 len=4 k=02,00
@@ -355,8 +355,8 @@ phase code bases: 0xe4 0x14aa8 0x24b4c
0349C4: 01030C00 set.i len=12 k=01,03 00000004 00000049 local[4] = imm 0x49
0349D0: 00000C13 call len=12 k=00,00 00000045 0000072C unit_state(ADT308)
0349DC: 00020417 op23 len=4 k=00,02
0349E0: 02020C0A op10 len=12 k=02,02 00000000 00000001
0349EC: 0000080D op13 len=8 k=00,00 0000FEB4
0349E0: 02020C0A cmp.i len=12 k=02,02 00000000 00000001
0349EC: 0000080D beq len=8 k=00,00 0000FEB4
0349F4: 00000C13 call len=12 k=00,00 0000000B 0000072D end_coroutine
034A00: 00000C13 call len=12 k=00,00 0000000B 00000731 end_coroutine
034A0C: 00000414 ret len=4 k=00,00

View File

@@ -150,14 +150,14 @@ resync from 0x5058
0050F4: 00000C13 call len=12 k=00,00 00000040 00000245 request_script_message(MSG_VOICE_D_257, 0x2, 0x1, 0x9, 0x1, -1)
005100: 0000080C jmp len=8 k=00,00 0000507C -> code+0x507C (file 0x50A0)
005108: 0000080C jmp len=8 k=00,00 0000507C -> code+0x507C (file 0x50A0)
005110: 01020C0A op10 len=12 k=01,02 00000000 00000000
00511C: 0000080D op13 len=8 k=00,00 00004D84
005124: 01020C0A op10 len=12 k=01,02 00000000 00000001
005130: 0000080D op13 len=8 k=00,00 00004E2C
005138: 01020C0A op10 len=12 k=01,02 00000000 00000002
005144: 0000080D op13 len=8 k=00,00 00004ED4
00514C: 01020C0A op10 len=12 k=01,02 00000000 00000003
005158: 0000080D op13 len=8 k=00,00 00004F7C
005110: 01020C0A cmp.i len=12 k=01,02 00000000 00000000
00511C: 0000080D beq len=8 k=00,00 00004D84
005124: 01020C0A cmp.i len=12 k=01,02 00000000 00000001
005130: 0000080D beq len=8 k=00,00 00004E2C
005138: 01020C0A cmp.i len=12 k=01,02 00000000 00000002
005144: 0000080D beq len=8 k=00,00 00004ED4
00514C: 01020C0A cmp.i len=12 k=01,02 00000000 00000003
005158: 0000080D beq len=8 k=00,00 00004F7C
005160: 00000C13 call len=12 k=00,00 00000078 00000248 wait_cmds_drained
00516C: 01021001 set.f len=16 k=01,02 00000000 40080000 00000000 special[0] = imm 3
00517C: 02030C01 set.f len=12 k=02,03 00000000 00000000 local[0] = special[0]
@@ -175,8 +175,8 @@ resync from 0x5058
005214: 01021001 set.f len=16 k=01,02 00000000 00000000 00000000 special[0] = imm 0
005224: 02030C01 set.f len=12 k=02,03 00000008 00000000 local[8] = special[0]
005230: 00000C13 call len=12 k=00,00 00000014 00000252 hp_pct_test(TCN004, 0)
00523C: 01020C0A op10 len=12 k=01,02 00000000 00000001
005248: 0000080E op14 len=8 k=00,00 0000575C
00523C: 01020C0A cmp.i len=12 k=01,02 00000000 00000001
005248: 0000080E bne len=8 k=00,00 0000575C
005250: 01020C00 set.i len=12 k=01,02 00000000 00000000 special[0] = imm 0x0
00525C: 02030C00 set.i len=12 k=02,03 00000000 00000000 local[0] = special[0]
005268: 00000C13 call len=12 k=00,00 00000074 00000253 builtin116(0x0)

View File

@@ -0,0 +1,138 @@
# ✅ The ISL branch ops — a condition-code machine, read off the handlers
[`BACKLOG.md`](../BACKLOG.md) had this as the last thing between the flat decode
and a per-phase clear condition: five unread handlers, and an explicit
instruction not to name a branch from a pattern. They are read now, from
`sylpheed.db`. Nothing below is inferred from usage.
## Getting the real handler addresses
The 25-entry jump table at `0x822635FC` holds **thunks** inside the dispatcher,
not the handlers. Each thunk is three register moves and a `bl`; the `bl` target
is the handler. Taking the addresses any other way gets them wrong — my first
attempt guessed them at a fixed stride and landed mid-function, which produced a
20-line "difference" that was pure misalignment.
| op | thunk | handler |
|---|---|---|
| 10 | `0x822636F8` | `0x82271598` |
| 11 | `0x8226370C` | `0x822716E0` |
| 12 | `0x82263720` | *inline in the thunk* |
| 13 | `0x82263738` | `0x82271830` |
| 14 | `0x8226374C` | `0x822718C8` |
| 15 | `0x82263760` | `0x82271960` |
| 16 | `0x82263774` | `0x822719F8` |
| 17 | `0x8226379C` | `0x82271AC8` |
| 18 | `0x82263788` | `0x82271B60` |
## ✅ op10 / op11 are COMPARE, and they write three condition bits
`0x82271598` resolves two operands and compares them:
```
822715B0 lwz r5, 8(r29) ; word@+8
822715B4 lbz r4, 0(r29) ; kind byte[0]
822715B8 bl 0x82271D40 ; integer operand resolver -> r28 = RHS
822715C0 lwz r5, 4(r29) ; word@+4
822715C8 lbz r4, 1(r29) ; kind byte[1]
822715CC bl 0x82271D40 -> r27 = LHS
822715D4 addi r31, r31, 24 ; the condition bitset lives at phase+24
822715D8 cmp cr6, 0, r27, r28 ; SIGNED
```
It then writes **three** bits, each set-or-cleared by its own `cmp`:
| bit | written at | condition | set / clear |
|---|---|---|---|
| **0** | `0x822715D8` | `EQ` | `or` if equal, `andc` if not |
| **1** | `0x8227162C` | `GT` | `or` / `andc` on `cr6+gt` |
| **2** | `0x82271678` | `LT` | `or` / `andc` on `cr6+lt` |
**`op11` is the same machine for floats** — `0x822716E0` resolves through the
float resolvers (`0x82271F10` / `0x82271E30`), issues `fcmpu cr6, f30, f31`, and
writes the same bitset at `phase+24`.
**Operand order matters and is easy to get backwards:** LHS is `(kind byte[1],
word@+4)` and RHS is `(kind byte[0], word@+8)`. So a listing line
```
cmp.i k=01,02 00000000 00000002
```
is *compare `special[0]` against immediate `2`*`k=01` is the RHS kind
(`imm`), `k=02` the LHS kind (`special`).
## ✅ op13op18 are the six relational branches
Every one of them tests bits in that same bitset and, when taken, sets the pc to
`[phase+232] + word@+4` — **the identical phase-relative target form as the
unconditional `op12`**.
| op | bits tested | taken when | name |
|---|---|---|---|
| 13 | 0 | set | **`beq`** |
| 14 | 0 | clear | **`bne`** |
| 15 | 2 | set | **`blt`** |
| 17 | 1 | set | **`bgt`** |
| 16 | 2, then 0 | either set | **`ble`** |
| 18 | 1, then 0 | either set | **`bge`** |
13, 14, 15 and 17 are **byte-identical to each other** apart from two
instructions — the bit index (`addi r5, r0, N`) and the polarity
(`cmpli r11, 0x1` vs `0x0`). 16 and 18 are longer because they test a second bit
after the first fails, which is exactly `<=` and `>=`.
🔑 **All six relations are present and each appears exactly once.** That
completeness is the check: a mis-read would not produce a closed, non-redundant
relational set.
## ✅ This closes the `op10`+`op13` "switch" question
[`isl-builtins.md`](isl-builtins.md) recorded it as 🟡 *"Consecutive small
immediates each paired with their own code offset is the shape of a case/branch
dispatch … **Not confirmed** — the handlers have not been read."* They are read
now, and the shape is what it looked like — a chain of compare-and-branch-if-equal:
```
cmp.i k=01,02 00000000 00000001
beq -> 0x4E2C
cmp.i k=01,02 00000000 00000002
beq -> 0x4ED4
cmp.i k=01,02 00000000 00000003
beq -> 0x4F7C
```
*"switch (special[0]) { case 1: … case 2: … case 3: … }"*, lowered to sequential
compares. Confirmed from the handlers, not from the pattern.
## ✅ And a phase-3 clear condition now reads as one
From [`../data/isl-stage02-phase-ends.txt`](../data/isl-stage02-phase-ends.txt):
```
0349D0 call unit_state(ADT308)
0349DC op23 ; takes the call result
0349E0 cmp.i k=02,02 special[0], special[1]
0349EC beq -> 0xFEB4 ; loop back while it holds
0349F4 call end_coroutine
```
A coroutine polling `unit_state` on unit `ADT308` and branching **back** while
the comparison is equal — the poll loop the previous iteration could only
describe by shape.
## 🟡 Not settled
* **`op23`** (`0x82271C30`) is left unnamed. It indexes a container at `r3+44`
and stores a word to `[phase+168]`; it is almost certainly how a built-in's
return value reaches `special[]`, but "almost certainly" is how this corpus
acquired two names it later had to withdraw. Characterised, not named.
* **`op21`** (`0x82175C20`) has a different shape from all of these — its thunk
passes `phase+168` and `phase+44`, not the pc — and was not read.
* **The bitset container at `phase+24`.** `op10` reaches it with `0x822749B0`
(by address, `phase+24`) and `op13` with `0x82274CC0` (through a word loaded
from `phase+32`). Both land on the same bits, but the exact container layout
is not established, so `phase+32` is *not* asserted to be its data pointer.
* **Which value `special[0]` holds at a given site.** Naming the branch does not
by itself give the clear condition for every stage — that needs the operand
chain feeding each compare, which is the next step.

View File

@@ -704,9 +704,15 @@ op13 -> 0x5598
Consecutive small immediates each paired with their own code offset is the shape
of a **case/branch dispatch**, and `op12` is already confirmed as the
unconditional jump. **Not confirmed** — the handlers (`0x82271598` for op10,
unconditional jump. ~~**Not confirmed** — the handlers (`0x82271598` for op10,
`0x82271830` for op13) have not been read, and I am not going to name them from
a pattern alone.
a pattern alone.~~
**(2026-08-27) CONFIRMED from the handlers — see
[isl-branches](isl-branches.md).** `op10` is a signed compare writing three
condition bits (0=EQ, 1=GT, 2=LT) to a bitset at `phase+24`; `op11` is the float
twin via `fcmpu`; `op13``op18` are the six relational branches
(`beq bne blt ble bgt bge`) on those bits, targeting `[phase+232] + word@+4`
like `op12`. It is a case dispatch lowered to sequential compare-and-branch.
## 🔴 Correction: `unit_state` does NOT read `+16` — it reads `+4` and `+104`