460 lines
34 KiB
Markdown
460 lines
34 KiB
Markdown
# Tasks: Port ROK Hero & Bag System
|
||
|
||
> **交付策略**:P1-P3 服务端业务先行,**用 GM 命令 + 日志验证**,**不写 UI**。所有 YIUI 面板集中在 P4 一次性补齐。每个新 RPC Handler MUST 同时新增对应 GM 命令。
|
||
>
|
||
> **复用原则**(详见 `Doc/ET-Packages-Audit.md`):38 个 ET 包已覆盖 GM / 日志 / BSON / 协议 / 资源热更 / UI / 网络。本变更**严格遵守"能复用不重写"**:日志扩展 `Log` 类、GM 直接用 `[GM]`/`[ConsoleHandler]` 属性、BSON 复用 `MongoHelper`、登录扩展 `C2G_LoginGateHandler`。仅以下能力新建业务包:`db` / `logging` / `config` / `hero` / `bag` / `gacha`。
|
||
|
||
## 1. P0 - 基础设施准备
|
||
|
||
### 1.1 客户端编译产物
|
||
- [ ] 1.1.1 在 Unity 中执行 `ET/Loader/Compile` (F6) 生成客户端热更 DLL
|
||
- [x] 1.1.2 检查 `Packages/cn.etetet.loader/Bundles/Code/` 下存在 `ET.Model.dll.bytes` / `ET.Hotfix.dll.bytes` / `ET.ModelView.dll.bytes` / `ET.HotfixView.dll.bytes`
|
||
- [ ] 1.1.3 执行 `ET/HybridCLR/Init` 初始化 AOT 元数据(如未执行过)
|
||
|
||
### 1.2 服务端编译产物
|
||
- [ ] 1.2.1 执行 `dotnet build "My project/ET.sln" -c Debug` 编译服务端
|
||
- [x] 1.2.2 检查 `My project/Bin/ET.App.dll` 存在
|
||
- [x] 1.2.3 编写 `tools/build-server.ps1` 一键编译脚本
|
||
|
||
### 1.3 跑通 Login Demo
|
||
- [ ] 1.3.1 在 Unity Build Settings 中添加 `Packages/cn.etetet.loader/Scenes/Init.unity`
|
||
- [ ] 1.3.2 启动 ET Server (单进程模式):`ET/Loader/ServerTools` → "Start Server"
|
||
- [ ] 1.3.3 启动 Unity Play,验证 `C2R_Login` → `R2C_Login` → `C2G_LoginGate` → `G2C_LoginGate` 链路打通
|
||
- [ ] 1.3.4 验证 Gate Scene 创建了 `Player` Entity 并关联 `Session`
|
||
|
||
### 1.4 DBComponent 业务封装(**基于现成 MongoHelper + MongoDB.Driver**)
|
||
- [x] 1.4.1 **复用确认**:`cn.etetet.core/MongoHelper` 已有 BSON 序列化、`com.etetet.init/Plugins/MongoDB` 已有原生 DLL,仅缺业务层封装
|
||
- [x] 1.4.2 新建 `Packages/cn.etetet.db/` 包目录与 package.json(依赖 cn.etetet.core)
|
||
- [x] 1.4.3 定义 `IDBComponent` 接口(Save<T>/Query<T>/QueryByEntityId<T>/Update<T>/Delete<T>)
|
||
- [x] 1.4.4 实现 `MongoDBComponent`:用 `IMongoClient` 直连 + `MongoHelper.ToBson/FromBson` 序列化;Collection 命名约定 = `typeof(T).Name`
|
||
- [x] 1.4.5 实现 `MemoryDBComponent`(`Dictionary<long, byte[]> + MongoHelper.Clone`)开发期默认
|
||
- [x] 1.4.6 在 Gate Scene 启动时根据 `StartConfig.DBConnection` 二选一挂载
|
||
- [x] 1.4.7 实现 `DBSaveComponent`(脏标记 + 30 秒定时批量 Save + Player 下线强制 Flush)
|
||
- [x] 1.4.8 添加 `Entity.SetDirty()` 扩展方法(写入 dirty set)
|
||
- [ ] 1.4.9 单元测试:save → query → update → delete 闭环(内存模式 + Mongo 模式各一遍)
|
||
|
||
### 1.5 Player 数据加载(**扩展现有 `cn.etetet.login`,不重写**)
|
||
- [x] 1.5.1 **复用确认**:`C2G_LoginGateHandler` 已有,目前只创建内存 Player;扩展点 = 在 `await CreatePlayer(...)` 后插入 DB 加载分支
|
||
- [x] 1.5.2 在 `cn.etetet.login/Hotfix/Server/Gate/C2G_LoginGateHandler.cs` 加入 `await DBHelper.LoadOrCreatePlayer(player, dbComponent)` 调用(约 5-10 行)
|
||
- [ ] 1.5.3 新建 `Packages/cn.etetet.config/Datas/InitialResource.xlsx`(首次登录发放 RewardEntry 列表)
|
||
- [ ] 1.5.4 实现 `DBHelper.LoadOrCreatePlayer`:DB 查 → 有则反序列化挂 Hero/Bag/Gacha Component;无则新建并按 InitialResource 配置调 `BagComponentSystem.AddItems`
|
||
- [x] 1.5.5 注册 Session Dispose 事件(用 ET 现有 EventSystem),玩家离线时 `DBSaveComponent.FlushNow(playerId)`
|
||
- [ ] 1.5.6 验证:登录 → DB 中存在 → 重启服务端 → 二次登录数据完整恢复
|
||
|
||
### 1.6 协议工具链
|
||
- [ ] 1.6.1 验证 `ET/Proto/Proto2CS` 工具可正常运行
|
||
- [x] 1.6.2 在 `Packages/cn.etetet.proto/Proto/` 新建占位 `HeroOuter_C_3000.proto`(仅含 1 个 ping 消息测试)
|
||
- [ ] 1.6.3 运行 Proto2CS 验证代码生成到 `CodeMode/Model/{Client|Server|ClientServer}/HeroOuter_C_3000.cs`
|
||
- [ ] 1.6.4 添加新消息后客户端能正常发送、服务端 Handler 能接收
|
||
- [x] 1.6.5 给所有 C2G/C2R 消息基类加入 `reqId` 字段(uuid 字符串,便于日志全链路追踪)
|
||
|
||
### 1.7 Luban 配置工具链(**替代 ExcelExporter**)
|
||
- [ ] 1.7.1 下载 luban 工具(dotnet luban),放到 `tools/luban/`
|
||
- [x] 1.7.2 新建 `Config/Defines/Root.bean.xml` + `Config/Datas/` 目录结构
|
||
- [x] 1.7.3 编写 `tools/luban/gen.ps1` 一键导出脚本(生成 C# 到 `Packages/cn.etetet.config/Scripts/Model/Share/cfg/`,json 到 `Bundles/Config/`)
|
||
- [ ] 1.7.4 写一张示例表 `ItemConfig.xlsx` + `Item.bean.xml`(含 itemId / itemType / name / icon / useType / maxStack / quality / desc / 通用 RewardEntry 字段示例)
|
||
- [ ] 1.7.5 跑通 gen.ps1 → 生成代码 + json 文件
|
||
- [ ] 1.7.6 在 YooAsset `AssetBundleCollectorSetting` 加 `Bundles/Config/` 收集规则
|
||
- [x] 1.7.7 新建 `Packages/cn.etetet.config/` 包,实现 `ConfigComponent` 挂在 Scene 上加载 Luban Tables
|
||
- [x] 1.7.8 实现 `SceneExtensions.Cfg()` 扩展方法
|
||
- [ ] 1.7.9 服务端 + 客户端 都能调 `scene.Cfg().TbItem.Get(1)` 查到金币配置
|
||
- [x] 1.7.10 编写 Luban 使用文档 `Doc/Luban-Guide.md`
|
||
|
||
### 1.8 日志扩展(**基于现成 `Log` + NLog,仅加扩展方法**)
|
||
- [x] 1.8.1 **复用确认**:`cn.etetet.core/Log.cs` 已有 `Log.Debug/Info/Warning/Error/Trace`;`cn.etetet.loader` 已集成 NLog 后端;**不重写不替换**
|
||
- [x] 1.8.2 新建 `Packages/cn.etetet.logging/` 包(依赖 cn.etetet.core + cn.etetet.loader)
|
||
- [x] 1.8.3 在新包内加扩展方法:`Log.Audit(module, action, payload)` / `Log.Slow(module, action, durationMs, ctx)` / `Log.Module(tag, level, msg)`(**全部走 ET 现有 Log 入口,仅做格式化前缀**)
|
||
- [x] 1.8.4 提供 `NLog.config.template`:3 个 target 分别输出 `Logs/Business/` / `Logs/Audit/{module}/` / `Logs/Error/`(jsonl 行格式)+ rolling by day
|
||
- [x] 1.8.5 提供 `LoggingInstaller`:服务端启动时合并 template 到现有 `Packages/cn.etetet.loader/Scripts/Loader/Server/NLog.config`
|
||
- [x] 1.8.6 实现 `Log.Audit` 同步落盘(NLog `<target type="AsyncWrapper">` 关闭 + `KeepFileOpen="true"` 强制 flush)
|
||
- [x] 1.8.7 约定模块标签前缀:`[Hero]` `[Bag]` `[Gacha]` `[Equip]` `[DB]` `[Login]` `[GM]` `[Net]`(**仅写文档约定,不做编译期校验**)
|
||
- [x] 1.8.8 实现 RPC Handler 基类 `RpcMessageHandler<TReq,TResp>`:在 OnRun 前后自动 `Log.Info($"[{module}] {action} in: ..." )` + 耗时计算(**业务 Handler 改继承即可,老 Handler 不强制**)
|
||
- [x] 1.8.9 实现 reqId 透传:在 C2G/C2R proto 基类加 `reqId` 字段(**任务 1.6.5 已包含**)+ Handler 基类把 reqId 放到 `AsyncLocal<string>`,扩展 `Log.Info` 自动拼前缀
|
||
- [x] 1.8.10 实现慢操作告警:包装 `IDBComponent` 入口 + `MessageDispatcher`,超阈值(DB 查询 100ms / 写入 200ms / Handler 500ms)调 `Log.Slow`
|
||
- [x] 1.8.11 实现错误码降频:同 playerId 同 errorCode 1 分钟内仅首条 + 计数(用 `MemoryCache` 1 分钟 TTL)
|
||
- [ ] 1.8.12 用 1.9 注册 `gm SetLogLevel <module> <level>` 运行时调级(写 `NLogManager.Configuration.LoggingRules`)
|
||
- [ ] 1.8.13 实现敏感字段脱敏(配置化 MaskFields,proto attribute 标记或 Json 路径配置)
|
||
- [ ] 1.8.14 实现客户端日志桥接 `C2G_ClientLogReport` + 服务端 `Logs/Client/{date}.log`(默认开发期开启,Release 关闭)
|
||
- [ ] 1.8.15 日志单测:扩展方法格式正确 / 模块过滤 / 同步落盘验证
|
||
|
||
### 1.9 GM 业务命令接入(**完全复用 `cn.etetet.yiuigm` + `cn.etetet.console`,零框架开发**)
|
||
- [x] 1.9.1 **复用确认**:
|
||
- `cn.etetet.yiuigm`:已有 `[GM]` 属性 + `IGMCommand` 接口 + 完整 GMPanel UI(参数 6 类型)
|
||
- `cn.etetet.console`:已有 `[ConsoleHandler]` 属性 + `IConsoleHandler` 接口 + stdin REPL
|
||
- **不新建 GM 框架**,仅按这两个属性写业务 GM 类
|
||
- [x] 1.9.2 新建文档 `Doc/GM-Commands.md`:约定服务端 console 用 `cn.etetet.console`、客户端 panel 用 `cn.etetet.yiuigm`、共享业务实现走 `GMHelper` 静态类
|
||
- [x] 1.9.3 新建 `cn.etetet.gachainvoke` / `cn.etetet.heroinvoke` / `cn.etetet.baginvoke` 跨包调用契约(用 `cn.etetet.yiuiinvoke`),让 GM 类不需要直接依赖业务包
|
||
- [x] 1.9.4 在 `cn.etetet.logging` 提供 `Log.AuditGM(operator, action, targetPlayer, args, success)` 专用方法,所有 GM 业务类调用前自动审计
|
||
- [ ] 1.9.5 用 `#if ENABLE_GM` 包裹所有 `[GM]` 业务类(参考 `cn.etetet.yiuigm/Scripts/HotfixView/Client/GM/GM_Command_Test.cs` 已有的条件编译模式)
|
||
- [x] 1.9.6 编写 CI 校验脚本 `tools/ci/check-release-no-gm.ps1`:Release IL 不含 `EGMType` 字符串
|
||
- [x] 1.9.7 编写 CI 校验脚本 `tools/ci/check-rpc-has-gm.ps1`:扫描每个 `*Handler.cs`,确认对应 `GM_*.cs` 或 `[ConsoleHandler]` 类存在
|
||
|
||
### 1.10 错误码体系
|
||
- [x] 1.10.1 新建 `HeroBagErrorCode.cs` 集中定义所有 Hero/Bag/Equipment/Gacha 错误码(数值段位约定:3xxxx Hero、4xxxx Bag、5xxxx Equip、6xxxx Gacha)
|
||
- [x] 1.10.2 客户端添加 `ErrorMessageHelper.GetMessage(int errorCode)` 本地化文案;**展示走** `cn.etetet.yiuitips` 的 `TipsHelper.OpenSync<TipsTextViewComponent>(scene, ErrorMessageHelper.GetMessage(code))`,**禁止自写 Toast**
|
||
- [x] 1.10.3 服务端 Handler 返回错误码时自动写 L3 错误日志
|
||
|
||
### 1.11 通用 Item 基础设施(**整个系统的基础**)
|
||
- [x] 1.11.1 在 Luban `Common.bean.xml` 定义 `RewardEntry { itemId, count }` / `CostEntry { itemId, count }`
|
||
- [x] 1.11.2 在 proto `Common_3000.proto` 定义同名 message(与 Luban 一致)
|
||
- [x] 1.11.3 在 `BagComponentSystem` 上预留 `AddItems(rewards)` / `RemoveItems(costs)` / `GetItemCount(itemId)` / `HasItems(costs)` 接口签名
|
||
- [ ] 1.11.4 在 Luban `ItemConfig` 中提前定义货币 itemId:1=金币、2=钻石、3=PvP币(写入 Datas/ItemConfig.xlsx 首批条目)
|
||
- [x] 1.11.5 写设计文档约定:**全游戏所有可获得资源必须用 itemId 表示,禁止在 Player Entity 上加货币字段**
|
||
|
||
### 1.12 GM Web Console(策划/产品可见)— **基于 console + http 包,最小化新增**
|
||
- [x] 1.12.1 **复用确认**:服务端 stdin REPL 已现成(`cn.etetet.console`),`cn.etetet.http` 已现成轻量 HTTP Server
|
||
- [x] 1.12.2 新建 `tools/gm-web/index.html`(input + fetch + 命令历史)作为静态资源
|
||
- [x] 1.12.3 实现 `[HttpHandler("/gm")] HttpGMHandler`:转发 POST body 到 `ConsoleComponentSystem` 命令分发器(**直接复用现有 console 命令注册**)
|
||
- [ ] 1.12.4 简单密码校验(StartConfig 配 `GMWebPassword`)
|
||
- [x] 1.12.5 默认仅 Debug 包加载,Release 不监听端口
|
||
|
||
### 1.13 ROK 配置迁移工具链(**新任务:取代手工录入**)
|
||
- [x] 1.13.1 **背景沉淀**:在 `Doc/ROK-Config-Source.md` 记录数据源真相:ROK 服务端只有 `Configs.data`(Lua 表,tabtoy 生成);ROK 客户端有加密 SQLite + 205 个 `*Define.cs` DTO;**无 Excel 源文件**
|
||
- [x] 1.13.2 拉取姐妹工程 `E:/Game/gmd/Tools/export_hero_json_from_configs_data.py` 作为基础(已验证可解析 Hero/HeroLevel/HeroStar)
|
||
- [x] 1.13.3 在 `tools/rok-migrate/export.py` 扩展支持本次需要的表(Hero/HeroLevel/HeroStar/HeroSkill/HeroTalent/Item/Equip/GachaPool/GachaWeight 等)
|
||
- [x] 1.13.4 在 `tools/rok-migrate/define_to_bean.py` 写 Roslyn-free 文本扫描器:读 `ROK/Client/.../*Define.cs` → 提取字段名 + 类型 → 输出 Luban `*.bean.xml`
|
||
- [x] 1.13.5 在 `tools/rok-migrate/json_to_luban_xlsx.py` 写 JSON → Luban `*.xlsx` 转换器(首行类型 / 二行字段名 / 三行注释 / 数据行)
|
||
- [x] 1.13.6 一键脚本 `tools/rok-migrate/run.ps1`:bean.xml + xlsx 一次出齐到 `Config/Defines/` 和 `Config/Datas/`
|
||
- [ ] 1.13.7 跑通 Hero + Item 两张表端到端:`run.ps1` → `tools/luban/gen.ps1` → 服务端 `scene.Cfg().TbHero.Get(10001)` 拿到 ROK 真实数据
|
||
- [x] 1.13.8 文档化:每张表的 ROK 源字段 ↔ Luban bean 字段映射表
|
||
|
||
---
|
||
|
||
## 2. P1 - 抽卡 + 通用 Item + 英雄基础(**纯服务端 + GM 验证,零 UI**)
|
||
|
||
> 执行顺序调整(2026-05-27 用户确认):**先跳过 2.0 抽卡**,优先做 2.1+ 英雄/背包;抽卡并入“酒馆(Tavern)发奖”语义后再回填 2.0。
|
||
|
||
### 2.0 抽卡系统(**已延期:并入酒馆 Tavern 语义后再落地**)
|
||
|
||
#### 2.0.1 抽卡 Luban 配置
|
||
- [ ] 2.0.1.1 在 `Config/Defines/Gacha.bean.xml` 定义 `GachaPool` bean(id / name / singleCost / multiCost / guaranteeCount / guaranteeRare / multiGuaranteeRare / openTime / closeTime)
|
||
- [ ] 2.0.1.2 定义 `GachaWeightItem` bean(poolId / entryIndex / reward: RewardEntry / weight / rare)
|
||
- [ ] 2.0.1.3 创建 `Config/Datas/GachaPool.xlsx` + `GachaWeight.xlsx`,填充测试卡池:1 个普池(消耗金币)+ 1 个高级池(消耗钻石)
|
||
- [ ] 2.0.1.4 跑 gen.ps1 验证代码生成 + json 输出
|
||
|
||
#### 2.0.2 抽卡协议
|
||
- [ ] 2.0.2.1 创建 `Packages/cn.etetet.gacha/Proto/GachaOuter_C_3200.proto`
|
||
- [ ] 2.0.2.2 定义 DTO:`GachaPoolStats { poolId, totalCount, guaranteeRemain }`
|
||
- [ ] 2.0.2.3 定义 RPC:`C2G_GachaDraw { poolId, drawType }` / `R2C_GachaDraw { error, rewards: repeated RewardEntry, isNewHero: repeated bool }`
|
||
- [ ] 2.0.2.4 定义推送:`Gacha_GachaInfo { stats: repeated GachaPoolStats }`
|
||
- [ ] 2.0.2.5 运行 Proto2CS 生成代码
|
||
|
||
#### 2.0.3 服务端 GachaComponent
|
||
- [ ] 2.0.3.1 创建 `cn.etetet.gacha/Scripts/Model/Server/GachaComponent`(挂在 Player 下)
|
||
- [ ] 2.0.3.2 含 `Dictionary<int, GachaPoolStats> PoolStats`,标记 `[MongoElement]` 持久化
|
||
- [ ] 2.0.3.3 实现 `GachaComponentSystem.Draw(poolId, drawType)` 完整算法(消耗→随机→保底→发奖→更新统计→审计日志)
|
||
- [ ] 2.0.3.4 实现 `UpdateGuarantee(poolId, rare)` 保底计数更新
|
||
- [ ] 2.0.3.5 实现 `ReplaceWithGuaranteed(rewards, weights, minRare)` 强制保底
|
||
- [ ] 2.0.3.6 实现 `RandomNumberGenerator` 工具类(CSPRNG `System.Security.Cryptography.RandomNumberGenerator`),**禁止使用 UnityEngine.Random**
|
||
- [ ] 2.0.3.7 抽卡过程中**严格走** `BagComponentSystem.RemoveItems` 扣消耗 + `AddItems` 发奖励,禁止绕过
|
||
|
||
#### 2.0.4 服务端 Handler + 日志
|
||
- [ ] 2.0.4.1 `C2G_GachaDrawHandler`:继承自带埋点的 RpcMessageHandler 基类(入口/出口自动 L1 日志)
|
||
- [ ] 2.0.4.2 抽卡完成后写 L2 审计日志:playerId / poolId / drawType / cost / rewards / guaranteeRemain / reqId
|
||
- [ ] 2.0.4.3 推送 R2C_GachaDraw + Gacha_GachaInfo
|
||
- [ ] 2.0.4.4 接口限速:单玩家 100ms 最多 1 次请求,超过返回 ERR_RATE_LIMIT 并写 L3
|
||
- [ ] 2.0.4.5 服务端单元测试:模拟 10000 次抽卡,验证概率分布与配置误差 < 1%
|
||
- [ ] 2.0.4.6 服务端单元测试:连续 90 抽 1 次不出保底品质 → 第 90 次必出
|
||
- [ ] 2.0.4.7 服务端单元测试:自然抽到保底品质 → 计数重置
|
||
|
||
#### 2.0.5 客户端 GachaComponent
|
||
- [ ] 2.0.5.1 创建 `cn.etetet.gacha/Scripts/Model/Client/GachaComponent`
|
||
- [ ] 2.0.5.2 实现 `Gacha_GachaInfoHandler` 同步保底进度
|
||
- [ ] 2.0.5.3 发布 `GachaUpdatedEvent`
|
||
|
||
#### 2.0.6 抽卡 GM 命令(**P1 唯一验证手段**)
|
||
- [ ] 2.0.6.1 `gm Gacha <poolId> <times>` 触发抽卡(默认 1 次,可指定 N 次)
|
||
- [ ] 2.0.6.2 `gm SimulateGacha <poolId> <times>` 服务端模拟 N 次抽卡(不修改真实玩家数据),输出概率统计
|
||
- [ ] 2.0.6.3 `gm ShowGachaStats [playerId]` 输出玩家所有卡池的累计/保底进度
|
||
- [ ] 2.0.6.4 `gm ResetGachaGuarantee <poolId> [playerId]` 重置保底计数
|
||
- [ ] 2.0.6.5 `gm SetGachaCount <poolId> <count> [playerId]` 直接设置抽数(测试用)
|
||
|
||
#### 2.0.7 抽卡验收(GM + 日志)
|
||
- [ ] 2.0.7.1 `gm AddItem 2 100` → `gm Gacha 1 1` → `gm ShowBag` → 钻石 -1,奖励入包
|
||
- [ ] 2.0.7.2 `gm Gacha 1 10` → 收到 10 个奖励,钻石按十连消耗扣除
|
||
- [ ] 2.0.7.3 `gm SetGachaCount 1 89` → `gm Gacha 1 1` 必出保底品质(查审计日志确认)
|
||
- [ ] 2.0.7.4 自然抽到保底品质 → `gm ShowGachaStats` 显示保底计数重置为 0
|
||
- [ ] 2.0.7.5 不同卡池保底独立:抽卡池 A 不影响卡池 B 进度
|
||
- [ ] 2.0.7.6 `gm SimulateGacha 1 10000` 概率与配置误差 < 1%
|
||
- [ ] 2.0.7.7 `gm Snapshot before` → `gm Gacha 1 10` → 重启 → `gm DumpPlayer` 对比 → 数据完整恢复
|
||
- [ ] 2.0.7.8 篡改 reqId / 客户端伪造字段 → 日志记录但服务端结果不受影响
|
||
- [ ] 2.0.7.9 **端到端验证**:`gm Gacha 1 100` → 抽到英雄碎片 → `gm SummonHero` → 招募成功
|
||
|
||
### 2.1 英雄 Luban 配置
|
||
- [x] 2.1.1 在 `Config/Defines/Hero.bean.xml` 定义 `Hero` bean(含 List<int> skillIds、List<int> talentIds、fragmentItemId、recruitLimit 等)
|
||
- [x] 2.1.2 定义 `HeroLevel` / `HeroSkill` / `HeroSkillEffect` / `HeroSkillLevel` bean
|
||
- [ ] 2.1.3 在 bean 中使用 `List<RewardEntry>` / `List<CostEntry>` 表达消耗与奖励
|
||
- [x] 2.1.4 填充测试数据:每张表 5 个条目
|
||
- [ ] 2.1.5 跑 gen.ps1 验证客户端/服务端读到 `scene.Cfg().TbHero.Get(heroId)`
|
||
|
||
### 2.2 英雄协议
|
||
- [x] 2.2.1 创建 `Packages/cn.etetet.hero/Proto/HeroOuter_C_3000.proto`
|
||
- [x] 2.2.2 定义 DTO:`HeroInfo`、`SkillInfo`
|
||
- [x] 2.2.3 定义 RPC:`C2G_SummonHero` / `C2G_HeroAddExp` / `C2G_HeroSkillUp` 及响应
|
||
- [x] 2.2.4 定义推送:`Hero_HeroInfo`、`Hero_HeroInfoList`
|
||
- [ ] 2.2.5 运行 Proto2CS
|
||
|
||
### 2.3 服务端 Hero 实体与持久化
|
||
- [x] 2.3.1 创建 `Hero` Entity(含 level/exp/star/starExp/skills/talents)
|
||
- [x] 2.3.2 创建 `HeroComponent`(挂在 Player 下)
|
||
- [x] 2.3.3 实现 Awake/Destroy + AddHero / RemoveHero / GetHero / GetAllHeroes
|
||
- [x] 2.3.4 给所有持久化字段加 `[MongoElement]`
|
||
- [ ] 2.3.5 验证:登录后服务端能加载玩家所有 Hero 实体
|
||
|
||
### 2.4 服务端业务 Handler + 日志
|
||
- [x] 2.4.1 `C2G_SummonHeroHandler`:校验碎片(`BagComponentSystem.HasItems`)+ 不重复 + 上限 → AddHero + `BagComponentSystem.RemoveItems` 扣碎片
|
||
- [x] 2.4.2 `C2G_HeroAddExpHandler`:校验道具 → 累加经验 → 触发升级 → 受星级上限约束
|
||
- [x] 2.4.3 `C2G_HeroSkillUpHandler`:校验道具 → 技能等级 +1 → 受 maxLevel 约束
|
||
- [x] 2.4.4 `HeroNoticeHelper.NotifyHeroUpdate(player, hero)` 封装推送
|
||
- [x] 2.4.5 登录后自动推送 `Hero_HeroInfoList`
|
||
- [x] 2.4.6 所有 Handler 走 RpcMessageHandler 基类自动埋点
|
||
- [x] 2.4.7 关键操作(招募、首次满级、首次满技能)写 L2 审计
|
||
|
||
### 2.5 客户端 Hero 缓存
|
||
- [x] 2.5.1 创建客户端 `HeroComponent` 缓存
|
||
- [x] 2.5.2 实现 `Hero_HeroInfoHandler` 增量更新 / `Hero_HeroInfoListHandler` 全量同步
|
||
- [x] 2.5.3 发布 `HeroUpdatedEvent` / `HeroListUpdatedEvent`
|
||
|
||
### 2.6 战力计算(客户端 + 服务端共享)
|
||
- [x] 2.6.1 在 `cn.etetet.hero/Scripts/Model/Share/HeroPowerCalculator.cs` 实现 GetBaseScore / GetLevelScore / GetSkillScore / GetPower
|
||
- [x] 2.6.2 单元测试:相同数据 → 客户端与服务端算出相同战力
|
||
|
||
### 2.7 英雄 GM 命令(**P1 唯一验证手段**)
|
||
- [x] 2.7.1 `gm AddHero <heroId>` 直接创建英雄
|
||
- [x] 2.7.2 `gm SetHeroLevel <heroId> <level>` 设置等级
|
||
- [x] 2.7.3 `gm SetHeroSkill <heroId> <skillIndex> <level>` 设置技能等级
|
||
- [x] 2.7.4 `gm ShowHeroes [playerId]` 输出 jsonl
|
||
- [x] 2.7.5 `gm SummonHero <heroId>` 走完整招募流程(验证碎片消耗逻辑)
|
||
- [x] 2.7.6 `gm HeroAddExp <heroId> <itemId> <count>` 走完整升级流程
|
||
|
||
### 2.8 P1 验收(GM + 日志,零 UI)
|
||
- [ ] 2.8.1 `gm AddItem <碎片itemId> 30` → `gm SummonHero <heroId>` → 招募成功 + 碎片扣 30
|
||
- [ ] 2.8.2 `gm HeroAddExp <heroId> <expBookId> 100` → 等级提升 + 经验书减少
|
||
- [ ] 2.8.3 升级技能 → 技能等级 +1 → 战力变化(用 `gm ShowHeroes` 对比)
|
||
- [ ] 2.8.4 重启客户端 + 服务端 → `gm DumpPlayer` 输出对比 → 数据完整恢复
|
||
- [ ] 2.8.5 审计日志:`tail Logs/Audit/Hero/2026-*.jsonl` 含完整招募/升级记录
|
||
- [ ] 2.8.6 错误日志:尝试用 0 碎片招募 → L3 日志含错误码 + reqId
|
||
|
||
---
|
||
|
||
## 3. P2 - 背包与装备穿戴(**服务端 + GM 验证,零 UI**)
|
||
|
||
### 3.1 背包/装备 Luban 配置
|
||
- [ ] 3.1.1 完善 `Config/Defines/Item.bean.xml`(含 BagItemType 枚举、useType 枚举)
|
||
- [ ] 3.1.2 定义 `Equip` bean(含 List<AttrEntry> 属性、equipItemType 枚举)
|
||
- [ ] 3.1.3 填充约 30 个测试道具 + 10 个测试装备到 xlsx
|
||
- [ ] 3.1.4 跑 gen.ps1 验证
|
||
|
||
### 3.2 背包/装备协议
|
||
- [x] 3.2.1 创建 `Packages/cn.etetet.bag/Proto/BagOuter_C_3100.proto`
|
||
- [x] 3.2.2 定义 DTO:`ItemInfo`、`EquipSlot`
|
||
- [x] 3.2.3 定义 RPC:`C2G_UseItem` / `C2G_HeroWearEquip` / `C2G_TakeOffEquip`
|
||
- [x] 3.2.4 定义推送:`Item_ItemInfo`、`Item_ItemInfoList`、`Item_RemoveItem`
|
||
- [ ] 3.2.5 运行 Proto2CS
|
||
|
||
### 3.3 服务端 BagComponent(**通用 Item 入口**)
|
||
- [x] 3.3.1 创建 `cn.etetet.bag/Scripts/Model/Server/BagComponent`
|
||
- [x] 3.3.2 创建 `ItemUnit` 子 Entity(含 itemIndex / itemId / count / heroId)
|
||
- [x] 3.3.3 实现 `AddItem(itemId, count)` - 自动堆叠(非装备类)/ 新建条目(装备类)
|
||
- [x] 3.3.4 实现 `RemoveItem(itemId, count)` 与 `RemoveItemByIndex(itemIndex)`
|
||
- [x] 3.3.5 实现 `SetHeroIdOnEquip(itemIndex, heroId)`
|
||
- [x] 3.3.6 实现 `AddItems(List<RewardEntry>)` / `RemoveItems(List<CostEntry>)` / `HasItems(List<CostEntry>)` 批量接口(事务性:任一失败全失败)
|
||
- [x] 3.3.7 实现 `GetItemCount(itemId)` 通用查询
|
||
- [x] 3.3.8 所有变更通过 `BagNoticeHelper.NotifyItemChange` 推送
|
||
- [x] 3.3.9 资源大额变动(>1000)写 L2 审计日志
|
||
- [x] 3.3.10 玩家首次登录通过 `InitialResourceConfig` 调 `AddItems` 发初始金币/钻石(**走通用接口**)
|
||
|
||
### 3.4 服务端业务 Handler
|
||
- [ ] 3.4.1 `C2G_UseItemHandler`:通用 dispatcher,按 ItemConfig.useType 分发
|
||
- [x] 3.4.2 `C2G_HeroWearEquipHandler`:校验装备 + 卸旧穿新 + 同步 Hero + Item
|
||
- [x] 3.4.3 `C2G_TakeOffEquipHandler`:清空槽位 + 装备 heroId = 0
|
||
- [x] 3.4.4 登录后推送 `Item_ItemInfoList`
|
||
|
||
### 3.5 客户端 BagComponent
|
||
- [x] 3.5.1 创建客户端 `BagComponent`(按 6 大类分类缓存)
|
||
- [x] 3.5.2 实现 `Item_ItemInfoHandler` / `Item_ItemInfoListHandler`
|
||
- [ ] 3.5.3 实现新道具标记本地存储(OctoberStudio SaveManager 集成)
|
||
- [x] 3.5.4 发布 `ItemChangedEvent` / `BagUpdatedEvent`
|
||
|
||
### 3.6 背包/装备 GM 命令
|
||
- [x] 3.6.1 `gm AddItem <itemId> <count>` 通用发放(已在 P0/P1 准备)
|
||
- [x] 3.6.2 `gm RemoveItem <itemId> <count>` 通用扣除
|
||
- [x] 3.6.3 `gm ShowBag [playerId]` 输出 jsonl
|
||
- [x] 3.6.4 `gm BagClear [playerId]` 清空背包(保留绑定装备)
|
||
- [x] 3.6.5 `gm HeroWearEquip <heroId> <itemIndex>` 穿戴
|
||
- [x] 3.6.6 `gm HeroTakeOffEquip <heroId> <slotIndex>` 卸下
|
||
- [x] 3.6.7 `gm UseItem <itemIndex> [count]` 使用道具
|
||
|
||
### 3.7 P2 验收(GM + 日志)
|
||
- [ ] 3.7.1 `gm AddItem <装备itemId> 1` → `gm ShowBag` 出现 → 查属性
|
||
- [ ] 3.7.2 `gm HeroWearEquip` → `gm ShowHeroes` 看战力变化
|
||
- [ ] 3.7.3 `gm HeroTakeOffEquip` → 装备回背包未绑定状态
|
||
- [ ] 3.7.4 切换英雄穿同一装备 → 自动从原英雄卸下
|
||
- [ ] 3.7.5 重启验证装备穿戴关系持久化
|
||
- [ ] 3.7.6 通用 Item 测试:金币、钻石、碎片、装备、材料都用 `gm AddItem` 发放,全部走 BagComponent.AddItems
|
||
|
||
---
|
||
|
||
## 4. P3 - 高级养成(**服务端 + GM 验证,零 UI**)
|
||
|
||
### 4.1 升星系统
|
||
- [ ] 4.1.1 Luban 配置 `HeroStar` / `HeroStarExp` bean
|
||
- [ ] 4.1.2 proto `C2G_HeroStarUp` / `R2C_HeroStarUp`
|
||
- [ ] 4.1.3 服务端 Handler:扣升星材料 + 幸运计算 + 星级提升
|
||
- [ ] 4.1.4 GM 命令:`gm HeroStarUp <heroId>` 走完整流程,`gm SetHeroStar <heroId> <star>` 直接设置
|
||
- [ ] 4.1.5 单元测试:连续升星,验证幸运系数概率分布
|
||
|
||
### 4.2 觉醒系统
|
||
- [ ] 4.2.1 给 Hero Entity 增加 `IsAwakening` 字段
|
||
- [ ] 4.2.2 proto `C2G_HeroAwake`
|
||
- [ ] 4.2.3 服务端 Handler:校验前 4 技能满级 + 扣觉醒材料 + 解锁第 5 技能
|
||
- [ ] 4.2.4 GM 命令:`gm HeroAwake <heroId>` / `gm ForceAwake <heroId>`(跳过校验,测试用)
|
||
|
||
### 4.3 天赋系统
|
||
- [ ] 4.3.1 Luban 配置 `HeroTalent` / `HeroTalentTree` / `HeroTalentMastery` bean
|
||
- [ ] 4.3.2 给 Hero Entity 增加 `TalentIndex` + 3 套 `TalentTreeData`
|
||
- [ ] 4.3.3 proto `C2G_TalentUp` / `C2G_ChangeTalentIndex` / `C2G_ResetTalent`
|
||
- [ ] 4.3.4 服务端 Handler:扣点数 + 节点等级 +1 + 前置依赖校验
|
||
- [ ] 4.3.5 GM 命令:`gm TalentUp <heroId> <nodeId>` / `gm TalentReset <heroId>` / `gm ChangeTalentIndex <heroId> <index>`
|
||
|
||
### 4.4 装备锻造
|
||
- [ ] 4.4.1 Luban 配置扩展:`Equip` bean 增加 `List<CostEntry> requireMaterials` / `List<RewardEntry> resolveRewards`
|
||
- [ ] 4.4.2 proto `C2G_MakeEquipment` / `C2G_ResolveEquipment` / `C2G_MixMaterial`
|
||
- [ ] 4.4.3 服务端 Handler:扣材料 + 创建装备 / 分解装备 + 返还
|
||
- [ ] 4.4.4 GM 命令:`gm MakeEquip <equipId>` / `gm ResolveEquip <itemIndex>` / `gm MixMaterial <materialId>`
|
||
|
||
### 4.5 P3 验收
|
||
- [ ] 4.5.1 `gm HeroStarUp` 重复使英雄升至 5 星
|
||
- [ ] 4.5.2 前 4 技能升满 → `gm HeroAwake` 成功 → 第 5 技能可用
|
||
- [ ] 4.5.3 `gm TalentUp` 学习天赋 → `gm ChangeTalentIndex` 切换天赋页 → `gm TalentReset` 重置
|
||
- [ ] 4.5.4 `gm MakeEquip` 用图纸 + 材料锻造装备 → 装备出现在背包
|
||
- [ ] 4.5.5 `gm ResolveEquip` 分解装备 → 材料返还
|
||
- [ ] 4.5.6 全流程审计日志完整:每个操作都有对应 jsonl 记录
|
||
|
||
---
|
||
|
||
## 5. P4 - UI 集中实现(**深度复用 YIUI 全套子包,零造轮**)
|
||
|
||
> **前提**:P0-P3 服务端业务已稳定,所有协议、错误码、推送已定型。
|
||
>
|
||
> **强制约束**(详见 `Doc/ET-Packages-Audit.md` 中 "YIUI 框架" 一节):
|
||
> - 面板打开/关闭**只用** `scene.YIUIRoot().OpenPanelAsync<T>()`,**禁止自建 PanelHelper**
|
||
> - 长列表**只用** `YIUILoopScrollChild.SetDataRefresh`,**禁止自写 ScrollRect 池化**
|
||
> - 按钮事件**只用** `UIEventBind*` + `[YIUIInvoke]`,**禁止 `Button.onClick.AddListener`**
|
||
> - 数据 → UI 刷新**只用** `u_Data*.SetValue` + `UIDataBind*`,**禁止手写 Refresh 样板**
|
||
> - 弹窗**只用** `TipsHelper.OpenWait/OpenSync`,**禁止自写 ConfirmDialog/Toast**
|
||
> - 红点**只在叶子节点 `SetCount`**,父节点 DAG 自动汇总
|
||
|
||
### 5.1 YIUI 通用组件准备
|
||
- [ ] 5.1.1 复用现有 `CommonHeader` + `YIUICloseCommon`(不再造顶栏/关闭)
|
||
- [ ] 5.1.2 创建 `BagItemUICommon.prefab`:图标用 `UIDataBindImage` + `u_DataIcon`、数量用 `UIDataBindText` + `u_DataCount`、品质边框用 `UIDataBindSprite` + `u_DataQualityFrame`、新道具标记用 `UIDataBindActive` + `u_DataNew`、不可用用 `UIDataBindGray` + `u_DataEnabled`(`cn.etetet.yiuieffect`)
|
||
- [ ] 5.1.3 `BagItemUICommonComponentSystem.Show(itemConfigId, count, options)` 入口:内部全走 `u_Data*.SetValue`,**禁止手写 `Image.sprite = xxx`**
|
||
- [ ] 5.1.4 多尺寸预设:`BagItemUICommon_Small/Mid/Large.prefab` 三个变体(仅 RectTransform 不同),共享同一 Component 代码
|
||
- [ ] 5.1.5 点击事件统一发布 `BagItemClickedEvent`(ET EventSystem)让宿主 Panel 处理
|
||
|
||
### 5.2 抽卡 UI
|
||
- [ ] 5.2.1 用 YIUI 自动化工具生成 `GachaPanel` Component + System
|
||
- [ ] 5.2.2 Prefab 用 `UIEventBindClick` + `[YIUIInvoke]` 接线(单抽/十连/切池按钮);按钮可用性用 `UIDataBindGray` + `u_DataCanDraw`(钻石不足自动灰显)
|
||
- [ ] 5.2.3 保底进度用 `UIDataBindSlider` + `u_DataGuaranteeProgress` + `UIDataBindText` + `u_DataGuaranteeText`
|
||
- [ ] 5.2.4 卡池列表用 `cn.etetet.yiuiloopscrollrectasync` 的 `YIUILoopScrollChild.SetDataRefresh(poolList)`
|
||
- [ ] 5.2.5 RPC 期间挡点击:`scene.YIUIMgr().BanLayerOptionForever()` 开 → 收响应 close 关
|
||
- [ ] 5.2.6 抽卡 CD 用 `CountDownMgr`(`cn.etetet.yiuiframework`),**禁止自建 Timer**
|
||
- [ ] 5.2.7 `GachaResultPanel`:10 个奖励槽用 `BagItemUICommon` 渲染;新英雄高亮用 `UIEffect` 发光 + `UIParticle` 粒子(`cn.etetet.yiuieffect`)
|
||
- [ ] 5.2.8 "确认消耗"用 `TipsHelper.OpenWait<TipsMessageViewComponent>(scene, "消耗 X 钻石?")` 模态等待
|
||
- [ ] 5.2.9 抽卡完成飘字用 `TipsHelper.OpenSync<TipsTextViewComponent>(scene, "招募成功")`
|
||
|
||
### 5.3 英雄 UI
|
||
- [ ] 5.3.1 `HeroListPanel` 长列表用 `YIUILoopScrollChild.SetDataRefresh(heroList)` + `ReRenderer` 增量刷新
|
||
- [ ] 5.3.2 三段分组(已拥有 / 可召唤 / 未集齐):在数据层 IList 中插入 Header Item,**不改 Loop 框架**
|
||
- [ ] 5.3.3 `HeroListItem` 字段(等级/星级/战力/碎片数)全用 `u_Data*` + `UIDataBind*`
|
||
- [ ] 5.3.4 SortType 切换:`UIEventBindToggleGroup` + 重排 IList → `Loop.ReRenderer()`
|
||
- [ ] 5.3.5 `HeroDetailPanel`:3D 模型用 `YIUI3DDisplayChild.ShowAsync("HeroModel_10001")`(`cn.etetet.yiui3ddisplay`),**禁止自建 RT/Camera**
|
||
- [ ] 5.3.6 升级/升星/技能/觉醒 按钮 → `UIEventBindClick` + `[YIUIInvoke]` → RPC
|
||
- [ ] 5.3.7 召唤按钮带"消耗 N 碎片"模态:`TipsHelper.OpenWait`
|
||
|
||
### 5.4 背包 UI
|
||
- [ ] 5.4.1 `BagPanel` 6 Tab + `YIUILoopScrollChild`(每 Tab 复用同一 Loop,切 Tab 仅换 IList)
|
||
- [ ] 5.4.2 道具 Tooltip:自建 `BagItemTooltipView`(**tips 包无现成 Tooltip View**),容器走 `TipsHelper.Open<BagItemTooltipViewComponent>(scene, itemId)` 承载
|
||
- [ ] 5.4.3 Tab 切换:`UIEventBindToggleGroup` 自动响应 + `Loop.SetDataRefresh(filteredList)`
|
||
- [ ] 5.4.4 点击 Item → 发布 `BagItemClickedEvent` → Panel 弹 Tooltip → "使用 / 穿戴"按钮走 RPC
|
||
- [ ] 5.4.5 操作确认用 `TipsHelper.OpenWait`;操作结果飘字用 `TipsHelper.OpenSync`
|
||
|
||
### 5.5 装备穿戴 UI
|
||
- [ ] 5.5.1 `HeroDetailPanel` 8 装备槽:每个槽 = `BagItemUICommon` + `UIEventBindClick`
|
||
- [ ] 5.5.2 `EquipSelectPanel`:`YIUILoopScrollChild` 列表 + 按 SubType 过滤 IList
|
||
- [ ] 5.5.3 点击空槽位 → `OpenPanelParamAsync<EquipSelectPanelComponent>(slotIndex, subType)`
|
||
- [ ] 5.5.4 点击已穿 → `TipsHelper.OpenWait` "卸下 / 替换 / 取消"三选一
|
||
|
||
### 5.6 天赋 UI
|
||
- [ ] 5.6.1 `HeroTalentPanel` 天赋树可视化:**YIUI 无图编辑器组件**,需自绘(普通 RectTransform 拼节点 + LineRenderer 连线)
|
||
- [ ] 5.6.2 节点状态用 `UIDataBindActive`(已点亮)+ `UIDataBindGray`(不可点)
|
||
- [ ] 5.6.3 切换 / 重置交互:`UIEventBindClick` + `[YIUIInvoke]` + 二次确认走 `TipsHelper.OpenWait`
|
||
|
||
### 5.7 锻造 UI
|
||
- [ ] 5.7.1 `EquipForgePanel` 3 Tab:用 `UIEventBindToggleGroup`
|
||
- [ ] 5.7.2 材料 / 产物用 `BagItemUICommon` 渲染
|
||
- [ ] 5.7.3 锻造按钮:足够材料时正常,不足时 `UIDataBindGray` 灰显
|
||
- [ ] 5.7.4 成功后 `TipsHelper.OpenSync` 飘字 + `UIParticle` 粒子动画
|
||
|
||
### 5.8 红点系统(**完全复用 `cn.etetet.yiuireddot`**)
|
||
- [ ] 5.8.1 扩展 `ERedDotKeyType` 枚举:HeroCanUpgrade / HeroCanStarUp / HeroCanAwake / TalentHasPoint / BagHasNew / BagHasUsable / GachaHasFree / EquipCanForge 等
|
||
- [ ] 5.8.2 在 `UIRedDotConfigDAG` 编辑器配置父子关系:HeroEntry = 4 个 Hero* 之和;BagEntry = Bag* 之和;MainMenu = HeroEntry + BagEntry + GachaEntry + EquipForge
|
||
- [ ] 5.8.3 业务侧**只写叶子**:`RedDotMgr.Inst.SetCount(ERedDotKeyType.HeroCanUpgrade, count)`,**禁止给中间节点 SetCount**
|
||
- [ ] 5.8.4 Panel / Button 上挂 `RedDotBind` 选 Key,**不写代码**自动显隐
|
||
- [ ] 5.8.5 红点检查逻辑统一封装到 `RedDotChecker.RefreshAll()`,业务变化时调用
|
||
|
||
### 5.9 UI 与 OctoberStudio 集成
|
||
- [ ] 5.9.1 在 OctoberStudio Main Menu 添加"英雄"/"背包"/"抽卡"三个按钮(普通 Button,因 OctoberStudio 不是 YIUI 体系)
|
||
- [ ] 5.9.2 按钮 OnClick → `scene.YIUIRoot().OpenPanelAsync<HeroListPanelComponent>()`(**不写 PanelHelper**)
|
||
- [ ] 5.9.3 处理"未联网"状态:按钮 OnClick 前检查 Session → 未连接时 `TipsHelper.OpenSync<TipsTextViewComponent>(scene, "请先登录")`
|
||
|
||
### 5.10 P4 验收
|
||
- [ ] 5.10.1 全功能 UI 走查:抽卡 → 招募 → 升级 → 穿戴 → 升星 → 觉醒 → 天赋 → 锻造
|
||
- [ ] 5.10.2 UI 操作与 GM 命令产生相同的业务结果
|
||
- [ ] 5.10.3 性能:背包列表 1000+ 道具滑动 60 FPS(`YIUILoopScrollChild` 池化保证)
|
||
- [ ] 5.10.4 红点正确显示与清除(叶子 SetCount → 父节点自动汇总)
|
||
- [ ] 5.10.5 **代码扫描验证零造轮**(CI 脚本 `tools/ci/check-no-ui-wheel.ps1` 校验业务包内不含 `Button.onClick.AddListener` / `Image.sprite =` / `Resources.Load` / 自写 ScrollRect 池化)
|
||
|
||
---
|
||
|
||
## 6. 跨阶段任务
|
||
|
||
### 6.1 单元测试与集成测试
|
||
- [ ] 6.1.1 服务端 Handler 单元测试覆盖率 ≥ 60%
|
||
- [ ] 6.1.2 客户端 Component 缓存逻辑测试
|
||
- [ ] 6.1.3 协议序列化往返测试
|
||
- [ ] 6.1.4 DBComponent 持久化测试(内存模式 + MongoDB 模式各跑一遍)
|
||
- [ ] 6.1.5 端到端测试:登录 → 抽卡 → 招募 → 升级 → 穿戴 → 退出 → 重新登录验证
|
||
- [ ] 6.1.6 抽卡概率回归测试:10000 次抽卡,各品质实际概率与配置误差 < 1%
|
||
- [ ] 6.1.7 抽卡保底回归测试:200 抽,保底触发次数与理论值匹配
|
||
|
||
### 6.2 文档
|
||
- [x] 6.2.1 `Doc/Luban-Guide.md` Luban 使用文档
|
||
- [x] 6.2.2 `Doc/GM-Commands.md` 所有 GM 命令列表 + 用法
|
||
- [x] 6.2.3 `Doc/Logging-Guide.md` 日志体系说明 + grep/jq 示例
|
||
- [x] 6.2.4 `Doc/Hero-System-Guide.md` 业务文档
|
||
- [x] 6.2.5 `Doc/Bag-System-Guide.md`
|
||
- [x] 6.2.6 `Doc/Server-Setup.md`(如何起服 + 起 mongo + 编译)
|
||
- [x] 6.2.7 在工程根 `README.md` 中添加新业务模块入口指引
|
||
|
||
### 6.3 性能与监控
|
||
- [ ] 6.3.1 验证慢操作告警实际生效(构造 100ms+ 的 DB 操作触发 L3 日志)
|
||
- [ ] 6.3.2 验证单服务器 100 个模拟玩家并发登录 + 业务操作不崩溃
|
||
- [ ] 6.3.3 客户端背包列表 1000+ 道具滑动 60 FPS(LoopScrollRect 确保)
|
||
- [ ] 6.3.4 日志写盘性能测试:1000 QPS 业务日志 + 100 QPS 审计日志稳定
|
||
|
||
### 6.4 CI 校验
|
||
- [x] 6.4.1 `tools/ci/check-release-no-gm.ps1` Release IL 不含 GM 代码
|
||
- [x] 6.4.2 `tools/ci/check-rpc-has-gm.ps1` 每个 RPC Handler 有对应 GM 命令
|
||
- [x] 6.4.3 `tools/ci/check-logging.ps1` 每个 Handler 必含日志埋点(继承 RpcMessageHandler 或显式声明)
|
||
- [x] 6.4.4 `tools/ci/check-no-ui-wheel.ps1` 业务包 `cn.etetet.hero/bag/gacha` 内禁止出现 `Button.onClick.AddListener` / `Image.sprite =` / `Resources.Load` / 手写 `ScrollRect` 池化等 YIUI 已覆盖能力的造轮代码
|
||
- [x] 6.4.5 `openspec validate --strict` 在 CI 中跑
|
||
|
||
### 6.5 上线前检查
|
||
- [ ] 6.5.1 所有 GM 命令仅 Debug 包启用,Release 包剥离 + IL 校验通过
|
||
- [x] 6.5.2 错误码本地化覆盖(中文 + 备 EN)
|
||
- [x] 6.5.3 数据备份策略(MongoDB 每日自动备份脚本)
|
||
- [x] 6.5.4 服务端日志切割与归档(L2 转 OSS)
|
||
- [ ] 6.5.5 HybridCLR 在 IL2CPP 包下验证:`ET/HybridCLR/Generate/All`
|
||
- [ ] 6.5.6 概率审计:所有卡池跑 SimulateGacha 10000 次,截图保留
|