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.
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.
sub_822748D0, called from ScriptPhase::Update every frame, walks the table at
[phase+240]: stride 24, each record carrying a coroutine offset (rec+4), an f32
threshold (rec+12) and a timer index (rec+20). It compares the previous and
current copies of the 32-entry float register file ([phase+104] and [phase+88])
and starts the coroutine at codebase+off on a RISING EDGE, prev <= t < cur, so
each fires once. Verified instruction by instruction.
The table comes from the mission-level begin_phase opcode 0x83's fourth operand
(the fifth is the phase end-event); sub_82270DF8 stores them at [phase+240] and
[phase+236]. On disc it is tagged constants -- 0x819 int, 0x81A float, both past
the ISL dispatcher's bound so they never execute. Verified on Stage 02: counts
25/13/18 with the tag triple correct in every record. Only timers 0 (1-1170s,
mission clock) and 5 (0-5s, phase intro) are used corpus-wide.
This explains the poke results. Two experiments set a squadron to 'destroyed'
and nothing happened; the leading explanation was that the condition coroutine
is not polling. It is not -- coroutines are started on a schedule by timer
crossings, so state written between firings is read by nobody.
It also reframes the arrival timetable: the routes' t=170 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.
Open: which built-in arms or resets each timer.