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/mission-phase-timers.md
Sylpheed RE agent 0b06e29d58 re: Stage 02 phase 1 read as a timeline; what arms the mission clock
Disassembling all 25 timer triggers in threshold order turns the phase into
readable script: fade, BGM, then squadron deployments at 30/60/90/120/170/210/
240s each as deploy + move_order + objective_marker, with radio messages
interleaved and a late block at 1020-1170s.

Answers the open question from the previous entry: timer 0 is armed by
timer_set(1200, 180) followed by timer_resume, fired at 4.0s on the phase-intro
clock (timer 5), which is already running when the phase begins. So the mission
clock has a 1200-second limit.

And it independently confirms the arrival measurement: the timer0 @ 170.0s
trigger deploys symbol index 0x01 = ADN110, the first of the three squadrons the
phase-1 condition polls. The live run measured those three going active at
~155-165s of mission time, and the route table also says 170. Three independent
sources agree -- the route table, the trigger table, and the running game.

Open: whether a coroutine can re-arm its own trigger, and timer_set's second
argument (180), for which 'warning threshold' is a guess rather than a finding.
2026-08-25 20:43:07 +00:00

120 lines
5.2 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.
# What starts a phase's coroutines: a timer table, scanned every frame
Status: ✅ mechanism and table format, verified against the disassembly and all
28 scripts. This answers the question every phase experiment has been circling.
## ✅ The scanner
`sub_822748D0`, called from `ScriptPhase::Update` each frame:
```
822748e0 lwz r11, 240(r30) ; the table at [phase+240]
822748e4 lwz r10, 4(r11) ; record count
822748f4 addi r31, r11, 20 ; first record (+8 header, +12 into it)
822748fc lwz r11, 0(r31) ; TIMER INDEX (rec+20)
82274900 lfs f0, -8(r31) ; THRESHOLD, f32 (rec+12)
82274904 lwz r10, 104(r30) ; PREVIOUS timers [phase+104]
82274910 fcmpu f13, f0 / bgt ; skip if prev > thr
82274918 lwz r10, 88(r30) ; CURRENT timers [phase+88]
82274920 fcmpu f0, f13 / bge ; skip if thr >= cur
8227492c lwz r5, -16(r31) ; COROUTINE OFFSET (rec+4)
82274930 lwz r4, 232(r30) ; phase code base
82274934 bl 0x822737C8 ; START THE COROUTINE
8227493c addi r31, r31, 24 ; stride 24
```
So each record says: **when timer *i* crosses *t* seconds, start the coroutine at
`codebase + off`** — a **rising-edge** test, `prev ≤ t < cur`, so it fires once.
`[phase+88]` and `[phase+104]` are the current and previous copies of the
32-entry float register file; `sub_822710D0(phase, dt)` copies cur→prev and adds
`dt` to the running ones each frame. **They are timers, in seconds.**
## ✅ The table, and where it comes from
The mission-level bytecode's `begin_phase` (op `0x83`) carries five operands; the
fourth is this table's offset and the fifth is the phase's end-event routine.
`sub_82270DF8` stores them as `[phase+240] = base + w4` and `[phase+236] = w5`.
On disc the table is tagged constants, same `<len><op>` shape as the bytecode
(tags `0x819` = int, `0x81A` = float — both past the ISL dispatcher's
`cmplwi 0x18` bound, so they are data and never execute):
```
tbl+0 : 0819 <count>
rec+0 : 0819 <coroutine offset>
rec+8 : 081A <threshold seconds, f32>
rec+16 : 0819 <timer index>
```
Verified here on Stage 02's three phases — counts **25 / 13 / 18**, and the tag
triple `(0x819, 0x81A, 0x819)` correct in **25/25, 13/13, 18/18** records:
```
phase 1 base 0xE4 tbl 0x14848 end 0x1482C
rec0: off 0x2B20 thr 0.0 timer 5
rec1: off 0x2B88 thr 0.5 timer 5
rec2: off 0x2BFC thr 1.0 timer 5
```
Only two timers are ever used corpus-wide: **0** (thresholds 1–1170 s — the
mission clock) and **5** (0/0.5/1/4/5 s — a phase-intro clock).
## 🔑 Why this matters: it explains the poke results
Two experiments set a squadron's state to "destroyed" and watched nothing happen
([script-runtime-probe](script-runtime-probe.md)). The leading explanation was
that the condition coroutine "is not polling" — **this is why.** A phase's
coroutines are **started on a schedule**, by timer crossings. They are not
running continuously waiting to notice a state change, so writing state between
firings changes data nobody is looking at.
It also reframes the arrival timetable: the routes' `t=170` entry
([mission-wave-arrivals](mission-wave-arrivals.md)) and these thresholds are the
same kind of thing — **the mission is substantially a timeline**, with unit
predicates deciding *what* happens at each scheduled point rather than *when*.
## ✅ Stage 02 phase 1, as a timeline — and what arms the clock
Disassembling all 25 triggers in threshold order turns the phase into a script
you can read:
```
timer5 @ 0.0s fade in
timer5 @ 0.5s play_bgm(0x3ED)
timer5 @ 1.0s damage_unit(TCN131, 0)
timer5 @ 4.0s timer_set(1200, 180) ; timer_resume ; set_flag(0) <-- arms timer 0
timer0 @ 1.0s radio 0x52, 0x53
timer0 @ 30.0s deploy + move_order(TCN105 group) ; radio 0x55
timer0 @ 60.0s radio 0x56
timer0 @ 90.0s deploy + move_order(idx 0x08) ; radio 0x57
timer0 @ 120.0s deploy + move_order(idx 0x43) + objective_marker ; radio 0x58, 0x59
timer0 @ 170.0s deploy + move_order(idx 0x01 = ADN110) ; radio 0x5A, 0x5B
timer0 @ 210.0s deploy + move_order(idx 0x33) + objective_marker
timer0 @ 240.0s deploy + move_order(idx 0x19)
timer0 @ 270/300/330 s radio only
timer0 @ 1020/1080/1140/1170 s radio only
```
### ✅ What arms the clock — the open question, answered
**`timer_set(1200, 180)` then `timer_resume`, at 4.0 s on the phase-intro
clock.** So timer 0 is started by the phase's own intro coroutine, with a
**1200-second limit**; timer 5 is already running when the phase begins. That
closes "which built-in arms or resets each timer" for the common case.
### ✅ It independently confirms the arrival measurement
The `timer0 @ 170.0s` trigger deploys **symbol index 0x01 = `ADN110`** — the
first of the three squadrons the phase-1 condition polls. The live run measured
those three flipping to *active* at roughly **155–165 s of mission time**
([mission-wave-arrivals](mission-wave-arrivals.md)), against a route table that
also says `170`. Three independent sources — the route table, this trigger
table, and the running game — agree.
## 🟡 Not settled
* Whether a coroutine started this way can **re-arm** its own trigger.
* `timer_set`'s second argument (`180`) — a limit and a warning threshold is the
obvious reading, but it is **not** established.