feat: integrate room framework and isolated subgame bundles

Complete room UI and protocol integration, move game definitions and resources behind bundle entries, publish authoritative version XML, and document single-game builds. Include all current resource changes and experiment artifacts.
This commit is contained in:
2026-09-09 03:13:18 +08:00
parent a7ec8aa6a7
commit 35216f75e7
1028 changed files with 835556 additions and 21415 deletions
@@ -0,0 +1,103 @@
# 房间平台重构实施记录
日期:2026-09-08。工作区:主 checkout;分支 `codex/web-native-settings`。本次仅修改前端,没有修改服务器、原工程或协议字段。
## 已落地的结构
### 游戏装配
唯一注册入口是 `assets/app/composition/game-definitions.ts`。游戏 key、route、工厂、聊天配置、创建页/房间组件名称及资源路径在这里关联。工厂接收装配身份,不再各自重复定义 key/route。
公共 `PlatformStartup` 依赖 `GameComposition`,不导入具体游戏或应用注册表。原 `SubgameAssets` 已通过 Cocos asset-db 移到组合根,UUID 保持 `ec19e470-cf36-40e8-b420-e4314e5fbf07`。模板与二七王房间视图共用 `CommonRoomViewBase`,二七王不再继承另一个游戏。
聊天内容与敏感词迁到对应游戏目录;SDK 只持有聊天配置契约。边界检查扫描 framework、实际 assets/games 和公共 scripts,禁止公共层依赖游戏/组合根、禁止游戏相互依赖。旧版 LoginFlow/RoomEventProbe/RoomSceneStart 明确隔离,现代代码不能导入这些原型入口。
资源检查直接读取同一注册表,再解析 TypeScript 继承关系及 meta UUID,验证组件唯一、根节点挂载、脚本可解析、资源类型和所属游戏一致。检查不会自动删除资源组件。装配校验不预执行游戏工厂,真实 open 时才创建并校验模块;100 次 Runtime 进退恰好创建、挂载、释放各 100 次。
### 协议与输入
`Roomtype = JsonValue`。平台验证可传输的有限、无循环 JSON,保留值与类型;不读取配置位、长度或推断游戏规则。非法 undefined、稀疏数组、非有限数字、访问器和非 JSON 对象在边界拒绝。保存配置读取和 VIP 入口也不再限制为 string/array。
`room.entered.entry` 保留 created、login-waiting、login-restored、joined-waiting、joined-snapshot、joined-started 来源。原始载荷和快照不因内部事件转换而丢失。
`room.started` 覆盖 self_makewar、other_makewar,以及 self_join_room / other_join_room / player_prepare 携带 deskwar 的路径。原工程 `makewar` 是处理函数名,线上 RPC 是 `other_makewar`。准备状态提交先于开战通知;deskwar 优先于 deskinfo,避免同一加入包同时执行 StartWar 和恢复。
兼容期保留现有 `restore(deskinfo)`。当前游戏只记录恢复快照,不宣称已有二七王牌桌恢复显示。
### 房间生命周期
`RoomCoordinator`、`RoomScope`、`RoomMailbox` 管理异步挂载、generation、排队和清理。实际 Runtime 已接入,不是独立未使用的工具类。
房间业务包在挂载期间排队;就绪后按整包执行 Router、Store 提交及游戏回调。同一个包的游戏回调在该事务内执行,因此第一条 player_prepare 的处理器不会读到第二条包提交后的状态。游戏路由采用相同顺序。登录/入房替换、踢出和换服等控制消息按原协议屏障处理,不被慢场景阻塞。
挂载、UI 绑定完成后才投递进入/恢复事件。退出或进入失败先撤销现有 Host 租约,再释放所属 UI、队列、订阅和计时器。旧 Promise 晚到不能绑定新房间;多项清理继续执行,保留原始错误。同步异常仍由调用链报告,异步失败走统一 fatal 诊断。
普通换服与解散换服分开:后者仍保留服务端结算所需的房间上下文。没有实现的游戏结算继续显式报错,不用空处理器冒充接入。
队列预算单独声明于 `framework/config/room-lifecycle.ts`:256 项、等待 30000 ms。这是可调整的客户端运行预算,不是测得的设备性能阈值。
### SDK 能力与事实投影
生产 Host 提供 `room / messages / ui / scope` 分组能力。准备与退出复用平台身份填充和命令策略;两个游戏工厂已迁移到这些能力。旧 Host 方法仍作为兼容表面存在,`requireRoomCapabilities` 在缺少新端口时显式报错,不提供伪默认实现。
数字、数字文本、倍率请求返回 confirmed/cancelled。取消原因是 user、scope-ended、replaced。同类重叠请求拒绝;离房、替换和用户关闭都有确定结果;迟到确认不能继续回调。数字工具的等待绑定机制受同一租约清理。
loading 与业务 tips 已接入真实 FeedbackPanels 的所有者句柄。一个来源结束不会关闭其他 loading,也不会关闭更新的业务提示。`Layer612_Tips` 仍只显示面向玩家的业务内容,程序错误走诊断。
游戏投影由游戏模型拥有,消费者只读;完整版本含 revision、roundPhase、selfParticipation、scoresBySeat、restrictions。投影不修改平台玩家、准备状态或资产。命令发送及公共按钮都消费 deny-only 限制,游戏不能放开平台已经禁止的操作。当前两款游戏没有已迁移的局内模型,明确返回 null;不从平台 stage 猜出局内事实。
**音频所有者端口未发布**:现有 RoomVoiceAdapter 的原生契约没有播放停止接口,不能承诺“释放句柄即可停止原生音频”。原有语音适配保持工作,未修改原生接口,也未构造空 audio 能力。
### 展示与资源
Store 为聊天消息分配客户端本地稳定 ID,不改变收发包。RoomChatPanel 按 ID 增量维护历史行;公告也保留已有节点。同帧状态变化合并为一次历史刷新。用户上翻历史时保留滚动位置,位于底部时继续跟随新消息。
RoomUi 通过共享头像租约缓存去重请求和 SpriteFrame/Texture2D。URL 替换和节点释放撤销旧所有者,迟到加载不能覆盖新头像。空闲缓存上限 32 项;活动引用不因缓存淘汰被销毁。资源关闭逐项执行,异常不会阻止其余清理。
投票倒计时仍依据 deadline 与 Date.now 计算,后台恢复后重新计算剩余时间;计时器不决定服务端结算。当前数据规模未提供可变行高虚拟列表的必要证据,因此没有引入虚拟化。
## 验证与性能证据
- 实施前相关基线:137 项通过。已有未提交修改 131 项、序列化资产哈希 47 项已记录于 `out/room-platform-refactor/`。
- 最终完整 Node 测试 **950/950 通过**;framework 与 Cocos 两套 TypeScript 检查通过;导入边界和两游戏资源装配检查通过。
- 通过 Cocos builder 生成独立 `build/web-mobile-room-refactor`,任务 `1788869153827` 成功(19 s)。产物启动场景为 PlatformStartup,主脚本包含新的 GameComposition、房间队列和生命周期代码;工程保持 `script.loose=false`。构建成功不代表已经完成真实账号整局联调。
- 真实 Runtime 测试覆盖创建/加入/登录恢复、开战、投票/结算边界、退出、慢挂载、踢出、断线、换服、队列超限、清理失败、整包状态顺序和 100 次进退。
- Cocos Creator 3.8.8 / Windows / Electron 31.3.1 的 Game View:二七王实际提交按钮的 32 种组合均输出 11 位字符串;模板提交自己的数组。
- 实际聊天预览:50 行追加到 51 行,原 50 个 Node 均保留;5 次同帧更新产生 1 次历史刷新;上翻位置保持。Cocos 有 3 次 Layout 检查、2 次实际布局,不能把“1 次历史刷新”写成“引擎只布局 1 次”。测量包含人工等待帧的 82 ms,不作为渲染耗时指标。
- 真实头像测试:相同 URL 的两个节点共用同一个 SpriteFrame;100 次 Cocos RoomScope + 房间聊天 UI 挂载/释放后,Canvas 节点数 8 → 8,活动头像租约为 0,保留 1 个有界空闲缓存资源。
- 预览复现脚本:`scripts/verify-room-refactor-preview.mjs`、`scripts/verify-room-resource-cycles.mjs`;原始结果:`out/room-platform-refactor/preview-result.json`、`resource-cycles.json`。测试停止真实 socket,使用本地夹具,没有向真实账号创建房间或扣卡。
这里只报告当前 Windows/Cocos 测量。未据此承诺移动设备帧率、内存上限或线上吞吐量。
## 尚未覆盖的业务与验收
- 二七王完整出牌、牌桌 deskinfo 恢复渲染、结算界面、回放、未支持的 VIP/百人场不属于本次平台核心接入成果。
- 没有进行真实服务器账号的创建/开战/重连整局联调,也没有执行 Android/iOS 原生设备验收。
- `room.entered.entry` 与 Host 旧方法保留兼容期。新接入使用来源事件和分组能力;后续迁移全部旧调用方后再移除兼容表面。
- 游戏投影为 null 的当前游戏不能展示未实现的局内数据;接入模型时由该游戏提供源,公共命令和视图已经消费该源。
## 工作区保护
没有提交、合并、stash 或服务器改动。没有手改 prefab、scene、anim、meta。原装配脚本通过编辑器移动并保留 UUID;旧公共敏感词模块通过编辑器删除,由对应游戏文件接管。
资产基线对比中,已有场景/prefab/动画等哈希保持;两处原路径不存在:迁出的公共敏感词模块 meta,以及编辑器 refresh 清理的 `assets/.meta` 孤立文件。详细差异保留在 `baseline-asset-differences.json`,不以批量 restore 覆盖工作区。
## 2026-09-08 补充:子游戏身份与导出选择边界
接入、切换子游戏不得修改 framework 代码或资源。框架只消费应用注入的完整身份和 GameDefinition,不内置具体 gameid。
- 各子游戏的 gameid 唯一来源为 `assets/games/<game>/game-config.ts`。
- 应用注册表 `assets/app/composition/game-definitions.ts` 引用对应配置。
- 当前游戏唯一选择项是启动场景应用组件 `SubgameAssets.gameKey`;当前为 `erqiwang`。在 Cocos 编辑器配置该组件并绑定对应 createRoom、room、roomScene 资源。禁止手改场景或 meta。
- 应用层 `assets/app/composition/build-identity.ts` 保存渠道默认值,组合所选游戏身份;`PlatformStartup` 经 `GameComposition` 契约注入 `resolveRuntimeConfig`。
- H5 保持 agentid/channelid/marketid/version 的 URL 覆盖。gameid 不接受 URL 覆盖。原生仍从原接口读取渠道,仅 gameid/version 来自注入配置;没有原生渠道时显式报错,不回退到 H5 渠道默认值。
- 缺少身份或游戏配置直接报错;不猜测默认游戏。旧 bootstrap 入口也必须显式注入身份。
- 二七王保留当前联调注册 ID。template ID 来自原工程 Game_Surface_3/version.js,其在当前开发服务器的注册状态未验证。
本次落实选择与身份注入,未新增按游戏裁剪构建产物的工具。当前注册表仍引用两个游戏;实际导出包是否排除未选游戏代码和资源,需要在后续构建隔离流程中单独落实与检查,不能以运行时只选一个游戏作为裁剪证明。
验证:953/953 自动测试通过;框架与 Cocos 严格类型检查通过;import boundaries、两套房间资源检查和 diff whitespace 检查通过。Cocos MCP 已导入三个新增脚本并生成 meta;重启 Game View 后,PlatformLogin 中 SubgameAssets.gameKey=erqiwang,注入身份与 PlatformStartup 实际解析身份的 gameid 一致,均为当前二七王注册值。未发送创建房间请求,未验证 template 当前服务器注册。
## 后续修订:version.xml 单一来源
以上“game-config.ts 保存 gameid、build-identity.ts 保存渠道和版本”的阶段方案已被 `2026-09-08-version-xml.md` 替代。当前身份与版本唯一来源是各游戏 version.xml;game-config.ts 只定位资源,build-identity.ts 仅保留 XML 没有的网页 marketid。XML value 映射 versionCode(登录协议数值 version),XML name 映射展示 version。此前将原生更新版本与协议版本分别配置的建议不适用,以用户最终确认的映射为准。
@@ -0,0 +1,47 @@
# 子游戏 version.xml:网页预览与原生发布共用
## 日常使用
1. 在 Cocos 启动场景的应用组件 `SubgameAssets` 中选择 `gameKey`,并绑定对应游戏页面和房间资源。当前为 `erqiwang`。
2. 编辑该游戏唯一的 `assets/games/<目录>/resources/<gameKey>/version.xml`。
3. 网页预览直接读取该文件。Web Desktop / Web Mobile 正常构建后,扩展自动将原文件放在输出根目录,与 `index.html` 同级,不需要手动复制。
当前文件:
- 二七王:`assets/games/erqiwang/resources/erqiwang/version.xml`
- 模板游戏:`assets/games/template/resources/Game_Surface_3/version.xml`
## XML 契约
保留原生既有 XML 声明、game 根节点及 agent/game/channel/version 节点,不新增 market 字段。
- `game/agent@id`、`game/channel@id`:网页身份。
- `game/game@id`:所选游戏 gameid。
- `game/version@value`:非负安全整数 versionCode,转换为数值后发送至 `player_login.data.version`。
- `game/version@name`:字符串展示版本 version,例如 `1.8`;登录页显示 `v1.8`,不由 versionCode 拼造。
- 各 name 属性保留供原生读取。
网页 marketid 唯一配置位于应用层 `app/composition/build-identity.ts` 的 `WEB_MARKET_ID`,当前为 4。XML 不包含 marketid,因此不重复维护该字段。现有显式 URL 渠道/版本调试覆盖仍遵循通用启动解析器规则;日常预览无需 URL 身份参数,直接使用 XML。
原生环境的 agentid、channelid、marketid 仍通过既有 settings/uAgent_3 接口读取,缺失时报错,不回退到网页身份。游戏与版本信息来自 XML。未更改服务器、原生接口名或登录协议。
当前 XML 保留迁移前的联调身份与数值版本 1;展示版本采用原游戏 XML 的名称(二七王 1.8、模板 1.1)。旧 XML 的 versionCode 不覆盖当前联调版本;正式发布需在这一份 XML 中配置部署要求的实际版本和身份。
## 接入新游戏
`game-config.ts` 仅指定 `versionResource: '<gameKey>/version'`,不再保存身份和版本。应用注册表引用该配置。
在编辑器中把游戏的 resources 文件夹配置为 Asset Bundle:名称必须是 `version-<gameKey>`。运行时只加载所选版本包,读取 `<gameKey>/version` TextAsset,解析后释放该文本资源。使用编辑器操作资源及 meta,禁止手改序列化文件。
构建扩展 `extensions/youle-version-manifest` 在当前编辑器已启用;新环境需要在项目扩展管理器中启用。它从构建任务真正的启动场景读取游戏选择,检查唯一 XML、合法版本、对应 bundle 元数据和构建结果的 bundle 列表。构建中修改 XML、启动场景或相关元数据会使构建失败,避免生成身份不一致的包。
框架只消费注入身份,不包含具体游戏配置。新增、选择子游戏不需要修改框架代码或资源。本次未实现排除所有未选游戏代码和资源的完整构建裁剪。
## 验证记录
- 956 项框架测试、22 项构建扩展测试通过;框架/Cocos 严格类型检查、import boundaries、两款游戏房间资源检查通过。
- 实际 Cocos Game View:二七王解析 gameid 为当前联调值、versionCode=1、version=1.8;登录页显示 v1.8。创建独立测试组件选择模板后读取到模板 gameid、versionCode=1、version=1.1,未发送业务请求。
- 同时修复现有场景切换清理问题:隐藏已销毁的加载节点时不再访问其子节点;实际预览确认销毁后 dispose 成功,有效但缺失必要组件的节点仍明确报错。
- 最终真实 Web Mobile 构建任务 1788872259776 成功,用时 28 秒,输出目录 `build/web-mobile-version-xml`。构建日志确认扩展在 onAfterBuild 输出根目录 version.xml。
- 源 XML 与导出根目录 XML 的 SHA-256 均为 `6871c9a71f7363261c85de52bd72d1d9fc3cc7dfbdf0c1ef949b11367cb657f7`。
- Web Desktop 已注册同一 hook 并通过注册测试,尚未另跑真实构建;真实原生壳读取未执行。