# 房间平台与子游戏接入架构设计 版本:1.1 · 日期:2026-09-08 · 状态:平台核心已实施;验收与未支持边界见[实施记录](../implementations/2026-09-08-room-platform-refactor.md)。 本文保留设计契约与目标示例;具体 API 名称、落地位置及兼容期以实施记录和代码为准。音频所有权、完整子游戏玩法与原生设备验收不因平台核心落地而自动视为完成。网络协议、原生接口与序列化资源约束继续以仓库规定及对应权威文档为准。 ## 文档导航 - [源码审查、方案优缺点与原流程依据](../../房间流程审查与架构重构建议.md) - [旧接口迁移与新接入契约](2026-09-08-room-platform-api-migration.md) - [分阶段实施计划与验收](../plans/2026-09-08-room-platform-refactor.md) - [平台协议目录](../../protocol/README.md) - [二七王当前玩法协议](../../../server/games/erqiwang/docs/protocol/packet_protocol.md) ## 1. 目标与非目标 ### 1.1 目标 1. 原服务器零修改即可兼容,公开协议字段、route/rpc、数据类型与重连快照保持不变。 2. 平台不依赖具体子游戏;子游戏不访问平台内部 Store、场景和网络实现。 3. 纯逻辑无需启动 Cocos 即可测试;资源装配与真实交互必须另行在编辑器/构建中验证。 4. 子游戏只开发规则、协议映射、游戏模型和展示,不重复实现大厅、聊天、投票、设置、原生桥。 5. 房间生命周期、消息顺序、资源所有权、失败处理都具有明确契约。 6. 对已证实的全量重建、重复头像资源等问题做增量优化,性能结论以基线测量为准。 ### 1.2 非目标 - 不重新实现服务器,不添加前端方便但服务器不存在的字段或 requestId。 - 不将原 Game_Modify/Utl 全量搬成同名 TypeScript 类。 - 不在本阶段引入 ECS、Worker、全局响应流框架、反射容器或远程热插件系统。 - 不以此文档承诺完成二七王叫分、出牌、计分、回放或结算画面。 - 不自动修复已经用错误 roomtype 创建的服务器房间,不静默改写用户缓存。 ### 1.3 “零耦合”的可验证定义 零具体游戏依赖:除应用组合根外,公共代码不得 import games/*。 零内部访问:游戏只能引用 sdk/contracts 和自身模块,不引用平台 Store、Router、PlatformStartup。 零引擎依赖的逻辑:房间应用层、状态与游戏规则/模型不 import cc,不读取 window、localStorage 或原生全局对象。 允许必要契约依赖:游戏和平台共同依赖小而稳定的 SDK。完全没有任何依赖既不可实现,也不是本方案目标。 ## 2. 权威数据与兼容边界 ### 2.1 平台数据 身份、连接/认证状态、房间公共元数据、成员、座位、在线/准备、公告、聊天、投票结果,以各自协议来源为准。平台负责公共协议字段的校验、归一化和状态提交。 归一化必须有实际协议依据、在入口单点执行。例如此前数字房号的兼容不能推广成“所有字段都 String/Number 一遍”。出站类型仍按对应 rpc 契约生成。 ### 2.2 子游戏载荷 roomtype、deskinfo、deskwar、deskfree 及游戏 route 的数据,由游戏解释。平台只保存、传递、按协议判断载荷是否存在,不读取内部选项、手牌或分数。 拟议的通用可传输类型: ```ts type JsonValue = null | boolean | number | string | readonly JsonValue[] | { readonly [key: string]: JsonValue }; type Roomtype = JsonValue; ``` 运行时仅允许有限数字、无循环的 JSON 值,不允许函数、BigInt、引擎对象和 undefined。这是传输限制,不是玩法格式限制。每个外层协议若规定字段必需,则必须存在;不能把缺失字段改成 null。 “原样”指保持 JSON 数据结构、值、类型、数组顺序和未知字段;不要求保留 JSON 文本空格或对象键的序列化顺序。若协议某个字段本身是字符串编码,则字符串内容必须完整保留。 二七王创建编码仍由其编码器生成 11 位字符串;模板可以发送数组。平台不得为了接入方便转换任何一种格式。 ### 2.3 游戏事实与公共展示 玩法包有时才包含下一局、参与状态和局内分数。因此平台不能要求所有公共展示变化只能来自平台 route,也不能允许游戏任意改 Store。 采用单向的 GameRoomProjection:游戏模型解析自己的协议后发布只读投影;公共展示选择器组合平台快照与游戏投影。 必须区分: - 服务端公共 room stage 与游戏自己的 round phase,不用同一个字段存两者。 - 账户余额与局内分数,不用 changeBean 写同一份值。 - 公共准备状态与玩法参与/等待下一局状态,不把派生按钮显隐反写准备值。 - 平台规则已禁止的动作与游戏附加限制:游戏可以收紧权限,不可扩大平台明确禁止的权限。 确有玩法包需要同步公共协议状态时,建立具名、带来源的映射,并在对应游戏适配器与协调器测试;禁止 patchRoom/setPlayerState 等任意写入接口。 ## 3. 模块结构与依赖 以下为目标职责分布,不要求第一批任务一次搬完目录: ```text assets/ app/ 唯一知道具体游戏的组合入口 bootstrap/ composition/ framework/ sdk/contracts/ 公共纯类型与稳定端口 net/ 保留 WireClient、心跳、连接代际 protocol/ 公共信封与协议适配 platform/room/ room-coordinator.ts 流程与事务编排 room-scope.ts 会话作用域和清理 room-mailbox.ts 有界业务消息等待队列 room-entry.ts 不同进入来源的描述 room-policy.ts 平台公共动作策略 platform/stores/ 保留并渐进拆分公共状态 presentation/ 无 Cocos 的展示模型与选择器 adapters/ 网络、原生、存储等端口实现 ui/room/ 公共 Cocos 面板与场景适配 games// definition.ts 唯一游戏定义 protocol/ 玩法解析、rpc 类型和入口映射 rules/ roomtype、座位、描述等纯规则 model/ 游戏状态和同步输入处理 presentation/ 游戏展示模型 cocos/ 创建页、房间 View、资源 ``` 依赖关系: ```mermaid flowchart TD App[应用组合入口] --> Platform[平台房间应用层] App --> Game[具体游戏模块] App --> Cocos[Cocos 与设备适配] Platform --> SDK[共享契约] Game --> SDK Cocos --> SDK Platform --> Protocol[公共协议与状态] Game --> Rules[游戏规则与游戏模型] ``` SDK 不能反向 import 平台实现。当前部分能力类型声明在 game-host-adapter.ts,迁移时应提取到契约目录,不能让 presentation 因一个类型依赖具体适配器。 ## 4. 游戏定义与资源装配 ### 4.1 唯一游戏定义 一个权威定义提供:本地 key、游戏协议 route、身份配置关联、规则、模块工厂、创建页/房间展示引用、游戏内容和可选能力声明。 身份中的 agentid/channelid/gameid 继续由配置来源提供。游戏定义引用该配置关联,不在多个文件复制实际渠道身份。 Cocos 组件可以引用生成的资源定义或唯一的装配资源,但不得与 TypeScript 定义分别维护两份 gameKey、route、prefab。实施时优先选择构建阶段验证明确静态引用的方式;不要运行时按名称扫描并猜测页面类型。 ### 4.2 装配验证 - 每个创建 prefab 恰好一个创建页契约实现。 - 每个房间 prefab 恰好一个房间展示契约实现。 - 所有必需资源可解析,引用目标与声明的游戏一致。 - 声明的协议 route 不与平台保留 route 冲突。 - 不通过“随便创建再销毁一个游戏实例”验证声明;在真实创建时验证实例契约,避免工厂预检产生业务副作用。 - 未使用的可选能力无需假实现;声明了必需能力却未绑定时装配失败。 最近二七王 prefab 中模板脚本残留的问题,必须由上述检查自动发现,而不是等创建 RPC 发出去才发现。 ## 5. SDK 开发者接口 ### 5.1 原则 SDK 面向开发者只暴露少量分组能力:room、messages、ui、audio、scope。内部实现可按职责拆分,不把每个旧函数变成独立服务。 禁止 getService(name)、任意 Store 写入、任意 app/route 发包、直接传平台节点。消息传输使用绑定当前游戏 route 的端口;通用房间命令由平台构建身份和房号。 ### 5.2 核心形状 以下用于说明边界;完整字段按实施阶段测试补全,不是可直接粘贴的 SDK 实现: ```ts interface GameModel { // context 已绑定一个房间;不得缓存到跨房间全局。 initialize(context: RoomContext): void; // 同步处理;不可返回等待动画结束的 Promise。 handle(input: GameInput): void; dispose(): void; } interface RoomContext { readonly room: RoomReadPort; readonly commands: RoomCommandPort; readonly messages: GameMessagePort; readonly ui: GameUiPort; readonly audio: AudioPort; readonly scope: ScopePort; } interface GameMessagePort { send(rpc: string, data: JsonValue): void; } ``` GameMessagePort 的 void 表示传输操作本身,不表示服务器业务成功。实际回复仍按协议作为输入处理。游戏内部可以将 rpc 与 payload 建成强类型映射,公共平台不需要知道映射内容。 RoomReadPort 提供只读快照、按选择器订阅和座位映射。快照变更具有本地 revision;本地 revision 不加入网络包、不冒充服务器序列号。 ### 5.3 输入事件保留来源 必须区分: - created:成功创建结果,原 roomtype 和 infinite 等数据。 - login-restored:登录恢复,携带原 deskinfo。 - login-waiting:登录已有房间但无恢复快照。 - joined-waiting:普通主动加入。 - joined-snapshot:主动加入已有 deskinfo。 - started:加入、准备、成员变化、self_makewar/makewar 引起的开战,携带 sourceRpc 和原整包。 - player-ready/joined/left/online/offline/seat-changed:公共成员事件。 - game-message:当前游戏 route 的原消息。 - dissolution-settlement:服务端解散结果与原 deskfree,保留 freeNow/确认时机。 内部可用联合类型表示,不扩展服务器包。类型名称可以调整,但这些语义不能被一个 restore(unknown) 覆盖。 ### 5.4 只读投影 游戏模型拥有 GameRoomProjection 并允许订阅;它只包含公共 HUD 真正需要的事实,不泄露完整手牌与内部模型。 第一阶段字段以模板和二七王实际需求为准:玩法阶段、自己的参与状态、按服务器座位的玩法分数、公共操作限制。投影是游戏状态的派生结果,不另设可任意写入的第二份状态。 RoomSnapshot revision 与 GameProjection revision 分别可追踪;在一次协议处理事务完成后向公共 UI 发布组合快照,避免 UI 看到同一消息的一半更新。 ### 5.5 页面接口 创建页接收上次配置和 submit/cancel。编码由游戏完成,平台收到 roomtype 只封装与发送;未知或错误旧配置由游戏迁移策略处理,没有迁移策略就明确提示并允许用户显式重置。 房间 View 的职责只有挂载、根据模型绘制、将用户操作交给控制器、取消动画与卸载。它不读取 WebSocket、不直接恢复协议快照、不以节点可见性决定业务状态。 ## 6. 生命周期与消息时序 ### 6.1 分开建模 - Connection:连接中、已连接、重连中、停止。 - Authentication:未登录、等待回复、已登录、拒绝/被踢。 - RoomLifecycle:outside、entering、active、recovering、leaving、failed。 - GamePhase:子游戏自己的轮次/行动阶段。 - Dissolution/Settlement:投票和结果展示独立于 GamePhase。 这些维度存在明确约束,但不要混成一个巨大的状态枚举。 ### 6.2 进入事务 1. 验证公共外层协议,保留原始载荷。 2. 在游戏规则入口验证其拥有的配置并解析需要的座位约束;不在 UI 层补缺失数据。 3. 完成公共状态构建的预检。已接受的新服务端房间不能在失败时偷偷恢复成“旧房间仍有效”。 4. 使旧 scope 失效并取消旧任务,创建新的 generation,提交进入状态。 5. 创建游戏模型、绑定能力,启动异步资源与界面挂载。 6. 界面挂载就绪后,以正确来源发送进入输入;恢复使用原快照,不自行填快照字段。 7. 标记可交互,按顺序处理进入后等待的业务消息。 失败策略:步骤 1–3 失败不创建半初始化 scope;步骤 4 后失败将新会话标为 failed 并完整释放,不伪装 active。是否重新登录恢复,由现有连接/恢复契约决定,不能对格式错误无休止自动重试。 ### 6.3 异步就绪 ```mermaid sequenceDiagram participant Net as 协议入口 participant Room as 房间协调器 participant View as Cocos 场景适配 participant Game as 游戏模型 Net->>Room: 接受创建/加入/登录结果 Room->>Room: 预检并建立新 scope Room->>Game: initialize(context) Room->>View: mount(scope) Net->>Room: 后续房间业务消息 Room->>Room: 暂存有界 FIFO View-->>Room: ready Room->>Game: 带来源的进入输入 Room->>Game: 按序处理暂存消息 ``` model.initialize 只绑定上下文与初始模型,不要求 View 已可用。需要页面的操作通过能力端口等待就绪,且离房时可取消。禁止每种能力各自无界积压请求来弥补没有统一就绪契约。 旧 mainSceneLoaded 的通知时点与“可以访问节点”的内部就绪点分开。兼容映射保留原 create/login/join 的调用语义,不用新名字掩盖顺序变化。 ### 6.4 队列规则 队列只保存当前已认证、当前 generation 的房间业务消息,FIFO;不改变原有登录屏障规则。换服尚未建立目标会话的消息按连接意图处理。 容量与最长等待时间由平台配置来源显式给出,先通过基线和压力测试确定;消费方不使用随意 fallback。溢出或超时导致明确诊断和恢复,不丢最旧的游戏包继续假装成功。 踢出、断线、用户取消等控制信号可立即使 scope 失效,不等待场景/动画。没有服务器序列号时,不按 rpc 名或 payload 相等对玩法事件去重。 ### 6.5 准备和开战 准备请求发出只更新本地 pending,不提前更改权威 ready。回复提交 ready 后产生成员事件;如包中携带 deskwar,再提交相应阶段并触发 started,原整包保留。 同步处理状态不等待动画。动画队列属于 View,断线/恢复可以取消旧动画并直接渲染当前状态。开始按钮的条件来自统一策略,不由各按钮、摇一摇与子游戏分别实现。 ### 6.6 解散与结算 投票倒计时不宣布结果。服务端拒绝结束本轮投票;普通通过等待结果确认后传递结算;freeNow 按协议立即传递。没有 deskfree 时按已核实的退出路径结束房间。 有 deskfree 时保留游戏与必要玩家上下文直到结算结束。明确区分 round-finished、room-finished 和 result-view-closed;不允许一个模糊 gameOver 同时决定分享、清空状态和跳场景。 ### 6.7 退出与换服 requestExit 是请求,不是本地 leave。平台按当前模式和阶段决定退出/申请解散等合法动作,等待对应响应。 换服保留既定 connection intent 和重发信封;解散换服可能仍需保留结算 scope。这里不能用统一“换服就销毁游戏”替代原协议路径。 scope 失效是立即、幂等的;任何晚到回调只能释放它自己的资源,不能发包、提交状态或挂载节点。 ## 7. 能力、错误与资源 ### 7.1 输入与模态框 数字/文本/倍率输入返回明确的 confirmed/cancelled 结果。取消原因区分用户取消、房间结束、请求被替代;真实失败则拒绝并提供原因。 每种单例输入面板明确声明冲突策略。默认设计为:同一 scope 的重叠请求拒绝,调用方显式结束前一个后再开新请求;若现有业务必须替换,则旧请求必须得到 cancelled,不能丢失回调。 不会因为 Promise 方便就把没有业务回复的发包动作包装成“等待成功”。 ### 7.2 Scope 拥有订阅、定时器、原生回调、资源租约、界面实例和未完成输入。释放顺序:停止接收/提交业务 → 取消异步输入与动画 → 释放游戏模型和 View → 释放能力资源与订阅。使用逆序登记的清理栈,异常逐项收集,不因一个 disposer 抛错而跳过其余清理。 共享头像/音频缓存不属于单个房间;房间只持有可释放租约。缓存有容量限制和明确逐出策略,不能以“缓存”名义永久持有所有资源。 ### 7.3 错误分类 **Layer612_Tips 专用于玩家业务提示。** 游戏和平台的业务拒绝、输入校验、操作结果及可操作的超时提示可以使用;代码异常、堆栈、协议/配置校验失败、资源导入/加载错误、模块未接入与调试信息只能进入诊断日志,不得直接显示 error.message。禁止为了显示技术错误而改用踢出面板;踢出面板仅处理真实业务踢出。业务提示与诊断端口必须分开命名、分别注入。 - 协议/状态不一致:阻止当前事务,显式失败,记录来源和字段。 - 业务拒绝:展示服务端约定信息、解除 pending,不终止正常连接。 - 用户取消:正常结果,不上报 fatal。 - 展示资源失败:局部能力按明确定义处理;没有 fallback 定义时报告资源错误,不能影响无关网络逻辑。 - 清理失败:继续清理其余项,保留原始异常链。 诊断包含 direction、route/rpc、连接代际、房间 generation、生命周期阶段和原始 cause。完整包日志由 debugIO 控制;对外提交的错误报告与生产日志需要独立的敏感字段处理,不复用完整调试包。 ## 8. 性能策略 优先处理已证实的结构性成本: 1. 聊天使用本地稳定消息 ID 维护行,追加不重建旧行。ID 不发送到服务器。 2. 头像按 URL 去重加载、共享纹理租约;行复用时验证当前 URL/generation,避免旧图片写到新玩家。 3. 选择器按最小相关状态通知;同帧合并的是绘制,不是业务消息。 4. 投票保留按座位复用;小座位列表不引入不必要的虚拟化。 5. 长聊天历史在测量后决定可变行高虚拟列表;上翻历史不被新消息强制滚底。 6. 倒计时按来源建立截止时间并按差值绘制;后台恢复后重算,不累计定时器减法误差。 7. 不在每个 UI 消费点递归 clone;入口隔离后结构共享。大型 deskinfo 的所有权策略应单独测量验证。 基线指标:进入/恢复至可交互耗时、主线程 p95、帧时间、长任务、节点数、纹理数、聊天追加开销、重复进退后的活跃订阅/计时器。 绝对耗时目标由目标设备测量确定。本版本不编造性能数字;结构性目标可以立即验收:一条消息不重建已有行,重复头像不重复创建纹理,100 次进入/退出后作用域资源数量回到基线。 ## 9. 回放隔离 回放使用独立 ReplayContext、玩家列表、视角、时钟和消息源。可以复用游戏模型/展示,但不能共享在线 RoomStore、认证身份或真实网络发送能力。 未声明回放能力的游戏无需实现空回调。回放是后续独立阶段,本阶段只保证 SDK 不以全局状态阻止未来隔离。 ## 10. 子游戏接入流程 1. 创建游戏定义并关联唯一配置身份与资源。 2. 实现 roomtype 编码、读取、座位规则和纯描述。 3. 将不同进入来源和游戏协议映射到自身模型输入。 4. 创建页提交自己的配置;房间 View 订阅模型,操作走绑定能力。 5. 声明需要的可选能力,不填写无意义的示例返回值。 6. 运行接入测试和 prefab 唯一性检查,再在 Cocos 中验证真实按钮→实际包。 接入验收的核心是第二款游戏加入时不修改平台核心、公共 UI 或公共协议分发器。开发者不需要了解 PlatformStartup 内部字段,也不需要手动复制 agentid/playerid/roomcode。 ## 11. 兼容与迁移策略 保留现有网络、状态缓存和会话租约,逐条迁移。每条消息在同一时刻只由一套业务处理器负责;严禁双跑旧新处理器发送重复命令或产生两份状态。 旧接口只在过渡适配层存在,并记录移除条件。新游戏不能新增 Utl 依赖。资源操作通过 Cocos MCP,主工作区已有修改不自动 stash、不覆盖、不批量恢复。 迁移检查从现有 framework/games 扩展到公共 scripts/ui,唯一豁免为明确的 app 组合根。CI 同时检查静态 import、契约测试和资源装配。 ## 12. 风险与设计取舍 接口分层增加初期代码量,但减少后续游戏接入修改面;通过少量能力分组控制抽象数量。 原流程存在不合理顺序和混合状态,不能逐行复制。保留协议与业务效果,对改变的生命周期时点明确记录兼容映射及测试。 扩大 Roomtype 的平台承载类型需要检查全部消费方,防止 UI 或存储再次假设 length/index。未知格式不得在平台描述或转为字符串显示为玩法规则。 等待队列只解决界面加载窗口,不是事件日志系统。缺少服务器序列号时不承诺精确一次业务投递;重连依赖服务器权威快照及原协议屏障。 ## 13. 完成标准 - 两种不同配置格式的游戏通过接入套件;新游戏无需修改平台核心。 - 创建、加入、登录恢复、各开战入口的来源与载荷不丢失。 - 加载中取消/踢出/换服、旧回调晚到、结算后释放均有测试。 - 公共核心无 cc/window,游戏无平台内部依赖;装配检查发现重复页面。 - 房间命令不由游戏手工拼公共身份;roomtype 不被平台解释或转换。 - 聊天与头像增量策略通过资源计数/真实交互验证。 - 文档明确未实现玩法,不以空函数或单元测试替代真实重连显示。 ## 14. 待实施阶段收敛的参数 队列容量/等待时间、缓存容量、性能目标设备和绝对耗时阈值,由 P0/P3 基线确定并写入单一配置来源。第一阶段按平台核心与契约优先安排;二七王完整玩法和回放另立范围,不隐含在此次重构中。