Real-Time

The parts that must never miss a deadline are written as native Zig tasks. The VM orchestrates around them. A runaway script cannot stop a native task — a preempting timer switches away.

The primitive set

The scheduler carries a hard-RTOS primitive set in arch-neutral code.

PrimitiveMechanism
Priority-inheritance mutexA blocked high-priority waiter boosts the owner to its own priority, restored on unlock. One mutex per task — nested and chained inheritance are not implemented
Deadline timerThe timer is armed at the next event in nanoseconds — the earliest of any sleeper's wake, any periodic release, or a round-robin slice. Not a fixed drum
Bounded timed waitWake on an event or after n nanoseconds, whichever comes first
Deadline-miss detectionA rate-monotonic implicit deadline that counts overruns
CPU-time accountingRun ticks charged to the interrupted task
CPU reservation / temporal isolationPer-task budget and period; a task exceeding its budget is throttled until refill
Software watchdogHeartbeat liveness, so a hung task is detectable
RM schedulability analysisThe hyperbolic bound Π(Cᵢ/Tᵢ + 1) ≤ 2 — all integer arithmetic
High-resolution clockx86 TSC (PIT-calibrated), riscv rdtime, aarch64 CNTVCT/CNTFRQ, ARMv6-M the µs TIMER
Sub-µs busy-waitSpin on the nanosecond clock

x86_64, riscv64 and aarch64 start and run the whole set. The RP2350 starts the deadline-satisfaction pair below — its task table is capped at 8 and the full set wants 12. The RP2040 starts none of them.

The RM analysis is not an admission gate. It is a routine you call to ask whether a task set fits; spawn does not consult it and does not refuse a task set that would not.

The scheduler meets its deadlines

The scheduler's notion of now is a nanosecond clock, and its timer is armed at the next deadline — the earliest of any sleeper's wake, any periodic release, or a round-robin slice. It is not a fixed periodic drum, so no tick sets a floor under the shortest deadline you can ask for. sleep and wait_period take nanoseconds; the tick forms are wrappers over them. All five targets already had the hardware this needs: an absolute one-shot comparator.

The real control law runs as an ordinary periodic task under the scheduler, at 1 kHz on real silicon, and the scheduler holds the deadline:

That is the real control() — the soft-float cascade-PID plus quaternion — with the OS in the loop: scheduling it, accounting for its CPU time, and sharing the chip with the network and the VM flight actors. It is the number that decides whether this is an RTOS — the scheduler releasing a real 1 kHz task on time, on real silicon, with the network and the mission contending for the same chip.

The same control law flies the delivery mission — the island, the drop, the return, the landing — as the hardware-in-the-loop flight controller on the same silicon, over Wi-Fi. That runs at the HIL frame rate. The 1 kHz figure above is the scheduled RT task: two demonstrations of one control law, not one loop.

A synthetic pair — C/T = 2/10 and 5/40, 32.5% utilization, a set the hyperbolic bound calls schedulable — also runs on x86_64, riscv64, aarch64 and the RP2350, meeting every deadline. The analysis prints its verdict before the set runs, so prediction and measurement check each other. But it is a set chosen to pass. The real control law is not.

The measurements

The control law's worst case, measured on both boards. Each board runs control() at boot over a 20,000-input adversarial sweep of the reachable flight envelope, interrupts masked, at 125 MHz — so the two counts are directly comparable:

Neither core has a cache, branch prediction, or speculation, so the measured maximum is the engineering WCET — there is no hidden tail. The sweep is deterministic and reproducible on the board.

The scheduler's own response floor, measured on riscv64: ~1.1 µs interrupt-driven with the Sstc timer, ~3.5 µs when the timer costs a firmware call. That is the cost of the OS getting to your task, and it is the number to weigh against your period.

Under deterministic virtual time (-icount shift=0) an absolute-deadline wait shows zero jitter — the mechanism is exact, and the jitter seen on a loaded host is the host, not the OS.

On real x86 the residual is firmware system-management interrupts (non-maskable) plus cache and micro-architectural effects. Below that, determinism needs dedicated hardware or an FPGA soft core.

The control loop's worst-case execution time

A hardware-in-the-loop flight controller runs on Lambda OS. The controller is real — real silicon, real compute, real motor outputs — driven by a simulated plant. This is how flight control is verified before a real airframe: it pins down the deadline behaviour, deterministically and repeatably, which no field flight can. The verification here is HIL-first by design.

The mission it flies is a cargo delivery. The full demo — the fault responses, the pre-flight gate, the HIL rig — is on the Flight Control page.

The number that decides whether a deadline is safe is the worst-case execution time of the control math itself — the soft-float cascade-PID plus quaternion. Each board measures its own at boot, interrupts masked so the delta is pure compute, over a 20,000-input adversarial sweep of the reachable flight envelope: a unit attitude quaternion anywhere on the sphere, ±35 rad/s gyro, ±400 m position error, the full 1.0–1.4 kg mass range. The inputs are chosen so the square root and divide take their data-dependent worst path.

Why a measured maximum can be called the WCET here

The Cortex-M0+ has no cache, no branch prediction, no speculation and no maskable SMIs. There is no hidden tail behind the measurement. The only variance is the data-dependent soft-float cost, which the mission exercises broadly.

On a cached, superscalar chip the measured max sits below the true worst case and you need static analysis; on the M0+ the measured maximum is the engineering WCET. That is why "slow but deterministic" wins for a hard deadline: raw throughput is poor — fib(25) interprets in 24 s — yet the deadline-bearing math has a 190 µs worst case. The slowness is confined to the VM interpreter, deliberately kept off the deadline path.

What these measurements cover