Simulating natural systems: automata, flow fields, and growth
Learning objectives
- learner can implement cellular automata (Game of Life, Langton's Ant, continuous CA) from local neighbour rules
- learner can build Perlin flow fields and noise fields that steer motion organically
- learner can grow organic structures via diffusion-limited aggregation and circle packing
Capstone — one whole task that evidences the objectives
Build a 'nature of code' simulation suite: a Game-of-Life/Langton's-Ant automaton, a Perlin flow field steering particles, and a growth process (DLA or circle packing), each running as a self-contained live sketch.
Prerequisite modules
Nature simulations are the visual backbone of an audiovisual live set: an automaton pulsing on the beat, a flow field carrying particles through a breakdown, a dendritic growth blooming over a build. This module turns three families of natural-system algorithms into self-contained sketches you can drop into a projection rig and mutate live — where every sketch must keep running while you edit it, so clean state handling matters as much as the rule itself.
The arc starts with the most constrained system: grid automata. Begin from the local-neighbour-rule model and its non-negotiable two-phase update (compute all next states, then apply), instantiate it as Conway’s two survival/birth rules, then loosen it twice — Langton’s Ant swaps the synchronous grid for a single agent writing its memory into the environment, and the continuous-value averaging CA trades discrete states for fluid wave propagation. Next you leave the grid’s lockstep for continuous space: 2D noise traversal (nested loops, per-axis seeds, the typewriter-style row reset) generates smooth fields, noise-driven grid displacement clusters shapes organically, and mapping noise to angles yields the flow field that steers your particles. Finally, growth: DLA’s snap-to-nearest-neighbour accretion and collision-frozen circle packing each build structure no one designed.
The nine required atoms gate the capstone directly — each of its three sketches fails without its rule set, and the noise-traversal mechanics recur in all field work, which is why the two-phase CA update and 2D noise loop are drilled to automaticity. Supporting atoms deepen rather than gate: the random-walk lineage behind DLA, emergence as the unifying idea, organic geometry as the aesthetic frame, plus pointers toward the Nature of Code track and camera-input extensions.
Walkthrough
Emergence — complex behaviour from simple local rules — is the visual backbone of an AV set. We’ll build three families of natural-system sketches, each self-contained. Paste each into the p5 web editor and press Run. Each keeps running while you edit — clean state handling matters as much as the rule.
1 — Conway’s Game of Life (a grid automaton). The rule is entirely local: a cell lives if it has 2–3 live neighbours, is born with exactly 3. The non-negotiable trick is the two-phase update — compute all next states from the current grid, then swap ([[cellular-automata]], [[game-of-life-rules]]).
let g, cols, rows, s = 8;
function setup() {
createCanvas(400, 400); noStroke(); frameRate(12);
cols = width / s; rows = height / s;
g = Array.from({ length: cols }, () => Array.from({ length: rows }, () => (random() < 0.3 ? 1 : 0)));
}
function draw() {
background(15);
for (let i = 0; i < cols; i++) for (let j = 0; j < rows; j++) if (g[i][j]) { fill(120, 255, 180); rect(i * s, j * s, s - 1, s - 1); }
let next = g.map(a => a.slice()); // phase 1: compute into a copy
for (let i = 0; i < cols; i++) for (let j = 0; j < rows; j++) {
let n = 0;
for (let a = -1; a <= 1; a++) for (let b = -1; b <= 1; b++) if (a || b) n += g[(i + a + cols) % cols][(j + b + rows) % rows];
next[i][j] = (g[i][j] && (n === 2 || n === 3)) || (!g[i][j] && n === 3) ? 1 : 0;
}
g = next; // phase 2: apply
}
2 — Langton’s Ant (an agent that writes to its world). Swap the synchronous grid for a single agent: on a white cell turn right, on black turn left, flip the cell, step forward. From two rules, ~10,000 steps of chaos then a sudden ordered “highway” — emergence you can watch ([[langtons-ant-emergent-order]]).
let grid = {}, x, y, dir, s = 5;
function setup() { createCanvas(400, 400); noStroke(); x = 40; y = 40; dir = 0; }
function draw() {
for (let k = 0; k < 200; k++) { // many steps/frame
let key = x + ',' + y, on = grid[key];
dir = (dir + (on ? 3 : 1)) % 4; // right on black, left on white
grid[key] = !on;
fill(on ? 15 : 255); rect(x * s % width, y * s % height, s, s);
x += [1, 0, -1, 0][dir]; y += [0, 1, 0, -1][dir];
x = (x + 80) % 80; y = (y + 80) % 80;
}
}
3 — a noise flow field steering particles. Continuous space now: map 2D noise to an angle at every point, and let particles ride the field. This is the workhorse of organic motion ([[perlin-noise-flow-field]], [[2d-noise-field]]).
let ps = [];
function setup() { createCanvas(400, 400); for (let i = 0; i < 500; i++) ps.push({ x: random(width), y: random(height) }); }
function draw() {
background(15, 20); stroke(180, 220, 255, 60);
for (let p of ps) {
let a = noise(p.x * 0.005, p.y * 0.005) * TWO_PI * 4;
let nx = p.x + cos(a), ny = p.y + sin(a);
line(p.x, p.y, nx, ny);
p.x = (nx + width) % width; p.y = (ny + height) % height;
}
}
4 — a continuous-value CA (fluid waves). Loosen Conway from discrete to continuous: each cell relaxes toward the average of its neighbours, and disturbances propagate like ripples — a different emergent texture from the same local-averaging idea ([[continuous-ca]]).
let f, cols, rows, s = 8; // s must divide the canvas evenly (400/8 = 50) so the grid loops stay in bounds
function setup() {
createCanvas(400, 400); noStroke();
cols = width / s; rows = height / s;
f = Array.from({ length: cols }, () => Array.from({ length: rows }, () => random()));
}
function draw() {
let next = f.map(a => a.slice());
for (let i = 0; i < cols; i++) for (let j = 0; j < rows; j++) {
let sum = 0, c = 0;
for (let a = -1; a <= 1; a++) for (let b = -1; b <= 1; b++) { let ni = i + a, nj = j + b; if (ni >= 0 && ni < cols && nj >= 0 && nj < rows) { sum += f[ni][nj]; c++; } }
next[i][j] = sum / c; // relax toward neighbour average
fill(next[i][j] * 255, 120, 255 - next[i][j] * 200); rect(i * s, j * s, s, s);
}
if (frameCount % 30 === 0) f[floor(random(cols))][floor(random(rows))] = random() < 0.5 ? 0 : 1; // poke it
else f = next;
}
5 — collision-frozen growth (circle packing). Structure no one designed: drop a circle, grow it until it touches another, freeze it, repeat. Accretion builds an organic, gap-filling pattern ([[circle-packing-collision]] if present).
let circles = [];
function setup() { createCanvas(400, 400); noStroke(); }
function draw() {
background(12);
for (let attempt = 0; attempt < 5; attempt++) {
let c = { x: random(width), y: random(height), r: 1, grow: true };
for (let o of circles) if (dist(c.x, c.y, o.x, o.y) < o.r + 2) { c.grow = false; break; }
if (c.grow) circles.push(c);
}
for (let c of circles) {
if (c.grow) { for (let o of circles) if (o !== c && dist(c.x, c.y, o.x, o.y) < c.r + o.r) { c.grow = false; break; } if (c.x - c.r < 0 || c.x + c.r > width || c.y - c.r < 0 || c.y + c.r > height) c.grow = false; if (c.grow) c.r += 0.5; }
fill((c.r * 6) % 255, 150, 255); ellipse(c.x, c.y, c.r * 2);
}
}
6 — a suite, staged (the capstone). Three self-contained systems is the deliverable — the automaton, the flow field, and a growth process, each a live sketch you’d drop into a rig and mutate on stage. Here they’re staged as one composited scene (CA texture behind, flow-field particles over it) to show they combine:
let ps = [], t = 0;
function setup() { createCanvas(400, 400); for (let i = 0; i < 400; i++) ps.push({ x: random(width), y: random(height) }); }
function draw() {
background(10, 25);
noStroke(); // background: noise-field cells (CA-like texture)
for (let y = 0; y < height; y += 20) for (let x = 0; x < width; x += 20) { let n = noise(x * 0.01, y * 0.01, t); fill(30, 40 + n * 40, 80, 90); rect(x, y, 20, 20); }
stroke(180, 230, 255, 90); // foreground: flow-field particles
for (let p of ps) { let a = noise(p.x * 0.006, p.y * 0.006, t) * TWO_PI * 4; let nx = p.x + cos(a) * 1.5, ny = p.y + sin(a) * 1.5; line(p.x, p.y, nx, ny); p.x = (nx + width) % width; p.y = (ny + height) % height; }
t += 0.005;
}
What good looks like. Systems that surprise you — Life should produce gliders and still-lifes you didn’t place, the flow field should braid particles into currents, growth should fill space organically. The craft is in the staging: one system as a bed, one as motion, colour and density chosen so the emergence reads rather than turns to visual noise. If a sketch freezes, it’s usually a state bug (the two-phase update, or an unbounded grow loop) — which is exactly why clean state matters when you’re editing it live. (Skill map: live-visualist Domain A4 — simulation and emergence.)
Now make it yours. Seed Life with a glider gun instead of random. Colour Langton’s ant cells by visit count. Drive the flow-field noise scale from mouseX. Make circle-packing colour by radius. Composite a different pair of systems in the capstone.
Runnable examples
Generated from the context/ instrument corpus by concept (redistributable idioms only). Do not edit — regenerate with gen-module-examples.mjs.
modulation-warp
osc(4).modulate(src(o0), 0.6).out(o0)
hydra-0022 · CC0-1.0
s0.initP5(); src(s0).modulate(noize(), 0.3).out()
p5live-0038 · CC0-1.0
feedback-trail
osc(4).modulate(src(o0), 0.6).out(o0)
hydra-0022 · CC0-1.0
function draw(){ fill(0, 20); rect(0, 0, width, height); circle(mouseX, mouseY, 40) }
p5live-0003 · CC0-1.0
noise-field
noise(4, 0.1).out()
hydra-0002 · CC0-1.0
float h21(vec2 p){return fract(sin(dot(p,vec2(12.9898,78.233)))*43758.5453);}
glsl-0013 · public-domain
flow-field
let ang = noise(x*0.01, y*0.01) * TWO_PI
p5live-0008 · CC0-1.0
blur-soften
filter(BLUR, 4)
p5live-0073 · CC0-1.0
noise-drift
let x = noise(frameCount*0.002)*width, y = noise(frameCount*0.003)*height
p5live-0007 · CC0-1.0
Atoms in this module
Required — these gate the capstone
Supporting — enrichment, not gating
Part of curricula
- Live Visualist — zero to performing live-coded & generative visuals — Perform the set — live-coded, generative, audio-reactive visuals for an audience optional