← mino.mobi

voronoi

Conway's Game of Life on a mesh that isn't a grid

part of the pack · geometry · cohomology · aztec · temperley–lieb · heilbronn

Conway's rule counts eight neighbours because a square has eight. Drop that assumption and the rule has to be stated as a fraction of whatever neighbourhood a cell happens to have — here, the cells of a Voronoi tessellation of a torus, which average six sides but range from four to eight. Everything below is a pure function of two seeds, so every universe you like is a URL.

click a cell to flip it
18/s
mesh seed
sites
lloyd relaxation
rule
B –
S –
soup seed
density
generation0
population—
activity—
cell degrees—

Most soups are boring. They die, or they lock into blinkers within a dozen generations, or they boil at a constant temperature forever. The hunt rolls initial conditions on the current mesh and scores each trajectory, stopping at the first that clears the bar — and it shows you everything it threw away, because the rejects are most of the story. A run scores well when it is still doing something after a long time without either dying out or saturating; the exact formula is emergence(), and it is in the docs tab.

#soupscoreoutcomeperiodtransient

Not started.

score to beat
generations per try
give up after
also roll

Rolling the rule widens the search enormously and is where the strangest results come from — but a soup that only works under one exotic rule is a different kind of find from one that works under a rule you chose in advance.

The automaton is only as trustworthy as the mesh under it, and a Voronoi mesh on a torus has enough structure to be checked rather than assumed. Every row below is recomputed from the mesh currently loaded — not cached, not from a fixture.

mesh ledger — recomputed live
degree histogram

How many sides each cell has. Σdeg is pinned to exactly 6n by Euler's formula, so relaxation cannot change the mean — only how tightly the distribution clusters on it.

lloyd energy

The quantity Lloyd's algorithm minimises, per iteration. It must never go up; that it descends and flattens is the evidence the mesh has converged rather than merely stopped.

Six universes worth keeping, found by an offline sweep of 198 rule bands × 3 meshes × 5 densities × 8 soups — 6099 trajectories that scored above zero. They are picked for behavioural variety rather than score: an attractor with a huge period and one that never repeats are different animals, and a collection of five near-identical storms would not be a collection. Every number on every card is re-measured from its own permalink by the selftest on each run.

Why a Voronoi mesh breaks the rule

B3/S23 is not a statement about life. It is a statement about a square grid, where every cell has exactly eight neighbours and "three" therefore means "three eighths of your world". Move to a mesh where cells have four, five, six, seven or eight sides and the counting rule stops being well defined: a four-sided cell can never see three live neighbours the same way an eight-sided one can, so a count rule silently hands power to the big cells and the automaton becomes a story about mesh irregularity instead of about the rule.

So the rule here is stated as a fraction. A cell is born if the share of its neighbours that are alive falls in the birth band, and survives if that share falls in the survival band. Every cell then plays by the same law regardless of how many sides it drew.

This is a genuine generalisation and not a different game, which is checkable: on a square grid with Moore neighbourhoods, the fractional rule with birth band [3/8, 3/8] and survival band [2/8, 3/8] is Conway's B3/S23. The selftest runs the engine on a 24×24 Moore torus against an independently written naive implementation over gliders, blinkers, toads, beacons, a pulsar and 200 random soups × 40 generations, and requires cell-for-cell agreement.

The mesh

The domain is the unit torus, not a bounded patch. On a patch the border cells have fewer neighbours and act as sinks; every hunt then converges on artefacts of the edge rather than on anything about the rule. On the torus there is no edge and no cell is special.

Cells are built by intersecting half-planes: start with a square around the site and clip against the perpendicular bisector to each other site, nearest first. The interesting part is knowing when to stop. After clipping against everything within distance R, let r be the distance to the furthest polygon vertex. Any site further than 2r has its bisector further than r from the site, so it cannot touch the polygon — the cell is final. That is a proof, not a tolerance, which is why the result is the Voronoi diagram rather than an approximation to it.

Adjacency comes out of the same pass. Each polygon edge carries a tag naming the site whose bisector cut it, so neighbours are recorded when the cut is made instead of being reverse-engineered from distances afterwards. That matters on a relaxed mesh, which is full of near-degenerate quadruple points where a distance-matching heuristic has to pick a tolerance and picks it wrong.

One number that catches everything

A Voronoi diagram on a torus satisfies V − E + F = 0, and Voronoi vertices are trivalent, so V = Σdeg/3 and E = Σdeg/2 with F = n. Substituting gives Σdeg = 6n exactly: the mean cell has six sides no matter where the sites fell, and no amount of relaxation changes it.

This makes an unusually sharp test. A dropped neighbour, a phantom neighbour, a wrap computed with the wrong sign — all of them move Σdeg off 6n, and none of them can cancel out. It is checked on every mesh the selftest builds, alongside the areas summing to 1, adjacency being symmetric with mirrored image shifts, and 3000 random probe points per mesh landing inside the cell that brute-force periodic nearest-site classification assigns them to.

Relaxation

Raw Poisson sites make a terrible mesh — cells range from slivers to monsters and the automaton is dominated by a handful of huge cells. Lloyd relaxation moves each site to its cell's centroid and rebuilds, repeatedly, converging towards a centroidal tessellation with blue-noise character. The degree histogram tightens onto 6; at 12 iterations a typical mesh is about 57% hexagonal with nothing outside 4–8 sides.

Lloyd's algorithm is a descent on the energy Σᵢ ∫|x − sᵢ|² dA, so that quantity must never increase. The anatomy tab plots it, and the selftest asserts monotonicity — which is the difference between "the mesh looks nicer" and "the relaxation is doing what relaxation does".

What "emergence" means here

Nothing rigorous. It is an explicit, tunable stand-in, and it is worth being blunt about that because the alternative is a number that looks objective and isn't. A trajectory scores well when it is still doing something a long way in, without either dying or saturating:

Cycle detection is exact rather than statistical: every generation's state hash goes into a map, and a hit is confirmed by comparing the full states, so a 32-bit collision cannot fake a period.

"Unsettled" means no state repeated inside the horizon. It is a claim about the window, not a proof of aperiodicity — the state space is finite, so every trajectory cycles eventually. The two specimens labelled unsettled were run out to 5000 generations and had still not repeated.

Permalinks

Every universe is six fields: mesh seed, site count, relaxation iterations, the two rule bands, the soup seed and its density. Nothing else enters — the PRNG is a hand-rolled splitmix32 seeded by FNV-1a over a string, so Math.random() never touches anything and the same URL builds the same mesh and the same soup on any machine.

Rule thresholds travel as per-mille integers. That keeps the URL short, but the real reason is exactness: a rule that round-trips to within 10⁻¹⁶ is a different automaton at the threshold, so the bands are placed at midpoints between neighbouring sixths — maximally far from every boundary they have to separate — and the round-trip is checked on 500 random specs per selftest run.

Running the tests

The engine is life.js, loaded by this page as a module and imported unchanged by life.selftest.mjs — there is no second copy to drift. 154 checks, about 1.5 seconds:

node voronoi/life.selftest.mjs

The offline sweep that found the specimens is search.mjs, and it is deterministic: the same invocation prints the same table anywhere, which is the only reason baking its answers into this page is defensible.

node voronoi/search.mjs --emit