小岛AI
| ONLINE |

posts/claude-amiga-port-verification-loop.md

Claude 搬完 7.2 万行汇编,真正值钱的是 0 像素差异

小岛AI 2026 / 09 / 04

72,758 行几乎没有注释的 68000 汇编。

目标机器是 1993 年的 Amiga 500,512KB 内存,没有硬盘,作者在巴格达把它接到电视上,一条扫描线一条扫描线地写出《Babylonian Twins》。三十三年后,Claude Fable 5 在 Claude Code 里把它搬进了 Godot 4

同一轮迁移里,还有 2010 年手工重写的 34,000 行 C++。空项目到可玩角色用了 21 分钟,当晚已经能导出 macOS、iOS 和 Android。

好家伙,这种数字天然适合当标题。

可我把作者 Rabah Shihab 的复盘翻完,最想记下来的不是「一晚上」。真正有点子牛逼的是另一组数字,重建出的发行二进制逐字节一致,五张完整关卡图的差异像素为零

一晚写完是速度故事。

零差异才是工程故事。

AI 开始写之前,先给它一把硬尺子

很多老系统迁移有个很尴尬的问题。大家知道新版本能启动,也知道主要页面看着差不多,但没人能准确回答,它到底和旧版本差在哪。

于是验收慢慢变成一种集体玄学。老同事点几下,说感觉还行。测试跑一遍,说主流程没挂。过两周,某个十年没动过的边角逻辑突然冒烟,所有人才发现那个奇怪的常数根本不是脏代码,而是当年留下的护栏。

《Babylonian Twins》的做法很值得抄。Claude 没有直接对着汇编开始改写,而是先让原始源代码在现代工具链里重新成立。

它调用 vasm 汇编器处理老代码,修复 2008 年转存时被截断的文件名,给 ASM-One 与 vasm 的语法差异补预处理层,再拿新产物和当年发行的二进制做 diff。随后用 FS-UAE 启动磁盘镜像,确认真实程序可以运行。

vasm old-source.s -o rebuilt.bin
cmp shipped.bin rebuilt.bin
fs-uae rebuilt.adf

这里的顺序很关键。先证明旧世界可以被重新构建,再谈把它迁到新世界。

不然模型一边猜原格式,一边猜工具链,一边猜迁移后的行为,三个变量缠在一起。最后画面不对,你连该怪解析器、编译器还是 Godot 都不知道。

而字节级比较很冷酷。差一个字节就是差一个字节,不接受「整体效果不错」这种安慰话。

1993 年的地图编辑器在 2026 年重新运行

这把尺子还抓到一个相当隐蔽的坑。ASM-One 的 org 可以把位置计数器往回挪,vasm 不行。第一版兼容代码把本应跳过的 800 字节写成了 800 个零,后面的 Copper List 整体错开 944 字节。

程序照样能汇编,照样能启动,甚至不会给你一个像样的报错。

它只是画错。

这事儿很像生产环境里最难受的那类故障。不是红灯,不是崩溃,而是悄悄给你一个看起来挺像答案的错误结果。没有确定性的比较基准,模型越快,错误只会越快抵达下一环。

0 像素差异,也不等于游戏真的对了

第二把尺子来自画面。

原版关卡不是常见的图片文件,而是一串没有文件头、没有宽高说明的数字。一个 16 位单元里,低 8 位决定画哪块砖,高 6 位决定这块砖能不能站、能不能爬、会不会伤人,或者它是不是一扇门。

Claude 沿着读取字节的代码倒推格式,把五个关卡重新渲染,再和作者 2020 年保留的完整地图逐像素比较。第一版没通过,因为天空渐变和水面颜色循环藏在 Amiga 的 Copper 逻辑里。把两处效果补回去以后,五张图全部达到 0 个差异像素。第一关单是横向就有 9,600 像素。

像素差异把遗漏的天空和水面效果标成白色

厉害了。

但故事到这里偏偏没有结束。

在第二关的一条走廊里,屏幕上没有敌人,角色却会凭空掉血。真正出手的是上方十三格、隔着实心岩石的长矛守卫。原版攻击范围有上下两条边界,移植时只留下其中一条,伤害范围于是沿着整列地图向下穿透地板。

漏掉一侧边界后,守卫的伤害范围穿过整层岩石

关卡能加载,截图能对齐,构建检查也能全绿。

玩法还是错的。

还有一处蹦床问题更麻烦。物理常数没错,模拟出的高度也没错,错的是输入语义。2010 版采用事件驱动,又叠了一层 tvOS 兼容处理,按住跳跃键会被当作已经释放,直到玩家重新按下。Godot 轮询输入,会持续报告按住。两边的代码都合理,手感却完全不同。

这个规格写在哪里?

没写在文档里,也没写在单元测试里。它写在作者握过旧手机的手里。

所以,字节一致证明你读对了结构,像素一致证明你还原了画面,只有行为回放和真人试玩才能接近产品语义。 三把尺子谁也替不了谁。

别只给 Agent 仓库,再给它可观测的系统

这次迁移能跑起来,还有一个常被「模型能力」四个字盖住的细节。Claude Code 拿到的不只是源代码,它有终端、文件系统、汇编器、模拟器,也能编译和启动 Godot 项目。

更重要的是,它主动给游戏补了一组命令行入口。

--level=<name>       直接载入关卡
--pose=<spec>        把角色放到指定位置
--drive=<spec>       逐帧执行输入脚本
--probe              输出门、钥匙和开关状态
--screenshot=<path>  渲染一帧后退出

这组参数没有游戏演示那么抓眼球,却比演示更接近 Agent 工程的要害。原本一句模糊的「跳起来好像不太对」,被拆成位置、速度、着地状态、最高点坐标。原本需要人工点进第五关才能看到的错误,开始有机会被固定输入和状态探针捕捉。

给 AI 搭脚手架,说到底常常就在干这种事。不是再写一段更长的提示词,而是把系统改造成机器可以操作、观察、比较和重试的形状。

很多团队让 Agent 接手老代码时,只交给它仓库和一句「迁到新框架」。接下来便盯着提交记录,看它一口气改了多少文件。坦率讲,这和用打字速度评审重构质量差不多,热闹有余,心里没底。

更稳的做法,是在迁移前先补四样东西。

第一样是可重复构建,旧版本必须能在固定工具链里从源代码重新产出。

第二样是黄金样本,保留二进制、截图、接口响应、数据库快照或关键报表,让新旧结果能机械比较。

第三样是可脚本控制的入口,把页面点击、角色移动、批处理步骤或业务状态变成命令参数和固定场景。

第四样是人的验收清单,专门记录手感、历史约定、运营习惯和那些代码没有表达的产品语义。

我自己的感受是,前三样决定 Agent 能不能持续往前跑,最后一样决定它跑到的是不是你真正想去的地方。

最后一公里,藏在一条拼错字的一星评论里

整篇复盘里,我最喜欢的不是汇编器,也不是零像素差异,而是一条 Google Play 的一星评论。

那位玩家大意说,第一关的门已经打开,却怎么都过不去。

游戏里「开门」和「走进出口」是两个动作。提示文本已经翻成十一种语言,但十八个关卡里只有两关放了提示。偏偏缺提示的地方又是免费试玩的最后一关。玩家站在一扇敞开的门前,不知道还要继续往里走,于是合理地判断游戏坏了。

作者、测试者和两次重建,十五年都没发现。

一条拼写不太对的一星评论发现了。修复后来随 2.0.3 发布。

这个细节挺戳我的。我们很容易把验证想成更大的测试集、更漂亮的覆盖率、更严密的自动化。可真实世界的规格,经常散落在旧用户的抱怨、某位同事的肌肉记忆、一个没人敢删的常数,或者一条看起来没什么价值的差评里。

AI 很擅长把这些碎片读得飞快,甚至能把 1993 年的原版2026 年的 Godot 版本 装进同一个程序。它还处理多语言商店截图、预览视频和上架字段,登录、定价、提交与发布则继续留给人确认。

边界划得很清楚,也很诚实。

所以这篇文章给我的结论并不是「老程序员要被替代了」。那太省事,也太无聊。

更准确的说法是,老代码正在从无法搬动的遗迹,变成可以被机器勘探的现场。 但勘探报告能不能信,取决于我们有没有先埋下坐标、标尺和回程路线。

一晚迁完,当然值得惊叹。

可到了第二天早上,真正能让你放心合并的,还是那行冷冰冰的结果。

0 个差异像素。