8.4 KiB
ADDED Requirements
Requirement: GM 双轨入口
系统 MUST 提供两种 GM 命令入口:服务端 GM Console(命令行 + ET Watcher 注入)和客户端 GM Panel(YIUI 简易面板)。两者共用同一套命令注册表,避免重复实现。
Scenario: 服务端 Console
- WHEN 运维登录服务端 shell 执行
gm AddItem 10001 100或通过 ET Watcher Web 接口 - THEN 命令在服务端直接执行(不需要客户端登录)
- AND 操作日志写入
Logs/Audit/GM/{date}.jsonl
Scenario: 客户端 Panel
- WHEN Debug 包内打开 GM Panel
- THEN 显示所有可用命令列表(按分类分 Tab)
- AND 点击命令 → 弹输入框 → 发送 RPC
C2G_GmCommand到服务端执行
Scenario: 命令注册统一
- WHEN 开发者新增 GM 命令
- THEN 用
[GmCommand("AddItem", "发放道具")]属性标记即可 - AND 服务端 Console 和客户端 Panel 都自动出现
Requirement: GM 命令必覆盖全部业务写操作
每个新增的服务端 RPC Handler MUST 同时新增对应的 GM 命令。这是 P1-P3 阶段唯一的业务验证手段。MUST 在 CI 中自动校验:每个 RPC Handler 都有至少一条对应 GM 命令的注册。
Scenario: 新增 Handler 必带 GM
- WHEN 开发者新增
C2G_HeroSkillUpHandler - THEN MUST 同时新增
gm HeroSkillUp <heroId> <skillIndex>命令 - AND CI 检查脚本扫描发现缺少 GM 时构建失败
Scenario: GM 命令调业务接口
- WHEN
gm HeroSkillUp被执行 - THEN MUST 调用真实的业务层 API(如
HeroComponentSystem.SkillUp(hero, skillIndex)) - AND NOT 直接修改 Hero 字段(避免绕过业务校验导致数据不一致)
Requirement: GM 命令分类
GM 命令 MUST 按以下分类组织:
| 类别 | 命令前缀 | 典型命令 |
|---|---|---|
| 资源发放 | Add* Set* |
AddItem / AddCurrency / AddHero / SetItemCount |
| 英雄操作 | Hero* |
HeroLevelUp / HeroStarUp / HeroSkillUp / HeroAwake / HeroWearEquip |
| 背包操作 | Bag* |
BagClear / BagShow / BagDump |
| 抽卡操作 | Gacha* |
Gacha / ResetGachaGuarantee / SetGachaCount |
| 数据查询 | Show* Dump* |
ShowHeroes / ShowBag / ShowGachaStats / DumpPlayer |
| 数据快照 | Snapshot* Restore* |
Snapshot / Restore / ListSnapshots |
| 批量模拟 | Simulate* |
SimulateGacha / SimulateBatchLogin |
| 数据校验 | Verify* |
VerifyConsistency / VerifyConfigRef |
| 服务管理 | (其他) | ReloadConfig / FlushDirty / Kick / SetLogLevel |
Scenario: 列出所有命令
- WHEN 执行
gm help - THEN 按分类输出所有可用命令 + 简要描述 + 参数说明
Scenario: 命令搜索
- WHEN 执行
gm help Gacha - THEN 仅输出 Gacha 类别的命令
Requirement: 数据查询命令输出 jsonl
Show* 和 Dump* 类查询命令 MUST 输出 jsonl 或可解析的结构化数据(默认 jsonl,可选 --pretty 输出格式化 JSON),便于后续 jq 处理或对比。
Scenario: ShowBag 输出
- WHEN 执行
gm ShowBag playerId=1001 - THEN 控制台输出形如:
{"itemId":1,"name":"金币","count":12345} {"itemId":2,"name":"钻石","count":50} {"itemId":10001,"name":"英雄A碎片","count":3} - AND 可重定向到文件用
jq处理
Scenario: DumpPlayer 完整快照
- WHEN 执行
gm DumpPlayer 1001 - THEN 输出完整玩家数据 JSON:Hero/Bag/Gacha/Equip/Talent
- AND 输出大小可能很大(数百 KB),自动写文件并提示路径
Requirement: 抽卡概率模拟与审计
GM MUST 提供 gm SimulateGacha <poolId> <times> 命令:服务端循环 N 次抽卡(不消耗真实玩家资源)输出统计数据。MUST 输出每个品质的实际命中率、保底触发次数、与配置期望误差百分比。
Scenario: 概率审计
- WHEN 执行
gm SimulateGacha 1 10000 - THEN 服务端循环 10000 次抽卡(不修改玩家数据)
- AND 输出形如:
Pool 1 - SimulateGacha 10000 times Rare 5: 145 hits (1.45%), expected 1.50%, diff -3.33% Rare 4: 1212 hits (12.12%), expected 12.00%, diff +1.00% Rare 3: 8643 hits (86.43%), expected 86.50%, diff -0.08% Guarantee triggered: 1 times - AND 误差超过 ±5% 时标红警告
Scenario: 不影响真实玩家
- WHEN 模拟抽卡执行
- THEN 真实玩家的 GachaComponent.PoolStats 不变
- AND 不发奖励、不扣消耗
- AND 不写真实审计日志(仅写 GM 模拟日志)
Requirement: 数据快照与回滚
GM MUST 提供 Snapshot / Restore 命令:保存当前玩家完整数据,后续可恢复。用于 A/B 测试不同操作链路、验证数据一致性。
Scenario: 测试场景前后对比
- WHEN 执行
gm Snapshot before_test playerId=1001 - AND 执行一系列业务操作
- AND 执行
gm Snapshot after_test playerId=1001 - AND 执行
gm DiffSnapshot before_test after_test - THEN 输出两个快照的差异(jsonl 格式)
- AND 便于验证"某操作的副作用是否符合预期"
Scenario: 一键回滚
- WHEN 测试发现某操作导致数据错乱
- AND 执行
gm Restore before_test playerId=1001 - THEN 玩家数据完全恢复到快照时点
- AND 内存 + DB 都同步更新
Requirement: 数据一致性校验
GM MUST 提供 gm VerifyConsistency <playerId> 命令:扫描玩家所有数据,校验关键不变式(invariants):
- 装备的 heroId 是否真实存在
- 英雄的等级是否符合稀有度上限
- 背包道具的 itemId 是否在 Config 中存在
- 抽卡保底计数是否 ≤ 配置阈值
- DB 数据与内存数据是否一致
Scenario: 发现脏数据
- WHEN 玩家数据中存在
Equip { heroId: 99999 }(不存在的英雄) - AND 执行
gm VerifyConsistency 1001 - THEN 输出
[ERR] Equip itemIndex=123 references invalid heroId=99999 - AND 输出修复建议
Scenario: 全服校验
- WHEN 执行
gm VerifyConsistency --all - THEN 对所有在线玩家执行校验
- AND 汇总输出错误统计
Requirement: GM 操作审计
每个 GM 命令执行 MUST 写 L2 审计日志(同 observability-logging 中的定义),含 operator(GM 操作者标识)、action(命令名)、targetPlayer(如有)、args(参数)、success(是否成功)。失败的命令也 MUST 记录。
Scenario: GM 操作审计日志
- WHEN 执行
gm AddItem 1 1000000 playerId=1001 - THEN
Logs/Audit/GM/{date}.jsonl追加:{"ts":"...","level":"WARN","module":"GM","operator":"admin","action":"AddItem","targetPlayer":1001,"args":{"itemId":1,"count":1000000},"success":true} - AND 审计日志同步落盘,不允许丢失
Scenario: 失败也记录
- WHEN GM 执行不存在的命令或参数错误
- THEN 仍然记录审计日志,success=false,附 errorMessage
Requirement: Release 包剥离 GM 代码
GM 命令的注册、命令实现、客户端 Panel MUST 通过编译条件 #if !RELEASE 包裹,Release 构建时完全不编译进包。MUST 通过 CI 校验:Release IL 中不含 GM 命令字符串。
Scenario: Release 包不含 GM
- WHEN 用
Build Settings: Release出包 - AND 反编译产物(dnSpy / il2cpp dump)
- THEN 不存在任何
GmCommand属性或注册代码 - AND 不含 GM 命令名字符串(如 "AddItem")
Scenario: Debug 包正常启用
- WHEN 用 Debug 配置编译
- THEN GM Panel 可打开
- AND 所有命令可用
Requirement: 阶段性使用约束
在 OpenSpec 当前 change(port-rok-hero-bag-system)的 P1-P3 阶段,业务验证 MUST 通过 GM 命令 + 业务日志,禁止为了验证而临时写 YIUI Panel。所有 P1-P3 落地的 RPC Handler MUST 有对应的 GM 命令验证场景在 tasks.md 中明确记录。
Scenario: P1 抽卡完成验证
- WHEN P1 阶段 GachaComponentSystem.Draw 实现完成
- THEN MUST 通过以下 GM 命令组合验证:
gm AddCurrency 2 100→ 给自己钻石gm Gacha 1 1→ 单抽gm ShowBag→ 检查奖励到账、钻石扣除gm SimulateGacha 1 10000→ 概率审计
- AND 验证场景必须在 tasks.md 的 2.0.7 节明确列出
Scenario: 禁止临时 UI
- WHEN 开发者觉得"做个简单 UI 看看"更直观
- THEN 不允许,必须通过 GM 命令验证
- AND UI 集中到 P4 阶段一次性做