## 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 ` 命令 - **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** 控制台输出形如: ```jsonl {"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 ` 命令:服务端循环 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 ` 命令:扫描玩家所有数据,校验关键不变式(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` 追加: ```json {"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 阶段一次性做