C
发布于 2026/05/24 · 阅读 1

Wake up! 16b:16字节x86汇编中的算法密度探索

  • #x86汇编
  • #尺寸编码
  • #Sierpinski分形
  • #Demoscene
  • #PC扬声器

Wake up! 16b:16字节x86汇编中的算法密度探索

发布于2026年5月Outline Demoparty(荷兰奥门)。

大家好。我在30多年前开始学习编程,用的是带单色绿色显示器的老IBM PC,一直想为这个系统写一个程序。过去15年里,我创作了超过100个微型intro。最近不太活跃,但Plex那个惊人的16字节“Rainbow Surf”激发了我,让我重新翻出一些尘封的草稿开始工作。

这个程序的创作过程就是通常的“折腾”。我摆弄着细胞自动机来生成图形和声音,并发现了大小编码的技巧,比如:a) 多态汇编指令,如 add [bx+si],al0x0000;b) 跳转到指令中间以节省字节并重用操作码。在数百个微小实验中,这个程序因其声音而脱颖而出。当我展开剩余的部分并移除“其他”后,我很难理解到底发生了什么。我挠着头看着经过大量字节精简后剩下的简单公式。我自己也没想到仅仅几个字节的解释会如此深入 xD。

我2014年的原始“M8trix”已经能将伪随机字母涂抹到屏幕上(8字节,后来精简到7字节),我一直想知道如何让它“听起来好听”。但在“wakeup”的开发时间线上,声音是首先出现的。既然你“看到你所听到的”,这并不重要,但“将Sierpinski声音变成Matrix雨滴的16字节”会是一个不错的副标题 =)

太长不看:每个时间步,另一条Sierpinski三角形线 a) 通过扬声器播放,b) 以56字节步长绘制到屏幕。你能感受到运动,但并不能真正看到它,因为它有8192“像素宽”,但一行字符只有80字节。在一个非常非常大的屏幕上,你能看到三角形。或者如果你不“跳过像素”而一次性全部绘制,你也能看到它。

那么,以下是16字节的x86实模式DOS汇编代码。当你运行它时,它使用视频内存作为计算空间来绘制无限的Sierpinski分形,同时用该几何形状敲击扬声器。

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. 画布:一个被填充的空虚

代码以一个标准BIOS中断开始:int 10h。这设置了视频模式0,提供40x25文本模式网格。然后数据段(ds)指向0xb800,即VGA/CGA文本缓冲区的内存地址。当BIOS清屏时,它不会将内存填充为绝对零。每个字符位置是两个字节:ASCII字符和颜色属性。所有2000个槽被设置为0x20(空格)和0x07(黑色背景上的浅灰色)。所以屏幕看起来是空的,但内存已经充满了均匀的模式。

我创作过很多“噪声”或“CA”声音intro,但这一一个脱颖而出。它过去和现在都非常出乎意料!这里的特殊之处在于清屏时内存的初始化方式,以及实际可见内存“之前”和“之后”的内容。“纯净”的声音也很可爱(我可以用多几个字节小心地设置一切,使其在所有系统上听起来相同),但这种我尚未完全理解的“辛辣”差异使得它听起来更好 imho =)

2. 引擎:加法前缀和

这种交织和联觉远远超出了我在其他微型intro中发现的。我甚至可以说,它比在没有RNG的情况下使用迭代函数系统玩“混沌游戏”揭示了更多数学秘密和关系。无论如何,这次我希望你从根本上理解你所听到的数学,而不仅仅是“你在这里做了一些操作,然后听起来有趣”。

将其简化为纯数学:假设状态被清零(而不是0x20),使用add而不是xor,每一步向前移动16字节。假设累加器al从2开始。一个DOS段正好是65536字节。每步移动16字节意味着恰好4096步遍历整个段(65536/16=4096)。然后si干净地回绕到0x0000。在单元格之间累加值会创建部分和。因为4096是256(8位寄存器大小)的倍数,进位在段回绕时完美对齐,在每次扫描开始时将al干净地重置为2。该值遵循一个缩放为2的二项式序列:

$$A^{(p)}[k] \equiv 2 \binom{k+p}{p-1} \pmod{256}$$

以下是前16步逐行累加的情况:

Pass \ Cell0123456789101112131415
02000000000000000
12200000000000000
22420000000000000
32662000000000000
428128200000000000
521020201020000000000
6212304030122000000000
72144270704214200000000
821656112140112561620000000
92187216825225216872182000000
10220902404205044202409020200000
112221103306609249246603301102220000
12224132440990158418481584990440132242000
1322615657214302574343234322574143057215626200
1422818272820024004600668646006400420027281822820
15230210910273060061001012870128701001060062730910210302

3. 结晶:XOR和Sierpinski移位

现在回到组合数学。根据特殊定律,当模2运算时,Sierpinski三角形就会出现。这个特定的比特被送入扬声器,而其他比特被忽略。为了分离位平面,比特的无进位加法就是XOR,这就是为什么这里使用XOR而不是add。由于代码以2(二进制00000010)开始,只有位1在0x00和0x02之间切换。这完美映射到基本细胞自动机中的规则60:

$$Cell^{(p)}[k] = Cell^{(p-1)}[k] \oplus Cell^{(p)}[k-1]$$

卢卡斯定理保证这与加法表中的位1匹配。请自行验证('2'表示位1被设置):

Pass \ Cell0123456789101112131415
02000000000000000
12200000000000000
22020000000000000
32222000000000000
42000200000000000
52200220000000000
62020202000000000
72222222200000000
82000000020000000
92200000022000000
102020000020200000
112222000022220000
122000200020002000
132200220022002200
142020202020202020
152222222222222222

4. 机器的声音:将数据转换为音频

技巧在于:out 61h, al。端口61h与PC扬声器接口。位1将扬声器锥体推出(1)和拉入(0)。代码通过XOR计算分形,写入内存,并将该字节直接送到扬声器端口。分形中的1和0产生方波,其脉冲宽度和频率自然变化:

按行播放时,这会创建自相似、几乎速度不变的字节节拍。但还不仅如此:不仅文本被输出到扬声器,整个64KB段中的剩余字节也是如此,在这种情况下还包括阴影视频ROM BIOS代码,这是使得声音与预期的基于Sierpinski线的叠加矩形波字节节拍截然不同的秘密成分,带来了朋克和砂砾感。

5. 56字节步长:八度移位和对角线剪切

为了重现M8trix效果,单元格本身必须以某种方式散布在屏幕上,使得声音缓冲区不会太大,并且屏幕上的字符稀疏填充。因此代码不以16为步长。sub si, byte 57加上lodsb意味着每次迭代移动-56字节——即向后移动。

音频:56不能整除65536。代码只访问8的倍数的偏移量,需要8192步,并在重置前环绕7次。这使周期长度加倍,基频减半。声音降低一个八度。

视觉:在80字节宽的屏幕上向后移动56字节相当于向前移动24字节(12列)。只有10个不同的列被访问。分形不是作为实心图像绘制的;它对角剪切成10个字符柱,在屏幕上向上移动。

6. 真实硬件与最终思考

场景成员miragept进行了如下捕获:“这太棒了,我不得不在真实硬件上运行它。绿色文本自然适合MDA/Hercules,所以我将地址从0xB800修补到0xB000,这是MDA使用的地址。我没有确切的IBM计算机,但使用了带有能够模拟MDA/Hercules的EGA卡的286,以及一个真正的MDA显示器,所以差不多。抱歉音频质量低(持续的噪音来自机器本身)。请注意,这台显示器(IBM 5151)有巨大的磷光持久性,我认为在这种情况下这会损害演示,因为它非常快。”

我的回复:“HellMood @miragept:非常感谢你这个♥。我很高兴看到它按预期工作,即使由于字节更改导致声音略有不同。也许它确实应该运行得慢一点会更好,但我发现值得注意的是,Sierpinski结构(接近末尾时)比我的版本更明显 =)”

模拟器和不同的BIOS版本会在RAM中留下略有不同的痕迹。由于代码对存在的内容进行XOR,输出对环境高度敏感。先清空内存会给出完全均匀的输出,但这会耗费宝贵的字节。拥抱硬件的自然状态正是大小编码的魅力之一。

感谢阅读。

链接与资源

  • Nanogems - 精选的Demoscene最佳微型Intro
  • HellMood在Pouet上的作品
  • miragept在286/MDA/Hercules上的捕获
  • Sizecoding Wiki
  • “Rainbow Surf” - Plex的16字节x86
  • “M8trix” - HellMood的8字节
  • 本文手写稿。
1 阅读0 评论0 点赞

评论

登录 / 注册即可发布评论!
暂无评论,成为第一个发表评论的用户吧。