Wake up! 16b
Wake up! 16b
wake up! 16b Released at the Outline Demoparty in May 2026, Ommen, NL. An exploration of algorithmic density in 16 bytes of x86 assembly. Watch Video | Demozoo Entry
wake up! 16b 于 2026 年 5 月在荷兰欧门(Ommen)举办的 Outline Demoparty 上发布。这是一次对 16 字节 x86 汇编代码中算法密度的探索。观看视频 | Demozoo 条目
Hey everyone. I learned programming as a kid on an old IBM PC with a monochrome green monitor over 30 years ago and always wanted to create a program for this system. I created well over 100 tiny intros in the last 15 years. Recently I was not too active but the fantastic “Rainbow Surf” from Plex in just 16 bytes motivated me to dig up some old dusty sketches again and get to work.
大家好。我 30 多年前还是个孩子时,就在一台带有单色绿屏显示器的老式 IBM PC 上学习编程,并一直想为这个系统编写程序。在过去的 15 年里,我创作了 100 多个微型演示程序(tiny intros)。最近我不太活跃,但 Plex 那段仅用 16 字节实现的精彩作品“Rainbow Surf”激励我重新翻出那些尘封的草稿,再次投入创作。
The creation of this program happened with the usual tinkering around. I was messing with cellular automaton for graphics and sounds and discovering sizecoding tricks. Actually: a) polymorphic asm instructions, like add [bx+si],al which is 0x0000 b) jumping into the middle of instructions to save bytes and reuse opcodes. In hundreds of tiny experiments, this one stuck out, just by the sound of it. When I unfolded what’s left and removed “the rest”, I had a hard time to grasp what’s really going on. I was scratching my head looking at the simple formula that remained after golfing many bytes away. I myself didn’t expect that the explanation would go this deep for just these few bytes xD.
这个程序的诞生源于我平时的折腾。我当时正在研究用于图形和声音的元胞自动机,并探索代码压缩技巧。实际上包括:a) 多态汇编指令,例如 add [bx+si],al(其机器码为 0x0000);b) 跳转到指令中间以节省字节并重用操作码。在数百次微型实验中,这个程序因其独特的声音脱颖而出。当我展开剩余部分并删掉“多余内容”后,我很难理解到底发生了什么。看着删减大量字节后剩下的简单公式,我挠头不已。我自己也没想到,仅仅这几个字节背后的解释竟然如此深奥 xD。
My original “M8trix” from 2014 already did smear pseudorandom letters across the screen (in 8 bytes, then in 7) and I always wondered how I could make it “sound good”. But chronologically in the development of “wakeup”, the sound was first. Since you “see what you hear” it doesn’t really matter, but “16 bytes that turn Sierpinski sound into Matrix rain” would be a good subtitle =)
我 2014 年的原创作品“M8trix”已经实现了在屏幕上涂抹伪随机字母(先是 8 字节,后来优化到 7 字节),我一直想知道如何让它“听起来不错”。但在“wakeup”的开发时间线上,声音是第一位的。既然你是“所见即所听”,这其实并不重要,但“将谢尔宾斯基(Sierpinski)声音转化为黑客帝国雨的 16 字节”会是一个很好的副标题 =)
TLDR: Each time step, another Sierpinski triangle line is a) played on the speaker b) drawn to the screen with a stepsize of 56. You can sense the motion, but not really see it, since it’s 8192 “pixels wide” but one line of chars is just 80 bytes. On a much much much bigger screen, you could see the triangle. Or, if you don’t “skip pixels” and draw it all at once, you would see it as well.
简而言之:在每个时间步长,另一条谢尔宾斯基三角形线会被:a) 在扬声器上播放;b) 以 56 的步长绘制到屏幕上。你可以感觉到运动,但无法真正看到它,因为它有 8192 个“像素宽”,而一行字符只有 80 字节。在更大得多的屏幕上,你就能看到这个三角形。或者,如果你不“跳过像素”而是一次性绘制出来,你也能看到它。
So, here are the 16 bytes of x86 real-mode DOS assembly. When you run it, it uses the video memory as a calculation space to draw an infinite Sierpinski fractal, and at the same time bangs the speaker with that geometry.
这就是那 16 字节的 x86 实模式 DOS 汇编代码。当你运行它时,它利用显存作为计算空间来绘制无限的谢尔宾斯基分形,同时用该几何结构驱动扬声器发声。
int 10h ; 2 bytes
mov bh, 0xb8 ; 2 bytes
mov ds, bx ; 2 bytes
L: lodsb ; 1 byte
sub si, byte 57 ; 3 bytes
xor [si], al ; 2 bytes
out 61h, al ; 2 bytes
jmp short L ; 2 bytes
1. The Canvas: A Primed Void
1. 画布:预置的虚空
The code starts with a standard BIOS interrupt: int 10h. This sets up video mode 0, giving a 40x25 text mode grid. Then the data segment (ds) is pointed to 0xb800, the memory address of the VGA/CGA text buffer. When the BIOS clears the screen, it doesn’t fill memory with absolute zeroes. Every character space is two bytes: the ASCII character and the color attribute. All 2,000 slots are set to 0x20 (space) and 0x07 (light gray on black). So the screen looks empty, but the memory is already filled with a uniform pattern.
代码以标准的 BIOS 中断 int 10h 开始。这设置了视频模式 0,提供了一个 40x25 的文本模式网格。然后数据段 (ds) 被指向 0xb800,即 VGA/CGA 文本缓冲区的内存地址。当 BIOS 清屏时,它并不会用绝对的零填充内存。每个字符空间占用两个字节:ASCII 字符和颜色属性。所有 2000 个槽位都被设置为 0x20(空格)和 0x07(黑底浅灰)。因此屏幕看起来是空的,但内存中已经填充了统一的模式。
I created a lot of “noise” or “CA” sound intros but this one stands out. It was and is still super unexpected! The specific spice here is how memory is initialized on “clear screen” and what’s “before” and “after” the actual visible memory. The “pure” sound is also lovely (I can carefully set everything with a few more bytes to make it sound the same on all systems) but this spicy difference I still have to fully understand makes it sound even better imho =)
我创作过很多“噪声”或“元胞自动机(CA)”类的演示程序,但这个作品脱颖而出。它过去和现在都非常出人意料!这里的独特之处在于“清屏”时内存是如何初始化的,以及实际可见内存的“之前”和“之后”是什么。这种“纯粹”的声音也很美妙(我可以多用几个字节仔细设置,让它在所有系统上听起来都一样),但这种我仍需完全理解的微妙差异,在我看来让它听起来更棒了 =)
2. The Engine: Additive Prefix Sums
2. 引擎:加法前缀和
The intertwine, the synesthesia goes far beyond what I found so far in other tiny intros. I would even go so far as to say it’s revealing more mathematical secrets and relations than using iterated function systems for the “chaos game” without an RNG. Anyway, this time I want you to fundamentally understand the mathematics of what you hear. Not just “you do some operation here and then it sounds interesting”.
这种交织和通感远远超出了我在其他微型演示程序中发现的内容。我甚至可以说,它揭示的数学秘密和关系,比在没有随机数生成器(RNG)的情况下使用迭代函数系统进行“混沌游戏”要多得多。总之,这一次我希望你能从根本上理解你所听到的数学原理,而不仅仅是“在这里做一些操作,然后它听起来很有趣”。
To strip it down to pure math: assume a zeroed state instead of 0x20, use add instead of xor, and step forward 16 bytes at a time. Assume the accumulator al starts at 2. A DOS segment is exactly 65,536 bytes. Moving 16 bytes per step means exactly 4,096 steps to traverse the segment (65536 / 16 = 4096). Then si wraps cleanly back to 0x0000. Adding up values between cells creates partial sums. Because 4,096 is a multiple of 256 (the 8-bit register size), the carryover aligns perfectly when the segment wraps, cleanly resetting al to 2 at the start of each sweep.
将其简化为纯数学:假设状态为零而不是 0x20,使用加法而不是异或,并且每次前进 16 字节。假设累加器 al 从 2 开始。一个 DOS 段正好是 65,536 字节。每步移动 16 字节意味着遍历该段正好需要 4,096 步(65536 / 16 = 4096)。然后 si 会整齐地回绕到 0x0000。将单元格之间的值相加会产生部分和。因为 4,096 是 256(8 位寄存器大小)的倍数,当段回绕时,进位会完美对齐,在每次扫描开始时将 al 干净地重置为 2。
3. Crystallization: XOR and the Sierpinski Shift
3. 结晶:异或与谢尔宾斯基位移
Now, back to combinatorics. By special laws, when doing modulo two, the Sierpinski triangle emerges. This specific bit is what gets banged into the speaker, while the other bits are ignored. To separate the bitplanes, the fact that carry-free addition of bits is XOR is why it is there instead of add. Since the code starts with 2 (binary 00000010), only bit 1 is toggled between 0x00 and 0x02. This perfectly maps to rule 60 in elementary cellular automata:
现在,回到组合数学。根据特殊规律,在进行模 2 运算时,谢尔宾斯基三角形就会显现出来。这个特定的位就是被驱动到扬声器中的内容,而其他位则被忽略。为了分离位平面,使用异或(XOR)而不是加法的原因在于:位的不进位加法就是异或。由于代码以 2(二进制 00000010)开始,只有第 1 位在 0x00 和 0x02 之间切换。这完美地映射到初等元胞自动机中的规则 60:
$$Cell^{(p)}[k] = Cell^{(p-1)}[k] \oplus Cell^{(p)}[k-1]$$
Lucas’s theorem guarantees this matches bit 1 from the addition table.
卢卡斯定理(Lucas’s theorem)保证了这与加法表中的第 1 位相匹配。
4. The Voice of the Machine: Translating Data to Audio
4. 机器之声:将数据转化为音频
Here is the trick: out 61h, al. Port 61h interfaces with the PC speaker. Bit 1 pushes the speaker cone out (1) and pulls it in (0). The code computes the fractal via XOR, writes it to memory, and shoves that byte straight into the speaker port. The 1s and 0s from the fractal create square waves that shift naturally in pulse width and frequency.
诀窍就在这里:out 61h, al。端口 61h 与 PC 扬声器接口。第 1 位将扬声器锥体向外推(1)并向内拉(0)。代码通过异或计算分形,将其写入内存,并将该字节直接推送到扬声器端口。来自分形的 1 和 0 创建了在脉冲宽度和频率上自然变化的方波。