Files
youle_cocos/docs/superpowers/specs/2026-08-30-legacy-platform-logic-migration.md
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

23 KiB
Raw Permalink Blame History

旧 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[ 全调用点