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

217 lines
29 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游戏加载与内存隔离方案(修订2,待独立评审)
日期:2026-09-23。**本文件与[字段及模块合同](阶段4B2字段与模块加载合同-2026-09-23.md)共同组成受审设计,替代原初稿;尚未批准实施。** 原初稿保留在[快照](阶段4B2方案初稿快照-2026-09-23.md)。前置[4B1关闭裁决](阶段4B1实施复核裁决与关闭-2026-09-23.md)保持有效;本修订对其接口扩展另行申请审查,不用原批准覆盖新增改动。
正式地址固定 `http://s101.local:18080/`。Unity只使用CLI Loop,不使用UnitySkills;暂不打包。此轮仅设计,无代码、测试或Unity执行;以下接口与用例均是计划,不是现有实现或已通过的证据。
## 1. 要交付的行为及明确的范围变化
4B2交付的是**可用替身运行真实业务加载代码的受控加载链**:单次认证→KV快照→严格解码全部已消费字段→替换各模块本账号加载状态→窗口内通知Agg→返回`LoadedForValidation`。所有失败不公布有效加载状态;加载期不写PlayFab UserData、PlayerPrefs存档或KV,不发奖励,不启动玩法。
默认PlayFab正常登录不变。正式配置不能启用KV玩法;完整启用仍等待4B3、4C、tickets有界历史、身份/场景专项及正式测试数据边界。UserId仍是PF身份,ApiPlayerId才是规范化数字账号。
| 原初稿问题 | 修订2决定(须本轮独立裁决) |
|---|---|
| LoginApiServer之后再Prepare导致两个S101认证session | KV链跳过旧LoginApiServer,4B1唯一认证;新增受控借用传输,外部不Dispose其session |
| 任一主档特征键就放行;键清单编码时才定 | 附件固化两种完整主档格式、全部当前加载消费键和缺失/坏值策略;单个活动键不能证明主档存在 |
| 再调用LoadPlayerData即声称全部重置 | 不复用旧的带副作用总入口;每个消费模块增加纯Decode、Replace/Reset入口,所有可选缺失产生显式空态,逐模块测试 |
| PlayFabMgr零改动同时承诺拦住间接保存 | 新增严格受控范围的防写/防读门;明确PlayFabMgr进入白名单。PF分支走原路径,不能再声称该文件hash不变 |
| 存在无界清理但Quit超时后销毁Root即可 | 进程级关闭登记表在Root之外保留失败/未完成任务;任何后续KV加载都受其阻挡 |
| 配置kv即可走Done,可能进玩法 | 公共Login在kv配置下返回Api阻塞;仅内部测试能力可运行KV链,成功只返回LoadedForValidation,永不返回ELoginStep.Done |
| 把有写入/UI行为的加载函数直接复用 | 提取/新增纯加载入口;旧PF入口保留,KV路径不调用Init、刷新UI、体力恢复、活动恢复及异步业务服务 |
最后两项是防止4B2绕过整体启用门的提案调整,不能被描述成已经完成正式游戏接线。审查者应分别评价整体方案、扩围和启用合同;任一阻塞则不编码。
## 2. 源码依据与路线选择
| 实际代码位置 | 本轮静态确认 |
|---|---|
| UserService.LoginCore / AggUserService.Login | 两处IsLogin提前返回Done;前者LoginPlayFab内已初始化PF档、随后SetMMTest写,再API登录 |
| PlayerKVLoginContext.PrepareCoreAsync | 内部创建session并Login,session私有;禁止在外面先登录再借同Cookie冒充单会话 |
| CustomServerMgr.UseAccountSession / ReplaceTransport | 另建HttpClient借Cookie,替换时直接Dispose旧client,无实际排水计数 |
| PlayerData.LoadPlayerData / LoadPlayerDataNew | 仅部分默认值;NewPlayerDataDic、钻石、模块字典等缺键时可能保留;后者按newData覆盖旧值 |
| PlayerFishData / PlayerShopData / BuffDataCenter / BattlePassData / CampDataMM | 多处只在键存在时赋值,无完整缺键清理 |
| EventPotion/Pinball/ScratchTicket.GetDataFromServer | Deserialize后调用Init;不能视为纯赋值 |
| BargainPackData.LoadData、EventPinataPackDataManager.LoadData | 可触发UI/刷新;不能直接用于无副作用加载 |
| EventPartnerGatherManager.ClearData | 会SaveData,不能借此清理KV加载态 |
| PlayFabMgr | 定时、暂停、销毁、普通/立即同步均可能写;只改UserService两处不够 |
| QuitAct、RootCtx | 先注销后Hub释放,Root销毁会重建静态容器;Root内的关闭引用无法充当跨Root屏障 |
| 路线 | 取舍 |
|---|---|
| 继续旧LoadPlayerData,补几个写点/默认值 | 改动少,但异常可吞掉、活动可能启动、缺键残留不能闭合;拒绝 |
| 一次重建全局DI/Root并启用真实KV玩法 | 涉及所有生命周期及场景,超出4B2且无法替代旧回调归属;拒绝 |
| **单认证+独立编排+纯加载适配器+内部受控门** | 改动文件增多,但每个模块状态、错误、写入边界可验;推荐,完整白名单见§9及附件 |
## 3. 后端选择、入口与公开状态
### 3.1 配置与受控能力
- 每次`Login`在等待gate之前捕获配置字符串、CustomId、logoutGeneration;进入gate后仅复核身份/代际,不重新读取后端配置。本次配置和表快照固定,热更只影响下次尝试。`SaveBackend`缺失为`playfab`;只接受精确小写`playfab`/`kv`,其余返回Api+BackendUnsupported,不默认回落。
- 现有公共Login的`playfab`分支保留原流程。`kv`分支在任何认证/初始化之前返回Api+BackendNotEnabled;Agg的提前IsLogin检查也必须先判断backend,不能绕过。
- 新增internal `LoadKVForValidationAsync(KVControlledLoadCapability, progress)`。能力只能由UNITY_EDITOR下的验收驱动创建,包含替身session工厂、PF身份替身、固定配置/时间快照、临时缓存根、测试生命周期及未启动玩法证明;玩家构建没有创建入口。DEBUG/GM/普通配置均不能伪造能力。
- 方法内调用与未来集成共用的`PlayerKVGameLoadCoordinator`;不调用公共Login去驱动界面,不创建新UI,不改PFLoginAct/AggLoginAct。内部能力不允许默认真实HttpClientHandler,缺任何替身即在首请求之前失败。
- 默认PF模式不调用新校验器、不新增KV磁盘/网络活动。禁止同一个业务容器在PF与KV受控模式间热切换;受控测试使用单独、未启动玩法的容器,finally恢复原全局引用。
### 3.2 类型职责
| 提议新增类型 | 合同 |
|---|---|
| PlayerKVGameLoadCoordinator | 串行登录/加载状态机,拥有context、借用传输、模块transaction与暂存意图;不负责具体字段解析 |
| PlayerKVSaveSchema / PlayerKVDecodedSave | 校验快照来源和附件格式;产出每个模块独立的已解码状态,不持有全局manager引用,不修改原快照 |
| PlayerKVGameLoadTransaction | 在主线程、无await区间内Reset/Replace所有已登记模块;保留generation校验;异常时清空全部本次模块并撤销 |
| PlayerKVInitializationIntents | 本epoch的内存意图,不是持久化成功凭证;只允许MMTest和当前渠道标记两类 |
| PlayerKVGameLoadGuard | 受控加载的拒写/拒旧缓存门和违规计数;PF无受控租约时直通 |
| PlayerKVShutdownRegistry | 跨Root的静态登记表,只保存context/借用关闭任务及脱敏状态;无游戏对象引用、无网络重试 |
`KVGameLoadResult`含State(LoadedForValidation/Rejected)、Error(None/BackendNotEnabled/BackendUnsupported/Input/Prepare/Schema/Apply/Stale/SideEffect/GateBusy/PreviousClosePending/PreviousCloseFailed/ClosePending/CloseFailed)、SchemaVerdict、原Prepare错误字段、CleanupFailed、OriginalError、Epoch、模块数量、意图数量。gate观察超时为GateBusy,历史屏障超时为PreviousClosePending,当前撤销后的排水观察超时为ClosePending;三者不能混成认证失败。失败Data为空,不传回半成品模块。LoadedForValidation不赋予IsLogin/IsAuthorized或玩法权限;对外InternalId保持0,不发布EvtAPISvrLoginSuccess。
## 4. 单认证、借用与关闭合同
### 4.1 唯一认证源
受控链捕获PF资料替身(PF UserId只用于遗留资料及MMTest分桶;不读PF UserData)→等待历史关闭→创建4B1context→Prepare一次。Prepare成功后从context得到非敏感认证描述(PlayerId、InternalId、NewlyCreated、时间),不调用旧LoginApiServer。所有Get与借用请求使用同一个CookieContainer;禁止第二次account/Login。
新增internal `PlayerKVAccountLease`,由context在gate内以“Prepared且未Closing”原子检查后最多发行一次;发行失败不能回落到新session。它持有借用client和固定epoch,**不公开Cookie/session/写句柄**。其方法仅为受控HTTP请求及CloseAsync;4B2只能在测试替身内发送,不允许用它调用account/Login或KV变更接口。
- Lease发送在首次await之前登记active计数;Closing后新请求拒绝。finally减计数。撤销后即使HTTP200也丢弃返回值。
- 本阶段Lease只接受驱动登记的合成401/200测试路由(`__kv_validation/unauthorized`、`__kv_validation/ok`)且必须存在受控能力;相对路径先规范化,拒绝绝对URL、跳转和未列路径。不能借CustomServerMgr的通用Request方法调用真实业务服务。将来普通业务API可用性须另审放宽路由,4B2不宣称已提供完整API登录授权。
- Lease.Close同步拒绝新请求→请求取消→等待active归零→Dispose借用client。忽略取消的传输必须真正结束,不能以超时当作排水成功。
- Context.Close顺序为同步撤销→等待Prepare workSettled→关闭已登记Lease→Bridge.Close→Dispose认证session。任一步故障保留对象/原关闭Task及具体错误,禁止重开。没有Lease时维持原4B1行为。Lease发放与Close竞争必须共享gate,不能先验后发造成遗漏。
- Context内部Prepared字段仍最后Volatile.Write;新增描述与Lease工厂在发布前完成初始化,保持4B1 P3-1'约束。待关闭时不能发布新Lease。
- CustomServerMgr新增**internal**借用绑定入口;ICustomServerMgr不暴露session所有权。绑定请求捕获Lease和SessionVersion;Clear先递增版本并分离绑定,再触发同一Lease.Close,返回任务交给登记表;不得把旧PF的ReplaceTransport行为套到KV租约。
- API地址不相同拒绝绑定;旧4B1公开构造仍固定正式地址,替身仅internal。此扩展修改4B1的源码hash,必须重新运行4B1全部测试并独立复核,不沿用旧hash声称未变。
### 4.2 401、换号和Root销毁
KV受控绑定收到401/403时,先比固定SessionVersion;只有当前绑定可撤销。同步递增epoch、撤销有效加载标记、开始context.Close并登记;**不调用后台LoginApiServer**。旧version事件不影响新epoch。PF模式原401行为保持。
注销、加载取消、换号、受控驱动退出均走同一撤销函数;重复返回同一关闭任务。下一次任何KV尝试(即使另一个Root/账号)先等待登记表全部旧Task成功。CloseFailed永久阻塞该进程后续KV尝试,本阶段不提供清空登记表/强制继续按钮;只保留诊断,人工恢复另审。
“等待”不意味着把调用者界面无限挂住:进入loginGate及等待Registry的**观察期限均为10秒**,超过后返回Rejected/PreviousClosePending(故障任务则PreviousCloseFailed),不开始新认证、不释放旧资源。用户取消也只结束观察。gate等待用可取消WaitAsync,超时取消排队请求,处理“超时与获锁同时发生”时必须释放本次已获锁;不遗留随后偷偷运行的加载。当前Prepare/关闭若忽略取消,外层受控调用同样可在取消后10秒返回ClosePending,但先撤销epoch并把worker/close任务移交Registry;worker后续只能清理,不能再Apply。返回不授予重试绕过屏障的权利;后续旧关闭确实成功才可重新申请,faulted只能提示重启进程/人工处理。禁止采用“缓存锁释放就忽略CloseFailed”方案,因为借用网络仍可能在执行。
QuitAct保持同步身份失效在任何await之前;Hub释放后观察KV关闭,**观察上限5秒**。超时只让场景退出继续,任务仍由登记表持有并观察最终异常;不是取消排水、强制Dispose或授权重登。OS真正退出不承诺网络收尾;同进程Root重建仍受屏障。默认PF没有KV任务时不增加5秒等待。
超时取只读配置`KVLoginCloseObserveMs`默认10000、`KVQuitCloseObserveMs`默认5000,均只接受1000~15000的整数;无效配置在受控入口拒绝而非无界等待,登录捕获一次,退出读取该次捕获值。测试以可推进的时钟/TCS模拟到期,不Sleep。配置不能修改Context真实排水期限(仍无强行中断)。
## 5. 字段解码与失败策略
具体键、优先级、模块归属、空态及格式见[字段及模块合同](阶段4B2字段与模块加载合同-2026-09-23.md),是本方案不可分割部分。
- 首先检查context.IsCurrent、PlayerId/epoch与捕获值;成功空字典按session.NewlyCreated分EmptyNew/EmptyExisting,都阻塞,不创建默认新档。
- 主档仅接受完整现代19槽或完整旧格式核心字段;任何单独活动/账号键、空NewPlayerDataDic、短newData均不算主档。两份主档同时存在且语义冲突则Conflict,不能静默挑一份。
- 所有被当前加载链消费的已存在字段均需解析;不采用“坏数值TryParse默认后继续”。JSON语法/重复属性/类型/null/数值溢出/NaN/无穷、紧凑格式错误、加载必须访问但不存在的配置记录均阻塞整个加载。原值不修改、不覆盖远端,失败不回PF。
- 缺失的可选模块按附件**显式空态**;与“存在但损坏”严格区分。配置表和时钟在Decode前捕获并固定,不在多个模块间读取不同版本。数值文化约束见附件。
- 未消费的扩展键仅保留在不可变原快照,不能用来判主档或送进未知加载函数;未知配置版本/模式返回UnsupportedSchema。对这些扩展键不声称做过语义验证。
- Decode不得触及GContext、PlayerPrefs、UI、网络、计时器或真实模块;只分配数据对象并读取显式传入的只读配置。返回全套模块空态/实态后才能开始Apply;不会边解析边污染运行时。
SchemaVerdict:Valid、EmptyNew、EmptyExisting、MissingCore、Malformed、ConflictingRepresentations、UnsupportedSchema、Stale。按Stale→Empty→外层格式→主档缺失/冲突→逐模块错误的顺序裁决;错误计数按模块记录,最多32条描述,描述仅模块编号/类别/字段序号/异常类型,无原值、原键、完整路径。键SHA不是匿名化保证,修订2不输出键哈希。
## 6. 应用、内存隔离与初始化意图
### 6.1 串行应用
1. 全部Decode完成,确认guard租约、context、epoch、配置版本和静止容器仍有效。
2. 主线程无await的事务:全部模块ResetForKVLoad→按附件固定次序ReplaceFromKVDecoded→MMTest内存计算→窗口内OnInitData通知(只允许已登记Agg接收器)→检查违规计数/epoch→公布LoadedForValidation。
3. Reset/Replace是**直接字段替换**,不经会发事件/保存的属性setter,不执行旧LoadPlayerData总入口。附件每行均有空态,缺键不省略该行。
4. Apply或接收器异常/代际变化:保持未就绪,全部模块回空态,丢弃意图;context关闭。若Reset亦失败,标记容器Poisoned,受控驱动销毁整个测试容器,不在残余对象上重试。失败不能公布LoadedForValidation。
5. 不主张数据库式回滚旧账号数据:旧epoch先失效且已关闭,不恢复旧账号快照。无效/Poisoned对象不能进入玩法。
受控容器必须尚未启动任何玩法Init/Start、计时器或业务订阅;受控驱动主动探测并拒绝已运行的游戏容器。两次加载测试在**同一组业务对象**上进行,证明模块Reset,而不是每次new全套来规避缺键问题。允许首次构造新对象供测试,不把Root重建当作重置证据。
本阶段仅保证附件列出的**登录加载态**隔离,不宣称已处理玩法运行后的所有延迟回调;该部分属于4B3/4C和场景启用门。4B2入口不能用于已进入玩法后的换号。
### 6.2 MMTest、渠道与其他副作用
- MMTest=`MMFailParam#整数`时读取该值;旧格式转0并产生规范化意图。缺失且PF资料NewlyCreated=false取0;为true时用捕获PF UserId的UInt64十六进制值mod16,PF ID非法则Schema错误。S101 NewlyCreated只用于空KV分类,禁止混用。
- MMTest意图与当前非guest/non-none渠道缺失标记,在当前加载副本与`InitializationIntents`中可见;键去重、同键最后规范值,最多2项;来自旧epoch的意图不能迁移。数据不得打印到日志。
- Agg入口增加内部受控方法,在await共用coordinator期间订阅,在finally退订;公共渠道SDK登录不跑。linkedAccout先由测试身份替身提供全新集合,再合并存档三个标记,不能继续往上一账号SDK集合Add;EAccoutUrl缺失设空字典。不改真实SDK登录态。
- 意图只属于本次内存态,close/失败时显式Discarded并记数量;不承诺跨重启“不丢失”,不向磁盘或KV写。4B3未来须重新设计持久化承接及失败合同,不能把4B2暂存当作已保存。
- InitTapJoy仅提取TapJoy_Diamond的余额解码为受控结果,4B2不创建TapjoyHelper、不接奖励回调。MMTService真实Init/GEvent、聊天、易盾、头像更新、邀请上报、体力恢复、活动Init/UI刷新及恢复逻辑全部不在受控加载中执行;PF路径保持原行为。后续运行激活需4B3/4C详审。
- 旧模块里兼有解析与副作用的函数必须拆出纯KV适配入口;不能通过catch异常/“写入最终被挡住”容忍半执行副作用。
## 7. 防写与防串入的第二道检查
新的PlayerKVGameLoadGuard只在持有internal受控租约时生效,租约跨await保留、非线程局部;进程中只允许一个受控加载范围,并发申请失败。它不是4B3存档facade,不为任何普通写提供成功返回。
PlayFabMgr以下入口在最前面检查:CompareDatas、GetLocalData、UpdateUserDataValue(包括remoteStore=false)、SyncDataImmidiate、SyncData,以及生命周期中PlayerPrefs.Save。受控范围内读PF缓存/写存档均记违规并拒绝,**在改字典、缓冲区、PlayerPrefs、构建网络请求之前**执行。立即同步返回false,普通void抛固定类别异常;定时/暂停/销毁只拒绝并计数,coordinator末尾及finally检查计数。
不复用PlayFabMgr.localData作为KV副本。进入测试范围前要求独立PlayFabMgr实例尚无pending buffer或in-flight PF发送,否则拒绝测试;测试结束后销毁实例,不能把PF旧队列在另一个账号下恢复发送。PF默认流程guard无租约时原分支不变,但因文件有修改必须做行为回归。
真正的零副作用保证来自纯Decode/Replace和受控身份依赖;guard用于捕捉遗漏,不能拦截所有直接SDK/PlayerPrefs调用。静态调用链审查+宿主的SDK/PlayerPrefs/网络/事件替身计数必须同时为零;命中直接调用时修正纯入口,不能扩充“允许写”名单。KVTransport除既有Get与本地缓存正常行为外,所有Mutation计数必须0;“零写”指业务存档写,不错误地要求4B1缓存文件完全无IO。
## 8. 失败、关闭和可观察性
Prepare拒绝、Schema失败、Apply异常、SideEffect、取消与401均保留最初原因;关闭再失败时Error=CloseFailed、CleanupFailed=true,OriginalError保留原阶段错误和原Prepare字段。先检查CleanupFailed,再结合OriginalError给出诊断,不能因为原错误可重试绕过关闭失败屏障。
日志只写固定阶段、epoch、模块ID、错误码、计数、耗时和异常类型。不写JSON值、账号标记值、Cookie/Token、原始异常Message/堆栈中的值、本机路径。输出与原始本地编译日志分开;后者不能宣称已脱敏。受控测试账号仅为合成标识,正式101/102授权不在本阶段使用。
## 9. 精确文件边界与实施检查点(仅待审提案)
以`Client/Assets/Scripts/`为根:
| 文件 | 唯一允许改动职责 |
|---|---|
| Services/UserService.cs | 新文件用partial时仅加partial;默认PF合同不动;backend前置检查、内部受控入口、注销/401分支和KV关闭观察接口 |
| Services/UserService.KVLoad.cs(新增) | KV入口/结果映射/能力检查;不复制旧PF实现 |
| Services/AggUserService.cs | backend先于提前Done;内部受控订阅窗口和账号标记纯合并;公共PF流程不变 |
| Services/PlayerData/PlayerKVLoginContext.cs | §4唯一Lease登记、元数据快照和Close排水扩展;不得改变重复Prepare/首token/历史任务语义 |
| Services/PlayerData/PlayerKVAccountLease.cs(新增) | 固定Cookie借用client、active计数、版本/取消/排水;不公开凭据 |
| Core/CustomServerMgr.cs | internal Lease绑定和版本隔离;PF UseAccountSession旧路径保留 |
| Core/PlayFabMgr.cs | §7受控检查点;不做KV路由、普通存档改写或队列重构 |
| Stages/QuitAct.cs | 在现有身份撤销与Hub释放顺序下增加5秒观察;不移除Registry责任 |
| Services/PlayerData/下新增 PlayerKVGameLoadCoordinator.cs、PlayerKVSaveSchema.cs、PlayerKVDecodedSave.cs、PlayerKVGameLoadTransaction.cs、PlayerKVInitializationIntents.cs、PlayerKVGameLoadGuard.cs、PlayerKVShutdownRegistry.cs、PlayerKVControlledLoadCapability.cs | 分别实现§3–§8职责;internal为主,避免成为外部绕过接口 |
| 附件§5逐项列出的既有模块文件 | 仅增加纯Decode/Reset/Replace及必要的纯解析提取;不得改变PF原入口行为或调用KV保存 |
| Services/PlayerData/PlayerKVEditorValidationHost.cs(新增,整文件UNITY_EDITOR) | 位于HotUpdate程序集内,创建internal能力、实际模块容器和替身;固定internal静态RunAsync入口,不接受任意类型/URL/代码参数 |
| Client/Assets/Editor/KVGameLoadValidation.cs(新增,路径相对项目根) | Unity CLI Loop调用的Editor包装器;按固定HotUpdate程序集和类型名反射调用上行RunAsync并await Task,不以反射暴露任意内部成员,不进入PlayMode |
| CodexOutput/upserver/KVGameLoadTests/(新增) | 显式链接生产代码的离线宿主、状态/传输/事件替身与证据 |
新Unity文件附meta。新增类若组合在上述文件中不扩职责;禁止临时新增其他业务文件。附表未列的文件、Server、场景/资源、Packages、asmdef、旧测试源码/csproj、4A/3A/3B/阶段2生产代码禁止改。4B1原结果类型保持;如构建依赖、公开接口、白名单不够,先形成差异方案并补审。
检查点顺序:
0. 外部方案审查→核对本次基线hash→逐条裁决并记录扩围及启用合同,才批准编码。
1. 单认证Lease/Registry与关闭竞争;先4B1原23组回归+新增借用竞争组。不能先接游戏再补生命周期。
2. 附件所有字段codec与模块空态,纯解码/应用宿主、每模块缺失及坏值测试。
3. UserService/Agg受控编排、PF拒写门与Quit观察,完整失败矩阵。
4. Unity CLI Loop受控真实模块测试、回归、hash/脱敏/自审,交外部独立实施复核。每个检查点更新开发日志和实施记录;任何未闭合项不写已通过。
测试分层固定:离线项目只显式链接纯协调器/Schema/DTO/Lease/Registry/guard及其必要已有账户、KV源码,模块Apply用记录型替身,验证时序和失败注入;UserService/Agg/PlayFabMgr分支另用链接真实源文件的Unity/SDK替身宿主检查入口,不能用复制逻辑替代。附件31组**真实对象**的字段与副作用验证由HotUpdate编辑器宿主执行,不能因为离线记录替身通过就勾选实际模块隔离。若必须修改旧csproj才能构建,先差异补审。
## 10. 验收矩阵(计划,不能预填通过)
| ID | 必验场景/判定 |
|---|---|
| B2-01 | 默认PF非AGG/AGG登录及IsLogin捷径行为回归;guard无租约不改变旧存档结果;静态核对原InitData(playfabUserData)仍在PF登录原位置与时间校准之后 |
| B2-02 | 配置kv/无效值/热变/AGG捷径均不能进入KV或Done;无能力和非编辑器创建能力失败,0请求 |
| B2-03 | internal真实共用链:一次认证→Get→全Decode→Reset/Replace→窗口内通知→LoadedForValidation;不Done、不启动玩法 |
| B2-04 | EmptyNew/EmptyExisting/只有活动键/短主档分别阻塞,0Apply、0意图、0业务写 |
| B2-05 | 现代19槽/旧完整主档/共存一致/冲突/损坏优先格式;不能回落另一份掩盖错误 |
| B2-06 | 附件每一键类型、null、溢出、坏JSON、重复键、未知配置、文化差异;每一种codec至少正反例 |
| B2-07 | 逐模块A有值→B缺可选键,同对象上断言所有登记加载字段为空态,不能抽样;新主档缺字段失败 |
| B2-08 | 在每个Reset/Replace及通知边界注入失败/撤销,无Loaded;Reset失败Poisoned销毁,不再用原容器 |
| B2-09 | MMTest存在/旧格式/缺失两种PF新旧身份/非法PFID;意图最多2、同键去重、关闭Discarded,0持久化 |
| B2-10 | Agg订阅只在await窗口内、finally必移除;linkedAccout/EAccoutUrl不残留,未触达真实SDK |
| B2-11 | PF缓存/普通写/remote=false/立即写/定时/暂停/销毁入口全拒绝;0缓冲修改/PlayerPrefs/SDK请求;违规导致加载失败 |
| B2-12 | TapJoy仅余额解析,无Helper、奖励、体力恢复、UI、网络或GEvent;离线宿主分别定义/不定义UNITY_ANDROID,链接真实UserService路径并提供SDK替身,验证PF原TapJoy分支和KV不创建Helper。Unity仅记录当前平台编译结果,不冒充Android真机/包验证 |
| B2-13 | Lease与Get同Cookie、仅一次认证、地址不匹配拒绝;无公共凭据/句柄泄露 |
| B2-14 | Lease发行与Close竞争、send与Close竞争、忽略取消传输;真实排水前不Dispose session;TCS闸门验证 |
| B2-15 | KV当前401撤销、旧401忽略、禁止后台认证;PF旧401行为不变 |
| B2-16 | 跨账号/同账号新epoch须等旧Close成功;跨Root登记表持续阻挡;10秒后观察返回专用阻塞错误,0新认证;gate超时/取消与获锁竞争不泄漏;迟到Prepare禁止Apply;Close失败不可绕过 |
| B2-17 | Quit同步撤销早于Hub await;5秒观察超时不释放资源、不清登记;延迟成功/故障均被观察 |
| B2-18 | Prepare全部错误+schema/apply错误叠加CloseFailed,OriginalError及阶段错误完整保留 |
| B2-19 | 4B1历史Task/首token/发布顺序测试完整保留;准备后的Lease关闭不破坏原语义 |
| B2-20 | Unity CLI Loop编辑器调用受控驱动:真实模块对象执行附件逐行Reset/Replace,临时容器/配置/替身finally恢复;不进入PlayMode |
| B2-21 | 既有23组、4A18组、42/86/29、Review、3C离线8、KVLiveTests仅build及Unity编译;记录实际退出码和新模块警告 |
| B2-22 | 最终白名单diff、禁改hash、所有产物脱敏、无真实网络证明(替身工厂+发送计数)、独立实施复核及关闭裁决 |
Unity驱动须在C盘项目、编辑器非播放且无正在运行的加载/场景切换时执行。一次性捕获/替换/恢复GContext容器与事件聚合器,不触碰真实Root配置;固定表使用当前资源的只读表快照,测试账号存档全为合成值。若无法构建隔离环境,B2-20记阻塞,不以“用假模块跑过”代替真实模块验证,也不临时打开PlayMode/联网。
已核实生产程序集名为HotUpdate(Scripts/HotUpdate.asmdef)。Editor包装器反射仅解决跨程序集调用入口,实际工厂和internal构造均在同一HotUpdate内调用;不新增InternalsVisibleTo、不更改asmdef、不依赖Editor直接访问internal。包装器接收固定caseId,结果为脱敏JSON/退出状态;环境不安全则运行前拒绝。Restore动作属于驱动finally,若后台任务尚未排水不得归还共享GContext给普通使用者:记录阻塞并保持测试隔离,待排水完成再恢复;不边后台运行边恢复全局容器。
## 11. 自审结果与交给审查者的裁决项
本修订把字段清单、缺失/损坏策略、模块空态、单会话借用、排水、配置/能力、意图丢弃语义、间接副作用、文件白名单和22条矩阵前置到编码前。原先“完整清单实施时补”“抽样即证明隔离”“超时即可重新开”的规则已撤回。
请审查者分别裁决:A字段与模块覆盖是否完整;B纯入口和显式空态是否可实现且不改变PF;C Lease/Registry扩展及4B1重新验证是否闭合;D受控入口不返回Done的阶段边界是否适当;E扩大白名单是否仍为4B2必要范围。**这是完整设计提案,不是审查通过声明;若任一合同仍有缺口,应列为P1/P2并退回补齐,不能留给实现者自由决定。**