← BACK TO MACHINA

FIELD NOTES

How MACHINA is made

MACHINA renders the Mandelbrot set from scratch, in your browser, one pixel at a time — no shader, no WebGL, no image assets — and drives a pre-planned camera through five real, verified coordinates before the float64 precision budget runs out.

The Concept

The Mandelbrot set is the set of complex numbers c for which the sequence z₀ = 0, zn+1 = zn² + c never escapes to infinity. That is the entire specification. Every shape on screen — the cardioid, the seahorse filaments, the infinitely nested minibrots — is a consequence of that one line, not a drawn or modelled asset. MACHINA’s job is to compute it live, continuously, while a scripted camera descends through thirteen orders of magnitude and then pulls back before the arithmetic gives out.

The Math

Each pixel maps to a point c in the complex plane and is iterated until it either escapes a bailout radius or exhausts an iteration budget that grows with depth. Instead of the raw integer step count — which bands harshly — MACHINA uses the standard renormalized (“smooth”) iteration count: n + 1 − log(log|z|) / log 2, evaluated at the escape step, which turns discrete iteration counts into a continuous value fit for a gradient. Orbits that never escape are still expensive to prove bounded, so a lightweight periodicity check stores a checkpoint (zr, zi) every 24 steps and bails out the instant an orbit exactly repeats — a cheap, correct shortcut that keeps the deep-interior regions from burning their full iteration budget.

The Renderer

No WebGL: a plain <canvas> 2D context, a Float32Array holding one escape value per pixel, and a Uint8ClampedArray backing an ImageData that gets putImageData’d every frame. Colour is decoupled from math entirely — three 1,024-entry gradient look-up tables are precomputed once at load, so cycling a palette recolours the whole field from the stored escape values with zero recomputation.

Recomputing every pixel every frame at full iteration depth would blow the frame budget, so the field refines progressively: pixels are visited in a 3×2 interleaved tile order, and each frame renders exactly one tile — full resolution, full iteration budget, time-boxed to ~8ms as a safety valve on slower hardware. A complete six-tile cycle refreshes the whole picture; because the camera is always creeping forward, the image is perpetually a beat behind and catching up — coarse, then sharp, on a loop, which is the requested render-coarse-then-sharpen behaviour applied continuously rather than once. During the fast pull-back, that scheme is swapped for a cheap full-frame block-filled pass, since detail is beside the point mid-whoosh.

The Path

Five waypoints are hard-coded as real coordinates, each independently sampled beforehand for genuine visual variance (a mix of escaping and bounded orbits — not a blank interior, not a flat exterior):

seahorse valley → c = −0.7436499 + 0.1318259i, ×10⁵
the minibrot → c = −0.743643887037 + 0.131825904205i, ×10¹¹

A single clock t (log-zoom, where zoom = 10^t) advances at ≈2 orders of magnitude per 20 seconds. The camera centre is a piecewise smootherstep interpolation between whichever two waypoints bracket the current t, so panning and zooming happen as one continuous gesture rather than a hard cut between shots. Past the fifth waypoint the centre holds — diving straight into the minibrot — until t reaches 13, the guard threshold where a double-precision float’s ~15–17 significant digits stop resolving neighbouring pixels. At that point the same centerAtT() function that drew the path forward gets driven backward, eased with a fast-out cubic, tracing the exact journey home in reverse before a short hold and a fresh loop.

The Controls

Clicking the field doesn’t hijack the path — it opens a temporary offset vector that eases toward the clicked point, holds a moment, then eases back to zero, so the scripted dive always resumes on schedule. Pausing freezes the clock (and the narrative timers) but leaves the corner brackets, grain and cursor reticle animating, so the frame reads as alive even at rest. prefers-reduced-motion disables autoplay entirely and lands on a single, fully-sharpened, static composition instead.

Iteration Passes

  1. Coordinate verification — before writing any UI, each candidate waypoint was sampled headlessly (mean/std/interior-fraction of escape values across a test grid) to confirm real visual detail at its target zoom, not a blank void or a flat plane.
  2. Renderer core — escape-time math, periodicity detection, and the tile-interleaved progressive scheme, tuned by watching the render-ms readout stay inside frame budget at the deepest, slowest-converging waypoint.
  3. Path + HUD — waypoint timing, the depth rail, live coordinate/zoom/iteration telemetry, and the pull-back guard, then a pass devoted purely to pacing: card duration, hold time, and easing curves tuned until the loop read as meditative rather than mechanical.
  4. Responsive + accessibility — mobile pixel-budget scaling, a reduced-motion static fallback, focus-visible states on every control, and a visible-frame check across both 1440×900 and 390×844.