# ✅ A ScriptPhase owns 32 STOPWATCHES, and they count SECONDS `read_freg`'s unit was the open question: the corpus knew the built-in returns a float out of `[phase+88]`, and that *seconds* was the unit had only ever been **inferred** from the values a mission compares it against (210, 300, 600, 1200). It is now read, end to end, from the code that advances it. ## The three parallel arrays The phase initialiser `sub_82270DF8` clears three arrays in one unrolled loop (`0x82270EC0`–`0x82270FAC`), eight words per iteration, base `12, 44, 76, 108` — **32 entries each, offsets 0…127**: | field | type | init | meaning | |---|---|---|---| | `[phase+88][i]` | f32 | 0.0 | the timer's **current** value | | `[phase+104][i]` | f32 | 0.0 | its value **last frame** | | `[phase+120][i]` | int | 0 | **1 = running** | `sub_822700C0` clears the same three the same way (the phase reset). ## What advances them — `sub_822710D0(phase, dt)` Called from **`ScriptPhase::Update` (`sub_82263408`) at `0x82263480`**, with Update's own float argument passed straight through (`fmr f30,f1` … `fmr f1,f30`). The body is the same eight-way unrolled shape, and per entry it is exactly: ``` 82271100 stfsx f0, r11, r9 ; prev[i] = cur[i] ([+104] <- [+88]) 82271104 lwz r10, 120(r31) 8227110C cmpi cr6, 0, r10, 1 82271110 bc 4, eq, ... ; skip unless running[i] == 1 8227111C fadds f0, f31, f0 ; cur[i] += dt 82271120 stfsx f0, r11, r10 ``` ## The three built-ins are one family — start / read / stop `isl-builtins.md` groups 8, 9 and 93 together; that grouping is **confirmed**, but the names are wrong. All three are inline in the dispatch switch `sub_82272220`, and all three bounds-check `0 <= local[0] < 32` — the array length above. | built-in | corpus name | what it actually does | |---|---|---| | 8 | `set_flag` | `cur[i] = 0.0` (the constant at `0x8209FD28` is literally `0.0`) **and** `running[i] = 1` — *start / restart timer i*; returns 1 | | 9 | `read_freg` | returns `cur[i]` — as a **double** into `[phase+176]`, not `[phase+164]` | | 93 | `clear_flag` | `running[i] = 0`; **`i == -1` clears all 32** (`0x82273078`, loop to 128 step 4) — *stop*, without resetting the value | `clear_flag` is called **133 times disc-wide and every one passes −1**, always in the sequence `reset_phase_threads ; timer_stop ; clear_flag(-1)` — a phase teardown. So `[phase+120]` is not "a different 32-entry array" belonging to some other family, as the previous iteration's note allowed for: it is the *running* column of this one. ## ✅ The unit: SECONDS, by four constants `dt` comes from a 48-byte timing singleton at `[0x828F35B4]` (constructed by `sub_8231A830`), field `+8`. Its value is built in the frame loop `sub_821AA1B0` and in `sub_821A49A8`: ``` 821AA2F8 lfs f0, -24328(r11) ; 0x8289A0F8 = 0.016666668 == 1/60 821AA300 fmuls f13, f31, f0 ; f31 = frames elapsed -> SECONDS 821AA308 lfs f0, 5772(r25) ; 0x820A13B4 = 10000.0 821AA30C fmuls f0, f13, f0 821AA310 fctiwz f0, f0 ; ticks = round(seconds * 10000) [100 us] 821AA32C cmpi cr6, 0, r29, 3200 ; clamp -> 0.32 s max frame 821AA390 lfs f0, -24324(r11) ; 0x8289A0FC = 1e-4 821AA398 fmuls f0, f13, f0 821AA39C stfs f0, 12(r11) ; [obj+12] = ticks * 1e-4 -> SECONDS again ``` and `sub_821A49A8` writes the two scaled copies the game actually reads — `[obj+16] = ticks·s24·1e-4·s44` and **`[obj+8] = ticks·s40·1e-4`**, where `s24 = s40 = s44 = 1.0` at construction (`0x8208583C`). `[obj+16]` is the field the flight/physics code reads (≈40 sites in `0x8238…`–`0x823B…`); `[obj+8]` is the one the script VM gets. The round-trip is what makes this **non-circular**: the value is produced as `seconds × 10000` and consumed as `× 1e-4`. It is seconds on both ends, and the clamp is `3200` ticks = **0.32 s** — a frame-time ceiling, which is only a sensible number in seconds. > ⇒ **`read_freg(i)` returns the number of SECONDS since `set_flag(i)`.** ## ✅ The refutation test — and it passes 675 / 675 If these really are stopwatches that only run once started, then every **timeline** entry ([isl-schedule](isl-schedule.md)) must name a timer its own phase starts — otherwise `cur` and `prev` both stay `0.0` and the walker's fire test `prev <= t < cur` can never be true, and the entry would be dead. | | | |---|---| | timeline entries whose `kind` is started by `set_flag` **in the same phase** | **675 / 675 = 100 %** | | entry-weighted control (a random index 0…31) | **11.2 %** | | `read_freg` indices started in the same phase | 82 / 83 | Every phase opens with `set_flag(5)` and `set_flag(0)` — which is why `kind` only ever takes the values 0 and 5. Indices used disc-wide run 0…21, inside the bounds check. ⚠️ **The first run of this test scored 433/675 and was wrong.** `isl.dis` defaults to `stop_at_ret=True`, so a `set_flag` sitting at the *start* of a coroutine lost its operand staging to the preceding `ret` and came back unresolved. With `stop_at_ret=False` all 172 `set_flag` arguments resolve. The failure looked exactly like a real refutation (the misses were all `kind = 0`, in a coherent block of stages) — checked before believing. ## 🟡 What this leaves * ~~The **naming**.~~ ✅ **RENAMED (2026-08-27).** `isl.py` now emits **`stopwatch_start` (8) / `stopwatch_elapsed` (9) / `stopwatch_stop` (93)**; built-ins 123–127 keep `timer_*` for the *other* clock ([isl-mission-timer](isl-mission-timer.md)), which acts on five scalar fields rather than a 32-entry bank. The rename touched **84 lines across 5 artefacts and every one pairs exactly** with its old-name partner once column padding is normalised — nothing but the three names changed, and this file's own artefact reproduces the same 675/675, 11.2 %, 82/1 numbers under the new labels. ⚠️ Older prose in the corpus still uses the old names where it is quoting what was believed at the time; that is deliberate. * The **one exception**: `read_freg(15)` in Stage 06 phase 1 is not started by a `set_flag(15)` in that phase. Cross-phase carry-over or a start in the unreached code — not chased. * `s24` / `s40` / `s44` are never written after construction *in any site read here*; a game-speed or slow-motion setter was not searched for. * The 31 `read_freg` sites whose index is computed rather than immediate.