summaryrefslogtreecommitdiffstats
path: root/_log/matrix-digital-rain.md
diff options
context:
space:
mode:
authorSadeep Madurange <sadeep@asciimx.com>2025-12-22 23:38:06 +0800
committerSadeep Madurange <sadeep@asciimx.com>2025-12-22 23:38:06 +0800
commit08e594268ed20c5c2355a249ac691c007e38aed9 (patch)
tree2153e6319eb0bee4f062565858185088f2a29d82 /_log/matrix-digital-rain.md
parent8f99ca06fd48386bee511d33d1207f8eaf8bb8f4 (diff)
downloadwww-08e594268ed20c5c2355a249ac691c007e38aed9.tar.gz
Matrix post.
Diffstat (limited to '_log/matrix-digital-rain.md')
-rw-r--r--_log/matrix-digital-rain.md111
1 files changed, 82 insertions, 29 deletions
diff --git a/_log/matrix-digital-rain.md b/_log/matrix-digital-rain.md
index b1c5e6c..2c35361 100644
--- a/_log/matrix-digital-rain.md
+++ b/_log/matrix-digital-rain.md
@@ -1,51 +1,104 @@
---
title: Recreating the Matrix rain with ANSI escape sequences
-date: 2022-08-22
+date: 2025-12-21
layout: post
project: true
thumbnail: thumb_sm.png
---
-Over the weekend, I came across Domsson's <a
-href="https://github.com/domsson/fakesteak" class="external" target="_blank"
-rel="noopener noreferrer">Fakesteak</a>: a beautifully lean rendition of the
-Matrix rain in raw C using ANSI escape sequences—zero dependencies, not even
-ncurses.
-
-To keep things simple, Fakesteak didn't support Japanese characters and that it
-used 8-bit color mode. The latter meant that the ghosting effect has to rely on
-different foreground colors rather than shades of the same color. As a tip of
-the hat to Domsson's impressive work, I decided to add Unicode and 24-bit
-truecolor support to it, aiming to faithfully recreate the original Matrix from
-the first movie during Neo and Cypher's conversation:
+The Matrix digital rain implemented in raw C using ANSI escape sequences with
+zero dependencies—not even ncurses.
<video style="max-width:100%;" controls="" poster="poster.png">
<source src="matrix.mp4" type="video/mp4">
</video>
-Adding Unicode support via `wchar_t` and `wprintf()` was easy enough.
-Implementing the ghosting effect with truecolor support, however, turned out
-harder than expected. To achieve the ghosting effect, I treated phosphor decay
-as a multiplier, which allowed me to emulate the dim afterglow by gradually
-transitioning each raindrop's color towards the background color:
+This is a fork of Domsson's unique rendition of the Matrix rain: <a
+href="https://github.com/domsson/fakesteak" class="external" target="_blank"
+rel="noopener noreferrer">Fakesteak</a>. Three years ago, I forked his project
+and added truecolor and Unicode support. I also drastically modified the
+algorithm to produce a rain that resembled the original aesthetic with high
+visual fidelity.
+
+## Unicode support
+
+Unicode support in the 2022 version lacked flexibility. The charset used in the
+rain had to be a single contiguous block defined by `UNICODE_MIN` and
+`UNICODE_MAX` settings:
+
+```
+#define UNICODE_MIN 0x0021
+#define UNICODE_MAX 0x007E
+
+static inline void insert_code(matrix *mat,
+ size_t row, size_t col)
+{
+ mat->code[index(mat, row, col)] = rand()
+ % (UNICODE_MAX - UNICODE_MIN)
+ + UNICODE_MIN;
+}
+```
+
+There was no way, for instance, to use both ASCII and Katakana at the same
+time. The user had to pick one. In the new version, the user can use any number
+of Unicode blocks using `glyphs` array. In fact, the default rain now includes
+both ASCII and half-width Katakana characters:
+
+```
+#define UNICODE(min, max) (((uint64_t)max << 32) | min)
+
+static uint64_t glyphs[] = {
+ UNICODE(0x0021, 0x007E), /* ASCII */
+ UNICODE(0xFF65, 0xFF9F), /* Half-width Katakana */
+};
+
+static uint8_t glyphlen = (sizeof glyphs) / (sizeof glyphs[0]);
+
+static inline void insert_code(matrix *mat,
+ size_t row, size_t col)
+{
+ uint64_t block;
+ uint32_t unicode_min, unicode_max;
+
+ block = glyphs[(rand() % glyphlen)];
+ unicode_min = (uint32_t)block;
+ unicode_max = (uint32_t)(block >> 32);
+
+ mat->code[index(mat, row, col)] = rand()
+ % (unicode_max - unicode_min)
+ + unicode_min;
+}
+```
+
+Entries in the `glyphs` array are Unicode blocks bit-packed in an 8-byte
+container: the four low bytes forms the first codepoint and the four high bytes
+the last.
+
+## Phosphor decay
+
+The dim afterglow of monochrome CRT displays is achieved by carefully scaling
+the RGB channels individually and mixing them:
```
-static void mat_shade(matrix *mat, size_t row, size_t col)
+#define DECAY_MPLIER 2
+
+static inline void blend(matrix *mat,
+ size_t row, size_t col)
{
unsigned char *color;
- color = mat->rgb[mat_idx(mat, row, col)].color;
- color[R] = color[R] - (color[R] - COLOR_BG_RED) / 2;
- color[G] = color[G] - (color[G] - COLOR_BG_GRN) / 2;
- color[B] = color[B] - (color[B] - COLOR_BG_BLU) / 2;
+
+ color = mat->rgb[index(mat, row, col)].color;
+ color[R] = color[R] - (color[R] - RGB_BG_RED) / DECAY_MPLIER;
+ color[G] = color[G] - (color[G] - RGB_BG_GRN) / DECAY_MPLIER;
+ color[B] = color[B] - (color[B] - RGB_BG_BLU) / DECAY_MPLIER;
}
```
-Looking back at the implementation, there are still a few improvements to be
-made. Instead of using a dedicated buffer, I should have bit-packed the
-phosphor decay into the RGB data buffer to save memory. I'm not entirely
-satisfied with the Unicode support as it's restricted to contiguous code
-points. The glitch effect, which I implemented with characters unexpectedly
-changing, would have been closer to the original if flashed white as well.
+The blending function emulates the phosphor decay by gradually transitioning
+each raindrop's color towards the background color. The multiplier is the
+number of passes over the rain track needed before the afterglow disappears.
+
+## The algorithm
Nonetheless, the rain resembles the original with high visual fidelity. It's
highly customizable and gentle on the CPU. On my 14" ThinkPad T490, which has a