Field Notes · v0.1
An interactive field guide

Field notes on a pocket universe

How much joy, and how much accidental engineering, you get from refusing to stop playing with a rule you can learn in a minute.

James Robert Somers · log entry 04
This is doorsofdelirium.com itself, running in the page. Nine universes, nine rolls of the dice: palette, rule, trail, drawing machine. Under every pixel of all of them, the same rule, with each cell's fate set by how many of its eight neighbours are alive. The piece below takes it apart.

It is in playing and only in playing that the individual child or adult is able to be creative and to use the whole personality, and it is only in being creative that the individual discovers the self.

D. W. Winnicott, Playing and Reality (1971)

The Game of Life has almost no rules. A cell looks at its eight neighbours and lives or dies by how many are alive. That is the whole thing, and John Conway published it in 1970. You can learn it in a minute. Building one is the sort of rite of passage most programmers pass early. I never had. In 2021 I finally sat down to write my own, and then couldn't leave it alone. It grew into a website, Doors of Delirium, that fills the screen with drifting colour: rings crawling across a dark field, threads tracing spirals and roses and forking lightning.

None of it was planned. I kept chasing the next what if, and each one made me learn something to answer it: how to run a grid on a graphics card, how to mix colour that doesn't turn to mud, how to draw a curve one cell at a time. The site was the byproduct. The techniques I had to build to reach it were the actual haul. This piece works outward from the rule, roughly the way the project grew: the rule itself, then the wall it hit, then the graphics hardware I learned to get past that wall, then everything I added once I could.

Section one · the rule

Three to be born, two or three to live

The universe is a grid. Each cell is either alive or dead. Time passes in ticks. At every tick, every cell looks at its eight neighbours, the cells touching it including diagonals, and counts how many are alive. Then the rule:

A dead cell with exactly three live neighbours becomes alive. A live cell with two or three live neighbours stays alive. Every other cell is dead on the next tick. Too few neighbours and a cell starves. Too many and it is crowded out. That is all the physics there is.

There are no moves. You choose a starting arrangement, and from there the board plays itself. The standard notation for the rule is B3/S23, born on 3, surviving on 2 or 3. The notation matters, because it says the whole rule is two small sets of neighbour counts. Two small sets fit in two small integers. We will come back to that in §6.

One more detail, easy to miss: all cells update simultaneously. The whole next generation is computed from the whole current one before any cell changes. No cell ever sees a half-updated board. Hold on to that one too: it is why the fast version of this program needs two copies of the universe.

The five cells on the board below are a famous arrangement called the R-pentomino. Step it by hand a few times, then let it run.

1.1   the rule, one tick at a time
gen 0 pop 0
Click or tap any cell to flip it. Drag sideways to paint. The counts toggle writes each cell's live-neighbour count on the board. The fates toggle colours the future: gold rings mark births about to happen, sienna marks cells about to die.

The fates toggle marks everything the next tick will change: sienna for cells about to die, gold rings for the empty cells about to come alive. Step the board and every marked cell flips at once, while the rest stay exactly as they are. When nothing is marked, nothing will change. That is a stable pattern.


Section two · the bestiary

Still lifes, oscillators, and things that travel

Run the rule for a while and you stop tracking cells and start recognising shapes, the same few arrangements turning up again and again. The classic bestiary sorts them into three families.

Still lifes are fixed points: arrangements where every live cell has two or three live neighbours and no dead cell has exactly three. The rule looks at a still life and changes nothing, forever. The block, four cells in a square, is the smallest. The beehive and the loaf are the ones you will see everywhere once you start looking.

Oscillators cycle. The blinker, three cells in a row, flips between horizontal and vertical every tick, period two. The toad is another period-two flicker. The pulsar is the big one: forty-eight cells that expand and contract on a three-tick cycle.

Spaceships are the third family, and the strangest. A glider is five cells that, after four ticks, re-form exactly, shifted one cell on the diagonal. No cell moved. Cells only lived and died in place. The pattern moved, and it will keep going until it hits something. The lightweight spaceship (LWSS) does the same trick horizontally.

2.1   the bestiary: pick a specimen
gen 0 pop 0
This grid is bounded: cells beyond the edge count as permanently dead, so a spaceship that reaches the wall dies there, leaving a little ash. The site's universe has the same edges.

It goes much further than this. Glider streams can be collided in ways that behave like logic gates, and from logic gates, with enough patience, people have built memory, arithmetic and entire computers inside the grid. The Game of Life can compute anything a computer can. I won't walk through that construction, which the history section points at. The fact I want from it is smaller: the rule contains none of this. Nothing in B3/S23 mentions a glider.


Section three · the soup

Seed it at random, watch it settle

So far the board has been arranged by hand. The other way is to seed it at random: light up a quarter of the cells and let the rule take over. The first generations are chaotic, big fronts of birth and death sweeping the grid. Then the activity burns down, and what is left is the bestiary again, uninvited: blocks, beehives, blinkers and now and then a glider escaping the wreck on a diagonal. The standing term for the leftover debris is ash.

This is what people mean by emergence, and the soup is where the word stops being abstract. The rule knows only neighbour counts. Nobody put the bestiary in. What you are watching is only what its fixed points and cycles happen to look like.

3.1   the soup: reseed it, stir it with the pointer
population
gen 0 pop 0
Drag across the board to paint live cells into the running soup. A stroke of the pointer is indistinguishable, to the rule, from a lucky arrangement of the initial seed.

The graph beneath the board plots the population over time, and it has the same shape every run: a steep early crash as the chaos burns off, then a long, flattening tail as the last unstable regions settle into still lifes and blinkers. On a grid this size the whole thing is over in about a minute. The natural next move is to ask what the same process looks like on a few million cells, and the obvious program for that has a problem.


Section four · the wall

The obvious program, measured honestly

Everything you have touched so far runs on the first program I wrote for this: plain JavaScript, two arrays, a loop over every cell counting its neighbours, swap and repeat. Nothing clever, and for a long time it was the whole project. Its cost is easy to state: a fixed amount of work per cell, times the number of cells, every generation.

Notice what that cost does not depend on: how many cells are alive. The loop visits every cell just to learn whether anything happened there. An empty board costs exactly as much as a boiling one. The program pays for silence.

Then I wanted it bigger. A cell for every pixel on the screen, a few million of them, sixty times a second, and the JavaScript stopped keeping up. The exhibit below is that wall. It runs the same stepper at four sizes and times the step alone, drawing excluded, averaged over the last forty generations. I won't quote numbers. The readout is measuring your machine, and a frame at sixty per second is about 16.7 milliseconds.

4.1   the wall: ms per generation, measured live
cells 65,536 ms/gen gen 0
The frame budget at sixty frames a second is 16.7 ms, and a real page spends part of that drawing, handling input, and running everything else. Watch what happens to ms/gen each time the side of the grid doubles.

Whatever your numbers are, the shape is the point: quadruple the cells, roughly quadruple the time. This is the wall the project actually hit, and it is where it started teaching me things. The answer was not a cleverer loop. There are clever loops, and the history section points at one that is genuinely startling, but they buy their speed by giving up the plain rule. The move I needed kept the program dumb and changed the machine it runs on. Learning that machine is the next two sections.


Section five · the picture

The grid is an image that computes itself

The thing I had to learn starts with a change in how you look at the board. A grid of cells, each holding a small value, laid out in rows, is an image. A cell is a pixel. Alive is one colour, dead is another. Laying the state out as an image is a real storage choice, and it is the one that lets a graphics card take the work over.

The computer has one machine built for exactly this shape of work, the GPU, which runs a small program at every pixel of an image, all of them effectively at once. That program is called a fragment shader. Normally it computes lighting or blur, but nothing stops it from computing the rule. Each pixel's shader reads its eight neighbours from the image and writes the pixel's next state. One shader pass, however many pixels, is one generation.

One catch, and §1 already handed us the answer. The shader cannot write into the image it is reading, and simultaneity says it should not want to. The whole next generation must be computed from the whole current one. So there are two images. Read from A, write into B. Next generation, read from B, write into A. The CPU program was quietly doing the same thing with its two arrays. Graphics programmers call it ping-ponging.

5.1   ping-pong: two textures, roles swapping
gen 0 reading A
The faint side is stale, last generation's picture, about to be overwritten. Nothing is ever copied. The only thing that changes hands is which texture plays which role.

The swap costs nothing. No cells move, no memory is copied. Two labels trade places. The universe alternates between two copies of itself, and at any moment one of them is the truth and the other is scratch paper.


Section six · the shader

Eight lookups, two masks, every pixel at once

Here is the heart of the shader I ended up writing, the program that runs at every pixel, once per generation. Reading a neighbour is a texelFetch: fetch the texture value at a coordinate, offset by one of the eight directions.

int numNeighboursAlive =
  isAlive(coordinate + ivec2(-1, -1)) + isAlive(coordinate + ivec2( 0, -1)) +
  isAlive(coordinate + ivec2( 1, -1)) + isAlive(coordinate + ivec2(-1,  0)) +
  isAlive(coordinate + ivec2( 1,  0)) + isAlive(coordinate + ivec2(-1,  1)) +
  isAlive(coordinate + ivec2( 0,  1)) + isAlive(coordinate + ivec2( 1,  1));

bool keepAlive = (uSurviveMask & (1 << numNeighboursAlive)) != 0;
bool born      = (uBirthMask   & (1 << numNeighboursAlive)) != 0;

The two masks are §1's promise kept. A rule like B3/S23 is two sets of neighbour counts, and each set is stored as one small integer with one bit per count. Birth on three is 1 << 3, which is 8. Survive on two or three is 12. Testing the rule is a shift and an AND, and swapping in an entirely different universe means changing two uniforms. The site ships four: Conway's B3/S23, High Life (B36/S23), Maze (B3/S12345) and Day & Night (B3678/S34678).

The plate below is the whole machine: this shader plus everything the next two sections unpack, running a complete scene from the site. A rolled palette, an afterglow, drawing machines seeding the grid, a slowly drifting camera. One cell per pixel. The cell count and the generations per second are in the readout. The rule toolrow does exactly what §1 promised: it swaps the two mask integers, mid-flight, without touching anything else. Watch Maze freeze the current scene into corridors.

6.1   the machine, everything on: one cell per pixel
rule
palette cells gen/s
Drag on the plate to draw. Your strokes inherit the scene's symmetry. New scene rolls a fresh seed: palette, rule, decay, emitters, camera. If your browser has WebGL2 turned off, this plate shows a note instead, and everything above it is unaffected.

At this size the character of the thing changes. You stop following any cell, or any glider, and start seeing fronts and textures, regions of churn with edges that advance and stall. This is the scale the site lives at, and it is where Life starts to look like weather. The rule is now fully accounted for. What is not yet accounted for is everything else you just saw: where the colour comes from, why the dead leave trails and what is drawing those spirals. None of it touches the simulation shader.


Section seven · the afterglow

A channel for the living, a channel for the dead

Once the rule was on the graphics card, everything after it was play, and the discipline that keeps that play honest is that none of it touches the rule. The trails come first. The state texture is an image, and an image has channels. The simulation only ever uses one: a cell is alive if its red channel is high, and the eight texelFetches in §6 read nothing else. That leaves the green channel free, and the site spends it on memory. While a cell is alive, the shader writes its green channel high too. When the cell dies, green starts to fade. It drops by a fixed amount, uDecay, every generation until it reaches zero. Green answers one question: how recently was this cell alive.

The memory never feeds back. The rule does not read it, so the trails are ornament in the strictest possible sense. Remove them and the simulation is bit-for-bit identical. The render pass simply treats a cell's brightness as whichever is stronger, the living red or the fading green.

The decay value sets the length of the wake, and the site names its presets. God mode (0.05) leaves about twenty generations of trail. Life on Mars (0.01) runs longer. Comet (0.005) stretches the wake to two hundred generations, where everything smears into tails. Alive in a simulation (1.0) keeps no memory at all.

Colour is the render pass's other job, and the trick is that hue has nothing to do with the cells. Brightness comes from the state. Colour comes from where the cell is. The distance from the grid's centre indexes into a palette stored as a texture one pixel tall, and a slowly drifting offset, the pulse, pushes that index over time, so rings of colour crawl inward or outward through the population. The palettes themselves are a handful of anchor colours expanded into a smooth ramp by interpolating in OKLab, a colour space where the midpoint of two colours looks like the midpoint. Interpolate naively in RGB and the ramp sags dark and muddy between saturated anchors.

7.1   the afterglow: one scene, re-dressed live
trail
palette
palette
The scene holds still while you re-dress it. Decay, palette and pulse live entirely in the render side's uniforms and textures. Draw on the plate and watch your stroke leave a ghost. The decay presets are the site's own, names included.

Section eight · the delirium

The drawing machines and the kaleidoscope

One question is left: where do the scenes come from? The simulation does not seed itself. There is a third texture, the spawn buffer, one byte per cell, uploaded fresh every frame, and any nonzero byte in it forces that cell alive. Everything that enters the site's universe enters through this buffer, including your finger when you draw on the plates above.

What writes into it is a set of drawing machines. Each scene rolls one family of particles that spend tens of seconds tracing paths into the buffer: splines threaded through random control points, rhodonea roses, phyllotaxis (seeds placed at the golden angle, the sunflower's arrangement), damped Lissajous harmonographs, walkers that wander outward and fork, streamlines carried along a flow field. These are the standard curve-drawing families you would find in any plotter or spirograph, aimed at a grid of cells instead of paper. They are the whole reason a scene has shape at the start instead of static.

One implementation detail is worth naming, because every version of this system hits it. A fast particle crosses many cells between two frames, and plotting only its current cell draws a dotted line. The cure is to draw the segment from where it was last frame, ribbons instead of dots. The site caps the segment length, so a particle that teleports (a delayed start, a mode flip) spawns a point instead of a streak.

Then the kaleidoscope: every mark a machine makes can be repeated around the grid's centre, rotated copies at two, four, up to eight axes, sometimes mirrored. One wandering spline becomes a rose window. Your own strokes get the same treatment, which is why a single drag on the plates above comes back to you multiplied.

The exhibit below runs the machines with the simulation switched off, pen on paper, nothing fighting back. These are the same objects, from the same code, that seeded every plate above. Pick a family. The symmetry slider applies to new marks as they land.

8.1   the drawing machines: simulation off, pen on paper
Each drawing runs its natural lifetime, some take half a minute, then a fresh one begins over the ghost of the last. Clear wipes the paper. Redraw starts a new drawing of the current family.

Last, the camera. The drifting rotation and the slow zoom on the site never touch the simulation either. They are a coordinate transform applied where the render shader samples the state texture, which is why §6's board sometimes sits tilted in its frame. The universe holds still. The window onto it moves.

That is the entire machine. A rule stored in two integers, run by a shader over a pair of ping-ponging textures. A memory channel for the dead. Colour by distance from the centre through an OKLab ramp. Drawing machines seeding it all through a spawn buffer, their strokes multiplied by a kaleidoscope. And a camera that is only ever a change of coordinates.

So, to close: the whole console. Every knob the piece has introduced, on one live universe: the rule, the decay, the palette, the pulse, the drawing machine, the symmetry. The site rolls all of these dice itself, once per scene. This time they are yours.

8.2   the console: every knob, one universe
rule
trail
palette
emitter
palette gen/s
The symmetry applies to the machines and to your finger alike. New scene rolls everything at once from the full set, the way the site does, and the toolrows then override one layer at a time. When a drawing machine finishes, a fresh one of the same family begins.

I did not set out to learn GPU programming, colour science or the geometry of a spirograph. I set out to make a grid of dots do something interesting, then something more interesting than that, and the learning arrived uninvited. Feynman told a version of this as autobiography. Stalled and going through the motions at Cornell, he watched someone spin a plate in the cafeteria, noticed its wobble and its spin were out of step, and worked out the motion for no reason beyond that it was fun. That idle calculation, by his own account, ran straight into the physics he was later given the Nobel for. This is nowhere near that, but it rhymes. Start with a toy, refuse to put it down, and you tend to end up somewhere you could not have planned: holding a beautiful thing, and the tools you had to build to reach it.

The site is this console running unattended, rolling its own dice once per scene. Go let it fill a screen. Then scroll back to the top. The wall of universes up there should read differently now, every one of them the same short rule, wearing everything the rest of this piece added.


Appendix · history

Where this game came from

John Conway devised the Game of Life at Cambridge in the late 1960s, and it reached the public in October 1970, in Martin Gardner's "Mathematical Games" column in Scientific American. The column offered a wager: Conway conjectured that no pattern could grow without bound, and put fifty dollars on anyone proving or disproving it before the end of the year.

The money was gone within weeks. In November 1970, Bill Gosper and his collaborators at the MIT Artificial Intelligence Laboratory found the glider gun: thirty-six cells that fire a fresh glider every thirty generations, forever, the first pattern with unbounded growth. Gun-built glider streams became signals, collisions became logic, and the universality construction, the Game of Life computing anything a computer can, was sketched in Winning Ways in 1982.

The B/S notation generalises the rule, and §6's other three universes each have a history. High Life (B36/S23) was devised by Nathan Thompson in 1994, and is loved for its replicator, a small pattern that copies itself. Day & Night (B3678/S34678) is Thompson again, in 1997, studied in depth by David I. Bell. Its name comes from a symmetry, in which inverting every cell of a pattern inverts its entire future. Maze (B3/S12345) freezes growth into corridors. I have not found a firm attribution for it.

Two honest footnotes to §4's wall. First, the clever CPU program exists. HashLife, Gosper again, uses quadtrees and memoisation to leap regular patterns forward by astronomical numbers of generations ("Exploiting Regularities in Large Cellular Spaces," 1984). It trades the dumb loop's generality for staggering speed on structured patterns. Second, running cellular automata in fragment shaders is old folklore by now. The site's version is about the plainest possible form of it, which is why it fit in an article.

Doors of Delirium itself was built by hand in 2021, and moved from plain JavaScript onto the GPU when the JavaScript ran out of room, the same wall §4 lets you walk into. Everything in this piece runs on the site's own code rather than a reimplementation. The plates above are the machine itself, running live in the page.

References