跳到主内容
@wquguru
精选88Hacker News Best(web_list)技巧与观点

用LLM辅助将1993年Amiga游戏移植至Godot引擎的工程复盘

用LLM辅助将1993年Amiga游戏移植至Godot引擎

原文
发到 X
推荐理由

这是一篇极具参考价值的AI辅助逆向工程实战复盘,详细拆解了处理老旧汇编代码时的时钟同步、物理引擎取舍等硬核细节,适合从事游戏移植或AI辅助编码的同学阅读。

1993年,在巴格达,我在 Amiga 500 上制作了一款名为《Babylonian Twins》的游戏:512KB 内存,没有硬盘,连接着一台电视。当时我是一名二十多岁的工程学学生。完全使用 68000 汇编语言编写,每一个精灵和每一行扫描线都是手工绘制的。Murtadha Salman 负责美术,Mahir AlSalman 负责作曲。我们处于制裁之下。没有互联网,没有游戏开发资源,只有一本《Amiga Hardware Reference Manual》,我利用它直接对硬件进行编程,而且每天只有几个小时的电力供应。由于内存很小,频繁的软盘切换以及高达 50°C 的夏季高温让我的磁盘驱动器报废了三次。左图:1993年,在 Amiga 上;右图:2026年,同一个网关。在 Amiga 上,“手工”意味着游戏在运行时不向操作系统请求任何资源。启动时,它会保存中断向量,关闭操作系统的中断并接管整台机器:``` move.l #$dff000,a0 ;Base for hardware registers lea save(pc),a1 ;Get the system move.w #$4000,intena(A0) ;from the AMIGA ``` “从 AMIGA 获取系统”是我的注释,写于 1993 年。从此以后,显示部分由游戏自己的铜列表(Amiga 的可编程视频协处理器)处理,并为精灵和天空颜色实时重写。通过直接写入 blitter 寄存器并等待其完成标志来移动图块。摇杆直接从硬件端口读取,开火按钮是 CIA 芯片上的一个引脚。操作系统仅在关卡之间恢复运行,以便从磁盘加载下一关的文件,然后再次被关闭。这是伊拉克制作的第一款商业游戏,并且在很长一段时间里,很少有玩家能玩到这款游戏。Commodore 破产且制裁吓跑了发行商,导致成品游戏被搁置在架子上。2008 年,一个 Amiga 论坛从我哥哥上传到 YouTube 的视频中发现了它,并顺藤摸瓜找到了我要磁盘;该帖子至今仍在。这款游戏终于问世了。原始的 1993 年软盘在 itch.io 上免费提供,而本文所讨论的重制版——最终版(Definitive Edition)——现已登陆 iOS 和 Android,今年秋天也将登陆 Steam。这款游戏此前曾于 2010 年由人工移植过一次。同一团队从零开始编写引擎,耗时数月,利用无数个夜晚和周末,用大约 34,000 行 C++ 代码将其重建为 iPhone 版本。Apple 和 Google 都曾推荐过它,下载量超过两百万次。这个故事在这里。我没有参与这次移植。是我要求移植的,每晚都试玩成果,指出感觉不对的地方,并做出了那些需要身处 1993 年的人才能做出的少数决策。文件格式和程序集读取都是 AI 的工作成果,关于如何将三十年前的代码迁移过去的决策也是由 AI 做出的,而且速度比我跟得上。几周后我坐下来阅读对我自己的游戏所做的处理时,发现了这些内容。其中有些是错误的,而我花了几个星期才注意到。## 为什么我再次尝试我之前试过这个。大约一年前,我给一个较早的模型提供了相同的 Amiga 素材,并要求它理解我的二进制关卡地图。它最终做到了,但这经历了几轮过程以及我大量的提示。然后 Claude Fable 5 发布了,我把同样的文件给了它。这是一个故意的测试。我的猜测是,LLM 训练集中几乎没有 Amiga 汇编代码。如果模型在推理方面比记忆方面更出色,那么这里就是它能展现出来的地方。7月4日周末即将到来,所以我计划了三个步骤,每一步都以前一步的成功为前提。第一步,安全的请求:我自己的 2010 年引擎,34,000 行的 C++ 代码,被移植到 Godot 4 中。这是控制组。第二步,不公平的请求:原始的 72,758 行 68000 汇编代码,针对一台已经停产的机器,几乎没有注释,与 C++ 也毫无共同之处。也在 Godot 中以 Amiga 原始的 50 Hz 频率重建它。第三步,贪婪的请求:将第二个嵌入第一个之中,这样购买现代游戏就能让你获得 1993 年的原版作为你可以启动的第二个东西。所有三个步骤都成功了。那个在一年前经过几轮过程和我的纠正才完成的关卡格式,现在一次性就出来了,没有任何来自我的提示。## 它是如何运行的我在 Claude Code 中运行它,因此它有终端和我的文件系统。它可以编辑文件、运行汇编器、构建游戏、启动游戏并读取返回的内容。当我在下面说它重建了我的 1993 年二进制文件并检查它们时,它是通过运行 vasm 并对输出进行差异比较来完成的。早期,它为游戏添加了一组命令行标志,以便在没有我的情况下也能运行:``` --level= 直接加载一个关卡 --pose= 将双胞胎置于精确位置 --drive= 逐帧在脚本中按下按钮 --probe 转储开关/门/钥匙状态 --screenshot= 渲染一帧并退出 ``` 这使得“跳跃手感是否正确”变成了机器可读的内容:``` drive[btw_jump:2.2] pos=(25.44, 24.04) vel=(0.00, -14.51) ground=false apex_y=22.48 ``` 它还具备两项无头检查功能,可在向我展示任何内容之前运行:一项是编译所有脚本,另一项是构建所有关卡并报告失败情况。在 Amiga 方面,它驱动了真正的工具链,使用 vasm 进行汇编,使用 FS-UAE 启动结果。未自动化的部分包括:现代移植版没有图像对比(它截取截图,我查看它们),也没有检查游戏手感是否合适。## 第一步:一晚编写 34,000 行 C++ 代码 周三晚上,稳妥的提议。时间戳,未经编辑:``` 22:23 Godot 4 项目脚手架、资产同步、TMX 关卡管线 22:44 两个双胞胎均可操作——碰撞、物理、相机、切换 23:19 所有 38 种实体类型已移植——完整对象列表上线 00:35 全屏流程——菜单、地图、故事、保存、游戏流程 02:15 导出至 macOS、iOS 和 Android ``` 从空项目到可玩角色仅用了二十一分钟。那天晚上它移动的每一行代码,都是我曾在 2010 年花费数月时间编写的。我带着困惑入睡。让游戏手感变得合适大约花了三天时间:跳跃弧线和弹跳时机,以及鼓励连击的命中检测,于 7 月 2 日、3 日和 4 日以批次方式修复。我不是独自测试。我十三岁的儿子与我一起试玩每一个版本。他一直都知道这是我做的游戏,这是他成长过程中关于他父亲的一个事实,但他从未见过我为此工作。这次测试变成了一件我没计划好的父子互动,这也是整个项目中我最喜欢的部分之一。## 相同的单位,相同的时钟周期 所有游戏状态都存在于瓦片单位中(1.0 = 一个 48px 瓦片),更新以固定的 60 Hz 运行,因为 2010 年的 iOS 版本以 60 Hz 运行。这很重要,因为原版每帧都应用乘法阻力:``` static const float GROUND_DRAG_FACTOR = 0.85f; this->velocity.x *= GROUND_DRAG_FACTOR; // 每个时钟周期! ``` 每秒乘以 0.85 六十次会得到一种摩擦力;每秒乘以五十次则会得到另一种。将其移植到不同的 tick rate(固定时间步长频率)后,游戏中所有的加速度曲线都会发生变化。程序不会崩溃,但手感永远不对,而且你无法通过阅读代码差异(diff)来找到问题所在。在 60 Hz 下,这些数值会原封不动地移植过来。这也是为什么 1993 年的重建版本运行在 50 Hz,而现代版本运行在 60 Hz:两套手工调优的参数,每一套都只在其对应的 tick rate 下正确。它保留了两个时钟。我本来可能会把它们整理成一个统一的时钟。## 它没有使用 CharacterBody2D Godot 中的飞船使用了 CharacterBody2D 和 move_and_slide(),每个教程都告诉你应该使用它们。这次移植并没有为玩家角色使用这两个组件。原版拥有自己手写的移动代码,如果在别人的物理系统上重建这段代码,会在难以追踪的细微之处产生违和感。玩家角色是一个普通的 Node2D,那 150 行的碰撞例程逐行移植了过来,包括我十五年前凭感觉选定的修正数值,以及我写给未来自己的注释:

更进一步:量化金融体系

看懂新闻只是起点——沿量化金融路径,把它变成能交付的工程能力

进入量化体系 →

相似阅读

关联信息,但可能不是同一事件