Files
youle_cocos/docs/superpowers/specs/2026-09-03-interface-conversion-playbook.md
T

13 KiB
Raw Blame History

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(关键字段)

<Spirit>
  <Property Name="SpiritType" Value="0|1|3|4|5"/>     <!-- 组件类型:Sprite/Label/ProgressBar/EditBox/(SLICED Sprite) -->
  <Property Name="ID" Value="<spirit_id>"/>          <!-- 唯一 ID -->
  <Property Name="Name" Value="<chinese_name>"/>     <!-- 中文节点名(YouleNexus 复刻保留) -->
  <Property Name="ImgResID" Value="<sprite_id>"/>    <!-- SpriteFrame 引用(→ gameabc_Image.json) -->
  <Property Name="X" Value="<left_top_x>"/>           <!-- 1280×720 屏幕坐标,左上角锚点 -->
  <Property Name="Y" Value="<left_top_y>"/>           <!-- 1280×720 屏幕坐标,左上角锚点 -->
  <Property Name="Width" Value="<w>"/>                <!-- spirit 尺寸(SizeCalcMode=0 直接用) -->
  <Property Name="Height" Value="<h>"/>               <!-- spirit 尺寸(SizeCalcMode=0 直接用) -->
  <Property Name="BelongGroupID" Value="<group_id>"/> <!-- group-X 父节点(0 = 直挂 Layer 根) -->
  <Property Name="IndexOfLayer" Value="<order>"/>    <!-- 同 group 内排序 -->
</Spirit>

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: 分析数据完整度

# 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/<LayerName>.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/<LayerName>.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__* 工具)

# 1. 打开 YouleNexus 现有 prefab 作为 baseline(看现有手工 UI 节点结构)
mcp__funplay_cocos__open_asset
  target: db://assets/framework/ui/prefabs/<LayerName>.prefab

# 2. 在新路径复制为 _test_legacy_migration/<LayerName>.prefab(spec "不覆盖" 原则)
mcp__funplay_cocos__duplicate_prefab
  source: db://assets/framework/ui/prefabs/<LayerName>.prefab
  target: assets/framework/ui/prefabs/_test_legacy_migration/<LayerName>.prefab

# 3. 打开新复制的 prefab,编辑 name 字段匹配原 XML 的 LayerName
mcp__funplay_cocos__edit_prefab_json
  target: db://assets/framework/ui/prefabs/_test_legacy_migration/<LayerName>.prefab
  jsonPath: /0/_name
  valueJson: '"<LayerName from XML>"'

# 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/<LayerName>.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