Input & Output

How values cross the boundary between a program and its host

CAUTION

Input and output channels are an experimental feature and may produce unpredictable or mixed results. Special care is especially advised in batch runs that log data, which can result in large sets of files being written on your hard drive.

Utomata separates computation from the systems that provide data to it and receive data from it.

Fields, formations, and triggers describe what the program computes. Input and output define how values cross the boundary between that program and its host environment.

The division of responsibilities is deliberately strict:

  • the Utomata program decides what data is read or produced and when
  • the host decides where it comes from or where it goes

This keeps the language independent of filesystems, cameras, terminals, and other environment-specific systems.

Input Values

There are two main ways to introduce changing external values into a Utomata program: uniforms for dynamic numerical values, stream markers for externally supplied realtime data, and source markers for modular source code variations.

Uniform values change data already available to the compiled program. Stream markers indicate large external data inputs, such as a webcam video stream. A source marker is placeholder for Utomata source code, and may therefore alter meaningful sections of a program, such as dimensions, topology, expressions, or other structural properties.

Uniforms

Uniforms provide the simplest runtime input mechanism.

They are declared at top level with the $ sigil:

$threshold = 0.5;

and can be used like ordinary values inside expressions:

#A {
  run = lrg(#, $threshold);
}

As elsewhere in Utomata, scalar values are lifted to three component vectors.

Uniform values may be changed by the host while the program is running. Updating a uniform writes a new value into the existing GPU binding and does not require recompilation.

This makes uniforms appropriate for parameters that may change frequently:

$speed = 0.01;
$scale = (4 , 4, 1);
$threshold = -0.5;

Changing the resolution or structure of a field is different. Properties such as dim are compile-time configuration and cannot be driven by a runtime uniform. For those cases, source markers provide the appropriate mechanism.

Input Streams

Streams provide external data that can be sampled by Utomata expressions.

A named stream is referenced through angle brackets:

#cam{
   dim = (64, 48, 1);
   run = <camera>
}

For image-like inputs, this resolves to an input stream that can be sampled by fields or formations.

Streams are independent of field dimensions. Their native resolution is mapped across normalized coordinates when sampled, so a stream does not need to match the resolution of the field reading it. A typical image input can therefore be treated like another spatial data source without first copying it into a field.

The runtime currently supports image streams directly. The same stream abstraction is also used by the desktop host for webcam input, where new image frames are uploaded continuously while the program runs.

Source Markers

The same <name> syntax can also represent a value that is substituted directly into the source before compilation.

This is useful when an input needs to change something structural:

#A {
  dim = <size>;
}

A uniform cannot change dim, because the field dimensions are fixed when the program is compiled. A source marker can, because its value becomes part of the source itself. Changing such a marker therefore requires recompilation.

This is the central trade-off:

$uniform   -> change a runtime value
<marker>   -> change the source

Source markers should normally define a fallback value so the program remains valid before an external sequence begins:

<size> -> 64

The fallback is used while editing or whenever no active input value has been supplied. Without one, a program containing the marker may have no valid source to compile outside the input run.

When a marker's input advances, Utomata substitutes the new value and waits for the resulting program to finish recompiling before execution continues. This makes structural parameter sweeps possible without exposing compilation logic inside the Utomata language itself.

Advancing Source markers

Source input files like .jsonl typically contain several entries.

They can be advanced explicitly by the host, or from a trigger during batch execution:

@advance(step == 100) {
  next();
}

In the desktop app, bare next() advances the next configuration in Project Settings input order: the first used input changes fastest. Unused text markers are skipped. next(name) advances only the named binding, without carrying into another binding.

At an input's upper bound:

Bounds Desktop behavior
RESET Return to the first selected record and carry into the next input
WRAP Return to the first selected record without carrying
STOP Hold the last record without carrying
END Finish the experiment and finalize its outputs

For a 3 × 3 sweep, put dim first with RESET and coord second with END. The sequence is (dim0,coord0), (dim1,coord0), (dim2,coord0), then (dim0,coord1), and so on: nine configurations. END is tested when traversal reaches that channel; a slower channel does not advance on every call. With only RESET channels, traversal loops instead of ending.

A configuration is applied together and text changes cause one awaited recompile. Terminal exhaustion does not publish a partially reset configuration or advance the configuration counter. Priming starts every channel at its first selected record and does not count as an advance.

For measurements after seven steps of each configuration, use a field clock and reset after advancing:

@measure(#A.step == 7) {
  log(#A[0,0], measurements);
  next();
  set(#A);
}

A dimension change may reset a field implicitly, but an explicit set(#A) also handles configurations whose dimensions stay the same. Other hosts can supply their own traversal policy; hosts using only the original single-value interface retain parallel pulls.

The collection itself is not stored by Utomata. The host owns the sequence and supplies values or a whole configuration. This allows an input to represent anything from a folder of images to a large dataset or a value generated dynamically during the run.

Output

Output is initiated from triggers using log().

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

The first argument selects the data — a whole field, or a single cell:

log(#A[0, 0], probe);

The second argument names an output channel. A keyless form is also available for simple diagnostic output:

log(#A[0, 0]);

As described in the Triggers chapter, log() and the other output actions operate only during batch or offline execution. They do not emit output during normal realtime execution.

Output Formats

Output channels currently support three formats:

Format Result
image image output, written as .png by file-based hosts
txt text rows
jsonl JSON Lines rows

The format name is image, not png. PNG is an encoding used by hosts when writing an image artifact to disk. Raw binary output is not currently an author-facing output format.

Whole-field logging is required for image output. A single-cell lookup produces only one sampled value and therefore cannot produce an image.

Output Rows

Several logged values can contribute to the same output row.

For example:

@sample(step % 10 == 0) {
  log(#A[0, 0], x);
  log(#B[0, 0], y);
}

may produce a row containing both x and y.

A row is completed when one of its keys is encountered again, before a successful input configuration advance increments its output index, or when output is explicitly flushed or ended.

Conceptually:

log(..., x)
log(..., y)
log(..., x)

commits the first { x, y } observation when the second x begins the next one.

This allows repeated measurements to accumulate naturally without requiring flush() after every row.

Filenames and row boundaries

In Project Settings → Outputs, Target filename groups channels into a file, and JSON key names each channel's value within a row. Give channels sharing a target the same output format and filename variation.

Filename variation controls whether filenames change:

Choice Saved value Effect
None — one file (data), Selected input indices only — last image (PNG) empty One JSONL file, or one PNG per selected index combination
Per input configuration rowIndex A separate file for each configuration; several rows may share that file
Per batch start time datetime A timestamp sampled once for the batch
Per field step (resets) step Image filenames follow the logged field's step
Per simulation step globalStep Image filenames follow the scheduler clock

JSONL and TXT hide the step-based choices. New JSONL outputs default to one file; existing explicit filename choices are preserved. The filename preview shows the effect. Multiple logs do not require a filename suffix: JSONL already supports many rows in one file.

flush() commits pending JSONL rows and waits for queued writes, then execution continues. It does not itself change the filename. TXT keeps rows in memory until end() writes its whole document. end() awaits finalization and finishes the batch; actions after it do not execute. Input END and batch completion also finalize outputs. Repeated finalization does not duplicate already committed rows, and writes to a given filename are serialized.

Relative input paths resolve against the default input directory, itself relative to the saved project's folder. The settings Info probe uses the same project-path resolution as execution and refreshes on opening settings and changing the directory. Absolute paths remain absolute.

Joining observations to input records

Each output channel has an optional Input indices field. Enter input channel names separated by commas, for example expr, dim, coord. No diagnostic field or extra trigger action is required.

For JSONL and TXT, the selected indices appear under a reserved inputs object:

{"inputs":{"expr":1,"dim":0,"coord":2},"roughness":0.15,"mean":0.02}

Indices are zero-based integers: expr: 1 means the second expression record. They refer to the full parsed input sequence, before from/to slicing. Blank lines and malformed JSONL lines skipped by the loader are not records, so indices are not physical line numbers. Image inputs use their ordered file sequence. Live webcams do not have sequence indices.

Indices are copied when the bridge receives the log, before an awaited commit or a later input advance can change them. Channels sharing a row target use the union of their selected inputs. Changing those indices starts a new row even if the next logged key differs. Selecting an unavailable input raises an error rather than inventing an index. The JSON key inputs is reserved on targets using this metadata.

For an image output, the selection adds matching named indices to its filename:

frame_expr-000001_dim-000000_coord-000002.png

For one representative PNG per expression, set Input indices to expr and Filename variation to Selected input indices only — last image. Each subsequent log for the same expression replaces the whole PNG; the last logged image remains. With no selected indices this keeps one image total. To retain one image per full configuration, select expr, dim, coord, or add Per input configuration filename variation. These choices compose: an extra variation can create several files for the same selected input index.

The Saving behavior readout explains grouping and replacement alongside the filename preview. PNG bytes are never appended. Writes to the same filename are ordered, so the final image is the last in logging order. An interrupted run leaves the last successfully written image, which may not be the intended final representative. Desktop filesystem failures reject output completion rather than merely printing a warning.

Normal filename variation remains in addition to these indices. Select all varying inputs to distinguish configurations; add a step-based variation when logging multiple frames per configuration. JSONL index metadata does not change the filename or split its rows. Through the bridge API, image index suffixes use generated filenames and cannot be combined with an explicit filename template.

For nine variants per expression, order inputs as dim RESET, coord RESET, then expr END. Select expr, dim, coord on the row and frame outputs. All nine rows and frames share the expression index; the complete tuple joins an individual row to its frame.

Indices identify records within a particular input sequence. Preserve that source alongside the results: reordering or replacing it changes the mapping. Reading an explicit lasting algorithm ID from a source record is not yet part of this option.

Logging one component of a cell

A sampled cell may select a single component with .x, .y, or .z:

log(#summary[0,0].x, contrast); // one number in the contrast output channel
log(#summary[0,0].y, spread);
log(#summary[0,0].z, range);
log(#summary[0,0], summary);   // default: the full vector
log(#summary[0,0].x);          // no output channel: internal diagnostic log

Single-component samples work with JSONL and TXT outputs. They require explicit cell coordinates; multi-component swizzles such as .xy and whole-field forms such as #summary.x are not supported. Invalid syntax is reported and the action is skipped, never silently converted into an internal log. An explicit output channel always selects the channel route; only omission of the channel selects the internal log.

Batch Input and Output

Inputs and outputs become especially useful together during batch execution.

For example:

$threshold = 0.5;

#SIM {
  dim = (<gridSize>, <gridSize>, 1);
  set = rand(1, 2, 3);
  run = ...;
}

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

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

@finish(#SIM.step == 300) {
  next();
  set(#SIM);
}

Here three different kinds of external control coexist:

  • $threshold can change without recompiling
  • <gridSize> can change the structure of the program and therefore recompiles
  • log() sends computed results back to the host

The language does not need to know whether those values came from a file, an interface, a camera, or another process. Those concerns remain with the host.

Host Support

Input and output capabilities depend partly on the environment running Utomata.

The desktop application currently provides the most complete implementation, including runtime uniforms, file-backed input sequences, image inputs, webcams, recompilation for source markers, and output handling.

Some headless and CLI paths remain less complete. In particular, image input under Node/Dawn is currently unavailable, and headless image encoding still depends on unresolved host-side support. These are host limitations rather than limitations of the Utomata language or bridge model.

The important architectural rule is therefore to treat input and output as capabilities supplied by the host. A program may describe an input or output operation correctly even when a particular host cannot currently provide that capability.

Input & Output Reference

Runtime Inputs

Syntax Role Description
$name uniform runtime vec3 value; changing it does not recompile
<name> input / marker external stream or source substitution
INPUT(name) stream sample internal form used to sample a bound stream

Output Actions

Action Description
log(#A, key) output a whole field
log(#A[x,y], key) output a single sampled field value
log(#A[x,y]) keyless diagnostic output
flush() commit pending output rows and await writes; continue running
end() await output finalization and finish the batch; skip later actions

Output Formats

Name Description
image image artifact
txt text rows
jsonl JSON Lines rows

Input and output deliberately remain outside Utomata's computational core. Uniforms expose inexpensive runtime values, streams expose external spatial data, source markers allow structural variation, and triggers determine when data enters or leaves a run.

Together they let a Utomata program participate in a larger system without making files, cameras, datasets, or host-specific APIs part of the language itself.