Foundations

The semantics of computation in Utomata

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 {
  ...
}

^camera {
  ...
}

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(^camera, (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.