Files
ft/CodexOutput/upserver/B3BatchMigration/M2/M2-四项PlayFab能力迁移-服务端开发对接文档-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

26 KiB
Raw Blame History

四项PlayFab能力迁移:服务端开发对接文档

日期:2026-09-30。用途:交给当前线上服务器开发者,实施等级榜、好友及公开资料、公告、匹配的KV/自有服务器替代能力。

目标是平移现有功能,保留客户端业务规则。 LevelB不重新设计,原客户端入口、计算、更新时机、显示、异常和匹配流程保持;替换PlayFab提供方及身份。本文请求/响应示例是拟议合同,不表示线上已部署。已有等价接口优先复用;路由或字段已有规范时,服务端给出映射即可,不必另建一套接口。好友现有路由的兼容字段不得随意变更。

本文件汇总当前交付要求,可以独立交给服务端阅读;附录旧文档仅供追溯。客户端Agent未修改服务器代码;客户端新增接线仍待C3c完整审核通过。服务器内部存储、索引和队列实现由服务器开发者负责,不要求引用PlayFab SDK。

此前清单中的“公告/匹配尚未拟定替代合同”更新为本文已有提案;对接时以本文为当前完整输入,不把历史待办当作尚未完成的文档工作。新提案与实际线上部署仍分别核对。

1. 总览:原调用与拟替代方式

能力 原PlayFab调用 原客户端位置(相对Client/Assets/Scripts) 拟替代方式
等级榜统计写入 PlayFabClientAsyncAPI.UpdatePlayerStatisticsAsync DataCenter/DataCenter.LeadboardData.cs:158 本人原统计名/值经KV管理器存本人KV;属于既定存储接线,不新增业务方案
全球/地区等级榜读取 PlayFabClientAsyncAPI.GetLeaderboardAsync 同文件:171 服务器内部读取对应KV统计并返回原榜单结果;缺等价入口时拟POST leaderboard/GetLeaderboard
好友列表及其资料 PlayFabClientAsyncAPI.GetFriendsListAsync Services/FriendService.cs:132 复用POST friend/GetFriendsList,一次返回好友关系和KV公开资料
他人昵称/头像、普通等级 GetPlayerProfileAsync;GetUserDataAsync(Keys=["Lv"], PlayFabId=目标ID) Services/UserService.cs:810、849、874 优先扩展原POST friend/SearchFriend公开投影,由KVUserService消费;不直接跨账号读私有KV
游戏公告 PlayFabClientAsyncAPI.GetTitleNewsAsync Services/TileNewsManager.cs:24 服务器提供游戏级公告读取;缺等价入口时拟POST commondata/GetTitleNews
多人匹配 PlayFabMultiplayerAPI.CreateMatchmakingTicket / GetMatchmakingTicket / CancelMatchmakingTicket / GetMatch Duel/UI/EventFishingDuelPanel.cs:608–825 服务器承接原四步票据流程;拟matchmaking下四个同名动作,规则和客户端时序保持

四类功能不是都能用“读本人KV”完成:个人统计保存在KV;跨玩家资料/榜单由服务器内部取KV后返回;公告是游戏级内容;匹配由服务器协调玩家。不能用普通键值读写冒充匹配,也不要求客户端取得他人完整存档。

2. 通用合同与已知事实

  • 固定基址:http://s101.local:18080/。HTTP示例路径相对于此基址;JSON请求使用Content-Type: application/json,沿现有登录Cookie认证。
  • 玩家唯一身份使用登录返回的PlayerId,即UserID,按字符串传输,不当整数解析。旧playFabId/PlayFabId字段名暂时兼容保留,值传新UserID;字段统一改名另行处理。
  • 本人playerdata/Get/Set/Delete由认证会话确定账号,不能带目标ID读取他人。跨玩家查询由服务端内部按业务授权读取,仅返回下文公开字段。
  • 新拟接口成功使用HTTP200、code=1、msg="";失败code=0并给非敏感错误类别。401/403沿现有认证处理;参数错误建议400,服务/存储异常建议500。既有friend接口的业务码保持,不强制改为新统一码。
  • 缺键、确认不存在、请求失败必须可区分;读取失败不能包装为成功空列表或默认数据。时间采用UTC ISO 8601往返格式,整数与JSON字符串类型按示例保留。
  • 新接口通过现有CustomServerMgr的同会话传输接线;原PF模式仍使用原SDK,KV模式不能再借PF读取缺少的数据。

当前证据:正式101/102账号已互加好友,friend/GetFriendsList与SearchFriend成功且返回新ID。好友项原格式为JSON字符串数组,字段PlayFabId/DisplayName/AvatarUrl/Level;搜索info为对象,字段playFabId/displayName/avatarUrl/level。当时level均1,本人KV现代档等级为62/60;未执行UpdateProfile,不能推断服务器内部原因。样例未返回LevelB/登录时间,本人KV当时也未取得这两个键。此为历史测试结果,不代表当前部署或全服数据状态。

3. 等级榜:沿用原数据,只替换提供方

3.1 原调用与写入边界

SetLvData仍由客户端在原时点计算:lv * 100000 + InfiniteBuildingLevelInt,保留原TryParse、建筑上限99999、地图等级上限及300秒延迟更新路径。原来一次UpdatePlayerStatistics请求包含:

{"Statistics":[{"StatisticName":"LevelB","Value":1200034},{"StatisticName":"ASLevelB","Value":1200034}]}

等级12、建筑34对应1200034。地区统计名为ContinentCode+LevelB;有效地区AF/AN/AS/EU/NA/OC/SA,地区缺失沿已审边界,不虚构地区或把全球榜当地区榜。

迁移后的本人KV线格式沿现有playerdata/Set:

{"data":{"LevelB":"1200034","ASLevelB":"1200034"}}

这只是原统计名称/值的存储适配:两个键同值,十进制Int32字符串。客户端仍经KV管理器/原存档句柄,不另建HTTP存档路径、不刷新无关待写数据。不能把它解释为重新设计LevelB,也不能让服务器查询时按当前lv/建筑重新算分。既有KV数据直接使用;PF旧统计是否已迁入KV需核实,不能把样例缺键当成允许初始化全服数据或删除旧数据。

3.2 原读取方式

GetLeadboardData构造GetLeaderboardRequest:StatisticName为全球或地区名,StartPosition=0,MaxResultsCount默认50,ProfileConstraints要求头像、昵称、Locations。返回GetLeaderboardResult.Leaderboard。

消费者使用PlayFabId、Position、StatValue、Profile.DisplayName/AvatarUrl/LastLogin。Position从0起,UI排名Position+1;分数除以100000/取模显示等级与建筑。当前该消费链不读Locations。空榜时原客户端不刷新缓存/不发事件,保持原条件。

3.3 拟读取合同

优先使用已有KV等级榜等价接口;若无,采用POST /leaderboard/GetLeaderboard:

{"statisticName":"LevelB","startPosition":0,"maxResultsCount":50}

statisticName仅允许LevelB及上述地区统计名,起始位置非负,拟数量范围1–100(当前调用50)。地区榜只换statisticName,不另增地区判断。无有效统计名/范围应失败,不静默改为全球榜。

{
  "code":1,"msg":"",
  "leaderboard":[
    {"playFabId":"102","position":0,"statValue":1200034,
     "profile":{"displayName":"Angler#102","avatarUrl":"","lastLogin":"2026-09-30T01:00:00.0000000Z"}}
  ]
}
字段 要求
playFabId 新UserID,非旧PF ID
position 全榜零基位置;分页不能每页归零
statValue 对应已存KV统计的Int32值,不能填普通等级
profile 必须是对象;昵称/头像来自该玩家KV公开资料,缺键可null
profile.lastLogin 来自LastLoginTimeUtc,缺键null;不因为缺时间过滤榜单项

成功无记录/起点超过榜尾返回{"code":1,"msg":"","leaderboard":[]}。不存在所选统计键的玩家不生成该榜条目;有键但格式损坏、聚合读取失败不伪装空榜,建议HTTP500、code=0、leaderboard=null、msg=KV_STAT_DATA_INVALID或KV_LEADERBOARD_READ_FAILED。

服务器读取已存统计,按原榜单规则返回;旧PF后台同分次序、统计更新聚合方式和重置配置不在客户端源码中,服务端需复用已有规则或对照原配置,不能自行改成按ID打破同分、每次重算、取最大值或新增周期清零。本要求是等价性核验,不是重新设计统计业务。已有event/GetLeaderboards属于活动榜,不能直接代替此榜。

4. 好友及他人公开资料:扩展原业务接口

4.1 原好友调用

GetFriendsListAsync请求ProfileConstraints:ShowAvatarUrl/ShowDisplayName/ShowStatistics/ShowLastLogin=true。消费者从Profile.Statistics找Name=LevelB,读取Profile.LastLogin;本人排除、默认名、排序和事件由原客户端完成。

原过滤只有Profile==null或Statistics==null;Statistics为空数组时好友保留、分数为0。缺LastLogin不自动过滤好友,下游原Nullable.Value路径有异常风险,不能通过假时间或额外过滤声称迁移通过。

4.2 拟复用POST /friend/GetFriendsList

请求仍{},服务器取认证本人原好友关系,一次批量投影好友KV公开资料。不修改增删好友流程,不增加客户端逐个取档请求。

外层code/msg/friends和friends中JSON字符串项保持;原四字段保持兼容含义,新增Profile供替代原PF消费者:

{
  "code":1,"msg":"",
  "friends":[
    "{\"PlayFabId\":\"102\",\"DisplayName\":\"Angler#102\",\"AvatarUrl\":\"\",\"Level\":12,\"Profile\":{\"DisplayName\":\"Angler#102\",\"AvatarUrl\":\"\",\"Statistics\":[{\"Name\":\"LevelB\",\"Value\":1200034}],\"LastLogin\":\"2026-09-30T01:00:00.0000000Z\"}}"
  ]
}
新增公开字段 来源/缺值规则
Profile.DisplayName / AvatarUrl 对方KV同名键;缺键null,原客户端默认名处理保留
Profile.Statistics 投影对方LevelB;Value为Int32,缺LevelB键为[],不以普通Level替代
Profile.LastLogin 对方LastLoginTimeUtc;UTC字符串,缺键null
Profile 资料实体确认不存在才为null;关系项保留。读取失败不能当不存在

Profile与其Statistics字段必须出现;明确不存在统计集合可Statistics=null,对应原跳过语义,不能与正常KV读取后缺LevelB的[]混淆。无好友返回friends=[]。部分好友KV读取失败、存在但损坏的统计/时间建议整次HTTP500/code=0/friends=null,避免假空列表或部分成功掩盖数据问题。原业务失败码保持;旧服务器未提供Profile是合同未部署,不能当全部好友资料为空。

LastLoginTimeUtc当前由本人每日初始化后沿已有流程写入,不等于所有认证成功时刻;本阶段按用户既定“先存KV”方向读取该值,联调对照真实时点,不擅自改本人跨日流程。外层Level仍为普通等级,不改塞复合分数。

4.3 原他人资料调用与拟复用SearchFriend

  • GetPlayInfo原先同步返回缓存中的同一占位对象,再异步GetPlayerProfileAsync取得昵称/头像,更新对象并发布GetClubPlayInfoEvent。
  • GetPlayLv原先GetUserDataAsync指定目标ID和Keys=["Lv"]。成功但缺Lv键时原默认1;错误路径和原缓存流程不在本次修正。
  • 这两个客户端入口保留,在KVUserService中替换数据源。SearchFriend已存在且新UserID可用,不另造公开资料查询系统。

沿现有POST /friend/SearchFriend及原请求字段:

{"PlayFabId":"102"}

拟最小兼容扩展:保留code/msg/info与info原字段;在info增加kvProfile,明确其是目标KV的公开投影,以免把旧缓存的level=1误当完整迁移成功。已有服务能保证info本身就是同义KV公开投影时,可省去新增kvProfile,由服务端在交付中明确源数据和缺值映射;两者择一形成最终合同,客户端不运行时猜测两套来源。

{
  "code":1,"msg":"",
  "info":{
    "playFabId":"102","displayName":"Angler#102","avatarUrl":"","level":12,
    "kvProfile":{"displayName":"Angler#102","avatarUrl":"","level":12}
  }
}

kvProfile昵称/头像来源同好友投影;level是普通等级,按现有KV存档布局和加载优先级取得,不能使用LevelB替代。当前现代档为NewPlayerDataDic.newData第0槽(19槽格式),旧格式按已存在加载规则处理;不得仅查独立Lv键就忽略已有现代档,也不为此新增等级真值副本。数据格式损坏应失败,不能静默返回1。

建议缺值合同:目标账号/公开资料实体确认不存在时kvProfile=null;资料存在但确无等级数据时level=null,由客户端沿原“成功缺Lv键默认1”映射;昵称/头像缺键null。已存在等级返回整数。旧搜索行为若返回Unknown占位,保留原info行为,新增kvProfile明确null,不能让占位冒充实际资料。现有账号的成功样例不能证明不存在/失败语义,服务端需回传这两类样例完成对照。HTTP/业务失败保留失败,不转为成功null。

5. 公告读取:原游戏级内容迁到服务器

5.1 原调用方式和依据

GetTitleNewsAsync(new GetTitleNewsRequest())为空请求;PlayFabSettings.TitleId由UserService.cs:339配置,表示游戏作用域。没有按玩家ID、等级、地区筛选。项目内SDK模型说明Count未设置默认10;原客户端未显式设置Count。

原返回News项包含NewsId、Title、Body、Timestamp。客户端只取News[0],界面显示标题、正文、日期;用Timestamp与本地PlayerPrefs已读标记及账号CreateTime比较决定红点。客户端没有对返回列表重新排序。

5.2 拟POST /commondata/GetTitleNews

如已有游戏级公告接口则复用。拟新入口沿现有同会话CommonDataRequest,无筛选参数:

{}
{
  "code":1,"msg":"",
  "news":[
    {"newsId":"notice-20260930-01","title":"公告示例","body":"此处为公告正文。","timestamp":"2026-09-30T01:00:00.0000000Z"}
  ]
}
  • 服务器从当前游戏/环境的全局公告数据源返回已发布公告,不能按账号返回不同私有存档;无需客户端再传PF TitleId。多游戏部署由服务端沿既有可信游戏上下文区分,不能将全局作用域意外扩大到其他游戏。
  • 内容可放在已有服务器公告存储,或服务端专用全局KV集合;不强制新建库/后台管理系统,不把公告写入每个玩家KV。发布入口复用现有运维方式,玩家只读接口无发布权限。
  • 原NewsId、标题、正文、发布时间和发布顺序整体迁移,不能为了迁移把旧公告时间改成“现在”,否则会改变红点和已读状态。
  • 拟返回最近10条已发布公告,与原默认数量对应;列表第一项须与原PF可见首项一致。通常按发布时间倒序,若原发布系统有同时间排序/优先级,则沿原顺序,不自行另定同时间ID排序。排序在服务端完成,客户端仍直接取第一条。
  • newsId/title/body为字符串,timestamp为有效UTC时间;空标题/正文若原内容允许则原样保留,不加内容修订。缺少必要字段或损坏时间是数据错误,不能伪造发布时间。
  • 无公告:HTTP200/code=1/news=[]。获取失败:建议HTTP500/code=0/news=null/msg=TITLE_NEWS_READ_FAILED。客户端按原PF失败null路径处理News和TaskCompletionSource,不在本轮修复原异步UI行为。

服务端交付要点:已有公告数据是否已迁移、实际作用域、原首条一致样例、UTC时间保留情况。具体线上公告正文未在本次读取,文中是合成示例,不能作为正式内容发布。

6. 匹配:服务器承接原四步,不重做对战流程

6.1 原调用与输入

队列名固定DuelPVP(FishingDuelManager_Network.cs:18),GiveUpAfterSeconds取eventSoloConfig.MaxMatchTime,不在替代接口硬编码新时长。原CreateMatchmakingTicketRequest.Creator.Entity是PF EntityKey;迁移后服务器从认证会话取UserID,不要求客户端伪造EntityKey。

原Attributes.DataObject内容:

字段 原类型与来源 迁移要求
Tier string[],FDM.eventSolomain.MatchTierList转字符串 原匹配条件输入原样传递
Map string[],FDM.mapIDs转字符串 原候选地图原样传递
Group string[],FDM.GetGroups() 原分组原样传递
Details JSON字符串,不是嵌套对象 原GetDetails输出,含ID/Name/Avatar/RandScore/Mag
Details.ID string,IUserService.UserId 必须对应认证UserID
Details.RandScore / Mag int,duelData.progress / magnification 原数值,不自行增加匹配过滤条件

原查询每3秒一次,EscapeObject=false;结果查询ReturnMemberAttributes=true。原匹配失败由既有MachingEnd(null)进入原后续流程;不能把当前KV分支一进入就结束的开发冻结当成正常迁移结果。

6.2 四个拟议动作

已有服务器等价匹配服务优先复用,下列路径仅在缺入口时采用。所有票据操作按认证账号核所有权,匹配结果仅参与者可读;TicketId/MatchId作为不透明字符串,不能用客户端任意目标ID访问他人票据。

动作 拟HTTP入口 请求 成功关键输出
创建 POST /matchmaking/CreateMatchmakingTicket queueName、giveUpAfterSeconds、attributes ticketId
查询 POST /matchmaking/GetMatchmakingTicket queueName、ticketId status、matchId、cancellationReasonString
取消 POST /matchmaking/CancelMatchmakingTicket queueName、ticketId code=1确认处理,已有终态按实际合同返回
结果 POST /matchmaking/GetMatch queueName、matchId matchId、members及每位成员原attributes

6.3 创建示例

示例数值仅用于说明类型,实际从客户端原配置/状态传入:

{
  "queueName":"DuelPVP","giveUpAfterSeconds":30,
  "attributes":{
    "Tier":["1","2"],"Map":["101","102"],"Group":["0"],
    "Details":"{\"ID\":\"101\",\"Name\":\"Angler#101\",\"Avatar\":\"\",\"RandScore\":100,\"Mag\":1}"
  }
}
{"code":1,"msg":"","ticketId":"ticket-example-101"}

服务器校验Details.ID与认证UserID一致;接受原属性类型、原队列和原超时时间范围,不以默认常量替换请求。原PF队列成员数、Tier/Map/Group规则、放宽条件等需要从现有等价实现或原DuelPVP配置复用,源码仅证明字段输入,不能凭字段名自行发明匹配算法。这属于服务端移植所需配置输入,不是要求用户重新设计玩法。

6.4 查询示例与状态

{"queueName":"DuelPVP","ticketId":"ticket-example-101"}
{"code":1,"msg":"","status":"WaitingForMatch","matchId":null,"cancellationReasonString":null}
{"code":1,"msg":"","status":"Matched","matchId":"match-example-01","cancellationReasonString":null}
{"code":1,"msg":"","status":"Canceled","matchId":null,"cancellationReasonString":"Requested"}
  • WaitingForMatch为本协议拟用的等待状态,若既有服务还有原有非终态,可按同义状态返回;客户端原来只对Matched/Canceled作终态分支,其余继续原轮询。
  • Matched必须同时给有效MatchId并能够读取该次结果;双方取得同一MatchId和同一成员集合,不能在每次查询新建结果。
  • Canceled给真实原因。Requested表示用户取消;达到GiveUpAfterSeconds的超时也需映射到原可识别Canceled并说明超时原因,不能永久保持等待。具体原PF原因字符串从原配置/响应对照保留,不用示例覆盖所有原因。
  • ticket不存在/过期读取/请求失败不得包装成Matched;服务端返回明确错误,客户端进入原查询失败路径。

6.5 取消

请求同查询的queueName/ticketId。等待中的取消成功示例:

{"code":1,"msg":""}

服务端必须处理取消与成对成功的竞争,终态要与后续查询一致;不能既向一方报告已取消成功又把其作为新匹配结果交给另一方。已取消可返回成功,已匹配时拟HTTP409/code=0/msg=MATCH_ALREADY_COMPLETED;此处为待审错误映射,若既有等价服务或原PF终态合同不同,应提供证据调整,不能宣称该错误码已经线上存在。

客户端沿原逻辑先隐藏按钮、清currentTicketId再发送取消,成功只记录,失败只记错。本次不把取消改为“等服务器确认后才能退出”,不顺便修竞态或新增重试。

6.6 获取结果

{"queueName":"DuelPVP","matchId":"match-example-01"}
{
  "code":1,"msg":"","matchId":"match-example-01",
  "members":[
    {"playerId":"101","attributes":{"Tier":["1","2"],"Map":["101","102"],"Group":["0"],"Details":"{\"ID\":\"101\",\"Name\":\"Angler#101\",\"Avatar\":\"\",\"RandScore\":100,\"Mag\":1}"}},
    {"playerId":"102","attributes":{"Tier":["1","2"],"Map":["102","103"],"Group":["0"],"Details":"{\"ID\":\"102\",\"Name\":\"Angler#102\",\"Avatar\":\"\",\"RandScore\":100,\"Mag\":1}"}}
  ]
}

成员playerId为新UserID;attributes是对象,Details仍为原JSON字符串。Map仍可按原消费者转int[],Tier转string[]。服务端保证成员ID与Details.ID一致,结果是本次票据提交的属性快照,不在查询时改成另一时刻的玩家数据。

原消费者遍历成员,按本人Entity.Id排除自己,迁移时仅换成playerId对比本人UserID;随后解析对方Details/Map,清currentTicketId,调用FDM.MachingEnd(matchId, details, mapArray),再启动原5秒Ready检查。缺成员/坏属性按原失败/解析处理,不创造假对手填补。

后续流程约束:FishingDuelManager_Network.cs:102的MachingEnd会取双方Map交集并以matchId.GetHashCode作为原种子输入,所以服务端不能更换MatchId或配出不符合原规则的无共同地图组合;本次不改变种子算法。对战继续使用原RT通信,enemyId等使用同一UserID。匹配服务不新增对战服务器分配、战斗结算或奖励逻辑。GetHashCode跨运行环境的旧风险只记录,不在本迁移改算法。

6.7 错误响应

新拟匹配接口的失败体统一保留code/msg,不返回假成功字段,例如:

{"code":0,"msg":"MATCHMAKING_UNAVAILABLE"}

参数/属性格式错误400、无权读取403、不存在票据/结果404、上述终态冲突409、服务错误500;认证沿原401/403。服务器仅在规则允许时产生终态,存储错误不冒充正常Canceled。客户端映射原相应SDK错误回调:创建失败FDM.MachingEnd(null),查询/结果失败MachingEnd(),取消失败记日志;不改变现有用户流程。

7. 服务端交付与联调验收

请一次交付下列内容,避免逐接口往返询问。已有实现填写实际映射;需要补实现按本拟合同对照。无需提供密码、Cookie、完整玩家私有档。

交付项 要求
接口对应表 本文拟路径 → 实际方法/路径;标明已有可用、待扩字段、需新增、已部署版本
请求与响应 各项成功、空/缺失、失败的脱敏JSON;新增字段精确大小写/类型,保持旧客户端兼容
数据来源 等级榜/好友/公开资料取哪些已存KV字段与布局;公告游戏作用域与发布来源;匹配队列配置来源
等价配置证据 原LevelB统计同分/更新/重置及DuelPVP匹配规则沿用情况;未取得配置处明确列出,不自行新定业务
数据准备情况 原统计/公告是否已迁移、测试账号是否有对应字段;缺档不补假数据,不用查询时刻充当登录时间
联调信息 固定正式地址对应版本、可用时间、必要服务器日志定位方式及已知差异;无需更换环境地址

验收按原行为执行:

  1. 榜单:全球/地区、0/1/50/超过50条、同分、缺统计键/损坏统计、写后读取;位置/分数/UI解码与旧流程一致。
  2. 好友/公开资料:101与102互查身份正确;好友一次返回资料、LevelB与本人KV一致、时间来自已存值;Profile/Statistics缺失和空数组分开;普通等级与现代KV档一致;旧搜索消费者兼容。
  3. 公告:同游戏两账号读到同一内容;原首条与时间保留;新/旧账号的原红点判断、已读标记、无公告和请求失败保持。不要发布示例正文作测试。
  4. 匹配:两个真实或隔离测试玩家创建→等待→匹配→同一结果;用户取消、超时、非法票据、服务失败及终态竞争;属性和共同地图有效;同UserID进入原RT与5秒Ready链路。不能只验证四个HTTP200就称匹配通关。
  5. 客户端回归:KV分支上述PF调用为0;PF/AGG原路线保留;不改变LevelB算法、原空榜行为、好友过滤、公告首项、匹配3秒轮询/原失败处理。所有开发冻结在完整通关前恢复。

账号101/102可用于已授权必要测试,103–110仅按需要使用,不扩展正式客户端准入名单。正式测试不能擅改等级/奖励/付费数据来制造样例;协议夹具与线上实测分开记录。客户端Unity只用CLI Loop,不打包。

8. 责任与审核边界

  • 服务端开发:在其负责的当前线上源码实现/复用上述接口、内部KV读取/聚合、游戏公告读取及匹配协调;保留已有接口兼容。本文不要求修改当前电脑上的旧服务器副本。
  • 客户端开发:在现有LeadboardData、FriendService/FriendModel、KVUserService、TileNewsManager、EventFishingDuelPanel中完成必要接线与结果映射,保留原流程。IPlayerSaveStore本人统计写是存储替换,不是新业务设计。
  • 审核:四类完整合同、原行为映射与客户端最小文件/方法边界合并现有C3c完整审核;通过后连续实施,不另拆LevelB、公告或匹配逐方法审核。当前文档是开发对接提案,非审核PASS或部署证明。

本轮文档已核对本地客户端代码和项目内SDK模型;没有访问PlayFab后台或当前服务端配置,没有实施生产代码/测试代码修改、线上写入或打包。

附录:既有材料

本文新增的公开资料kvProfile、公告和匹配HTTP合同均为拟议设计;旧接口已有等价能力可用实际映射替代,服务端实施反馈与同次完整审核明确最终版本,不让客户端靠多重开关猜协议。