← Devlog

M0.3 — the first verb (in progress)

This sprint is not finished. What follows describes the part that’s landed — terrain, blocks, and the analyser. The run timer, the score screen, the loadout rules and the actual five-friend playtest are still open work. Nothing here should read as “M0.3 shipped.”

The analyser computes a rate; it does not simulate cargo

The load-bearing design rule for this whole project is that the server compiles a machine to a throughput rate — a pure function of the block layout, recomputed only when something changes — rather than running a tick-by-tick simulation of individual items moving down a belt. That distinction sounds pedantic until you look at what it actually buys: the server never owns a single cargo entity. Not one. What gets sent to clients is a table of edge rates; the cubes you see tumbling down a belt are drawn client-side, derived from that rate and a shared server tick, and two clients watching the same belt agree on what they’re seeing without either of them being told a phase or a seed.

A grey ground plane in a dark void. On the left, a coral-orange block marking a resource vein. In the middle, a flat violet belt with a small coral cube sitting partway along it. On the right, a larger violet block acting as the sink.
The first cargo this project ever moved: coral freight leaving a vein, crossing a violet belt, heading for a sink. The small coral cube is drawn entirely by the client from the compiled rate — the server has no idea it exists. Captured during the block-types work, before the Drop moved onto the plateau, so the layout here is not the current one.

The module that computes this imports nothing from the game engine. No dt, no mutable state carried between calls, no per-tick anything. If nothing about the layout changes, the server broadcasts nothing about flow at all — proven by a test that runs a connected line for many ticks with no player input and asserts silence on the wire, the same discipline this project already applies to avatar replication.

The door violation that hid behind a second, unrelated bug

The connection rule for this project’s block grammar is supposed to be strict: at most one edge feeds any given port. That rule is true, and the reasoning behind it is sound — an input port facing a specific direction has exactly one possible source cell, so two blocks can never physically share one.

What that reasoning doesn’t cover is that merging doesn’t happen at a port. It happens at a node. Every block kind was given a second, generic input port so a Lifter could hand cargo upward — which meant an ordinary belt fed from behind and from below received two live inputs and quietly summed them. One belt, two joined lines. That’s a Merger block, and the Merger is deliberately not supposed to exist yet; letting it in by accident teaches players a grammar the design explicitly reserves for later.

The fix: a node accepts at most one live incoming edge, chosen by a deterministic rule, and the loser compiles idle rather than summing in silently.

The uncomfortable part is how this stayed invisible. A placement rule elsewhere in the same code made it physically impossible to stack one block on top of another — which meant the Lifter could never actually feed anything, which meant the merge could never actually happen in play. The test suite that appeared to prove stacked Lifters worked did so by constructing the block layout directly in code and skipping the placement check entirely — certifying a configuration the game itself would have refused. A grammar violation masked by a second, unrelated bug is the worst shape this kind of mistake can take: the demo works, the tests pass, and the rule is already broken underneath both of them.

What’s still open

Loadout mass-capping and recovery, the three cargo classes, the run timer and score screen, a tuned wasm size measurement, and — the one that actually answers the question this whole milestone exists to ask — a real playtest with real friends. Until that happens, “is building a rig together, under a clock, actually fun” is still an open question, not a shipped answer.