SIX BYTES
September 20, 1984. David Braben[1] and Ian Bell[2] — both undergraduates at Jesus College, Cambridge, ages 20 and 21 — shipped Elite[3] on the BBC Micro.[4]




The BBC Micro had 32 KB of total RAM, shared with video memory. Elite generated 2048 star systems — eight galaxies, 256 systems each. None of it was stored. Every system name, economy type, government, technology level, and star distance was computed on the fly from a seed: three 16-bit numbers, six bytes. A Tribonacci[5][6] recurrence twisted them forward: each step produced the next system; 256 steps produced the next galaxy. Infinite variety from almost nothing.
The game shipped with a 64-page novella — The Dark Wheel by Robert Holdstock[7] — set in that same procedurally-generated[8] universe.

That pattern has a mathematical name. The Mandelbrot set[9] generates infinite visual complexity from a two-line iteration. Every coastline of it, every spiral arm, every miniature copy of the whole set buried in its boundary — all of it falls out of the same formula, applied to every pixel.
Your version has to render all of it. Not a screenshot, not a pre-computed image — a live computation. For every pixel on screen, evaluate the formula. Map the result to a colour. Write it to a buffer. Upload the buffer to the GPU and display it.
The GPU
That last sentence introduces something new. The GPU.[10] The inner loop of a fractal renderer — compute a colour for every pixel — is structurally identical to what GPUs do for real-time 3D graphics. Game developers in the mid-1990s needed to shade millions of pixels per frame, faster than any CPU could manage. The answer was dedicated silicon with thousands of small parallel processors, each running the same per-pixel computation independently.
3dfx Interactive[11] shipped the Voodoo in 1996. NVIDIA named its GeForce 256 "the world's first GPU" in 1999. NVIDIA acquired 3dfx's intellectual property in December 2000; the company filed for bankruptcy in October 2002. Programmable per-pixel shaders became the baseline that year.
You are about to write the CPU version of the algorithm that hardware was designed to run in parallel. Zoom deep into the set. Feel it slow. That latency is the argument that built the GPU. The r-tier will return to this — when the renderer moves to hardware-accelerated targets, the pixel loop you write here is the one the GPU was designed to accelerate.
The implementation pages build the renderer one layer at a time: the SDL2 event loop and pixel buffer, the Mandelbrot escape-time function, viewport mathematics, colour schemes, and finally the Julia set and the Burning Ship. Start at Setup.
The Iteration
Before I touched an editor, I drew one thing on paper: the formula.
That is the Mandelbrot iteration. z and c are complex numbers —
pairs of real and imaginary components. z starts at zero. c is the
point you are testing. You apply the formula repeatedly. If
exceeds 2 and keeps growing, the point escapes — it is not in the
set. If it never escapes, it is in the set.
The escape-time algorithm counts how many iterations before .
Points that never escape get a count of MAX_ITER. That count becomes
the pixel's colour.
Two doubles. One loop. One comparison. That is the entire engine.
The Pixel Buffer
SDL2's pixel buffer model is the same model every rasteriser uses:
a flat array of uint32_t, one entry per pixel, packed as 0xAARRGGBB.
The screen is 800 × 600 pixels, so the array has 480,000 entries.
uint32_t pixels[WIDTH * HEIGHT];To address pixel (x, y):
pixels[y * WIDTH + x] = colour;After filling the array, you upload it to a streaming texture and present it. SDL2 handles the rest. The pixel buffer is a camera: you decide what colour each pixel is; the GPU puts it on screen.
The choice of 800 × 600 is deliberate. Large enough to see detail;
small enough for a CPU to compute in a few seconds without threading.
Zoom deep and feel the render slow. Scroll zoom via SDL_MOUSEWHEEL
would be a one-line addition to the event loop, but adding it means
explaining mouse state across frames, accumulating scroll deltas,
and distinguishing a click from a drag. That belongs in a game chapter.
Here, the keyboard navigates the view. The mouse picks one point.
The Three Fractals
The chapter builds three fractals in sequence. Each one changes exactly one thing in the iteration, and the visual result is completely different.
Mandelbrot. The base case. c is the pixel; z starts at zero.
Julia. The same iteration; the roles swap. c is fixed — set by
a mouse click in the Mandelbrot view. z is the pixel.
Every point in the Mandelbrot plane parameterises a Julia set. Click
inside the Mandelbrot set: the Julia set for that c is connected —
one solid shape. Click outside: the Julia set is disconnected dust.
The Mandelbrot set is the map of all Julia sets.
Burning Ship.[12] One additional operation before squaring: take the absolute value of both components.
The result looks nothing like the Mandelbrot set. The lesson: the
formula IS the visual. One fabs() call changes the geometry
entirely.
Colour
The first colour scheme is a linear gradient: divide the iteration
count by MAX_ITER, map that ratio to an HSV hue, convert to RGB.
Simple and fast. Build it, run it — and zoom in. You will see bands.
Sharp colour transitions where the iteration count jumps by one. The
visual continuity is broken.
The escape-time count is an integer. The banding is not an artefact of rendering; it is built into the algorithm. Smooth colouring fixes it by computing a fractional escape value:
is the final magnitude when the point escaped; t is now a
real number, not an integer. The bands disappear. The colour
transitions smoothly across the iteration boundary.
The math uses two logarithms and requires tracking the final value out of the escape loop — a small change to the implementation with a large visual effect.
The Project
Build the complete fractal renderer. Source is split across six files:
| File | Contents |
|---|---|
main.c | argument check, init SDL2, event loop, free |
mandelbrot.c | mandelbrot(), burning_ship() |
julia.c | julia() |
view.c | view_t struct, pixel_to_re(), pixel_to_im() |
colour.c | colour_linear(), colour_smooth() |
render.c | SDL2 setup, texture upload, render_frame() |
The program takes no arguments:
./infiniteControls:
| Key | Action |
|---|---|
| Arrow keys | Pan |
= / - | Zoom in / out |
M | Mandelbrot mode |
J | Julia mode (uses last clicked c) |
B | Burning Ship mode |
C | Cycle colour scheme |
| Mouse click | Set Julia c parameter (Mandelbrot mode) |
Esc | Quit |
Rules:
- The platform rule applies:
mandelbrot.c,julia.c,view.c, andcolour.cmust not include SDL2 headers. SDL2 lives only inrender.candmain.c. This is what makes the automated tests possible — and what makes the code portable to other platforms. MAX_ITERis defined as a#defineconstant (100 is a good starting value).WIDTHandHEIGHTare#defineconstants (800 and 600). Adjust if your machine renders slowly.
The Tester
The companion repo contains test.sh. Clone it, copy test.sh
into your working directory, and run it:
git clone https://github.com/thecodingidiot-com/c04-the-infinite.git
cp c04-the-infinite/test/test.sh ~/c04-practice/
cp c04-the-infinite/test/test_mandelbrot.c ~/c04-practice/
cp c04-the-infinite/test/test_view.c ~/c04-practice/
bash test.shThe tester compiles mandelbrot.c and view.c separately — no SDL2
required. Two suites run:
Suite 1 — escape time. Known (re, im) inputs with precomputed
expected iteration counts. Verifies that the Mandelbrot, Julia, and
Burning Ship iterations are correct.
Suite 2 — viewport mapping. Given a known view_t (centre and
scale), verifies that pixel_to_re and pixel_to_im return the
expected complex coordinates for specific pixels, and that zoom steps
scale the view correctly.
If the tester fails with a compile error mentioning SDL2, the platform separation rule has been violated — check your includes.
The Companion Repo
The reference solution is at
github.com/thecodingidiot-com/c04-the-infinite.
The solution/ directory contains all six source files, a Makefile,
and the test/ directory with the two test suites and test.sh.