Codex 编码、Claude 审核的网页游戏实践
作者以网页小游戏为试验,让 Codex 编写代码、Claude 编排并复核结果,预先限定 TypeScript、Phaser 3、Vite 和测试工具。任务要求游戏逻辑可设种子、独立于浏览器测试,展示了用明确约束推进 AI 开发的实践。
AI 深度解读
527 个测试全部通过,一款手机赛车却依然没法玩。这是作者用 AI 做网页街机合集 Opencade 时,最值得程序员记住的反差。代码能构建、流程能跑通,与用户愿不愿意继续玩,中间还隔着一轮真实体验。
作者原本想研究 GTA V 怎么进了浏览器,最后却做起了泡泡龙、赛车和横版射击。这篇没有揭开 GTA 的技术秘密,但留下了更实用的经验:怎样把「不好用」「太重复」「应该没有追踪」变成可以验证的问题,以及验证之后仍有哪些问号。
AI 能很快做出游戏,但需求还得靠人玩出来
作者让 Codex 写代码,Claude 负责编排和复核,尤其不能直接相信 Codex 的「全部通过」。工单提前限定了技术栈:strict TypeScript、Phaser 3、Vite、Vitest、Playwright。每款游戏的引擎都必须是纯逻辑、支持随机种子,并且能脱离浏览器测试。
这个约束很关键。给定相同输入和种子,逻辑就应产生相同结果,方便复现问题,也方便让自动化程序接管操作。
第一款泡泡龙不到一小时就能运行。但搬到手机上,作者收到的评价是:像在邮票上玩游戏。接下来的修改没有增加玩法,而是让画布占满屏幕,把其他功能收进 ☰ 菜单,触控按钮排成拇指附近的一行。
这类问题,单靠「功能完成」很难发现。
随后作者要求增加游戏选择页,以及每款 30 关的街机游戏,灵感来自 Tetris、Arkanoid、Nokia 3210 的贪吃蛇等作品。成品采用原创名称、关卡和代码绘图,代码里不出现原作品牌。项目以 MIT 许可证公开,CI 包含 lint、单元测试、构建和端到端测试,演示部署在 GitHub Pages。
从第一张工单到最后一张卡片,只过去两天半,待办编号增加了 40 个,其中 33 个与游戏有关;版本从 0.1.0 走到 0.3.2,共发布五次。首页的游戏预览还直接由各自引擎渲染成动画。
视觉创作同样需要人给方向。作者先手绘,再用 ChatGPT 整理出八张角色参考图,交给 Codex 用 TypeScript 重绘;公开仓库不放位图。至于穿芭蕾裙的猴子该拖着尾巴还是彩珠项链,也由人来决定:消除彩珠,比消除一截截尾巴更合适。
速度有了,但产品判断并没有自动出现。
为什么 527 个绿灯,仍然挡不住一句「没法玩」
赛车 Dérapage 最能说明测试边界。作者先反馈手机操作不方便、草地减速不够、需要加速带和树木,后来又嫌赛道总是同一条。于是游戏增加了 30 条赛道,以及终点线、计时和最佳圈速。
0.3.1 发布时有 527 个通过的测试,包括 299 个单元测试和 228 个端到端测试。第二天清晨,作者的评价却是「没法玩」。
Claude 随后写了一个「模拟拇指」:每 150 ms 读取游戏状态,根据朝向误差转向。它知道完整状态,操作条件比普通玩家更好。结果在第一条赛道跑了 40 秒,只到达 gate 2,碰撞 121 次,32% 的时间在赛道外,还有 23 次采样处于停滞状态。
问题落在具体设计上:第一关就是狭窄街道,摄像机跟车旋转,转向需要不断换算,护栏还容易把车卡住。
0.3.2 改成绝对方向的 360° 摇杆,摄像机固定朝北,护栏把车推回赛道,第一关也换成宽阔路线。随后测试给每次操作加入 ±0.35 rad 随机噪声,模拟笨拙的手指。然而后续一次运行仍在一分半后卡在第一圈,作者因此没有宣布问题已经解决。
测试通过只能证明已检查的条件成立,不能替用户证明体验合格。
横版射击 Ferraille 的问题则是重复。Claude 为十二款游戏构造了一个 0 到 100 的重复度指标,把相邻关卡过于相似、缺少新元素、连续没有新内容分别计入:
repetitiveness = 100 × [ 0.50 × share of clones
+ 0.25 × (1 − novelties / (n − 1))
+ 0.25 × longest stretch without novelty / (n − 1) ]
这里要看的是 0.50、0.25、0.25 三项权重:它们体现作者采用的评价规则,不是经过验证的行业标准。单纯调大数值,也不算出现新元素。
Ferraille 得分 84.5,只有四种新内容,而且都在第 7 关之前出现;迷宫游戏甚至达到 100。之后 Ferraille 增加了六种敌人行为、三种武器、车厢、人质、守卫和三阶段反应堆。但原文没有给出改版后的分数,也没有玩家对照数据,因此只能确认内容增加,不能确认无聊感已经消失。
离线和隐私承诺,怎样从工单变成证据
0.2.0 把 Opencade 做成可安装的 Web 应用。service worker 预缓存整个构建产物,包含 Phaser;存档写入 localStorage,安装后不需要继续联网取资源。
作者也遇到了缓存带来的疑惑:手机上是不是还在运行旧版?0.3.2 因而在首页显示版本和构建日期,启动时、每隔 30 分钟、应用回到前台时检查更新,并在菜单里加入「Force update」。
离线可用还留着一个缺口:作者尚未亲自拿手机开飞行模式验证,目前主要证据来自端到端测试。
「没有广告和追踪」也经历了类似的补证过程。作者承认自己没写代码,工单上的「不请求第三方」不能直接当成事实,于是让 Claude 分三层检查。
源码层面,公开仓库 src/ 只有一个 fetch,用于请求同源 version.json;没有自行发出的 XHR、sendBeacon、WebSocket,也没有 cookie。唯一生产依赖是 Phaser,连带一个小型事件库;字体使用 system-ui。
构建产物里虽然出现了 phaser.io 和 Workbox 警告中的 bit.ly,但出现网址与实际发请求是两回事。最后还得观察浏览器行为:
ctx.on('request', r => reqs.push({ url: r.url(), sw: !!r.serviceWorker() }));
// ... play Dérapage, Alunir, Orbite, then ctx.setOffline(true) and reload
const third = reqs.filter(r => new URL(r.url).origin !== origin);
要注意第一行的 r.serviceWorker():记录覆盖 service worker 发出的请求,第三行再按 origin 筛出第三方请求。
这次测试在小型 VPS 的 Playwright 容器里模拟手机,操作三款游戏后断网重载。结果共 30 次请求,全部指向 Pages 同源,涉及 11 个不同文件;离线仍能看到十二款游戏。存储里只有一条登月游戏的 localStorage 记录 {"unlocked":1,"best":0},没有 cookie、没有 IndexedDB,只有应用自己的一个缓存。
证据也有边界:只检查了 Chromium 和三款游戏;GitHub Pages 会为安全目的记录访客 IP;页面没有 Content-Security-Policy,未来提交仍可能加入第三方请求。
我的判断
我更认可这套方法里「把抱怨变成可观察现象」的部分。赛车难开,就记录碰撞、离轨和停滞;关卡重复,就检查新元素出现的位置;怀疑有追踪,就检查实际网络请求。对前端开发而言,这比继续堆功能更有参考价值。
但别把两天半的成果当成通用生产率承诺。作者持续试玩、拆工单,项目又是可以反复修改的个人街机合集。原文没有提供人工开发对照、模型配置、完整成本或维护投入,无法据此推算一般团队能节省多少时间。
相比只做单元测试,模拟玩家能发现操作与关卡之间的问题;相比真人试玩,它又拥有完美状态信息,行为模型也很简单。两者适合互补。重复度公式同样是项目自定义启发式,适合筛查候选问题,不能替代体验判断。
这篇最有价值的经验,是让 AI 帮忙追查问题,而不是让它替自己的交付盖章。
能拿来做什么
- 做 Phaser 或 Canvas 小游戏时,可以把规则、状态变化和随机种子放进独立逻辑层,再用浏览器承接渲染与输入,让失败能够稳定复现。
- 给 AI 提体验问题时,先描述具体阻碍,例如拇指够不到按钮、第一关频繁撞墙,再要求它补充观测数据,避免只收到一句「已经优化」。
- 开发离线 Web 应用时,把版本号、构建日期和更新入口一起设计进去,并实际验证手机飞行模式下的启动、游玩与存档。
- 审查 AI 生成项目的网络行为时,同时检查源码、构建产物和包含 service worker 的请求记录;把本次验证范围写清楚,后续版本继续复查。