ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

守卫丢了一个边界,玩家隔墙挨打:Claude Code 移植 1993 年汇编游戏踩坑复盘

守卫丢了一个边界,玩家隔墙挨打:Claude Code 移植 1993 年汇编游戏踩坑复盘 来源说明热点来源Babylonian Twins 官网博客 -《Porting a 1993 Amiga Game to Godot》https://babyloniantwins.com/blog/porting-a-1993-amiga-game-to-godot开发者 Rabah Shihab 用 Claude CodeClaude Fable 5移植自己 1993 年在 Amiga 500 上写的游戏 Babylonian Twins3.4 万行 C2010 年 iPhone 版引擎一个晚上迁进 Godot 47.2 万行无注释的 68000 汇编先用 vasm 重建出与发售版字节一致的二进制再移植最后把 1993 原版作为第二启动项嵌进新版。这看起来是个「AI 编程很能打」的故事但作者复盘里真正值钱的是另一面那些代码能编译、游戏能启动、画面却悄悄错掉的地方。这篇按踩坑复盘来写每个坑的现象、排查与根因以及作者怎么用「字节一致」给 AI Agent 的每一步兜底。一、事件72,758 行无注释汇编的移植任务移植对象是 1993 年发售的 Amiga 游戏1993 年的代码没有文档、没有注释26 个文件共 72,758 行 68000 汇编跑在只有 512 KB 内存的 Amiga 500 上。作者把它拆成三步递进每一步以前一步成功为前提步骤内容结果134,000 行 C2010 iPhone 版引擎迁入 Godot 4一个晚上完成主体空项目到可玩角色仅 21 分钟272,758 行原版 68000 汇编重建出字节一致二进制首次逐字节复现发售版只用了约 15 分钟3把 1993 原版作为「客座游戏」嵌入新版一个晚上完成Steam 版内置原版作为第二启动项关于手感空项目到双胞胎可玩只花了 21 分钟但跳跃弧线、蹦床时机、命中判定这类「手感」又调了两三天作者 13 岁的儿子参与每个版本的测试。第 1 步没使用 Godot 的 CharacterBody2D 和 move_and_slide()玩家是普通 Node2D约 150 行的碰撞例程逐行迁移、连原注释都保留地面摩擦常数 GROUND_DRAG_FACTOR 0.85f 每帧乘一次换掉 tick rate 就会改变整套加速曲线。二、验证主线先重建出字节一致的二进制第 2 步是整篇复盘的地基。作者导入源码后 15 分钟vasm 工具链就逐字节复现了发售版二进制——此后作者的每一个推断都能用「和原版二进制做字节比较」来验证。作者原话的意思是这是整个项目里最重要的一件事而他本人过去 18 年都没觉得值得花一个下午从源码重建二进制。这条基线建立得并不容易第一关就是汇编器方言差异原版用 ASM-One 编写重建用 vasm两套方言差异有 5 处例如 ASM-One 会把cmp #4,d0编码成 CMPI而 vasm 会选另一个同样合法但不同的编码——只告诉 vasm「别优化」是必要但不充分的作者的方案是让模型写一个预处理通道去桥接差异而不是修改原始源码——保持原版可字节复现的纯度。三、坑 1能汇编、能启动画面却是错的最贵的坑出在 org 指令上。原版无链接器、无重定位关卡源码手工按地址布局 Amiga 内存ASM-One 的 org 可以向后移动位置计数器而 vasm 不行。第一个 workaround 有一个案例出错重绕块里的ds.b 800被当成「写入 800 个零字节」而 ASM-One 的语义是「跳过 800 字节」——该文件后续所有内容包括 copper list因此偏离发售二进制整整 944 字节。结果很有迷惑性游戏能汇编、能启动但画错了。这种错误只有字节比较能抓出来「能启动」在这个阶段是个弱信号启动成功与内容正确之间隔着 944 字节的偏差。顺带的真相是约 108 字节的散落差异发售文件并不是干净的汇编输出而是游戏运行后把内存保存到磁盘的产物——ASM-One 汇编到内存、运行、再保存代码读取变量前总会先写入所以那些零是无害的。判断「差异要不要管」得先搞清楚文件是怎么来的。四、坑 2守卫丢了一个边界玩家隔墙挨打这是作者复盘里最严重的逻辑错误第 2 关走廊里玩家会莫名受击而那个持矛士兵站在 13 格开外、隔着实心岩石。他是「看门人」只会推搡站在自己脚下附近的人原版对垂直距离同时检查上下两个边界sub.w d1,d4 ; d4 目标相对守卫的垂直距离 cmp.w #4,d4 ; 向下超过 4 行 bpl .out_of_range; 是太靠下不归守卫管 cmp.w #-2,d4 ; 向上超过 2 行 bmi .out_of_range; 是太靠上也不归守卫管 ; 落在范围内才执行推搡逻辑摘录自作者博客展示的原版逻辑注释为中文标号名按行文简化bpl 是「大于等于则跳转」bmi 是「小于则跳转」两条分支把判定范围收在上下各几行内。移植版保留下界、丢了上界——原来覆盖约 3 行的推搡被放大成覆盖整列地图高度攻击直接穿过地板打到走廊里的玩家。更糟的是这个 bug 几周后才被发现。这就是代码 Agent 迁移老代码时最典型的失败模式逻辑迁移会静默丢约束语法不错、编译能过行为却变了。五、坑 3蹦床「太高」——源码里查不出的输入语义玩家反馈蹦床太高。排查过程很标准物理常数检查无误20 行模拟预测弹到 19.1 格实测 19.5 格误差在可接受范围——那问题就不在物理。真正的根因在输入语义2010 版是事件驱动的当时为绕一个 tvOS bug 加的 workaround 让「按住跳跃键」被读成「已释放、直到再次物理按下」而 Godot 是轮询输入会持续报告按键处于按住状态。复现那个「意外」之后高弹需要重新、精确计时的按压——这恰恰是手机上的真实手感。关键是2010 年源码里完全没有记录这个 workaround。从源码视角看一切正常你得「当时在场、手里握着手机」才知道。源码不是全部事实——输入语义、运行时历史、发布流程这些隐性知识不会写进代码里。六、坑 4门不在图里——33 年的单一事实源幻觉作者 33 年来一直认为门是关卡地图数据的一部分。结果模型加载地图后发现每个门的位置都是空洞根本没有门 tile——18 字节的对象记录在运行时把门1×4 tile 的门柱列盖到地图上closed 用实心 tile 地址$528、$53C、$550、$564open 用可通行地址$129、$13D、$151、$165正好右移一个 sheet 列。模型同时持有两个互相矛盾的事实地图数据说没有门关卡代码说有门然后去找调和两者的例程最终还原出设计真相门由代码在运行时绘制从未在编辑器里被画进地图。这个案例的教训是单一事实源假设要能被打破——「地图数据是唯一真相」和「代码是唯一真相」都是危险的让 Agent 并行持有冲突事实去找调和代码比让它二选一更可靠。七、其它小坑速览坑现象根因隐藏第二 tile 层「忠实」渲染让所有秘密通道一开始就敞开着每关都有一层原版从不渲染的隐藏美术只在门开/假墙崩塌时露出敌人移动顺序蹦床跳跃被计两次弹到 20 格高敌人被移到玩家之前而非之后移动门被读成钥匙教学关出口打不开4 棵棕榈树p1、p2、p3、p4被读成名字奇怪的单把钥匙音效截断重播每个音效播到一半被掐断重来按立体声计算的循环长度实际是单声道精灵表歧义同一份数据有两种读法且都匹配每个字节模型没有猜而是主动提问确认是「每朝向一行的两行堆叠」而非镜像雕像互吃双胞胎近距离交换功能删掉后雕像损坏空闲的双胞胎以雕像形式盖在地图里两个雕像叠放会互相吃掉对方的 tile精灵表那个坑值得单独说Amiga 精灵表是平面格式帧数×宽×高×2×5 里那个 2 有歧义——可能是双倍宽帧也可能是两个堆叠行每个朝向一行。两种读法与文件的每个字节都吻合。模型的处理是标记歧义、主动提问而不是挑一个猜。作者点评这是全程唯一一次模型主动询问。八、一星评论挖出 15 年 bug代码之外的坑更隐蔽。作者用 Google Play 官方 API只有 7 天窗口加公开爬虫拉全部评论按语言读取、列出真实缺陷其中一条一星评论是“cant get through door on level one. opens but level dowsnt end”字面读这是 bug 报告而且是对的开门和走进去是两个动作而操作提示只存在于 18 关中的 2 关缺失的恰是最后一个免费关。这个 bug 在 15 年里逃过了作者、测试人员和两次重建最后以 2.0.3 修复作者把这条评论保留了下来。教训是用户报告要当测试用例读连拼写错误里的 bug 都别放过。九、怎么让 Agent「自证」作者搭的验证闭环这篇文章最值得抄的不是某个提示词而是作者把「手感对不对」这类主观指标转成机器可读输出的做法。他给移植版加了一批 CLI 旗标--levelname 直接加载某个关卡 --posespec 把双胞胎放到精确位置 --drivespec 按脚本逐帧按键 --probe 转储开关/门/钥匙状态 --screenshotpath 渲染一帧后退出「跳跃手感对不对」因此变成了可断言的输出例如drive[btw_jump:2.2]一行就带出位置、速度、是否落地、最高点高度。给作者展示之前还有两个无头检查自动先跑编译每个脚本构建每个关卡并报告失败。Amiga 一侧用真实工具链vasm 汇编 FS-UAE 启动结果没有自动化的是现代移植版的图像对比和手感检查截图由作者人工看。验证到底有多硬五张全关卡图与 2020 年截图的逐像素对比先是发现两处 copper 特效天空渐变、水色循环漏掉补上后达到零差异像素——第一关就有 600 tile 宽、9,600 像素。地图格式16 位格子高 6 位是属性、bit 8 选 tile bank、低 8 位选 tile 图没有任何文档模型靠读取两个例程对同一字的不同位恢复出来五个关卡一次通过——而一年前的旧模型需要多轮迭代和大量提示。作者的原话是他猜 LLM 训练集里几乎没有 Amiga 汇编——「如果模型擅长的是推理而不是回忆这里会显现出来」。十、给 Agent 开发学习者的启示先立 ground truth 再做逆向可字节比较的中间产物让 Agent 的每一步都能被机器验证「重建发售版二进制」就是整件事的锚把「能编译、能启动」当弱信号944 字节漂移的案例说明启动成功与内容正确是两回事验证要落在可比较的产物上逻辑迁移要防「静默丢约束」守卫 bug 不是语法错误而是边界丢失光靠编译器和 lint 抓不到要拿原版行为做对照测试drive 脚本 输出断言源码不是全部事实蹦床的输入语义、发售文件的快照身份都藏在源码之外——文档、历史、用户评论也是语料歧义要问不要猜精灵表两种读法都成立时主动提问比赌一个答案安全得多把主观指标先转成机器可读证据手感、画面、音效都通过旗标输出可断言结果Agent 才能「自证」而不是「自述」。信息边界以上数字与时间线全部来自作者博客自述代码、时间戳、截图作者声明为真实本文未独立复现作者在原文中也诚实标注了三点——文章由 Claude Fable 5 草拟、他花一周逐行编辑108 字节差异的解释他本人没有亲自验证过。文中涉及的模型与工具Claude Code、Claude Fable 5、Godot 4、vasm版本以原文为准未验证最新版本。回到守卫那个 bug它被几周后才发现恰恰说明「让 AI 写代码」的下半场不是生成而是验证。作者用字节比较、像素对比、机器可读的驱动脚本把每一步的信任成本降到了最低——这套思路比任何一次「AI 一次通过」的炫技都更值得移植到你自己的 Agent 工作流里。
返回列表