# M2:保留 PlayFab 并新增独立 KV 实现的双提供方切换约束 日期:2026-09-29。**用户最新裁决,覆盖此前“最终删除 `PlayFabUserService`”的错误记录**:原 PlayFab 路线必须保留;另写独立的 KV 路线;用一个明确的启动开关选择 PlayFab 或 KV。不得把“移植完成”解释为删除 `PlayFabUserService`、`PlayFabMgr` 或 PlayFab SDK。此文供开发 Agent 接手和编制完整方案,**不是当前批次扩大代码白名单的批准**。 ## 全局简洁性规则在 M2 的应用 遵守项目根目录的 [`AGENTS.md`](../../../../AGENTS.md):在满足已确认功能、正确性和必要安全边界的前提下,抽象越简单明了越好。双提供方方案先比较“复用现有接口并做最小拆分”与新增层次的成本;每新增接口、适配器、管理器或状态,都要指出不可省的职责和实际调用者。不要为了看起来对称而复制 `PlayFabUserService`、额外包裹 `KVMgr` 或叠加第二套开关/Guard。确有安全或生命周期约束时应保留并说明,不能以简化为由删掉必要保护。 ## 目标调用关系 ```text 游戏业务 / 登录入口 ↓ 提供方无关的用户与存档契约 启动时选择提供方(PlayFab 或 KV;一次会话内不热换) ├─ PlayFabUserService + PlayFabMgr → PlayFab Client SDK(保留、可启用) └─ 独立 KV 用户服务 + KV 存档入口 → KV 会话 / 业务 API(新增) ``` 开关需有单一、可观察的选择结果,并在创建用户服务、存档实例及登录前冻结;不能在已登录或已有存档实例时改后端,不能在 KV 失败时静默回退 PlayFab。现有 `SaveBackend` / `PlayerKVRuntimeSelection` 可作为设计输入,最终开关语义和 DI 注册须经完整评审确定。PlayFab 路线和 KV 路线均须可分别启动与回归。 ## 当前代码与应处理的边界 | 位置 | 当前事实 | 目标 | |---|---|---| | `Services/UserService.cs` | `PlayFabUserService : IUserService` 承载原 PlayFab 登录;`IUserService` 含 `EntityKey`、`playFabId` 等 PlayFab 语义。 | 保留原类与原功能;抽出真正共用的身份、资料、授权契约。PlayFab 专属能力留在 PlayFab 专属接口/实现,KV 不伪造 PlayFab ID 或 `EntityKey`。 | | `Services/UserService.KVLoad.cs`、`UserService.KVGameplayLoop.cs` | KV 编排目前接入 `PlayFabUserService` partial,属于迁移过渡。 | 经评审后将 KV 登录、Coordinator、原 handle、生命周期与普通业务请求迁入**独立 KV 实现**或其组合组件;不继承 `PlayFabUserService`,不把旧类复制一份。清理旧类中迁移后闲置的 KV 分支,但保留其 PlayFab 路线。 | | `Stages/LoginStage.cs`、`PFLoginAct.cs` | 当前 DI 注册 `PlayFabUserService`/AGG 子类,登录入口又读取具体旧类的 KV 状态。 | 按冻结开关注册相应实现,登录入口经中立结果/会话契约消费;去掉 KV 对旧具体类型的下转,保留 PlayFab 模式的合法依赖。 | | `Core/PlayFabMgr.cs` 与 `Core/KVMgr.cs` | 前者旧存档缓存/同步直接调用 `PlayFabClientAPI`;后者通过 `PlayerKVGameSessionHandle` 操作 KV。 | 两条提供方路径并存;业务逐步改依赖中立存档入口或提供方适配层,PlayFab 模式仍可调用原同步能力,KV 模式只用原 KV handle,不启动旧 PF 定时同步。 | | `PlayerKVGameplayBindings.cs` | 31 模块绑定仍要求 `PlayFabUserService`。 | 抽离 KV 加载接收/身份状态所需的窄契约,保持 Apply、Guard、epoch、失败回滚原合同;PlayFab 模式功能不回归。 | | `AggUserService.cs` | AGG 当前继承 `PlayFabUserService`。 | 保留现有 PlayFab/AGG 通道;KV 是否覆盖 AGG 需另行评审,不能因新增 KV 服务自动改变渠道行为。 | ## 实施顺序与验收 1. 先按既有审查白名单收口当前 CP2B-T 批次。后续新增双提供方方案须完整评审,明确开关、契约拆分、DI、账号与业务玩家身份、31 模块、AGG、异常/退出/旧 epoch 处理、场景和 Prefab 绑定,再逐项裁决开发边界后编码。 2. 建立独立 KV 用户实现及其 KV 存档路径;保持 PlayFab 实现可运行。各批迁移业务调用时同时验证 PlayFab 与 KV 两侧的读写、身份、失败及退出行为,不以空实现或临时冻结冒充完成。 3. 最终通关前按[临时门控清单](M2-最终通关前临时门控与忽略路径清理清单-2026-09-29.md)解除开发期跳过路径,核两种开关模式的完整业务链、重启恢复及安全边界;只清理迁移后闲置的过渡实现,不删除仍供 PlayFab 模式使用的类、SDK、注册和资源。 4. `PlayerKV*` 命名按既有用户裁决,在整体移植完成后另批处理。`Core/KVMgr.cs → Services/PlayerData` 的物理层级反向依赖另行纠偏,不因双提供方方案自动认定已解决。 **验收标准**:一个启动开关可明确启用原 PlayFab 路线或独立 KV 路线;二者互不串用凭据、存档与运行时实例;PlayFab 旧功能回归通过,KV 全链通过,KV 不再依赖 `PlayFabUserService` 的具体类型或 PlayFab SDK,且迁移期保护门在最终通关前按清单销账。正式地址、101/102 当前准入和服务器代码边界遵守既有裁决。 ## 项目结束前必须逐项确认的边界(当前均未验收) 以下项目必须由最终实现、调用图、双模式运行证据和审查裁决逐项销账;不能因本文已写下目标就标记通过。 - [ ] **PlayFab 路线保留**:开关选择 PlayFab 时,`PlayFabUserService` 仍负责其登录/资料,`PlayFabMgr` 仍负责旧存档缓存与同步,并直接使用 `PlayFabClientAPI`;其余 PlayFab SDK 调用按原功能保留。确认原渠道、场景、Prefab、DI 注册及代表性业务回归,而不是只证明能编译。 - [ ] **KV 路线独立**:开关选择 KV 时,使用独立的 KV 用户服务和基于原 `PlayerKVGameSessionHandle` 的 KV 存档路径;不得借 `PlayFabUserService` 实例、`PlayFabMgr` 活动实例或 PlayFab SDK 凭据完成 KV 业务。当前 KV partial 与旧类绑定应有经评审的抽离结果。 - [ ] **开关只有一处决策**:启动阶段明确选择并冻结提供方;用户服务、存档、账号会话、普通请求和场景入口得到一致选择。登录后不得热切换,也不得因 KV 错误静默落到 PlayFab;失败时保持可解释的错误和可恢复边界。 - [ ] **业务依赖可读**:业务层依赖中立且语义明确的用户/存档契约,提供方专属类型留在各自实现或适配层;核 `IUserService` 中的 `EntityKey`、`playFabId`、`UserId` 语义,`PFLoginAct` 的具体类型下转,以及 31 模块绑定。不能为了共用接口给 KV 伪造 PlayFab 身份。 - [ ] **抽象简洁性**:逐项说明最终保留的接口、适配器、管理器和状态各自的实际调用者与不可省职责;合并重复开关、重复状态和无消费者的包装层。用真实调用图确认业务流程能直接读懂,复杂性只来自已确认的功能或必要保护。 - [ ] **会话与写入隔离**:分别核两模式的登录、授权、读写、同步/票据、退出、暂停恢复、重启恢复、过期凭据和旧 epoch 回调;确认 KV 的 Guard/原 handle 租约与 PlayFab 的原同步路径不会交叉执行或串档。 - [ ] **渠道和临时路径清账**:明确 AGG 与其他渠道各自支持哪种提供方;按临时门控清单恢复为开发方便而冻结/忽略的功能。只清理无消费者的过渡 KV 分支与闲置实现,不能误删仍供 PlayFab 模式使用的代码和资源。 - [ ] **最终证据与裁决**:输出两种开关设置的真实最终调用流程图、覆盖上述项目的测试/运行证据和未覆盖范围;完成独立复核与主会话逐项裁决后,才可将双提供方边界标记为通过并结束项目。