Systems

A system is one piece of a mind's logic. It reads some components, does its work, and writes others. Systems never call each other — they only meet in the world.

Each one declares exactly what it touches. That declaration is the whole program's structure, and the engine reads it to work out everything else: what runs first, what runs in parallel, what runs at all this tick.

What a system declares

// src/body/odometry.system.ts
export default defineSystem({
    on: ["clock"],              // what runs it
    reads: ["imu"],             // this tick's values — their writers run first
    recalls: ["pose"],          // last tick's values — no ordering
    writes: ["pose"],           // what it may change

    run({ now }) {
        // …
    },
})
FieldMeans
readsThis tick's values. Every system that writes one runs before this one.
streamsEvery value written since it last ran, not just the latest.
recallsLast tick's values. No ordering — how a feedback loop is closed.
writesWhat it may change. One writer per component.
usesThe services it calls. Only an async system may.
onWhat runs it. Absent, a change to what it reads does. clock means every tick.
thread"worker" runs it on another core, with the same result.
lagHow many ticks its work takes, for work longer than one.

A system's name is its path: src/body/odometry.system.ts is body/odometry.

The engine orders them

Writers run before readers. From the declarations alone, the engine sorts every system into the order the data needs:

one tick, ordered
feel→imu→odometry→pose→map

Nobody wrote that order down, so it can never drift from the code. A cycle — two systems each reading what the other writes this tick — is refused, with a fix: make one of them recalls.

It runs when something changed

A system runs in a tick only if something that runs it changed since it last ran. Nothing changed, it doesn't run. By default that is what it reads; on replaces it — a system that should look ten times a second names the beat it looks on, not the picture.

Checked before the first tick, enforced every run

Before anything runs, every declaration is checked, and every problem is reported at once:

  • two systems writing one component;
  • a component read that nothing writes — it would read nothing forever;
  • a system run by something nothing writes — it would never run;
  • a cycle.

Then every run is watched. Reading or writing anything a system didn't declare fails that run, every time, whether or not it mattered. A failed run is recorded with its error and the values it read.

Fast and slow work, in one mind

A mind has reflexes and it has thought. Systems come in three speeds so the slow never holds up the fast:

RunsFor
PlainInside the tick, in order.Most work — fast, every tick.
Worker — thread: "worker"Inside the tick, on another core.Heavy pure work that still fits a tick.
Async — async run()Beside the tick. Its writes land when it finishes.Waiting on something — a model, the kernel.

A depth model that takes forty milliseconds can't sit in a tick that has fifty. As an async system it doesn't have to: the tick carries on, and the answer lands on a later tick, about the picture it was asked about.

Keep a system thin

A system file is glue: what it touches, a few lines reading the world, a call to plain functions, a write. The real work lives beside it, in functions that take values and return values — testable without a world.

body/
├── odometry.system.rs    # reads the world, calls odometry::step, writes the pose
├── odometry.rs           # odometry::step — plain functions, no world