Files
ft/CodexOutput/upserver/阶段4B2方案初稿快照-2026-09-23.md

115 lines
12 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.
# 阶段4B2游戏加载与内存隔离方案(待独立评审)
日期:2026-09-23。前置:4B1正式关闭([关闭裁决](阶段4B1实施复核裁决与关闭-2026-09-23.md))。**本文是待审提案,不是实施批准;4B2未编码,游戏仍走原存档路径。** 编制依据:阶段4分期§3、4B分期§2对4B2的审查要求、4B1方案§5硬边界、4B1关闭裁决带入项(CleanupFailed+OriginalError双检查、发布顺序约定)。正式地址 `http://s101.local:18080/`;Unity CLI Loop;不使用UnitySkills;暂不打包。
## 1. 现状源码证据(本轮静态读取)
| 位置 | 事实 | 对4B2的约束 |
|---|---|---|
| `UserService.LoginCore` | 顺序=LoginPlayFab(内含PF UserData初始化)→SetMMTestValue→聊天/易盾→LoginApiServer→InitTapJoy→Done | KV加载必须插在API登录成功之后、Done之前,且仍在`base.Login` await窗口内(Agg订阅约束) |
| `UserService.LoginPlayFab` 末尾 | `InitData(playfabUserData, UserId)`在时间校准后调用 | KV模式下此调用必须移除/分流,PF模式保持 |
| `UserService.InitData` | `CompareDatas`(PF远端×PlayerPrefs本地按时间戳合并)→MMTest读+**两处UpdateUserDataValue写**→`LoadPlayerData(comparedData)`→`OnInitData?.Invoke` | KV路径不得走CompareDatas/PlayerPrefs;加载期禁止后端写 |
| `UserService.SetMMTestValue` | UserId十六进制解析+**MMTest键写** | 同上:写点必须延迟/暂存 |
| `AggUserService.Login/InitData` | OnInitData订阅仅限base.Login期间;InitData读账号绑定标记,非游客渠道**补写渠道标记**并修改传入字典 | OnInitData必须照常触发;其写点需暂存 |
| `DataCenter.PlayerData.LoadPlayerData` | **先全量置默认值**(lv/gold/energy…),再按键覆盖;空字典仅打日志"默认值初始化"——静默默认档路径;另有模块InitData分发与LoadPlayerDataNew解析链 | 空KV/缺主档不得进入此函数的默认路径;重载隔离需逐模块验证 |
| `PlayFabMgr.CompareDatas/GetLocalData` | `localData`是PlayFabMgr内共享可变字典,InitTapJoy等经`GetLocalData`读它 | KV模式零触达PlayFabMgr;快照副本承担该角色 |
| `UserService` 401订阅/Logout | 401→InternalId=0+后台重登;Logout先Dispose apiSession | 追加:撤销并收尾当前KV context;关闭任务并入下次登录await(沿用3B期"同步注销不阻塞主线程"裁决) |
| `QuitAct.StartAsync` | 先同步失效身份→await Hub释放→销毁RootCtx | 在Hub释放后、销毁前增加有界await KV关闭;不以网络等待延迟身份失效 |
## 2. 路线比较
| 路线 | 判断 |
|---|---|
| 直接切换全量KV登录(删PF初始化) | PF档案/昵称/头像/语言/易盾/TapJoy等仍依赖PF登录;一次切换回归面过大,不采用 |
| PF/KV双写过渡 | 违反"KV成功后不镜像PF、失败不回落PF"的已裁边界,不采用 |
| **后端开关+完整KV分支(推荐)** | `IConfig`键`SaveBackend`:`playfab`(默认,行为与现状逐字节等价)|`kv`(新分支完整实现)。4B2交付KV分支+受控验收;真实全量启用另立门(见§9) |
## 3. 4B2交付的公开合同
新增 `PlayerKVSaveSchema`(含结果类型,同文件):输入4B1快照+账号上下文,输出结构化 `PlayerKVSaveValidationResult`:
- `Verdict`:`Valid`|`EmptyExisting`(newCreate=false空档)|`EmptyNew`(newCreate=true,等待显式建档流程)|`IncompleteMainSave`(主档特征键缺失)|`ParseFailed`(结构层失败)。
- 明细:特征键命中分组、逐模块解析失败计数、总键数;全部脱敏(键SHA/计数,无值)。
- **主档特征键集**:锚点=模块大档键(PlayerFishData/PlayerShopData/FishingEventData/BuffDataCenter/CampDataMM等消费的存档键)+进度标量键(Lv/RewardNewMap/EquipRodID/LastEnergyRecoveryTime等)分组,**任一组非空命中即主档存在**。完整键清单由实施检查点1以《阶段4存档调用盘点》为起点固化成文档并纳入验收(不能只验一项JSON就当完整档)。
- 解析策略分层:主档存在性(阻塞级)与字段级解析(沿用`GlobalUtils.TryParse*`逐字段默认+诊断计数,不静默吞也不扩大阻塞)分别处理,不捕获成空档。
新增 `UserService` 内KV分支编排(不新增编排类)与以下合同:
- **加载门**:`LoginApiServer`成功且代际有效后创建`PlayerKVLoginContext`(epoch=单调long登录代际;`isCurrent`=仅读代际与CustomId的轻量比较,遵守P3-3约束),`PrepareAsync(LoginLifetime)`。
- 结果处理:`Prepared`且校验`Valid` → 快照**复制为可变字典副本**(对4B1不可变快照的防御性拷贝;该副本同时承担原`PlayFabMgr.localData`的共享读取角色,供InitTapJoy等读取)→ `InitDataFromKV(副本)` → Done。失败/空档/校验不过 → 返回阻塞登录步骤、`CloseAsync`清理、**不调用LoadPlayerData、不触发OnInitData、不Done、不写任何后端**;结果检查必须同时看`CleanupFailed`与`OriginalError`(4B1关闭裁决带入)。
- `InitDataFromKV`:MMTest按现语义从副本读;缺失分支**不写后端**,记录待写意图(4B3落盘);`LoadPlayerData(副本)`;`OnInitData?.Invoke(副本)`(仍在订阅窗口内)。
- PF模式(默认):全部现有路径不变,含原`InitData(playfabUserData)`。
## 4. 初始化副作用逐项处置
| 副作用 | 现位置 | KV模式处置 |
|---|---|---|
| MMTest写(InitData两处+SetMMTestValue) | UserService | 加载期禁止;值从副本计算;意图暂存,4B3落盘 |
| Agg渠道标记写 | AggUserService.InitData | 数据照常读取/传递;后端写改为暂存(该文件最小改动) |
| InitTapJoy读TapJoy_Diamond | UserService | 改读快照副本;PF模式不变 |
| 体力恢复链保存 | DataCenter.PlayerData | 4B2不接写路径(4B3范围);仅验证加载后内存态正确 |
| PF登录本身(档案/昵称/头像/语言/时间) | LoginPlayFab | 保留(仅移除KV模式下的UserData初始化调用) |
## 5. 内存隔离与换号
- **重载即重建**:KV每次登录都完整重跑`LoadPlayerData`(其头部默认值赋值承担基础重置);实施检查点2对全部`InitData(userDatas)`消费模块逐个列表,验证"缺键=回默认、无上一账号残留",抽样断言进矩阵。
- **换号顺序**:LoginCore入口先await旧context的关闭任务(如有)才允许新Prepare;101→102时旧句柄因4A/4B1撤销语义全部拒绝。
- 业务内存清空以"完整重载"为准,不引入部分清理;发现模块存在非加载字段残留即为该模块缺陷,记录并阻塞该模块接入(不静默)。
## 6. 401/退出接线
- 401订阅:现有InternalId=0+后台重登保留;追加撤销当前KV context(`CloseAsync`不阻塞主线程,任务并入下次登录await)。不得在旧Owner上静默续用。
- Logout:代际失效→现有清理→context关闭任务移交下次登录。
- QuitAct:身份失效语义不变;Hub释放后增加**有界await** KV关闭(超时记录未完成诊断并继续销毁流程——OS退出不保证网络完成的既裁边界)。
## 7. 文件白名单(仅提案)
| 类型 | 路径/职责 |
|---|---|
| 新增 | `PlayerData/PlayerKVSaveSchema.cs`(+meta):校验器与结果类型 |
| 修改(最小) | `Services/UserService.cs`:登录链KV分支、InitDataFromKV、401/Logout接线 |
| 修改(最小) | `Services/AggUserService.cs`:渠道标记写暂存 |
| 修改(最小) | `DataCenter/DataCenter.PlayerData.cs`:空档阻塞入口与隔离断言辅助(默认值语义不变) |
| 修改(最小) | `Stages/QuitAct.cs`:有界关闭接线 |
| 新增测试 | `CodexOutput/upserver/KVGameLoadTests/`:显式链接生产源码的合成宿主+受控验收证据 |
**禁止修改**:PlayFabMgr(PF模式必须逐字节不变,hash门验证)、4A/4B1/3A/3B/阶段2生产文件、其余DataCenter/玩法/UI、服务器、包。接口不足先记差异再补审。
## 8. 验收矩阵(计划,全部合成/受控,不访问正式服)
| 编号 | 场景 | 必须观察 |
|---|---|---|
| B2-01 | PF模式回归 | 默认开关下编译与关键路径行为不变;PlayFabMgr等禁改文件hash不变 |
| B2-02 | KV正常加载 | 完整主档替身快照→Prepare→校验Valid→副本→InitDataFromKV→OnInitData收到副本→Done;全程0 PlayFabMgr调用、0后端写 |
| B2-03 | 空档newCreate=false | EmptyExisting阻塞;无LoadPlayerData/OnInitData/Done/写;context清理完成;原错误保留 |
| B2-04 | 空档newCreate=true | EmptyNew专用阻塞(建档流程未实现,不静默默认档) |
| B2-05 | 缺主档特征键 | Incomplete阻塞;诊断计数正确 |
| B2-06 | 字段级解析异常 | 主档通过+坏值按TryParse默认+分类计数;不吞不空档 |
| B2-07 | MMTest三态(存在/旧格式/缺失) | 值正确、加载期0写、意图暂存可查 |
| B2-08 | Agg渠道标记 | 窗口内收到数据;KV模式0后端写、暂存;账号绑定标记读取正确 |
| B2-09 | InitTapJoy | KV模式读副本TapJoy_Diamond;PF模式不变 |
| B2-10 | PF本地缓存零参与 | KV路径不触达CompareDatas/PlayerPrefs/localData(结构断言+替身计数) |
| B2-11 | 隔离 | 两次不同账号加载后业务内存抽样无残留 |
| B2-12 | 换号 | 旧context关闭完成后新Prepare;旧句柄拒绝;快照不串 |
| B2-13 | 401 | 撤销context、关闭并入下次登录;旧句柄失效 |
| B2-14 | Quit路径 | 有界await关闭;超时诊断记录;身份失效不被延迟 |
| B2-15 | Prepare各失败态 | 认证失败/OwnerRejected/NotReady/CloseFailed逐一阻塞;CleanupFailed+OriginalError双检查 |
| B2-16 | 加载中代际失效 | 结构化取消、无Done、清理完成、无孤儿任务 |
| B2-17 | 校验器单元矩阵 | 五种Verdict×代表模块组合全断言 |
| B2-18 | 副本与脱敏 | 副本可变不影响4B1不可变快照;输出无值/Cookie/完整键 |
| B2-19 | 回归 | 4B1/4A/3B/3A/阶段2/Review/3C离线/KVLiveTests仅build/Unity编译全过;KVSyncTests偶发失败按P3-1口径处理(保留首败证据) |
| B2-20 | Unity受控加载 | 编辑器KV模式+替身传输跑通"登录→Prepare→校验→InitData→Done"加载段;不进玩法、不PlayMode全流程 |
| B2-21 | 键清单固化 | 检查点1的完整主档键集文档落盘并被B2-17引用 |
| B2-22 | 启用门 | 默认`playfab`;`kv`仅在受控验收启用;全量启用另立裁决 |
并发用TCS闸门,不用Sleep;每命令保留完整stdout/stderr与真实退出码。
## 9. 启用门与不做清单
4B2通过≠游戏存档切换。全量启用前置:4B3读写路由+消费流程、tickets有界历史、4D Unity场景验收、正式场景测试数据边界授权。4B2不实现:新玩家建档显式流程(仅阻塞)、任何KV写路径(含立即同步true适配,4C)、他人资料/排行榜迁移、身份切换(UserId仍PF ID,ApiPlayerId为KV身份)、SDK删除、真机/打包。
## 10. 自审与待审问题
- 已写明:现状证据、路线、合同、副作用处置、隔离、401/退出、白名单、22条矩阵、启用门;未把建档、写路径、身份、场景验收当作已解决。
- 请审查者重点裁决:①`SaveBackend`开关与默认PF等价性保证是否足够;②主档特征键分组判定(而非逐键必备)是否满足"完整业务档校验"要求;③MMTest/Agg暂存意图的边界(4B3未落地前暂存不丢失);④UserService等四个既有文件的最小改动边界是否清晰可审;⑤22条矩阵与§9启用门是否有绕过路径。