Files
ft/CodexOutput/upserver/阶段4B2游戏加载与内存隔离方案-待评审-2026-09-23.md

12 KiB
Raw Blame History

阶段4B2游戏加载与内存隔离方案(待独立评审)

日期:2026-09-23。前置:4B1正式关闭(关闭裁决)。本文是待审提案,不是实施批准;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启用门是否有绕过路径。