Skip to content

Building initial worlds

Keep your simulation rules in .hyle files. Put its starting layout in a .hyleworld file selected by the project manifest, beside main.hyle. New Studio projects already select initial.hyleworld; edit that file to define the starting scene. No hand-written dependency manifest is needed. Compile builds the starting cells and opens them in Studio. The same builder supplies Atlas previews and validates published projects.

These three files build two copies of a small tank and a procedural platform.

main.hyle
property Matter { density: Float<1.0> [0.0 10.0]; }
model Water : Matter;
model Wall : Matter;
world { dimensions 3; cell Cube; neighborhood VonNeumann; }
visual WaterColor for Matter { color { rgba(0.1, 0.6, 0.9, 1.0) } }
visual WallColor for Matter { color { rgba(0.6, 0.5, 0.4, 1.0) } }
visualize Water with WaterColor;
visualize Wall with WallColor;
objects/tank.hyleobject
---
#####
#WWW#
#####
---
#...#
#WWW#
#####
initial.hyleworld
use models {Water, Wall};
let tank = load "objects/tank.hyleobject";
let materials = legend {
# = Wall;
W = Water { density = 1.5; };
};
fn platform(width: Int) -> Object {
return object {
place Wall in box([0, 0, 0], [width, 5, 1]);
};
}
place platform(18) at [-1, -1, -1];
for x in 0..2 {
place tank at [x * 10, 0, 0] with materials;
}
// Later occupied placements replace earlier cells.
place Water at [2, 1, 2] { set target.density = 2.0; }

.hyleobject is literal geometry, with one or more equal-size slices. Columns advance along +X, rows along +Y, and each --- advances +Z. The first cell is [0, 0, 0]; adding slices never recenters existing coordinates. Place the object at any world coordinate to move that local origin.

. always means empty. Every grid character occupies exactly one cell. There are no skip symbols, compression, operations, or implicit padding. Leading and trailing indentation, blank lines, and whole-line // comments are formatting. Use dots for actual empty cells. Rows must have equal widths and slices equal heights. Define the legend in the consuming world. Legend symbols must be single ASCII tokens; ., -, /, quotes and legend delimiters are reserved. Legend field values are scalar literals.

Empty cells in a placed object leave existing world cells untouched. Use erase in the world script when you want to remove cells.

Bind models declared in your physics project, optionally with recipe-local aliases:

use models {Water, Wall as Barrier};

Or import a published model directly with its exact revision. This illustrative coordinate must exist in your Atlas installation:

use model "@hyle/Water#4" as Liquid;
place Liquid at [0, 0, 0];

Loading geometry does not place it. Bind its symbols when placing it:

let tank = load "objects/tank.hyleobject";
let materials = legend {
# = Wall;
W = Water { density = 1.5; };
};
place tank at [20, 0, 0] with materials;

A world file builds an isolated reusable world. Each placed copy has independent cells:

let room = load "rooms/room.hyleworld";
place room at [0, 0, 0];

The following snippets assume the model and object names above are in scope.

Operation Example
Immutable binding let width = 10;
Single cell place Water at [2, 3, 0];
Region placement place Water in sphere([0, 0, 0], 5.0);
Remove cells erase box([0, 0, 0], [2, 2, 2]);
Translate an object translate(bind(tank, materials), [0, 0, 2])
Translate a region translate(region, [0, 0, 2])

A placement filter selects cells inside a region. A property body sets the placed cell’s values:

place Water in box([0, 0, 0], [10, 10, 1]) when random(42, position) < 0.2;
place Water at [2, 3, 0] { set target.density = 2.0; }

Update existing cells without adding new ones:

set Water in sphere([0, 0, 0], 3.0) {
set self.density = self.density + 0.1;
}

Loops exclude the upper bound. Conditions choose which construction to perform:

for x in 0..10 {
place tank at [x * 10, 0, 0] with materials;
}
if width > 5 {
place tank at [0, 0, 0] with materials;
} else {
place Water at [0, 0, 0];
}

An object block groups procedural geometry. A constructor returns an object parameterized by its arguments:

fn make(height: Int) -> Object {
return object {
place Wall in box([0, 0, 0], [1, 1, height]);
};
}
let wall = object { place Wall in box([0, 0, 0], [10, 1, 5]); };
place wall at [0, 0, 0];
place make(8) at [12, 0, 0];

Constructor parameters and return values use Int, Float, Bool, Position, Region, or Object. Declare constructors before calling them. Recursion is rejected. Objects are immutable values, not classes with inheritance or shared mutable state.

Boxes use an inclusive minimum and exclusive maximum. Spheres select cell centers at distance at most their radius. cylinder(start, end, radius) uses flat end caps. Combine regions with | (union), & (intersection), and - (difference). Integer vector + and - translate coordinates exactly.

position.x, .y, and .z are available inside placement filters and property bodies. Property writes can use qualified names such as target.Matter.density. Writes in one body read the original cell and commit together. Put any local let bindings before the set assignments.

Scalar expressions support arithmetic, comparisons, boolean operators, min, max, clamp, abs, sqrt, sin, cos, exp, floor, and ceil. Trigonometric arguments use radians; these math functions return finite f64 values or a construction error. They are useful for waves and Gaussian initial fields. Transcendental results may differ by floating-point roundoff across platforms. Integer arithmetic stays integer, including integer division. floor and ceil return floats; coordinates require integers. random(seed, position) returns a repeatable value in [0,1). There is no implicit random seed or dependency on iteration order.

Source edits take effect on Compile. Construction is bounded and runs in a separate worker in Studio; changing the source cancels an unfinished build. Failed construction keeps the existing scene. Scripts cannot access the network, filesystem, clock, or simulation solver.

There is no step statement. Running, scrubbing, and painting in the live viewport operate on the timeline; they do not append commands to your source files. Use Fork & save to preserve a simulated or manually edited state. That fork converts the exact viewed cells into captured.hyleworld and selects it as the world entrypoint. Other hand-written recipes remain available in the project. No simulation steps are replayed to reconstruct this starting state.

Projects and bakes retain all source files. Standalone objects need a consuming world script and model definitions. Object placement handles, automatic brush-to- source rewriting, rotations, and noise functions are not implemented yet.

Hyle Astro