PERCOLATE.

Field notes · site 155 · Wave 7 Chance

How a whole grid learns to connect at once

Percolate takes one stochastic law — site percolation on a square lattice — and makes the phase transition physical. Open each cell with probability p, cluster what touches what, and raise p. Below pₒ ≈ 0.5927 the field is all islands; cross it and a single cluster leaps edge to edge. This page is how it is built.

01 The concept

One number decides whether the field is connected

Percolation is the cleanest phase transition in probability. Fill a large grid at random — each cell open with probability p, independently — and ask a single question: is there a path of open, touching cells all the way across? For small p the answer is plainly no. For large p, plainly yes. The surprise is that the switch is sharp: on the infinite square lattice there is a single critical value pₒ ≈ 0.5927 with only finite islands below it and an infinite spanning cluster above it.

The fictional brief: a single, wordless instrument for a curious reader who has heard the phrase "phase transition" and wants to feel one. Its only job is to let you watch connection arrive — not fade in, arrive — as the threshold is crossed.

02 Palette

Two states, one edge between them

Teal is a cell that is open and connected; a warm flare is the cluster that has won its way across. Everything else is unopened ground. The bright marks are reserved for strokes and cells — the body ink is a separate, calmer grey derived to clear 4.5:1 on the dark field.

Ground
#0D1014
Unopened space — the lattice at rest, and the page itself.
Open cell
#4FB0A0 · line / mark
An open, connected site. Used only for cells and strokes, never body text.
Spanning flare
#E86A3C · mark
The cluster that spans edge to edge. It ignites; bloom carries the heat.
Ash ink
#CDD6D2 · body
Derived body ink, ~10.5:1 on ground. The readable voice.

03 Type

A geometric display, a technical monospace

Display · Space Grotesk
connect
edge to edge
A geometric grotesk with just enough quirk to feel built, not decorative. It carries the headline and every section title.
Data · JetBrains Mono
p = 0.5927
198 clusters
Tabular figures for the live readouts — occupation probability, largest cluster, cluster count — so numbers change in place without reflow.

04 Technique

Ten thousand instanced tiles, clustered every frame

  • three.js InstancedMesh — a 100 × 100 grid of tiles (10,000 instances) laid as a shallow slab. Each cell carries a frozen occupation threshold and a smooth relief height as per-instance attributes.
  • Custom ShaderMaterial vertex displacement — openness is decided on the GPU from aRand < uP, so raising p opens cells smoothly for free every frame. The vertex shader lifts open cells to their relief height and spanning cells higher still.
  • Union-find clustering — weighted union with path compression labels every open cell into clusters, then tests whether one root reaches both the left and right columns. Recomputed only when p moves past a small delta, and the spanning membership is uploaded as a single dynamic attribute.
  • EffectComposer + UnrealBloomPass — a high threshold so only the ignited spanning cluster blooms, keeping the teal islands crisp.
  • Threshold sweep — p rests in a spanning hold, then breathes across pₒ so the ignition recurs; a drag/keyboard rail lets you scrub the threshold yourself and feel the jump.

The signature. Below pₒ the largest cluster is a small fraction and the cluster count runs into the hundreds. As the sweep lifts p through 0.5927, dozens of islands fuse in a few frames: the count collapses toward one, the largest cluster leaps to fill most of the lattice, and that winning cluster recolours from teal to a blooming flare the instant it touches both edges. Nothing eases — it arrives.

05 Iteration log

Three passes, driving the transition each time

Pass 1Craft

Rebuilt the hero scrim as a directional multi-stop gradient so the monospace lede clears the bright slab, and narrowed that column to 30rem so its right edge never runs into the flare. Tamed the bloom (strength 0.72 → 0.52, threshold 0.66 → 0.74) so the teal islands read against the orange span instead of washing out, and set the closed / open / spanning colours in linear space so the rendered hexes match the palette.

Pass 2Depth

Gave the spanning cluster a slow per-cell breathing in the vertex shader, and wired the live readouts so the cluster count visibly collapses from hundreds to ~one while the largest-cluster percentage leaps — a second read the data performs on its own at pₒ. Added the drag-and-keyboard threshold rail and an ignition flash the first time a field spans, so the transition can be driven by hand, not only watched.

Pass 3Hardening

Verified 375px (wordmark and nav within the viewport, scrollWidth clean) and added a mobile top-scrim so the hero and readouts stay legible over the slab. Confirmed reduced-motion renders a single static spanning frame with the loop parked, and added a spanning hold window so the static thumbnail is reliably mid-span regardless of load timing. Capped DPR at 2 across renderer, composer and bloom, paused the loop on document.hidden, wired a WebGL context-loss fallback, and removed a redundant vignette layer.