CRITICALITY
Build Notes · No. 61

How this pile was built.

CRITICALITY is a live Abelian sandpile rendered in canvas 2D. Grains rain onto a 129×129 grid; any cell holding four topples, shedding one grain to each neighbour, which can topple in turn. Its single job: let a viewer drop one grain too many and watch a self-similar avalanche repaint the field — while, left alone, the pile drives itself to the critical state and settles into its identity fractal.

01 Palette

Four colours only. The bright gold is reserved for full cells, marks and rules; body text runs on a lighter ink so nothing small sits under 4.5:1 on the slate.

Slate
#1C1E22
The empty table. A cool near-black that lets a single lit grain read against it.
Grain gold
#E0B23C
A cell at capacity — three grains, one from toppling. The brightest note; used for marks, never small text.
Avalanche red
#C2442E
The toppling front. Painted only on cells firing this frame, then decaying back to gold.
Dust
#D8D2C4
Settled sand — the readout digits and telemetry, warm and legible over the dark.

02 Type

Space Grotesk · 700 · display
Grains that topple.
A grotesque with faint mechanical quirks — right for a system that is half physics, half arithmetic. Set tight and large for the one moment of bravery.
JetBrains Mono · 400–600 · instrument
last_avalanche = 54,610
Every number is a count of grains or topplings, so the telemetry, legend and eyebrows are monospaced with tabular figures — the digits never jitter as avalanches tick.

03 Technique

No libraries. Pure canvas 2D and typed arrays — the simulation is small and hot, so a shader or physics engine would only get in the way. The field is drawn into a 129×129 ImageData (one pixel per cell) and scaled up with smoothing off, which keeps each grain a crisp block for the cost of one drawImage.

The signature — toppling. Cell states live in an Int16Array. A grain lands and, if a cell reaches four, it joins an unstable list. Each frame runs parallel Abelian sweeps under a 7 ms time budget: every unstable cell sheds 4×⌊grains/4⌋, its neighbours inherit the load via a delta buffer marked per-generation so no cell is touched twice, and the newly overloaded cells form the next sweep. Because toppling is Abelian the order never changes the result — so the sweep can fire a whole ring at once, which is exactly what draws the expanding avalanche. Toppling cells are flashed in a heat buffer that decays each frame, painting the red cascade front. Grains crossing the boundary fall into a sink, and that loss is what lets the pile balance itself. Grains are only dripped when the field is fully settled — the slow-drive / fast-relax separation of timescales that makes the avalanche sizes fall on a power law.

The identity fractal. Press Identity fractal and the field is set to 6 − stabilize(6), then relaxed live: the avalanche settles into the sandpile group's identity element — the stable configuration that adds to nothing — an intricate self-similar fractal computed honestly, not drawn.

04 Iteration log

Pass 1 — Craft
  • Caught a real contrast miss: the label/hint/colophon ink token was ~3.8:1 on the slate. Lifted --ink-3 from #78746B to #8C887D to clear the 4.5:1 floor (the recurring W2 failure).
  • Re-tuned the grain ramp so levels 1→2→3 step more evenly (#463C2A → #927236 → #E0B23C); the fractal's shells now read distinctly instead of the mid-tones muddying together. Legend swatches matched to the exact render values.
  • Set a deliberate type scale and reserved gold strictly for marks and full cells — never small text.
Pass 2 — Depth
  • Added a reward moment: a system-spanning avalanche (>2000 topplings) pulses the frame border gold for 260 ms — a second-read detail you only notice once a big cascade fires.
  • Enriched the telemetry with a log-scaled histogram of recent avalanche sizes, so the power-law tail is visible: a wall of small events with rare tall spikes.
  • Tightened the red trail decay (0.84 → 0.82) so the avalanche front feels sharper without losing the wake.
Pass 3 — Hardening
  • Verified 375px: field leads, rail stacks under it, nav and wordmark sit fully inside the viewport box (not just scrollWidth≤375).
  • Reduced motion computes the identity fractal once and renders a settled, rich static frame — no rAF, no drip — confirmed with an emulated headless pass (richness 0.91, drop count frozen).
  • DPR capped at 2, resize re-scales the display, rAF pauses on document.hidden with the drip clock reset on resume. Removed the one unused ornament (a dead .stat.wide rule). Final copy read.

05 Elsewhere