Isomorph, model 01
A control deck for a recursive Game of Life, built like a piece of studio hardware.
Isomorph is a Game of Life you can zoom into forever. Every cell is itself a machine running the same rules, so zooming in never reaches a bottom and zooming out never reaches a top. It renders in real time in WebGL2 with an HDR pipeline, motion trails and a glyph layer bound to the simulation.
This case study is about the part people touch: the control deck. It replaced a set of floating glass panels that looked like every other generated interface and did not feel like an instrument.
The constraint: a shell over a sealed renderer
The renderer is written in Haxe and compiled into one JavaScript bundle. For this work the compiler and the engine library were not available, so the interface could not change a line of the renderer. Everything had to live in a shell around it.
The shell talks to the renderer through three narrow bridges:
- Two globals the renderer already reads every frame: the simulation rate and a zoom boost. The deck writes them; the renderer smooths them.
- Ink color and zoom direction only change on a middle or right click in the engine. The engine latches a button press until its next update, so the deck dispatches a synthetic press and release on the canvas and the engine counts it as one click. Real clicks from the visitor are mirrored back into the deck.
- Lenses are CSS filter chains on the canvas wrapper. Every chain lists the same six functions in the same order, so the browser can interpolate between any two of them.
From glass to hardware
The visual language comes from dark IBM keyboards and the Roland MC-500 sequencer: flat plastic, keycaps with physical depth, LEDs, a dot-matrix LCD and a floppy drive slot. There is no blur and no translucency anywhere.
The LCD is a canvas with a hand-drawn 5 by 7 font. It shows the rate, the zoom, the lens and a frame-rate band scaled to the refresh rate of the display, and it takes over for short messages when something changes.
The deck has two layouts with the same keys. The panel sits in a corner. The strip is one row of keys along the bottom, like a keyboard resting on a desk, and it can be switched with one key.
No fixed sizes
Every dimension in the deck derives from two fluid tokens defined with clamp(): the size of a key and the pitch of an LCD dot. The key grows with the viewport inside a range and grows again for coarse pointers, which is an adaptation to the input, not to a device.
- The LCD counts how many columns fit in its own width. More room means more text, not bigger text.
- The fader counts how many LEDs fit along its length.
- The strip is a wrapping flex row where only the LCD and the fader grow. With room it is one row. Without room the groups wrap on their own, and inside a narrow container the composition changes through a container query, not a device breakpoint.
- The four lens keys are one row or a 2 by 2 grid, never three and an orphan.
Loading is part of the machine
The deck appears at once, but inert. While the simulation data loads, the drive LED blinks and the LCD reads READING DISK and TRACK 2/3. The progress is real: a PerformanceObserver watches the three data files arrive. When the renderer is ready, the deck runs a LED test and powers on. The layout never changes size at that moment, because every decision about it was made before loading started.
Coarse and fine
The rate runs from almost frozen to about 65,000 times the base speed on an exponential curve. A fader alone cannot be both fast and precise across that range, so next to it sits a jog wheel: an endless encoder with no absolute position. Each detent moves the rate by about four percent. It turns by circular drag, by mouse wheel, by arrow keys or by the bracket keys.
Building it exposed a bug. The fader had a magnetic detent at 1×, and a keyboard step from 1× landed inside the detent and snapped back, so the arrow keys could never leave 1×. Now only a drag snaps; discrete steps stop on 1× when they cross it and move on with the next step.
Capture
- Snapshot saves the canvas as a PNG with the current lens applied.
- REC records the simulation to video with
MediaRecorder, MP4 where the browser supports it and WebM otherwise, up to fifteen seconds, with a red LED and a timer on the LCD.
Sound
All sound is synthesized with Web Audio; there are no audio files. Keys click on the way down and up, switches thunk, the jog ticks on each detent. A music layer, a synthesized drone, listens to the simulation: it samples the canvas at eight by eight pixels four times a second, and the brightness opens its filter while the movement between samples raises a layer of hiss. Both stay off until the visitor presses the sound key.
State you can send
The URL hash carries only what differs from the default, in readable form, for example #lens=plate&rate=2.3&ink=amber. Opening that link reproduces the setup once the renderer is running. A link key copies it.
A shift key on the strip gives each action key a second function printed in ink on the keycap. It brings to the strip what only the panel had: zoom drive, scanlines and link. Icon and legend of both functions share one grid cell, so a key never changes width when the layer switches. Sound has one key with the system keys, visible in both layouts, because on a phone there is no keyboard shortcut to fall back on. It turns on the music and the key clicks together.
Accessibility
- Real roles: the fader and the jog are sliders, the lens bank is a radio group, the toggles are switches, the panel sections are tabs.
- A polite live region announces changes; the LCD itself is decorative.
- Every control works from the keyboard, and a shortcut shows its key and result at the top of the screen, which also makes screen recordings readable.
- Reduced motion removes animation, and forced colors mode gets real borders and highlight outlines.
Delivery
The site is static files behind Caddy. The content security policy allows scripts only from the site itself plus two hashed inline snippets, and fonts only from the site itself, so Inclusive Sans is self-hosted. One lesson from shipping: a script named after a smart-home word was blocked by content blockers on the name alone, and with it the whole speed control. Shell scripts now get neutral names.