re: the timeline's consumer confirms the decode, and kind is a clock index

The schedule decode previously rested on structural checks alone.  The engine's own
reader is now found, statically.

The phase initialiser sub_82270DF8 takes the 0x1883 record's six words as arguments
and stores the table pointer:

    82270FDC  add  r10, r26, r21      ; code_base + entry_a
    82270FEC  stw  r10, 240(r30)      ; [phase+240]

[phase+240] has exactly two readers in the ISL region, and one -- sub_822748D0 -- is
called from sub_82263408, ScriptPhase::Update.  It walks the table and confirms every
field independently of my inference: N at table+4, kind at group+20, float t at
group+12, offset at group+4, and a 24-byte stride (three 8-byte records).

kind is NOT a flag.  `rlwinm r11, r11, 2, 0, 29` then `lfsx` -- it is multiplied by 4
and used to index float arrays at [phase+104] and [phase+88], so it selects WHICH
TIMER the entry is scheduled against; 0 and 5 are two of at least six.  An entry fires
only when its t lies between the two readings, the classic "which events did this
frame cross" test.  Which array is previous and which is current follows from the two
comparison directions and is stated as a reading of those branches, not measured.

Both arrays are fields of the phase object, so the clock is PER PHASE -- which is why
every phase's table restarts at t = 0.  That was an inference from the layout in the
previous commit; it is now a read.

All five artefacts regenerate byte-identical: this changes documentation only.

Still open and said so: what the six clocks are (only indices 0 and 5 appear in the
data, and neither array was traced to a source); sub_822737C8, which actually starts
the routine, is unread; and this is still not what starts the unreachable code, since
0 of the 675 targets are unreached run-starts.
This commit is contained in:
Sylpheed RE agent
2026-08-27 07:15:25 +00:00
parent 2463748a71
commit 4e948d0317
2 changed files with 77 additions and 10 deletions

View File

@@ -65,9 +65,20 @@ unknown, what evidence exists, and what the first step would be. Move an item in
each appear TWICE** — but the times are round numbers, so presence alone is weak;
the doubling is the sharper detail. Artefacts `data/isl-stage02-schedule.txt` and
`data/isl-schedule-all.txt`; the four earlier artefacts regenerate byte-identical.
🟡 **Still NOT what starts the unreachable code** — 0 of the 675 targets are
unreached run-starts, so the ~15 % gap stands. The consumer is unread, and whether
the clock is per-phase or per-mission is an inference from layout, not a read.
**(2026-08-27) THE CONSUMER IS FOUND — `sub_822748D0`, called from
`ScriptPhase::Update` (`sub_82263408`).** The phase initialiser stores
`[phase+240] = code_base + entry_a` (`add r10,r26,r21; stw r10,240(r30)`), and
`+240` has exactly two readers in the ISL region. The walker confirms EVERY field
independently of my structural inference: count at `table+4`, `kind` at
`group+20`, float `t` at `group+12`, offset at `group+4`, **stride 24**.
🔑 **`kind` is a CLOCK INDEX, not a flag** — `rlwinm kind,2,0,29` + `lfsx` into
float arrays at **`[phase+104]`** and **`[phase+88]`**; an entry fires when its
`t` lies between the two readings (the "which events did this frame cross" test).
✅ Both arrays are phase fields, so **the clock is PER PHASE** — previously an
inference from layout, now a read. 🟡 Still open: what the six clocks are (only
indices 0 and 5 are used); `sub_822737C8`, which actually starts the routine, is
unread. 🟡 **Still NOT what starts the unreachable code** — 0 of the 675 targets
are unreached run-starts, so the ~15 % gap stands.
## ✅✅ SOLVED — the mission freeze was a modal sign-in dialog (2026-08-26)

View File

@@ -64,13 +64,69 @@ sharper detail, and it was not predicted in advance.
675 entries), generator `isl_report.py schedule`. The four earlier artefacts
regenerate byte-identical.
## 🟡 Not settled
## ✅ The CONSUMER — `sub_822748D0`, called from `ScriptPhase::Update`
* **`kind` (0 or 5) is not identified.** Two behaviours, no reading.
Found statically. The phase initialiser `sub_82270DF8` takes the `0x1883`
record's six words as arguments and stores
```
82270FC8 stw r26, 232(r30) ; [phase+232] = code base (already documented)
82270FDC add r10, r26, r21 ; base + entry_a
82270FEC stw r10, 240(r30) ; [phase+240] = THE TABLE POINTER
```
`[phase+240]` has exactly **two** readers in the ISL region, and one of them,
`sub_822748D0`, is called from **`sub_82263408` = `ScriptPhase::Update`**. It is
the timeline walker, and it confirms every field of the decode independently:
```
822748E0 lwz r11, 240(r30) ; the table
822748E4 lwz r10, 4(r11) ; N -- the count, at table+4 ✓
822748E8 addi r11, r11, 8 ; skip the count record ✓
822748F4 addi r31, r11, 20 ; -> group+20
822748FC lwz r11, 0(r31) ; kind at group+20 ✓
82274900 lfs f0, -8(r31) ; t at group+12, as a FLOAT ✓
82274904 lwz r10, 104(r30) ; clock array A
82274908 rlwinm r11, r11, 2,0,29 ; kind * 4
8227490C lfsx f13, r10, r11 ; A[kind]
82274910 fcmpu cr6, f13, f0
82274914 bc ... skip
82274918 lwz r10, 88(r30) ; clock array B
8227491C lfsx f13, r10, r11 ; B[kind]
82274920 fcmpu cr6, f0, f13
82274924 bc ... skip
8227492C lwz r5, -16(r31) ; offset at group+4 ✓
82274930 lwz r4, 232(r30) ; the code base
82274934 bl 0x822737C8 ; start the routine
8227493C addi r31, r31, 24 ; STRIDE 24 = three 8-byte records ✓
```
Count position, float position, offset position and the 24-byte stride are all
confirmed by the engine's own reader rather than by my structural inference.
### ✅ `kind` is a CLOCK INDEX, not a flag
`rlwinm r11, r11, 2, 0, 29` then `lfsx``kind` is multiplied by 4 and used to
index **float arrays at `[phase+104]` and `[phase+88]`**. So `kind` selects
*which timer* the entry is scheduled against, and 0 / 5 are two of at least six.
The entry fires only when its `t` lies between the two clock readings — the
classic "which events did this frame cross" test, with one array holding the
previous value and the other the current. 🟡 Which array is which follows from
the comparison directions (`A[kind] > t` skips; `t >= B[kind]` skips) and is
stated as a reading of those two branches, not measured at runtime.
### ✅ And the clock is PER PHASE
Both arrays are fields of the phase object, so each phase has its own timers —
which is why every phase's table restarts at t = 0. That was previously an
inference from the layout; it is now a read.
## 🟡 Not settled
* **This is not what starts the unreachable code.** All 675 targets are already
reachable — **0 are unreached run-starts** — so the ~15 % gap stands.
* **Whether the schedule is per-phase-clock or mission-clock** is not
established; each phase's table restarts at t=0, which suggests per-phase, but
that is an inference from the layout, not a read of the consumer.
* **The consumer is unread.** Nothing here shows the engine walking this table;
the decode rests on the structural checks above.
* **What the six clocks are.** `kind` indexes arrays at `[phase+88]` and
`[phase+104]`; only indices 0 and 5 are ever used by the data, and neither
array's contents were traced to a source.
* **`sub_822737C8`**, which actually starts the routine, is not read — it is
presumably the same spawner `start_coroutine` uses, but that is unchecked.