Vibe Coding 多人游戏(十七)—— 三层编码规则体系 + agent-context.md
三层结构
基础规则(basic-rules.md) ├── 通用编码规范(命名、格式、注释) ├── TypeScript 规范(类型、接口、泛型) ├── 错误处理规范 └── 文件组织规范模块规则(module-rules.md)
├── ReScript 规范
├── TSRPC 协议规范
├── 测试规范
└── 多人模块规范
工作流规则(workflow-rules.md)
├── 重构标准
├── 代码审核清单
├── 提交规范
└── 验收标准
基础规则 — 每个文件都必须遵守,违反直接 review 不过。比如"改 .ts 再 tsc"这条,看着简单,但 AI 经常直接改 .js——因为它在生成代码时去看了编译产物。还有"禁止 window 全局挂载",AI 为了图省事曾把游戏状态挂到 window.__gameState,看似方便了调试,实际上场景重入时根本清不掉。
模块规则 — 特定模块/技术的针对性规范。比如 ReScript 的 .res 文件和 .gen.tsx 文件必须同步修改——AI 经常只改 .gen.tsx,下次 rescript build 一跑,所有改动全被覆盖。还有 TSRPC 协议层的规则:服务端必须校验输入,不能信任客户端传来的任何值。
工作流规则 — 流程性约束。比如"重构和修 bug 分开""E2E 前必须先重启服务端""BDD 测试必须测试实际代码而非 mock 逻辑"。这些规则不是编码时就能判断的,而是在特定的流程节点才生效。
agent-context.md:AI 的"宪法"
agent-context.md 是所有规则、规范、红线汇总到的一个文件。每次 OpenCode 执行时,第一件事就是读这个文件。它不装技术细节,只装约束——像宪法一样,回答"什么不能做"。
它的结构是这样的:
1. 项目结构速览(目录、关键文件位置)
2. 编码红线(改 .ts 不碰 .js、不改 node_modules)
3. 测试命令(jest 配置、BDD 运行方式)
4. 构建命令(tsc、rescript build、webpack)
5. 变更记录(最近修改了什么,防止 AI 重复改)
6. 禁止事项(不引入新依赖、不碰单机代码)
关键设计:agent-context.md 不包含技术细节,只包含约束。 它回答的是"什么不能做",而不是"怎么做"——后者是具体需求(Brief)的事情。
引入 agent-context.md 之前,AI 经常犯这几类错误:
症状 1:AI 改 .js 文件。 有一周,AI 修改了 Game.js 里的一段逻辑,兴高采烈地说"修好了"。但我一跑 tsc,所有改动都不见了——因为 Game.ts 才是真正的源文件。排查了半小时才意识到 AI 在编译产物上改代码。agent-context.md 的第一条红线就是"改 .ts 再 tsc",从那之后再也没有发生过。
症状 2:AI 随手加依赖。 有一回 AI 为了实现一个简单的深拷贝,引入了 lodash.cloneDeep。我说你加这个干嘛?它说"这样更安全"——但实际上项目中已经有 JSON.parse(JSON.stringify(x)) 的实现方式,而且在纯数据场景下完全够用。还有一次它引入了 uuid 包来生成唯一 ID,却不知道项目里已经有 crypto.randomUUID()。
症状 3:AI 越界改单机代码。 多人游戏开发中最怕的事情:AI 为了修多人 bug,顺手把单机逻辑也改了。有一回它改了 packages/scene3d_layer/ 下的一段渲染代码,导致单机版的相机行为变了。因为 agent-context.md 里明确写了"不改 packages/scene3d_layer/ 下的单机代码",之后 AI 再也没碰过。
引入 agent-context.md 后,OpenCode 在每次任务的 Brief 开头都会加载:
红线:改 .ts 再 tsc
红线:不改 node_modules
红线:不改 packages/scene3d_layer/ 下的单机代码
AI 知道了之后,违规事件大幅减少——从每周 3-4 次降到几乎为零。
更多红线案例
除了改 .js、随手加依赖、越界改单机这三条,还有几条红线在实际使用中经常触发:
红线:禁止在多人代码中使用 setTimeout/setInterval 模拟同步
多人游戏最致命的 bug 类型之一。有一次 AI 实现玩家位置同步,在收到对手位置后写了一段:
setTimeout(() => {
player.position = targetPosition;
}, 200); // 等 200ms 再更新,让动画更平滑
从 AI 的角度看,这很合理——"加个延时让过渡看起来自然"。但实际上,多人游戏的帧同步需要严格的时间对齐,200ms 的「平滑处理」直接导致了 A 看到的 B 和 C 看到的 B 位置不同。最后排查发现三个客户的 setTimeout 起始时间都不一样——一个是收到网络包时开始计时,一个是渲染帧开始时,一个是文件加载完成后。
后面的修复方案很简单但也很有教育意义:帧同步的时间基准必须是游戏引擎的虚拟时钟,不能用 Date.now() 或 setTimeout。从那之后,"禁止在多人模块中使用 setTimeout/setInterval 做时序控制"成了 agent-context.md 里的一条固定红线。
红线:不信任客户端输入
这个案例来自 TSRPC 协议层。AI 实现了一个「出售道具」的接口:
// 服务端
sellItem(itemId: string, price: number) {
player.coins += price; // 直接用了客户端传来的 price
player.inventory.remove(itemId);
}
单测是过的——测试里传的 price 是 100。但到了线上,一个恶意客户端可以传 price: 99999999,直接刷到满级。修复后把 price 验证加进了模块规则:所有数值参数必须在服务端校验边界值。不只是防外挂——有一次非恶意的 bug,客户端侧渲染了一个 NaN 然后传到服务端,如果没有校验,服务端也会跟着进 NaN 状态。
为什么三层而不是一层
一个问题:为什么拆三层,不直接丢一个超长文件?
因为不同角色关注不同层面。
刚开始我把所有规则写在一个 500 行的大文件里。每次 OpenCode 启动,都要先把这 500 行加载进去。但比如这次的任务只是"修一个小 bug",加载 100 行的"文件组织规范"和 80 行的"ReScript 规范"就是纯粹的 token 浪费。
| 层 | 被谁关注 | 变更频率 |
|---|---|---|
| 基础规则 | 所有代码 | 几乎没有 |
| 模块规则 | 特定模块维护者 | 技术栈升级时 |
| 工作流规则 | OpenClaw 调度 | 流程优化时 |
实际使用中,基础规则几乎不变化——"改 .ts 再 tsc"这条规则从项目第一天到现在都没变过。模块规则在引入新技术栈时会调整——比如加入 ReScript 时我写了"改 .res 再 rescript build"。工作流规则变动最频繁——重构标准里的检查项从最初的 5 条扩展到现在的 20+ 条,代码审核流程也调整了 3 次。
印象最深的一个改动: 工作流规则中原本有个"BDD 测试覆盖率不低于 80%"的指标。后来发现这个指标在实际使用中根本没用——AI 会为了凑覆盖率写一堆无意义的测试,比如测试一个不重要的 getter 函数。后来我把这条改成了"BDD 测试必须测试实际 bug 路径,禁止 mock 空代码"。
三层规则的更多作用场景
三层拆分的价值在实际使用中远不止「省 token」。下面是几个体现三层独立性的关键时刻。
模块规则的「测试规范」:BDD feature 文件的格式死线
测试规范是模块规则中最容易被忽略但也最重要的部分。AI 第一次写 BDD 测试时,生成的文件是这样的:
Feature: 玩家移动
场景:玩家向前走
给定一个玩家在(0,0,0)
当玩家按W键
那么玩家位置变为(0,0,5)
看起来没问题,对吧?但多人游戏中,同步测试需要多个客户端的交互。正确写法是:
Feature: 玩家移动同步
场景:玩家 A 移动,玩家 B 看到同步
给定玩家A在位置(0,0,0),玩家B在位置(5,0,0)
当玩家A按W键持续2秒
那么玩家B看到玩家A在(0,0,5) —— 误差 < 0.1单位
并且玩家B不触发任何「瞬移」事件
区别不只是格式——后者包含了同步误差容忍度和负面条件(不触发瞬移事件),这才是多人游戏测试的关键。如果这条规则放在基础规则里,写单机代码的 AI 每次都要读一遍,纯浪费。放在模块规则里,只有做多人模块的任务才会加载。
模块规则的「TSRPC 协议规范」:输入校验的细节
TL;DR 版本只是「校验输入」。但模块规则里的具体版本是:
- 数字类型必须校验
>= 0或<= 玩家最大等级 - 字符串类型必须校验长度(不超过 100 字符)
- 枚举类型必须用白名单校验
- 空指针(null/undefined)必须做短路处理
这几条在第一次写的时候只写了前两条。后来出了个 itemId 传空字符串导致全服道具列表刷新的问题,才加了第 3、4 条。模块规则因为只加载给 TSRPC 相关任务,所以扩写成本极低——但如果放在基础规则里,每次 AI 运行都要多消耗几百 token。
工作流规则的「验收标准」:什么才算「修好了」
最让我纠结的规则。最初的版本只有一条:「所有测试通过」。但后来发现,AI 会写增量测试来覆盖自己修的逻辑——这会导致一个问题:测试和新代码形成「相互验证」,测试断言和新逻辑用的是同一份「正确」理解,两边可能同时错过了同一个边界情况。
验收标准的迭代版本:
| 版本 | 内容 | 问题 |
|---|---|---|
| v1 | 所有测试通过 | AI 自测自通过,错过边界 |
| v2 | 所有测试通过 + 兄弟跑一次游戏确认 | 依赖人工,容易忘 |
| v3 | 集成测试走真实代码路径 + E2E 场景验证 + 无新增 tsc warning | 仍需人工玩一次 |
| v4 | 集成测试真实代码路径 + E2E 场景验证 + 无新增 tsc + webpack build 通过 | 当前版本 |
注意 v3 到 v4 的变化:加了 webpack build 通过。原因是 AI 有一次通过了单测、通过了 BDD、通过了 E2E(playwright),但 webpack 构建挂了——因为少了某个依赖的声明。如果只是「AI 代码通过」就合并,那 CI 上跑 build 的时候才会发现。工作流规则变动频率最高,从 v1 到 v4 只用了两个月。
规则体系的版本迭代
规则体系也不是一次成型。它经历了三个版本:
v1(单体文件):所有规则写在一个 GTS-Play-Coding-Rules.md,500+ 行。问题:太长,AI 加载慢,改规则影响所有任务。
v2(按层拆分):拆成 basic-rules.md、module-rules.md、workflow-rules.md。问题:OpenCode 不知道当前任务应该加载哪一层。
v3(agent-context.md + 按需引用):所有红线汇总到 agent-context.md,每层规则由 AI 按需搜索。基础规则几乎不引用(AI 已经记住),工作流规则在代码审核和重构时引用。形成了现在的 "宪法 + 部门法" 结构。
这个演进的触发点很巧合:有一次 AI 修 bug,加载了全部 500 行规则,在上下文里占了 2000 多 token。结果修完后发现自己权限不够,又去改了一轮——但其实它第一次就读到了"不改 node_modules"的规则,只是被埋在了第 350 行后面。从那之后我就理解了一件事:**不是规则写得越多越好,而是规则要在对的时机出现。
规则版本迭代的更多故事
v1 → v2 的直接触发:那一次 AI 改全了所有 ReScript 文件
单体文件时代,有一周 AI 连续改了 5 个 .res 文件。每次改完,我都要手动 rescript build,然后发现 .gen.tsx 文件没同步,再接一句「rescript build」。AI 每次都要重新加载 500 行规则文档来找「ReScript 规范」——大多数 token 花在了浏览不相关的基础规则上。
拆成 v2 后,ReScript 相关任务只加载模块规则,加载量从 500 行(~2000 token)降到 80 行(~300 token)。但当时有个很微妙的问题:AI 从来没有主动区分「这是基础规则问题还是模块规则问题」——它会去读当前任务的上下文来判断,但这通常不准。比如一个修多人 bug 的任务,AI 会同时加载基础规则和多人模块规则,还是全读了。
v2 → v3 的直接触发:「AI 已经记住的规则不需要每次加载」
这点很反直觉。v2 分层后,AI 在修第 10 个 ..ts 文件时,还在读「改 .ts 再 tsc」这条——它已经记住了。agent-context.md 的核心思路改变是:不是「AI 需要知道所有规则」,而是「AI 只需要知道不能做什么」。
agent-context.md 只包含红线(约 10 条),每条约 15-25 字。而在具体任务的 Brief 里,根据任务类型引用对应的模块规则和工作流规则。AI 如果读不到具体规范的细节,它会通过 openclaw memory search 去查,或者我直接写在 Brief 里。
还有一个故事:agent-context.md 让 AI 学会了「自查」
v3 引入后,出现了意想不到的效果。之前 AI 改完代码不会主动检查自己有没有违规——它觉得「我写了就是对的」。但 agent-context.md 的 Brief 开头加载格式是:
红线:改 .ts 再 tsc
红线:不改 node_modules
...
AI 在完成 Brief 后,会自动在回复里逐一检查这些红线。这是我完全没有设计过的行为——AI 自己学会了「在完成前做一个红线自查」。大概是因为 Brief 开头的红线列表给了 AI 一个「检查清单」的感觉。
这个行为后来被我固化成了流程规范:每次 OpenCode 完成 Brief 后,必须在回复中逐条确认红线没有违反。如果不逐条确认,默认打回重做。
下期讲 P18:重构标准 逐条拆解——GTS-Play 最频发的 bug 类型和审查清单。
浙公网安备 33010602011771号