Files
ft/CodexOutput/upserver/B3BatchMigration/M2/M2-合批工作稿独立预审意见-2026-09-29.md
Liubing\LB 6813da149a feat(upserver): 保存M2客户端KV迁移至本人统计写检查点
保存独立KV用户服务、固定会话存档与原业务接线,以及C3d资料缓存和C3e本人统计写。
保留原PF/AGG路线、业务公式和时序;榜单读取、好友资料、公告、匹配及首场景闸仍待完成。

验证:ServiceHost两条件各33组646断言;Unity CLI Loop编译0错误269警告。
包含相关方案、评审裁决、测试及状态文档。未操作服务器、正式账号或打包。
2026-09-30 19:19:05 +08:00

74 lines
9.2 KiB
Markdown
Raw 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.
# M2 合批实施方案工作稿 独立预审意见
日期:2026-09-29。审查人:原 ZCode(独立侧)。性质:按[合批节奏裁决](M2-合批审核节奏纠偏-2026-09-29.md)对[合批工作稿](M2-合批实施方案工作稿-2026-09-29.md)及其引用文档([CP2B 合同矩阵](M2-CP2B客户端首场景线上合同待核矩阵-2026-09-29.md)、[集中确认项](M2-客户端致服务端线上合同集中问题单-2026-09-29.md)、[接手卡](M2-新主会话接手卡-2026-09-29.md))做只读事实/问题吸收。**这不是 hash 门控的正式前审**(工作稿尚无受审基线与提示词),也不触发 DP1 单独实施轮。未修改任何生产/测试/服务器代码,未调用正式 101/102,未打包。
## 1. 事实核验结论:工作稿的事实底座成立
对工作稿"同一启动链上的代码事实"表及合同矩阵的关键声明逐项对照源码,**全部属实**:
| 声明 | 源码证据 |
|---|---|
| 启动捕获选择、KV 不建 PlayFabMgr | `SubBoostrap.cs:37-42,103` |
| LoginStage 仍注册旧 PF 类;PFLoginAct 下转设 CustomId/读 IsFormalKV;RequestNextAct KV 不跳场景 | `LoginStage.cs:24,114-115`;`PFLoginAct.cs:21,88,94,102,106,316` |
| SmallGameData 构造要求 IUserService | `DataCenter.SmallGame.cs:26-30` |
| FishingStage 等授权最多 5 秒后 InitData/InitManager | `FishingStage.cs:143` 起:`while (checkAuthorTimes < 10 && !UserService.IsAuthorized)`(10×500ms) |
| 首场景自动链:Survey 抽样、AIHelp、CheckBan、Friend 邀请数、RT 暂停恢复再连+FetchMail、LoginDays 事件 | `FishingStage.cs:233-247`(GetSurveyMatchlist/AiHelp/CheckBan/AutoRestart)、`252-258`(InitEvent→GetInvitedListCount+OnEvent+Pause→OnApplicationResumedAsync)、`283-305`(InitData 末尾 `ConditionType.LoginDays` 事件) |
| 等级榜分数 = 等级×100000+建筑等级,直调 PlayFab 统计 | `DataCenter.LeadboardData.cs:141`(`LV_MODELING=100000`)、`165-186`(`UpdatePlayerStatisticsAsync`/`GetLeaderboardAsync`)、`SetLvData` 计算 |
| Survey 构造读旧 PF 两键、十六进制抽样、领奖写 | `SurveyService.cs:21-22,35,40,56`(`Convert.ToUInt64(userService.UserId,16)`)、`112,136` |
| AntiCheat `playerID=userService.UserId` | `AntiCheatManager.cs` `OnBan` 请求体 |
| Friend 邀请数空请求体 | `FriendService.cs:322-342`(`FriendRequest("GetInvitedListCount","")`,async void) |
| mail/fetch + lastSyncTime + MailCache 按 UserId 分键 | `InGameMailHandler.cs:14,125,309` |
| AIHelp `pfid=UserId`、AutoRestart 读 `IsLogin/LoginTime` | `AiHelpManager.cs:29,45,58`;`AutoRestart.cs:41-43` |
| 新 PlayerId 来源 = 认证会话(`PlayerKVAccountDescriptor.PlayerId`) | `PlayerKVLoginContext.cs:198,319-330`(formal 路径由 `session.PlayerId` 构造) |
| 224 项调用矩阵文件存在 | `M2-当前调用矩阵-待核证.json`(240KB,9 月 29 日 10:23) |
用户三项新裁决(新 PlayerId 不做映射、原游戏服接口沿用原逻辑只集中确认新 ID、等级榜分数存 KV)在工作稿/矩阵/问题单三份文档间口径一致,且未违反"客户端不改服务器代码"边界——问题单只提出确认项与聚合入口建议,符合约束。节奏纠偏与接手卡对历史的覆盖关系(旧问句被工作稿覆盖)自洽。
## 2. 需并入最终完整方案的问题(按严重度)
### P2-A(新发现):地区等级榜命名依赖 `ContinentCode`,KV 模式无来源,地区榜会静默退化为与全局榜同名
`DataCenter.LeadboardData.cs` `SetLvData`:`localName = IUserService.ContinentCode + leadboardLevelName`,随后把同一分数同时写入全局名与地区名两个统计。正式 KV 路径从不给 `ContinentCode` 赋值(PF 模式来自 PlayFab 档案 Locations),KV 下该值为 null,C# 字符串拼接结果 `localName == leadboardLevelName`——**地区统计名与全局统计名相同,地区榜语义静默消失**。工作稿只处理了分数来源(KV `Lv`/`InfiniteBuildingData` 推导)与前 50 聚合入口,未处理地区维度。最终方案须明确裁决:KV 模式地区榜停用/延期(产品范围裁决并登记清账),或给出大陆码的 KV/会话来源;不允许静默同名。
### P2-B:DP1 报告三项 P2 修订承接不完整
纠偏裁定 DP1 报告"只吸收事实与问题"。工作稿吸收了 DI 事实(行 2),但以下两项属于必须进入最终完整方案的问题,目前缺失:
1. **R2**:既有 formal 合成测试迁移义务(`KVGameLoadTests/ServiceHost/Program.cs`、`Stubs.cs` 直接 `new PlayFabUserService()` 驱动 formal 登录/401/quitting/双登用例),以及 `UserService.KVGameLoop.cs`、`AggUserService.cs`、`PlayerKVEditorValidationHost.cs` 三个"不改但受 31 模块窄契约签名耦合"文件的核验义务。
2. **R3**:登录/退出阶段中立面机制未定死——`PFLoginAct.cs:88` 经 `playFabUserService.CustomId` 下转赋值在新类注册后为 NRE;`QuitAct.cs:40,61` 双下转(validation 与 formal 退出观察分别落两类)。须在最终方案二选一定死(建议窄接口,消费者 `PFLoginAct`/`QuitAct`),避免实施时各写各的。
另:新 PlayerId 裁决覆盖了 DP1 报告 §4 的"UserId 恒 null"过渡表述——最终方案的 `IUserService` 成员三分表须按新裁决重写为"就绪后发布新 PlayerId + 登录态",`EntityKey` 留 PF;我的 DP1 §4 表相应部分以本条为准,不冲突,是演进。
### P2-C:身份发布时点与 FishingStage 5 秒软门的关系须闭合
`FishingStage.StartAsync` 的授权等待是**软门**:10×500ms 后仅 `LogWarning("Api Svr not author")` 并继续初始化(不 return)。工作稿行 4 的原则("公开登录态在新 PlayerId 会话与原 handle 就绪后发布")正确,但最终方案须补两条硬性时序断言:① KV 模式下登录态发布必须先于场景切换(不能依赖 5 秒软门兜底);② `OnApplicationResumedAsync`(`IsLogin` 消费者→RT 重连+FetchMail)与 `AutoRestart.Init`(`IsLogin/LoginTime`)在发布前后的可观察行为要定义并测。否则开闸时序存在"场景已进、身份未发布、软门放行"的静默路径。
### P3-D:`GetDefaultName` 短 ID 崩溃未并入
新 PlayerId 若为短十进制串,`GetDefaultName` 的 `id[0..5]` 抛 `ArgumentOutOfRangeException`(约 10 处社交/伙伴 UI 调用,闸后)。工作稿只列了 Survey 十六进制抽样改稳定规则;此项应一并并入同一清账批次。Survey 侧注意:`Convert.ToUInt64(UserId,16)` 对十进制短串"可解析但语义改变",改规则时不得保留十六进制解释。
### P3-E:"消除二次决策"方向留白
工作稿行 1 称 `SaveBackend` 登录期再读取"需消除二次决策"。最终方案须定死其一:消除再读取(登录只信冻结选择)或保留防御性对账(再读值必须与冻结一致,不一致即 Input 失败)。两者皆可,但不能留白;倾向保留对账(现状语义,攻击面不变)。
## 3. 流程与边界核对
- 工作稿自标"未送审、未获新代码白名单"属实;本预审不改变该状态。正式前审仍需:完整方案定稿 + 受审 hash 基线 + 一次提示词 + 一次完整前审 + 主会话逐项裁决与白名单冻结。
- 合同矩阵已被后续裁决标注为"早期扫描记录、不作为阻塞清单",与纠偏一致;其线上无凭据探查(`n3ftapi 0.0.1-nogit`、swagger 404)如实声明只证明可达,未冒充合同。维持该口径。
- "跨玩家聚合读取须等服务器开发侧给出现有或新增入口后才能称等级榜完整迁移"的表述准确,未用本机服务器源码或历史合成数据冒充。个人分数"从已存 KV 值推导、避免重复存储"的方向与 AGENTS.md 简洁性一致。
- 服务器边界:问题单只含一项兼容性确认 + 一个聚合入口询问/建议,未要求客户端读写服务器代码。符合用户约束。
## 4. 给最终完整方案的清单式输入(并入后即可送审)
1. `IUserService` 成员三分表按新 PlayerId 裁决重写(真实实现/就绪后发布/显式失败),含 §2-P2A 的 ContinentCode 裁决与 §2-P3D 的 GetDefaultName/Survey 抽样规则。
2. 登录/退出中立面机制二选一定死(窄接口或双下转),消除 `PFLoginAct.cs:88` NRE 与 `QuitAct` 双下转失效。
3. 测试迁移与签名耦合核验义务(ServiceHost Program/Stubs + 三个不改文件)写入白名单说明。
4. 身份发布先于场景切换的硬断言 + 两个 IsLogin 消费者时序测试。
5. 首场景最短链(Survey 两键读写、Mail、AntiCheat、Friend 邀请数、Club GetSelfClub、等级榜个人分)逐项:新 ID 接线点、epoch/SessionVersion 旧回包丢弃、失败不清已知状态。
6. 等级榜:分数推导算法按 `lv*100000+建筑` 落地、地区维度裁决、聚合入口到位前不宣称等级榜完成。
7. SaveBackend 再读取:消除或保留对账,二选一。
## 总结论
**工作稿方向与事实底座通过,可继续完善为正式受审方案**。无 P1;P2 三项(地区榜 ContinentCode、DP1 修订承接、身份发布时序软门)与 P3 两项按 §4 清单并入后再送审。本意见为只读预审,不构成编码许可;正式前审仍按"一次 hash 基线、一次完整审查、一次裁决"执行。