Last time we designed a whole generative system from a blank file, and right at the end I left you with a nagging question that I promised we'd finally give a proper name to. All arc long we kept bumping into the same spooky thing: a four-line agent grows negotiated borders, one grammar rule grows a town, a single seed cell blooms into a living blob. Tiny rules, huge behaviour. And underneath it there was always this feeling that the good stuff - the alive stuff, the stuff worth looking at - lives on a knife's edge. Turn the knobs one way and everything freezes into boring order. Turn them the other way and it all dissolves into static noise. The interesting things happen in a thin band right between those two. Today we finally name that band, and it turns out mathematicians have been staring at it for forty years. Welcome to complexity theory, artist's edition.
I want to be upfront: this is a slightly more heady episode than usual. There's less "make this exact picture" and more "here's the framework that explains why everything we've built actually works". But don't worry, I'm not going to hand you a wall of equations and run away. Every idea here comes with runnable code, and a couple of them are honestly some of the prettiest things you can put on a canvas. So let me show you what I figured out about the edge of chaos, and by the end you'll have a mental map that ties together half this series. Allez, let's give the magic a name.
We start with the single most famous little equation in all of chaos, and it's almost insultingly simple. Take a number between 0 and 1, and update it like this: x becomes r * x * (1 - x). That's it. One multiply, one subtract. The r is a knob - a growth rate. This is the logistic map, and it was invented to model animal populations (too few and they breed up, too many and they starve back down). Watch:
// the logistic map: the whole of chaos theory in one line.
// x lives in [0,1], r is a growth-rate knob roughly in [0,4].
function logistic(x, r) {
return r * x * (1 - x);
}
Now the interesting bit is what happens when you run it over and over from some starting value. Let me write a tiny driver that iterates it a bunch of times and just tells me where it ends up:
// run the map many times from x0 and report the last few values it settles into.
function orbit(r, x0 = 0.5, warmup = 200, keep = 8) {
let x = x0;
for (let i = 0; i < warmup; i++) x = logistic(x, r); // let transients die out
const tail = [];
for (let i = 0; i < keep; i++) { x = logistic(x, r); tail.push(x.toFixed(4)); }
return tail;
}
Here's where your brain does a little backflip. Feed it three diffrent values of r and you get three completely diffrent kinds of destiny out of the exact same equation:
console.log(orbit(2.5)); // -> all 0.6000 : settles to ONE value (a calm fixed point)
console.log(orbit(3.2)); // -> 0.79.., 0.51.., 0.79.., 0.51.. : oscillates between TWO
console.log(orbit(3.9)); // -> never repeats, looks random : full-blown CHAOS
Same rule. At r = 2.5 the population settles to a single steady number and sits there forever - dead calm, total order. Nudge r up to 3.2 and it starts flip-flopping between two values, a little heartbeat. Push it to 3.9 and it goes wild, never repeating, sensitive to the tiniest change in the starting value. This is the whole story of complexity in miniature: order on one side, chaos on the other, and a knob that walks you between them. The question that should be itching at you now is what happens in between - and that's the prettiest picture in this whole field.
If I sweep r slowly from left to right and, at each value, plot every value the map settles into, I get the famous bifurcation diagram. It's a map of the road from calm to chaos, and it's genuinly one of the most beautiful objects in mathematics. Let me draw it on a canvas:
// the bifurcation diagram: sweep r across the canvas, plot the attractor at each r.
function drawBifurcation(ctx, w, h) {
ctx.fillStyle = "#12101a"; ctx.fillRect(0, 0, w, h);
ctx.fillStyle = "rgba(255, 90, 200, 0.35)"; // soft magenta dots
for (let px = 0; px < w; px++) {
const r = 2.5 + (px / w) * 1.5; // r from 2.5 to 4.0 across the width
let x = 0.5;
for (let i = 0; i < 200; i++) x = logistic(x, r); // settle
for (let i = 0; i < 200; i++) { // then plot where it lands
x = logistic(x, r);
const py = h - x * h; // x in [0,1] -> screen y
ctx.fillRect(px, py, 1, 1);
}
}
}
Run that against a canvas and stare for a second. On the left it's a single clean line - one fixed point per r. Then at about r = 3 the line splits in two. Then each of those splits into two again (four values), then eight, then sixteen, faster and faster - this is period doubling, and each split is called a bifurcation. And then around r = 3.57 the whole thing shatters into a foggy cloud: chaos. But look closer at the cloud and you'll spot pale vertical windows of calm hiding inside it, little slivers where order briefly returns before dissolving again. Order and chaos aren't two countries with a border - they're marbled through each other. That marbling is the first big idea of the day.
const canvas = document.querySelector("canvas");
canvas.width = 800; canvas.height = 500;
drawBifurcation(canvas.getContext("2d"), canvas.width, canvas.height);
Here's the thing that took me embarrassingly long to really get. That thin, marbled boundary between order and chaos has a name - the edge of chaos - and it's not just a curiosity. It's where all the interesting behaviour lives. Too far into order and your system is frozen and predictable, boring. Too far into chaos and it's random static, also boring in its own way (noise carries no structure). The band right between them is where you get patterns that are neither dead nor random: patterns that grow, remember, compute, surprise you.
We actually met this before without naming it, way back in the cellular automata episodes. Remember Wolfram's rules from episode 47? People eventually sorted all those CA rules into four classes: Class I dies to a blank screen (order), Class II settles into simple repeating stripes (still order), Class III is pure noise (chaos), and Class IV - the rare, precious one - makes complex crawling structures that never settle and never dissolve. Rule 110 is Class IV, and it turns out to be powerful enough to compute anything. Class IV is the edge of chaos.
There's even a knob for it, discovered by Chris Langton: lambda, roughly "what fraction of the rule's outcomes are alive rather than dead". Crank lambda from 0 and you march order -> edge -> chaos, the same journey as sweeping r. Let me show you lambda is a real, countable thing for an elementary rule:
// Langton's lambda for an elementary CA rule: the fraction of its 8
// neighbourhood-outcomes that produce a LIVE cell. low = frozen, high = chaotic.
function lambda(ruleNumber) {
let alive = 0;
for (let i = 0; i < 8; i++) alive += (ruleNumber >> i) & 1; // count the 1-bits
return alive / 8;
}
console.log(lambda(0)); // 0.0 -> everything dies (Class I, dead order)
console.log(lambda(110)); // 0.625 -> that fertile middle band (Class IV, the edge)
console.log(lambda(255)); // 1.0 -> everything lives, saturates (order again)
The lesson for us as artists is enormous and I want to say it plainly: when your generative system looks boring, you are almost always too far into order or too far into chaos, and the fix is to find its lambda knob and walk it toward the edge. Every "temperature" or "chaos" or "randomness" slider you've ever built is secretly this same dial.
Let's name another thing we kept meeting. When a system runs for a long time, the set of states it eventually settles into is called an attractor. The logistic map at r = 2.5 has a point attractor (one value). At r = 3.2 it has a periodic attractor (a little two-state cycle). And in the chaotic zone it has something wonderful: a strange attractor - it never repeats, but it never wanders off to infinity either. It's trapped forever inside an intricate shape, tracing it out but never quite landing.
And strange attractors are, no exaggeration, some of the most gorgeous generative art you can make with almost no code. Here's a de Jong attractor. Two little equations, iterate a point through them a few hundred thousand times, and plot every position it visits with a whisper of alpha:
// the de Jong attractor: a point wandering forever inside an intricate trapped shape.
function deJong(x, y, a, b, c, d) {
return [
Math.sin(a * y) - Math.cos(b * x),
Math.sin(c * x) - Math.cos(d * y),
];
}
Now we just run it and let the visited points pile up. The magic is in the alpha: each dot is nearly invisible, so brightness builds only where the orbit passes often, and the hidden structure develops like film in a darkroom:
// plot 300k points of the orbit. faint dots -> density becomes light.
function drawAttractor(ctx, w, h) {
ctx.fillStyle = "#0d0b14"; ctx.fillRect(0, 0, w, h);
ctx.fillStyle = "rgba(255, 120, 210, 0.06)"; // barely-there magenta
const a = -2.0, b = -2.0, c = -1.2, d = 2.0; // these four numbers ARE the artwork
let x = 0, y = 0;
for (let i = 0; i < 300000; i++) {
[x, y] = deJong(x, y, a, b, c, d);
const px = (x + 2) / 4 * w; // map [-2,2] -> canvas
const py = (y + 2) / 4 * h;
ctx.fillRect(px, py, 1, 1);
}
}
Change those four numbers a, b, c, d and you get an entirely diffrent creature every time - swirling ribbons, delicate webs, smoky knots. That's your seed-based art principle from episode 24 in its purest form: four numbers in, an infinite unique image out, perfectly reproducible. I could lose an afternoon just nudging these constants, and I have. The "strange" in strange attractor is really just "the shape the edge of chaos likes to trace".
So far we turned the knobs to find the edge. But here's the mind-bending part, and it's the deepest idea in the episode: some systems drive themselves to the edge, with nobody touching a dial. They naturally sit right at the critical point between order and chaos. This is called self-organized criticality, and the classic demo is a sandpile.
Picture dropping grains of sand one at a time onto a table. The pile gets steeper and steeper until - at some critical slope - a grain triggers a slide. Sometimes one grain moves; sometimes you get a huge avalanche. And the pile keeps itself at exactly that critical angle: too shallow and it just builds up, too steep and it collapses, so it hovers forever at the tipping point. Let me build the abelian sandpile. A grid of numbers; any cell with 4 or more grains topples, handing one grain to each neighbour:
// abelian sandpile: a cell with >= 4 grains topples, giving 1 to each of its 4 neighbours.
// grains falling off the edge just vanish. returns the avalanche size (number of topples).
function topple(grid, n) {
let avalanche = 0, unstable = true;
while (unstable) {
unstable = false;
for (let y = 0; y < n; y++) {
for (let x = 0; x < n; x++) {
if (grid[y * n + x] >= 4) {
grid[y * n + x] -= 4;
if (x > 0) grid[y * n + x - 1]++;
if (x < n - 1) grid[y * n + x + 1]++;
if (y > 0) grid[(y - 1) * n + x]++;
if (y < n - 1) grid[(y + 1) * n + x]++;
avalanche++;
unstable = true;
}
}
}
}
return avalanche;
}
Now we drive it: drop grains one at a time in the middle, and each drop, measure how big the resulting avalanche was. Most drops do almost nothing. Every so often, one grain sets off a cascade that rearranges half the grid:
// drive the pile: drop grains at the centre, record every avalanche's size.
function runSandpile(n = 51, drops = 20000) {
const grid = new Int32Array(n * n);
const c = Math.floor(n / 2);
const sizes = [];
for (let i = 0; i < drops; i++) {
grid[c * n + c]++; // one grain in the middle
sizes.push(topple(grid, n)); // let it settle, remember the avalanche size
}
return sizes;
}
There's no knob here. Nobody told the pile to sit at the critical slope. It found the edge by itself, just from the simple local rule "topple when you're too tall". That is self-organized criticality, and once you see it you start noticing it everywhere: forest fires, earthquakes, traffic jams, neurons firing, even how ideas spread. Nature loves the edge and keeps wandering back to it on its own.
How do we know a system is sitting at the critical edge and not just being random? It leaves a fingerprint, and the fingerprint is a power law. Look at those avalanche sizes: there are tons of tiny ones, a fair few medium ones, and a small handful of enormous ones - and the relationship between "size" and "how often" is astonishingly regular. Let me bucket the avalanches by size and count them:
// bucket avalanche sizes into powers of two and count each bucket.
// a power law shows up as counts that fall by a roughly CONSTANT ratio each step.
function sizeHistogram(sizes) {
const buckets = {};
for (const s of sizes) {
if (s <= 0) continue;
const bin = Math.floor(Math.log2(s)); // group by order of magnitude
buckets[bin] = (buckets[bin] || 0) + 1;
}
return buckets;
}
const sizes = runSandpile();
console.log(sizeHistogram(sizes));
// smaller avalanches are FAR more common than big ones, in a very regular ratio
The signature of a power law is this: there is no "typical" size. A normal bell-curve distribution has an average that things cluster around - human heights, say. A power law doesn't. You get avalanches at every scale, and the small ones outnumber the big ones by a fixed ratio rather than a fixed amount. On a log-log plot (log of size against log of count) a power law becomes a straight line, and that straight line is how physicists recognise a system at criticality. Same math describes the size of earthquakes, the popularity of words, the reach of a viral post. Scale-free. No typical size. The edge signs its work with power laws.
For us, the takeaway is a design instinct: if you want a piece to feel natural and alive rather than mechanical, you often want power-law variety, not uniform variety. A few big features, several medium ones, many tiny ones. Not everything the same size. Your eye reads that ratio as "organic" because it's the ratio the physical world actually runs on.
Right, let me bring it back down to the desk, because theory is only worth anything if it changes how you work. Here's the whole framework distilled into a practical move. Almost every generative system you build has, somewhere in it, a dial that controls how much disorder is in play - a noise amount, a randomness weight, a mutation rate, a temperature. Expose that dial, name it honestly, and sweep it slowly while you watch:
// almost every system has a hidden "temperature" between order and chaos.
// EXPOSE it and sweep it - the good stuff is nearly always in the middle band.
function mixOrderAndChaos(value, neighbourAvg, temperature) {
// temperature 0 -> pure order (copy your neighbours, freeze)
// temperature 1 -> pure chaos (ignore everything, go random)
const ordered = neighbourAvg;
const chaotic = Math.random();
return ordered * (1 - temperature) + chaotic * temperature;
}
That's the meta-skill this episode buys you. When something you built looks dead, you now know which direction to reach: it's too ordered, add temperature. When it looks like TV static, it's too chaotic, pull temperature down. You're no longer poking randomly - you're walking a system along the exact axis complexity theory says matters, hunting for its edge. Episode 155's whole "tuning is the art" hour was really this: I was searching each system's edge of chaos by feel. Now you can do it on purpose, with a name for what you're looking for.
And it re-frames this entire arc, honestly. Emergence, back in episode 61, was the observation: dumb local rules make rich global order. Complexity theory is the explanation: rich order lives in a specific place, the thin edge between frozen and random, and both nature and good generative art keep gravitating there. Evolution finds it, learned CA find it, sandpiles find it for free, and you find it with a knob and a good eye.
r*x*(1-x) - shows it: low r settles to a point, higher r oscillates, high r goes fully chaotic. Same rule, a knob walks you acrossSo that's complexity theory for artists, and it's the piece that quietly wraps up this whole generative-systems arc. We spent a long stretch building things that grow themselves - noise fields, agents, grammars, evolving populations, learning cells, a system designed from scratch - and every single one of them worked because of the same underlying truth: complexity is not order and it's not chaos, it's the fertile edge between them, and both nature and good art keep drifting back to that edge. The equations have names now, but the instinct is what you carry to the desk: when it looks boring, ask which way the edge is, and walk there.
And here's the thread forward, because it's a turn. For a hundred-and-fifty-odd episodes we've been heads-down building technique after technique, and you've now got a genuinly deep toolbox. But a toolbox isn't a body of work, and knowing how to make things is not the same as knowing what to do with the things you've made. So next time we lift our heads up from the code and start thinking about the work itself - not the next algorithm, but the collection, the practice, the you-as-an-artist part. Different muscle, and an important one. 't Was plezant to finally name the edge with you today :-).
Sallukes! Thanks for reading.
X