Built-in 9 is inline in its stub, with no vtable slot. It bounds-checks
0 <= local[0] < 32 and then:
822725E8 lwz r10, 88(r31) ; [phase+88]
822725EC rlwinm r11, r11, 2, 0, 29 ; i * 4
822725F0 lfsx f0, r11, r10 ; a FLOAT
isl-builtins.md already recorded read_freg(i) -> [phase+88][i]. What is new is that
this is exactly the array, and exactly the indexing, that the TIMELINE walker uses for
its `kind` field. So kind is not a selector over six private clocks -- it is an index
into the same 32-entry float register file the script reads with read_freg(i). A
clear condition like `read_freg(0) < 1200` and a timeline entry with kind = 0 read the
same register. My "six clocks" framing came from the data only ever using indices 0
and 5, and it is corrected in isl-schedule.md.
Recorded as a nuance rather than a correction, because it is half-read: clear_flag
(93) clears a DIFFERENT 32-entry array, [phase+120], looping to 128 in steps of 4 --
not [phase+88]. set_flag (8) does bounds-check against [phase+88], but only its first
13 instructions were read, so the corpus's grouping of 8/9/93 as one "32-entry file"
family is neither confirmed nor overturned here.
This does NOT settle what the item actually asked: read_freg returns a float out of a
register file, and that the unit is seconds remains INFERRED from the 210/300/600/1200
gate values, exactly as it was before. Also still open: what writes [phase+88], which
has 73 writers in the ISL region alone.
All artefacts regenerate byte-identical; documentation only.
151 lines
6.3 KiB
Markdown
151 lines
6.3 KiB
Markdown
# ✅ Each phase carries a TIMELINE — 675 scheduled routines across the disc
|
||
|
||
The trailing data table found at the end of every phase region
|
||
([isl-stream-entry-points](../isl-stream-entry-points.md)) is decoded. It is the
|
||
mission's **scripted event schedule**.
|
||
|
||
## Layout
|
||
|
||
After the phase's code ends — at the first value of that phase's mission-level
|
||
`0x1883` record — comes a run of 8-byte typed records, tag `0x19` = int,
|
||
`0x1A` = IEEE float:
|
||
|
||
```
|
||
int N -- entry count
|
||
N x [ int offset ; float t ; int kind ]
|
||
```
|
||
|
||
`1 + 3N` matches the record count in **every** phase measured: Stage 02's three
|
||
phases hold 76, 40 and 55 records for N = 25, 13 and 18.
|
||
|
||
## ✅ The checks
|
||
|
||
| | |
|
||
|---|---|
|
||
| schedule entries disc-wide | **675** |
|
||
| `0x1A` float records disc-wide | **675** — the same number, independently counted |
|
||
| offsets landing on the instruction stream | **675 / 675 = 100.0 %** |
|
||
| control: random 4-aligned offsets | 33.3 % |
|
||
|
||
The float count and the entry count are derived by different routes and agree
|
||
exactly, and every single offset resolves. `kind` is **0** (556) or **5** (119).
|
||
|
||
## ✅ The floats are seconds
|
||
|
||
The distribution is unmistakable: 0, 0.5, 1, 4, 5, 30, 50, 60, 90, 120, 150,
|
||
170, 180, 210, 240, 270, 300, 330, 360, 420, 570, 1020, 1080, 1140, 1170 …
|
||
mission times, not fractions.
|
||
|
||
And the targets are what a schedule would point at — small one-shot coroutines:
|
||
|
||
```
|
||
002C04: set.i local[0] = 1
|
||
002C1C: set.i local[4] = 1
|
||
002C34: set.f local[8] = 0.4
|
||
002C50: call builtin106(0x1, 0x1, 0.4)
|
||
002C5C: call end_coroutine
|
||
```
|
||
|
||
## 🟡 A runtime cross-check — consistent, and not proof
|
||
|
||
The closed `REMAINING OB` work measured, on the emulator over n=5 runs, that
|
||
Stage 02's squadrons arrive at **t = 0, 120 and 210 s**. All three times are in
|
||
phase 1's static schedule, and **120 and 210 each appear twice**, which is what
|
||
two squadrons arriving together would look like.
|
||
|
||
⚠️ Stated as consistency rather than confirmation: these are round numbers, and
|
||
phase 1 has ~22 distinct times spread over 0–1170, so three specified round
|
||
values all being present is not by itself unlikely. The **doubling** is the
|
||
sharper detail, and it was not predicted in advance.
|
||
|
||
## Artefacts
|
||
|
||
`data/isl-stage02-schedule.txt` and `data/isl-schedule-all.txt` (all 28 stages,
|
||
675 entries), generator `isl_report.py schedule`. The four earlier artefacts
|
||
regenerate byte-identical.
|
||
|
||
## ✅ The CONSUMER — `sub_822748D0`, called from `ScriptPhase::Update`
|
||
|
||
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
|
||
|
||
🔑 **(2026-08-27) And the array it indexes is the one `read_freg` exposes.**
|
||
Built-in 9's stub bounds-checks `0 <= local[0] < 32` and then
|
||
|
||
```
|
||
822725E8 lwz r10, 88(r31) ; [phase+88]
|
||
822725EC rlwinm r11, r11, 2, 0, 29 ; i * 4
|
||
822725F0 lfsx f0, r11, r10 ; a FLOAT
|
||
```
|
||
|
||
— exactly the array and the indexing the timeline walker uses. So `kind` is not a
|
||
selector over some small private set of clocks: **it is an index into the same
|
||
32-entry float register file the script reads with `read_freg(i)`.** A clear
|
||
condition like `read_freg(0) < 1200` and a timeline entry with `kind = 0` read
|
||
the same register.
|
||
|
||
🔴 The "six clocks" framing below was mine and is too narrow — the file has **32**
|
||
slots; the timeline data merely never uses more than indices 0 and 5.
|
||
|
||
`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.
|
||
* **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.
|