让大模型读懂 68000 汇编,把我 1993 年的 Amiga 游戏移植到 Godot
文章摘要
作者 Rabah Shihab 在 1993 年的巴格达,用一台 512KB 内存、没有硬盘、接在电视上的 Amiga 500,写出了《Babylonian Twins》——伊拉克第一款商业游戏。纯 68000 汇编,每一个精灵、每一条扫描线都是手写的。美术是 Murtadha Salman,音乐是 Mahir AlSalman。当时正值制裁期,没有互联网、没有任何游戏开发资料,全部依据只有一本《Amiga Hardware Reference Manual》,每天只有几小时电力,反复换软盘加上 50℃ 的夏天把软驱烧坏了三次。因为 Commodore 倒闭加上制裁吓跑了发行商,这款做完的游戏被搁置在架子上,直到 2008 年才被 Amiga 论坛的玩家从他弟弟上传的 YouTube 视频里找出来。2010 年团队曾手工重做过一次 iPhone 版,从零写了约 34,000 行 C++ 引擎,累计下载超过两百万次。
这篇文章记录的是 2026 年 7 月,他用 Claude Fable 5 跑在 Claude Code 里做的第三次移植。他设计了三级递进的实验:第一步「安全的要求」,把 2010 年那 34,000 行 C++ 搬进 Godot 4;第二步「不公平的要求」,直接把 1993 年那 72,758 行、跨 26 个文件、几乎没有注释、目标机器早已停产的 68000 汇编重建成 50Hz 的 Godot 版本;第三步「贪心的要求」,把第二个塞进第一个里,让买现代版的人可以直接启动 1993 年原版。三步全部成功。第一步从空项目到可操作角色只花了 21 分钟,四小时内跑完全部 38 种实体、菜单流程和三平台导出;手感调校又花了三天。第三步只用了一个晚上(21:58 到 23:43)。
技术细节里最有分量的部分是”字节级验证”。在开始移植之前,模型先用 vasm 在 Apple Silicon Mac 上把 1993 年的源码重新汇编,一直折腾到输出与当年发行的二进制逐字节一致——从一堆文件到第一次字节匹配只花了 15 分钟。过程中要跨过 ASM-One 与 vasm 的五处方言差异(比如 cmp #4,d0 的编码选择),还要修复 2008 年迁移时被截断的长文件名。最贵的坑是 org:ASM-One 允许位置计数器往回移动,vasm 不行,第一版绕过方案把一个回绕块里的 ds.b 800 写成了 800 个零字节,导致包括 copper list 在内的后续内容整体偏移 944 字节——游戏能编译能启动,只是画错了东西。最后还剩约 108 字节对不上,模型给出的解释是:ASM-One 汇编到内存,当年的发行文件是把跑过一遍的游戏内存直接存盘得来的,所以那是一份”已经运行过的快照”而不是干净的汇编输出。作者坦承这一条他自己至今没有验证过。
反向工程格式的部分同样精彩:关卡地图没有文件头也没有尺寸,模型找到绘制例程、读懂网格遍历方式、从别处的常量推出宽高,五个关卡一次通过;再把提取的数据重新渲染,与 2020 年的整关截图逐像素比对,找出两处 copper 效果(天空渐变与水面色循环)后做到零像素差异。地图每个 cell 是一个 16 位字,高 6 位是”这块地做什么”的属性(1 是实地,2/3 可攀爬,10-13 是四个方向的伤害,14 秒杀,63 是门),低 8 位是贴图编号——这套编码从来没有写在任何文档里,是靠”绘制循环屏蔽低字节、碰撞检测屏蔽高位”这两段读同一个字的代码互相印证还原出来的。模型甚至把 1993 年那个 1,254 行的地图编辑器 MEDITOR.S 重新汇编、把关卡 2 的数据摆到内存里、在模拟器里启动——作者时隔 33 年第一次看到自己的工具运行。另一个被推翻的认知是门:作者坚信门是地图数据,实际上门从来没有画进地图,而是运行时由一条 18 字节的对象记录贴上去的。
也有翻车的地方。最典型的是 2 关的”守门人 bug”:原版对垂直距离做了上下两个边界的判断,移植版只保留了下界,结果一个站在你头顶 13 格、中间隔着实心岩石的持矛士兵能一路把你打到走廊里。其他还有:隐藏图层被”忠实”渲染导致所有秘密通道一开局就敞开;敌人在玩家之前更新导致蹦床跳被算两次、弹到二十格高;立体声/单声道算错导致音效播一半重来。最耐人寻味的一条是双胞胎任意距离换位功能——原来那个”距离检查”根本不是距离限制,闲置的那一个是被当作雕像直接盖印进地图的,两个雕像叠在一起会互相吃掉对方的贴图。
最后模型还接手了发行工作:十一种语言五种像素尺寸的商店截图(用游戏本身走位截图再叠上真实字体的字幕带,绝不让 AI 渲染文字)、预览视频、六种形状的图标、三家商店的上传。Steam 没有 API,就直接驱动浏览器填 Steamworks 表单(作者自己负责登录和一切”提交/发布/定价”按钮)。它还抓取了 Google Play 十五年的评论,筛出真正描述缺陷的那些。其中一条拼写糟糕的一星评论说”第一关的门开了但关卡不结束”——读进去发现是真 bug:开门和走出去是两个动作,而提示文案只放在 18 关中的 2 关,恰好漏掉了最后一个免费关卡。这个 bug 十五年、两次重做、所有测试者都没发现,最后靠一个陌生人的一星评论找出来,修复以 2.0.3 发布。
HN 评论精华
这条帖子拿到 364 分,作者 rabahs 全程在评论区回帖,讨论明显分成三股。
第一股是”这套方法能不能通用化”,几乎变成了一场复古游戏移植的经验交流会。hedgehog 建议让 Claude Code 导出一份移植工程指南,rabahs 回应说这可以做成一个通用的”游戏移植 skill”,并透露他后来直接把游戏的二进制文件丢给 Claude,同样能逆向出 Godot 版本——16KB 的 MSX ROM(他试了 TimePilot 和 Road Fighter)不到一小时就能一次性跑出可玩版本,再补几轮提示词修修精灵和音效就接近原版了。gmerc 列了一长串自己近几个月的成果:Robin Hood、Syndicate、Kaiser 2、Populous、Dragon Strike、Dungeon Keeper、Red Baron、Test Drive 3,甚至在把 Ultima 6 转成 3D,有的是源码移植有的是纯逆向。s-macke 补充了他的分步方法论:先给每个主题写 spec/ 下的 Markdown 结论文件,识别文件格式、提取并人工复核资产、判断可执行文件用了哪种壳(当年常见的就那五种)、手工脱壳、用老版免费 IDA Pro 或改过的 DOSBox 定位代码/数据块与段寄存器值,再按功能域拆分反汇编文件。Gamemaster1379 则展示了另一条路线——做可复用的主机移植框架(NES 6502、SNES、GBA、NDS、PlayStation、Sega Genesis),并把 68k 解码器单独抽成仓库以便复用到 Philips CD-i。他提到 PlayStation 是社区最活跃的、SNES 第二,而 NES 和 Genesis 几乎无人问津。mattjoyce 说他把一个 ZX81 的内存转储丢给 Claude 要求用 Go 重建,成功了,并感叹”先亲历个人计算的起点,再在拐点上看着 AI 把那段经历当考古”是件很疯狂的事。
第二股是关于 AI 写作痕迹的争论。 mattbruv 直言文章明显的 AI 文风让他读不下去,认为真正热爱项目就该用自己的话写。khazhoux 反驳说这个故事很可能不借 AI 就根本不会被写出来;rabahs 承认这一点完全成立,但强调 AI 是基于他自己的笔记起草,他花了一周逐行编辑,AI 最大的帮助其实是定位正确的源码片段和截图。值得注意的是版主 dang 亲自出面,说他已经私下就此给作者发过邮件,但”这个故事太好了,不值得因为这个卡住”。他还顺带解释了 HN 现在面对的灰色地带:大量非母语用户来信问为什么帖子被杀,官方的建议是”用自己的声音写,别怕犯错,语法错误正在快速变成真实性的标志”,因为翻译工具本身就在文本里留下大量 LLM 痕迹。IshKebab 指出他的”呕吐感”具体触发在”The test was deliberate”“the safe ask”“the unfair ask”这几句上。
第三股是最尖锐的质疑。 lstodd 语气很冲,认为有原盘就该跑 UAE,”移植到 Godot”毫无意义,还暗示这不过是”AI 重写了我 93 年的破 BASIC 游戏”。rabahs 平静地回应他已经把 Amiga 原版 ADF 免费发布给 Amiga 社区了,AI 还帮忙修了几个 bug,这篇文章讲的是他跟 90 年代汇编代码打交道的体验。cstrahan 补了一句很到位的话:选项只有”借 AI 之力重制”和”根本不重制”,原版依然免费可玩,这不是零和游戏,看不出负面情绪从何而来。tomcam 和 mickeyp 都称赞了作者的应对。
技术追问方面,coder-pm 追问有没有把移植版和 UAE 原版并排跑同样输入做对比,rabahs 承认没有,只靠自己上千小时的游玩手感判断,并认为”不用 Godot 物理引擎、原样搬运”这个决定是手感对得上的关键。Lerc 从汇编器角度解释了那 108 字节:不少指令有功能等价的多种编码,汇编器的归一化阶段可能生成同样大小、同样行为但编码不同的指令;coder-pm 立刻指出这反而说明字节比对证明不了什么,行为验证才是正道。Lerc 还提到 YouTube 上 Amiga 移植 Sonic 的做法是直接跑原版游戏逻辑只重写显示层,rabahs 表示会在 Steam 发布前研究这个思路。Sharlin 好奇性能对比,得到的回答是两个版本都跑满各自平台的帧率上限(60fps vs 50fps),Sharlin 半开玩笑地补了一句”我很怀疑 Godot 版能塞进 512KB 内存”。snvzz 指出 1993 年那段关中断的代码其实有系统友好的写法(调用 exec.library 的 Disable()),rabahs 回应说他当年只有硬件手册那一本书。
关于”应该跟谁对话”的一条评论很有哲学意味:a2ff6eeb0 问既然是 Claude 做的移植、写的文章、而且比作者本人更懂现状,能不能直接跟 Claude 对话?rabahs 说他自己做事后分析时正是这么干的,那 108 字节的差异就是这么发现的(虽然他没完全理解那个解释)。rmunn 补充说这行不通的原因是:脱离了当时的上下文,你面对的只是另一个没有那段记忆的实例,答案不会有用;actionfromafar 再加一刀——模型对自己几乎没有自省能力。
情感层面最动人的几条:dannyobrien 说自己同期在 Atari ST 上写游戏,对前互联网时代资料极度匮乏下的这份坚持表达敬意,并追问调试故事。rabahs 的回答是他用 Amiga 的 copper 在垂直消隐期开头画一条白线,随着游戏对象和渲染逻辑增加,只要白线掉到活动显示区起点以下就意味着掉帧闪烁,于是他不得不把对象处理拆成批次分散到多帧执行。sgt 问 1993 年的巴格达是什么样,rabahs 说战后确实艰难,但真正打击他们的是经济制裁。btbuildem 说游戏有强烈的《Gods: Into the Wonderful》气质,rabahs 澄清真正的灵感来源是没人提到过的 MSX 游戏《The Maze of Galious》。