1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
|
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Recreating the Matrix rain with ANSI escape sequences</title>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Recreating the Matrix rain with ANSI escape sequences</title>
<link rel="stylesheet" href="/assets/css/main.css">
<link rel="stylesheet" href="/assets/css/skeleton.css">
</head>
</head>
<body>
<div id="nav-container" class="container">
<ul id="navlist" class="left">
<li >
<a href="/" class="link-decor-none">hme</a>
</li>
<li class="active">
<a href="/log/" class="link-decor-none">log</a>
</li>
<li >
<a href="/projects/" class="link-decor-none">poc</a>
</li>
<li >
<a href="/about/" class="link-decor-none">abt</a>
</li>
<li><a href="/feed.xml" class="link-decor-none">rss</a></li>
</ul>
</div>
<main>
<div class="container">
<div class="container-2">
<h2 class="center" id="title">RECREATING THE MATRIX RAIN WITH ANSI ESCAPE SEQUENCES</h2>
<h6 class="center">21 DECEMBER 2025</h5>
<br>
<div class="twocol justify"><p>My 2022 implementation of the Matrix rain had too many loose ends. Unicode
support was inflexible: the charset had to be a single contiguous block with no
way to mix ASCII with something like Katakana; Phosphor decay level was stored
in a dedicated array–still don’t understand why I did that when I had already
used bit-packing for the RGB channels; The algorithm was difficult to decipher.
The 2022 version worked, but that’s not the same thing as being correct.</p>
<p>I began by placing the decay factor in the LSB of the 4-byte RGB value. Let’s
call that RGB-PD. PD plays a somewhat analogous role to an alpha channel; I
avoided labelling it A so as not to cause confusion:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>enum {
R, /* Red */
G, /* Green */
B, /* Blue */
PD /* Phosphor decay level */
};
typedef union color_tag {
uint32_t value;
unsigned char color[4];
} color;
</code></pre></div></div>
<p>The decision to use union over more portable bit twiddling was made three years
ago, as I recall, for readability. Seeing as all my systems are little-endian,
this is unlikely to cause me trouble. Besides, if union is never to be used,
why is it in the language anyway?</p>
<p>The blend() function, which emulates the dim afterglow of Phosphor by eroding
the RGB channels towards the background, remains as elegant as it did three
years ago:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#define DECAY_MPLIER 2
static inline void blend(matrix *mat,
size_t row, size_t col)
{
unsigned char *color;
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;
}
</code></pre></div></div>
<p>While the memory inefficiency of Phosphor decay tracking was a technical
oversight I hadn’t noticed, the limitation around mixing nonadjacent Unicode
blocks was a nagging concern even three years ago. So, a fix was long overdue.</p>
<p>In the new version, I introduced a glyphs array that enables a user to add as
many Unicode blocks as they want. The insert_code() function picks a block
from the array at random, and then picks a character from that block at random:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#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;
}
</code></pre></div></div>
<p>The Unicode blocks are stored in 8-byte containers: the low four bytes form the
first codepoint and the high four bytes the last. Here, I chose bitwise
operations over unions because, first and foremost, the operations themselves
are trivial and idiomatic, and the UNICODE() macro simplifies the management of
charsets. The insert_code() function is now ready to take its rightful place
next to blend().</p>
<p>The result is a digital rain that captures the original Matrix aesthetic with
high visual fidelity:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cc -O3 main.c -o matrix
$ ./matrix
</code></pre></div></div>
<video style="max-width:100%;" controls="" poster="poster.png">
<source src="matrix.mp4" type="video/mp4" />
</video>
<p>There was no cause to measure the program’s performance characteristics
precisely; it’s gentle on the CPU. On my ThinkPad T490 running OpenBSD, which
has a resolution of 1920x1080, it uses about 2-3% of the CPU, with occasional
jumps of up to about 8%; the cores remain silent, the fans don’t whir, the rain
falls in quiet.</p>
<p>Files: <a href="source.tar.gz">source.tar.gz</a></p>
</div>
<p class="post-author right">by W. D. Sadeep Madurange</p>
</div>
</div>
</main>
<div class="footer">
<div class="container">
<div class="twelve columns right container-2">
<p id="footer-text">© ASCIIMX - 2025</p>
</div>
</div>
</div>
</body>
</html>
|