一个 16 字节程序能做什么?
在 Outline Demoparty 2026 上,有人交出了一段 x86 real-mode DOS 程序。只有 16 bytes。它在 VGA 文本显存里长出谢尔宾斯基式分形,又把同一份字节直接送进 PC Speaker,变成粗糙但有结构的方波声音。
最有意思的地方不在“老电脑还能玩”。16 字节太小,小到没有空间写完整初始化、兼容层、音频驱动和解释器。硬件约定、数学结构、内存初态,全被挤到台面上。
16 字节到底做了什么
它跑在 DOS/BIOS 约定下,目标很明确:用文本显存当画布,用 XOR 当规则,用 PC Speaker 当输出端。
| 关键点 | 位置 / 指令 | 实际作用 |
|---|---|---|
| 显示初始化 | int 10h | 进入 BIOS 文本模式,屏幕通常看起来被清空 |
| 显存位置 | 0xB800 | VGA 文本缓冲区,字符字节和颜色属性字节交错存放 |
| 主循环 | lodsb / sub si,57 / xor [si],al | 读取一个字节,调整指针,再用 XOR 改写显存 |
| 声音输出 | out 61h,al | 把同一字节写到 PC Speaker 控制端口 |
VGA 文本模式不是抽象画布。它就是一段内存。典型文本屏幕里,每个字符占两个字节:一个 ASCII 字符,一个颜色属性。
BIOS 初始化后,屏幕可能看起来是空的,但内存里并不空。常见状态会出现空格 0x20 和灰字黑底属性 0x07 这类规律数据。程序吃的正是这些位。
lodsb 读出当前字节,xor [si],al 把它和另一个位置的字节做 XOR。XOR 没有进位,只做位翻转。某一位沿着显存传播,就会出现类似 Stephen Wolfram 初等元胞自动机 Rule 60 的结构,也就是谢尔宾斯基三角形那种空洞递归的影子。
声音没有单独生成。
out 61h, al 把刚参与画面计算的字节送到 PC Speaker 控制端口。端口 61h 里的 Bit 1 会影响扬声器控制。于是,字符数据中的某些位变化,在扬声器上变成方波开关节奏。
图案不是配乐。图案本身被当成了声音信号。
还有一个小细节决定了它不是普通扫屏。lodsb 会让 SI 自动前进一步,后面的 sub si,57 又把它拉回去,所以净移动是 -56 字节。
文本模式一行常按 80 字节解释时,-56 相当于反向斜切。画面因此会形成斜向推进、柱状闪烁的结构。步进也拉长了循环节奏,声音的基频随之降低,听起来更慢、更沉。
它为什么漂亮:同一份数据干三件事
这段程序最漂亮的地方,是它不把功能分开。
现代软件喜欢分层:渲染层、状态层、音频层、驱动层、兼容层。分层当然有价值。没有分层,浏览器、AI 工具、协作软件都做不起来。
但分层也会让人忘掉机器。
这里刚好反过来。同一段 0xB800 文本显存,既是画布,也是计算空间,还是音频信号源。字符位被 XOR 改写,画面出现分形;同一字节写到 61h,扬声器发出方波。
“天下熙熙,皆为利来。”放到软件工程里,也可以粗暴改一句:天下层层,皆为抽象来。抽象让系统可维护,也让成本变得不透明。
16 字节把这层遮布扯掉了。
它没有跨平台承诺。不同 BIOS、真实机器、DOSBox、PCem,显存初始状态和端口模拟细节都可能不同。画面纹理、声音节奏、启动时的那点随机感,都可能变。
如果要完全一致,可以写更多初始化代码:清显存、固定寄存器、设定状态、规避模拟器差异。问题是,那就不再是 16 字节作品。
这正是 demoscene 的题目。限制不是瑕疵,限制就是规则本身。
对懂一点汇编、复古计算和底层的人,这类作品最适合拆开看:看 SI 怎么走,看 0xB800 的字符/属性字节怎么参与计算,看 61h 的 Bit 1 怎么把位模式变成声音。别急着把它移植成现代程序。先在 DOSBox、PCem 或真机上对比运行差异,差异本身就是材料。
对关心软件膨胀的人,它给的动作更简单:回到自己的系统里,找一层真正有成本的抽象。启动慢,是资源加载的问题,还是框架链路的问题?界面卡,是渲染慢,还是状态同步绕远?别把“复杂业务”当万能挡箭牌。
这段 16 字节程序不能替代现代工程。它只提醒一件事:有些复杂,是问题需要;有些复杂,是团队已经看不见机器了。
我的判断:小代码不是玩具,是一面反光镜
我不太买账那种简单骂法:现代软件都臃肿,所以古早代码更高贵。
不成立。
今天的软件要处理安全、国际化、字体、输入法、GPU、网络、权限、云同步、可观测性。拿 16 字节去要求浏览器、IDE、AI 产品,属于错位比较。
但这段程序依然有批判性。它批判的不是“代码多”,而是“代码多到没人知道代价在哪里”。
很多产品变慢,并不是因为功能真的复杂到无解。它可能只是多了一层 SDK、多了一套埋点、多了一轮远程配置、多了一次无意义的渲染。每一层都说自己很小,合起来就成了用户体感里的慢。
16 字节作品的残酷之处在这里:它没有地方藏浪费。
一条指令要同时服务多个目标。一次读写要同时改变画面和声音。一个环境假设要同时带来效果和风险。它把工程关系压到最紧,压到每个字节都必须交代用途。
这也解释了为什么它不该被夸成“音乐生成器”。PC Speaker 发出的只是由位模式触发的方波节奏。它没有现代意义上的音色设计、采样控制、编曲结构,也没有稳定跨平台输出。
它更像一个硬件诗句。短,硬,靠约束成立。
接下来真正该观察的,不是谁能把 16 字节吹得更神,而是不同环境下它怎么变:
- BIOS 文本模式初始化是否一致;
0xB800初始内容是否残留;- DOSBox、PCem、真实硬件对
61h的模拟差异; sub si,57这类步进变化如何改变画面周期和声音节奏。
这些变量不会削弱作品,反而说明它的本质:它不是封装好的产品,而是把硬件、数学和偶然性绑在一起的小实验。
我喜欢它,正因为它不完整。
完整软件追求遮蔽差异。这个 16 字节程序反过来展示差异。它让我们看到,所谓“运行结果”,从来不是代码单方面决定的。机器、环境、初始状态,都在共同写这幅图、这段声音。
回到开头那个问题:16 字节能做什么?
它做不了现代应用。也不需要做。
它只要让人重新看见机器,就已经够了。少到极限时,工程美学不靠口号,靠每一个字节承担后果。
