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-mission-timer.md
Sylpheed RE agent d79f59d24e re: timer_set's second argument is a SECOND countdown, not a warning threshold
mission-phase-timers.md left 180 open as "a limit and a warning
threshold is the obvious reading, but not established".  Reading
sub_822639B8 -- ScriptPhase::Update 0x82263528, same dt as the
stopwatch bank -- settles it the other way:

    if running: [+304] += dt
    if armed:   [+308] -= dt   while [+308] > 0
                else [+312] -= dt, clamped at 0

[+312] is never compared with [+308]; it is decremented, and only in
the A<=0 arm.  Two sequential countdowns.  Disc-wide the second
argument is 180 in all 29 timer_set sites while the first varies
(600 x19, 1200 x8, 900, 1800).

Built-ins 123-127 are vtable slots 90-94 on five scalars at
[phase+304..320].  125 and 126 have ZERO call sites in all 28 scripts:
the script arms, starts and stops this clock but never reads it.

Corrects mission-phase-timers.md, which merged this clock with
stopwatch 0 -- timer_resume starts [+304], set_flag(0) one instruction
later starts the stopwatch the timeline's kind=0 reads.

Docs only; all seven ISL artefacts regenerate byte-identical.
2026-08-27 09:03:18 +00:00

123 lines
5.0 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.
# ✅ The MISSION timer is a second, separate clock — and `timer_set`'s second argument is a second COUNTDOWN
`mission-phase-timers.md` left this open: "`timer_set`'s second argument (`180`)
— a limit and a warning threshold is the obvious reading, but it is **not**
established." It is established now, and **the obvious reading is wrong**.
This clock has nothing to do with the 32 stopwatches in
[isl-timers](isl-timers.md); it is five separate scalar fields on the same
ScriptPhase, driven by its own updater.
## The five fields
Zeroed together by `sub_8225FEF8` (`0x82260084`–`0x82260098`):
| field | type | meaning |
|---|---|---|
| `[phase+304]` | f32 | **elapsed**, counts **up** |
| `[phase+308]` | f32 | **countdown A**, counts **down** |
| `[phase+312]` | f32 | **countdown B**, counts down *only after A reaches 0* |
| `[phase+316]` | int | **running** — gates the elapsed counter |
| `[phase+320]` | int | **armed** — gates the two countdowns |
## The five built-ins
All are stubs into the ScriptPhase vtable, slots 90–94
(`0x8226C690`–`0x8226C6F0`):
| built-in | slot | what it does | calls disc-wide |
|---|---|---|---|
| 127 `timer_set(a, b)` | 94 | `armed = 1; A = (f32)local[0]; B = (f32)local[1]` | 29 |
| 123 `timer_resume` | 90 | `running = 1` | 40 |
| 124 `timer_stop` | 91 | `running = 0; armed = 0` | 170 |
| 125 `timer_reset` | 92 | `elapsed = A = B = 0.0; armed = 0` | **0** |
| 126 `timer_elapsed` | 93 | returns `elapsed` as a double in `[phase+176]` | **0** |
`timer_set` reads its two arguments as **doubles** at `0(r4)` and `8(r4)`, `r4`
being the converted locals array — so `local[0]` and `local[1]`.
## ✅ The update — `sub_822639B8(phase, dt)`
Called from **`ScriptPhase::Update` at `0x82263528`**, with **`fmr f1, f30`** —
the *same* `dt` the stopwatch bank gets at `0x82263480`. So this clock counts
the same **seconds** ([isl-timers](isl-timers.md)), and `600 / 900 / 1200 / 1800`
and `180` are 10 / 15 / 20 / 30 minutes and 3 minutes.
```
822639C8 if [+316] != 0: [+304] += dt ; elapsed counts UP
822639E4 if [+320] != 0:
822639F4 if [+308] > 0.0: [+308] -= dt ; A counts down ...
82263A00 else: [+312] -= dt ; ... then B does
82263A10 if [+312] < 0.0: [+312] = 0.0
```
### 🔴 That refutes "a limit and a warning threshold"
A threshold would be **compared** against the countdown. `[+312]` is never
compared with `[+308]` anywhere. It is *decremented*, and the `bc 12, gt`
at `0x822639FC` jumps **away** to the `A -= dt` arm — so `B -= dt` runs **only**
in the `A <= 0` branch. The two are **sequential countdowns**: a per-mission
period, then a fixed second period.
## ✅ Every mission agrees, and the second value never varies
29 `timer_set` sites across the disc:
| first argument | sites |
|---|---|
| 600 | 19 |
| 1200 | 8 |
| 900 | 1 (Stage 26) |
| 1800 | 1 (Stage 24) |
| **second argument** | **180 in all 29** |
A per-mission limit that varies, followed by a fixed 3-minute stage. Note this
constancy is *consistent* with the sequential reading and does not by itself
establish it — the disassembly above is what does.
## ✅ What it feeds — and what it does not
Each frame the updater allocates a 32-byte message (type tag `0xAB03E5BA`,
vtable `0x820A8D68`) and posts it to the bus at `[0x828F35DC]`, carrying:
```
+24 = [phase+304] elapsed
+28 = [phase+312] countdown B
+16 = running ([+316] != 0)
+20 = expired ([+320] == 1 && [+308] <= 0.0)
```
`expired` is exactly "countdown A has run out" — the point at which B starts.
🔑 **The script never reads this clock.** `timer_elapsed` and `timer_reset` have
**zero call sites in all 28 scripts**; the script only *arms* (127), *starts*
(123) and *stops* (124) it. Its only consumer read here is the posted message.
## 🔴 A conflation in `mission-phase-timers.md`, corrected
That page says timer 0 "is started by `timer_set(1200, 180)` then
`timer_resume`, with a 1200-second limit". Those are **two different clocks
armed back to back**:
```
timer_set(1200, 180) -> this object: A = 1200, B = 180, armed
timer_resume -> this object: elapsed starts counting
set_flag(0) -> STOPWATCH 0 starts (isl-timers.md)
```
The timeline's `kind = 0` reads stopwatch 0, not `[phase+304]`. The two start in
the same coroutine one instruction apart, which is why they read almost the same
value and the conflation was invisible. **The 1200-second limit belongs to the
mission timer; it is not stopwatch 0's limit.**
## 🟡 Not settled
* **Who subscribes to `0xAB03E5BA`.** The literal appears at exactly one site —
the producer — but `0xAB03` is the high half of *many* message tags in this
image and dispatch is not by literal compare, so a single occurrence is **not**
evidence that nothing consumes it. Whether running out of time ends the mission
is therefore unread.
* What B counting to zero does. Nothing observed acts on it.
* Whether `timer_stop`'s 170 sites are all phase teardown, or some are a real
mid-mission stop.