Conway's Game of Life with time as the third axis. Every generation is a horizontal slice, stacked upward as the simulation runs — so instead of watching cells flicker, you watch the history accumulate into a solid.
A single self-contained HTML file. No build step, no dependencies, no canvas library — the 3D is a hand-rolled orthographic projection over plain Canvas 2D.
The bright frame is the present. Everything below it already happened.
| In the grid | In the block |
|---|---|
| Still life | a straight vertical column |
| Glider | a diagonal thread — motion in space becomes a tilt in time |
| Oscillator | a braid: threads that split and rejoin on its period |
| A cell being born | one cold blue mark, the only cool hue in a warm field |
| Extinction | a thread that simply stops |
The Gosper glider gun is the clearest demonstration: the gun itself is a knot of braided threads, and each glider it emits leaves at a constant angle, forever.
open index.htmlOr serve it, if your browser is strict about file://:
python3 -m http.server 8000It also works as a GitHub Pages site with zero configuration — index.html is at the repo root, so Settings → Pages → Deploy from branch → main / (root) is all it takes.
| Input | Action |
|---|---|
| Drag | orbit the camera |
| Scroll / pinch | zoom |
Space |
run / pause |
S |
single step |
R |
reseed the current pattern |
C |
clear the grid |
The rail on the right holds the rest:
- Speed — 1 to 30 generations per second.
- Seed — glider, Gosper glider gun, R-pentomino, acorn, diehard, pulsar, lightweight spaceship, random soup.
- Present state — a 2D plate of the newest slice. Draw on it to edit the present; the past stays where it is.
- Depth — how many generations stay on the axis, 6 to 110. Older slices fall off the bottom.
- Spacing — the height of one generation relative to one cell. At
1.00×a glider's thread is steep, because it only travels one cell every four generations; drop it toward0.30×and gliders lean into obvious diagonals. - Slices / Both / Threads — draw the filled generations, the worldlines connecting a cell to its own next state, or both.
- Wrap — toroidal grid on, bounded grid off. Note that with wrapping on, the glider gun's output eventually comes back around and collides with the gun.
The automaton is unremarkable on purpose: a 44×44 grid in a Uint8Array, standard B3/S23, double-buffered per step, with an optional toroidal wrap. Each step also records which cells were born that generation, which is what drives the blue accent.
The history is a ring buffer of snapshots, trimmed to the depth setting. Editing the present slice writes through to the newest snapshot without disturbing anything below it.
The renderer is where the work is. Three things keep it cheap enough to run at 60 fps while drawing tens of thousands of quads:
-
Orthographic projection, computed once per frame. The projection is affine, so a cell's position is
origin + i·e₁ + j·e₂ + t·e₃with the three basis vectors derived once from the camera angles. Per cell it's two multiplies and two adds — no matrix stack, no per-vertex transform. -
No depth sorting. Cells within one generation are coplanar and disjoint, so their orthographic projections can never overlap — they can be drawn in any order. Only the slices themselves need ordering, and that's a single comparison against the camera elevation to decide whether to walk the time axis up or down.
-
Two paths per slice. Every live cell in a generation shares one colour and one alpha, so the whole slice goes into a single
Path2D(survivors) plus one more (births) and gets filled twice, instead of once per cell. A hundred slices cost ~200 fill calls regardless of population.
Measured worst case — 110 slices, ~14,000 quads and ~11,000 worldline segments at 1900×1500 — renders in 6.4 ms, comfortably inside a 60 fps budget.
Colour encodes age. The ramp runs from a hot near-white at the present through amber and mulberry to a deep indigo at the oldest kept generation, with alpha falling off alongside it, so depth in time reads as depth in the image. Births are the single cool hue so that new growth pops against the warm field. The light theme rebuilds the same structure as plotter ink on cool paper rather than inverting it.
index.html the entire app — markup, styles, simulation and renderer
docs/ README assets
LICENSE MIT
MIT — see LICENSE.


