Stratum Engine
experimental · multiplayer variable turn-based · voxel

The world is paused until someone acts.

Stratum is an experimental multiplayer variable turn-based voxel engine. Nothing moves on its own: a fireball hangs in the air, a falling tree stops mid-topple, a fire waits to burn — until a player acts, and then the whole world advances one tick together. Every rule is decided on the server; the client only draws.

Procedural — the same kind of scene the demo world builds from chunks of 4×4×4 voxel leaves.
the experiment

Variable turn-based, for many players at once

Paused by default

Between actions nothing happens. Projectiles, falling debris, burning logs and cooldowns are all frozen mid-state. Walk away from the keyboard and the world waits for you — and for everyone else.

A turn is any action

A step, a cast, a use. When a player acts, the server advances one tick and every live thing moves its share: the fireball flies its 300 voxels, the tree falls a little further, the fire eats a little more. Turns have no fixed length in seconds — a quiet world sits still, a busy one ticks every 200 ms.

Shared, not sequenced

There is no turn order. Everyone acts whenever they like; players who act inside the same 200 ms join the same tick. The result plays like real time when the room is busy and like a turn-based game when it isn't — and the client replays every tick at the same pace regardless of how they arrived.

what it is

Two halves and a world

Server

Owns the clock, and the clock is stopped by default. Time advances one action tick when any player acts — at most once per 200 ms however many act at once, so a crowd can't make the world run faster than one person. Wren scripts define actors, projectile types, skills and what fire does; the server carves the world, resolves hits, and tells witnesses what happened.

Client

Zig, GLFW and OpenGL. Raymarches the world and every actor directly from voxel DAGs — no meshes. Paces incoming facts by their tick so a burst replays at nominal speed, predicts nothing, and infers nothing from assets. Runs on Linux and Windows; keyboard, mouse and gamepad.

World

One hash-DAG octree per 2048³ cell, 4×4×4 voxel leaves deduplicated across the whole world. Trees, walls and props are baked into the same tree and indexed by a small registry, so carving, felling and burning are ordinary DAG edits that server and client apply identically.

architecture · from the server

Intents in, facts out

The server holds the only copy of the world that matters. Players send intents — move here, cast this, use that — and the server answers with facts: a player moved, a projectile appeared, a tree lost its top. It never tells a client what to compute; it tells it what happened, stamped with the tick it happened on. Facts are LZ4-compressed JSON on a netstring stream; world chunks and the registry of destructible placements go out on a second channel so a 30 MB cell never queues behind a step.

Intents

Validated first: range, line of sight, cooldown, rage. A rejected cast costs the caster one error fact and nobody else anything. An accepted one advances the tick.

→ tick →
Server

Advances every live thing one tick: projectile sweeps, debris on its curves, fire on its subjects, monster AI on its detection timers. Carves the world. Decides who witnessed what.

→ facts →
Witnesses

Each client gets only what is inside its vicinity — a known set the server diffs per tick — capped at what a client can draw, nearest first. Nothing is ever sent twice.

What the server actually does on a tick

StageWhat happens
Governed tickThe first accepted action after 200 ms opens a new tick; every action inside the same 200 ms joins it. So the tick rate is bounded by the clock, not by the player count, and a hundred players in one square still produce five ticks a second at most.
Projectile sweepEach live projectile advances its governed speed along a deterministic path and is swept as a sphere against coarse octree nodes of the world — first solid voxel in the first touched node is the hit — and against actor bounding boxes. Position history a few ticks deep lets a shot be judged against where things were when the caster saw them.
Carve and settleA hit carves a sphere out of the instance it struck — never the terrain beneath — then finds what the carve left disconnected. The largest pieces become rigid bodies with a motion curve; the rest is reported as dust. A tall piece severed at the base topples about its bottom edge, away from the blast.
FireTwo axes per subject: what it emits on contact, and whether it is burning. A fire-tagged hit rolls the target's flammability; a burning piece consumes 3 % of itself per tick at rest and publishes its shrink every five. Fire moves by contact only — nothing spreads by adjacency.
Interest managementOne vicinity radius for everything. Per witness the server keeps a known set of actors, projectiles and pieces and emits appear / disappear as it changes; projectile facts are scoped by the projectile's own position, so its end is always delivered to whoever can see where it ended.
World editsThree operations — delete a box, unsert a shape, insert a shape — applied to the server's DAG and sent as the same three operations to each witness holding that cell. A damaged tree's remaining geometry crosses the wire once; the two copies stay byte-identical.
ScriptsActors, projectile types, skills and objects are Wren scripts with hot reload. A type declares its light, its emissions, its flammability; the engine stamps those into facts. The client never reads a script.

And what the client is left with

Drawing. It raymarches the world and every actor straight from voxel DAGs, replays facts at the pace of their ticks, applies the same three world-edit operations, evaluates motion curves in closed form, and infers nothing from a model or a name. It has no simulation of its own to disagree with the server about.

demo game

A small world to break

The demo is an isometric fantasy town and its surroundings: players, guards, a forge, monsters that see and hear you, and trees that come apart when a fireball hits them. It exists to exercise the engine end to end — login and character select, streaming chunks, chat over IRC, skills and cooldowns, combat, destruction — with real assets and a real server.

Everything is in development. The engine is not packaged for players yet; what you can run today is the client against your own server.

learnings

Rules we arrived at the hard way

Each of these replaced something that looked reasonable and wasn't.

RuleWhy
Time is the tickThe world advances only on actions, at most once per 200 ms no matter how many players act. Physics, fire and cooldowns count ticks, never seconds — so a paused world is exactly paused, a busy one doesn't run faster, and a projectile that lingers in the air for an hour while nobody plays is the engine being honest, not a bug.
The client infers nothingWhether a thing smokes, burns or glows arrives as data on its own facts. Keying the fireball's trail on "is a projectile" once put a trail on arrows and none on a burning knight.
Never expire on a clockA projectile or piece is dropped only when the server says so, or when the tick has run past its maximum flight — and that case is logged as a server bug, because it is one.
Overflow degrades, never failsSixty-four actor slots and thirty-two lights. When a town square exceeds them, the lowest-ranked go unrendered and the log says so; the 65th actor used to exit the process, which handed every crowd a kill switch.
Edits are the same ops on both sidesDelete a box, unsert a shape, insert a shape. A damaged tree's remaining geometry is sent once; the client applies the same three operations the server did, and the two copies stay byte-identical.
Extents are content, not containersA row's bounding box is its voxels' tight extent. Recording the element's power-of-two cube instead once deleted a 512³ block of ground on a tree's second hit.
Motion is described, not streamedA falling limb gets one closed-form curve in tick units — linear, ballistic, or a topple about a hinge — evaluated identically everywhere. Facts that arrive before the curve ends queue behind it, so a flight resolved in one server pass still plays out.
Presentation is the server's to projectEmission (what a thing does to what it strikes) and burning (what is happening to it) are separate axes; both project onto one fx field and a glow. The client draws the projection and can't tell the axes apart — by design.
download and run

Download and run

Precompiled builds of the client, each with a working configuration that points at the demo server. Unpack, run, log in.

# Linux
tar xzf stratum-linux-x86_64.tar.gz
cd stratum && ./run.sh

# Windows
unzip stratum-windows-x86_64.zip, then run stratum.exe

On first login the client fetches the world's asset bundle — models, sounds, skills, the default action map — into .cache/<world>/ beside the binary, and streams the world from there on. Keyboard and mouse or a gamepad; rebind anything in .cache/<world>/bindings.json.