# Interface Conversion Playbook > **Status**: Draft v1 (2026-09-03) > **Purpose**: 为其他工程师 + 后续 plan 提供 YouleNexus 界面转换规范 > **适用**: 把 `projects/Game_Surface_3/` (旧 HTML5 Cocos-JS 平台) 迁移到 `cocoscreator_projects/YouleNexus/` (新 Cocos 3.8.8 TS 平台) ## 决策树:代码迁移 vs MCP 手工复刻 ``` 原 Layer XML 含业务 UI 节点吗? ├── 否 → ✅ 代码迁移(legacy-layer-to-cocos-prefab.mjs 已支持) │ 适用场景: Login_Layer, BackHall_Layer 等纯 spirit 表达的场景 │ └── 是 → ❌ 代码迁移不足 + ⚠️ 改用 funplay MCP 手工复刻 适用场景: YouleNexus 现有 prefab 含原 XML 没有的手工 UI 节点 例: Bg / 装饰 / 状态显示 / 多按钮等 ``` **如何判断 "原 XML 是否含业务 UI"**: | 信号 | 说明 | |---|---| | 原 Layer XML 的 spirit 数 ≥ YouleNexus 现有 prefab 的 node 数 | ✅ 代码迁移足够 | | 原 Layer XML 的 spirit 数 < YouleNexus 现有 prefab 的 node 数(差 ≥ 3) | ⚠️ MCP 手工 | | 包含 `SpiritName` 像 `Bg / 装饰 / 状态` 等 | ⚠️ MCP 手工(业务 UI 命名约定) | | YouleNexus prefab 含 `_string` Label 含业务文案("准备中" / "游戏中") | ⚠️ MCP 手工 | **量化证据**(2026-09-02 实测): | Layer | 原 XML spirits | YouleNexus nodes | 差 | 路径 | |---|---|---|---|---| | Layer00002 Login | 18 | 20 | +2 | ✅ 代码迁移(PlayerInfoView 16/18 PASS + Login_Layer Cocos 编辑器验证) | | Layer00619 BackHall | 1 | 6 | +5 | ⚠️ MCP 手工 | | Layer00616 Kick | (待查) | 6 | ? | ⚠️ MCP 手工(diff 1381 行) | | Layer00614 Loading | (待查) | (待查) | ? | ⚠️ MCP 手工(diff 1012 行) | | Layer00010 Protol | 4 | (待查) | ? | ⚠️ MCP 手工(diff 1161 行) | ## 数据契约 ### 原工程数据来源(`projects/Game_Surface_3/`) ``` projects/Game_Surface_3/ ├── save/ ← 68 个 Layer XML (GB18030/UTF-8, 子集按 Layer ID) │ ├── Layer00002.xml ← Login_Layer │ ├── Layer00619.xml ← BackHall_Layer (1 spirit) │ └── ... (66 more) ├── output/ │ ├── gameabc_Layer.json ← Layer 索引 (ObjectList = spirit IDs) │ ├── gameabc_Image.json ← ImgResID → SpriteFrame 真 UUID (440 entries) │ ├── gameabc_Project.json ← ScreenWidth/Height/GameSceneWidth/Height/ScreenFitMode │ ├── gameabc_GameTxt.json ← 多语言文案(未迁移) │ └── ... (5 more) └── js/ └── gameabc.min.js ← 引擎核心(compiled-to-JS, 185KB, 5544 行) ← 难逆向(汇编输出 SIt2/L$qig3/PzF5 寄存器命名) ``` ### Spirit XML schema(关键字段) ```xml ``` ### Cocos prefab schema(YouleNexus 复刻基准) 每个 prefab 是 JSON 数组(按 schema 顺序): ``` [ cc.Prefab, ← 1 个: _name + data.__id__=1 cc.Node (root), ← 1 个: _name + 包含 cc.UITransform + cc.Widget + cc.PrefabInfo cc.Node (group-X), ← N 个 (group>0): children = spirit ids cc.Node (spirit), ← M 个: 包含 cc.UITransform + 组件 + cc.Widget + cc.CompPrefabInfo cc.UITransform, ← 每个 Node 一个 cc.Sprite/Label/..., ← 每个 spirit 一个主组件(按 SpiritType) cc.Widget, ← 每个 Node 一个(含 alignFlags + horizontalCenter/verticalCenter) cc.CompPrefabInfo, ← 每个组件一个(fileId 随机字符串) cc.PrefabInfo, ← 每个 Node 一个(root/asset/fileId) ] ``` **对照参考**:`cocoscreator_projects/YouleNexus/assets/framework/ui/prefabs/Login_Layer.prefab`(157 objects, 20 nodes, 1:1 验证通过) ### 锚点转换公式(实测验证) ``` cocos_lpos.x = original_X + original_W/2 - 640 # 原坐标系半宽 1280/2 cocos_lpos.y = 360 - (original_Y + original_H/2) # Y-up 翻转 + 半高 720/2 ``` 参考:`PlayerInfoView` 在 Login_Layer 中 16/18 spirit 精度 < 0.001 匹配(fixed in final fix wave commit `ec550ba`) ## 转换流程 ### Step 1: 分析数据完整度 ```bash # 1. 读 Layer XML cat cocoscreator_projects/YouleNexus/../projects/Game_Surface_3/save/LayerXXXXX.xml # 2. 读 YouleNexus 现有 prefab 作 baseline cat cocoscreator_projects/YouleNexus/assets/framework/ui/prefabs/.prefab # 3. 对比节点数 (XML spirit 数 vs YouleNexus node 数) python -c " import xml.etree.ElementTree as ET, json with open('projects/Game_Surface_3/save/LayerXXXXX.xml', 'rb') as f: text = f.read().decode('utf-8', errors='replace') root = ET.fromstring(text) xml_spirits = len(root.find('SpiritList').findall('Spirit')) with open('cocoscreator_projects/YouleNexus/assets/framework/ui/prefabs/.prefab', encoding='utf-8') as f: data = json.load(f) ynex_nodes = sum(1 for obj in data if obj.get('__type__') == 'cc.Node') print(f'XML spirits: {xml_spirits} YouleNexus nodes: {ynex_nodes} diff: {ynex_nodes - xml_spirits}') " ``` ### Step 2: 决策路径 - **diff ≤ 2**: 代码迁移足够 → 见 `docs/superpowers/specs/2026-09-02-legacy-layer-migration-design.md` - **diff ≥ 3**: MCP 手工复刻 → 跳到 Step 3 ### Step 3: MCP 手工复刻流程(仅当代码迁移不足时) 参考 `cocoscreator_projects/YouleNexus/CLAUDE.md`(所有 Cocos 编辑器操作必须走 `mcp__funplay_cocos__*` 工具) ```bash # 1. 打开 YouleNexus 现有 prefab 作为 baseline(看现有手工 UI 节点结构) mcp__funplay_cocos__open_asset target: db://assets/framework/ui/prefabs/.prefab # 2. 在新路径复制为 _test_legacy_migration/.prefab(spec "不覆盖" 原则) mcp__funplay_cocos__duplicate_prefab source: db://assets/framework/ui/prefabs/.prefab target: assets/framework/ui/prefabs/_test_legacy_migration/.prefab # 3. 打开新复制的 prefab,编辑 name 字段匹配原 XML 的 LayerName mcp__funplay_cocos__edit_prefab_json target: db://assets/framework/ui/prefabs/_test_legacy_migration/.prefab jsonPath: /0/_name valueJson: '""' # 4. 对每个 XML spirit 的 Sprite 引用:在 _test_legacy_migration prefab 中 # 找到原 YouleNexus prefab 对应节点,更新 _spriteFrame.__uuid__ 为 resolveSpriteFrameUuid(imgResID) 输出 # 5. 验证加载 mcp__funplay_cocos__refresh_assets mcp__funplay_cocos__validate_prefab_references target: db://assets/framework/ui/prefabs/_test_legacy_migration/.prefab # 6. 删除 _test_legacy_migration/ 临时文件(不 commit) ``` ## 8 项 1:1 匹配验证清单 每个 prefab 迁移完成后,必须验证以下 8 项(任一不符则**禁止 commit**): | # | 验证项 | 检查方法 | |---|---|---| | 1 | spirit 节点数匹配 | count `cc.Node` === XML spirits count | | 2 | spirit id 一致 | 每个 spirit id 在 prefab 中能找到(_prefab.root 等) | | 3 | 中文 Name 一致 | 每个 spirit._name 严格匹配 XML Spirit Name | | 4 | _lpos 精度 < 0.001 | 用 `computeLpos` 公式对比 (渠道logo 123.5/143, 游客登录 60/-75.5 等) | | 5 | _contentSize 一致 | W/H 匹配(SizeCalcMode=0 直接读) | | 6 | SpriteFrame UUID 解析 | `_spriteFrame.__uuid__` 是有效 UUID(含 `@f9941` 后缀) | | 7 | group 层级正确 | `BelongGroupID > 0` 的 spirit 必须在 group-X 父节点下;`==0` 的必须在 Layer 根下 | | 8 | 加载 0 报错 | `mcp__funplay_cocos__open_asset` + `validate_prefab_references` + `search_project_logs` 无 error | ## 已知限制(2026-09-02 实测) ## 关键迁移规则:原工程多帧 sprite → Cocos 多张散图 **原工程** (gameabc 引擎): 一个 sprite PNG 含多个动画帧 (e.g. `00014.png` 含 12 frames) **Cocos 引擎**: 一个 sprite PNG → 一个 spriteFrame, 多帧 = 多张散图 **映射规则**: | 原工程 | Cocos | |---|---| | `00014.png` (1 file, 12 frames) | `00014_01.png` ~ `00014_12.png` (12 files, 各含一个 spriteFrame rect) | | `frame_all` 字段 (gameabc_Image.json) | 子图数量 = frame_all | | SpriteFrame 引用单个 texture (atlas image) | SpriteFrame rect 指向 atlas image 的 sub-region | **验证方法**: 1. 读 `projects/Game_Surface_3/output/gameabc_Image.json` 找 id=ImgResID 的 entry,确认 bmp=00014.png + frame_all=12 2. 在 YouleNexus assets/framework/ui/atlas-*/ 找 00014_01.png ~ 00014_12.png (12 个文件) 3. 读对应 00014_01.png.meta 找 subMetas 里 name=spriteFrame 的 UUID + imageUuidOrDatabaseUri 4. 在 Notice prefab 的 6 按钮 ProgressBar 用这个 spriteFrame UUID 引用 **当前实测**: - `00014_01.png` 的 spriteFrame UUID = `04f4d29d-861f-4b46-930a-baf544a80b1b@f9941` (atlas-hall) - 6 个按钮都引用同一个 spriteFrame UUID (Cocos atlas 9-slice 自动共享 sprite, 类似原 HTML5 sprite 复用) ### 类型 4 = cc.EditBox 是 best-fit - `phoneInfo` (Layer00029): 3 个 type-4 spirit 真的是 EditBox (42×28 输入框) - `Pay_Layer` (Layer00014): 11 个 type-4 spirit (id 446-455, ImgResID=191, 52×32) 可能是 Button 而非 EditBox - **行动**: 加载 Pay_Layer 进 Cocos 后人工验证 UI 实际行为;若不是 EditBox,更新 `fixtures/spirit-semantics.json` type-4 为 cc.Button + rerun ### 类型 5 SLICED 9-slice borders 丢失 - `fixtures/spirit-semantics.json` 标 type=5 = cc.Sprite(_type=1 SLICED) - 但 Login_Layer Spirit465 引用的 SpriteFrame `capInsets = [0, 0, 0, 0]`(**不是 9-slice**) - 当前代码硬编码 `_type=1` 与 YouleNexus 现有手工 prefab 一致,运行时 SpriteFrame 实际是 SIMPLE - **行动**: 扩展 `resolveSpriteFrameUuid()` 返回 capInsets → emitPrefabJson 根据 capInsets 决定 `_type`(详见 `docs/superpowers/plans/2026-09-02-legacy-layer-migration.md` final fix wave 后 Deferred) ### 6 个 expected-fail Layer sprite atlas 缺失 | Layer | 缺失 ImgResID | 影响 | |---|---|---| | Layer00002 Login | 14, 30, 97, 380, 427, 429 | `Login_Layer.prefab` 生成 throw(PoC 3 fallback 用 filter 跳过这些 spirit) | | Layer00029 phoneInfo | 同上 | Group106_PhoneVerify.prefab 抛 | | Layer00609 Setting | 同上 | Layer609_Setting.prefab 抛 | | Layer00615 Reconnect | 同上 | Layer615_Reconnect.prefab 抛 | | Layer00008 Notice | 同上 | Layer617_Notice.prefab 抛 | | Layer00620 recordLayer | 同上 | Layer620_Record.prefab 抛 | **行动**: 独立 content-import 任务(不在本 playbook 范围):把缺失 sprite 导入 `cocoscreator_projects/YouleNexus/assets/framework/ui/atlas-login/` 然后 rerun legacy-layer migration batch。 ## 实验结果:BackHall_Layer (Layer00619) — MCP 手工复刻 vs 代码迁移 **待执行**(本 playbook v1 草稿时未完成,作为 v1 实施的第一个验证 case) 预期步骤: 1. 打开 `YouleNexus/assets/framework/ui/prefabs/widgets/Layer619_BackOtherGame.prefab`(1172 行) 2. 复制到 `_test_legacy_migration/BackHall_Layer.prefab` 3. 修改 `_name`: `Layer619_BackOtherGame` → `BackHall_Layer` 4. 唯一 spirit (`Spirit465` ProgressBar): 替换 `_spriteFrame.__uuid__` 为 `resolveSpriteFrameUuid("191")` 输出 5. 验证 8 项清单 6. 报告 diff size + 验证 Playbook 8 项清单是否足够 ## 相关文档 + 索引 - **Spec**: `docs/superpowers/specs/2026-09-02-legacy-layer-migration-design.md`(代码迁移方案) - **Spec**: `docs/superpowers/specs/2026-09-03-interface-conversion-playbook.md`(本文档) - **Plan**: `docs/superpowers/plans/2026-09-02-legacy-layer-migration.md`(12 task + 4 fix round) - **Fixtures**: `cocoscreator_projects/YouleNexus/framework-tests/legacy-layer-migration/fixtures/spirit-semantics.json`(SpiritType 4/5 + SizeCalcMode 公式) - **CLAUDE.md 第二准则**: `数据源权威唯一、下游不兜底` —— 缺失字段 throw 不 fallback - **CLAUDE.md 第三准则**: 所有 Cocos 编辑器操作走 `mcp__funplay_cocos__*` 工具 - **memory `legacy-ui-data-and-resolution`**: "旧 UI 是可程序化转换的结构化数据;但零锚定,1280x720 适配是全新工作" ## Rulings (committed in this playbook) - **Ruling F (Migration strategy)**: 决策树用 "原 XML 是否含业务 UI 节点" 作为分流条件 —— 这是**架构选择**不是技术约束 - **Ruling G (Validation gate)**: 8 项验证清单是 commit 前的强制检查 —— 任一不符禁止 commit - **Ruling H (Known limits)**: type-4 best-fit + type-5 SLICED borders + 6 expected-fail sprites 都是后续子任务,本 playbook 不掩盖而是 park