刷屏AI公众号文章

一个 Claude Code 小游戏的仓库里有 396 条决策记录

开发者 Edwin Vermeer 用 Claude Code 做了一款立体四子棋,上架后几乎没人下载。他索性公开了全部 prompt 和一份逐条编号的决策日志,里面记着测试悄悄挂住、检查读错文件这类坑。

刷屏AI

公众号 · 21 篇文章

开发者 Edwin Vermeer 用 Claude Code 做了一款立体四子棋,上架后几乎没人下载。他索性公开了全部 prompt 和一份逐条编号的决策日志,里面记着测试悄悄挂住、检查读错文件这类坑。

Edwin Vermeer 用 Claude Code 做了一款手机游戏,订的是每月 €15 的 Pro 计划,前后 8 个工作日,摊在 4 周里。游戏叫 Quadrim,在 4×4×4 的立方体里下四子棋,64 个格子、76 条能连成线的路,最强的电脑对手按 1980 年的一份数学证明走,先手必胜。

上了 App Store 之后几乎没人下载。他干脆把仓库公开当教程,10 月 13 日在 DEV 社区写了篇复盘,标题就说:决策日志才是这个项目最值钱的产出。日志一共 396 条,每条带编号。

我把仓库克隆下来翻了一遍。他发给 Claude Code 的 32 段 prompt 按顺序收在 workflow.md 里;决策记录分在三个文件,光前两个加起来就 600 多 KB,比很多项目的代码还长。翻代码时我数了一下,注释里引用这些编号的地方有 390 处左右。

Quadrim 项目数字卡片和一个 4×4×4 立方体

Quadrim 项目数字卡片和一个 4×4×4 立方体

✿ ✿ ✿

大半 prompt 都要求记决策

他的流程分六步,写在另一个仓库 CodingAgentOrchestration 里:

  1. 01
    澄清:把粗糙的想法交给 agent,让它复述目标、列出假设和未知项,一直问到没有歧义,这一步不写代码
  2. 02
    对每个开放问题想几种方案
  3. 03
    写带验收标准的 spec
  4. 04
    切成小工单,每张都能单独合并
  5. 05
    写代码之前,让架构师、安全工程师、产品经理三个专家 agent 审 spec 和工单
  6. 06
    一张工单写完跑一次 code review,提交,再做下一张

他在文章里说,每段 prompt 都带着一句:Add all decisions to the decisions file。我在 workflow.md 里对了一下,32 段里有 18 段提到要把决策写进日志,措辞不完全一样,意思都是这个。

这是我最想抄的一步。它不用你搭什么记忆系统,一句话而已:agent 每做一个不那么显然的选择,就往一个文件里追加一条,下次开会话它自己会读到上次踩过的坑。

实现阶段也不顺。workflow.md 里记着,Claude Code 中途停下来 5 次,他靠一段“继续按工单实现”的 prompt 让它接着跑,每张工单做完跑一遍 code-review-expert skill,审完直接提交到 main。

✿ ✿ ✿

一条决策长什么样

每条记录三部分:问题、决定、代价。他在文章里举了 D295:

问题:图片只指定了高度的话,在图片加载完之前,程序以为它的尺寸是 0。决定:所有图片外面套一个尺寸事先写死的框(Flutter 里叫 SizedBox)。代价:这张工单因此出了三个长得一模一样的 bug,New Game 按钮被排成了 316×0,压成一条线,点不到;logo 成了一条零宽的空隙。

写成这样,规则连同理由只说一次。之后每次开会话,agent 读到的是一条现成的事实,不用再踩一遍才想起来。

让这个日志好用的是两条约定:

  •  只增不改
    。某个决定被推翻时,旧条目加一行 superseded by,新条目写在旁边,走错的路一直留着
  •  编号不重来
    。第一版 D01 到 D217,第二版 D218 到 D391,现在从 D392 往后。代码注释直接引用编号,比如测试里写着 // D369,任何一行看着奇怪的代码都能顺着编号找到原因
决策条目的三段格式,以及代码注释通过编号指回日志

决策条目的三段格式,以及代码注释通过编号指回日志

编号这条我会直接用。很多人给 agent 写 CLAUDE.md,写着写着就变成一份越来越长的规则清单,没人知道哪条是为什么加的。编号加代码注释,等于给每条规则配了一个出处,看到一行怪代码,拿编号一搜就知道当初为什么这么写。

✿ ✿ ✿

测试挂住比测试失败难发现

他讲的第一条教训来自 D178。

有一次会话里,完整的检查脚本前后启动了 10 次,只有 1 次写出了退出那一行,而那一次在跑到引擎测试之前就失败了。连着 5 张工单,没人注意到检查其实从来没跑完过。

原因是一个测试要求电脑从一个还剩 58 个空格的局面开始“精确求解”,也就是把后面所有可能的走法穷举到底。这种算法不设步数上限,也不会中途放弃,等于让程序把整个游戏解出来。它不报错,也永远算不完。

找到它之前,日志里记了 3 个错误诊断。其中一个是怀疑某个函数逐节点分配内存,日志原话评价它“看着合理,没测过,错的”。

失败的测试会自己喊出来,挂住的测试不会,它看上去就和一个跑得慢的测试一样,你每次的直觉都是“再等等”。5 张工单没人发现,就是这么来的。

后来的修法有两条:

  •  
    每条长命令都加硬超时,仓库的 CLAUDE.md 里写的是 timeout 2400 bash tool/check.sh
  •  
    搜索代码里加断言,精确求解最多只接受 16 个空格

自己跑 agent 的人可以直接拿走第一条。Claude Code、Codex 这类工具跑测试时如果没有超时,agent 和你都会把“还在跑”当成一种正常状态。

✿ ✿ ✿

agent 的解释再顺也要跑一遍

游戏有 31 个电脑对手,强弱要排成一架梯子。这件事让他吃了不少苦头。

他说 AI agent 特别擅长给出一段听上去很有道理的解释,告诉你为什么对手 A 应该比 B 强。他花了一阵子才学会全都不信,原因是实测结果一次次跟解释对不上:

  •  偏好会转圈
    。有一级对手是按“喜欢先占哪些格子”来拉开强度的。实际对局里,角优先赢了中心优先,中心优先赢了只占中心,只占中心又赢了角优先,石头剪刀布。偏好只是风格,排不成梯子
  •  省时间的测法会把结论翻过来
    。为了快,校准时只给电脑十分之一的思考量。最强那一档里,Quartz 在这种条件下比更高一级的 Titan 强 83 分,给满思考量后反而弱 115 分;另一个对手 Flint 从强 95 分变成弱 241 分。它们的本事集中在每局少数几个很费算力的局面上,省掉的恰好是这部分
  •  一个坏参照会带偏一片
    。对手的评分是一环扣一环比出来的:拿邻居的分数,加上两者实测的差距。一个邻居量错,附近一串都跟着歪。他把多个估计取平均改成取中间值,一个离谱的数就带不偏整体(D341)

最后定下一条规则:任何“谁比谁强”的说法,都必须来自对战工具跑出来的结果,不能来自对代码的论证。整个项目里单次最大的提升也是这么测出来的:让强制搜索除了看自己的威胁,也去看对手的威胁,涨了大约 300 Elo(D169)。

左边是三种开局偏好的循环箭头,右边是两种预算下的分差柱

左边是三种开局偏好的循环箭头,右边是两种预算下的分差柱

✿ ✿ ✿

检查全绿的包里带着联网权限

这是我读得最久的一条,D356。

他对玩家的承诺是:和电脑下棋不联网。为了守住这条,检查脚本 check.sh 里有一条规则:Android 的权限清单(manifest)里不许出现联网权限 INTERNET。

后来加了应用内购买。按他的说法,Google 计费库往下三层的依赖里,有一个遥测组件 transport-backend-cct 自带了联网权限。第一个 release 包 app-release.aab 里就带着它,另外还多了网络状态等三项。

检查照样是绿的。它查的是他自己写的那份权限清单,而实际打进包里的,是构建时把所有依赖的清单合并后的另一份。有点像只核对自己写的购物单,没看收银小票。

日志还记下了一个中间的错误结论。第一次排查时,他只看了自己想到的两个依赖,没发现联网权限,就判定检查不用改。其实构建工具会生成一份合并报告,逐行写着每个权限是谁带进来的,一次 grep 就能回答。日志里的说法是:读产物,别读输入。

左边是 check.sh 检查的源 manifest,右边是合并后带 INTERNET 的 manifest

左边是 check.sh 检查的源 manifest,右边是合并后带 INTERNET 的 manifest

这个坑跟 AI 没什么关系,人写的检查也常这样。它跟 vibe coding 有关系的地方在于:agent 很愿意帮你写检查,也很愿意告诉你检查通过了。检查到底盯的是不是最后交到用户手里的那个东西,得有人问一句。

紧接着的 D357 把权限从合并结果里去掉,检查也改成读合并后的清单。后来这条检查又改过一次形状。加了排行榜之后,Play Games Services 必须联网,权限只能按整个应用申请,“完全不联网”守不住了。现在的 check.sh 改成:联网权限必须在自家清单里明确写出来,而且不能带移除指令。脚本注释里写:出问题的是权限悄悄混进来,权限存在本身现在是一个决定。

✿ ✿ ✿

哪些规则写成 grep,哪些故意不写

只写在文档里的规则,碰上某次会话没读到那份文档,就会被打破。所以重要的规则他都变成了 check.sh 里的检查:

规则
为什么要守
引擎包不许引入 Flutter、`dart:io`、时钟和全局随机数
同一个局面加同一个种子必须下出同一步,自我对弈才能复现
App 里必须能找到策略字典的署名
字典的授权要求署名,署名要出现在用户拿到的 App 里,放在仓库里不算
用户能看到的文字只能写在本地化文件里
防止某次改动直接写死英文

他的原话大意是:agent 打破检查的方式和人一样,忙的时候不小心;区别是检查第二天还在。

也有他故意没写成检查的。美术素材是一个手动跑的 Python 脚本转出来的,产物提交进仓库,但 check.sh 不比对字节。D290 解释了原因:同一张图换个 Pillow 版本重新编码,字节就不一样,这种检查会在库升级时报错,而其实什么都没坏。日志里的原话是,会喊狼来了的门禁,人会学着跳过。

他换了个办法:写一个测试,检查玩家真会碰到的问题,比如当时 20 个对手的头像在安装包里都能找到,素材总大小不超过 3 MB。规则写多了、误报多了,人就会找办法绕过检查,他宁可少写一条。

规则分成三类:写成 grep、写成测试、只留在文档

规则分成三类:写成 grep、写成测试、只留在文档

✿ ✿ ✿

把日志折回需求文档

决策攒到 391 条的时候,他让 agent 把所有决策折回最初的想法文档,把一个个踩坑故事改写成通用规则,得到 idea-v4.md。这份文件约 184 KB。

按他的说法,如果有人想从头重做这个游戏,就该拿这份。问题和答案一起到手,不用等几个月后自己踩出来。他给读者的建议也是:别从代码开始,从 idea-v4.md 开始跑一遍流程,然后对照日志,看你的 agent 做出的决定和他的有什么不同。

文章最后列了哪些事只能他自己来:

  •  
    范围和品味:游戏做成什么样,收费怎么设计
  •  
    说“去测一下”:上面几条教训,大多始于他不信一个听着合理的解释
  •  
    仓库外面的事:商店账号、签名密钥、App Store 审核、Play Console
  •  
    美术:设计用 Claude Design 出,再让 Microsoft Copilot 给四种完全不同的视觉方向,挑一种后由 Copilot 写每张图的 prompt,最后用 ChatGPT 出图

✿ ✿ ✿

想抄的话,最少做这四件事

看完这个仓库,我给自己的项目记了四件事,都不需要额外装工具:

  1. 01
    在仓库里建一个 decisions.md,格式照抄 D295 那样的三段,编号从 D01 开始,永不重排
  2. 02
    每段让 agent 干活的 prompt 末尾加一句,让它把这次做的决定追加进这个文件;推翻旧决定时只加 superseded by,不删原文
  3. 03
    选三五条最不能破的规则写进一个 check 脚本,检查读的是构建产物,比如合并后的 manifest、打出来的包,不读源文件
  4. 04
    所有长命令加超时,让挂住变成失败
夹板上的四项勾选清单和一个沙漏

夹板上的四项勾选清单和一个沙漏

这套做法的成本是文件会很长,600 多 KB,人读不完。好在你不用从头读,agent 每次开会话自己读;你只在某行代码看不懂时,拿着编号去查。Android 版还在等 Google 的 12 人封闭测试,仓库里的 idea-v4-decisions.md 已经记到了 D396。

前往微信阅读全文

内容来自公众号,可前往微信查看原文。

查看作者的更多文章 →订阅更新、RSS 与阅读指南 →