mediocrity.media

There Is No Loop

@mdcrty/digital-rain, and a complaint I'd been carrying since I was a kid.

published 7 min read

When The Matrix came out I didn't want the poster. I wanted the code - running on my desktop, endlessly, as a screensaver.

So I went looking, and what I found was disappointing in a way I couldn't articulate at the time. Most of the Matrix screensavers around then were looped animations. They looked right for a minute or two, and then you'd catch it: the same column of characters falling in the same order it had ten seconds ago. Once you spotted the loop you couldn't unspot it. The thing stopped being a window and became a GIF.

I didn't have the skills to build a better one, so I put it down. For about twenty-five years, as it turns out.

Coming back to it as an adult, with a career's worth of JavaScript behind me and Canvas already in hand from building Paint, the appeal wasn't nostalgia exactly. It was that I finally understood what had annoyed me. The problem with those screensavers was never the animation. It was that they had no state - nothing that could surprise them, and therefore nothing that could surprise you.

Accuracy first, feeling second

I didn't want a Matrix-inspired effect. There are plenty of those. I wanted the effect, which meant starting with research rather than with code.

There's remarkably little written about how the sequence was actually produced. What I could establish is that the character set mixed Roman letters and numerals with Japanese glyphs, and that the Japanese characters were altered - squashed and reworked to fit the visual language of the effect rather than used as they'd normally appear.

That last part became a limit I had to accept. Generating modified glyphs was beyond what I wanted to take on, so instead of approximating them badly I filtered the set down to the characters that already read correctly at small sizes in a monospace face. It's a compromise, and it's the kind I'd rather make than fudge. The green (#0aff0a) I did not compromise on.

The reason I care this much about accuracy isn't purism. It's that in my experience, feeling follows accuracy and almost never arrives without it. You can't design your way to the sensation of something. You get the details right, and the sensation shows up on its own - usually before you've finished.

The hardest part is invisible

Digital rain doesn't clear the canvas between frames. It paints a nearly-transparent black rectangle over the previous frame - alpha of 0.02, by default - so old characters decay gradually instead of vanishing. That single trick is the entire trailing fade, and it's also what makes the effect so unforgiving to build.

Because the overlay compounds, every mistake compounds with it. Each character occupies a cell that has to be blanked and redrawn precisely, and cell position, cell size and the fade region all have to agree exactly. If a rectangle is misaligned by a pixel, that pixel doesn't get repainted - and it doesn't just look wrong for one frame, it accumulates. A faint vertical seam builds up over several seconds where nothing should be.

Now try debugging that. The screen is black. The error is black-adjacent. The obvious move is to temporarily colour the cell rectangles so you can see where they land, except the rectangles are the background - colour them and the entire page becomes that colour, and you've destroyed the thing you were trying to inspect.

Most of the work on this project went into a problem whose successful outcome is that nobody ever notices anything.

The moment

There wasn't a version number where it started working. It happened while I was watching it.

Multiple drops were falling independently down the same column. Characters behind each head were still mutating rather than sitting frozen. The fade was carrying old glyphs down into the dark at the right rate. And at some point I stopped evaluating it and just watched it, which is how I knew.

I wasn't looking at a recreation. I was looking at the thing I remembered.

Killing the loop

Here's where the kid's complaint got answered, and it's the part of the codebase I'd point at if someone asked what the project is actually about.

Nothing in the animation repeats, because nothing is scheduled. Each column restarts on a randomised roll rather than a fixed interval - variabilityOfStart defaults to 0.98, which produces staggered, arrhythmic restarts across the screen. Each column carries more than one drop at a time, so columns overlap themselves. Characters behind each head keep changing for roughly fifteen cells before settling. Random cells flicker independently of any of that.

None of these are impressive individually. Together they mean the screen never enters a state it's been in before. You can watch it for an hour and there is no moment where you catch it repeating, because there's nothing to catch.

The animation also runs at 20 frames per second rather than 60. Smoother would be easy and would be wrong - the film's effect has a slight mechanical stutter to it, and matching that mattered more than matching the frame rate of everything else on the web.

Beyond the rain

Packaging came early. I wanted the effect on two different sites, and shuttling increasingly complicated files between folders had become genuinely annoying. Digital Rain and Bees became the first two packages under @mdcrty, mostly so I could consume my own work like a normal person.

What I didn't expect was how much the package framing loosened me up. Once it wasn't tied to one page, the features I'd been deferring stopped feeling like scope creep.

It isn't a component that sits in a box. Most React components are well-mannered about their boundaries: they render into a div, respect whatever dimensions their parent gives them, and leave the rest of the page alone. Digital Rain has no contained mode at all. It takes the viewport, because a screensaver occupying a 400-pixel panel in the corner of a blog isn't a screensaver, it's a widget. The theme-colour meta tag goes black along with it, so on a phone the browser's own interface disappears into the rain too. The colour picker reaches past its frame for a moment; this one never had one.

There's a console. You press ~ and get a prompt, because a film about people typing into terminals should ship with a terminal. Every animation parameter is settable live: fade alpha, frame rate, character mutation rate, column overlap, all of it. Type ~ % set alpha 0.2 and the rain changes underneath you. Asking it for help returns a refusal; asking twice gets you the actual command list.

There are hidden messages, including a SYSTEM FAILURE screen borrowed from the end of the film.

And the thing I'm most pleased with: a QR code that materialises out of the rain. Any string can be encoded as a mask over the animation - cells inside the code stay bright, cells outside get progressively suppressed until the shape resolves. It arrives on a delay and sweeps in with the falling rain rather than flashing into existence, so it reads as something surfacing from the code rather than an overlay dropped on top of it. It feels like exactly the sort of thing an operator inside the Matrix would leave lying around. Not obvious, not intrusive, just there for whoever's still watching.

qrOffAlpha turned out to be my favourite piece of accidental maths in the whole package. Because the dimming is applied every frame, its effect is geometric rather than linear - 0.05 gives you a gentle shadow the rain still shows through, and 0.5 is functionally solid black within a fraction of a second. Almost all of the useful range lives between 0.01 and 0.05. It's a slider where 96% of the values do the same thing, which is either a design flaw or an accurate reflection of how compounding works, depending on how charitable you're feeling.

Tribute

I want to be clear about what this is. It's a tribute, and not a small one.

I could not have invented this. Nobody had done anything like it before that film, which is precisely why it landed the way it did - a visual idea with no precedent, arriving fully formed. All I've done is take it apart carefully enough to put it back together.

What I hope someone gets from it is a bit of nostalgia and a bit of delight, and maybe enough curiosity to open the console and start breaking things. Or nothing that active at all. Leave it running in a tab and glance at it occasionally. Mental floss.

That's all I ever wanted from it in the first place.

See it in action

The one thing I couldn't research

There is very little reliable information about how the original effect was actually produced. I found enough to work from and no more.

If you know anything concrete - the real character set, what it was built in, how the glyphs were reworked, anything beyond the handful of interviews that circulate - I would genuinely like to hear it. It's the only part of this project where accuracy stopped being a matter of effort and started being a matter of access.

Side projects have taught me more about engineering than any course or certification I've taken.