One row, one cell on
const seed = c => { row = new Uint8Array(c); row[c >> 1] = 1; y = 0; };
The entire initial condition. A byte array with a single 1 in the middle: everything you see grows from that one cell.
One row of cells, each either on or off. One rule that looks at three neighbours and decides the cell below. Repeat. The left side settles into stripes, the right side never settles into anything.
Move the cursor over the canvas — it is part of the simulation.
Drag your finger across the canvas — it is part of the simulation.
The asymmetry. Same rule applied to both sides of a symmetric starting condition, and one side produces order while the other produces something that passes every statistical test for randomness.
The whole rule. l, c and r are the three cells above. That single boolean expression is the entire program.
The name: write the outputs for the eight possible neighbourhoods in order and read them as a binary number.
A Uint8Array one cell per 3px of width, starting with a single cell set in the middle. One row is drawn per frame and the array is replaced by its successor. When the bottom is reached it clears and starts over from the same single cell, and it draws exactly the same thing again.
Cut straight from lab.js. These are the real lines that run above, not a simplified version.
const seed = c => { row = new Uint8Array(c); row[c >> 1] = 1; y = 0; };
The entire initial condition. A byte array with a single 1 in the middle: everything you see grows from that one cell.
const R = P.rule | 0, next = new Uint8Array(cols);
for (let i = 0; i < cols; i++) {
const l = row[(i - 1 + cols) % cols], c = row[i], r = row[(i + 1) % cols];
const idx = (l << 2) | (c << 1) | r; // vecindario como número 0..7
next[i] = (R >> idx) & 1; // el bit idx de la regla
}
row = next; y++;
The three neighbours are packed into a number 0-7, and the answer is that bit of the rule number. Rule 30 is 00011110 in binary. Move the slider and you can run all 256 elementary automata.
persist: true,
This flag tells the engine to skip the clear. Each frame paints exactly one new row on top of everything already drawn, which is how the triangle accumulates.
Wolfram used the centre column of this automaton as the random number generator in Mathematica for years. It is one line of boolean logic producing a sequence nobody has been able to predict or compress.
Everything above, in one piece. This is the entire experiment as it lives in lab.js.
{ id: 'rule30',
name: L('Rule 30', 'Regla 30'),
note: L('One line, one rule, applied forever. The result looks random.',
'Una línea, una regla, aplicada infinitas veces. El resultado parece azar.'),
params: [
{ key: 'rule', label: L('Rule number', 'Número de regla'), min: 0, max: 255, step: 1, def: 30 },
{ key: 'cell', label: L('Cell size', 'Tamaño de celda'), min: 2, max: 8, step: 1, def: 3, unit: 'px' }
],
make(w) {
let cols = 0, row = null, y = 0, lastCell = 0;
const seed = c => { row = new Uint8Array(c); row[c >> 1] = 1; y = 0; };
return {
persist: true,
resize() { cols = 0; },
reset() { cols = 0; },
step(ctx, w, h, t, acc, P, M) {
const CELL = P.cell | 0;
if (!cols || CELL !== lastCell) {
lastCell = CELL; cols = Math.max(8, Math.floor(w / CELL));
seed(cols); ctx.clearRect(0, 0, w, h);
}
if (y * CELL > h) { seed(cols); ctx.clearRect(0, 0, w, h); }
ctx.fillStyle = `rgba(${acc},0.8)`;
for (let i = 0; i < cols; i++) if (row[i]) ctx.fillRect(i * CELL, y * CELL, CELL - 0.6, CELL - 0.6);
const R = P.rule | 0, next = new Uint8Array(cols);
for (let i = 0; i < cols; i++) {
const l = row[(i - 1 + cols) % cols], c = row[i], r = row[(i + 1) % cols];
const idx = (l << 2) | (c << 1) | r; // vecindario como número 0..7
next[i] = (R >> idx) & 1; // el bit idx de la regla
}
row = next; y++;
}
};
}
},