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 orset()call onto a tested field is required to return it to0. This is particularly important during live coding: adding the trigger above after the program has already reached step500will 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, orz - 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.