SOURCE / PINNED RELEASE
Made of little things.
Conway's Game of Life
- Release
- 230955df1900…
- Author-recorded commit
- c08196c2b050…
- License
- LICENSE
- Author’s source reference
- nostr://npub16nzc4ql748r2kv9202rgzrt0fmtk4n3tzsjl86yf6ty9agqe9u0qjcc7hc/wss%3A%2F%2Fgit.napplet.soy%2F/n-644c45e22c7
Archive hash verified: 2627ce678935a213…. The source-to-build association is the author’s claim; it has not been independently rebuilt.
// Phase 1 engine pins (build plan §Phase 1): still life, oscillator period 2,
// glider translation (2,2)/4 steps proving torus wrap, dead-cell birth,
// stable() on empty and block, aliveCount bookkeeping. Constants asserted
// against the exported symbols (BIRTH/SURVIVE_MIN/SURVIVE_MAX), not literals.
import assert from 'node:assert/strict';
import test from 'node:test';
import {
BIRTH,
GameOfLife,
MOORE_NEIGHBORS,
SURVIVE_MAX,
SURVIVE_MIN,
decodeRle,
encodeRle,
registerPattern,
} from '../src/engine.ts';
import {
GOSPER_GUN,
GOSPER_GUN_RLE,
GUN_SEED_X,
GUN_SEED_Y,
PATTERNS,
} from '../src/patterns.ts';
// Glider phase moving down-right; translates (+1,+1) every 4 generations
// (c/4 diagonal). Canonical phase, verified in-engine 2026-10-30: evolving
// this RLE 4 steps yields gen0 shifted by exactly (1,1).
const GLIDER_RLE = 'bo$2bo$3o';
// --- Phase 7: save-wire RLE encoder (spec §7) -------------------------------
test('encodeRle round-trips every cell through decodeRle (partial board)', () => {
const g = new GameOfLife(7, 5);
setBlock(g, 0, 0);
g.set(6, 4, 1); // far corner + an isolated edge cell stress runs
g.set(3, 2, 1);
const d = decodeRle(encodeRle(g.grid, 7, 5));
assert.equal(d.w, 7);
assert.equal(d.h, 5);
assert.deepEqual([...d.cells], [...g.grid], 'cell-for-cell round-trip');
});
test('encodeRle on the empty default board decodes back to 120x90 zeros', () => {
const g = new GameOfLife(); // 120x90 per D2
const d = decodeRle(encodeRle(g.grid, g.w, g.h));
assert.equal(d.w, 120);
assert.equal(d.h, 90, 'trailing blank rows survive the round-trip');
assert.equal(d.cells.reduce((s, v) => s + v, 0), 0);
});
test('gun board survives the save round-trip with pop 36 in place', () => {
const g = new GameOfLife();
g.stampRle(GOSPER_GUN_RLE, GUN_SEED_X, GUN_SEED_Y);
const d = decodeRle(encodeRle(g.grid, g.w, g.h));
assert.deepEqual([...d.cells], [...g.grid]);
assert.equal(d.cells.reduce((s, v) => s + v, 0), 36, 'gun pop pin mirrors save path');
});
test('encodeRle refuses a grid whose length does not match the board', () => {
assert.throws(() => encodeRle(new Uint8Array(6), 4, 4), RangeError);
});
function setBlock(g, x, y) {
for (let py = 0; py < 2; py++) for (let px = 0; px < 2; px++) g.set(x + px, y + py, 1);
}
function aliveCells(g) {
const out = [];
for (let y = 0; y < g.h; y++)
for (let x = 0; x < g.w; x++) if (g.get(x, y) === 1) out.push(`${x},${y}`);
return out.sort();
}
test('rule constants match Conway B3/S23', () => {
assert.equal(BIRTH, 3);
assert.equal(SURVIVE_MIN, 2);
assert.equal(SURVIVE_MAX, 3);
assert.equal(MOORE_NEIGHBORS, 8);
});
test('constructor guards against degenerate grids', () => {
assert.throws(() => new GameOfLife(0, 5), RangeError);
assert.throws(() => new GameOfLife(5, -1), RangeError);
assert.throws(() => new GameOfLife(2.5, 5), RangeError);
const g = new GameOfLife();
assert.equal(g.w, 120);
assert.equal(g.h, 90);
assert.equal(g.aliveCount(), 0);
assert.equal(g.generation, 0);
});
test('block is a still life and reads stable', () => {
const g = new GameOfLife(10, 10);
setBlock(g, 2, 2);
assert.equal(g.aliveCount(), 4);
assert.ok(g.stable(), 'block must report stable before any step');
g.step();
assert.deepEqual(aliveCells(g), ['2,2', '2,3', '3,2', '3,3']);
assert.equal(g.generation, 1);
assert.ok(g.stable());
});
test('empty board is stable', () => {
assert.ok(new GameOfLife(8, 8).stable());
});
test('blinker is a period-2 oscillator and not stable', () => {
const g = new GameOfLife(10, 10);
for (let x = 3; x <= 5; x++) g.set(x, 4, 1);
assert.ok(!g.stable());
g.step();
assert.deepEqual(aliveCells(g), ['4,3', '4,4', '4,5']);
g.step();
assert.deepEqual(aliveCells(g), ['3,4', '4,4', '5,4']);
assert.equal(g.generation, 2);
assert.equal(g.aliveCount(), 3);
});
test('dead cell with exactly BIRTH live neighbors is born', () => {
const g = new GameOfLife(10, 10);
// Three orthogonal neighbors of (5,5): above, left, right. (5,4) is
// diagonal-adjacent to both side cells, so it has 2 neighbors and survives
// one generation while the side cells die of underpopulation.
g.set(5, 4, 1);
g.set(4, 5, 1);
g.set(6, 5, 1);
assert.equal(g.countNeighbors(5, 5), BIRTH);
g.step();
assert.equal(g.get(5, 5), 1, 'birth at exactly 3 neighbors');
assert.deepEqual(aliveCells(g), ['5,4', '5,5']);
assert.equal(g.aliveCount(), 2);
g.step();
assert.equal(g.aliveCount(), 0, 'lonely birth dies of underpopulation');
});
test('survival band asserts against exported symbols', () => {
const g = new GameOfLife(6, 6);
// Live cell with SURVIVE_MIN neighbors survives.
g.set(2, 2, 1);
g.set(1, 2, 1);
g.set(3, 2, 1); // (2,2) has exactly SURVIVE_MIN
assert.equal(g.countNeighbors(2, 2), SURVIVE_MIN);
g.step();
assert.equal(g.get(2, 2), 1, `survives at ${SURVIVE_MIN}`);
});
test('live cell with more than SURVIVE_MAX neighbors dies', () => {
const g = new GameOfLife(6, 6);
setBlock(g, 1, 1);
setBlock(g, 2, 2); // (2,2) ends up with 5 neighbors
assert.ok(g.countNeighbors(2, 2) > SURVIVE_MAX);
g.step();
assert.equal(g.get(2, 2), 0, 'overpopulation death');
});
test('glider translates (1,1) per 4 steps (c/4 diagonal) across the torus edge', () => {
// Build-plan wording said "(2,2) after 4 steps"; the true c/4 diagonal
// kinematics (probed 2026-10-30, engine probe on an open 14x14 grid) are
// (1,1) every 4 generations and (2,2) every 8. Pinned both ways.
const g = new GameOfLife(20, 20);
// Glider top-left near the bottom-right corner so its path wraps edges.
const ox = g.w - 3;
const oy = g.h - 3;
const written = g.stampRle(GLIDER_RLE, ox, oy);
assert.equal(written, 5);
assert.equal(g.aliveCount(), 5);
const cells0 = aliveCells(g).map((s) => s.split(',').map(Number));
for (let i = 0; i < 4; i++) g.step();
// Same phase, every cell translated by exactly (1,1) modulo the grid —
// expected positions derived from the stamped cells, not hand-written.
const expectShift = (dx, dy) =>
cells0
.map(([x, y]) => `${(x + dx) % g.w},${(y + dy) % g.h}`)
.sort();
assert.deepEqual(aliveCells(g), expectShift(1, 1));
for (let i = 0; i < 4; i++) g.step();
assert.deepEqual(aliveCells(g), expectShift(2, 2));
assert.equal(g.aliveCount(), 5, 'glider wrapped edges and kept going');
});
test('torus wrap: corner neighbor counts see the far edges', () => {
const g = new GameOfLife(10, 10);
g.set(9, 9, 1);
g.set(0, 9, 1);
g.set(9, 0, 1);
assert.equal(g.countNeighbors(0, 0), 3);
g.step();
assert.equal(g.get(0, 0), 1, 'birth wraps from the (9,9) corner');
});
test('get/set wrap coordinates (torus get/set)', () => {
const g = new GameOfLife(10, 10);
g.set(-1, -1, 1);
assert.equal(g.get(9, 9), 1);
assert.equal(g.get(-1, -1), 1);
assert.equal(g.aliveCount(), 1, 'aliased writes must not double count');
g.set(-1, -1, 1);
assert.equal(g.aliveCount(), 1, 'idempotent re-set of same value');
g.set(9, 9, 0);
assert.equal(g.aliveCount(), 0);
});
test('aliveCount bookkeeping survives clear and random extremes', () => {
const g = new GameOfLife(12, 12);
setBlock(g, 1, 1);
g.clear();
assert.equal(g.aliveCount(), 0);
assert.equal(g.generation, 0, 'clear resets generation counter');
g.random(0);
assert.equal(g.aliveCount(), 0);
g.random(1);
assert.equal(g.aliveCount(), 12 * 12);
});
test('RLE decoder round-trips and honors the subset grammar', () => {
const d = decodeRle('bo$2bo$3o!');
assert.equal(d.w, 3);
assert.equal(d.h, 3);
assert.equal(d.cells.reduce((a, b) => a + b, 0), 5);
// Header form is accepted and ignored.
const hd = decodeRle('x = 3, y = 3, bo$2bo$3o!');
assert.deepEqual(Array.from(hd.cells), Array.from(d.cells));
// Numeric runs expand.
const runs = decodeRle('3o$3o$3o!');
assert.equal(runs.cells.reduce((a, b) => a + b, 0), 9);
});
test('Gosper glider gun from the reference seed stamps 36 cells and keeps working', () => {
// Seed arrays transcribed from upstream GameOfLife/game_of_life.py and
// cross-verified byte-exact against the vault reference doc (the original
// crawl had mangled row widths; doc fixed 2026-10-30).
// Everything derived (population 36, RLE, bounding box) is computed.
const seed = [
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0],
[1,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[1,1,0,0,0,0,0,0,0,0,1,0,0,0,1,0,1,1,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
];
for (const row of seed) assert.equal(row.length, 39);
// Trim dead margin columns to the pattern bounding box.
const xs = [];
for (let x = 0; x < 39; x++) if (seed.some((r) => r[x] === 1)) xs.push(x);
const x0 = xs[0];
const x1 = xs[xs.length - 1];
let total = 0;
let rle = '';
for (const row of seed) {
let run = 0;
let out = '';
for (let x = x0; x <= x1; x++) {
if (row[x] === 0) {
run++;
} else {
if (run > 0) out += `${run}b`;
run = 0;
out += 'o';
total++;
}
}
if (run > 0) out += `${run}b`;
rle += out + '$';
}
const decoded = decodeRle(rle);
assert.equal(total, 36, 'population computed from the seed data (plan pin)');
assert.equal(decoded.w, x1 - x0 + 1);
assert.equal(decoded.h, 9);
const g = new GameOfLife(120, 90);
// Reference seeds at pos (3,3): row offset 3, column offset 3 — x0 is the
// left edge of the bounding box (column 0 of the data, the block pair).
assert.equal(g.stampRle(rle, 3, 3), 36);
assert.equal(g.aliveCount(), 36);
// Gun period is 30: block-pair cell at data (row 4, col x0) -> (x=3, y=7)
// is alive again after exactly one period, and the gun emitted gliders.
assert.equal(x0, 0, 'bounding box left edge is the block pair column');
for (let i = 0; i < 30; i++) g.step();
assert.equal(g.get(3, 7), 1, 'gun structure intact after one period');
assert.ok(g.aliveCount() > 36, 'gliders emitted: population grew');
});
test('patternAt stamps a registered pattern and wraps at the edge', () => {
registerPattern('glider', GLIDER_RLE);
const g = new GameOfLife(10, 10);
assert.equal(g.patternAt('glider', 8, 8), 5);
assert.equal(g.get(0, 0), 1, 'glider body wrapped past (9,9)');
assert.equal(g.aliveCount(), 5);
assert.throws(() => g.patternAt('nope', 0, 0), RangeError);
});
test('patterns.ts gun export equals the upstream seed and is registered', () => {
// The Phase 2 seed must be the same gun the seed-array test above derives
// from upstream: decode the shipped RLE and compare against the seed rows
// (bounding box crop), and confirm patternAt(GOSPER_GUN, 3, 3) reproduces
// the reference stamp (alive 36 at (3,3)).
const seed = [
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0],
[1,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[1,1,0,0,0,0,0,0,0,0,1,0,0,0,1,0,1,1,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
];
const d = decodeRle(GOSPER_GUN_RLE);
assert.equal(d.w, 36);
assert.equal(d.h, 9);
assert.equal(d.cells.reduce((a, b) => a + b, 0), 36);
for (let y = 0; y < 9; y++)
for (let x = 0; x < 36; x++)
assert.equal(d.cells[y * 36 + x], seed[y][x], `cell ${x},${y} matches upstream`);
registerPattern(GOSPER_GUN, GOSPER_GUN_RLE);
const g = new GameOfLife(120, 90);
assert.equal(g.patternAt(GOSPER_GUN, GUN_SEED_X, GUN_SEED_Y), 36);
assert.equal(g.aliveCount(), 36);
});
// --- Phase 5: pattern library (build plan §Phase 5 verify gate) -------------
function bboxOf(d) {
let x0 = Infinity, y0 = Infinity, x1 = -1, y1 = -1;
for (let y = 0; y < d.h; y++)
for (let x = 0; x < d.w; x++)
if (d.cells[y * d.w + x] === 1) {
if (x < x0) x0 = x; if (x > x1) x1 = x;
if (y < y0) y0 = y; if (y > y1) y1 = y;
}
return { w: x1 - x0 + 1, h: y1 - y0 + 1 };
}
test('library metadata matches the engine measurements', () => {
const ids = new Set();
for (const def of PATTERNS) {
assert.ok(!ids.has(def.id), `duplicate pattern id: ${def.id}`);
ids.add(def.id);
const d = decodeRle(def.rle);
const bb = bboxOf(d);
assert.equal(bb.w, def.w, `${def.id} width`);
assert.equal(bb.h, def.h, `${def.id} height`);
assert.equal(d.cells.reduce((a, b) => a + b, 0), def.pop, `${def.id} population`);
}
});
test('RLE round-trip: decode -> encode -> decode is lossless', () => {
for (const def of PATTERNS) {
const d1 = decodeRle(def.rle);
const d2 = decodeRle(encodeRle(d1.cells, d1.w, d1.h));
assert.deepEqual(Array.from(d2.cells), Array.from(d1.cells), `${def.id} round-trip`);
assert.equal(d2.w, d1.w);
assert.equal(d2.h, d1.h);
}
});
test('RLE $N advances N rows (blank rows included)', () => {
const d = decodeRle('2o$3$2o!'); // row0, advance 3 (rows 1-3 blank), row4
assert.equal(d.h, 5);
assert.equal(d.cells.reduce((a, b) => a + b, 0), 4);
assert.equal(d.cells[0], 1);
assert.equal(d.cells[4 * d.w], 1);
assert.equal(d.cells[1 * d.w], 0);
assert.equal(d.cells[3 * d.w], 0);
// A digit before $ binds to the row advance, never to blank cells.
const e = decodeRle('2o2$!');
assert.equal(e.h, 2); // row + one blank row
assert.equal(e.cells.reduce((a, b) => a + b, 0), 2);
});
test('gun stamps 36 from the library export (menu path, not just seed path)', () => {
registerPattern(GOSPER_GUN, GOSPER_GUN_RLE);
const g = new GameOfLife(120, 90);
assert.equal(g.patternAt(GOSPER_GUN, 3, 3), 36);
assert.equal(g.aliveCount(), 36);
});
test('pulsar is period 3 — full CP 48-56-72 phase pin (population alone is not enough: a malformed RLE can still stamp 48 cells)', () => {
const g = new GameOfLife(40, 40);
const def = PATTERNS.find((p) => p.id === 'pulsar');
assert.equal(g.stampRle(def.rle, 13, 13), 48);
const s0 = g.grid.join('|');
g.step();
assert.notEqual(g.grid.join('|'), s0, 'not fixed');
// Conway's own name for the pulsar is "Cambridge Pulsar CP 48-56-72":
// the per-phase populations 48 -> 56 -> 72 -> 48 are the fingerprint
// that no look-alike oscillator with pop 48 can fake.
assert.equal(g.aliveCount(), 56, 'phase 2 population is 56 (CP 48-56-72)');
g.step();
assert.notEqual(g.grid.join('|'), s0, 'not period 1 or 2');
assert.equal(g.aliveCount(), 72, 'phase 3 population is 72 (CP 48-56-72)');
g.step();
assert.equal(g.grid.join('|'), s0, 'returns at exactly 3');
assert.equal(g.aliveCount(), 48);
});
test('beacon oscillates at period 2 (LifeWiki classification pin)', () => {
const g = new GameOfLife(20, 20);
const def = PATTERNS.find((p) => p.id === 'beacon');
assert.equal(g.stampRle(def.rle, 8, 8), 8);
const s0 = g.grid.join('|');
g.step();
assert.notEqual(g.grid.join('|'), s0);
g.step();
assert.equal(g.grid.join('|'), s0, 'beacon returns at 2');
});
test('pentadecathlon returns at 15 and never earlier', () => {
const g = new GameOfLife(40, 40);
const def = PATTERNS.find((p) => p.id === 'pentadecathlon');
assert.equal(g.stampRle(def.rle, 15, 18), 12);
const s0 = g.grid.join('|');
let first = null;
for (let i = 1; i <= 15; i++) {
g.step();
if (first === null && g.grid.join('|') === s0) first = i;
}
assert.equal(first, def.period, 'first return equals declared period');
});
test('block and beehive are still lifes; r-pentomino churns', () => {
for (const id of ['block', 'beehive']) {
const g = new GameOfLife(20, 20);
const def = PATTERNS.find((p) => p.id === id);
g.stampRle(def.rle, 8, 8);
assert.ok(g.stable(), `${id} stable`);
g.step();
assert.ok(g.stable(), `${id} stable after step`);
assert.equal(g.aliveCount(), def.pop);
}
const g = new GameOfLife(40, 40);
const def = PATTERNS.find((p) => p.id === 'r-pentomino');
g.stampRle(def.rle, 18, 18);
for (let i = 0; i < 50; i++) {
assert.ok(!g.stable() || g.aliveCount() !== def.pop, `r-pentomino still active at gen ${i + 1}`);
g.step();
}
});
test('library glider and LWSS translate; near-edge stamp survives wrap', () => {
// Library glider phase (LifeWiki glider.rle) on a small torus: after 8
// steps it has crossed its own start by (2,2) modulo the grid.
const g = new GameOfLife(14, 14);
const glider = PATTERNS.find((p) => p.id === 'glider');
const cells0 = [];
assert.equal(g.stampRle(glider.rle, 11, 11), 5); // hugs the bottom-right corner
for (let y = 0; y < 14; y++) for (let x = 0; x < 14; x++) if (g.get(x, y)) cells0.push([x, y]);
for (let i = 0; i < 8; i++) g.step();
const shifted = cells0.map(([x, y]) => `${(x + 2) % 14},${(y + 2) % 14}`).sort();
const now = [];
for (let y = 0; y < 14; y++) for (let x = 0; x < 14; x++) if (g.get(x, y)) now.push(`${x},${y}`);
assert.deepEqual(now.sort(), shifted, 'glider wrapped and kept its shape');
assert.equal(g.aliveCount(), 5);
// LWSS: c/2 orthogonal (2 cells right per 4 gens), pinned via bbox delta.
const h = new GameOfLife(40, 30);
const lwss = PATTERNS.find((p) => p.id === 'lwss');
assert.equal(h.stampRle(lwss.rle, 5, 13), 9);
const before = bboxOf(decodeRle(lwss.rle));
for (let i = 0; i < 4; i++) h.step();
// Bounding-box width is unchanged after a full 4-step cycle phase...
const live = [];
for (let y = 0; y < 30; y++) for (let x = 0; x < 40; x++) if (h.get(x, y)) live.push([x, y]);
assert.equal(live.length, 9, 'LWSS population intact after wrap-free travel');
const bx0 = Math.min(...live.map(([x]) => x));
assert.equal(bx0, 5 - 2, 'LifeWiki LWSS.rle phase travels LEFT at c/2: 2 columns in 4 generations');
void before;
});
test('engine module has zero DOM/host surface', async () => {
const { readFile } = await import('node:fs/promises');
const src = await readFile(new URL('../src/engine.ts', import.meta.url), 'utf8');
assert.doesNotMatch(src, /\bimport\b/, 'engine must be import-free (pure module)');
assert.doesNotMatch(src, /document|window|canvas|localStorage/i);
});
