Files
ft/CodexOutput/upserver/B3BatchMigration/M2/M2-C3c剩余SDK接线独立差异审查-2026-09-30.md
Liubing\LB 6813da149a feat(upserver): 保存M2客户端KV迁移至本人统计写检查点
保存独立KV用户服务、固定会话存档与原业务接线,以及C3d资料缓存和C3e本人统计写。
保留原PF/AGG路线、业务公式和时序;榜单读取、好友资料、公告、匹配及首场景闸仍待完成。

验证:ServiceHost两条件各33组646断言;Unity CLI Loop编译0错误269警告。
包含相关方案、评审裁决、测试及状态文档。未操作服务器、正式账号或打包。
2026-09-30 19:19:05 +08:00

232 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.
# M2 C3c 剩余 SDK 接线独立差异审查
日期:2026-09-30。审查人:原 ZCode(独立侧),按[纯平移统一审查入口](M2-C3c纯平移统一审查入口-2026-09-30.md)执行。只读审查:未改生产/测试/服务器代码,未运行正式账号操作,未 Play/打包。执行用户 14:45 铁律:**凡会改变正常线上逻辑的修改一律不通过**。
## 0. 基线核验
- [M2-C3c纯平移统一审查基线.json](M2-C3c纯平移统一审查基线.json)(28 项)逐项重算:**28/28 长度与 SHA256 一致,零漂移**;清单 SHA256 `7ACC6338C044B8A6E503DEEC5256AE81589549C4E8872220EC63876B91E3A6EA`。其内含的原 C3c 受审 hash(31 项)经该清单间接核验一致(`previousC3cVerified: 31` 且本审查 28/28 复算覆盖其交集文件)。
- 本报告为**静态审查**:全部结论来自源码字符级核对与已归档证据(互加好友实测、服务器只读核查),未运行任何测试;凡断言"线上能力"处均以实测/只读证据为限,未证实写作未证实。
## R1 原业务基线核对:**通过,全部属实,另补两处入口未列的等价陷阱**
入口 §3 表逐行对源码核验,**13/13 属实**(首次上报 `FishingStage.cs:249` 不 await;`SetLv` 链 :381-396 顺序事件→OnLvChange→SavePlayerData→AwaitSetLvData;建筑 :664-668 deltaLvl>0 触发;`AwaitSetLvData` :211-218 isRefreshing 守卫+300 秒(`RefreshSeconds=300` :144);分数生成 :189-206 双 clamp;`SetLeadboardData` :158-169 一次请求双统计名;`UpdatePlayerStatisticsAsync` 错误 `PlayFabClientAsyncAPI.cs:2428-2431` LogError→SetResult(null);`GetLeadboardData` :171-186 StartPosition=0/50/三项资料约束;`SocialLeadboardItem.SetData` :91-99 Position/rank=Position+1/StatValue;`GetFriendData` :133-216 过滤→LevelB→SetPlayInfo→降序→position/rank→发布事件;`SocialFriends.cs:106-107` 除模解码;`EventPartnerInvitationPanel.cs:360-362` `LastLogin.Value` **无 HasValue 检查**直接读并按时间排序)。
**补充陷阱 A(重要,入口未列)**:`FriendService.cs:150,153`——`Profile==null` 或 `Statistics==null` 的好友**整条 continue 跳过**。若 KV 好友响应不含统计等价字段,好友列表会**静默变空**(连带伙伴邀请、好友 UI、AGG 计数)。任何好友映射必须为每项提供 LevelB 等价值或明确该跳过语义保持。
**补充陷阱 B**:实测 friends 每项为**序列化 JSON 字符串**(仅 PlayFabId/DisplayName/AvatarUrl/Level 四字段)。原消费链要求 `FriendPlayFabId`+`Profile.Statistics[LevelB].Value`+`Profile.LastLogin` 形状;新 DTO 映射属 `FriendModel.cs` 候选范围,但**字段缺失即不等价**(见 R3)。
另核:`KVUserService.cs:278` 的 `GetDefaultName` 已按裁决实现 `Math.Min(5, id.Length)`(短 ID 修复在位);`GetPlayerLv` 未命中返回 **-1**、`GetPlayLv` 抛 `InvalidOperationException`——与原 PF 版(`UserService.cs:836-839` 未命中默认 **1**;`GetPlayLv` 未命中走网络取 Lv,失败时 `PlayLvs[id]` 抛 `KeyNotFoundException`)**返回值与异常类型均有差异**,列入 D3 实施时对齐项。
## R2 KV 统计更新(LevelB 写路径):**③ 缺真实接口证据(不得先行编码)**
- 客户端侧无可疑义:触发点、计算、双统计名、async void、错误 log 后结束——全部可原样保留,`LeadboardData.cs` 在候选白名单内。
- **缺的是承接目标**:现客户端证据内不存在任何"按玩家更新统计值"的服务入口——`PlayerKVClient` 仅个人 playerdata 三操作;`account/UpdateProfile` 只带普通 Level 四字段(且其调用点是 `CampPanel.cs:1362` 的 UpdateProfileLv,与 SetLvData 触发链不同);被撤回的"本人 KV 暂存 LevelB 键"方案已因语义拆分被用户否决。服务器只读参考中的 `PlayerDataService.cs:48-57`(读 Statistics[LevelB])**整份注释**,仅历史旁证。
- 裁决:**禁止**在无服务端统计入口证据时把 `SetLeadboardData` 改写为任何客户端自选目标(个人 KV 键、UpdateProfile、暂存副本)——那正是已被撤回的方案变体。服务端最小问题见 §末尾问题 1。存储容器(统计键/派生)是实现细节,但**更新时点语义**(仅原触发点更新 vs 查询时派生)是行为差异,须随问题 1 一并由用户/服务端定。
## R3 榜单与好友读取:**② 有具体合同差异需裁决 + ③(聚合读取缺证据)**
- **等级榜读取**(全球/地区前 50):③。个人 KV 无法产生跨玩家榜;`event/GetLeaderboards` 是活动榜(不同功能);无聚合入口证据。对同分顺序、版本策略等后端行为**不凭空添加规则**(入口 §6.3 已列,维持)。
- **好友列表**:②。路由已实测存在(101/102 互加后 HTTP200、四字段非空项),但相对原消费链缺 **LevelB** 与 **LastLogin**:
- D1(等级):普通 Level=1 直接填 value → 好友 UI 解码为等级 0(`/100000`);只乘 100000 → 丢建筑等级与排序正确性。**反证成立:不可采用"Level 直接映射",也不可用 `Level*100000` 伪装**。
- D2(时间):填 null → 伙伴邀请 `friend.LastLogin.Value` NRE;填当前/MinValue → 改变排序与显示。**反证成立:不可用假时间或改消费者规避**。
- 需裁决的合同差异:服务端在好友单项投影 LevelB 的方式(存统计值 vs 响应时从 KV lv+建筑派生——两者更新时点语义不同)及 LastLogin 来源(问题 2)。**在裁决前 FriendService 不动**。
- 陷阱 A/B(R1 补充)并入本项验收:映射后必须验证"无 Statistics 跳过"语义下的非空好友列表、伙伴邀请真实消费者(`.Value` 直读)不炸。
## R4 LastLogin:**② 未裁决保持——两个候选来源均未获确认,禁止预选**
- 候选一:服务器 `DBPlayer.LastLoginTime`(每次认证写 UTC,参考源码 `AccountService.cs:28-65` 实证存在但 `PlayerInfo` 四字段不投影——线上实测吻合)。需服务端确认投影(问题 2b)。
- 候选二:客户端 KV `LastLoginTimeUtc`(首场景每日初始化后写)。**不等价于 PF Profile.LastLogin**:PF 模式玩家与未写入账号无此键(101/102 现均缺),且写入时点受每日刷新流程约束——不得为好友显示提前改写(C2d 已裁定该时序保持)。
- 结论:维持未裁决。缺值时沿用原链的"跳过/null"语义(陷阱 A),不填假时间、不改 UI 过滤。
## R5 原 C3c 其余项
| 项 | 分类 | 结论 |
|---|---|---|
| 他人资料 D3(`GetPlayInfo`/`GetPlayLv`) | **①(形状恢复)+②(远端源)** | 可立即实施:在 `KVUserService` 恢复 PF 原语义——同步返回同一占位对象、异步更新该对象并发布 `GetClubPlayInfoEvent`、`GetPlayerLv` 默认 1(对齐原版,含 -1/异常类型差异修正,登记前后对照)。**远端读取源仍是 ②**:`friend/SearchFriend` 为候选(路由/DTO 已有、新 ID 已验),但其线上语义(精确 ID 查询?Unknown 默认资料?)未经正式实测,C3b 响应为合成夹具;在实测确认前不得绑定为准。 |
| 公告(`TileNewsManager.cs:28` GetTitleNewsAsync) | **③** | 无任何候选入口。公告是全局内容,个人 KV 作用域不符;不猜私有键。服务端问题 3。客户端映射(保留 TitleNewsItem 结果与首项选择、NoticePopupPanel 零修改)待入口后实施。 |
| 匹配(Duel `EventFishingDuelPanel.cs:608-817` 四调用) | **③** | PlayFab Multiplayer 票据能力无 KV 等价候选。保留 PF 分支;**KV 路径当前 `MachingEnd(null)` 开发性结束不得作为产品行为放行**(面板在活动配置后,非首场景必经,不阻断当前批次)。服务端问题 4。 |
## R6 最小开发范围(当前可批准/不可批准清单)
**当前无新增编码许可维持。若主会话按下述范围裁决,可连续实施的仅有:**
1. `Services/KVUserService.cs`:`GetPlayInfo`/`GetPlayerLv`/`GetPlayLv` 的**原语义形状恢复**(占位对象引用、异步更新、事件发布、默认 1、异常形态对齐原版并逐项登记差异)——纯平移,无线上行为变化;远端源留 TODO 待 SearchFriend 实测。
2. 对应 ServiceHost 用例(占位/事件/默认值/本人命中路径)。
**明确不批准(在对应事实到位前)**:`LeadboardData` 任何写/读目标替换(R2/R3③)、`FriendService.GetFriendData` 映射(R3②)、`TileNewsManager`(R5③)、Duel KV 匹配路径(R5③)、`FriendModel.cs` DTO 新增(字段未裁决)。已撤回暂存方案的"五测试文件"不继承为许可(入口 §6,维持)。UI 消费者、HomePanel、场景、`Core/KVMgr`、底层协议均不在本增量边界。
**新增抽象检查**:本报告未批准任何新接口/管理器/Provider;问题 1-4 的服务端入口由服务端开发侧定义,客户端只做调用映射。
## 给用户与服务端的最小问题清单(本轮唯一需要外部输入的项)
1. **统计更新与聚合读取**:游戏服是否已有/可加"按玩家更新统计(LevelB 与 地区+LevelB 两名同值)"+"全球/地区前 50 读取(返回 StatValue、Position、玩家 ID、昵称、头像)"入口?存储用统计键还是查询时从 KV 派生(更新时点语义差异需用户定)?
2. **好友单项投影**:`friend/GetFriendsList` 单项能否增加 LevelB(来源同问题 1)与 LastLogin(推荐核 `DBPlayer.LastLoginTime` 投影 vs 客户端 `LastLoginTimeUtc`,由用户定)?缺值时字段省略还是 null?
3. **公告**:是否存在全局公告读取入口(Title/Body/Timestamp 列表)?
4. **匹配**:是否存在票据式匹配(创建/3 秒轮询查询/取消/结果含成员 ID 与属性)?若无,Duel 的 KV 匹配迁移需要该能力或产品延期裁决。
以上均无现成客户端证据可自答;不要求客户端读服务器源码,不猜测路由。
## 结论汇总
| 编号 | 裁决 |
|---|---|
| R1 | 通过;补陷阱 A(无 Statistics 好友整条跳过)与陷阱 B(JSON 字符串项形状)两处等价验收点 |
| R2 | ③ 缺统计入口证据,禁止客户端先行改写 |
| R3 | ②(好友缺 LevelB/LastLogin 合同差异)+ ③(榜聚合读取);反证否决"Level 直接映射"与假时间 |
| R4 | ② 未裁决保持;两候选来源待问题 2 |
| R5 | D3 形状恢复 ① 可实施(远端源 ②);公告 ③;匹配 ③ |
| R6 | 仅 KVUserService 三方法原语义恢复可批;其余文件零编码直至事实到位 |
本报告不构成编码许可;主会话核受审 hash 后逐条裁决。凡未列为准许的实现,按用户铁律一律视为不允许。
---
## 修正与状态补充(2026-09-30 16:20,原ZCode)
[主会话裁决](M2-C3c审查主会话裁决与资料缓存实施边界-2026-09-30.md)接受本报告 R1–R6 结论并纠正两处技术表述,[待确定事项](M2-C3c完整审核前待确定事项-2026-09-30.md)转达用户"全部审核通过后才可编码、未确定内容先与用户确定"的最新要求。原ZCode 独立复核确认如下:
**修正一(本人表述错误,如实更正)**:本报告 R3-D2 称缺时间会使伙伴邀请触发 "NRE"——**不准确**。`friend.LastLogin` 为 `DateTime?`,`EventPartnerInvitationPanel.cs:361` 的 `friend.LastLogin.Value` 在空值时抛 **`InvalidOperationException`**(Nullable 语义),非 NullReferenceException。该错误属对语言语义的未验证断言,与 P3a 同类,记录在案;不影响"不得伪造时间"的结论。
**修正二(歧义澄清)**:本报告 R4"缺值时沿用原链的跳过/null 语义"表述不精确。源码复核:原链跳过条件**仅为** `Profile==null`(:150)或 `Statistics==null`(:153);**缺 LastLogin 的好友仍进入列表**(`friendData.LastLogin=null` 赋值自 `Profile.LastLogin`),其在原链的下游表现就是伙伴邀请的上述 InvalidOperationException 异常路径。因此任何"缺时间就跳过好友"的做法都是**扩大过滤、改变原行为**,明确禁止;R4 结论维持"未裁决保持、不填假时间",且补充:缺时间的原表现是异常而非过滤。
**撤回状态复核(干净)**:主会话曾按本报告 R5/R6 批准 KVUserService 三方法局部实施,用户最新要求后已整体撤回该开工许可。原ZCode 独立核证:统一基线 **28/28 仍一致**(生产零改动)、`C3ProfileCacheTests.cs` **不存在**(测试未建)、无任何 C3c 局部编码痕迹。撤回完整、无残留。
**对裁决与待确定事项的复核意见**:
1. 六项逐条裁决与本报告结论一致,两处纠正均已采纳(见上);
2. 撤回局部开工符合用户"整体审核通过后才编码"要求,执行干净;
3. 待确定事项表中"好友登录时间来源"给出的方向建议(优先复用服务端每次登录维护的 DB 时间、保持真实登录更新时点)与原 PF `Profile.LastLogin`(最近登录时间)语义吻合,为合理候选——但按流程该方向由用户确定后并入 C3c 材料再审,本复核不代为裁决;
4. 维持:C3c 整体未通过、五类 SDK 残余零编码、服务端四问为唯一解锁路径。原报告其余结论(含服务端最小四问清单)不变。
---
## 第二轮修正(2026-09-30 16:25,原ZCode,依[既有裁决与接口证据复核](M2-C3c既有裁决与接口证据复核-2026-09-30.md))
**修正三(登录时间方向表述)**:前节补充第 3 点称 DB 时间方向"待用户确定后并入 C3c 再审"——**不准确的待选项化**。复核确认:用户此前已定阶段方向为"先存 KV、后联调"(《先存KV后联调的阶段移植方案》:5——先在 KV 保存对应数据、优先完成移植、联调时再定最终接口;LastLogin 复用 `LastLoginTimeUtc` 不另加同义键),跨玩家投影的 DB-vs-KV 终选属**联调时点决策**(同文件 :41),非当前待用户重选的开放题;主会话已撤回"DB 还是 KV 请重选"的提问。本审查报告 R4 维持"未裁决保持",但准确表述应为:**按既有 KV 阶段方向推进,缺的是跨玩家字段投影的事实证据,不是方向再决策**。
**修正四(接口证据分级精确化)**:原报告及服务端四问的"缺证据"表述需按两级证据校正(依复核文件 §二,原ZCode 已对引用文件核实):
- **正式实测级**(101/102、HTTP200):`account/Login`、`playerdata/Get`、`friend/AddFriend`、`friend/AcceptFriend`、`friend/GetFriendsList`(路由+外层+四字段非空项格式)——这些**不是**缺证据项,服务端四问不再涉及它们是否存在;
- **离线兼容级**(C3b 双条件 21 组 380 断言、formalRequests=0、内存 HTTP):`SearchFriend`/`DeleteFriend`/`GetHomePage`/`GetRecommendedFriends`/`IsFriendWaitingForAccept`/`GetInvitedList` 等——已验证客户端新 UserID 兼容,**不声称经正式服务器请求**;
- **真正缺证据的仅剩**:跨玩家好友项的 LevelB/LastLogin 字段投影语义(含源 KV 缺键时的返回差异)、全球/地区统计更新与前 50 聚合读取、公告、匹配四类 KV 替代接口。
服务端四问清单相应收窄为上述四类**字段/接口事实**,不再包含任何已实测路由的存在性问题。本修正不改变任何裁决级别(②/③ 分类维持),不构成整批 PASS;"服务器接口未改变"为用户提供的事实,据此沿用既有样例,不推断接口已新增字段。
---
## 第三轮:KV 读取隔离最终裁定与相关逻辑重设计意见(2026-09-30 16:45,原ZCode,依用户明确指示)
**用户最终裁定**:`playerdata/Get` **限制为只能读取本人 KV,不可以读取他人**;与此相关的设计需重新审核并重设计。
### 一、事实锁定与撤回
1. **隔离事实锁定**(用户确认的服务端合同):`playerdata/Get/Set/Delete` 仅作用于认证账号本人数据。本报告第二轮修正后曾按"服务端可按对方 ID 读取"暂列好友/他人资料两项为"可通过逐个读取好友 KV 接线"——**该解锁作废**,相关接线方向禁止采用。
2. 源码侧印证(不变):`PlayerKVClient.GetAsync` 请求体仅 `{keys:[...]}` 无目标 ID 字段——客户端实现与服务端限制一致,**不得为跨玩家读取扩展该请求**。
### 二、重设计意见(结合用户全部既有裁决)
正确且唯一符合"入口不变、返回逻辑不变、值不变、存储迁 KV"的路径是**服务端侧投影**:跨玩家数据由服务端在内部读取 KV 后,经既有路由按原 PF 逻辑形状返回。客户端不新增任何跨玩家读取逻辑。
| 能力 | 正确设计方向 | 客户端改动 |
|---|---|---|
| 好友 LevelB/LastLogin | 服务端在 `friend/GetFriendsList`(及 SearchFriend 等资料路由)内部从各好友 KV 读取 LevelB/LastLoginTimeUtc 并按原 PF 字段语义(Statistics[LevelB]、Profile.LastLogin 等价形状)返回 | 仅把 `GetFriendsListAsync` 替换为该路由调用+DTO 映射;过滤/排序/发布顺序原样 |
| 他人公开资料 | 同上——经资料类路由返回(SearchFriend 已实测四字段,缺等级/时间等价字段的服务端投影) | `GetPlayInfo/GetPlayLv` 远端链接到该路由;原占位/异步更新/事件/默认值语义恢复 |
| 排行榜前 50 | 服务端聚合入口(客户端无法枚举全服 ID,本人 KV 读取不适用) | `GetLeaderboardAsync` 替换+结果映射 |
| 统计更新(写 LevelB) | 客户端写**本人** KV(本人写不受隔离限制),服务端聚合读取侧从 KV 取值 | `UpdatePlayerStatisticsAsync` → 本人 KV 写,原触发点/计算/时序不变 |
| 公告、匹配 | 服务端全局内容/协调入口 | 对应替换+原消费逻辑 |
### 三、需要重做/明确的设计材料
1. C3c 材料中凡涉及"按对方 ID 读 KV""客户端逐好友读取"的表述(含本报告第二轮解锁段)**全部按本意见推翻**;
2. 开发侧需按上表重写好友/资料接线方案:投影字段形状(服务端返回的 LevelB/LastLogin 等价字段名与缺键行为)与原 PF 消费链逐字段对齐后送完整裁决;
3. 服务端待确认项维持四类:好友/资料路由的字段投影、前 50 聚合、公告、匹配——均为"服务端内部读 KV 后按原逻辑返回",不要求客户端可见的跨玩家 KV 读取。
### 四、禁止项(重申并扩展)
- 禁止扩展 `PlayerKVClient` 增加目标 ID 参数或任何跨玩家 KV 读取;
- 禁止客户端拼榜、重算 LevelB、伪造 LastLogin/默认资料、扩大好友过滤;
- 禁止以"隔离限制"为由把好友/榜单功能降级或停用——缺口按第三节走服务端投影与聚合。
本节为用户指示下的正式审核意见,并入 C3c 待裁材料;整体"审核通过后才编码"门槛不变。
---
## 第四轮:对《隔离边界修订与复审确认》的审核确认(2026-09-30 16:50,原ZCode)
按修订文件第五节要求,在原报告明确三项结论如下。
### 一、§1–§3 准确性:**确认准确**
- §1 与源码一致(`PlayerKVClient.GetAsync` 仅 keys/token,本审查第一轮已核);[跨账号实测 JSON](M2-C3c-KV跨账号读取实测-2026-09-30.json) 结构与结论吻合——带目标参数的 8 次 Get 均 HTTP200 且**只返回本人数据**(服务端忽略未契约的目标参数),为"仅限本人"裁定提供了网络实证;探测参数仅存在于用户授权的只读脚本、未入生产,属实。
- §2 六项确认与第三轮意见一一对应,无偏差。其澄清正确并**予以采纳为审查口径**:"不新增跨玩家读取逻辑"指不新增**直接跨账号 KV 读取**;`friend/SearchFriend` 等既有业务路由的原目标 ID 参数是业务合同组成部分,**必须保留,不得误删**。
- §3 逐字段映射表与本报告 R1 核证的原消费链一致(跳过语义、LastLogin 缺失的原异常表现、排序/名次/事件顺序、每日初始化时序),无新增或遗漏。
### 二、§4 四项的最终合同与文件边界
1. **本人统计写**:客户端侧形状可预定——`DataCenter.LeadboardData.cs` 单文件:原两统计名映射为本人 KV 两键(同值)、原触发点/计算/async void/错误 log 后复位语义经既有 `IPlayerSaveStore` 保持,不引入额外等待/重试/无关刷新。**但最终批准与第 3 项耦合**:键名/序列化必须与服务端聚合读取侧一致,须与服务端一次对齐后共同定稿——未对齐前不编码。
2. **好友与公开资料字段投影**:合同=既有路由按原 PF 字段语义返回(§3 表)。**审核端无法单方面定稿**——字段名/缺键/失败语义是服务端事实,缺此无法写 DTO。允许文件维持候选清单(FriendService/FriendModel/KVUserService 相关方法),零编码直至事实到位。
3. **等级榜读取**:全球/地区前 50 聚合入口及结果映射纯为服务端事实,待指认;与第 1 项键对齐后一并成文。
4. **公告/匹配**:同上,待入口指认;原消费者/轮询时序在客户端侧已锁定不变。
### 三、整体是否可编码:**未达到**
方向、隔离边界、既有路由事实均已闭合;剩余为**四个具体服务端事实**,无一可由客户端或审核端猜测:
①好友/资料路由的 LevelB、LastLogin、真实普通等级字段名与缺键/失败语义;②等级榜前 50 读取入口+与本人统计写的键对齐;③公告入口;④匹配入口。
四项事实到位 → 并入 C3c 材料 → 本审查给完整裁决与文件/方法白名单 → 统一编码。此前零编码、双闸保持。修订文件所列旧叙述("SearchFriend 仅离线""DB/KV 时间重选""客户端加目标 ID 读 KV")确认不再作为执行依据。
---
## 第五轮:四项服务端事实挂起后的可编码范围裁决(2026-09-30 16:58,原ZCode,依用户指示)
用户裁决:四项服务端事实(好友/资料字段投影、前50聚合+键对齐、公告、匹配)**放后面等服务器实现**;审核其余内容是否可编码。逐项核对后裁决如下。
### 可编码范围(批准,唯一生产文件 `Services/KVUserService.cs`)
| 方法 | 批准内容 | 依据 |
|---|---|---|
| `GetPlayerLv` | 对齐原 PF 缓存合同:命中返回原值(含 0/负数,不归一化),未命中返回 **1**(当前 -1 移除);`SetPlayerLv` 不改 | 纯平移;原版 `UserService.cs:836-839` 语义 |
| `GetPlayInfo` | 恢复原三段语义:空 ID 返回 null;缓存命中返回**同一对象引用**;未命中创建默认昵称占位并缓存,本人分支用已认证资料填同一对象并**同步发布一次** `GetClubPlayInfoEvent`(后续命中不重复发布);他人远端异步更新**绑定 `friend/SearchFriend`**(已正式实测:存在 ID 四字段、目标 ID 原样返回)——`code>0 且 info!=null` 时更新占位对象并发布事件,否则静默保留占位(= 原版 PF 失败路径"无事件") | 形状=原版逐字符对齐;远端源字段(昵称/头像)已实测;失败语义按用户裁决归服务端域,客户端按既有 `Code>0` 契约消费 |
| `SetPlayInfo(string,string,string)` | 原版替换缓存对象语义,不发布事件、不改已有引用 | 原版 :902-912 |
| `ClearPlayInfos` | 原版清空 | 原版 |
| `GetPlayLv` | **维持现状不动**(未命中显式 `InvalidOperationException`)——不预改 `KeyNotFoundException` 假装做过网络查询;真实远端等级读取随事实①迁移(SearchFriend level=1 与 KV 62/60 的数据源差异未定论) | 沿用第四轮限定 |
**测试**(`CodexOutput/upserver/KVGameLoadTests/ServiceHost/`):`M2MergedGameplayTests` 接入已认证 101/102 合成场景+新增 `C3ProfileCacheTests`(真实 KV/PF 服务对照);红测先行证明当前默认值/对象引用/事件差异;覆盖:默认 1 不污染异步查询、缓存含 0/负数、null/空 ID、本人引用与事件恰好一次、SetPlayInfo 替换规则、ClearPlayInfos、SearchFriend 成功/失败两态占位行为、KV 路径 SDK 计数 0、PF 对照不变。ServiceHost 双条件+CLI Loop 编译后记检查点。
### 明确不批准(挂起等四项事实)
`LeadboardData`(统计写/榜读)、`FriendService.GetFriendData`/`FriendModel` DTO、`TileNewsManager`、Duel 匹配路径、任何 LevelB/LastLogin 相关代码、`PlayerKVClient` 任何改动。首场景双闸维持(PF 调用计数≠0 的来源正是挂起项)。四项事实到位后并入 C3c 补充裁决,不在本批顺带处理。
### 边界重申
不新增跨账号 KV 读取;业务路由目标 ID 参数保留;`runtimeVerified` 在真实运行前保持 false;红测首败留痕;PF/AGG 路线零改动。本裁决为**范围性编码许可**:仅上表内容,批内常规编译/测试失败自修不送审,文件/行为越界即停。
---
## 第六轮:C3d 交付确认与 R2-S 本人统计写独立实施裁决(2026-09-30 19:10,原ZCode)
### 一、C3d 交付确认:通过
[C3d 交付清单](M2-C3d交付与验证hash.json) 13/13 独立复算一致(另 2118 项基线未动);代码现状与第五轮批准逐点吻合(`GetPlayerLv` 缺省 1、`GetPlayInfo` 占位/同引用/一次事件+SearchFriend 绑定、`GetDefaultName` min(5,长度)、`GetPlayLv` 未接远端保持显式失败)。双条件 24 组 491 断言、编译 0 错误、各首败留痕——按批内规则关闭,不需重审。
### 二、R2-S 裁决:附条件允许本人统计写独立实施(替代第四/五轮对该写入侧的挂起;读取/聚合侧挂起不变)
依据:用户已裁"LevelB 值不变、存储迁 KV";写入本人 KV 不受隔离限制;服务端聚合尚未有任何既有设计可与客户端键冲突——客户端先行固定键合同是降低而非增加总对齐成本。**本裁决不复活已撤回的"暂存快照+通用 Flush"方案**:该候选在原触发点经正常 store 写统计值本身,无旁路快照、无新刷新管线。
| 条件 | 内容 |
|---|---|
| C1 键/序列化合同 | 原统计名 `LevelB` 与 `ContinentCode+LevelB` 直接作为本人 KV 键;值=同一复合整数的 `InvariantCulture` 十进制字符串。此合同冻结为客户端事实并写入集中问题单,服务端聚合侧照此对齐;日后若服务端另定键名,属一次性对齐修改而非行为变更 |
| C2 地区缺失 | `ContinentCode` 缺失/未知时**仅写全球键**——这是原 PF 请求中地区名退化为全球名(`null+"LevelB"=="LevelB"`,双同名统计净效果=仅全球统计)的忠实翻译;不新增地区默认值、不两键同写。KV 档普遍缺大陆码与 PF 档案普遍有值属已登记的数据覆盖差异 |
| C3 失败映射 | store 拒写/非 Confirmed/传输异常一律走**原错误日志路径后任务正常结束**——原 `UpdatePlayerStatisticsAsync` 从不抛出(错误→log→SetResult(null)),KV 分支同样不得让异常逃出 async void(= Unity 崩溃,行为变更)。仅捕获 store 文档化的失败形态映射为日志,不加宽泛 catch-all、不加重试/回退 |
| C4 等待范围 | ≥1 键被接受时 await 一次现有 `FlushAsync`;全部拒写则跳过并按 C3 记日志。`FlushAsync` 刷新全部 pending(含无关键)是既有唯一原语、不新建选择性刷新——登记为已知差异(无关键可能提前随行同步、完成时点≠原单请求),不构成行为变更 |
| C5 原子性 | 两键可部分接受/确认、同键重写 Superseded——逐键结果记日志,不声称同时成功,不加回滚/补偿/锁。原 PF 单请求的后端原子性本就不可由客户端源码证明,不推定 |
| C6 不变项 | `SetLvData`/`AwaitSetLvData` 的公式、clamp、lv 回写、300 秒延迟、async void、isRefreshing 复位、原触发点全部不动;`SetLeadboardData` 仅在收到的 names/values 列表上做 KV 分支(含 C2 去重),`SetLvData` 零改动;PF/AGG 分支不变 |
| C7 完成声明 | 统计写完成≠等级榜功能完成;聚合读取、榜读取、双闸挂起不变。PF 后端统计聚合模式(Last/Max/Sum)系历史配置事实,客户端无法证明——作为一行服务端确认项登记(推荐按 Last 语义核),非本批阻塞 |
**文件边界**:生产仅 `DataCenter/DataCenter.LeadboardData.cs`(`SetLeadboardData` KV 分支+必要 using)。测试按候选:ServiceHost 链接真实 LeadboardData、`C3IdentityCompatibilityTests` 移除业务替身、Stubs 最小签名+PF 统计请求记录、新增 `C3StatisticsWriteTests.cs`;既有 M2Merged/C3ProfileCache 断言不因本批改动。红测先行;覆盖清单沿申请方 §5.3(原公式/上限/坏值/负值、双统计名、拒写/部分接受/各确认态、延迟确认中换号注销、无关键失败随行、真实 store 重读、PF/AGG 对照、formalRequests=0)。
### 三、核查文档其余事项的立场
§2 剩余调用清单与 §3 临时冻结边界(F1/F1补充/F5/F6/F7/F8/F9)与本审查各轮记录一致,无异议。特别确认三点:**F1补充**(PFLoginAct 销毁时 Logout 须与双闸同批重审正常转场/异常退出区别——盲删两处 return 会在正常转场撤销会话,同意挂起);**F7**(EmptyMailService/BaseEventData 删除须先证迁移因果与全引用,不凭名称或"无调用者"简表删除,同意);移动端易盾/TapJoy、PlayerKV 改名维持既有登记。§5.4 回写要求按本节履行完毕。