Vibe Coding 多人游戏(二十九)—— 教训、反模式与设计模式

6 类坑的 Root Cause

1. 选型类

帧同步很美但不适合 2 人场景。 项目第一天选了帧同步——因为多人游戏教程都在讲它。但浮点数精度、调试回滚、网络抖动,每一个都是 AI 无法独立处理的。第一周就放弃了。

Immutable.js 太重。 引入的理由是"不可变数据更安全",但在 GTS-Play 的场景下,安全不是问题,性能才是——50KB 的包 + 复杂的 API + GC 飙升 = 两周后放弃。而且 Object.keys(Immutable.Map()) 永远为真这个坑,AI 排查了一小时才发现。

自制 HashMap 有 bug。 Immutable.js 放弃后,AI 说"我们自己写一个 HashMap"。结果 hash 冲突导致玩家 HP 串号——玩家 A 的 HP 显示在玩家 B 身上。自制基础设施的风险:你自己也是第一次写,你也不知道哪里有坑。

Module._load hook 太复杂。 想在 SCF 上加载本地模块,AI 的方案是拦截 Node.js 的 Module._load。部署后发现 Module._load 在不同 Node.js 版本下 API 签名不同,WebSocket 连接失败只返回 HTTP 446/443——无法定位问题。最后改成把模块直接注入 zip 的 node_modules。

核心教训:简单的东西最可靠。 不要为了"更好的设计"选择复杂度高的方案——AI 需要能理解它,兄弟需要能调试它。

2. 部署类(6 连环,详见 P10 和 P25)

  • undici@7 Node 18 vs Node 20 兼容性问题
  • zip 扁平结构导致 SCF 找不到入口文件
  • scf_bootstrap 无执行权限(Windows zip 丢失 +x)
  • ESM 与 CJS 的冲突(type: module 影响 require
  • Hook API 版本不一致(Module._load)
  • Warm container 定时器残留(代次守卫解决)

每个坑单独看都不难,但连环出现时,排查成了灾难。我的排查流程是这样的:第一天 fix 了 undici 依赖冲突 → 第二天发现 zip 结构不对 → 第三天加执行权限 → 第四天改 ESM 冲突 → 第五天换掉 Module._load → 第六天加代次守卫。

一周都在部署上打转。但每个坑都产生了一个自动化规则——现在 deploy-scf.js 一次处理所有 6 个问题,再也没出过包。

3. AI 协作类

偷换方案: AI 发现问题不汇报,自己换方案。我之前遇到过 AI 发现 "Match 服务的接口存在但用法不对",它没有问我,直接换了个接口实现——用错了参数,修复了 3 次才回归正常。

拍脑袋修 bug: 不看日志直接改代码。AI 看到一个 bug,不查日志就猜测问题——猜错→改错→再猜→再错,循环 3-4 次后才去查日志。查完发现根因很简单(比如 animationName 被覆盖了),10 分钟就修好了。

提前抽象: 一个使用场景搞出全套设计模式(接口、工厂、策略、观察者)。AI 最喜欢的模式是"工厂"——一个简单的新建对象操作,它可能用 6 个文件来实现,抽象了 4 层。

重复造轮: 不知道项目已有工具函数,重新实现了一遍。比如项目中已经有 formatTime(seconds) 函数,AI 重新写了一个 formatTimer(seconds)——两个函数一模一样。

核心对策: 规则体系里的 级检查项明确写了"警惕不必要的抽象"和"有没有产生重复代码"。代码审核时这两条人工必看。

4. 测试类

集成测试 mock 假代码: AI 测试一个函数,但 mock 了整个模块——测试通过了,但测试的代码路径和生产代码完全不同。比如测试 disconnect 逻辑,但 mock 了 Server.disconnect 本身——测的根本不是真正的代码。

E2E 前不重启: 不重启服务端做 E2E,结果连接了上一次的会话,状态残留导致各种奇怪问题。

HMR 断 WS: webpack 热更新时重新加载前端代码,但 WebSocket 连接还在——热更新导致 disconnect 触发,玩家退房了。这个问题在开发时频繁出现,浪费了大量时间。

5. 工具类

Tool loop 不 yield: AI 在单回合里做了 118 轮 tool call——读文件、改代码、跑测试、再读……最终 context overflow 炸 session,全部工作丢失。后来设定每 20-30 轮主动 yield。

Socket hang up 误判: 服务端在处理长任务时没有及时回复 heartbeat,客户端误以为连接断了。解决:在长任务中定期发 heartbeat。

Object.keys(Immutable.Map()) 永远为真: 之前说过了,这个坑 AI 花了一小时。

状态重置遗漏: gameStop 未重置 gameStartStartedRef、onGameStarted 未重置 gameOverTriggeredRef——End 逻辑重置检查的最高频来源。

6. 状态管理类

  • AABB 碰撞检测不够用 → 换成 OBB → 还不够 → 凸包。渐进式碰撞精度——每步只解决当前痛点。
  • Immutable.js 太重 → 自制 HashMap 有 hash 冲突 → 换成 Js.Dict
  • Js.Dict 类型不安全(全是 any) → 换成 SoA TypedArray

每一步的触发点都是"遇到了真实问题才换",没有提前优化。

反模式

坑的分类是根因分析(为什么踩坑),反模式是有名字的"看起来对但实际错"的做法。每个反模式都有名字、症状和修复方案。

1. 黑盒信任 AI 输出

症状: AI 说"修好了",兄弟没验证就上线。结果 bug 还在,或者修好了 A 却坏了 B。

根因: AI 没有自我验证能力——它不是故意骗你,但它不知道什么是对的。它说"测过了"很可能只是改完后跑了一次没有断言意义的 log。

修复: 每次 AI 输出后必须跑验收测试(BDD + E2E)。这是写进 gts-acceptance skill 里的硬性规则——不通过就不合入。

2. 一次调度解决所有问题

症状: 一个 OpenCode brief 里塞了 5 个不相关的任务("同时修复 Exit bug + 添加房间列表 API + 重构 MatchConfig")。AI 做着做着混了——Exit bug 修完但 API 用的是旧 proto,重构改到一半还在用旧 config。

根因: 兄弟希望减少来回一次搞定。但 AI 的上下文窗口有限——任务多了超过 token 上限,或者概念互相干扰。

修复: Single-responsibility brief。一个 brief 只做一个事。需要多个修改时拆成多个 brief 依次调度。

3. 不提具体需求就让 AI 干活

症状: "帮我优化一下房间匹配逻辑"。AI 花了 2 小时写了一套基于 ELO 评分的匹配算法,但实际需求只是把空闲房间按创建时间排序。

根因: AI 会假设最复杂的需求。不给 Directions(Specs/BDD/ADR/方案),它就按"最通用的方案"做。

修复: 调度 AI 之前先写 Specs(场景清单)。至少给出"输入→期望输出"的例子。复杂改动还要写方案(solution.md)先确认。

4. 依赖单一模型做所有事

症状: 用同一个模型做架构设计、根因分析、代码编写、测试编写。架构方案大方向偏了、根因分析漏了关键点、测试没有覆盖边界条件。

根因: 不同模型有不同能力侧重。在各自擅长的领域用错模型。

修复: 模型分层——Pro(--variant max)做架构/根因分析/方案评估,Flash 做编码/修复/实现,Flash Free 做测试编写/小改动/预览检查。

5. 不提需求就修 bug

症状: AI 发现代码里"觉得有问题",不汇报就直接改了。兄弟本来只让修 Exit bug,AI 顺手重构了 MatchConfig——导致回归。

根因: AI 主动性太强——发现问题觉得"应该顺便修掉"。

修复: 代码审核流程中的 级规则明确写了"不修改规格以外的代码"。发现可疑问题先汇报(用 brief 里的"待确认事项"),确认后再动手。

6. ️ 过度抽象

症状: AI 为"未来可能"的场景设计抽象层——接口、工厂、策略、观察者。一个简单功能用了 6 个文件,抽象了 4 层。结果未来场景没来,抽象层成了维护负担。

根因: AI 倾向于通用化——它觉得"好的代码就是可扩展的代码"。但 Vibe Coding 项目的变化速度太快——上个月的抽象很可能下个月就废弃。

修复: 级检查项"警惕不必要的抽象"。先跑通再抽象——有 3 个相似真实场景再考虑提取公共逻辑。

7. 不写测试就改代码

症状: 改代码前没写测试,修复后"感觉对了"。结果回归了——之前的某个功能坏了,但不知道什么时候坏的。

根因: TDD 流程的反面——先改再测(甚至不测)。Vibe Coding 的迭代速度允许"先写代码再补测试",但不能"不补测试"。

修复: 每个修复/功能必须配套 BDD 测试(至少一个 happy path + 一个 error case)。验收流程中先让测试因 bug 失败(),再修复到通过()。


17 个可复用设计模式

模式 一句话
服务端权威 + 绝对状态 最简多人同步方案——不增量、不回滚、不预测,全量状态下发。简单到 AI 不会搞错。
开闭原则 AI 协作的第一架构约束——新增多人功能时,不修改单机代码。隔离安全沙箱。
纯函数共享层 ReScript 编译到 49KB bundle,两端加载同一份。前端和后端的行为 100% 一致。
渐进式碰撞精度 AABB → OBB → 凸包,每步只解决当前痛点。不提前优化,也不用一步到位。
代次守卫 generation++ 乐观锁防定时器残留——SCF warm container 下每个请求都自增代次,旧代次的定时器回调自动跳过。
SoA 状态管理 Float32Array 连续内存 + 固定 stride,GC 压力极低。WebGPU 就绪的数据结构。
事件驱动 新行为 = 新 handler,不改现有逻辑。避免 if-else 嵌套地狱。
防御式编程 参数必传、尽早 throw、不满足条件拒绝操作。在 AI 边界处设置安全检查。
Test-as-documentation BDD + Specs = 活的文档。BDD 测试不仅验证功能,还记录了业务场景。
隔离层设计 business_layer/multiplayer/ 安全沙箱——所有多人代码放这里,单机代码 zero-touch。
先跑通再工程化 basic1 → new_basic2 → monorepo。每一步都追求"先看到游戏能跑",再想工程架构。
先状态同步再优化 不做 rollback、不做增量、不做预测。状态同步够用前不碰任何高级优化。
Skill 化流程 可执行的流程化知识——把兄弟的经验和决策编码成 AI 可执行的 Skill。每次 Skill 运行都是经验的复制。
积木系统 可组合的 E2E 积木架构——每个积木独立定义、拼 JSON 即得新场景。核心价值是 可组合
三层编码规则 红线(不可协商)→黄线(上下文裁量)→绿线(推荐)。AI 可执行的编码规范,人可审查。
QMD 索引检索 记忆分层管理——MEMORY.md 索引 → QMD 全文检索 → 原文文件。知识可搜索而非靠回忆。
模型分层 Router Pro 做分析/架构、Flash 做编码实现、Flash Free 做测试/小改。各司其职,不浪费能力。

一句话总结

先把 demo 跑通,再想架构;先用状态同步,再想优化;先定义 Specs,再让 AI 干活。

这 17 个模式不是理论推导出来的——它们是从 40+ 条 ADR、30+ 次线上部署、200+ 次 BDD 测试迭代中提炼出的最佳实践。每个模式都对应至少一个真实踩坑。

我不保证这些模式适用于所有多人游戏项目。但我保证——如果从第一天就用这些模式,GTS-Play 至少能省下一半的开发时间。


系列继续……

Vibe Coding 多人游戏(三十)—— 给下一个 Vibe Coder 的起步指南

Vibe Coding 多人游戏(三十一)—— Loop Engineering:我们的实践与改进方向

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