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

29 KiB
Raw Blame History

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

日期:2026-09-23。本文件与字段及模块合同共同组成受审设计,替代原初稿;尚未批准实施。 原初稿保留在快照。前置4B1关闭裁决保持有效;本修订对其接口扩展另行申请审查,不用原批准覆盖新增改动。

正式地址固定 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. 字段解码与失败策略

具体键、优先级、模块归属、空态及格式见字段及模块合同,是本方案不可分割部分。

  • 首先检查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原结果类型保持;如构建依赖、公开接口、白名单不够,先形成差异方案并补审。

检查点顺序:

  1. 外部方案审查→核对本次基线hash→逐条裁决并记录扩围及启用合同,才批准编码。
  2. 单认证Lease/Registry与关闭竞争;先4B1原23组回归+新增借用竞争组。不能先接游戏再补生命周期。
  3. 附件所有字段codec与模块空态,纯解码/应用宿主、每模块缺失及坏值测试。
  4. UserService/Agg受控编排、PF拒写门与Quit观察,完整失败矩阵。
  5. 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并退回补齐,不能留给实现者自由决定。