保存独立KV用户服务、固定会话存档与原业务接线,以及C3d资料缓存和C3e本人统计写。 保留原PF/AGG路线、业务公式和时序;榜单读取、好友资料、公告、匹配及首场景闸仍待完成。 验证:ServiceHost两条件各33组646断言;Unity CLI Loop编译0错误269警告。 包含相关方案、评审裁决、测试及状态文档。未操作服务器、正式账号或打包。
168 lines
24 KiB
Markdown
168 lines
24 KiB
Markdown
# M2 DP1 独立方案审查(双提供方独立 KV 用户服务抽离)
|
||
|
||
日期:2026-09-29。审查人:原 ZCode(独立侧)。只读审查,未修改任何生产/测试/服务器代码,未调用正式 101/102,未打包。
|
||
|
||
## 0. 受审基线核验
|
||
|
||
- [18 项受审基线](M2-DP1受审基线.json)逐项按字节数与 SHA256 重算:**18/18 一致,无漂移**。
|
||
- 清单自身 SHA256 实测 `858C43EF26361EB348D4CE36C7F3AFC8AF4DAF85E754A45F3D960A2D078145FA`,与提示词记录一致。
|
||
- 本报告据无漂移基线作出,可作为主会话裁决输入。基线外只读扩展源码调用点(`PlayerKVEditorValidationHost.cs`、`EventFishingDuelPanel.cs`、`FriendService.cs`、`LeadboardData` 等业务文件、`S101LoginWindow.cs`、`CodexOutput/upserver/KVGameLoadTests/ServiceHost/*`)仅用于证据,相关发现单独标注"基线外"。
|
||
|
||
## 总裁决
|
||
|
||
**限定通过(需修订后可进入白名单裁决)**。方案 C 的最小纵切方向成立:调用图证据显示正式 KV 路径可以在 DP1 内完全脱离 `PlayFabUserService` 运行时实例与 PlayFab SDK 调用,同时 PF/AGG/受控验证/kv-loop 四条既有通道不需要行为变化。无 P1 阻断。有 3 项 P2 需修订(R1/R2/R3)与 3 项 P3 登记(见 §8),全部是"把方案自身遗留的未决点落成可执行裁决",不是结构性缺陷。
|
||
|
||
---
|
||
|
||
## 1. 三方案比较是否成立(重点:方案 C 能否真正移除 PlayFabUserService 与 SDK 依赖)
|
||
|
||
**结论:通过(附 R1/R2 修订条件)。**
|
||
|
||
源码证据链:
|
||
|
||
- 启动装配:`SubBoostrap.cs:37-42` 一次捕获 `PlayerKVRuntimeSelection`;`SubBoostrap.cs:103` `if(!selection.IsKV) rootCtxGo.AddComponent<PlayFabMgr>()` —— KV 模式下 `PlayFabMgr` 组件根本不创建,`PlayFabMgr.HasLiveInstanceForKVLoad` 恒假(`PlayFabMgr.cs:31`)。
|
||
- 登录注册:`LoginStage.cs:24` 目前无条件 `Register<IUserService, PlayFabUserService>()`,是正式 KV 依赖旧类的唯一注入源;方案 C 只需在此按冻结选择分支注册新 KV 类。
|
||
- 登录行为:`UserService.cs:195-226` `LoginCore` 中 `if(IsFormalKV) return await LoginKVFormalAsync(...)`(:200)在 PlayFab SDK 全部步骤(`LoginPlayFab`/`OnYinDunLogin`/`InitTapJoy`/`LoginApiServer`,:207-225)之前返回;`PFLoginAct.cs:94-115` 正式 KV 分支在 `MailService.Init(); iapService.Init()`(:139-140)之前 return。即正式 KV 现有执行路径已零 PlayFab SDK 调用,抽离后把这段逻辑整体搬进新类即可,无需改语义。
|
||
- 正式 KV 编排全部位于 `UserService.KVLoad.cs` partial(`LoginKVFormalAsync` :82-147、`LogoutFormalKV` :67-75、`BindFormalPromptServices` :148-171、`CreateFormalKVHandler` :64-65、`OnFormalKVApplicationQuitting` :66),对旧类其余成员的依赖只有:`config`/`customServerMgr` 注入属性(:104-106)、`kvControlledInstance`/`runtimeSelection` 字段(:16-17)、`logoutGeneration`/`loginLifetime`(`UserService.cs:169-172`)、模块 31 的 `ResetForKVLoad`/`ReplaceFromKVDecoded`/`KVIntents`(:222-251)。这些均可随迁或在窄接口上重建,见 §3。
|
||
- 旧类保留通道不受损:
|
||
- PF 模式:`LoginCore` 去掉 `IsFormalKV` 分支后即恢复纯 PF 语义;`LoginStage` 非 KV 分支注册不变。
|
||
- AGG:`PlayerKVRuntimeSelection.Capture` 在 agg+kv 时直接抛异常(`PlayerKVRuntimeSelection.cs:29-30`),AGG 构建永远走 `AggUserService`(`LoginStage.cs:20-22`),不因 DP1 改变。
|
||
- kv-loop:`UserService.KVGameplayLoop.cs:537-586` `LoginKVGameplayLoopAsync` 深度依赖 PF 身份(`LoginPlayFab` :552、`LoginApiServer` :553、`PlayFabMgr.Instance.StartKVGameplayLoopSync()` :576、`ForgetAllCredentials` :613),它必须也应当留在 `PlayFabUserService` 上;`Bind(this)`(:570)在 Bind 改窄接口后仍编译(旧类实现该接口)。
|
||
- 受控验证:`PlayerKVEditorValidationHost.cs:236,265-267` 手工 `new AggUserService(...)` 并直接调用 `user.ResetForKVLoad`/`ReplaceFromKVDecoded`/`KVIntents` —— 签名不变即不受影响(基线外证据)。
|
||
- 编译级约束(须写入方案):新 KV 用户服务源文件**不得**引入 `PlayFab.*` using、不得引用 `PlayFabMgr`/`PlayFabUserService` 具体类型;"双模式证明"不得以能编译冒充,须按 §7 矩阵以注册结果与 `HasLiveInstanceForKVLoad` 断言。
|
||
|
||
方案 A/B 的否决理由与既有一致(A 无法满足双提供方目标,B 需要未核的线上合同),维持。
|
||
|
||
**为什么是"附条件"**:方案把两个决定性问题(`IUserService` 实现义务、登录/退出阶段的中立面)留作未决点(方案 §"完整评审需集中裁决的未决点" 1、4),本审查已给出确定性答案(见 §2、§6),方案文本需按此落成可执行裁决,否则白名单无法精确冻结。
|
||
|
||
## 2. DI/构造路径追踪:DP1 是否必须让独立 KV 类实现当前宽泛 IUserService
|
||
|
||
**结论:必须实现。追据如下(R1 的核心证据)。**
|
||
|
||
- `PlayerKVGameplayBindings.Bind` 在正式登录 `LoginKVFormalAsync` 内执行(`UserService.KVLoad.cs:100`),`Require<GameCore.SmallGameData>()`(`PlayerKVGameplayBindings.cs:28`)触发容器构造 `SmallGameData`。
|
||
- `DataCenter.SmallGame.cs:26-30`:`public SmallGameData(cfg.Tables tables, IUserService userService, ICustomServerMgr customServerMgr)` —— **构造函数注入** `IUserService`,是 31 个模块中唯一的构造参数级依赖(其余 4 个 DataCenter 文件均为方法内 `Resolve<>` 或属性注入:`DataCenter.PlayerData.cs:599`、`DataCenter.FishingEventData.cs:514` 等、`DataCenter.LeadboardData.cs:137`)。
|
||
- 模块 14 `ClubService` 有 `[Inject] public IUserService userService { get; set; }`(`ClubService.cs:86`),在 Bind 解析时做属性注入,同样要求容器内已注册 `IUserService`。
|
||
- `PFLoginAct.cs:15` `[Inject] public IUserService` 是登录页的必经注入。
|
||
- 结论:若 KV 类不实现 `IUserService`,KV 模式下 `SmallGameData` 无法构造、Bind 失败。替代路线(把 `SmallGameData` 构造与 `ClubService` 属性改为窄契约)需把 `DataCenter.SmallGame.cs`、`ClubService.cs` 加入白名单,改动两处业务文件,但 DP1 执行路径上**没有任何成员被调用**(`SmallGameData` 构造函数只存引用不调用成员,`DataCenter.SmallGame.cs:26-34`;受控验证 fixture 甚至传 `null`,`PlayerKVEditorValidationHost.cs:237` 基线外证据),因此该替代在 DP1 无收益,**不推荐本批做**,登记为 DP2+ 可选项。
|
||
|
||
**必须同时给出的成员三分表**(否则"显式失败"不可审计):
|
||
|
||
| 类别 | 成员 | KV 类处理 | 依据 |
|
||
|---|---|---|---|
|
||
| 真实实现 | `Login`/`Logout`/`LoginLifetime`/`CustomId` | 迁入正式 KV 逻辑 | `PFLoginAct.cs:38,88,96,111,168` 是登录页仅有的接口调用;`LoginLifetime` 用于链接取消 |
|
||
| 可观察状态(如实反映"未发布") | `IsLogin`/`IsAuthorized`/`UserId`/`ApiPlayerId`/`InternalId`/`entityKey`/`DisplayName`/`AvatarUrl` | 恒 `false`/`null`/`0`,与今日正式 KV 现状逐项一致 | `UserService.cs:110-115` + `LoginKVFormalAsync` 从不写这些字段;`LogoutFormalKV` 置空(`UserService.KVLoad.cs:71`);不是伪造,是如实反映"身份未发布" |
|
||
| 显式失败(throw InvalidOperationException,消息含"formal KV identity/profile contract pending") | `ChangeDisplayName`/`UpdateAvatarUrl`/`UpdateLanguage`/`LinkAccount`/`GetLinkState`/`SetPlayerLv`/`GetPlayLv`/`GetPlayerLv`/`GetPlayInfo`/`SetPlayInfo`/`ClearPlayInfos`/`UpdateProfile`/`UpdateProfileLv` | 全部显式失败并登记清账 | 全部调用点位于 FishingStage 闸后(社交/资料/UI)或 PF 专属链;DP1 执行路径零调用(§6 证据) |
|
||
| 登记裁决 | `CreateTime`/`LastLoginTime`/`LoginTime`/`CreateTotalSeconds`/`ContinentCode`/`Language`/`EAccoutUrl`/`Dictionary EAccoutUrl` | `EAccoutUrl` 按模块 31 置换真实 KV 值(`UserService.KVLoad.cs:249`);时间/大陆/语言字段维持默认值并在清账清单登记(P3-1) | `FishingEventData`/`PlayerShopData` 读 `CreateTime`(基线外,闸后);默认值行为与今日正式 KV 完全一致,不新增伪造 |
|
||
|
||
不接受项复核:方案明令禁止静默空实现、假 EntityKey、假 PlayFab ID、KV 失败回退 PF——上表不触碰任何一项;`GetDefaultName` 不属于上表(见 §4)。
|
||
|
||
**P2 风险 R1**:方案 §"未决点 1" 仍以问句留空。按本节证据应改写为确定裁决:"DP1 的独立 KV 类实现 `IUserService`,成员按上表三分;窄化 `SmallGameData` 构造依赖延后到 DP2+,登记最终清账"。
|
||
|
||
## 3. 第 31 模块的最小中立接收契约
|
||
|
||
**结论:通过。契约成员恰好三个,方案描述准确,可按下述精确化。**
|
||
|
||
事务侧全部消费点(`PlayerKVGameplayBindings.cs:55-56`):
|
||
|
||
```csharp
|
||
modules.Add(new PlayerKVLoadModule(31, user.ResetForKVLoad,
|
||
d => user.ReplaceFromKVDecoded(d.Module<PlayerKVLoginState>(31), d.Epoch, d.Raw)));
|
||
return new PlayerKVGameLoadTransaction(modules, ..., () => user.KVIntents?.Discard());
|
||
```
|
||
|
||
即窄契约 = `ResetForKVLoad()` + `ReplaceFromKVDecoded(PlayerKVLoginState, long epoch, IReadOnlyDictionary<string,string> raw)` + `KVIntents`(或折叠为 `DiscardKVIntents()` 委托源)。三个调用通道:正式(`UserService.KVLoad.cs:100`)、kv-loop(`UserService.KVGameplayLoop.cs:570`)、受控验证(`PlayerKVEditorValidationHost.cs:265` 基线外,手工组模块、直调具体方法、不经接口)。接口只需服务 Bind 一处调用者,两实现类(`PlayFabUserService` 供验证/kv-loop、新 KV 类供正式),符合 AGENTS.md"实际调用者"要求。
|
||
|
||
**字段/成员归属清单**(方案要求"哪些留在旧类、哪些必须迁出",此处给出完整盘点,全部出自 `UserService.KVLoad.cs` 行号):
|
||
|
||
| 归属 | 成员 | 行号 |
|
||
|---|---|---|
|
||
| 迁入新 KV 类(正式专属) | `kvHandoffEpoch`/`kvHandoffHandle`/`kvFormalLoading`/`kvFormalAttempted`/`kvFormalPromptsBoundEver`/`kvFormalClosing` | :22-26 |
|
||
| 迁入新 KV 类 | `IsFormalKV`/`KVHandoffReady`/`TryGetCurrentGameHandle`(epoch 对账) | :27-39 |
|
||
| 迁入新 KV 类 | `LoginKVFormalAsync`/`LogoutFormalKV`/`TrackFormalClose`/`BindFormalPromptServices`/`CreateFormalKVHandler`/`OnFormalKVApplicationQuitting` | :64-171 |
|
||
| 迁入新 KV 类 | `CaptureSaveBackend`/`RejectSaveBackend` 的 formal 分支(:47-51)——旧类保留 PF/kv-loop/受控分支 | :40-55 |
|
||
| 迁入新 KV 类 | `ObserveKVQuitAsync` 的 formal 观察语义 | :194 |
|
||
| 留在旧类(受控验证/kv-loop 专属) | `ConfigureKVForValidation`/`LoadKVForValidationAsync`/`CloseKVForValidationAsync`/`KVLoadedForValidation`/`KVReceiverCountForValidation`/`EnterKVReceiver`/`ExitKVReceiver`/`kvReceiver`/`kvReceiverUsers`/`MMTestForKVValidation`/`IsControlledKVInstance` | :56-63,172-203,219-220 |
|
||
| 两边各自实现(窄契约接口) | `ResetForKVLoad`/`ReplaceFromKVDecoded`/`KVIntents`/`KVLoadingCopy`/`MMTestValue`/`KVTapJoyBalance`/`EAccoutUrl`/`KVLastError` | :21,216-218,222-251 |
|
||
| 留在旧类(旧类自身还需) | `kvControlledInstance`/`runtimeSelection`(受控实例判定) | :16-17 |
|
||
|
||
拆分时必须保持的安全语义(方案已列,逐条核过源码成立):31 模块定序与主线程 Apply(`PlayerKVGameLoadTransaction.cs:37-45`)、失败 `ResetAll`+`discard` 回滚(:77-83)、`admitted` 准入在两个实现里各自收紧(旧类删掉 formal 项后剩 `kvControlledInstance || KVLoopLoading`;新 KV 类为"formal loading/closing 且无 handle 且 `InternalId==0` 且无 api 会话")、`AggUserService` 的 `internal override ResetForKVLoad/ReplaceKVLinked`(`AggUserService.cs:20-26`)不受影响。
|
||
|
||
## 4. IUserService 身份成员的分提供方语义
|
||
|
||
**结论:需修订(并入 R1 的成员表);DP1 不修改共享接口。**
|
||
|
||
- `entityKey`:真实非注释读取全客户端仅 2 处,均在 Duel 事件 UI:`EventFishingDuelPanel.cs:634-635`(`Id`/`Type`)、`:748`(成员比对)——FishingStage 闸后(基线外证据)。KV 无 Entity 合同,新类 `entityKey => null`(与今日正式 KV 一致,今日也从未赋值)。Duel 两处属 DP2+ 身份/业务合同批次;null 会在该 UI 触发 NRE,闸未开前无影响,但必须登记到最终门控清账清单(P3-2)。
|
||
- `UserId`:PF 语义为 PlayFabId;正式 KV 今日恒 `null`(`LoginKVFormalAsync` 不写、`LogoutFormalKV` :71 置空)。社交/缓存键(如 `DataCenter.SmallGame.cs:390,434,513`)全部闸后。KV 侧最终以哪个 ID(`101`/`102` 规范名还是服务端内部 ID)作社交键,属 DP2+ 身份合同,本批不得提前拍板。
|
||
- `ApiPlayerId`:`IsAuthorized ? apiSession.PlayerId : null`(`UserService.cs:115`),正式 KV 无 `apiSession` 恒 null,新类照实返回 null。
|
||
- `InternalId`:正式 KV 恒 0(不经过 `LoginApiServerCore`)。
|
||
- `IsLogin`/`IsAuthorized`:正式 KV 现状恒 false(`UserService.cs:110-112`),新类照实。**不得**在 `KVHandoffReady` 后翻转这两个值——那是首场景身份发布(CP2B 后续)的裁决,DP1 的"就绪"只通过 KV 专用状态面暴露(见 §6 R3)。
|
||
- `GetDefaultName`:`id[0..5]`(`UserService.cs:146`)对 `"101"`/`"102"` 会抛 `ArgumentOutOfRangeException`(长度 3 < 5)。调用点约 10 处全在闸后(`FriendService.cs:164`、`LeadboardData.cs:239`、各社交/伙伴 UI,基线外)。方案已如实记录;登记清账(P3-2),DP1 不改。
|
||
|
||
**临时门控与清账条件**:DP1 的门 = `LoginStage.RequestNextAct` 的 KV 早退(`LoginStage.cs:114-115`)+ 显式失败成员表;清账条件 = 线上身份/资料合同(DP2+)落地后逐成员改为真实语义或迁移到窄接口,届时重审。
|
||
|
||
## 5. 启动开关、冻结、AGG、URL、准入与生命周期安全性
|
||
|
||
**结论:通过。方案继承的时序在源码全部成立,可整体迁入新类。**
|
||
|
||
- 唯一开关与冻结时点:`SubBoostrap.RegisterServices` 在任何用户服务注册/登录之前捕获一次(`SubBoostrap.cs:37-42`);`LoginStage` 只读 `SubBoostrap.RuntimeSelection`(:35,114)。会话内不可换:`LoginKVFormalAsync` 的 `kvFormalAttempted` 单发 + `loginGate` 信号量 + generation/requestedId 对账(`UserService.cs:172-193`、`UserService.KVLoad.cs:85-119`)阻止二次登录/换号,符合用户已裁决的重启式重登。
|
||
- `SaveBackend` 再读取:登录时 `CaptureSaveBackend` 重读配置(`UserService.cs:173`)并经 `RejectSaveBackend` 与冻结选择对账(`UserService.KVLoad.cs:47-51`:formal 下非 `"kv"` 即 `Input` 错)。这是对同一事实的防御性复读,不是第二开关;**拆分后新 KV 类必须保留等价校验**(读到的后端串必须等于 `"kv"`),旧类保留 PF/kv-loop/受控分支。
|
||
- 编辑器直开:`InternalEditorBoost.Init` 仅允许 playfab,KV 选择直接抛异常(`SubBoostrap.cs:378-392`)。不因 DP1 改变。
|
||
- AGG:`Capture` 在 agg+kv 时抛(`PlayerKVRuntimeSelection.cs:29-30`),早于 `LoginStage` 的 `#if AGG` 注册(`LoginStage.cs:20-22`),"AGG-KV 启动失败、不静默回退"已由现状保证。
|
||
- 正式 URL:`Capture` 严格比对 `FormalApiUrl`(:31-32);`LoginKVFormalAsync` 再核 `server.ApiBaseUri` 与同 URL(`UserService.KVLoad.cs:92-95`)。
|
||
- 准入:`TryAdmitPlayer` 仅 101/102 且规范化(`PlayerKVRuntimeSelection.cs:36-43`);"有现存档"由加载三态语义保证(无档≠假空,此前批次已裁定)。
|
||
- KV 无 PF 活动实例:`SubBoostrap.cs:103` 不挂组件 + `LoginKVFormalAsync:94` 核 `HasLiveInstanceForKVLoad`。
|
||
- 生命周期可迁性:401/403 经 `server.KVUnauthorized` 事件 → 版本对账 → `LogoutFormalKV`(`UserService.KVLoad.cs:105-109`、`CustomServerMgr.cs:180-184`);`Application.quitting` 订阅(`UserService.cs:156`)在旧类删除 formal 分支后由新类构造器订阅;`Logout`/`QuitAct` 先同步失效再观察关闭(`LogoutFormalKV` 先 `DisableKVBusiness`/置空 handle/取消 LoginLifetime,再 `TrackFormalClose`,:67-75);Close 失败保留 `kvFormalClosing` 屏障(:76-81);在途旧响应与同账号新 epoch 由 `TryGetCurrentGameHandle` 的 epoch/引用对账(:30-39)+ `CustomServerMgr.SessionVersion`(`CustomServerMgr.cs:177`)拒绝。以上全部位于可整体搬迁的 formal 段,无旧类私有耦合。
|
||
|
||
## 6. 跨批依赖与隐藏必经路径
|
||
|
||
**结论:通过(附白名单修订 R2/R3)。**
|
||
|
||
- DP1 执行路径(SubBoostrap → LoginStage → PFLoginAct → Bind/Apply → 交接 → 停在登录页)上的 `IUserService` 实际调用面 = `Login`/`Logout`/`LoginLifetime`/`CustomId` 四个(`PFLoginAct.cs:38,88,96,111,168`);`SmallGameData` 构造只存引用(`DataCenter.SmallGame.cs:26-34`);`ClubService` 属性注入后在 Reset/Replace 中不触 `userService`(`ClubService.cs` M14 段无引用,社交请求全部 `IsAuthorized`/`ApiPlayerId` 守卫且闸后,:245-330,384-589)。
|
||
- DP1 执行路径上的 `PlayFabMgr.Instance` 直调 = **0**:组件不创建(`SubBoostrap.cs:103`),登录页 KV 分支不触 TapJoy/邮件/IAP(`PFLoginAct.cs:114` 提前 return)。全客户端 225 处 `PlayFabMgr.Instance`(83 文件)与 213 处 `IUserService` 文本引用全部位于 FishingStage 闸后或 PF/AGG 模式——本批无须迁移任何一个;打开闸门前(DP2+)必须成组处理,方案已如实声明。
|
||
- 本批必须迁移的最小调用者(与方案候选白名单一致):`LoginStage.cs`(注册分支)、`PFLoginAct.cs`(中立检测/状态/输入)、`QuitAct.cs`(退出观察)、`PlayerKVGameplayBindings.cs`(Bind 窄接口)、`UserService.cs`+`UserService.KVLoad.cs`(formal 段搬出与旧类分支清理)、新增 KV 类。
|
||
- `FishingStage` 闸保持:`LoginStage.RequestNextAct`(:114-115)不动。
|
||
- 不把客户端路由字面量或本机服务器代码当合同:本审查未读服务器目录;`mail/fetch` 等仅为 CP2B-T 的路径示例,不构成线上合同。
|
||
|
||
**R2(P2)— 测试迁移与不改文件的编译耦合核验义务**:`CodexOutput/upserver/KVGameLoadTests/ServiceHost/Program.cs` 与 `Stubs.cs` 直接 `new PlayFabUserService()`/`new AggUserService()` 驱动 formal 登录、401、quitting、双登等用例(:50,64,73,88,105,109,124,153,基线外证据)。formal 成员搬出后这批用例必须改指向新 KV 类;同时 `UserService.KVGameplayLoop.cs`、`AggUserService.cs`、`PlayerKVEditorValidationHost.cs` 不在白名单也确实无须改动,但白名单文件须**显式记录**"这三个文件依赖窄契约签名不变"的核验义务,防止实施时顺手扩面。方案文本需补这两点,否则"必要合成测试"不可审计。
|
||
|
||
**R3(P2)— 登录/退出阶段的中立面须落成具体机制**:方案只说"提供方中立的登录结果或窄 KV 会话能力"。具体缺陷三处:
|
||
1. `PFLoginAct.cs:88` `playFabUserService.CustomId = configCustomId` 与 :167-170 的下转——新类注册后 `playFabUserService` 为 null 会 NRE;应改 `userService.CustomId`(接口已有成员,`UserService.cs:70`),彻底去掉该下转。
|
||
2. formal 检测(:94,:316)应以 `SubBoostrap.RuntimeSelection?.IsKV==true` 为准,不得以"注入对象可下转到某具体类"为准。
|
||
3. `QuitAct.cs:40,61` 两个下转在新类下全部失效(validation 退出观察与 formal 退出观察分别落在两个类上)。建议一个 internal 窄接口(例如含 `KVHandoffReady`、`KVLastError`、`ObserveKVQuitAsync` 三成员),`PFLoginAct`/`QuitAct` 为其仅有的两个消费者,PF/受控语义由旧类实现、formal 语义由新类实现;或明确采用"新类具体下转 + 旧类下转并存",二选一须在方案定死,避免实施时各写各的。
|
||
|
||
## 7. AGENTS.md 简洁性核对
|
||
|
||
**结论:通过(附条件)。**
|
||
|
||
- 新增抽象仅两件,均有实际调用者:31 模块窄契约(消费者=`Bind` 一处,实现=旧类[验证/kv-loop]+新类[formal]);KV 状态/退出观察窄接口(消费者=`PFLoginAct`/`QuitAct`,实现=两类)。无第二套 Coordinator/Guard/存档管理器;方案明令禁止,源码确认无需。
|
||
- 开关单一:复用 `RuntimeSelection` 冻结捕获;kv-loop `Enabled` 是编辑器测试通道许可(permit 文件驱动),`CustomServerMgr.kvOnly/kvBusinessReady` 是运输层状态,各有不可省职责,DP1 不新增任何开关。
|
||
- 闲置路径清账已在方案 §DP2+ 与约束文件清单中登记;本报告补充:旧类拆分后残留的 formal 分支(ctor :156、`EvtAPISvrUnauthorized` :159、`IsLogin` :110、`LoginCore` :200、`Logout` :684 的 `IsFormalKV` 项)必须同批清干净,不得留死代码——这正是 `UserService.cs` 入白名单的原因,方案候选已含。
|
||
- 最小必测矩阵(建议写入验收):
|
||
1. PF 回归:同构建选 playfab → `Resolve<IUserService>()` 为 `PlayFabUserService`(AGG 配置为 `AggUserService`),现有 PF/kv-loop/受控验证套件原样通过;
|
||
2. KV 正式:选 kv → `Resolve<IUserService>()` 为新 KV 类 且 `PlayFabMgr.HasLiveInstanceForKVLoad==false` 且全进程零 `PlayFabUserService` 构造(以注册/类型断言,不以编译冒充);
|
||
3. 既有 formal 用例迁移后全绿:成功/无档/非 101/102/错误 URL/AGG-KV 拒绝、401/403、双登单发、`Logout`/`Application.quitting`、Close 失败屏障、旧响应/新 epoch、31 模块定序与回滚、`KVHandoffReady` 与 `EnableKVBusiness` 单次发布;
|
||
4. 显式失败成员逐个断言抛出(防静默空实现回潮);
|
||
5. Unity CLI Loop Editor 编译 0 错误;可取得的真实 Player 编译另记。
|
||
- 不能声称已通过的限制:线上业务路由/响应合同、`IsLogin`/`ApiPlayerId` 正式发布、SignalR 真连接、首场景链路、真实 Player 编译(工具受限时)。方案 §DP1 验收已如实声明,维持。
|
||
|
||
## 8. 发现清单(严重度汇总)
|
||
|
||
| 编号 | 严重度 | 内容 | 最小修订 |
|
||
|---|---|---|---|
|
||
| R1 | P2 | 方案未决点 1(IUserService 实现义务)应按 §2 证据落成确定裁决 + 成员三分表 | 修订方案文本,白名单裁决时引用本报告 §2 表 |
|
||
| R2 | P2 | 既有 formal 合成测试(ServiceHost Program/Stubs)迁移义务与三个"不改但受签名耦合"文件的核验义务未写入方案 | 补入 DP1 白名单说明与验收 |
|
||
| R3 | P2 | PFLoginAct/QuitAct 中立面机制未定死(CustomId NRE 风险 :88/:167-170;QuitAct 双下转失效 :40/:61) | 按 §6 R3 定死窄接口或双下转方案 |
|
||
| P3-1 | P3 | KV 类时间/大陆/语言只读成员维持默认值(与今日一致),`CreateTotalSeconds` 返回值无意义 | 登记最终清账;DP2+ 身份合同批次裁决 |
|
||
| P3-2 | P3 | `GetDefaultName` 短 ID 抛异常;Duel `entityKey` 两处读取在身份发布后 NRE | 登记清账清单,指定 DP2+ 批次 |
|
||
| P3-3 | P3 | 基线外观察:`S101LoginWindow.cs` 编辑器最小认证探针与 IUserService 无关,不构成双提供方影响 | 无须动作,记录在案 |
|
||
|
||
## 9. 可执行修订建议(供主会话直接裁决)
|
||
|
||
1. 方案按 R1 改写未决点 1 为确定裁决(§2 表原样落入方案)。
|
||
2. DP1 白名单定格:生产 = `LoginStage.cs`、`PFLoginAct.cs`、`QuitAct.cs`、`PlayerKVGameplayBindings.cs`、`UserService.cs`、`UserService.KVLoad.cs`、新增 `Services/PlayerKVUserService.cs`(命名可裁);测试 = `KVGameLoadTests/ServiceHost/Program.cs`、`Stubs.cs` 及既有 formal 用例迁移、必要新增断言。`SubBoostrap.cs` 现状无 DI/冻结缺口,不入白名单(若实施发现缺口须先补审)。
|
||
3. 明确禁止项写入白名单:新类禁 PlayFab using/具体类型引用;不碰 `UserService.KVGameplayLoop.cs`、`AggUserService.cs`、`PlayerKVEditorValidationHost.cs`、`DataCenter.SmallGame.cs`、`ClubService.cs`、场景/Prefab/服务器/付费/社交路由。
|
||
4. R3 的中立面机制二选一定死后才可编码;建议窄接口(消费者明确、两类实现语义分离)。
|
||
5. `PlayerKV*` 改名、`Core/KVMgr` 物理依赖、公开身份发布、GetDefaultName/时间字段语义——全部留在既有延期清单,本批不动。
|
||
|
||
**再次声明**:本报告落盘不等于批准编码。主会话须先重核 18/18 hash(本报告 §0 可复核),再逐项裁决并落精确白名单。
|