Hacker News 中文摘要

RSS订阅

用 LLM 解读 68000 汇编,把 1993 年 Amiga 游戏移植到 Godot -- Porting my 1993 Amiga game to Godot, with an LLM reading the 68000 assembly

1993年在巴格达,我在Amiga 500上制作了游戏Babylonian Twins:512KB内存、无硬盘、连接电视。当时我是二十多岁的工程系学生,用纯68000汇编语言手写每个精灵和扫描线。Murtadha Salman负责美术,Mahir AlSalman作曲。在制裁下没有互联网和游戏开发资源,只有一本Amiga硬件参考手册,我直接用它编程硬件,每天仅几小时供电。频繁的软盘交换和50°C的夏天三次损坏了我的磁盘驱动器。

正文

1993年,我在巴格达用Amiga 500做了一款叫《巴比伦双胞胎》的游戏:512KB内存,没有硬盘,接在电视上。我当时是二十多岁的工科学生。纯68000汇编,每个精灵和每条扫描线都是手写的。Murtadha Salman负责美术,Mahir AlSalman负责音乐。我们处于制裁之下。没有互联网,没有游戏开发资源,只有一本Amiga硬件参考手册,我直接用它对硬件编程,每天只有几小时供电。频繁的软盘交换(因为内存太小)和50°C的夏天让我的磁盘驱动器坏了三次。

在Amiga上,“手写”意味着游戏运行时完全不向操作系统请求任何东西。启动时保存中断向量,关闭操作系统中断,接管整台机器:

move.l #$dff000,a0 ;硬件寄存器基址 lea save(pc),a1 ;获取系统 move.w #$4000,intena(A0) ;从AMIGA手中接管

“从AMIGA手中接管”是我1993年写的注释。从那时起,显示画面由游戏自己的铜列表(Amiga的可编程视频协处理器)控制,实时重写以处理精灵和天空颜色。图块通过直接写入blitter寄存器并等待其完成标志来移动。摇杆直接从硬件端口读取,开火键是CIA芯片上的一个引脚。操作系统只在关卡之间回来,加载下一关的文件,然后再次被关闭。

这是伊拉克制作的第一款商业游戏,很长一段时间里很少有人能玩到。Commodore倒闭了,制裁吓跑了发行商,所以完成的游戏被搁置在架子上。2008年,一个Amiga论坛从我兄弟的YouTube上传中发现了它,并追着我要到了磁盘;那个帖子还在。

这款游戏之前被移植过一次,是2010年手工完成的。同一个团队从头开始编写了一个引擎,大约34,000行C++,花了几个月的夜晚和周末,将其移植到iPhone上。苹果和谷歌推荐了它,下载量超过了两百万。那个故事在这里。

我没有做这次移植。我提出了要求,每晚玩移植的结果,说出感觉不对的地方,并做了少数几个需要1993年当时在场的人才能做的决定。文件格式和汇编代码的解读是AI的工作,关于如何将三十年前的代码迁移过来的决定也是它做的,而且速度之快让我跟不上。这篇帖子是我几周后坐下来阅读自己的游戏被做了什么时发现的。有些地方是错的,而我几周都没注意到。

为什么我再次尝试

我以前试过。大约一年前,我给一个早期模型提供了同样的Amiga材料,让它理解我的二进制关卡地图。它最终做到了,但花了好几轮,而且我给了很多提示。

然后Claude Fable 5发布了,我给了它同样的文件。

这个测试是有意为之。我的猜测是,LLM训练集中几乎没有Amiga汇编代码。如果模型更擅长推理而不是回忆,那么这里就能体现出来。

独立日周末快到了,所以我计划了三个步骤,每一步都取决于上一步的成功。

第一步,安全的要求:我2010年的引擎,34,000行C++,移植到Godot 4。这是对照组。

第二步,不公平的要求:原始的72,758行68000汇编,针对一台已经停产、没有注释、与C++毫无共同点的机器。也在Godot中重建,以Amiga原始的50 Hz运行。

第三步,贪婪的要求:把第二个放进第一个里,这样购买现代游戏就能得到1993年的原版作为第二个可启动的内容。

三个都成功了。一年前花了好几轮和我的修正才搞定的关卡格式,这次一次通过,没有我的任何提示。

运行方式

我在Claude Code中运行它,所以它有一个终端和我的文件系统。它可以编辑文件、运行汇编器、构建游戏、启动游戏并读取返回结果。当我说它重建了我1993年的二进制文件并检查它们时,它确实是通过运行vasm并比较输出来完成的。

早期它给游戏添加了一组命令行标志,这样它就可以在没有我的情况下运行:

--level=<name> 直接加载关卡 --pose=<spec> 将双胞胎放在精确位置 --drive=<spec> 按脚本逐帧按下按钮 --probe 转储开关/门/钥匙状态 --screenshot=<path> 渲染一帧然后退出

这把“跳跃感觉对不对”变成了机器可以读取的东西:

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

从空项目到可玩角色用了21分钟。它当晚移动的每一行都是我2010年花了几个月写的。我困惑地上床睡觉了。

之后花了大约三天时间调整手感:跳跃弧线和蹦床时机,以及奖励连打的命中检测,在7月2日、3日和4日分批修复。

我不是一个人在测试。我十三岁的儿子和我一起玩每个版本。他一直知道我做这个游戏,这是他关于父亲的一个事实,但他从未见过我在这上面工作。测试变成了一件我计划外的父子活动,这是整个项目中最喜欢的部分之一。

相同的单位,相同的时钟

所有游戏状态都以图块为单位(1.0 = 一个48像素的图块),更新以固定的60 Hz运行,因为2010年的iOS版本以60 Hz运行。这很重要,因为原版每帧都乘法应用阻力:

static const float GROUND_DRAG_FACTOR = 0.85f; this->velocity.x *= GROUND_DRAG_FACTOR; // 每帧!

每秒乘以0.85六十次得到一种摩擦力;每秒乘以五十次得到另一种。移植到不同的时钟频率,游戏中的每个加速度曲线都会改变。不会崩溃,只是永远感觉不对,而且你无法通过阅读差异找到它。在60 Hz下,常量原封不动地移植。这也是为什么1993年的重建版以50 Hz运行,而现代版以60 Hz运行:两套手工调整的数字,每套只在各自的时钟频率下正确。它保留了两个时钟。我本来可能会想把它们统一成一个。

它没有使用CharacterBody2D

Godot自带CharacterBody2D和move_and_slide(),每个教程都告诉你用它们。移植版对玩家两者都没用。原版有自己的手写移动代码,在别人的物理系统上重建它会感觉有点不对,而且这种问题很难追踪。玩家是一个普通的Node2D,150行的碰撞检测例程逐行移植过来,包括我十五年前凭感觉挑选的修正数字和我写给未来自己的注释:

```

加0.5是因为我们希望角色的脚在方块中间。

var bottom := pos.y + dim.y / 2 + 0.5 + i + fraction if int(bottom) == int(pos.y + dim.y / 2 + 0.49): continue var right := pos.x var left := pos.x - dim.x / 4 # 非对称探测! ```

没有清理掉那个奇怪的0.49。没有测试,没有文档;那些注释就是规范。

第二步:68000汇编

到7月5日周日下午,我交出了我真正想测试的东西。72,758行,分布在26个文件中,为只有512 KB内存的机器编写,由我为我而写,注释习惯是那种从未期望别人会读的人。没有文档。2008年转移到现代存储时缩短了所有长文件名,所以每个包含指令都指向不再存在的名字。五个关卡源文件之一在一个数据表中间被截断了。没有其他副本。

在移植任何东西之前,它让1993年的源代码再次汇编,在Apple Silicon Mac上使用vasm,并一直进行直到输出与发布的二进制文件逐字节相同。

14:34 导入Amiga源代码、资源、参考文件 14:49 vasm工具链逐字节重现了发布的二进制文件 15:20 磁盘映像重建 15:42 重建的演示版在FS-UAE中启动并运行

从一堆文件到第一个匹配发布字节的重建版用了十五分钟。我当初是用ASM-One写的,它的方言与vasm在改变字节的方式上不同:ASM-One将cmp #4,d0编码为CMPI,vasm选择另一种同样有效的编码,所以告诉它不要优化是必要但不充分的。它没有编辑我的源代码,而是编写了一个预处理通道来桥接五个这样的差异,并逐个文件重建了损坏的文件名映射。

最麻烦的是org。没有链接器,没有重定位,关卡源代码手工布局Amiga的内存,地址接地址:

org $6a000 ; 这部分位于地址$6a000 Mapadd: incbin"btwins:binary/L1/Map1.b" ;游戏地图 org mapadd+73*1024 ; 跳过地图起始后73 KB GLBtable: dc.w $3333,50,20,100 ; 一个对象记录开始 dc.w SahamR-grb,26 ; 例程,长度 ... org glbtable+2*1024 ; 对象表正好2 KB

第一关的地图使用了74,400个字节中的74,752个,余量352,除了我1993年之外没人检查过。(SahamR-grb将对象的行为附加为命名偏移;saham是阿拉伯语的箭。)ASM-One的org还可以向后移动位置计数器,而vasm不能。第一个解决方法搞错了一个情况:一个在回退块内的ds.b 800,ASM-One将其视为“跳过800字节”,被写成了800字节的零。文件中该点之后的所有内容,包括铜列表,都位于距离发布二进制文件应有位置944字节的地方。游戏汇编并启动了,但画出了错误的东西。

即使在那之后,一些块仍然不匹配,大约108个字节分散在变量区域。这些字节解释了发布文件来自哪里。ASM-One汇编到内存中,游戏通过将内存保存出来而写入磁盘,这是在游戏运行之后。所以发布文件是已经运行过的游戏的快照,而不是干净的汇编器输出。新的汇编在这些变量中有零,因为还没有设置它们;发布的磁盘有机器保存时它们持有的任何值。代码在读取它们之前写入它们,所以零是无害的。

当时我读了那行,继续前进,等待实际的游戏。我花了几周才意识到这是项目中最重要的事情,而且没有人要求过它。从那时起,关于这个游戏的每个声明都可以通过比较字节来解决。我自己不会这么做。我已经有了二进制文件,在十八年里,从源代码重建它们似乎从未值得花一个下午。

格式

对于每种格式,它都去读取字节的代码,然后逆向推导。关卡加载器是1,652行无注释的68000汇编,这就是为什么我总是用十六进制编辑器。

关卡

关卡是一个图块网格:一长串数字,每个数字表示“把图片47放在这里”,用我1993年私有的布局。这是早期模型和我一年前一起费力搞定的格式。

这是关卡构建所用的完整图块集,256个,每个16×16像素,用于第一关:

输入是一个压缩块内的数字列表,没有头部,没有尺寸。这次我没有解释任何东西。它找到了绘图例程,读取了网格的遍历方式,从文件中其他地方的常量推导出宽度和高度,并在第一次尝试中为所有五个关卡生成了正确的地图。

然后它从自己提取的数据中重新渲染每个关卡,并将结果与我2020年制作的全关卡截图逐像素比较。在不匹配的地方,它去寻找原因,发现了两个铜效果:天空渐变和水色循环。考虑到这两个效果后:五张全关卡图像,零个不同像素。仅第一关就有600个图块宽,9,600像素。

地图单元格属性

绘制关卡只是地图单元格功能的一半。每个单元格是一个16位字,图片是其中较小的一部分:

``` 一个地图单元格,16位:

位15..10 属性:这个方块做什么 (6位) 位8 使用两个图块库中的哪一个 (1位) 位7..0 绘制256个图块图片中的哪一个 (8位) ```

属性是关卡不可见的物理属性。1是实心地面。2和3可以攀爬。10到13都表示“这会造成伤害”,四个代码因为击退需要方向。14直接杀死。63是门。这些都没有在任何地方写下来。它是通过两个例程读取同一个字而恢复的,每个例程揭示了自己的一半:绘制循环屏蔽低位字节,碰撞检查做相反的事情:

move.w (a1),d6 ; 同一个单元格 and.w #$fc00,d6 ; 保留高6位 lsr.w #2,d6 lsr.w #8,d6 ; d6 = 属性,0..63 bsr cbCheck ; 2或3?可以攀爬 bsr Checkrmh ; 10..13?这会造成伤害,从哪一侧

Checkrmh将伤害情况交给一个叫rmhEnjury的标签,这是1993年的我拼错了“injury”。

这些位是在一个编辑器中绘制的。在游戏可以构建之前,我必须构建构建它的工具:MEDITOR.S,1,254行汇编,根据其自己的头部日期,用我1993年的英语写的:

; *********************************************************************** ; * 这个程序是在四天内写成的 * ; * 1993-2-8/7/6/5 * ; * 我制作它来帮助我为我的第一个严肃游戏制作地图 * ; * Game * ; ***********************************************************************

1993年2月的四天。用鼠标绘制图块,在面板的CURRENT FLAG计数器上选择一个属性编号,用PUT FLAG将其印到单元格上,标志视图标记每个携带所选编号的单元格。在写这篇帖子时,我让模型运行地图编辑器并截取屏幕截图。它用现代汇编器汇编了1993年的源代码,将发布的第二关数据布局在编辑器期望的内存位置,并在模拟器中启动了结果。

我自己的工具,三十三岁,编辑真实的第二关,标志视图开启。CURRENT FLAG显示0001,实心,你可以站立的土地被标记,而你穿过的装饰则没有。面板上写着1994:面板美术是编辑器加载的单独位图文件,幸存下来的副本是1993年2月代码之后的版本。

面板上的另一个名字Udai,是我在美索不达米亚软件公司的合伙人,我们当时这么称呼自己。他当时正在制作自己的游戏。我写了编辑器,为我们俩,但它的设计是我们一起制定的,这样同一个工具可以服务于两个游戏。他的游戏从未完成。

对象表

敌人不在地图中。世界一次存储一个屏幕,25个图块乘20个,每个屏幕有一个小型的对象表。我1993年的注释解释了标记:

``` ; $1111=这是一个屏幕,但里面什么都没有或(屏幕结束) ; $2222=这是一个对象,但不要绘制它(死亡)转到下一个 ; other=这是一个对象,绘制它并转到下一个

Scr0: dc.w $3333,50,23,17 ; 一个活对象:帧,然后是x,y dc.w hiddenwallR-lrb,20 ; 它的行为:一个例程,作为偏移 dc.w 0 dc.w 0 dc.w 7 dc.w 10 ; 只有那个例程能理解的参数 dc.w $3333,50,12,14 dc.w GreatTR-LRb,16,GkeyT-GTT,1 dc.w $1111 ; 这个屏幕结束 ```

一个敌人是一行字:一个标记,一个帧,它在屏幕内的位置,然后是它的行为。hiddenwallR-lrb是崩塌墙壁例程,作为从基础标签的偏移附加,和之前的射箭者一样的技巧。它后面的字是参数,含义由那个例程决定。文件中没有任何东西说明哪个字是哪个,所以它找到了每帧遍历这些表的例程,让它命名字段,然后将所有五个关卡中的每个对象转换为世界坐标,并与渲染的地图进行核对。

GAME.S

大多数数据文件用存储在文件内的密钥打乱其16字节头部,这是1993年用来阻止磁盘编辑器的技巧。零售加载器GAME.S根本没有解扰步骤。它将其解读为线索:GAME.S是在添加打乱之前编写的,所以它是一个更旧的文件。这个线索后来让它从同一个文件内的扇区映射中恢复了丢失的双磁盘零售版。

门不在地图中

我确信它们在。

加载关卡的图块地图,每个门应该在的地方都有洞,无论开或关,都没有门图块。一个18字节的对象记录在运行时将它们印到地图上,一个1×4图块的列,来自一个表:

关闭 $528 $53C $550 $564 ; 实心,挡住路 打开 $129 $13D $151 $165 ; 可通过——正好向右一列

地图数据说没有门。关卡代码说有。三十三年来,我会告诉你地图是真相的来源,门是地图数据,我永远不会去查。它同时持有这两个事实,找到了协调它们的例程,并得出了设计:门是由代码在运行时绘制的;它们从未在编辑器中绘制到地图中。这就是为什么对关卡数据的明显移植会产生一个门口满是天空的塔。

铜天空

在每个关卡中,颜色索引31是天空,图块美术从未绘制它。图块图集将其渲染为透明,在它后面,铜在选定的扫描线上重新绘制背景颜色以形成垂直渐变。渐变作为纯颜色列表存在于关卡源代码中。这是第二关的整个天空:

backgndcol: col1: dc.w $09FF,$09FF,$09FF,$09FF,$09EF,$09EF,$0ADF,$0ADF dc.w $0ACF,$0ACF,$0ABF,$0BBF,$0BBF,$0CBF,$0CBF,$0DCF dc.w $0DCF,$0ECF,$0DCF,$0DCF,$0CCF,$0CCF,$0CDF,$0CDF

顺着列表往下读,天空从浅蓝色变为地平线附近的暖色。

同样的24个字,渲染出来。左边是屏幕顶部。

第一次重建错过了它,关卡看起来还行。平淡,以一种我说不出的方式。像素比较拒绝变绿,渐变被加了回去。

不会变绿的差异:白色是第一次重建搞错的每个像素,铜的天空和水。

精灵表歧义

Amiga精灵表是平面的(五个独立的1位位平面,按平面主条带排列,加上一个透明度掩码),所有这些都是从绘制例程和org算术中推导出来的。形式为帧数 * 宽度 * 高度 * 2 * 5的表大小是歧义的:那个2可能意味着双倍宽度的帧,或者两个堆叠的行,每个朝向一行。两种解读都符合文件中的每个字节。它是两个朝向的行;那是我1993年的选择。

它标记了歧义并询问。

那是最后一个格式。从那里开始,1993年的游戏以与C++相同的方式进入Godot,行为在GDScript中以原始的50 Hz重写。

第三步:旧游戏在新游戏内部

贪婪的要求花了一个晚上,21:58到23:43。复古游戏作为访客运行,有自己的命名空间和场景宿主,引擎在进入时切换到50 Hz,退出时切换回60 Hz。这很繁琐,而且一次就完成了。我原本以为这个功能会花掉一周然后被砍掉。这就是Steam版本内置1993年游戏的原因。

你下载的游戏不包含Amiga代码。数据、压缩块、平面图形和音乐,在我的机器上由Python脚本解码一次,变成普通的PNG、WAV和JSON。行为(守卫如何巡逻,门何时打开)用引擎自己的语言重写。如果你想要真实的东西,那是这篇帖子末尾的免费磁盘映像和一个模拟器。

错误的地方

守卫错误

在第二关,你沿着一条走廊走。左边是瀑布,前面是石柱。屏幕上没有敌人,没有东西靠近,你受到了伤害。击中你的是一个持矛的士兵,站在你上方十三个图块处,在一个棕榈树旁的草地上,中间隔着实心岩石。

他是一个守门人。他推搡站在他脚边的人。在原版中,这个检查在两边都有围栏:

sub.w d1,d4 ; d4 = 到孩子的垂直距离 cmp.w #4,d4 bpl Sg.Far ; 4行或更多行以下?不关我的事 cmp.w #-2,d4 bmi SG.far ; 太远以上?也不关我的事

移植版保留了下限,但去掉了上限。本应覆盖守卫自己三行的推搡,现在贯穿了他下方整个地图列的高度,穿过地板,进入他并不出现的走廊。

小一些的错误:每个关卡都有一个原版从未渲染的第二图块层;它是门打开或假墙崩塌时揭示的隐藏美术。如果“忠实地”渲染它,每个秘密通道从一开始就敞开着。如果在玩家之前而不是之后移动敌人,蹦床跳跃会被计算两次,飞到二十个图块的高度。一个门列出"p1,p2,p3,p4",意思是所有四个棕榈树,被解读为一个名字奇怪的单一钥匙,教程出口从未打开。一个音效循环长度被计算为立体声,而音效是单声道的,导致每个音效中途被切断并重新开始。

代价最大的是我在1993年版本中要求的一个功能:让双胞胎在任何距离交换位置。接近检查被去掉了,雕像开始损坏。它回到例程中,得出了真正的答案,这不是我期望的:那个检查从来不是距离限制。空闲的双胞胎作为雕像被印在地图本身中,两个雕像印在彼此上面会吃掉对方的图块。守卫被放回去了,我在1993年版本上砍掉了这个功能。

我2010年自己也犯了同样的错误,慢慢地,花了几个月。

然后它发布了

然后它做了发布工作:五种像素尺寸、十一种语言的截图,一个预览视频,商店文本,六种形状的图标,上传到三个对一切意见不一的商店。我以前发布过这个游戏,所以我知道这部分要花多少个晚上。

截图来自游戏本身。它以每个商店的像素尺寸启动真实游戏,使用所需的语言,将角色走到选定的位置,拍照,然后用游戏自己的字体绘制标题栏。AI从不渲染商店图像中的文本。标题是真实的字体和真实的翻译字符串,否则图像不会发布。有一次它确实坏了,俄语和韩语标题变成了一排空框,我在上传前在输出文件夹中看到了。

我对Steam的抱怨是它的表单有很多字段,比苹果App Store和Google Play Console多得多。此外,它没有使元数据更新过程变得简单的API。对于iOS和Android,有合适的API,它使用了它们。Steam有一个网页仪表板,所以它驱动了浏览器:商店页面字段、成就、演示清单、美术上传,在Steamworks中点击。我负责登录,我按任何提交、发布、定价或发布的东西。它填写表单。

它也读我的评论。官方的Google Play API只给你最近七天,这对一个有十五年评论的游戏来说没用,所以它用公共爬虫拉取其余部分,一次一种语言。然后它读了所有评论,并列出了哪些描述了真正的缺陷。我批准了列表。

其中一个是Google Play上的一星评论,拼写很差,你会滚动过去的那种:

“cant get through door on level one. opens but level dowsnt end”

按字面意思理解,这是一个错误报告,而且它是正确的。在我的游戏中,打开出口和走进去是两个独立的动作,提示这一点存在于所有十一种语言中,但只放在了我十八个关卡中的两个里。缺少它的关卡之一是最后一个免费关卡。所以决定这个游戏是否值得付费的玩家站在一扇敞开的门前,不知道该怎么办,并得出结论游戏坏了。

十五年来我从未发现这一点,我的测试者或两次重建也没有。它需要一个陌生人的一星评论。修复作为2.0.3在两个商店发布。我保留了那条评论。

蹦床错误

“蹦床感觉太高了。”那是完整的报告,来自我,晚上玩构建版。常量检查过了:原版积分器的二十行模拟预测19.1个图块,构建版测量为19.5。物理是正确的。

是输入语义的问题。2010年的构建是事件驱动的,由于我们多年前为tvOS的一个怪癖而做的变通,按住跳跃键被读取为释放,直到你物理上再次按下。Godot轮询输入,并持续报告按住状态。重现那个意外使得高跳需要一次新鲜的、时机恰当的按压,这就是游戏在手机上玩的方式,也是我的手所期望的。

2010年的源代码没有记录这一点,因为从源代码的角度来看,没有发生任何不寻常的事情。你必须当时在场,拿着手机,解决电视上的一个错误。

原版发布

三十三年后,完整的原版在itch.io上免费发布。在FS-UAE、WinUAE或真实硬件上启动它。Definitive Edition现在在iOS和Android上,Steam上有免费演示,完整的Steam版本(Windows、Mac、Linux)今年秋天发布,其中包含1993年的游戏作为第二个启动选项。

移植是Claude Fable 5在Claude Code中完成的;我要求、玩并决定。这篇帖子也是同样的方式。我给了它我关于移植的笔记,我对旧游戏关键部分的记忆(地图编码,对象表),以及Amiga版本和移植版的仓库,它写了初稿。我花了一周逐行编辑。代码、时间戳和截图是真实的。我最不确定的部分是那108个字节:模型告诉我发布文件是运行后保存的内存快照,并且代码在读取变量之前写入它们。我读了,继续前进,从未自己检查过。


《巴比伦双胞胎:Definitive Edition》今年秋天登陆Steam——在此加入愿望单。 免费演示现已可用 · 今天在iOS和Android上上线 · 1993年原版ADF在itch上免费提供。

评论

讨论围绕一篇用 Claude 将 1993 年 Amiga 游戏移植到 Godot 的文章展开,气氛两极分化:一方惊叹 AI 逆向工程的能力和成果的惊艳,另一方则质疑文章是 AI 代笔、移植缺乏验证,甚至认为直接跑模拟器更有意义。

对 AI 代笔和作者角色的反感

  • 文章明显的 AI 写作风格让读者难以投入,认为如果作者真的热爱项目,应该用自己的语言表达。(较多人) > 文章里那股明显的 AI 腔调让我读不下去,我不是反对用 AI 写代码,但如果你真的对这个项目有热情,想分享给别人,就该用自己的话来说。(@mattbruv)
  • 既然 Claude 完成了移植、写了文章,甚至比作者更懂代码,那读者为什么不直接问 Claude?作者的角色被弱化为“按按钮的人”。(少数人)

对移植结果和验证方式的质疑

  • 仅凭“玩起来感觉对”就声称移植成功不够严谨,应该用相同输入在原始模拟器上对比输出,文中提到的 108 字节差异令人困惑。(较多人)
  • 直接运行原始 Amiga 代码的模拟器(UAE)或 FPGA 实现才是正道,用 Godot 重写等于“AI 重写了我 93 年的垃圾 GWBASIC 游戏”,会被人嘲笑。(少数人)
  • 关心新移植版的性能和内存效率,想知道相比原始 68k 汇编版本差了多少倍。(少数人)

对 AI 逆向工程能力的惊叹与怀旧

  • 看到 AI 能处理 68k 汇编、理解 90 年代非标准且充满 hack 的硬件编程,感到既震撼又欣慰——这些“计算机考古”领域原本被认为对 AI 极难。(高赞)
  • 在 512KB 内存、汇编语言下写出如此精美的画面,本身就是一件了不起的作品。(高赞)
  • 有人用类似方法将 ZX81 游戏转成 Go,感叹自己经历了个人计算黎明和 AI 考古两个时代。(较多人)

有价值的补充

  • 有评论指出 68k 汇编其实比 x86 汇编更易读,这解释了为什么 LLM 能相对成功地处理它。
  • 有人提到 Tim Follin 在 SNES 上为《Plock》做音频时,任天堂的宫本茂都震惊于音效的真实性——以此说明过去开发者对硬件的“非预期使用”有多疯狂,而 AI 现在居然能逆向这些技巧。