Vibe Coding 多人游戏(十七)—— 三层编码规则体系 + agent-context.md

三层结构

基础规则(basic-rules.md)
├── 通用编码规范(命名、格式、注释)
├── TypeScript 规范(类型、接口、泛型)
├── 错误处理规范
└── 文件组织规范

模块规则(module-rules.md)
├── ReScript 规范
├── TSRPC 协议规范
├── 测试规范
└── 多人模块规范

工作流规则(workflow-rules.md)
├── 重构标准
├── 代码审核清单
├── 提交规范
└── 验收标准

基础规则 — 每个文件都必须遵守,违反直接 review 不过。比如"改 .tstsc"这条,看着简单,但 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 的第一条红线就是"改 .tstsc",从那之后再也没有发生过。

症状 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 调度 流程优化时

实际使用中,基础规则几乎不变化——"改 .tstsc"这条规则从项目第一天到现在都没变过。模块规则在引入新技术栈时会调整——比如加入 ReScript 时我写了"改 .resrescript 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.mdmodule-rules.mdworkflow-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 文件时,还在读「改 .tstsc」这条——它已经记住了。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  杨元超  阅读(7)  评论(0)    收藏  举报