Files
youle_cocos/docs/superpowers/specs/2026-08-30-legacy-platform-logic-migration.md
T
joywayerandClaude Opus 5 02dc5d51ca chore(spec): 综合清理 + legacy-layer 迁移 spec/plan/data
主要改动:
- 切到 funplay-cocos-mcp v0.5.1 (用户级配置, 项目级 .mcp.json 删除)
- 仓库文档/CLAUDE.md/.gitignore 等清理过时 cocos-mcp-server 引用
- memory 文件同步: cocos-mcp-setup/path/blocker/spriteframe-uuid/prefab-persist 等加 funplay 实测警告
- memory 新建 funplay-cocos-mcp-pending-verification.md (后已被实测覆盖)
- spec/plan/data:
  - docs/superpowers/specs/2026-09-02-legacy-layer-migration-design.md
  - docs/superpowers/plans/2026-09-02-legacy-layer-migration.md
  - docs/superpowers/data/layer-spirit-summary.json
- YouleNexus: profiles.ts / defaults.ts / PlayerInfoView.prefab / scene 改动

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 07:36:54 +08:00

277 lines
23 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.
# 旧 gameabc 平台逻辑 → Cocos TypeScript 移植(子系统 C·平台逻辑移植)
> 状态:已逆向、待与用户确认。
> 适用工程:`cocoscreator_projects/YouleNexus`(框架真源)。
> 数据源:`projects/Game_Surface_3/js/00_Surface/`(12 文件)、`js/gamemain.js`、`js/gameabc.min.js`(均逐行逆向,行号见各节引用)。
> 关联:本文是 A §0.1 定义的**第三个子系统**,依赖 **A(`2026-08-28-legacy-ui-migration-design.md`)** 与 **B(`2026-08-30-legacy-engine-api-mapping.md`)**;目标分层落到 **框架 spec(`2026-06-28-cocos-framework-design.md`)** 的 §3/§4。
> 范围:旧工程的**逻辑流程与逻辑架构**——四层结构、数据流向、状态归属、核心流程、平台层对引擎 API 的依赖清单——到 Cocos 的移植。**不含**单条 API 的属性号语义(B §3)与渲染等价物(B §5),本文只引用。
---
## 0. 最高原则
承接 A/B 的两条原则(**原理等价**、**表现等效原理自由**),本文新增第三条,专门约束「逻辑流程一致性」:
> **原则三 · 复刻业务逻辑,不复刻引擎机制**:旧引擎的主循环、双缓冲、帧内事件派发、层叠渲染、对象注册表等,都是「无 Cocos 时手工搭的引擎」——Cocos 引擎**自带等价物**,一律交给 Cocos 运行时,**不得在 TypeScript 里重写一遍渲染循环 / 命中检测 / 定时器调度**。要复刻的是**业务逻辑流程**:启动门控、登录/进房/重连状态机、收发包路由、状态归属边界、界面切换时机——这些是协议契约与业务正确性的承重墙,必须逐条对齐。
判定「该复刻还是该交给引擎」的判据:**看它属不属于协议契约 / 业务状态机**。是 → 复刻;只是旧引擎的实现手段 → 交给 Cocos。
---
## 1. 四层架构 → Cocos 框架分层
旧前端是**四层**(自底向上);目标框架 spec §3 已定义 **6 层**(`core/net/protocol/platform/ui/sdk`)。两者不是 1:1,映射如下:
| 旧层 | 旧文件 | 在 Cocos 的落点 | 说明 |
|---|---|---|---|
| **引擎层** | `js/gameabc.min.js` + Spine + Canvas | **无对应**,由 Cocos 引擎接管 | 主循环/渲染/命中/定时器/资源/对象注册全是引擎机制(§2) |
| **桥接层** | `js/gamemain.js`(306 行) | `core/EventBus` + `sdk/` 回调钩子分发 | `gameabc_face` 全局回调表 → 事件总线;三方扇出 → `IGameModule` 钩子(§1.1) |
| **平台层** | `js/00_Surface/*`(12 文件) | `net` + `protocol` + `platform` + `ui` 四层 | 见 §3 逐文件映射 |
| **子游戏层** | `js/01_SubGame/*`(3 文件) | `sdk/`(子游戏实现 `IGameModule`) | 对局态 + `serialize()/restore(deskinfo)`(框架 spec §3 零耦合规则 3) |
> 关键澄清:旧「桥接层」`gamemain.js` **没有业务逻辑**,只是「把引擎回调扇出到三个命名空间」的接线盒。Cocos 里它**消失**了——引擎回调变成 Cocos 事件,扇出变成 `IGameModule` 的一组具名钩子(`onPlayerJoin`/`onReady`/`onDissolve`/`onOffline`…)。不要为它单独建一个「gamemain 模块」。
### 1.1 桥接层 `gameabc_face` → 事件分发
旧 `gamemain.js` 的机制是「引擎反射调用 `gameabc_face.<事件名>` 具名函数,函数内部按固定三方扇出」。Cocos 对应为**两种机制**,各管一半:
| 旧引擎回调(`gameabc_face.*`) | 旧扇出目标 | Cocos 对应 |
|---|---|---|
| `gamestart` | 仅 `Logic.AppStart()` | 引擎 `onLoad`/启动时序 → `platform` 启动编排 |
| `onloadurl`(资源加载进度) | `GameUI.onloadurl` | Cocos 资源系统 `Resources.load` 的 `onProgress`,**无需逐张计数** |
| `ontimer` / `ontimer_<objid>`(每精灵定时器) | 仅 `GameUI.utlontimer` | `component.schedule`(B §3 cpid=57 已映射) |
| `mousedown`/`mousedown_nomove`/`mouseup`/`mousemove` | `GameUI` + `Game_Modify` + `gameCombat` 三方 | Cocos 节点 `Button`/`EventTouch` 事件 + `IGameModule` 钩子 |
| `ani_doend`/`box_doend`(动画结束) | `GameUI` + `gameCombat` | `Animation`/`Tween` 完成回调 |
| `gamemydraw`/`gamemydrawbegin`/`gamebegindraw`/`gameenddraw` | 三方 | **无对应**(渲染循环交给引擎,业务「帧同步」需求另议) |
| `onresize` / `chongzhi` / `tcp*` / `httpmessage` | 空桩 | 不迁移(空实现) |
> 「初始化 / 定时器」旧只走平台层;「触屏 / 动画 / 绘制」旧三方扇出。Cocos 里前者是 `platform` 内部事务,后者是 `IGameModule` 的具名钩子(子游戏按 spid 过滤是否处理)——这一「按 spid 过滤」的模式在 Cocos 里**不再需要**,因为钩子是类型化的、子游戏只收自己关心的。
---
## 2. 引擎运行时机制:引擎自带 vs 业务复刻
任务逆向出的引擎运行时(33fps 主循环、backBuffer 双缓冲、帧内事件派发、层管理、`sp+id` 对象注册表、100 并发资源加载),逐条判定归属:
| 旧引擎机制 | 源码位置 | 判定 | Cocos 等价物 |
|---|---|---|---|
| 33fps `requestAnimationFrame` 主循环(`FPS=33` 硬编码,忽略数据 `fps=30`) | `tick()` `:1954`、`checkrun()` `:1858` | **引擎自带,不复刻** | Cocos 渲染循环(`director`) |
| backBuffer 双缓冲 + 脏标记 blit | `drawone()` `:1874`、`draw()` | **引擎自带,不复刻** | Cocos 场景图局部重绘 |
| 帧内事件派发(DOM 只记 `g_mouse`,回调在 `draw()` 里派发) | `gameabc_check_click` `:4095`、`check_click1` `:4068` | **引擎自带,不复刻** | Cocos 事件系统(`EventTouch`) |
| 每精灵定时器 `ontime`/`click_time` | `drawone` `:1878-1900` | 引擎自带 | `component.schedule`(B §3 cpid=57) |
| 层管理 `LayerList`/`showorder`/`set_level`/`set_level_up` | `:3102`/`:3114` | **引擎自带,且平台层从未直接调用**(§6.5) | Cocos 节点树 + `node.active` |
| `sp+id` 对象注册表 / `levelobj` 反向引用 | — | 引擎自带 | Cocos 节点树 + `manifest.json`(A §1.3) |
| 100 张并发 + 10ms 节流资源加载 | `load_img1` `:4454`/`load_img2` `:4597` | 引擎自带 | Cocos `Resources`/`Asset Bundle` |
**结论**:上表**没有一条需要复刻**。引擎层的价值在「协议无关的渲染/输入/资源」,Cocos 引擎整体接管;真正要搬到 TypeScript 的是 §3–§6 的平台层业务。
---
## 3. 平台层模块 → framework 分层落点
平台层 12 个文件,逐个落到框架 6 层(框架 spec §3 的映射表已给粗对应,本文精确到「文件 → 模块」):
| 旧文件(行数) | 职责 | framework 层 | 落点模块(示意) |
|---|---|---|---|
| `02_Const.js`(822) | 协议常量 + 静态配置 | `protocol` + `core` | `protocol/routes.ts`、`protocol/rpc.ts`(唯一来源);UI 常量 → `ui/theme` |
| `00_minhttp.js`(656) | WebSocket/XHR 封装 + 工具 | `net` + `core` | `net/NetClient` + `core/utils`(`min_*` 40+ 工具) |
| `09_Net.js`(897) | 收发信封 + 路由表 | `protocol` + `net` | `protocol/platform-rpc.ts`(`send_*`/`handle_*`) |
| `04_Data.js`(584) | 全局态 `GameData` | `platform` | `platform/stores/app-store.ts`(`AppStore`) |
| `06_Player.js`(575) | 玩家结构 `Player`/`C_Player` | `platform` | `platform/stores/player-store.ts`(`PlayerStore`) |
| `07_Desk.js`(1427) | 房间状态机 `Desk` | `platform` | `platform/stores/room-store.ts`(`RoomStore`) |
| `12_Logic.js`(2444) | 连接/重连/分发编排 | `platform` + `net` | `platform/session.ts`(登录/重连编排)+ `net/NetClient` |
| `11_GameUI.js`(9862) | 平台全部 UI | `ui` | `ui/` 下 Prefab + 各界面控制器 |
| `08_Utl_Output.js`(1034) | 平台对子游戏的输出/工具 API | `sdk`(facade) | `sdk/GameContext`(`getMyInfo`/`getRoomcode`/`getPlayerList`…) |
| `05_Func.js`(4185) | 原生/系统能力封装 | `platform` + `sdk` | 原生桥接归 `sdk`;纯工具归 `core/utils` |
| `03_Banwords.js` | 违禁词 | `core` | `core/utils` |
| `10_Game.js` | (零散 `set_self/get_self`) | `ui` | 并入对应界面控制器 |
### 3.1 三个状态机的职责与唯一归属(框架 spec §3 零耦合规则 3 的细化)
| 状态机 | 旧归属 | framework Store | 唯一职责 |
|---|---|---|---|
| `GameData` | 连接/配置/资产/面板态 | `AppStore` | `Server/AjaxUrl/AgentId/ChannelId/GameId`、`hallConfig/sysConfig`、各类列表 |
| `Desk` | 房间/座位/局状态 | `RoomStore` | `PlayerList[]`、`roomcode/stage/state/roomtype/warcnt`、解散投票 `AgreeList` |
| `C_Player` | 自己的状态 | `PlayerStore` | `playerid/bean/roomcard/seat/status/isprepare/state` |
> 旧 `C_Player` 与 `Desk.PlayerList[C_Player.seat]` 是**同一玩家的两份镜像**,靠 `Player.SetDeskInfo/getDeskInfo` 手工同步(`06_Player.js:177-180` 改 bean 时显式回写 `Desk.PlayerList[seat].bean`)。Cocos 里这是**单一来源原则的反例**:`PlayerStore` 应持有「座位 → 玩家」的**唯一**响应式映射,自己的座位只是其中一个下标;消除双份镜像的手工同步。
---
## 4. 数据流向与调用方向(谁调谁、谁改谁)
```
旧(四层 + 双向手工刷新) Cocos(单向依赖 + 响应式)
────────────────────────────── ──────────────────────────────
引擎回调 ─► gamemain 扇出 ─► GameUI/Logic/子游戏 事件/钩子 ─► 单一 handler
收包 onmessage ─► Net.<rpc> 薄转发 ─► Desk/C_Player 收包 ─► Router ─► Store 更新 ─► UI 响应式刷新
└► 改状态后 手工调 GameUI.* 刷界面 └► (无手工刷 UI,Store 变化自动驱动)
```
**关键差异(移植时必须理解的)**:
- 旧是「改状态 → 手工调 `GameUI.xxx()` 刷新」。Cocos 是「Store 响应式 → UI 自动刷新」,**消灭 `Desk`/`C_Player` 方法里那批 `GameUI.setHallRoomCard`/`setHallStar` 手工回调**。
- 旧 `Net.<rpc>` 收包函数几乎全是「薄转发 + 菊花」(`Desk.<rpc>(_msg)` 或 `C_Player.<xxx>` 或 `gameCombat.<xxx>`,夹 `GameUI.StartLoad()/EndLoad()`)。Cocos 里「薄转发」退化为 `protocol` 层的 typed handler → 直接调 Store 方法,「菊花」变成全局 loading 态。
### 4.1 收包路由分界(协议契约,必须逐字对齐)
`12_Logic.js:258-264` 的分发是**平台/子游戏的分水岭**:
```
route ∈ { platform, agent, room } ─► 平台层 Net.<rpc>(protocol/platform-rpc)
route = <game route> ─► 子游戏 Game_Modify._ReceiveData(sdk → IGameModule.onReceive)
```
框架 spec §4 已把这条分界固化为 `Router`,本文只强调:**这个三分支的 route 判定是协议红线**,`platform/agent/room` 之外的任何 route 都不得被平台层拦截或解析。
### 4.2 发包信封(协议契约,逐字节对齐)
`Net._SendData`(`09_Net.js:12`)组装 `{app:"youle", route, rpc, data}`;`netType==0` 走 WebSocket,`netType==1` 走 `Func.AjaxHttp2`(HTTP 兜底,`player_login` 特判 `putMsg="playerLogin"`)。Cocos 侧 `net/NetClient` + `protocol` 编码必须保持信封结构与两条传输路径(含 HTTP 兜底),见框架 spec §4 与 `native-bridge-contract` 技能。
---
## 5. 状态归属与边界
| 归属 | 状态 | 旧位置 | Cocos 位置 |
|---|---|---|---|
| 平台-连接/配置/资产 | `Server/AgentId/ChannelId/GameId`、`hallConfig/sysConfig`、列表 | `GameData` | `AppStore` |
| 平台-房间 | `PlayerList[]`、`roomcode/stage/state`、`AgreeList`、`roomtype` | `Desk` | `RoomStore` |
| 平台-自己 | `playerid/bean/roomcard/seat/status/isprepare` | `C_Player` | `PlayerStore` |
| 平台-UI 瞬态 | `onstate[]/isprepare[]/VoteList/Mem`(方位映射后显示态) | `GameUI` 顶部变量 | `ui` 各控制器局部态 |
| 平台-本地持久化 | 开关/roomlist/playerid/machineId 等 | `Utl` storage key | `core/storage`(key 按 `GameId+AgentId` 拼,保持兼容) |
| **子游戏-对局态** | 手牌/出牌历史/轮次/分数/结算 | `Game_Modify`/`gameCombat` | 子游戏自身命名空间 + `serialize()/restore(deskinfo)` |
**边界判据(三条,移植时逐条守住)**:
1. **路由判据**:`route ∈ {platform,agent,room}` 归平台层,其余归子游戏(§4.1)。
2. **对局态透传不解析**:服务器在 `player_login` 回包下发 `deskinfo` 对局快照,`Desk.login` **原样**交给 `Game_Modify.Reconnect(deskinfo)`(`07_Desk.js:419-426`),平台层**不持有、不解析**对局态。Cocos 里是 `IGameModule.onReconnect(deskinfo)`,`RoomStore` 只透传快照、不拆解。
3. **同一玩家单一来源**:消除 `C_Player` 与 `Desk.PlayerList[seat]` 双镜像(§3.1)。
---
## 6. 核心流程(逻辑流程一致性基准)
以下五条流程是「逻辑流程一致」的验证基准,Cocos 侧必须**按同一次序、同一门控条件**复刻(实现手段可不同,流程与时序必须一致)。
### 6.1 启动流程(页面加载 → 首屏登录)
旧:资源加载(`onloadurl` 逐张计数到 100)→ `gamestart` → `Logic.AppStart`(定 netType/isGameHall、`registerFunction`、`setChannelId→setGameServer→setAgentId→getVersionState`、`new C_Player`)→ `get_config`(HTTP 取 `player_server_tcp/game_server_tcp/urlserver`)→ `getConfig_Succ`(写 `GameData.Server/AgentId`、`GameInit`)→ `firstConnect` 建 WS。
**首屏门控四条件**(全齐才进登录页,`GameUI.JumpWxAuth()`):
1. `LoadCount==100`(资源)
2. `firstConnect_Succ`(WS onopen)
3. `getJSONState`(配置就绪)
4. `Timesup`(加载计时器 `Game_Config.Max.showtime` 到点)
> 门控四条件的**时序语义必须保留**(缺一不进登录页),但实现手段升级:资源计数交给 Cocos 加载系统,计时器用 `scheduleOnce`,四条件合成一个「就绪信号」而非各自散落置位。
### 6.2 登录 / 进大厅 / 进房间
`Net.Send_login`(组包 agentid/openid/nickname/…/machineid)→ `Net.player_login` → `Desk.login`,按 `roomcode` 分支:
- **无 `roomcode`** → `Game_Modify.closeGameScene()` + `GameUI.JumpMenuScene()`(大厅)+ 按 `Logic.JudgeShow()` 弹公告。
- **有 `roomcode`** → `Logic.updateMainSceneData(roomtype)`(=`Game_Modify.onCreateDesk` + `Desk.Create`)→ `GameUI.MainScene(true)` → 按 `deskinfo` 有无走 `Game_Modify.Reconnect(deskinfo)` 或 `ReconnectNoMakewar()`。
### 6.3 断线重连(`isbattle==1` → `Reconnect(deskinfo)`)
- **断线检测**:`game_websocket.onclose` → 置 `netWorkSate=false`;非首次连接 `NetType=1` + `TryConnect()` + `OpenDisConnect()`。
- **重连后**:`onopen` → 若 `isLogin` 重发 `Send_login`。
- **触发点**(`07_Desk.js:419-426`):登录回包带 `deskinfo` 时 → `Game_Modify.Reconnect(deskinfo)`;否则 `ReconnectNoMakewar()`。子游戏必须能**反序列化同一结构**(与 `docs/protocol/` 的 `deskinfo` 快照对齐,B 已强调)。
- **旁路**:`self_join_room`(`07_Desk.js:649`)带 `deskinfo` → `Desk.stage=1` + `Game_Modify.DeskInfo(deskinfo)`(他人未开战牌桌数据)。
### 6.4 对局流程(`Desk` 状态机 + 子游戏协作)
开战 / 准备 / 对局收发包 / 解散投票 / 离桌 / 结算投降,均以 `Desk` 状态机为骨架、`Game_Modify` 为协作方:
| 流程 | 平台层(`Desk`/`C_Player`) | 子游戏(`Game_Modify`,Cocos `IGameModule`) |
|---|---|---|
| 开战 | `self/other_makewar` → `stage=1`、`ChangeExit(0)`、`HideStartScene()` | `StartWar(_msg)` 建对局 |
| 准备 | `player_prepare` → 改座位 `isprepare` + `SetIsprepare` | `onReady` |
| 对局中 | route 非 platform/agent/room 的包**完全不介入** | `_ReceiveData`(`onReceive(rpc,data)`) |
| 解散投票 | `state=1` + `AgreeList` 累积 + `OpenApply` | `Free(deskfree)`(`free_room` 时) |
| 离桌 | `stage==1` → `breakRoom()`;其余 `playerLeaveRoom` 等 | `breakRoom/playerLeaveRoom/playerOffline/changeSeat` |
| 结算/投降 | `get_player_grade1/2`→`gameCombat`;`beanroom_surrender` | `onSurrender` |
### 6.5 界面切换(**不是 `set_level`**)
> 重要澄清(全仓 grep 实证):`set_level`/`set_level_up` 是**引擎层函数**,平台层**从未直接调用**。平台层的「界面切换」机制是**精灵级显隐 + 场景哨兵**:
- `set_self(spid, 37, vsb)`(cpid=37 可见性)+ `set_group(groupid, 37, vsb)` 整组显隐。
- 界面骨架:`GameUI.invisible()`(全隐藏)→ 各界面只点亮自己的 group(登录 `set_group(2,37,1)`、大厅 `set_group(3,37,1)`、房间 `set_group(21,37,1)`、各弹窗各自 group)。
- **场景哨兵**:`get_self(149,37)` = 「是否在房间主界面」、`get_self(4,37)` = 「大厅」标志,被 `Utl.isMainScene`/`Logic.checkRoom` 读。
Cocos 等价:`set_self(spid,37,vsb)` → `node.active`(B §3 cpid=37);「界面」→ 独立的**场景 / 预制根节点**;「场景哨兵」→ `AppStore` 里显式的 `currentScene` 状态(而不是反查某个精灵的可见性)。**不要**去找 `set_level` 的对应物。
---
## 7. 平台层 gameabc API 依赖清单(19 个,分域)
全平台层 `00_Surface` 实际调用 **19 个引擎 API,共 2305 处**,其中 `set_self`/`get_self`/`set_group` 三项占 88.6%(2042 处),且集中在 `11_GameUI.js`。按域分列;**B 已覆盖的引用 B,本文只补 B 未覆盖的映射方向**。
| 域 | API(次数) | Cocos 对应方向 |
|---|---|---|
| 属性读写 | `set_self`(1525)、`get_self`(373)、`set_group`(144) | 属性号对照见 **B §3**;`set_group(groupid,37,vsb)` 整组显隐 → 该 group 父节点的 `active`(A §2.1 已并入父节点) |
| 裁剪/记录 | `set_clip`(21) | 设置裁剪区域(滚动列表/进度条)→ `cc.Mask` / `ScrollView` 视口裁剪 |
| 记录 | `set_rec`(4)、`get_rec`(2) | 见 **B §5.6**(文字表 → `Label`,不建全局可变文字表) |
| 窗口变量 | `set_windows`(1) | ⚠️ 待逆向(`12_Logic.js:523 set_windows(2,"var","1977")`,仅 1 处,落地前看用途) |
| 渲染绘制 | `ifast_mydrawbmp`(19)、`ifast_mydrawtext`(2) | 见 **B §5.1/§5.2** |
| 精灵绘制 | `ifast_mydrawsprite`(—) | 见 **B §5.3** |
| 批量加载 | `ifast_loadsprite`(1) | 批量预加载精灵 → Cocos `Resources.loadDir` / 图集预载(引擎自动,通常无需) |
| 实例/资源 | `ifast_addtospritefromspritecopy`(77)、`ifast_dllpritefromspritecopy`(69) | 见 **B §5.5**(列表 → `Layout`/`ScrollView`/`NodePool`) |
| 命中/检测 | `ifast_check_add`(12)、`ifast_checkimg`(1) | 按坐标检测精灵 → Cocos 节点事件系统天然覆盖;`checkimg` 图片已加载检查 → 资源系统 |
| 对象查找 | `ifast_getobj`(4) | 按 id 取引擎对象 → `node.getComponent` / `manifest.json`(A §1.3)定位 |
| 工具/数学 | `ifast_random`(14)、`ifast_abs`(14) | 纯 JS:`Math.random()`/`Math.abs()`,**无引擎依赖** |
| 引擎单例 | `gameabc_face`(14)、`gameabc_Voice`(8) | `gameabc_face`(取 div/canvas ctx)→ 场景根/Canvas 持有;`gameabc_Voice`(语音列表)→ 音频资源清单 + `AudioSource` |
**网络域(独立,见 `native-bridge-contract` 技能)**:平台层网络**不走**引擎的 `ifast_ws/tcp/ajax/http`(0 次调用),而是自有 `00_minhttp.js` 的 `min_tcp`(原生 `new WebSocket`)+ `min_http`(原生 `XMLHttpRequest`),收发经 `Net.ws_tcp.send(JSON.stringify(msg))`。Cocos 用 `WebSocket` 封装 + 协议信封逐字节对齐。
**任务提及但平台层实际未使用(避免白做迁移)**:
| 未使用 API | 实测 |
|---|---|
| `ifast_ws`/`ifast_tcp_*`/`ifast_ajax`/`ifast_http`/`ifast_split` | 0 次(平台层改用自有 `min_tcp`/`min_http`) |
| `get_selfdiv`/`set_selfdiv` | 0 次 |
| `set_level`/`set_level_up`/`openurl`/`showmessage` | 0 次 |
| `gameabc_Object`/`gameabc_Image`/`gameabc_Layer`/`gameabc_GroupList`/`gameabc_GameTxt` | 0 次(仅 `gameabc_face`、`gameabc_Voice` 被用) |
| `up_imgurl` | 平台层自有 `Func.up_imgurl`(`05_Func.js:1710`),非引擎 API |
---
## 8. 逻辑流程一致性检查点(验证基准)
迁移完成后,以下检查点逐一核验(作为「逻辑流程一致」的验收单):
- [ ] **信封**:`{app:"youle", route, rpc, data}` 结构与 `docs/protocol/` 逐字节对齐;`route` 三值 `platform/agent/room` 判定正确。
- [ ] **收包分界**:`route ∈ {platform,agent,room}` → 平台层;其余 → `IGameModule.onReceive`;平台层不拦对局包。
- [ ] **发包路径**:`netType==0` WebSocket / `netType==1` HTTP 兜底两条都在;`player_login` 的 `putMsg="playerLogin"` 特判保留。
- [ ] **启动门控四条件**:资源/WS onopen/配置/加载计时器,缺一不进登录页。
- [ ] **登录分支**:`roomcode` 有无 → 大厅 or 房间;`deskinfo` 有无 → `onReconnect` or `ReconnectNoMakewar`。
- [ ] **断线重连**:onclose → 重连 → 重发登录 → `deskinfo` 反序列化恢复对局(子游戏 `restore`)。
- [ ] **状态归属**:`RoomStore`/`PlayerStore`/`AppStore` 三态机职责唯一;`C_Player` 与 `PlayerList[seat]` 无双镜像。
- [ ] **对局态透传**:平台层不解析 `deskinfo`,原样交 `IGameModule`。
- [ ] **界面切换**:用 `node.active` + `AppStore.currentScene`,不搬 `set_level`。
- [ ] **原生接口**:`window.settings` 同步取值 + WVJB 异步桥,接口名/数据格式/URL 构造与旧逐字一致(见 `native-bridge-contract`)。
---
## 9. 落地约定 / 红线 / 待验证
### 9.1 落地约定
1. **模块名遵循框架 spec §3**:旧文件 → framework 层的映射(§3 表)是唯一落点,不得另起命名。
2. **Store 响应式驱动 UI**:`RoomStore`/`PlayerStore`/`AppStore` 状态变 → UI 自动刷新,**消灭** `Desk`/`C_Player` 里那批 `GameUI.*` 手工回调。
3. **路由/信封/deskinfo 只认 `docs/protocol/`**:任何网络层疑问先读协议文档,不臆测(CLAUDE.md 第一准则)。
4. **遇到 ❓/⚠️ API**(如 `set_windows`)回到 `gameabc.min.js` 对应处补齐再回填,**禁止臆测**(第二准则)。
### 9.2 红线
- 不手写 `.scene`/`.prefab`/`.meta`(CLAUDE.md)——本规范落地产物仍走 MCP。
- 不复刻引擎机制(主循环/双缓冲/帧内命中/对象注册表)——交给 Cocos 运行时。
- 不改变协议信封、route 判定、`deskinfo` 透传边界(第一准则)。
### 9.3 待验证(落地前)
| # | 待验证 | 方法 |
|---|---|---|
| 1 | `set_windows(2,"var","1977")` 的语义(仅 1 处,`12_Logic.js:523`) | 看该调用上下文,判定是否需迁移或可直接丢弃 |
| 2 | `Utl` storage key(`GameId+AgentId` 拼接)在新工程是否要保持原 key 以兼容旧缓存 | 与后端/运营确认是否需沿用旧 key |
| 3 | 首屏门控四条件里「加载计时器 `showtime`」的精确时长与超时行为 | 读 `Game_Config.Max.showtime` 定义值 |
| 4 | `C_Player`/`Desk.PlayerList[seat]` 双镜像在哪些方法里被读写,确保 `PlayerStore` 单一来源无遗漏 | grep `SetDeskInfo`/`getDeskInfo`/`PlayerList[` 全调用点 |