summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--_log/matrix-digital-rain.md146
-rw-r--r--_site/feed.xml2
-rw-r--r--_site/index.html2
-rw-r--r--_site/log/index.html2
-rw-r--r--_site/log/matrix-digital-rain/index.html151
-rw-r--r--_site/posts.xml2
-rw-r--r--_site/projects/index.html4
7 files changed, 264 insertions, 45 deletions
diff --git a/_log/matrix-digital-rain.md b/_log/matrix-digital-rain.md
index 4958eed..b691e84 100644
--- a/_log/matrix-digital-rain.md
+++ b/_log/matrix-digital-rain.md
@@ -1,34 +1,146 @@
---
-title: 'Matrix Rain: 2025 refactor'
+title: Recreating the Matrix rain with ANSI escape sequences
date: 2025-12-21
layout: post
project: true
thumbnail: thumb_sm.png
---
-Unicode support added. ASCII + Katakana working:
+My 2022 implementation of the Matrix rain had too many loose ends. Unicode
+support was inflexible: the character set 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 correct.
-<video style="max-width:100%;" controls="" poster="poster.png">
- <source src="matrix.mp4" type="video/mp4">
-</video>
+I began by placing the decay factor in the MSB of the 4-byte RGB value. The PD
+value plays a somewhat analogous role to an alpha channel in that both
+influence transparency. However, they work very differently. So, I avoided
+labelling it A so as not to cause confusion:
+
+```
+enum {
+ R, /* Red */
+ G, /* Green */
+ B, /* Blue */
+ PD /* Phosphor decay level */
+};
+
+typedef union color_tag {
+ uint32_t value;
+ unsigned char color[4];
+} color;
+```
+
+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 any trouble. Besides, if union is never to be used,
+why is it in the language anyway?
+
+The blend() function, which emulates the dim afterglow of Phosphor by eroding
+the RGB channels towards the background, with minor refactoring, remains as
+elegant as it did three years ago:
+
+```
+#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;
+}
+```
+
+While the memory inefficiency of Phosphor decay 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.
-Algorithm notes: mat.col[] = shuffled column indices, mat.row[] = last row per
-column. shuffle() sets working set, main loop draws columns via index i (line
-333), swap() rotates set.
+In the new version, I introduced an array that enables a user to add as
+many Unicode blocks as they want. The insert_code() function picks a block
+from it at random, and then picks a character from that block at random:
-Phosphor decay moved to LSB of RGB union. Should have done this originally.
+```
+#define UNICODE(min, max) (((uint64_t)max << 32) | min)
-RGB/PD union stays. Little-endian machine, portability not a concern.
+static uint64_t glyphs[] = {
+ UNICODE(0x0021, 0x007E), /* ASCII */
+ UNICODE(0xFF65, 0xFF9F), /* Half-width Katakana */
+};
-Charset via UNICODE(min, max) macro - packs range into uint64, insert_code()
-unpacks and selects random char.
+static uint8_t glyphlen = (sizeof glyphs) / (sizeof glyphs[0]);
-Half-width Katakana (U+FF61-U+FF9F) for column alignment.
+static inline void insert_code(matrix *mat,
+ size_t row, size_t col)
+{
+ uint64_t block;
+ uint32_t unicode_min, unicode_max;
-Removed license, automake files. Build: cc -O3 main.c -o matrix
+ 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;
+}
+```
+
+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 init_term() function is the arbiter of this zero-dependency software. It
+prepares the graphical environment so that I can interact with it via ANSI
+escape codes instead of unnecessary layers of abstraction:
+
+```
+static inline int init_term(const struct winsize *ws)
+{
+ struct termios ta;
+
+ if (tcgetattr(STDIN_FILENO, &ta) == 0) {
+ ta.c_lflag &= ~ECHO;
+ if (tcsetattr(STDIN_FILENO, TCSANOW, &ta) == 0) {
+ wprintf(L"\x1b[48;2;%d;%d;%dm",
+ RGB_BG_RED, RGB_BG_GRN, RGB_BG_BLU);
+ wprintf(L"%s", ANSI_FONT_BOLD);
+ wprintf(L"%s", ANSI_CRSR_HIDE);
+ wprintf(L"%s", ANSI_CRSR_RESET);
+ wprintf(L"%s", ANSI_SCRN_CLEAR);
+ setvbuf(stdout, 0, _IOFBF, 0);
+ ioctl(STDOUT_FILENO, TIOCGWINSZ, ws);
+ return 1;
+ }
+ }
+ return 0;
+}
+```
+
+insert_code() seeds the Matrix, blend() creates the old monochrome CRT display
+nostalgia, and ANSI control sequences paint the screen. The result is a digital
+rain that captures the original Matrix aesthetic with high visual fidelity:
+
+```
+$ cc -O3 main.c -o matrix
+$ ./matrix
+```
+
+<video style="max-width:100%;" controls="" poster="poster.png">
+ <source src="matrix.mp4" type="video/mp4">
+</video>
-Performance: 2% CPU, OpenBSD, T490.
+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.
-Commit:
-[03f8d87](https://git.asciimx.com/matrix-digital-rain/commit/?id=03f8d87ba7c2e46bd3f3cc4c772fb3a2ac740c92)
+Files: [source.tar.gz](source.tar.gz)
diff --git a/_site/feed.xml b/_site/feed.xml
index 109f0b4..80e38e0 100644
--- a/_site/feed.xml
+++ b/_site/feed.xml
@@ -1 +1 @@
-<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="/feed.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2025-12-25T17:40:37+08:00</updated><id>/feed.xml</id><title type="html">ASCIIMX | Log</title><author><name>W. D. Sadeep Madurange</name></author><entry><title type="html">Matrix Rain: 2025 refactor</title><link href="/log/matrix-digital-rain/" rel="alternate" type="text/html" title="Matrix Rain: 2025 refactor" /><published>2025-12-21T00:00:00+08:00</published><updated>2025-12-21T00:00:00+08:00</updated><id>/log/matrix-digital-rain</id><author><name>W. D. Sadeep Madurange</name></author><summary type="html"><![CDATA[Unicode support added. ASCII + Katakana working:]]></summary></entry><entry><title type="html">Suckless upgrade workflow</title><link href="/log/suckless-software/" rel="alternate" type="text/html" title="Suckless upgrade workflow" /><published>2025-11-30T00:00:00+08:00</published><updated>2025-11-30T00:00:00+08:00</updated><id>/log/suckless-software</id><author><name>W. D. Sadeep Madurange</name></author><summary type="html"><![CDATA[Workflow for managing suckless patches across upgrades:]]></summary></entry><entry><title type="html">Fingerprint door lock</title><link href="/log/fpm-door-lock/" rel="alternate" type="text/html" title="Fingerprint door lock" /><published>2025-08-18T00:00:00+08:00</published><updated>2025-08-18T00:00:00+08:00</updated><id>/log/fpm-door-lock</id><author><name>W. D. Sadeep Madurange</name></author><summary type="html"><![CDATA[This project features a fingerprint door lock powered by an ATmega328P microcontroller.]]></summary></entry><entry><title type="html">On the use of MOSFETs as electronic switches</title><link href="/log/mosfet-switches/" rel="alternate" type="text/html" title="On the use of MOSFETs as electronic switches" /><published>2025-06-22T00:00:00+08:00</published><updated>2025-06-22T00:00:00+08:00</updated><id>/log/mosfet-switches</id><author><name>W. D. Sadeep Madurange</name></author><summary type="html"><![CDATA[Recently, I needed a low-power circuit for one of my battery-operated projects. Much of the system’s power savings depended on its ability to electronically switch off components, such as servos, that draw high levels of quiescent currents. My search for a solution led me to MOSFETs, transistors capable of controlling circuits operating at voltages far above their own.]]></summary></entry><entry><title type="html">How to configure ATmega328P microcontrollers to run at 3.3V and 5V</title><link href="/log/arduino-uno/" rel="alternate" type="text/html" title="How to configure ATmega328P microcontrollers to run at 3.3V and 5V" /><published>2025-06-10T00:00:00+08:00</published><updated>2025-06-10T00:00:00+08:00</updated><id>/log/arduino-uno</id><author><name>W. D. Sadeep Madurange</name></author><summary type="html"><![CDATA[This is a quick reference for wiring up ATmega328P ICs to run at 5V and 3.3V. While the 5V configuration is common, the 3.3V configuration can be useful in low-power applications and when interfacing with parts that themselves run at 3.3V. In this guide, the 5V setup is configured with a 16MHz crystal oscillator, while the 3.3V configuration makes use of an 8MHz crystal oscillator.]]></summary></entry><entry><title type="html">My first PCB</title><link href="/log/my-first-pcb/" rel="alternate" type="text/html" title="My first PCB" /><published>2025-04-26T00:00:00+08:00</published><updated>2025-04-26T00:00:00+08:00</updated><id>/log/my-first-pcb</id><author><name>W. D. Sadeep Madurange</name></author><summary type="html"><![CDATA[In 2023, I started tinkering with DIY electronics as a hobby. Until now, I’ve been using development boards like the Arduino Uno and ESP-32-WROOM so that I can focus on the software. Recently, I decided to step outside of my comfort zone and design a PCB from scratch for a door lock I’m working on.]]></summary></entry><entry><title type="html">Bumblebee: browser automation</title><link href="/log/bumblebee/" rel="alternate" type="text/html" title="Bumblebee: browser automation" /><published>2025-04-02T00:00:00+08:00</published><updated>2025-04-02T00:00:00+08:00</updated><id>/log/bumblebee</id><author><name>W. D. Sadeep Madurange</name></author><summary type="html"><![CDATA[Bumblebee is a tool I built for one of my employers to automate the generation of web scraping scripts.]]></summary></entry><entry><title type="html">How to set up ATSAM3X8E microcontrollers for bare-metal programming in C</title><link href="/log/arduino-due/" rel="alternate" type="text/html" title="How to set up ATSAM3X8E microcontrollers for bare-metal programming in C" /><published>2024-09-16T00:00:00+08:00</published><updated>2024-09-16T00:00:00+08:00</updated><id>/log/arduino-due</id><author><name>W. D. Sadeep Madurange</name></author><summary type="html"><![CDATA[This article is a step-by-step guide for programming bare-metal ATSAM3X8E chips found on Arduino Due boards. It also includes notes on the chip’s memory layout relevant for writing linker scripts. The steps described in this article were tested on an OpenBSD workstation.]]></summary></entry><entry><title type="html">Etlas: e-paper dashboard</title><link href="/log/etlas/" rel="alternate" type="text/html" title="Etlas: e-paper dashboard" /><published>2024-09-05T00:00:00+08:00</published><updated>2024-09-05T00:00:00+08:00</updated><id>/log/etlas</id><author><name>W. D. Sadeep Madurange</name></author><summary type="html"><![CDATA[Etlas is a news, stock market, and weather tracker powered by an ESP32 NodeMCU D1, featuring a 7.5-inch Waveshare e-paper display and a DHT22 sensor module.]]></summary></entry><entry><title type="html">Experimental e-reader</title><link href="/log/e-reader/" rel="alternate" type="text/html" title="Experimental e-reader" /><published>2023-10-24T00:00:00+08:00</published><updated>2023-10-24T00:00:00+08:00</updated><id>/log/e-reader</id><author><name>W. D. Sadeep Madurange</name></author><summary type="html"><![CDATA[This project features an experimental e-reader powered by an ESP-WROOM-32 development board and a 7.5-inch Waveshare e-paper display built with the intention of learning about e-paper displays.]]></summary></entry></feed> \ No newline at end of file
+<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="/feed.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2025-12-25T22:47:54+08:00</updated><id>/feed.xml</id><title type="html">ASCIIMX | Log</title><author><name>W. D. Sadeep Madurange</name></author><entry><title type="html">Recreating the Matrix rain with ANSI escape sequences</title><link href="/log/matrix-digital-rain/" rel="alternate" type="text/html" title="Recreating the Matrix rain with ANSI escape sequences" /><published>2025-12-21T00:00:00+08:00</published><updated>2025-12-21T00:00:00+08:00</updated><id>/log/matrix-digital-rain</id><author><name>W. D. Sadeep Madurange</name></author><summary type="html"><![CDATA[My 2022 implementation of the Matrix rain had too many loose ends. Unicode support was inflexible: the character set 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 correct.]]></summary></entry><entry><title type="html">Suckless upgrade workflow</title><link href="/log/suckless-software/" rel="alternate" type="text/html" title="Suckless upgrade workflow" /><published>2025-11-30T00:00:00+08:00</published><updated>2025-11-30T00:00:00+08:00</updated><id>/log/suckless-software</id><author><name>W. D. Sadeep Madurange</name></author><summary type="html"><![CDATA[Workflow for managing suckless patches across upgrades:]]></summary></entry><entry><title type="html">Fingerprint door lock</title><link href="/log/fpm-door-lock/" rel="alternate" type="text/html" title="Fingerprint door lock" /><published>2025-08-18T00:00:00+08:00</published><updated>2025-08-18T00:00:00+08:00</updated><id>/log/fpm-door-lock</id><author><name>W. D. Sadeep Madurange</name></author><summary type="html"><![CDATA[This project features a fingerprint door lock powered by an ATmega328P microcontroller.]]></summary></entry><entry><title type="html">On the use of MOSFETs as electronic switches</title><link href="/log/mosfet-switches/" rel="alternate" type="text/html" title="On the use of MOSFETs as electronic switches" /><published>2025-06-22T00:00:00+08:00</published><updated>2025-06-22T00:00:00+08:00</updated><id>/log/mosfet-switches</id><author><name>W. D. Sadeep Madurange</name></author><summary type="html"><![CDATA[Recently, I needed a low-power circuit for one of my battery-operated projects. Much of the system’s power savings depended on its ability to electronically switch off components, such as servos, that draw high levels of quiescent currents. My search for a solution led me to MOSFETs, transistors capable of controlling circuits operating at voltages far above their own.]]></summary></entry><entry><title type="html">How to configure ATmega328P microcontrollers to run at 3.3V and 5V</title><link href="/log/arduino-uno/" rel="alternate" type="text/html" title="How to configure ATmega328P microcontrollers to run at 3.3V and 5V" /><published>2025-06-10T00:00:00+08:00</published><updated>2025-06-10T00:00:00+08:00</updated><id>/log/arduino-uno</id><author><name>W. D. Sadeep Madurange</name></author><summary type="html"><![CDATA[This is a quick reference for wiring up ATmega328P ICs to run at 5V and 3.3V. While the 5V configuration is common, the 3.3V configuration can be useful in low-power applications and when interfacing with parts that themselves run at 3.3V. In this guide, the 5V setup is configured with a 16MHz crystal oscillator, while the 3.3V configuration makes use of an 8MHz crystal oscillator.]]></summary></entry><entry><title type="html">My first PCB</title><link href="/log/my-first-pcb/" rel="alternate" type="text/html" title="My first PCB" /><published>2025-04-26T00:00:00+08:00</published><updated>2025-04-26T00:00:00+08:00</updated><id>/log/my-first-pcb</id><author><name>W. D. Sadeep Madurange</name></author><summary type="html"><![CDATA[In 2023, I started tinkering with DIY electronics as a hobby. Until now, I’ve been using development boards like the Arduino Uno and ESP-32-WROOM so that I can focus on the software. Recently, I decided to step outside of my comfort zone and design a PCB from scratch for a door lock I’m working on.]]></summary></entry><entry><title type="html">Bumblebee: browser automation</title><link href="/log/bumblebee/" rel="alternate" type="text/html" title="Bumblebee: browser automation" /><published>2025-04-02T00:00:00+08:00</published><updated>2025-04-02T00:00:00+08:00</updated><id>/log/bumblebee</id><author><name>W. D. Sadeep Madurange</name></author><summary type="html"><![CDATA[Bumblebee is a tool I built for one of my employers to automate the generation of web scraping scripts.]]></summary></entry><entry><title type="html">How to set up ATSAM3X8E microcontrollers for bare-metal programming in C</title><link href="/log/arduino-due/" rel="alternate" type="text/html" title="How to set up ATSAM3X8E microcontrollers for bare-metal programming in C" /><published>2024-09-16T00:00:00+08:00</published><updated>2024-09-16T00:00:00+08:00</updated><id>/log/arduino-due</id><author><name>W. D. Sadeep Madurange</name></author><summary type="html"><![CDATA[This article is a step-by-step guide for programming bare-metal ATSAM3X8E chips found on Arduino Due boards. It also includes notes on the chip’s memory layout relevant for writing linker scripts. The steps described in this article were tested on an OpenBSD workstation.]]></summary></entry><entry><title type="html">Etlas: e-paper dashboard</title><link href="/log/etlas/" rel="alternate" type="text/html" title="Etlas: e-paper dashboard" /><published>2024-09-05T00:00:00+08:00</published><updated>2024-09-05T00:00:00+08:00</updated><id>/log/etlas</id><author><name>W. D. Sadeep Madurange</name></author><summary type="html"><![CDATA[Etlas is a news, stock market, and weather tracker powered by an ESP32 NodeMCU D1, featuring a 7.5-inch Waveshare e-paper display and a DHT22 sensor module.]]></summary></entry><entry><title type="html">Experimental e-reader</title><link href="/log/e-reader/" rel="alternate" type="text/html" title="Experimental e-reader" /><published>2023-10-24T00:00:00+08:00</published><updated>2023-10-24T00:00:00+08:00</updated><id>/log/e-reader</id><author><name>W. D. Sadeep Madurange</name></author><summary type="html"><![CDATA[This project features an experimental e-reader powered by an ESP-WROOM-32 development board and a 7.5-inch Waveshare e-paper display built with the intention of learning about e-paper displays.]]></summary></entry></feed> \ No newline at end of file
diff --git a/_site/index.html b/_site/index.html
index c4aea58..3bf1b02 100644
--- a/_site/index.html
+++ b/_site/index.html
@@ -56,7 +56,7 @@
<tr>
<td class="posts-td posts-td-link">
- <a href="/log/matrix-digital-rain/" class="link-decor-none">Matrix Rain: 2025 refactor</a>
+ <a href="/log/matrix-digital-rain/" class="link-decor-none">Recreating the Matrix rain with ANSI escape sequences</a>
</td>
<td class="posts-td posts-td-time">
<span class="post-meta">
diff --git a/_site/log/index.html b/_site/log/index.html
index c3c652a..fad9164 100644
--- a/_site/log/index.html
+++ b/_site/log/index.html
@@ -46,7 +46,7 @@
<tr>
<td class="posts-td posts-td-link">
- <a href="/log/matrix-digital-rain/" class="link-decor-none">Matrix Rain: 2025 refactor</a>
+ <a href="/log/matrix-digital-rain/" class="link-decor-none">Recreating the Matrix rain with ANSI escape sequences</a>
</td>
<td class="posts-td posts-td-time">
<span class="post-meta">
diff --git a/_site/log/matrix-digital-rain/index.html b/_site/log/matrix-digital-rain/index.html
index b428f9e..7c8c659 100644
--- a/_site/log/matrix-digital-rain/index.html
+++ b/_site/log/matrix-digital-rain/index.html
@@ -2,12 +2,12 @@
<html>
<head>
<meta charset="utf-8">
- <title>Matrix Rain: 2025 refactor</title>
+ <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>Matrix Rain: 2025 refactor</title>
+ <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>
@@ -41,34 +41,141 @@
<main>
<div class="container">
<div class="container-2">
- <h2 class="center" id="title">MATRIX RAIN: 2025 REFACTOR</h2>
+ <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>Unicode support added. ASCII + Katakana working:</p>
+ <div class="twocol justify"><p>My 2022 implementation of the Matrix rain had too many loose ends. Unicode
+support was inflexible: the character set 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 correct.</p>
+
+<p>I began by placing the decay factor in the MSB of the 4-byte RGB value. The PD
+value plays a somewhat analogous role to an alpha channel in that both
+influence transparency. However, they work very differently. So, 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 any 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, with minor refactoring, 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-&gt;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 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 an array that enables a user to add as
+many Unicode blocks as they want. The insert_code() function picks a block
+from it 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 &lt;&lt; 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 &gt;&gt; 32);
+
+ mat-&gt;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.</p>
+
+<p>The init_term() function is the arbiter of this zero-dependency software. It
+prepares the graphical environment so that I can interact with it via ANSI
+escape codes instead of unnecessary layers of abstraction:</p>
+
+<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>static inline int init_term(const struct winsize *ws)
+{
+ struct termios ta;
+
+ if (tcgetattr(STDIN_FILENO, &amp;ta) == 0) {
+ ta.c_lflag &amp;= ~ECHO;
+ if (tcsetattr(STDIN_FILENO, TCSANOW, &amp;ta) == 0) {
+ wprintf(L"\x1b[48;2;%d;%d;%dm",
+ RGB_BG_RED, RGB_BG_GRN, RGB_BG_BLU);
+ wprintf(L"%s", ANSI_FONT_BOLD);
+ wprintf(L"%s", ANSI_CRSR_HIDE);
+ wprintf(L"%s", ANSI_CRSR_RESET);
+ wprintf(L"%s", ANSI_SCRN_CLEAR);
+ setvbuf(stdout, 0, _IOFBF, 0);
+ ioctl(STDOUT_FILENO, TIOCGWINSZ, ws);
+ return 1;
+ }
+ }
+ return 0;
+}
+</code></pre></div></div>
+
+<p>insert_code() seeds the Matrix, blend() creates the old monochrome CRT display
+nostalgia, and ANSI control sequences paint the screen. 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>Algorithm notes: mat.col[] = shuffled column indices, mat.row[] = last row per
-column. shuffle() sets working set, main loop draws columns via index i (line
-333), swap() rotates set.</p>
+<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>Phosphor decay moved to LSB of RGB union. Should have done this originally.</p>
-
-<p>RGB/PD union stays. Little-endian machine, portability not a concern.</p>
-
-<p>Charset via UNICODE(min, max) macro - packs range into uint64, insert_code()
-unpacks and selects random char.</p>
-
-<p>Half-width Katakana (U+FF61-U+FF9F) for column alignment.</p>
-
-<p>Removed license, automake files. Build: cc -O3 main.c -o matrix</p>
-
-<p>Performance: 2% CPU, OpenBSD, T490.</p>
-
-<p>Commit:
-<a href="https://git.asciimx.com/matrix-digital-rain/commit/?id=03f8d87ba7c2e46bd3f3cc4c772fb3a2ac740c92">03f8d87</a></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>
diff --git a/_site/posts.xml b/_site/posts.xml
index ca75414..1f5e654 100644
--- a/_site/posts.xml
+++ b/_site/posts.xml
@@ -1 +1 @@
-<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="/posts.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2025-12-25T17:40:37+08:00</updated><id>/posts.xml</id><title type="html">ASCIIMX</title><author><name>W. D. Sadeep Madurange</name></author></feed> \ No newline at end of file
+<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="/posts.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2025-12-25T22:47:54+08:00</updated><id>/posts.xml</id><title type="html">ASCIIMX</title><author><name>W. D. Sadeep Madurange</name></author></feed> \ No newline at end of file
diff --git a/_site/projects/index.html b/_site/projects/index.html
index 4fae8d8..c75a620 100644
--- a/_site/projects/index.html
+++ b/_site/projects/index.html
@@ -51,8 +51,8 @@
<td class="project-item">
<a href="../log/matrix-digital-rain" class="link-decor-none">
- <img src="../log/matrix-digital-rain/thumb_sm.png" alt="Matrix Rain: 2025 refactor">
- <h5>Matrix Rain: 2025 refactor</h5>
+ <img src="../log/matrix-digital-rain/thumb_sm.png" alt="Recreating the Matrix rain with ANSI escape sequences">
+ <h5>Recreating the Matrix rain with ANSI escape sequences</h5>
</a>
</td>