15 KiB
阶段4存档接线分期与4A方案
最新补充(4B1关闭):4B1独立实施复核已完成,主会话核对6项交付及68项范围hash并逐条裁决三条P3,正式关闭4B1,见关闭裁决。下一步为4B2完整方案及独立评审;4B2/4B3未启动,整个4B/G4未完成。下方待审、待复核均保留为历史时点。
历史补充(4B1交付,现已关闭):4B分期及4B1方案已独立审查双PASS,主会话裁决后完成限定实现与技术验证,见《阶段4B1交付与验收矩阵-2026-09-23.md》。当时下一步为外部独立实施复核;4B2/4B3未启动,游戏/G4启用门不变。
最新补充(2026-09-23):4A独立实施复核已通过并由主会话逐项裁决关闭,见《阶段4A实施复核裁决与关闭-2026-09-23.md》。下一步为《阶段4B分期与4B1登录存档准备方案-待评审-2026-09-23.md》的外部独立审查;仅方案准备、未编码。下文4A“待外部实施复核”均为原设计/交付时点,已被本条覆盖;4B/4C和G4启用门继续有效。
日期:2026-09-23。状态:独立审查B及构建依赖补审通过;4A已实施并完成本轮技术验证,等待外部实施复核。 裁决见《3C补验收与4A方案审查裁决-2026-09-23.md》。最新准入:3C/G3已终审通过;4A构建依赖差异已补审并批准,实施及证据见《阶段4A交付与验收矩阵-2026-09-23.md》;本方案不把3C测试产物等同于游戏存档已迁移。
1. 完成效果和路线选择
最终效果:数字涂游ID登录s101后,游戏从该账号KV加载存档;正常编辑先持久化到本账号缓存,再串行同步;退出、换号、旧响应和不确定写入不会把存档混到另一账号。正式地址固定http://s101.local:18080/。PlayFab身份与其他未迁移功能分阶段保留,不把UserId机械替换为涂游ID。
| 路线 | 优点 | 代价/裁决建议 |
|---|---|---|
| A:先桥接,再统一切存档,消费路径单独验收 | 可复用大量现有存档调用,范围可审查,每步可验收 | 推荐;4A不接游戏,4B/4C需一起满足启用门 |
| B:直接整体重写PlayFabMgr并同时取消PlayFab登录 | 一次消除旧入口 | 登录、资料、玩法消费等耦合一起变更,回归面过大,本次不采用 |
| C:按业务key逐个双后端路由 | 单次变更看似小 | 同一次游戏初始化会拼接两份存档,跨key依赖和回退难证明,本次不采用 |
沿用用户分工:主会话编写文档,用户交其他AI审查,主会话裁决后实施;不创建子Agent代替外部审查。
2. 现状证据
已读取另一个会话新增的《阶段3C最终验收-2026-09-23.md》:其结论为G3关闭。本轮检查的Program.cs仍为相同SHA256(3C89DA087AA3841ADBA79A11BB2BAC724FD8F5F429D2124F826185EBBDDA64DA),R1–R5是该版本新增发现,需复裁;不覆盖外部审查原文,不否认其复跑事实。
调用检索快照见《阶段4存档调用盘点-2026-09-23.txt》,236行检索命中,包含定义和注释,并非236个有效调用。
| 位置 | 当前行为 | 接线影响 |
|---|---|---|
Services/UserService.cs LoginCore、LoginPlayFab、InitData |
先登录PF,从InfoResultPayload取UserData,经CompareDatas初始化,最后登录API | KV接入必须把游戏初始化移到API登录与Get/Reconcile之后;失败不触发Done |
Services/AggUserService.cs Login |
OnInitData临时订阅在base.Login结束后释放;使用UserId作为PF埋点身份 |
新初始化必须在订阅有效期间完成,不能后台延迟触发;身份迁移另审 |
Core/PlayFabMgr.cs |
旧时间戳/PlayerPrefs比较,40秒定时同步,5×10缓冲,Public PF写入 | 新路径不能继续使用旧缓冲或时间戳;localOnly需保持独立语义 |
Services/PlayerData/PlayerKVSessionOwner.cs |
固定账号/环境/epoch,提供Update/Delete/Flush/Close | 尚无公开读取本地合并快照入口;游戏层不能另开State争同一文件锁 |
DataCenter/DataCenter.PlayerData.cs LoadPlayerData/New |
缺数据默认值,分派大量模块;NewPlayerDataDic仅在key存在时赋值 | 换号不能沿用旧字典;不能把Get失败/老账号空KV当新存档 |
GampPlay/FishingB/FishingBehaviorB.cs ConsumeBaitOnPlayfab |
本地先扣体力,保存bool仅打印 | Unknown后不允许重新扣减,须固定本次操作意图 |
UI/Turntable/FishingTurntablePanel.cs ConsumeOnPlayfab |
无论bool结果都设isDataSynced=true | 接KV前必须处理确认/未知结果,防止假成功 |
身份假设已定位:UserService.cs:306的MMTest十六进制解析、SurveyService.cs:56、FriendService.cs:375的邀请码转换、SandDig/model/Data/DinggingSceneData.cs:94的随机种子、UserService.cs:146的5字符截取。另有排行榜、小玩法、水族馆的目标玩家ID,以及活动机器人HexNumber解析;这些来源不同,不得全局替换。正式身份切换前逐项标明本人/他人/机器人、存储兼容与稳定种子规则。
Coordinator目前持有不断追加的tickets字典(PlayerKVSyncCoordinator.cs:68/114/127),未见按确认状态清退。4A短生命周期测试可以沿用;游戏长期会话启用前须完成有界历史设计和评审,不能直接删记录破坏旧票据Wait语义。
3. 阶段顺序与启用门
| 子阶段 | 交付物 | 可验收的效果 | 不可提前执行 |
|---|---|---|---|
| 3C补验收 | R1–R5闭环、逐行矩阵、外部复核 | 正式fixture安全边界及隔离证据完整 | 前置不通过不改生产接线 |
| 4A 会话桥接与安全读接口 | 独立桥接类、Owner受租约保护的只读快照接口、合成测试 | 单一owner、只读Ready快照、异步关闭、旧句柄失效可验证 | 不接UserService/PlayFabMgr/游戏,不访问正式业务KV |
| 4B 登录加载与普通存档路由 | 单次登录固定后端、KV初始化链、旧facade兼容、生命周期接线 | 普通读写及换号闭环;空档/解析失败不会触发默认写 | 4C未通过不启用含消费的游戏路径;不PF/KV双写或自动迁移 |
| 4C 立即保存与消费流程 | 两个调用点的结构化结果处理、冻结交互和防重入 | 未确认不发奖、不重复扣减、不把未知标成功 | 不以重试业务动作补救Unknown;不宣称KV具备交易原子性 |
| 4C后身份专项 | 涂游UserId契约、hex假设修正、PF依赖分类与降级方案 | 本人身份一致,数字短ID正常;主登录不依赖PF认证成功 | 未迁移外围不能把数字ID冒充PF ID;需单独完整评审 |
| 4D Unity场景验收 | 指定账号场景、失败/退出/换号/暂停恢复证据 | 真实场景使用批准的存档来源,无旧账号数据残留 | 真实业务key写入需单独测试数据边界;101/102的fixture授权不覆盖游戏全量写 |
4B/4C/4D目前只是排期边界,不能直接作为可实施任务书。每阶段仍需完整方案、文件边界、独立审查和裁决。打包最后做。
4. 4A完整设计
4.1 职责
新增PlayerKVGameSessionBridge,由未来登录控制器持有。它负责等待旧owner释放、创建新owner、把固定会话封装成不可变句柄,并提供结构化错误。它不负责登录、读取PF、业务默认值、定时器或游戏初始化。
每个桥接实例最多有一个活动owner。持有S101AccountSession引用但不拥有登录session,不主动Dispose它;未来登录控制器负责在桥接Close完成后再销毁session。生产缓存根由未来Unity组合入口捕获Application.persistentDataPath并注入,本类不引用Unity API。
所有延后保存调用必须持有创建时获得的固定句柄,不能在回调时通过“当前账号”查找新owner。4A只实现句柄规则,4B必须盘点并落实到现有全局facade调用链;仅增加全局epoch不足以防止旧业务回调写新账号。
4.2 Owner新增最小读取接口
建议接口:ReadLocalSnapshot()返回独立的PlayerKVLocalReadResult:Success、Error、OwnerInstanceId、PlayerId、Epoch、只读字符串字典Data。Error范围:None、StaleOrClosing、NotReady。
错误优先级:StaleOrClosing优先于NotReady。读前、读后及处理State.ReadLocal的NotReady异常时都需复查归属/关闭状态,避免旧句柄被误报为仍可等待的数据未就绪。
实现边界:进入Owner已有操作租约,验证固定会话/epoch;只有State.ReadLocal成功且读取结束前再次确认owner有效才返回;Data必须是独立不可变快照,不暴露内部字典。生命周期竞争返回结构化失败,不泄漏InvalidOperationException。禁止把拒读包装为空字典;业务key不存在只有在有效快照里才能解读为缺失。
读状态与读取之间有竞争时,以State.ReadLocal的原子状态检查为准;捕获其NotReady情况。不新增第二个State实例,不暴露Cookie、HttpClient或状态可变引用,不改变3A状态机。
4.3 桥接操作契约
| 操作 | 输入/结果 | 规则 |
|---|---|---|
| OpenAsync | 已认证session、规范API、数字playerId、epoch、isCurrent委托、缓存根、生命周期token;结构化打开结果+固定句柄 | 串行等待前一Close真正完成;入场后重查epoch/token;构造过程不得发布半初始化句柄 |
| 句柄ReadLocalSnapshot | 只读结果 | 仅当前且Ready;旧句柄不能转发到新owner |
| 句柄Update/Delete | 原有edit result和票据 | 保留本地持久化接受语义;Accepted不是远端确认 |
| 句柄Flush/WaitForTicket | 原有结构化结果 | 保留3B Confirmed/Superseded/Unknown等区别;不增加重试 |
| CloseAsync | 可重复等待的Task | 同步停止句柄新操作;等待owner Close完成;释放后才允许新Open取得活动位置 |
Open排队取消只取消该次Open等待,不取消前一个Close。Close异常保留关闭失败状态并拒绝后续Open,不把锁未释放误作可重用。Close没有成功前不得遗失原Task/owner引用。若调用方停止等待,内部收尾继续;不能通过超时返回后立刻再开同一账号。
若Create/初始Get失败,在失败owner正常收尾后必须清空桥接活动位,让下一合法Open可以进入;若收尾失败,沿用关闭失败阻塞规则,不能无条件清空。
CreateAsync成功只说明owner建成;Conflict/Unknown不是Ready。4A不得对非Ready句柄发布可玩数据;返回状态供4B决定阻塞提示。注销先使外部generation失效并停止业务入口,再等待Close;更换session必须经过此屏障。
4.4 测试与文件边界
批准后仅允许:
- 修改
Client/Assets/Scripts/Services/PlayerData/PlayerKVSessionOwner.cs:新增受租约保护的读取接口。 - 新增
PlayerKVLocalReadResult.cs、PlayerKVGameSessionBridge.cs及对应meta。 - 新增
CodexOutput/upserver/KVGameBridgeTests/显式链接源码的合成测试宿主及证据。 - 主会话维护4A实施记录和入口状态。
禁止修改UserService、PlayFabMgr、玩法、3A State/Cache、3B Coordinator、网络Client、服务器和包;新增接口需求必须先差异评审。允许测试通过internal构造点注入Handler,公开生产默认构造仍使用固定Cookie、禁重定向,不新增公共任意传输入口。
4A不向正式服务发请求。所有缓存写入任务目录,合成值和Cookie仅用于替身测试。项目继续使用Unity CLI Loop,不使用UnitySkills。
4.5 4A验收矩阵
| 编号 | 场景 | 必须观察到的结果 |
|---|---|---|
| A1 | Get完成、Ready读 | Data与ReadLocal语义一致,包含pending和localOnly,不能修改内部状态 |
| A2 | 未认证/ID或API不匹配/Open取消 | 结构化拒绝,未发出变更请求,无遗留锁;失败完成收尾后下一合法Open成功 |
| A3 | Busy/ReadFailed/Conflict/Unknown/StorageError | 不返回可用游戏快照,不返回伪空档;初始Get/Create失败不永久占用活动位,收尾失败仍阻塞 |
| A4 | 101 Close尚有忽略取消的传输,102请求Open | 102等待;旧传输及Close完成后才能发布102句柄 |
| A5 | 同账号快速重复Open、重复Close | 至多一个活动owner;Close幂等,没有后台操作遗失 |
| A6 | 旧101句柄在102已开后Update/Flush/Read | 拒绝且102缓存和请求计数不变 |
| A7 | 排队Open取消,Close仍在进行 | 取消方收到失败;原收尾正常完成,下一合法Open可用 |
| A8 | 读取与Close/epoch失效/在途Flush竞争 | 有效不可变快照或结构化拒绝;StaleOrClosing优先;在途Flush时验证baseline→pending→localOnly视图且不改变批次或发送次数 |
| A9 | session提前销毁或重登换Cookie | 旧句柄失效;桥接仍能等待Close,不借新Cookie |
| A10 | 持久化pending后关闭重开 | Close不隐式Flush,原账号经Get恢复后可读pending,无跨账号覆盖 |
| A11 | 生产默认传输与诊断 | 默认固定Cookie,无正式请求;输出不含Cookie、值或完整业务key |
| A12 | 回归与集成 | 阶段2/3A/3B及修复后的3C离线通过;Unity CLI Loop编译通过;未允许生产文件hash不变 |
测试至少使用闸门控制并发时序,不能以短Sleep冒充“旧任务仍在运行”。每条矩阵映射到测试名和运行证据,缺项不能用总通过数代替。
5. 4B/4C设计约束(后续详审)
- 单个登录会话固定后端;切换存档后端必须退出并销毁当前上下文。KV成功后不镜像到PF;KV失败不自动回落PF。
- 初始建议保留PF登录用于仍依赖它的资料/外围,拆掉PF UserData初始化;游戏Done要求s101认证、KV Ready、数据校验和初始化都成功。这不等于完整移除PF依赖。
- 上条只定义4B过渡方案;既有G4“涂游ID直接作为UserId”的目标继续保留,完整G4关闭还需要身份专项及非AGG/编辑器路径通过。不得用4A/4B通过宣称G4完成。
- 公私数据契约不同:旧PF写为Public,新KV为本人私有;他人等级/资料读取继续单独迁移,不能改成读本人KV。
- KV为空不等于“新玩家”,
newCreate也不单独授权覆盖。默认策略:现有账号空KV阻塞进入游戏,不自动导入旧PF/PlayerPrefs、不生成新档覆盖。新玩家首次建档须另定显式流程与可重复结果,不能把初始化失败后的重试当新账号。 LoadPlayerData的所有初始化副作用需盘点;加载、失败重试、换号必须清除旧业务内存,且不向未Ready后端写默认值。主档缺失/字段解析异常分别处理,不捕获成空档。- 立即保存只在对应票据Confirmed时视为成功;Superseded不是该消费确认,Unknown不重新执行扣减动作。冻结消费并保留证据;没有服务端事务/幂等协议,不能声称解决跨设备交易一致性。
- 退出/暂停只可触发有明确所有者的Flush;OS退出不保证网络完成。本地pending持久化与网络确认分别报告。
6. 审查与开发前置条件
其他AI已审查本文4A接口、生命周期、边界和矩阵,结论通过,无新增P1/P2;主会话采纳B-S1–B-S3。3C补验收及外部复核已闭环。开工构建依赖差异已补审PASS并裁决,4A已获准实施;现已完成本轮技术验证,等待外部实施复核。
本方案4A已完成设计审查、实施和本轮技术验证,待外部实施复核。4B中的新档策略、完整游戏回调隔离与消费流程需要后续具体方案;这些不影响4A作为独立桥接层的接口测试,但阻塞4B启用。