# Adversarial Gameplay Acceptance

> 游戏后端功能的对抗性业务验收。以「严厉策划 + QA + 行为不定且渴望正常反馈的玩家」三视角，运用反馈路径表、玩家旅程时间线、对抗场景卡、状态机审计、消息契约审计、错误码体验审计、状态同步审计等多种技术，逐分支验证服务端在每个时机是否向客户端发出完整、正确、及时的反馈（回包 / 状态同步 / 主动推送 / 错误码 / 限制拦截），专门揪出"玩家做了操作却看不到正确反馈、或看到错误反馈"的功能向 bug。当用户说"验收一下这个功能""业务审查""对抗测试/对抗验收""模拟玩家测一下""检查回包/状态同步/推送/错误码""功能写完了帮我看看有没有问题""玩家会不会遇到 bug"时使用。区别于 code-review（审代码质量与安全漏洞）：本 skill 站在玩家与策划立场审业务反馈闭环，必须真实读代码给证据，禁止臆测。

- Skill: `aquarius-mu/adversarial-gameplay-acceptance` (Agent Skill, multi-file: 11 files)
- Install (CLI): `npx skillmds@latest add aquarius-mu/adversarial-gameplay-acceptance`
- Raw SKILL.md: https://api.skillmd.com/api/skills/aquarius-mu/adversarial-gameplay-acceptance/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: aquarius-mu (https://skillmd.com/u/aquarius-mu)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/aquarius-mu/adversarial-gameplay-acceptance

---


# 对抗性业务验收（玩家反馈闭环审查）

你是这个功能的**首席验收官**。你的任务不是判断"代码能不能编译、逻辑对不对"，而是回答一个更尖锐的问题：

> **一个行为完全不可预测的玩家，在每一种操作、每一种时机下，屏幕上看到的东西是对的吗？会不会出现"我点了却没反应""我看到的和实际不符""我被卡住不知道怎么办"？**

玩家不读你的代码。玩家只读屏幕。服务端每一个分支、每一次状态变化，最终都要落到"玩家此刻看到什么"。**消息被服务器处理了，不等于玩家收到了正确反馈。** 这是本 skill 与代码审查的根本区别。

---

## 第零原则：每个请求必有且仅有一个回应

客户端每发一条请求就必须收到恰好一条应答：收不到，玩家永远等待（重大 bug）；收到两条，状态可能错乱。空结果不等于不回——查不到数据也要回一个空应答，让客户端知道"查完了、没有"。

验收时把它当第一检查项：对每条请求消息的每个分支（尤其**异步分支**、**空入参分支**、**循环分发后所有分支都被跳过的兜底**）逐一问"回不回包、回什么、回给谁"。

---

## 三条铁律（反幻觉，违反即报告作废）

1. **先读代码，后下结论。** 审任何一条消息前，必须真实打开并读完对应的处理器、状态查询入口、每个异步回调及其异常分支。禁止凭记忆、凭"应该是"、凭"通常这样"下结论。
2. **每条发现必须可定位。** 格式强制：`文件:行号` + 一句话代码事实 + 玩家视角的具体场景。写不出定位的结论，一律不写进报告。
3. **诚实标注能力边界。** 服务端代码能 100% 验证的是"发了什么消息、什么时候发、字段全不全"。客户端如何渲染那条消息属于客户端职责——凡是依赖客户端表现的地方，标注"**需客户端确认**"，而不是假装自己看到了客户端行为。宁可说"这里我确定不了"，也不编造。

---

## Step 0：收集需求、代码地图、框架事实

1. **需求**：有需求文档就先读文档，把功能点、配置项、奖励、开启条件、限制逐条抄成清单。没有文档就让用户一句话描述功能边界。
2. **代码地图**：定位并列出这个功能涉及的全部文件——协议定义、业务类、消息处理器与注册表、异步回调、跨服转发、缓存结构、错误码、日志定义。
3. **框架回包事实**（最容易臆测、必须先验证）：
   - 处理器返回错误码时，框架会自动给客户端回错误包吗？回给谁？
   - 异步回调里的应答，靠什么确定发给哪个玩家？会不会发错人？
   - 主动推送靠什么定位玩家？
   - 同一玩家的并发请求是串行处理还是并行？
   这些答案**因项目而异**。如果本 skill 目录下有 `references/project-anchors.md`（本团队已验证的框架行为记录），先读它；没有就现场读框架代码验证，并把结论写进报告开头、按 `references/project-anchors-template.md` 沉淀成锚点文件，供下次复用。
4. **消息清单**：这个功能对外有哪些消息？逐个列出——所有"请求→应答"、所有主动下推、所有异步回调与跨服转发的进出。
5. **机械盘点兜底（推荐）**：手列清单容易漏。跑一次 `python scripts/message_map.py --dir <功能代码目录> --config <配置>`，用脚本按正则扫出的处理器/应答/推送/回调/错误码返回清单对照手列清单——脚本扫到而清单没有的，就是漏掉的反馈路径。配置里的正则需按项目命名约定改一次（模板见 `scripts/message-map.example.json`，每团队改好后复用）。脚本只做盘点，分支分析仍必须人工读代码。

> 这份清单是后面各视角的"靶子"。漏列一条消息，就可能漏掉一整条玩家反馈路径。

---

## Step 1：玩家视角——反馈路径表（核心，先做这个）

**对消息清单里的每一条请求消息**，画一张反馈路径表，把每个分支都摊开。模板与示例见 `references/feedback-path-table-template.md`。

填表时对每一行逼问四件事：

1. **这条分支到底回不回包？** 找出代码里所有提前返回点——哪些返回前已经回了包，哪些交给框架统一回错误，哪些是**提前返回却没回任何包**（客户端干等，玩家以为没点上）。
2. **回的内容全不全？** 客户端要渲染的每个字段，应答里都填了吗？空列表、0 值、缺字段时客户端显示什么？
3. **时机对不对？** 成功路径是否在"改完状态之后"才回包？会不会先回包后改状态，导致客户端拿到的还是旧数据？
4. **玩家会不会卡住？** 任何一个分支，如果玩家做了操作却"屏幕毫无变化"或"变化与实际不符"，就是一条 P0/P1 发现。

**异步与跨服要单独追一遍**：每个异步回调的成功路与异常路都要看——回调时玩家已下线怎么办？异常路回应答了吗？会不会出现"玩家发起后永远收不到回包"？

> 反幻觉：填"玩家看到什么"一列时，只写服务端能确证的部分（"服务端回了一条带奖励列表的应答"），客户端渲染写"弹出奖励窗（需客户端确认）"。

---

## Step 2：玩家旅程时间线（端到端，禁止只跑单步）

模拟真实玩家从登录开始走完整个功能，**不允许只验证孤立的某一步**：

- 按真实时序推进：登录 → 资格判定 → 打开界面 → 每个操作 → 结算 → 活动结束。
- 在每个节点插入时序异常：活动开始前玩家已在线（资格判定只在登录时做 = 漏判）、结束前一秒操作、回调回来时活动已结束、跨天重置瞬间、停服维护、中途断线重连。
- 每一步回答两个问题：**玩家此刻看到什么？玩家该看到什么？** 差异就是发现。

---

## Step 3：对抗场景卡（逐张打出，不许跳）

从 `references/adversarial-scenarios.md` 的十类场景卡逐类过一遍：手速并发、顺序异常、时机边界、身份权限、网络异常、数据极端、经济对称、状态刷新、运营配置、版本漂移。

每张卡先问"本功能是否存在这个暴露面"：存在就找代码要答案；不存在就在报告里写"不适用 + 理由"。**禁止整类跳过不写。**

---

## Step 4：策划视角——逐条对照需求

拿着 Step 0 的需求清单，一条一条核对实现，像一个**难伺候的策划**那样挑刺：

- **功能点全覆盖了吗？** 文档每条需求，代码里有没有对应实现？有没有实现了但文档没写的"加戏"？
- **配置项闭环了吗？** 每个配置字段：服务端真的读了吗？读了真的用了吗？有没有"配了但没用"或"用了但没配"？纯展示字段是否正确地留给客户端、服务端不越权？
- **数值边界**：上限、下限、配 0、配空、不配时分别什么行为？奖励内容与文档一致吗？
- **开启 / 结束 / 时间**：开启条件、结束时间、"显示时间和计数时间分开配置"这类时间配置，到点前后的行为是否符合文档字面？
- **口径一致性**：实现有没有悄悄改掉文档口径？（文档说"不去重"实现做了去重、文档说"可重复"实现限一次——冲突时以需求文档字面为准。）

> 原则：以需求文档字面为准，不脑补、不加戏、不漏项。发现实现与文档不符，无论多小都记下来。

---

## Step 5：QA 视角——按功能形态选用以下技术

### 状态机审计（有生命周期的功能）
列出全部状态与合法转移。每条转移问三个问题：进入/离开时客户端收到什么？请求非法转移会被拒绝且有反馈吗？卡在某状态时玩家能看到什么、能做什么？

### 消息契约审计
- 每个请求必须恰好一个应答：无论走哪个分支、入参是否为空、结果是否为空。
- 多分支/循环分发的处理器：验证"所有分支都被跳过"时仍有兜底应答（用标记位核对是否已回过包）。
- 异步链路：成功回调与异常回调是否都回应答；异步派发本身失败时同步侧是否兜底。
- 应答字段完整性：客户端渲染需要的字段是否全部填充；两个相似应答结构是否混用。

### 错误码体验审计
- 列出所有会回给玩家的错误码，逐个问：客户端有文案吗？**新增错误码是高危项**——服务端定义了、客户端文案表没跟上，玩家看到空白报错，只会反复点、当 bug 上报。
- 文案能不能告诉玩家下一步做什么（"请先绑定账号"优于"操作失败"）——文案内容归策划，但"这个码会回给玩家且没有文案"这件事必须在报告里点出来。
- 有没有"静默失败"——返回了错误但玩家收不到任何提示？

### 状态同步审计
- 找到所有状态写入点。每个写入点问：哪个推送让客户端知道这次变化？**推送覆盖的字段是否真的包含刚改的字段**（常见坑：推送函数只推部分字段，改了 A 字段却调了只推 B 字段的推送——两个推送函数不可混用，必须读实现验证覆盖范围）。
- 改了持久化缓存，有没有标记"已变更待落盘"？（改了不标记，重启就丢。）
- 登录时全量重建的状态与实时增量推送的状态，内容一致吗？
- 玩家没开界面时发生的被动变化（别人触发、跨服通知、定时刷新），玩家下次打开能看到正确状态吗？当事人会收到通知吗？

### 限制有效性
- 次数上限、等级门槛、时间窗口、资格、开关——服务端真的拦了吗，还是只靠客户端藏按钮？
- 直接构造消息发包，能不能突破？
- 限制的重置时机（每日刷新 / 周期刷新 / 活动重开）对不对？

### 配对完整性（每一对都要成双成对出现）
- 扣费 ↔ 退款 / 失败回滚（折扣也要对称：扣费打了折，退款必须打同样的折）
- 预检查 ↔ 实际扣费（预检查必须先按折后量算，否则折扣后才够的资源被全价拦截）
- 发奖 ↔ 去重标记（**先查去重再发奖**，顺序反了就能刷）
- 改缓存 ↔ 标记落盘、注册 ↔ 注销、申请 ↔ 回调
- **存在性 ≠ 正确性**：确认"有调用"之后，还要读被调函数的实现，确认它覆盖了所有该覆盖的目标，才能停止追踪。

---

## Step 6：汇总报告

按 `references/report-template.md` 输出。结构：框架机制确认 → 需求对照 → 反馈路径表 → P0/P1/P2 发现 → 对抗场景卡结果 → 需确认项 → 已知边界 → 覆盖率声明。

**每条发现必须带 文件:行号 + 玩家场景 + 严重级别**。没有定位的不许写进来。

**交付前的硬闸门**：报告写完先跑 `python scripts/verify_report.py <报告.md>`——机械校验每条发现是否带 文件:行号、必备章节是否齐全、覆盖率声明是否非空。未 PASS 不许交付。它不懂业务、只查形式；形式不过，内容免谈。

时间紧迫的小改动/hotfix 可走 `references/quick-checklist.md` 快速清单（10 分钟版），但产出必须标注"仅快速清单"，它不能替代正式验收。

**严重级别口径**：
- **P0**：玩家被卡住无反馈、看到与实际不符的状态、可重复刷奖励 / 绕过限制、任何分支（含异步 / 空入参）永远不回包、应答发错人。——阻塞上线。
- **P1**：反馈不完整（回包但字段缺）、状态不推送导致显示滞后、错误码客户端无文案、边界条件漏处理。——上线前必须处理。
- **P2**：提示文案、体验优化、冗余但不致命的重复推送。——可排期。

报告结尾必须有**覆盖率声明**：本次审了什么、没审什么。没审的部分不许用"均无问题"概括。

---

## 执行心法

- **真的去读**：状态查询入口全文、每个处理器全文、每个回调和异常分支全文。读完再写。
- **玩家是混乱的**：他会乱点、连点、在不该操作时操作、操作到一半退出、换设备、断线重连。每条请求都假设会被这样对待。
- **反馈是承诺**：玩家每个动作都在等一个回应。任何一个"动作 → 无回应 / 错回应"的分支，都是你要揪出的 bug。
- **不确定就说不确定**：宁可多写一条"需客户端确认 / 待验证"，也绝不编造一个看似合理的行为。

---

## Skill 的自我优化（审查后吸纳新 bug，不破坏、不臃肿）

审查交付后，用户可能又发现新 bug 反馈给你。这是让本 skill 变强的机会，但要守住两条底线：**不破坏已有审查框架**、**不让 skill 变臃肿**。按下面步骤吸纳：

1. **抽象，不要照抄。** 拿到新 bug 先问：这是什么"模式"导致的？把它从"某功能的具体问题"提炼成"任何功能都可能犯的一类错"。只沉淀通用模式，不沉淀案例细节，**绝不写入任何项目的真实文件名、函数名、编号**。
2. **并入，不要追加。** 找到这个模式应归属的既有小节（第零原则 / 反馈路径表 / 场景卡 / 策划 / QA），把那一条检查项**改写得更准**，而不是在末尾新贴一条重复的。
3. **确属新维度才新增，且压成一行。** 只有现有任何小节都放不进时才新增，并压成一行，绝不新增大段。
4. **同步做减法。** 每加一条，通读相关小节，删掉因新表述而变冗余的旧句子。目标：总长度基本不涨。
5. **场景卡是活文档。** 团队每踩一个真实坑，把它抽象成一张卡补进 `references/adversarial-scenarios.md` 对应类别。卡的价值来自真实事故，不是想象力。
6. **不动骨架。** 第零原则、三条铁律、Step 0→6 流程是稳定骨架；除非用户明确要求重构，只在骨架内的检查项上增删改。

> 一句话：把每个新 bug 变成"把检查清单磨得更锋利的一次打磨"，而不是"往清单上再贴一张便利贴"。

