Files
youle_cocos/docs/superpowers/specs/2026-09-05-platform-ui-wiring-design.md
T

111 lines
10 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.
# 公共 UI 接线设计:启动 → 登录 → 大厅 → 进房
日期:2026-09-05。基线:`master@8eae325`。
## 1. 目标与实施边界
这是总设计 `2026-09-04-framework-subgame-zero-coupling-migration-design.md` 第三阶段的第一个子项目:把已完成的 PlatformRuntime 接到可测试的公共 UI Presenter。先交付不依赖 Cocos 的接线核心,再进行编辑器接管。
本轮产物为设计与实施计划,不运行实施任务。下一实施批次称为 **3A:UI 接线核心**,包括 Runtime 只读观察、ViewModel 投影、命令入口、同步 ScenePort 适配与真实 Runtime 回放测试。
**3B:Cocos 实际接管**单独实施:需要编辑器确认节点、挂载与持久化,且须先取得修改指定序列化资源的明确授权。用户现有“不修改任何 `.scene/.prefab/.anim/.meta`”限制继续有效,不能因为有 MCP 就绕过。3A 不启动编辑器导入,不写这些文件,也不声称实际界面已经可用。
主题扩展、SemanticAssetKey/ExtensionSlot 完整实现、创房规则页、聊天/商城/设置、首个真实子游戏均不在 3A 内。现有主题模块不复制、不重构。
## 2. 已核对的现状
- `assets/framework/platform/runtime.ts` 已拥有唯一 Store、WireClient、Router、GameSessionHost,公开 `state`、`ready`、登录/进房/准备/退出命令,但没有 UI 订阅入口。
- Store 为不可变快照,订阅不立即回调;其通用监听异常处理只记录日志。UI 需要显式错误通道,不能让渲染故障只留日志。
- `ScenePort` 是同步接口。当前房间时序为 module attach、`room.entered`、`showRoom()`、可选 `restore(deskinfo)`。不得将 `showRoom()` 实现为未等待的异步场景切换。
- 当前 `assets/scenes` 只有 Login 场景;旧 `LoginFlow.ts` 跳转 MainMenu,且自行建立旧网络栈、内置调试账号。`LaunchFlow.ts`、`SceneStart.ts`、`RoomEventProbe.ts` 也包含演示流程,不是新 Runtime 入口。
- 已迁移 Login_Layer、Layer4_MainMenu、Layer15_JoinRoom、Layer50_MainScene、Layer202_PlayerHeadScore 及 loading/reconnect/kick 等 Prefab。存在资源不等于已确认按钮语义或绑定。
- 两个旧 PlayerInfoView 使用相同 ccclass 名,且含下游默认值。新核心不依赖它们;是否阻碍实际编辑器接管由 3B 预检决定,不能随手修改旧资源引用。
- 旧 `net/cocos-transport.ts` 含可选发送、字符串强转和吞异常,不能不经契约测试就作为现代生产适配器。
以上路径均相对 `cocoscreator_projects/YouleNexus`。已废止的 `2026-09-04-platform-vertical-slice.md` 不得执行。
## 3. 方案选择
选择单场景常驻根、页面预加载、同步页面激活。3A 用严格 ViewPort 验证该语义,不创建 Cocos 节点;3B 用组件引用实现同一端口。
另外两个选项不采用:
- 多场景异步导航需要修改 Runtime/Host 的进房恢复时序,扩大已验证生命周期契约的变更面。
- 直接给旧 LoginFlow/RoomSceneStart 接新网络对象会保留双 Store/双网络栈及下游默认值,无法满足唯一来源。
3B 的所有基础页面、公共座位容器和必需绑定须在 `loadResources()` 完成前准备好。加载画面本身由启动根预先具备,不依赖这次异步加载。`showRoom()` 只投影最新快照、同步渲染并激活已就绪页面;不加载资源、不发包、不初始化第二个 GameSessionHost。
## 4. 核心接口与所有权
新增 `framework/presentation/`,允许依赖 platform 的只读类型、现有 selector 与 ScenePort;不导出到 SDK,不允许子游戏反向导入。View 不得到 Store、WireClient、Router 或协议包构造器。
Runtime 增加:
```ts
subscribeState(
listener: (state: PlatformState) => void,
onError: (error: unknown) => void,
): () => void;
```
订阅不立即通知;通知交付 Store 提交的原快照,不复制、修补或另建响应式状态。调用者订阅后同步读取 `state` 获取初值。取消幂等;stop/fatal 清理观察者;终止后新订阅显式拒绝。某监听器抛错时先取消该监听器,再将原错误交给必需 onError,其他监听器仍可接收。onError 也是可抛的外部回调:复用现有清理/错误聚合路径,原错误不可丢失;不得修改通用 Store 的订阅语义来满足 UI。
Presenter 接口:
```ts
type UiRuntime = Pick<PlatformRuntime,
'state' | 'ready' | 'subscribeState' | 'login' | 'joinRoom' | 'prepare' | 'exitRoom'>;
interface FrameScheduler { request(callback: () => void): () => void; }
interface PlatformViewPort {
render(model: PageModel): void;
activate(page: PageId): void;
showOverlay(model: OverlayModel): void;
}
```
FrameScheduler 必须异步、至多调用一次;返回取消函数。测试用手动帧,3B 才使用 Creator 更新周期。Presenter 拥有订阅与帧取消;外层应用拥有 Runtime.stop。清理与错误处理不得相互递归复活。
`PlatformUiController` 实现 ScenePort,构造参数为 ViewPort、FrameScheduler、必需 `onFault(error: unknown): void`,随后 `connect(runtime: UiRuntime): void` 一次。外层顺序:构造 controller → 用 controller 作为 ScenePort 构造 Runtime → connect → Runtime.start。controller 不调用 Runtime.start/stop、不选择 GameEntry/渠道/账号来源。
## 5. ViewModel 与页面状态
`PageId = 'loading' | 'login' | 'lobby' | 'room'`。PageModel 是对应判别联合:loading 无业务字段;login 携带 canLogin;lobby 携带必需 self;room 携带 roomcode、selfSeat、动态 seats、canPrepare、canExit。玩家公共显示字段仅 playerid/nickname/avatar/bean。
SeatModel 为 `empty` 或 `occupied`,都携带服务器 seat 索引,occupied 额外携带 player、ready、offline。数组长度严格等于权威 `seatPlayerIds.length`;不固定为 4/8,不压缩空位,不解析 roomtype 推测布局。2/4/10 座位都须验证。
已登录页缺少 self 或 occupied 缺实体必须报错;空座位是来源明确的 null 语义,不是伪造匿名玩家。头像 URL 原样交给 View,资源失败的视觉策略不在核心中猜测。
canPrepare 在 room.needprepare 为 1、自身未准备、stage 为 0 且连接 phase 为 logged-in 时为真;canExit 在 stage 为 0 或 infinite 为 1,且 phase 为 logged-in 时为真。这是本批 UI 的可用性策略,不是新增服务器规则。Runtime/Commands 仍为命令合法性的最终执行入口。
页面只由 ScenePort 决定,不能同时通过 app.phase 推断第二套导航。快照通知只触发当前页面更新。页面切换取消待处理旧帧、同步生成最新模型并 render,然后 activate;render 抛错不得激活半成品页面。room 首帧必须在 showRoom 返回前完成,保证既有 restore 时序。
同一帧多次状态变化只渲染最新值。dispose、fatal、kick 后旧帧/旧订阅无效。生命周期检查必须覆盖 View 回调重入 dispose,不能仅在入口检查一次。
## 6. 操作、拒绝与错误
controller 暴露 `login(account: LoginAccountIdentity)`、`joinRoom(command: JoinRoomCommand)`、`prepare()`、`exitRoom()`、`dismissDenial()` 与 `dispose()`,均返回 void。
- 登录只在 login 页且 runtime.ready 时转发;大厅进房只在 lobby 页;prepare/exit 只在 room 且模型对应能力为真时转发。界面本地误用显式抛出,不偷偷缓存请求。
- account/JoinRoomCommand 原引用转发;不填 mock_openid、ip、location、gameid,不裁剪/重写 roomcode,不擅加正则或长度协议。账号/设备/进房环境由外层权威适配器完整供给。
- 本批不新增超时、重连策略或通用请求 pending 状态机;网络策略沿用 Runtime。按钮节流/账号异步获取另有来源契约后再实施。
- OverlayModel 为 none/reconnect/denial/kicked/fatal 判别联合。denial 保存完整 ServerDenial 引用,包括原 data;不把合法非零结果升级 fatal,不修改 Store。dismissDenial 仅清除当前 denial;登录/进房成功页面切换清除非终止 overlay。
- 重连保留当前页面。Store phase 从 reconnecting/slow 恢复 connected/logged-in 时只清 reconnect overlay,不清 denial,不自行跳大厅。
- kicked 保存原 data,fatal 保存原 Error。两者终止交互并取消观察/帧,但保留底图用于展示;fatal 可覆盖 kicked,其他回调不得覆盖终止画面。
- 同步 ScenePort/View 异常向 Runtime 原样传播;帧/订阅异步异常必须取消更新并调用 onFault。外层应展示 fatal 并停止 Runtime;两项操作均须尝试,清理异常聚合保留。3A 不把错误转换为默认模型。
## 7. 外部兼容与后续实际接管
服务器包、route/rpc、roomtype 与 deskinfo 全部沿用已验证 contracts。不得为 UI 新增字段。远程配置与原生读取/WVJB 不在本批修改;后续适配前须完整读 native-bridge-contract,并用原工程核实精确契约,不能根据摘要重新发明接口。已核定远程配置 POST 空 body、URL 拼接、gameconfig 等行为保持现状。
3A 测试夹具只留在 framework-tests,不导入 assets,不声明生产 GameEntry。3B 启动前须明确真实 GameEntry 与 config.identity.gameid 一致、账号来源及目标宿主。尚未选定真实游戏不能用夹具顶替生产入口。
3B 单独批准后需完成:MCP 枚举 Login 活跃组件及 Prefab 控件语义;形成唯一绑定清单;移除活跃路径上的旧模拟驱动;以一个应用根持有新 Runtime;严格 WebSocket adapter 契约测试;保存并重开确认挂载;浏览器回放截图与真实宿主验收分别记录。没有这些证据,3A 完成不能称为“UI 接管完成”。
## 8. 验收
3A 必须通过:观察生命周期、2/4/10 座位、同帧合并/切页首帧、命令原样转发、非零拒绝可重试、重连 overlay 恢复、kick/fatal 终止、回调重入销毁、真实 PlatformRuntime 登录/进房/deskinfo 恢复顺序回放、现有非 UI 回归、类型与架构检查。
代码边界测试禁止 presentation 导入 cc/net/protocol 实现、旧 PlatformSession/RoomRPCBus,以及 SDK 导出 presentation。允许 type-only 引用现有 LoginAccountIdentity、JoinRoomCommand、ServerDenial;协议 parser/builder 不进入 Presenter。
任何 `.scene/.prefab/.anim/.meta` 变更均使 3A 验收失败。本计划完成后交付测试证据与明确 3B 门槛,不自动启动编辑器、选择真实游戏或删除旧资源。