115 lines
12 KiB
Markdown
115 lines
12 KiB
Markdown
# 阶段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启用门是否有绕过路径。
|