188 lines
8.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## ADDED Requirements
### Requirement: GM 双轨入口
系统 MUST 提供两种 GM 命令入口:服务端 GM Console命令行 + ET Watcher 注入)和客户端 GM PanelYIUI 简易面板)。两者共用同一套命令注册表,避免重复实现。
#### 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** 控制台输出形如:
```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** 输出完整玩家数据 JSONHero/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 中的定义),含 operatorGM 操作者标识、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 当前 changeport-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 阶段一次性做