Triggers

The scheduling layer — controlling when parts of a program run

Triggers control when parts of a Utomata program run.

While fields and formations describe values, triggers act on the scheduler: they can stop or resume fields, reset their state, advance them manually, or perform batch-output actions at specific moments.

Triggers are declared with the @ sigil:

@freeze(step == 16) {
  stop(#A);
}

This trigger fires when the global step reaches 16 and stops #A from advancing.

A direct comparison refers to an exact step value. If the clock has already passed 10, this trigger will not fire later. Live edits do not restart the clock; a global reset or set() call onto a tested field is required to return it to 0. This is particularly important during live coding: adding the trigger above after the program has already reached step 500 will not cause it to fire.

A trigger contains one predicate and one or more actions:

@name(predicate) {
  action(...);
  action(...);
}

Triggers are not Utomata expressions. They use their own small syntax for describing scheduler conditions and commands.

Predicates

A predicate determines when the trigger fires.

The simplest form compares the global clock, as in the example above. step is the global scheduler step and begins at 0.

@freeze(step == 16) {
  stop(#A);
}

A trigger may instead follow the local step of a particular field:

@freeze(#A.step == 16) {
  stop(#A);
}

This distinction matters when fields run at different rates. The global step advances on every scheduler tick, while #A.step advances only when #A itself advances.

Periodic Predicates

Modulo predicates create repeating events:

@init(step == 0) {
  stop(#A);
}

@sample(step % 10 == 0) {
  step(#A);
}

This fires at steps 0, 10, 20, and so on, effectively emulating every = 10; on #A. This mechanism can be useful to create multi-pass rendering sequences where Field #A might need more than one tick for every tick of #B.

The same form can use a field-local clock:

@sample(#A.step % 4 == 1) {
  ...
}

The interval must be a positive integer, and the phase must fall within that interval:

step % 10 == 0   // valid
step % 10 == 9   // valid
step % 10 == 10  // invalid

Clock predicates currently use == only.

Field-Value Predicates

Triggers can also respond to a sampled field value:

@converged(#A[0, 0].x > 0.98) {
  set(#A);
}

A cell predicate contains:

  • an explicit field lookup
  • a component: x, y, or z
  • either < or >

The lookup coordinates are literal normalized field coordinates, not Utomata expressions.

Field-value predicates are evaluated through an asynchronous GPU readback. They therefore use the most recently available sampled value rather than the exact current-frame value. For this reason they are best suited to coarse conditions such as convergence, thresholds, or state changes and should not be relied on for tick-exact timing.

They also fire on the crossing of the condition. If the value rises above the threshold, the trigger fires once. It must fall below the threshold and cross it again before the trigger can fire a second time.

Firing Behavior

The predicate forms differ in how often they fire. A direct clock comparison is a one-shot event: it fires when the condition first becomes true. A periodic predicate fires on every matching step, and a field-value predicate fires on each threshold crossing.

Predicate Behavior synchronous
step == N fires once when the clock reaches N yes
#A.step == N fires once when the field reaches local step N yes
step % N == P fires on every matching global step yes
#A.step % N == P fires on every matching field step yes
#A[x,y].x > v fires on each false → true threshold crossing NO!

Triggers do not currently support boolean composition such as &&, ||, or !. Each trigger contains one predicate.

Controlling Fields

Four trigger actions control fields directly and work during normal realtime execution:

Action Description
set(#A) re-evaluate the field's set expression and reset its local step
run(#A) resume a stopped field
stop(#A) stop the field while preserving its current state
step(#A) advance the field exactly once on the next scheduler tick

Several fields may be targeted by the same action:

@stopAll(step == 100) {
  stop(#A, #B, #C);
}

Set

set() restores a field to the state defined by its set property:

@restart(step == 100) {
  set(#A);
}

It also resets that field's local step to 0.

Stop and Run

stop() pauses recurrent evaluation without removing or clearing the field:

@freeze(step == 20) {
  stop(#A);
}

The field retains its last committed state and can continue to be sampled or rendered.

run() resumes normal scheduling:

@resume(step == 40) {
  run(#A);
}

run() does not itself evaluate the field; it simply allows it to advance normally again.

Step

step() schedules one manual field advancement:

@advance(step == 20) {
  step(#A);
}

The advance occurs on the next global scheduler tick, ignoring the field's local every property.

Batch Actions

Triggers also provide actions for controlling offline and batch execution:

Action Description
log(...) read field data into an output channel or diagnostic log
flush() flush pending output
end() end the batch run
next() advance an input binding or combination
halt() pause batch execution until externally resumed

These actions run only in batch or offline execution. They are ignored during normal realtime execution: a log() trigger produces output as part of a batch run, but not while the same program is running interactively. Input bindings and the next() action are described further in the Input & Output chapter.

Logging

A whole field can be logged:

@snapshot(step % 10 == 0) {
  log(#A, frames);
}

or a single cell can be sampled:

@probe(step % 10 == 0) {
  log(#A[0, 0], values);
}

The second argument names the output channel.

A keyless form is also available for diagnostic output:

@probe(step % 10 == 0) {
  log(#A[0, 0]);
}

Ending a Run

A batch program can terminate itself explicitly:

@finish(step == 300) {
  end();
}

This is useful when the duration of a computation is defined by the source rather than by the host command.

Reference

General syntax:

@name(predicate) {
  action(...);
}

Predicates

Form Description
step == N global step comparison
tick == N alias of step == N
#A.step == N field-local step comparison
step % N == P periodic global trigger
#A.step % N == P periodic field-local trigger
#A[x,y].x > v sampled field-value threshold
#A[x,y].x < v sampled field-value threshold

Clock predicates support ==. Field-value predicates support < and >.

Realtime Actions

Action Description
set(#A, ...) re-seed fields and reset their local step
run(#A, ...) resume fields
stop(#A, ...) pause fields
step(#A, ...) schedule one manual advancement

Batch / Offline Actions

Action Description
log(...) read field or cell data
flush() flush output
end() finish the run
next() advance an input binding
halt() halt until externally resumed

Triggers form the scheduling layer of a Utomata program. They do not compute values themselves; they observe clocks or sampled state and use those conditions to control how computation proceeds.