The night the fractal generator froze during the NUS live demo

It takes exactly 0.3 seconds for a projector screen to go black. The 0.3 seconds you experience in front of an audience feels like roughly half a lifetime.

On a humid Singapore evening, an algorithmic art meetup was held in a seminar room in the NUS School of Computing building. There were about twenty attendees. I was the last of three presenters that day, and I planned to run a fractal generator I'd built myself as a live demo. I connected my laptop via HDMI, opened a terminal, and ran the compiled binary. The idea was to show one of those moments where you gradually increase the zoom level and the swirling structures at the edges of the Mandelbrot set reveal themselves in real time.

The first few seconds were flawless. The colour palette transitioned smoothly, and as the iteration count climbed, the detail along the boundary came alive. A few people in the audience leaned forward. I felt a quiet surge of pride. All those weeknight hours wrestling with C++ and OpenGL after work were finally paying off.

And then, around zoom level 14x, the screen froze.

The Terror of Silence

Moving the mouse, pressing keys, nothing responded. The GPU shader had clearly fallen into an infinite loop. To be precise, it was a convergence-check error that occurs when you push the zoom near the limits of double-precision floating point. During testing at home, I'd only zoomed in to 12x. The complacent assumption that "12x is deep enough, isn't it?" had betrayed me.

The hum of the seminar room's air conditioning suddenly seemed deafening. Someone in the front row asked, "Did it crash?" and I answered, "Yes, it looks like it." I surprised myself with how calm my voice came out. Inside, it felt like the floor was caving in.

Laptop screen showing a crashed program and terminal during a live coding session

I force-killed the process and reopened the terminal. I hardcoded the zoom cap down to 10x, recompiled, and ran it again. Calling it a live hotfix would be generous. It was closer to a retreat. I finished the demo within the limited zoom range. There was applause, but I knew full well it was out of politeness.

On the Bus Home

Walking toward Kent Ridge station, my pride stung. It was a qualitatively different humiliation from getting a time-limit exceeded (TLE) in a competitive programming contest. In a contest, only the judge server knows you failed. In a live demo, people watch it happen right in front of you.

I got on the bus and opened the notes app on my phone. I started writing down the causes.

  • There was no precision-switching logic based on zoom depth. When you exceed the limits of double precision, it should fall back to arbitrary-precision arithmetic, but I'd put that off with a "I'll get to it later."
  • There was no timeout guard inside the shader. I'd set an upper bound on the iteration count for non-converging points, but once a floating-point error occurs, that upper-bound check becomes meaningless.
  • There was zero UI for communicating error states to the user. When the screen froze, there was no way to tell whether it had crashed or was still rendering.

All three were the result of my obsession with the visual output while neglecting defensive design.

What I Fixed and What I Couldn't

Over the next two days, I poured every post-overtime hour into adding fallback logic. I linked the GNU MPFR library so that when the zoom depth exceeds a threshold, it automatically switches to arbitrary-precision mode. I added a per-frame maximum computation time limit to the shader, and if it's exceeded, an overlay reading "Precision limit reached" appears.

Honestly, almost nothing changed visually. At zoom levels of 12x and below, the output looks identical to before. The difference lies where you can't see it. Instead of freezing when it hits its limits, the program now tells the user what's happening. The spectacle remains the same, but now it doesn't collapse.

There's a parallel to accounting. A ledger that balances perfectly is taken for granted, but the moment a single number is off, all trust crumbles. Nobody ever says, "What a stable ledger." Stability is the baseline. Code works the same way.

Deeply zoomed Mandelbrot set fractal with vibrant color gradients and intricate boundary detail

Since that meetup, I've made it a habit to write failure scenarios first before adding features to side projects. If I want to add a new shader effect, I first define the input ranges where it could break, then design the screen the user will see when it does break. It's just a change in order, but the reliability of the end product is completely different.

The silence in that Kent Ridge seminar room still comes back to me sometimes. I never want to go through it again, but if not for those 0.3 seconds, I'd probably still be proudly showing off a fractal generator that "looks pretty but dies at 13x zoom." So here's a question worth sitting with: what's the thing in your own side project you've been putting off with "I'll get to it later"?

Comments