--- order: 5 title: Runtime Model --- # Runtime Model Understanding *when* and *in what order* a graph runs explains almost every "why did it fire twice / not fire / fire late" question. The model is deliberately simple: GDLink evaluates the whole graph on a fixed beat, in a fixed order, the same way every time. ## One tick, one pass The runtime is driven by a timer that fires at the project's physics rate (60 Hz by default). On **every tick**, the graph runs the pipeline once, in this order: ``` Listeners -> Gates -> Executors (sense) (decide) (act) ``` All listeners evaluate first and produce their pulses, then gates filter those pulses, then executors apply the results. Because the order is fixed and bricks evaluate in a **stable registration order**, the same inputs always produce the same outcome — the model is **deterministic**, which is what makes brick logic reproducible and debuggable. ## Pulses are per-tick A pulse represents a *transition*, not a steady value, and **pulses are cleared at the end of every tick**. A listener emits a positive pulse the tick a condition becomes true and a negative pulse the tick it becomes false — then the slate is wiped. This is why an "On Press" trigger fires once rather than every frame the key is down: the press is an edge, and the edge only exists for one tick. ```{note} If a behavior seems to fire continuously when you expected it once, you're usually reading a *level* (still true) where you wanted an *edge* (just became true). Choosing the right trigger mode on the listener is how you pick between them. ``` ## Only what matters is scanned A graph can hold hundreds of bricks, but the runtime keeps a cache of the **active** listeners — those that are enabled and actually connected to something — and skips the rest each tick. Typical graphs only scan a few dozen bricks per tick, so you can author generously without paying for dormant logic. Disabling a branch (or hiding its [state](states.md)) genuinely removes it from the per-tick cost. ## Messages settle within the tick Object-to-object [Messages](../reference/generated/executors/message_executor.md) don't jump the queue. When an executor sends a message, it's processed *after* brick execution within the same tick, in order — so a broadcast and its reaction stay on a predictable timeline instead of causing mid-pass surprises. (See the [Debugging](../guides/debugging.md) guide for proving a message was actually received.) ## Why this matters in practice - **Reproducible** — same inputs, same result, every run; golden behaviors stay stable. - **Frame-rate independent** — logic runs on the physics beat, so it behaves the same on fast and slow machines. - **Cheap to reason about** — there's exactly one pass per tick, top to bottom; no hidden re-entrancy. ## Where to go next - [Listeners, Gates, Executors](listeners-gates-executors.md) — the three stages of the pipeline in detail - [States](states.md) — how hiding a state removes it from per-tick work - The [Debugging](../guides/debugging.md) guide — closed-loop proof that a tick did what you intended