Utomata is a computational framework for exploring emergent and procedural systems. It is designed to perform many independent calculations in parallel and does not rely on conventional programming constructs such as variables, conditionals, functions, and loops.
Instead, computation is built around a single value type: a numeric three-component vector. Values are transformed and combined through a small set of operators. Operators compose into expressions, allowing the same basic syntax to describe positions, colors, velocities, forces, rotations, pointers, and many other quantities. Expressions are assigned to named properties of independent, interacting blocks.
In this sense, Utomata is not a general-purpose programming language, but a domain-specific language (DSL) built around a particular computational model.
These elements — values, operators, expressions, properties, and blocks — define the basic semantics of computation in Utomata and are the subject of this chapter. The following chapters introduce the different block types and their properties in detail.
Values
Utomata uses a single fundamental value shape: a three-component vector — an ordered group of three numeric values.
The components have no inherent meaning. Depending on context, a vector can represent any property of a system. Because coordinates share this same form, a vector can also serve as the address of another value: data stored in one place can point to data stored in another, forming chains and structures rather than merely holding quantities directly.
This flexibility is central to Utomata's computational model. Three-component quantities are common in spatial, visual, and dynamical systems, and treating them all as the same kind of value makes it possible to move freely between these roles, combine them, and reinterpret them without introducing separate data types or conversion layers.
Values do not always need to be written with all three components explicitly. Shorter forms are automatically lifted to a three-component vector, while swizzles can be used to select, reorder, or repeat existing components.
| Syntax | Resolves to | Description |
|---|---|---|
(1, 2, 3) |
(1, 2, 3) |
Explicit three-component vector |
1 |
(1, 1, 1) |
Scalar lifted across all components |
(1) |
(1, 1, 1) |
Single component repeated |
(1, 2) |
(1, 2, 2) |
Final component repeated |
(1, 2, 3).xyz |
(1, 2, 3) |
Components in their original order |
(1, 2, 3).bgr |
(3, 2, 1) |
Components reordered using rgb aliases |
(1, 2, 3).gr |
(2, 1, 1) |
Selected components are lifted back to three |
The names x, y, and z describe component position only; r, g, and b are equivalent aliases. Neither notation assigns semantic meaning to the value.
Lifting is implicit throughout Utomata. Scalars and partial vectors can therefore be used wherever a vector is expected, keeping expressions compact while preserving the uniform three-component value model.
Numeric ranges
Utomata uses a small set of conventional numeric ranges throughout the language. These are not separate types, but useful domains shared by many operators and constructs.
| Range | Name | Used in |
|---|---|---|
-1 to 1 |
SIGNED |
Value domain, coordinate domain |
0 to 1 |
UNIT |
Value domain, coordinate domain |
-∞ to ∞ |
REAL |
Value domain |
Individual operators may generate, transform, clamp, or interpret these ranges differently. Those rules are documented where they apply.
Constants
Common numerical constants can be used directly inside expressions.
| Name | Value | Description |
|---|---|---|
PI |
≈ 3.14159 |
π |
TAU |
≈ 6.28319 |
2π, one full turn |
HALF_PI |
≈ 1.5708 |
π/2 |
Operators
Operators transform and combine Utomata values.
In conventional mathematical notation, an addition might be written as a + b. Utomata writes the same operation in prefix notation, with the operator first:
add(a, b)
Operator names are intentionally short and regular. Most use a three-letter form such as add, mlt, sub, or sin. This keeps the syntax compact and visually consistent, which becomes increasingly useful as operations are combined into more complex expressions.
Most operators work per channel. Given two vectors, the operation is applied independently to their corresponding components:
| Operation | Result |
|---|---|
add((1, 2, 3), (4, 5, 6)) |
(5, 7, 9) |
mlt((1, 2, 3), 2) |
(2, 4, 6) |
sub((1, 2, 3), (4, 5) ) |
(4, 10, 15) |
Some operators instead treat a vector as a whole. Operations such as len(), dot(), and sum() produce a single mathematical result, which is then repeated identically across all three vectors according to the same lifting rules described above.
| Operation | Result |
|---|---|
len((3, 4, 0)) |
(5, 5, 5) |
sum((1, 2, 3)) |
(6, 6, 6) |
dot((1, 0, 0), (0, 1, 0)) |
(0, 0, 0) |
Arithmetic
Arithmetic operators perform common numerical transformations. Unless otherwise noted, they operate independently on each component.
| Signature | Description |
|---|---|
add(a, b, …) |
Sum of inputs |
sub(a, b) |
a − b |
mlt(a, b, …) |
Product of inputs |
div(a, b) |
a ÷ b |
pow(a, b) |
a raised to the power b |
sqt(a) |
Square root |
log(a) |
Natural logarithm |
mod(a, b) |
Modulo |
frc(a) |
Fractional part: a − floor(a) |
flr(a) |
Floor |
cil(a) |
Ceiling |
rnd(a) |
Round to nearest integer |
abs(a) |
Absolute value |
sgn(a) |
Sign: −1, 0, or 1 |
add() and mlt() are variadic and can combine several values directly. With a single argument, they return that value unchanged.
Comparison
Comparison operators return numerical values rather than a separate boolean type. A true comparison produces 1 and a false comparison produces 0, making the result immediately usable as a mask or input to another expression.
| Signature | Description |
|---|---|
eql(a, b) |
1 where a = b, otherwise 0 |
lrg(a, b) |
1 where a > b, otherwise 0 |
sml(a, b) |
1 where a < b, otherwise 0 |
min(a, b, …) |
Per-component minimum |
max(a, b, …) |
Per-component maximum |
With a single vector argument, min() and max() instead reduce its components and splat the result. For example, max((1, 4, 2)) resolves to (4, 4, 4).
Trigonometry
Trigonometric operators work component-wise and use radians.
| Signature | Description |
|---|---|
sin(a) |
Sine |
cos(a) |
Cosine |
tan(a) |
Tangent |
asn(a) |
Arcsine |
acs(a) |
Arccosine |
atn(a) |
Arctangent |
atn(a, b) |
Arctangent in two-argument form |
Vector
Vector operators treat the three components as a single geometric or aggregate quantity rather than operating on each channel independently.
| Signature | Description |
|---|---|
dot(a, b) |
Dot product, splatted |
dst(a, b) |
Distance between a and b, splatted |
len(a) |
Vector length, splatted |
nrm(a) |
Normalize to unit length |
aim(a) |
Rotation that aims along direction a |
sum(a) |
Sum of components, splatted |
avg(a, b, …) |
Per-component average |
With a single argument, avg() reduces the components of that vector and splats their average. With several inputs, it averages corresponding components.
Domain
Domain operators map, constrain, wrap, interpolate, or reinterpret values. They are particularly useful for coordinates, periodic spaces, normalized ranges, falloffs, and transformations between representations.
| Signature | Description |
|---|---|
dom_idx(a, b) |
Partition index: floor(a ÷ b) |
dom_phs(a, b) |
Positive phase of a within periodic span b; alias wrp |
dom_del(a, b, c) |
Shortest signed displacement across a periodic span |
fof(a, b) |
Linear falloff from 1 at the origin to 0 at radius b |
clp(a, b, c) |
Clamp a value to a range |
mix(a, b, t) |
Linear interpolation from a to b by t |
rmp(a, b, c, …) |
Sample evenly spaced stops by normalized position |
hsl(a, b, c) |
Convert RGB to HSL |
rgb(a, b, c) |
Convert HSL to RGB |
Some operators support abbreviated forms. For example, clp(a, hi) clamps from 0 to hi, while the color conversion operators may accept either one vector or three separate channel values.
Random
Random operators produce deterministic pseudo-random values from explicit seeds.
| Signature | Range | Description |
|---|---|---|
rand(a, b, c) |
[0, 1] |
Deterministic random value |
srand(a, b, c) |
[-1, 1] |
Deterministic signed random value |
Randomness in Utomata has no hidden mutable state: the same seed always produces the same result. Different values may therefore be generated by changing the seed explicitly, including by supplying an expression that varies over time or space.
Expressions
Expressions combine values and operators into larger calculations. Because every operator receives values and returns a value, the result of one operation can be used directly as the input to another.
A simple expression might be:
add(a, b)
Its inputs may themselves be expressions:
add(mlt(a, b), c)
This recursive composition is the basic structure of computation in Utomata. Rather than storing intermediate results in local variables, operations are nested directly inside one another.
| Expression | Read as |
|---|---|
add(a, b) |
add a and b |
mlt(add(a, b), c) |
add a and b, then multiply by c |
frc(add(mlt(a, b), c)) |
multiply a by b, add c, then take the fractional part |
Expressions are evaluated from the innermost operation outward. Their nested structure therefore describes the dependencies of a calculation directly: each operation consumes the values produced beneath it and returns a new value to the operation above.
There are no local assignment statements or procedural sequences inside an expression. The expression itself contains the complete calculation.
Conditions
Utomata has no separate boolean value type or conditional expression syntax. Comparisons produce ordinary values containing 0 or 1, which can be composed with other operations like any other result.
This makes comparisons useful as numerical masks:
| Expression | Effect |
|---|---|
lrg(a, b) |
1 where a > b, otherwise 0 |
mlt(a, lrg(a, b)) |
preserve a where a > b, otherwise 0 |
mlt(lrg(a, 0), sml(a, 1)) |
1 where a lies between 0 and 1 |
Because masks are ordinary numerical values, arithmetic operations can also combine conditions. Multiplication behaves like logical AND: the result is 1 only where both masks are 1.
mlt(lrg(a, 0), sml(a, 1))
This produces 1 where both a > 0 && a < 1.
Addition can be used like logical OR when any non-zero result is considered active:
add(lrg(a, 1), sml(a, 0))
This produces a non-zero value where either a > 1 || a < 0. Unlike a normalized Boolean OR, however, two active masks would add to 2 rather than remain 1.
Because comparison results use the same three-component value form as every other expression, conditions can be combined directly with arithmetic, vector, and domain operations.
This compositional model remains the same regardless of expression complexity. Larger Utomata structures determine where values come from and where results are used, but the calculations themselves are always constructed by nesting value-producing operations.
Macros
As expressions grow, useful calculations often need to be named and reused. Utomata does this with macros.
A macro assigns a name to a complete Utomata expression using the & sigil:
&drag = 0.9;
&gravity = (0, -0.01, 0);
That name can then appear anywhere a value or expression is expected:
&motion = add(
mlt(&velocity, &drag),
&gravity
);
Macros are declared at the program's top level, but they can be used anywhere calculations are permitted. A macro can also reference other macros, making it possible to build larger calculations from smaller named parts:
&speed = len(&velocity);
&direction = nrm(&velocity);
&movement = mlt(&direction, &speed);
A macro is not a mutable variable and does not represent a step in an execution sequence. It is a named expression, and must therefore contain complete, valid Utomata syntax.
&motion = add(&velocity, &gravity);
&gravity = (0, -0.01, 0);
&velocity = (1, 0, 0);
Utomata resolves these declarations as part of the program as a whole rather than evaluating them from top to bottom. Macros can therefore be arranged according to meaning and readability instead of execution order.
Blocks
A Utomata program is a flat collection of independent declarations and blocks. Blocks represent the major constructs of the system and are identified by a sigil, followed by a name:
#position {
...
}
~particles {
...
}
{
...
}
The sigil identifies what kind of construct the block represents, while the name identifies that particular instance. Macros, introduced above, follow the same convention with the & sigil. The individual block types are introduced in the following chapters; for now, the important point is that they all share the same basic structure.
This makes the kind of construct being addressed visible directly in the program.
Properties
Inside a block, named properties determine its configuration and behavior.
#A {
dim = (64, 64, 1);
mode = F32;
run = frc(add(#A, 0.01));
}
Here, dim, mode, and run are all properties of #A, but they do not all play the same role.
Where a block can be referenced elsewhere, its sigil remains part of the reference:
add(#position, #velocity)
mlt(, (0.9, 0.9, 0.3))
Some properties contain ordinary Utomata expressions and are evaluated using the value and operator rules introduced earlier in this chapter. Others configure the block itself and accept only particular kinds of values.
| Property kind | Example | Role |
|---|---|---|
| Computed | run = add(#A, 0.01); |
defines a calculation |
| Literal | dim = (64, 64, 1); |
defines fixed configuration |
| Enumerated | mode = F32; |
selects from predefined options |
Which properties exist, what they mean, and what kinds of values they accept depend on the block type. Those rules are documented alongside each construct rather than being part of the general expression language.
With this, the basic grammar of Utomata is complete. Values are transformed by operators, operators compose into expressions, macros give those expressions names, and blocks place them within larger computational structures through their properties.
The following chapters move from this common foundation to the individual block types and the specific behaviors they provide.