Add the Burning Ship fractal, run the tester, and explore the renderer. This page completes the chapter with one final demonstration: a single-line change to the iteration formula produces a completely different geometry.
The Burning Ship
The Burning Ship fractal[1] modifies the Mandelbrot iteration with one
operation added before each squaring: take the absolute value of both
components of z.
What absolute value does to the iteration. The Mandelbrot iteration
treats the complex plane symmetrically above and below the real axis:
(re, im) and (re, −im) produce conjugate orbits, which is why the
Mandelbrot set is perfectly symmetric top-to-bottom. Forcing and
before each squaring step folds both half-planes to the positive
quadrant. Every orbit is reflected into the upper-right quarter before
each step. This fold breaks the conjugate symmetry — the Burning Ship
is not symmetric — and changes which orbits escape and which stay
bounded. The asymmetric ship silhouette visible at the bottom of the
set is a direct consequence of how that fold interacts with the
squaring.
Add burning_ship to mandelbrot.c. The mandelbrot.h declaration
was already updated on the previous page.
int burning_ship(double c_re, double c_im, int max_iter, double *mag_out)
{
double re;
double im;
double new_re;
double sq;
int i;
re = 0.0; /* z starts at zero, same as Mandelbrot */
im = 0.0;
i = 0;
while (i < max_iter) {
sq = re * re + im * im;
if (sq > 4.0) { /* |z|² > 4: point has escaped */
if (mag_out)
*mag_out = sq; /* squared magnitude for smooth colouring */
return (i);
}
if (re < 0.0)
re = -re; /* |Re(z)| — fold negative real to positive */
if (im < 0.0)
im = -im; /* |Im(z)| — fold negative imaginary to positive */
new_re = re * re - im * im + c_re; /* real part of (|z|)² + c */
im = 2.0 * re * im + c_im; /* imaginary part — uses folded re */
re = new_re;
i++;
}
if (mag_out)
*mag_out = re * re + im * im;
return (max_iter); /* never escaped: point is in the set */
}The fold step. The two if blocks appear after the escape check
and before the squaring — between these two positions the fold has its
intended effect. They use conditional negation (if (x < 0) x = -x)
rather than fabs() for a deliberate reason: fabs() requires
<math.h>, and the platform separation rule keeps mandelbrot.c free
of math-library includes. Conditional negation is exactly what fabs()
does for a single double value. After the fold, the squaring and
addition proceed identically to mandelbrot. Everything else —
initialisation, escape condition, mag_out interface, return value —
is unchanged.
burning_ship was declared in mandelbrot.h from the
Set
page and is already wired into the else branch of the render_frame
dispatch from the previous page.
make re && ./infinitePress B for Burning Ship mode. The set looks nothing like Mandelbrot
— an asymmetric, spiky structure with a region at the bottom that
genuinely resembles a burning ship or city skyline. One fold, one extra
pair of conditionals, completely different geometry. The formula IS the
visual.
Zoom into the ship region at the bottom (roughly centre (−0.5, −0.5),
zoomed to around 0.3× the default scale). The detail is as rich as the
Mandelbrot set — spirals and miniature copies are present, but their
shape is distorted by the fold.
Run the tester
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/
cd ~/c04-practice
bash test.shThe tester compiles mandelbrot.c and view.c directly into a test
binary — no SDL2, no renderer, no window. This is only possible because
those files have no SDL2 includes: the platform separation rule, which
kept them free of SDL2 throughout the chapter, is what makes automated
testing possible. The same code that runs in the renderer compiles into
a standalone binary.
Suite 1 — escape time. Known (re, im) inputs with precomputed
expected iteration counts for mandelbrot(), julia(), and
burning_ship(). The tests use the strict escape condition sq > 4.0
(strictly greater than, not greater-than-or-equal). If you wrote
>= 4.0, some counts will be off by one — the output identifies which
test failed and what value was expected.
Suite 2 — viewport mapping. Given a view_t with known centre and
scale, verifies that pixel_to_re(WIDTH/2, &v) returns center_re
and pixel_to_im(HEIGHT/2, &v) returns center_im. Also verifies
that one view_zoom(&v, 0.9) step scales the view correctly.
If the tester fails with a compile error naming an SDL2 header, the
platform separation rule has been violated. Check the #include list
in mandelbrot.c, julia.c, view.c, and colour.c — none should
include SDL2/SDL.h or any SDL2 header.
What the three fractals taught
The chapter built three fractals in sequence.
Mandelbrot. The base case. c = pixel, . Generates the
canonical shape: cardioid, bulbs, infinite boundary.
Julia. Same formula. c fixed by mouse click, = pixel. Every
point in the Mandelbrot plane produces a different Julia set. Points
inside the Mandelbrot set produce connected Julia sets; points outside
produce dust. The Mandelbrot set is the map of all Julia sets.
Burning Ship. Same loop. |re| and |im| taken before each
squaring. Completely different geometry. The proof that the formula IS
the visual.
Each step changed exactly one thing in the iteration. Each step produced a completely different image. The lesson is not specific to fractals — it is about what a mathematical formula contains. The structure visible on screen is not drawn. It falls out of arithmetic.
What comes next
The renderer is complete. It renders three fractals in colour, zooms and pans interactively, and responds to mouse clicks. It runs on one CPU core and slows measurably at deep zoom.
That slowdown is load-bearing. Every pixel is independent: computing
the colour of pixel (100, 200) has no dependency on pixel (101, 200). That independence is called embarrassingly parallel[2] — the
ideal workload for a GPU, which runs thousands of small threads
simultaneously. The GPU was built to run exactly this computation, not
just for fractals but for every pixel in every triangle in every frame
of every real-time 3D game.
The r-tier returns to this. When the renderer moves to hardware-accelerated targets, the pixel loop written here is the one the GPU was designed to run in parallel. What you experienced as latency becomes measured in microseconds.
Before that, the curriculum builds more of the systems stack: c05 teaches processes, c06 tackles sorting, and c07 builds a shell that brings it together. The graphical side returns in g01b — the quiz game from g01a rebuilt with SDL2 graphics using the event loop and pixel buffer you just wrote.