Files
youle_cocos/docs/superpowers/specs/2026-09-04-platform-vertical-slice-contract-inventory.md
T

15 KiB
Raw Blame History

平台纵向链路契约清单

本清单只覆盖 2026-09-04-platform-vertical-slice-design.md 的第一批范围。

完整 84 项平台 RPC 的唯一清单仍是 docs/protocol/README.md,字段权威仍在 docs/protocol/01-05。本文不复制完整协议,只记录第一批实现门槛、源码锚点和当前偏差。

1. 权威证据

领域 权威文件
传输、信封、心跳、登录门控、切服 docs/protocol/01-传输层与架构.md
agent RPC docs/protocol/02-协议-agent路由.md
room RPC docs/protocol/03-协议-room路由.md
登录、玩家、房间、roomtype 数据 docs/protocol/04-数据结构.md
游戏 route、deskwar、deskinfo docs/protocol/05-游戏内协议与桥接.md
旧连接与启动流程 projects/Game_Surface_3/js/00_Surface/12_Logic.js
旧发包与收包入口 projects/Game_Surface_3/js/00_Surface/09_Net.js
旧房间状态机 projects/Game_Surface_3/js/00_Surface/07_Desk.js
原生配置与 WVJB projects/Game_Surface_3/js/00_Surface/05_Func.js
默认 gameserver projects/Game_Surface_3/js/01_SubGame/00_SubGame_Config.js:11

2. 启动与配置契约

编号 契约 原工程锚点 当前 YouleNexus 偏差 第一批门槛
CFG-01 默认 gameserver 来自子游戏配置唯一常量 00_SubGame_Config.js:11 profiles.ts 使用不同 URL profile 与目标原工程值一致
CFG-02 原生 gameconfig 非空时:-→/、#→:,构造 http://<value>.txt 12_Logic.js:1268-1279 未完整复刻 黄金输入输出测试
CFG-03 H5 渠道取 query channelid、agentid;原生渠道走原方法 12_Logic.js:1215-1246 source 合并策略可能改变分支 mode/userAgent 分支显式建模
CFG-04 原生 agent 使用 window.settings.getothername('agent');仅 uAgent_3 使用 window.app_agent 05_Func.js:2451-2468, 12_Logic.js:1232-1246 捕获任意错误后都会尝试全局回退 只有来源明确定义的分支可回退
CFG-05 get_config(gameserver) 成功后解析 JSON 并取 _msg.data.urlserver 12_Logic.js:521,533-550 当前解析 *_server_tcp WebSocket 地址只取 urlserver
CFG-06 urlserver 可为单地址或候选数组 12_Logic.js:826-899 target 支持 servers 数组,但上游来源不同 保持顺序并逐项规范为 ws/wss URL
CFG-07 配置失败不产生替代服务器 仓库第二准则 LoginFlow.ts 回退 localhost release/debug 都显式失败
CFG-08 debug 直连是 profile 的显式语义 profiles.ts 目标设计 已具备,但入口硬编码 local 仅显式选择 profile 时生效

启动就绪条件固定为四项:

resourcesReady && configReady && socketOpen && minimumDisplayElapsed

任一项失败都不能把其它项当作成功。LaunchFlow.ts 的固定一秒模拟不属于契约实现。

3. 传输契约

编号 契约 精确要求
NET-01 出站信封 {app:"youle", route, rpc, data},单层 JSON
NET-02 入站载荷 浏览器 MessageEvent.data 解出协议单层信封;允许旧服务实际出现的字符串化形态,但不能虚构第二层业务包装
NET-03 route 分界 platform/agent/room 进入平台;其它 route 仅在等于 active game route 时进入子游戏
NET-04 登录门控 发出 login 后,只处理 player_login 与 kick_server,其它业务包丢弃
NET-05 登录守护 子游戏模式四秒未收到 login 响应时触发重连
NET-06 收包超时 有效帧重置 watchdog;超时进入 slow 并重连
NET-07 踢下线 kick_server 停止自动重连并进入 kicked
NET-08 切服 connect_roomserver.data.roomserver、connect_agentserver.data.agentserver 更新当前服务器并重连重登
NET-09 发送前置条件 transport 或完整 login identity 缺失时显式抛错,不静默 return
NET-10 唯一消费者 每个业务信封只交给一个 Router 一次

4. 第一批 RPC 契约

4.1 agent route

RPC 方向 第一批用途 请求/响应权威 必须验证的分支
player_login 双向 首登、重登、恢复房间 protocol/02 §登录;protocol/04 §登录响应 state 失败、无房、大厅、在房、含 deskinfo
self_join_room 双向 房号加入房间 protocol/02 §房间创建/进入;protocol/04 §公共字段 state=0、state=99、一般失败、deskwar、deskinfo、普通进房
connect_agentserver S→C 切换 agent server protocol/01 §7.4 地址存在、地址缺失报错
connect_roomserver S→C 切换 room server protocol/01 §7.4 地址存在、地址缺失报错
kick_server S→C 强制下线 protocol/01 §3.2 停止重连、显示踢下线态

player_login.data.version 按已验证协议使用数字 versionCode。请求的必需字段是:

agentid, gameid, openid, nickname, avatar, sex,
province, city, unionid, version, channelid, marketid,
machineid, machineroom

条件字段严格按来源条件加入:ip、location、telphone、telphoneAuto、缓存 playerid。条件不成立时字段应省略,不用空值伪装已提供。

self_join_room 基础请求字段是:

agentid, playerid, gameid, roomcode

location 与 ip 按原发送器注入;vipMatch、match_id 不属于第一批普通房号加入入口。

4.2 room route

RPC 方向 必需 data Store/钩子结果
other_join_room S→C seat + 完整玩家座位对象 upsert 玩家实体,写 seatPlayerIds;deskwar 时开战
self_exit_room 双向 响应可含 seat/isowner/roomcode 成功后清房间;保留登录玩家实体
other_exit_room S→C seat 清座位映射;玩家实体无其它引用时移除
player_prepare 双向/推送 seat,可选 deskwar 更新 ready,调用 onPlayerReady;deskwar 时开战
other_offline S→C seat 对应玩家 onstate=1,调用 onPlayerOffline
other_online S→C seat, ip 对应玩家 onstate=0 并更新 ip,调用 onPlayerOnline

上表的座位值以服务器协议为准。转换为数组索引必须集中在一个座位转换函数中;控制器和 handler 不允许各自执行 seat - 1。

5. 登录响应字段组

5.1 A 组:玩家与代理状态

第一批 parser 至少显式声明并校验 protocol/04 已验证字段:

state, playerid, agentid, channelid,
nickname, avatar, openid, sex, unionid,
roomcard, bean, score, invitecode, advanced, taskstate,
ip, bankpower, bank, sign, tel, initCard, initBean, bankpwd,
agentname, agentmode, gameversion

字段是否可选以 protocol/04 的条件说明为准。可选性必须写在 parser 类型中;不得用 ?? 0 或 ?? '' 把缺失变成有效数据。

5.2 B 组:房间恢复状态

存在 roomcode 时,parser 至少处理:

roomcode, roomtype, asetcount, isbattle, makewar, seat, isowner,
players, roommode, beanlimit, needprepare, infinite,
rebateNumber, rebateMode, rebateType, sign, ownerNotice,
videoConfig, shortcode, match, matchid, agreefree, isbet, deskinfo

关键判定:

  • roomcode 存在决定是否恢复房间;
  • isbattle 只写房间 stage;
  • deskinfo 字段存在决定是否调用 onReconnect(deskinfo);
  • deskinfo 值不被平台复制、补字段或解析;
  • roomtype 保留完整嵌套数组结构。

6. 状态所有权

数据 唯一来源 禁止的第二来源
runtime mode runtime-mode resolver Cocos 控制器硬编码
gameserver active profile,经原生 gameconfig 合法覆盖 LoginFlow 常量
WebSocket servers remote data.urlserver 或显式 debug direct profile Store/NetClient 自行推导
channel identity identity resolver 发包器补默认 ID
login identity 登录/授权来源 NetClient mock identity
rpc→route 生产 RPC_ROUTE 控制器字符串
玩家实体 PlayerDirectory entities RoomStore 中复制 PlayerState
自己身份 selfPlayerId 指向玩家实体 独立 C_Player 镜像
房间座位 RoomState seatPlayerIds UI 局部玩家数组
当前场景 AppState scene/phase 反查某个节点 active
roomtype RoomState 原样值 UI 重新生成或规范化
deskinfo 当前入站响应中的原样值,完成一次性派发后由子游戏接管 PlatformState 或平台拆解后的派生对象

7. 子游戏解耦契约

7.1 公开边界

编号 契约 验收方式
GAME-01 平台核心只依赖 sdk/contracts,不导入具体游戏 import-boundary test
GAME-02 具体游戏只依赖 sdk/contracts 与自身目录,不导入 platform/net/protocol/ui 内部 import-boundary test
GAME-03 SDK contracts 不依赖 Store、Reactive、EventBus、WebSocket 或 cc import-boundary test
GAME-04 每个游戏以 GameDescriptor 注册,key/gameId/route 唯一 registry unit test
GAME-05 apiVersion 必须与平台完全匹配 mismatch failure test
GAME-06 descriptor factory 每局产生新 GameModule identity assertion
GAME-07 平台→游戏只走生命周期和 PlatformToGameEvent fake module event log
GAME-08 游戏 route 包只走 handleGameMessage router integration test
GAME-09 deskinfo 只走 restore,平台不保存、不解析 reference/value assertion
GAME-10 游戏→平台只走受限 GameHost public type and import scan
GAME-11 sendGameMessage 自动绑定本游戏 route encoded envelope assertion
GAME-12 platform command 使用判别联合,未知命令显式失败 exhaustive parser test
GAME-13 snapshot 是冻结 DTO,不暴露内部 Store 类型 mutation failure/type test
GAME-14 dispose 后 Host 失效并释放全部订阅 lifecycle/leak test
GAME-15 不同游戏、不同房间会话之间无状态泄漏 two-game isolation test

第一版 GameHostCommand 只包含:

room.prepare  → 发送第一批 player_prepare
room.exit     → 仅未开局时发送 self_exit_room;已开局显式拒绝

平台先提交权威 Store,再串行通知子游戏 event。subscribe 初次立即发 snapshot,后续仅在公开 snapshot 变化时通知;snapshot 不能替代需要恰好处理一次的 event。

7.2 现有 SDK 必须移除的耦合

现状 原因 目标
sdk/index.ts import platform/readonly.ts 和 Store types SDK 类型随平台内部重构而变化 使用独立 PlatformGameSnapshot DTO
GameContext.events 暴露内部 EventBus 子游戏依赖平台事件实现且可产生无约束事件 删除;子游戏自行管理内部事件
GameNet.send(route,rpc,data) 子游戏可伪造平台 route 改为自动绑定 route 的 sendGameMessage
大量可选 hook 未实现时静默跳过,接口持续膨胀 改为判别联合 PlatformToGameEvent
IGameModule.serialize?() 无客户端上传快照的协议证据 删除;重连快照只接受服务器 deskinfo
ActiveGame.set/clear 无注册、版本和释放校验 用 GameRegistry + GameSessionHost 替代

7.3 子游戏接入步骤

接入一个新子游戏只允许以下步骤:

  1. 在 assets/games/<game-key>/ 创建游戏自身模块、状态、组件和资源;
  2. 实现 GameModule 与唯一 GameDescriptor;
  3. 使用 game-contract-harness 跑通标准契约测试;
  4. 在应用组合根注册 descriptor;
  5. 增加该游戏自己的 route/rpc、roomtype 和 deskinfo 测试。

上述步骤不包含修改 framework Router、NetClient、PlatformState 或平台 UI。若接入必须修改这些核心模块,说明 SDK 契约缺失,应先作为平台 API 变更单独设计和升级版本。

8. 首批 UI 对应关系

行为 prefab/Layer 控制器职责
启动背景和资源进度 Layer 1 只显示加载状态
网络请求遮罩 Layer 614 订阅 pending command 数量
断线重连 Layer 615 订阅 reconnecting/slow
强制下线 Layer 616 订阅 kicked 并阻止继续操作
登录/授权入口 Login_Layer.prefab 产生 login identity 或触发已有身份登录
大厅 Layer 4 展示 self player selector;打开 JoinRoom
输入房号 Layer 15 本地六位输入;确认时调用 joinRoom command
房间壳 Layer 50、411 展示房号、时钟、网络态和退出入口
准备按钮 Layer 403 调用 prepare command,状态由服务器推送确认
玩家座位 Layer 202、416 从 seat selector 渲染,不保存玩家副本

对 prefab 的节点路径、按钮绑定与组件挂载,以当前 Cocos 资源实际结构为准,并在实施时通过 funplay-cocos MCP 查询和修改。

9. 原生接口范围

第一批只实现启动必需的同步读取:

window.settings.getothername('agent')
window.settings.getothername('gameconfig')
window.settings.getothername('servertype')
window.settings.getchannelName()
window.settings.getmarketname()

uAgent_3 的 window['app_' + name] 是原工程明确定义的独立分支,不是任意异常的通用回退。

完整 WVJB 作为后续独立子项目。现阶段已确认的审计事实:

  • 原工程注册 21 个唯一 handler 名;
  • 源码文本出现 41 个唯一 callHandler 名,其中 getcompareCode、getothername、getmarketname 仅出现在注释调用;
  • 入站与出站名称集合不同,且存在大小写敏感名称,例如 getphoneinfo 与 getphoneInfo;
  • 初始化必须保留 window.WVJBCallbacks、WebViewJavascriptBridgeReady 和 wvjbscheme://__BRIDGE_LOADED__。

因此现有 14 项共用白名单不能作为完整桥契约继续扩展。

10. 黄金样本清单

实施前必须把以下样本固化到 cocoscreator_projects/framework-tests/fixtures/protocol/:

  1. 远程配置:单 urlserver、数组 urlserver、缺失 urlserver;
  2. player_login 请求:普通身份、带 ip/location、设备手机号、缓存 playerid;
  3. player_login 响应:失败、成功无房、成功在房无 deskinfo、成功在房含 deskinfo;
  4. self_join_room:成功普通、state=99、一般失败、deskwar、deskinfo;
  5. other_join_room:完整玩家和准备条件;
  6. player_prepare:普通准备与 deskwar;
  7. other_exit_room、other_offline、other_online;
  8. connect_agentserver、connect_roomserver、kick_server;
  9. 一段纵向序列:登录大厅→进房→准备→掉线→重登恢复。

若仓库中没有真实样本,先从原客户端或测试服务器抓取并脱敏。没有证据的字段不能凭名称推断。

11. 完成判据

每条契约须具备:

  • 一个权威源码/协议锚点;
  • 一个具名 TypeScript 类型;
  • 一个运行时 parser;
  • 至少一个成功黄金样本;
  • 必需字段缺失的失败测试;
  • 对应 Store action 或运行时状态迁移测试;
  • 涉及 UI 时的控制器测试;
  • 涉及服务器时的出站信封断言。

每个子游戏 descriptor 还必须通过 game-contract-harness 和 import-boundary 测试。

缺少上述任一项,该契约仍视为未迁移。