Per-object mission script scheduler
The native dispatcher and wait/delay machinery now execute in a controlled Unicorn fixture, including a trace using the first mission's actual instruction bytes. This establishes per-object timing; it does not establish the order of all objects in a live frame or simulate mission completion.
Object state and dispatch
| Object offset | Meaning | Evidence |
|---|---|---|
| +58 | Saved script continuation | BF9C, C45C |
| +5A | Checkpoint continuation; also reused by ownership transfer | C1BA, C1E1, C2EA |
| +5C | Rounded coarse clock at checkpoint | C1BD–C1CA |
| +5E | Script deadline, wrapping 16-bit ticks | BF97, C459 |
BF97 receives the current comparison clock in BX. It subtracts the deadline through CMP and returns if the sign flag is set. This is a wrapping half-range comparison, not an unsigned comparison or x86 signed-less-than condition. Equality is runnable. BX is saved in the self-modifying immediate C2D9 for subsequent dispatches in the same invocation.
BFA9 fetches an opcode and pushes its own address as the handler's return continuation. Normal handlers RET into the next opcode. C459 instead removes that continuation, saves SI and re-enters BF97. Consequently a delay need not yield to the caller: an overdue script can execute several instructions in a single invocation.
Opcode 22 multiplies its byte operand by the shared header multiplier at F002 and adds that duration to the existing deadline. It does not set deadline = now + duration. Opcode 23 stores C50F (the resident stop script) as continuation, stores the captured comparison clock as deadline and unwinds to the caller. This stops script progression, not the object's movement or existence.
Checkpoints and waits
Opcode 1A saves SI immediately after itself. Its coarse timestamp is the 24-bit clock formed from BF8 and 3DB3, rounded by adding bit 7 before division by 256. All arithmetic wraps to the native width.
A failed wait at C1E1 rewinds SI to +5A and adds 8 * multiplier to the deadline. With multiplier 50, retries are 400 ticks apart, approximately 0.8 seconds. A late invocation can perform multiple retries immediately to catch up. Instructions between the checkpoint and failing wait can therefore execute repeatedly; they are not necessarily one-shot setup.
Opcode 1B adds operand * 8 to the saved coarse timestamp, compares against the current unrounded coarse clock and proceeds only when the difference is negative. Equality still waits. Each operand unit is therefore 2,048 fine ticks (about 4.096 seconds at 500 Hz), subject to rounding and retry cadence. This is a different unit from opcode 22's multiplier-scaled delay.
The objective wait (1E) also uses this retry path. The fixture executes its native cell-list/counter test with an exhausted cell list and controlled outstanding-object count.
First mission controlled trace
Actual script bytes from 04_000/0, resource offsets 002D–0037, are relocated into fixture memory without changing operands. Objective completion is supplied by the fixture, not generated by combat. The counted text is replaced with a short synthetic string after the real empty/nonempty layout; text notification is stubbed.
| Clock tick | Native result |
|---|---|
| 1000 | Establish checkpoint; two objectives remain; schedule retry for 1400 |
| 1399 | No script execution, even though the fixture has now cleared objectives |
| 1400 | Objective wait passes; delay 500 ticks until 1900 |
| 1899 | No script execution |
| 1900 | Consume empty message without delay; display next message until 3900; advance continuation deadline to 4000 |
| 3999 | No script execution |
| 4000 | Execute stop opcode |
Message lifetime is computed from the current display clock, whereas script delay advances its previous deadline. They run in parallel but can have different starting times after a late update. The viewer now explains this distinction.
Verification and remaining work
Run PYTHONPATH=/tmp/darker-python python3 tools/verify_mission_scheduler.py.
Machine-readable results cover 74 native cases: 36 deadline boundaries, 18 catch-up cases, eight checkpoint rounding cases, three objective retry cases and nine coarse-time wait cases, plus the first mission trace. Tests include clock wrap and the half-range boundary. The existing message-handler checks remain separate.
Reserve activation, exhaustion boundaries and ownership/context instructions are now covered in a separate native analysis. Native update-order checks now establish category traversal, activation eligibility and the late context gate. Still to trace: the full owner/context lifecycle and SI contract, clock selection/pause interaction, and complete mission state transitions. The isolated fixture does not settle the convoy route discrepancy.