Skip to content
A wrecking ball on a chain smashing through a stack of wooden crates at night, drawn as large pixels on a curved, scan-lined screen

Every pixel is a palette index

Phosphor is our indexed-color 2D engine, written in Swift and aimed at iPhone and iPad. It draws each frame as a small grid of palette indices, the way consoles and home computers of the 1980s and 1990s did, and can show it through a tube you can tune.

v0.9.3 · in development

The palette of the captures on this page: eight ramps of eight steps, made up for them.
Indexed color

Indices, not colors

Each pixel is one byte: an index into a palette of up to 256 colors. A palette groups its colors into ramps, runs from dark to light for one material, and shading is arithmetic on the index. Two steps lighter is two places along the ramp.

A hit flash, a fade or fog is arithmetic on indices, so it costs work in proportion to the palette, not to the number of pixels, and a new color scheme is new data with no new art. The GPU looks each index up in the palette as it draws the frame.

A night scene of hills, a campfire, crates and a moon, in large square pixels with no screen effects
The plain grid, with no display effects: 478 by 220 pixels, drawn at six times.
The grid

A fixed grid, scaled by whole numbers

A game says how much of the screen it needs, such as a number of rows along the short side. The engine picks the largest whole-number scale that shows it, the other side takes what the screen gives, and any leftover becomes a border, so every grid pixel is the same size. On a large iPhone held upright, a 220 by 478 grid fills the screen at exactly six times.

Rotation redraws a shape onto the grid instead of turning a texture, so a rotated sprite is still made of whole pixels and never blurs.

Display

A CRT you can tune

Phosphor can show its grid through a model of a cathode-ray tube. Each effect has its own switch and settings, a game can give players a settings screen for them, and with every effect off a frame takes one pass on the GPU. Drag the split on each picture to compare it with the plain grid.

Scan lines

A dark line along the last physical row of each grid row.

Aperture grille

Red, green and blue stripes, one physical column each. Best seen at full size.

Curvature

Barrel distortion, as if the picture sat on the curved front of a tube.

Corner pin

Moves each corner on its own. Shown here as keystone, with the top edge narrowed.

Color fringing

Red pulls outward from the center and blue inward, more so toward the edges, the way a tube’s three beams drift apart away from the middle.

Glow

A blurred copy added back on top. It has no brightness threshold, so every lit pixel glows.

Persistence

Lit pixels fade over a set half-life in milliseconds, the same on a 60 Hz and a 120 Hz screen. Here on the shooting star, the embers and the barrel.

Burn-in

The ghost a tired screen keeps of whatever it showed longest. Here, a title card held for six seconds before the cut.

Vignette

Darkening toward the corners. The center stays exact.

Over time

Effects that move

Three effects read best moving, so they are shown as clips.

LOOP 0:08

Persistence

Trails fade at the same rate whatever the screen’s refresh rate.

LOOP 0:08

Flicker

An occasional dip in brightness every few seconds, like a tube on unsteady power.

LOOP 0:08

The lean

The rows near one edge slide sideways, and the lean wanders. It reads best with the tired tube below, beside flicker and burn-in.

Presets

A tube in good order, and a tired one

Two presets combine the effects. One is a shadow-mask tube in good order: scan lines, the grille, curvature, fringing, glow, trails and vignette. The tired tube adds burn-in, flicker and the lean, for a set twenty years on and never once serviced.

LOOP 0:08
The good tube, running.
LOOP 0:08
The tired tube, running.
Physics

Physics that settles, and breaks

Phosphor’s 2D rigid-body physics is deterministic. Stacks settle and go to sleep together, a body rolling over ground made of tiles doesn’t catch on the seams between them, and joints can be given a load at which they break.

LOOP 0:08
A wrecking ball on an 11-link chain, into 28 crates welded row by row. Each weld breaks at 1.25 times the load it carries at rest, with a minimum. The splinters and dust come from the particle module.
The crate pyramid standing, with the wrecking ball held back on its chain
Settled, before the release.
The ball in the middle of the pyramid, crates flying with trails behind them
Impact.
Crates scattered across the ground after the collapse
About seven seconds later.
Cloth

Cloth that blows, tears and falls

Version 0.9.3 added cloth for flags and wall hangings. A sheet is pinned to anchors that follow bodies, and gusting wind pushes it. It tears where it is stretched too far, a hit cuts away the cloth under it, and a pin can tear loose or be let go. Each cell is drawn as two triangles, shaded on a palette ramp by how squarely it faces the light.

LOOP 0:08
Gusting wind on a silk flag, a worn flag and a wool hanging. Nothing here is scripted.
LOOP 0:05
One post is moved away at a steady speed until the banner tears. It splits down the middle, then its left half tears off the rod and falls.
LOOP 0:08
A ball on a set path crosses the hanging twice and cuts away the cloth under it. The first pass cuts it in two, and the lower half falls.
LOOP 0:08
A pushed stone falls under the rigid-body physics, and its hanging’s pins tear loose. The other hanging is let go, one side and then the other, and drops in the wind.

Cloth reads the bodies’ positions and writes nothing back, so a world steps to the same bytes with cloth on or off, and a test checks that. On an iPhone 16 Pro Max, a 192-cell cloth takes about 0.13 ms to step and draw, under 1% of a frame at 60 Hz.

  1. Seed and inputsConnected to the next step by: run
  2. Simulationfixed 60 HzConnected to the next step by: every time
  3. Same framesbyte for byte
Same seed and inputs, same frames.
Determinism

Same seed, same frames

The simulation steps at a fixed 60 Hz with no variable time step, and its core uses its own sine, cosine and arctangent instead of the platform’s math library, so it can’t drift between machines. The same seed and inputs give byte-identical frames.

That makes a recorded input trace replay a session exactly, and a packed session can be replayed on another machine, which names the first step where the two disagree. Hundreds of tests cover the modules, and determinism is tested directly: a replay run without a window or GPU must reproduce the saved frame hashes.

Intro cards

Wipes the old hardware could do

Title cards come with eight wipes: cut, fade, blinds, iris, curtain, dissolve, an interlaced scan, and collapse, which shrinks the picture the way a tube did when the power went off. Each is something the hardware Phosphor imitates could actually do. The fade walks every pixel down its own ramp.

LOOP 0:03
Iris in, collapse out.
LOOP 0:03
Blinds in, curtain out.
LOOP 0:03
Dissolve in, fade out.
LOOP 0:07
A hold on a palette cycle: a run of indices rotates, so a band of light rolls through the art with no redraw.
A phone-shaped tuning screen in the engine’s pixel font, listing pinned dials and five groups
The tuning screen on a phone-sized grid, 220 by 478 at six times.
Tuning

Tuning on the phone

Every value a game wants to tune is registered once, and the tuning screen is generated from that list. It runs in a browser through a debug server, or on the phone itself, where it stays usable with hundreds of dials.

Phosphor’s other modules include a tracker-style synthesizer, a 5 by 7 bitmap font with an immediate-mode interface, and tile grids with shadowcast line of sight and A* and flow-field pathfinding. Each module is imported on its own, so a game that doesn’t need physics never links a solver.

A tile maze lit from one cell, with light stopping at walls
Shadowcast line of sight from one cell, radius 30. The falloff of the light is the scene’s own.
Sound

Playing the same song on period hardware

Phosphor’s synthesizer can play a game’s songs and effects on the voices of a period machine, which the engine calls an era. The music, instruments and effects stay the same, and the era changes how they are heard. The next release has seven: the NES, the PC speaker, the Tandy 1000 and the SNES play the music on the machine’s own sound hardware, and the Mac Plus, the Mac II and the Sound Blaster Pro play it through the machine’s digital audio output.

“Shadow Protocol”, a song made for this page, rendered offline by Phosphor in each mode at matched loudness. Switching keeps your place in the song, and the spectrogram works as a seek bar. The chip modes play the song’s MIDI with an arrangement and instruments written for this demo; the Mac and Sound Blaster modes play the recording itself.
Two rows of charts in teal on black. Top: a spectrogram of a mixed recording, with energy spread across the whole band and bright vertical stripes at each drum hit, and a smooth, rounded waveform. Bottom: the same passage on the NES era, with evenly spaced horizontal lines, wavy lines that move in small steps, fainter drum stripes, and a jagged, stepped waveform.
The same six seconds of a track from Wreckwing. Top, the original recording. Bottom, the track’s MIDI played by the NES era. Each row has a spectrogram from 0 to 16 kHz and 200 milliseconds of waveform.

The figure shows a track from Wreckwing. The recording spreads its energy across the whole band, and each bright vertical stripe is a drum hit. The NES era plays the track on a model of the console’s sound chip, the 2A03: two pulse channels, a triangle and a noise channel, with the console’s output filters on the mix. A part is heard only while it holds a channel of its kind, pitch snaps to the nearest value the chip could play, and volume to its 16 levels.

The evenly spaced horizontal lines are the harmonics of the pulse and triangle waves, and the vertical stripes are drums on the noise channel. The wavy lines are the lead’s vibrato, which moves in steps because the engine writes the chip’s registers once per video frame, 60.1 times a second, as NES sound drivers did. A sound effect takes a channel from the music until it ends, and changing era mid-song fades one machine out and the next in without a click.

Status

Made for our own games

Phosphor is private. We built it for our own games and don’t offer it to other developers. We pulled it out of an earlier physics game of ours, and like the rest of our work it is built almost entirely with Claude Code. One Thousand Ruins, for iPhone and iPad, is the first game built on it, and Bit Pixel Horde is likely to be next.

It is at version 0.9.3, from October 21, 2026, and its interfaces still change between versions. So far it has run on macOS, the iOS Simulator and one iPhone. Version 0.9.2 added Game Center and a one-time unlock through in-app purchase, which still have to be checked on a device against the App Store. Haptics are still to come.