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

260 lines
13 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.
# 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
<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: 分析数据完整度
```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/<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__*` 工具)
```bash
# 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