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 版本只是「校验输入」。但模块规则里的具体版本是:

  1. 数字类型必须校验 >= 0 或 <= 玩家最大等级
  2. 字符串类型必须校验长度(不超过 100 字符)
  3. 枚举类型必须用白名单校验
  4. 空指针(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 类型和审查清单。

下一篇:Vibe Coding 多人游戏(十八)—— 重构标准 逐条拆解

posted @ 2026-07-08 11:38  杨元超  阅读(36)  评论(0)    收藏  举报