# 旧 gameabc UI 数据 → Cocos 平台界面:迁移与分辨率适配 设计 > 状态:已与用户确认(brainstorming 通过),待转 writing-plans。 > 适用工程:`cocoscreator_projects/YouleNexus`(框架真源)。 > 数据源:`projects/Game_Surface_3/output/gameabc_*.json`(旧 H5 工程的 UI 导出数据)。 > 关联:`2026-08-27-ui-asset-and-skin-design.md`(资源归属 §2 / 图集划分 / override 白名单 / theme §4); > `2026-06-28-cocos-framework-design.md` §5(换肤)。 > **本 spec 只覆盖子系统 A**,B(引擎机制映射)与 C(平台逻辑移植)另立,见 §0.1。 --- ## 0. 目标与背景 ### 0.1 范围:这是三个子系统中的第一个 把「旧平台 UI 搬到 Cocos」拆成三件互相独立的事,本 spec 只做 A: | | 子系统 | 产出 | 依赖 | |---|---|---|---| | **A** | **UI 数据迁移**(本 spec) | 991 对象 / 55 界面 → 中间描述 → prefab + 1306 张散图 | 无 | | **B** | 引擎机制映射 | ~10 个 `ifast_*` API + `set_self`/`get_self` **属性号对照表** → Cocos 等价物 | 需逆向 `js/gameabc.min.js`(186KB) | | **C** | 平台逻辑移植 | `11_GameUI.js`(9862 行)等 → TypeScript | **A 与 B 都要先有** | A 与 B 互不依赖、可并行;C 必须等两者。 > **B 的紧迫性**:平台层有 `set_self` **1773 处** + `get_self` **447 处**调用,全部使用数字属性号(37/7/18/20/43…)。若属性号解不出来,C 就只能**重写**而非**移植**,工作量差一个量级。建议 A 开工后尽快并行启动 B。 ### 0.2 决策依据(实测数据) 以下全部实测自 `projects/Game_Surface_3`,2026-08-28。**记录在此,便于日后回看每个取舍的依据。** **布局数据规模** - `gameabc_Object.json`:**991 个对象**,`ObjectType` 仅两种——`2` = 图片精灵(782)、`4` = 文字(209) - `gameabc_Layer.json`:**55 个 Layer = 55 个界面**(`Logo_Layer` / `Login_Layer` / `MainMenu_Layer` / `Player_Head_Score_Layer` / `inputPannel` …) - `gameabc_GroupList.json`:**80 个组**,覆盖 **990/991** 个对象(仅 1 个未分组) - **109 个对象**使用九宫格(`L9`/`T9`/`R9`/`B9` 非零) - **52 个对象**为满屏尺寸(≥1280×720),其中 **40 个是所在层的首个对象** - **222 个对象**坐标超出画布边界;`Left` 范围 **−286 ~ 1501**、`Top` 范围 **−318 ~ 1206** **图片资源** - `gameabc_Image.json`:**439 张图**,其中 **139 张多帧(31.7%)** - 多帧图全部为规整网格:**`w × h = frame_all`,139 张零例外** - 切分后总计 **1006 帧 + 300 张单帧图 = 1306 张散图** - **78/139** 张多帧图被多个对象共用,最多一张被 **13 个对象**共用 - **389 个对象**引用多帧图,其中 `FrameIndex = 0` 的仅 **2 个** **坐标系与适配(现状)** - 旧引擎原点**左上**、Y 向下、**精灵锚点左上**,`position = (Left, Top)`,设计分辨率 **1280×720** - `OriginID` / `Parent` **全为 0**,`OriginPos` / `SelfPos` **全为 1** → **零相对锚定数据** - 引擎(`js/gameabc.min.js`)用 `innerWidth`/`innerHeight` + `scale` + `onresize` 做**整画布等比缩放**,靠 letterbox 适配 **文字** - 209 个 Type4 对象**全部没有 `ImageFileID`、`TextFrames` 为空** → 纯动态文本占位 - 样式在代码里:`Utl.setFontColor(spid, value)`(`08_Utl_Output.js:824`),引擎用 canvas `fillText` - → **转换只能得到几何信息,字体/字号/颜色须由 `theme.fonts` 提供** **运行时动态精灵(B 的线索,A 需为其保留信息)** - `ifast_addtospritefromspritecopy(父精灵ID, 模板精灵ID, x, y, tag)` —— **88 处调用** - `ifast_dllpritefromspritecopy` —— **74 处调用** - → 典型「克隆模板 → 用完删除」的列表/对象池模式;**991 个对象中有一部分是模板、一部分是容器** ### 0.3 帧序:1 基 + 行优先(实证结论) ``` 列 = (FrameIndex − 1) % w 行 = ⌊(FrameIndex − 1) / w⌋ 源矩形 = (列 × w1, 行 × h1, w1, h1) ``` **验证方法**(记录在此,因为这是最容易搞错且后果最广的一处):`00014.png` 为 450×280、网格 3×4,被 11 个按钮共用。直接读取该 PNG,其网格内容为: ``` 我的 福利 任务 战绩 背包 仓库 吐槽 财富榜 邀请码 通知 设置 (空) ``` 对照对象的 `FrameIndex`:通知=10、设置=11、战绩=4、背包=5、仓库=6、任务=3。按「1 基 + 行优先」换算,六项**全部精确命中**;列优先与 0 基行优先均对不上。用户随后独立确认了该结论。 `FrameIndex = 0` 的 2 个对象按帧 1 处理。 > **一处被推翻的统计**:早期曾得出「1306 帧中 674 帧(51.6%)从未被引用」并险些据此裁剪资源。该数字**错误且不得使用**,两个原因:① 帧号 1 基,统计时把下标 0 当成了有效帧;② 平台层有 `set_self` 1773 处调用,**帧是运行时切换的**,静态数据里的 `FrameIndex` 只是初始帧。按钮的按下态/选中态就在那些「未引用」帧里。**任何帧都不得因静态未引用而删除。** ### 0.4 已确认决策 - **设计分辨率设为 1600×720(20:9 主流移动端比例)+ `fitHeight`** —— 991 个对象的 X/Y **全部 1:1 迁移**,不做任何坐标缩放。旧引擎的 1280×720 降格为**内容区基准**(`DESIGN_WIDTH`),只用于坐标换算、三区锚点推断、满屏/越界判断;设计分辨率画布宽度是**另一个独立常量**(`CANVAS_WIDTH = 1600`),落地时据此设定 Layer/Group 根节点尺寸。20:9 实机 1:1 无缩放,16:9 老设备由 `fitHeight` 收缩画布、Widget 弹性贴边自动适配。 - **多帧图切成散图**(1306 张)—— 多帧网格是旧引擎缺图集能力的手工替代品,Cocos 的 Auto Atlas 在构建期自动打包;照搬它等于把引擎缺陷当成新框架的约定。 - **同组对象挂同一父节点** —— `GroupList` 覆盖 990/991,是旧引擎用来做整体操作的机制,语义等价于父节点。 - **锚点按中心点三区自动推断**,仅水平方向(§2.3)。 - **背景四边拉伸**,美术后续手动更换正确尺寸的图片。 - **全部 55 个界面转进框架**(含牌桌内的平台部分)。 - **落地走「中间产物 + 编辑器批量」**(§4.1),不直接生成 `.prefab` 文件。 ### 0.5 红线 - **绝不手写 `.prefab` / `.meta` / `.scene`**(CLAUDE.md)。序列化资源只能由编辑器经官方 API 产生。这一条直接否决了「Node 脚本直接吐 prefab」这一最直觉的做法(§4.1)。 - **服务器零改动**:本 spec 不触及协议。 - **数据源权威唯一、下游不兜底**:中间描述是转换的唯一真相;转换失败必须显式报错,不得静默产出残缺节点。 - **不得因静态未引用而裁剪帧**(§0.3)。 ### 0.6 方法论教训(写给后续维护者) 本 spec 的调研过程中**两次**因看错字段而误判「数据不存在」: 1. 查 `OriginID`/`Parent` 全为 0 → 结论「没有层级信息」。**错**:层级语义在 `GroupList` 里。 2. 统计 `FrameIndex` 引用 → 结论「51.6% 帧是死重量」。**错**:帧号 1 基,且帧在运行时切换。 **教训**:在 gameabc 这类自研引擎的数据上,「某字段全为空」**不等于**「该语义不存在」——它可能编码在另一个字段、另一个文件,或根本在代码里。下结论前先把 `output/` 下**全部** JSON 与相关引擎 API 都看一遍。 --- ## 1. 中间描述的数据模型 ### 1.1 形态:一层一个 JSON + 一份 manifest ``` tools/ui-migration/intermediate/ ├─ manifest.json # 55 界面索引 + ObjectID 全局映射 + 转换器版本 ├─ 002-Login_Layer.json ├─ 004-MainMenu_Layer.json └─ …(共 55 个) ``` 一层一文件:**局部重跑与人工校对的单位就是界面**。改了锚点规则只想重转登录页,不该动其余 54 个。 ### 1.2 节点模型 ```jsonc { "id": 254, "name": "微信登录按钮", // ObjectName 原样保留(中文名信息量大) "kind": "sprite", // ObjectType 2→sprite, 4→label "position": { "x": -245, "y": 125 }, // 已转为 Cocos 坐标系(§2.2) "size": { "width": 150, "height": 70 }, "siblingIndex": 7, // 来自 IndexOfLayer,决定 z 序 "widget": { "horizontal": "center", "horizontalCenter": -245 }, // §2.3 "sprite": { "frame": "atlas-login/00014_05.png", // 当前帧的逻辑路径(§3) "frameSet": { // 供 FrameSet 组件(§3.4) "prefix": "atlas-login/00014", // 帧文件名前缀 "count": 12, // 帧总数,索引 1..count "pad": 2 // 补零位数,用于拼出 00014_01 … 00014_12 }, "type": "sliced", // L9/T9/R9/B9 任一非零 → sliced "slice": { "left": 12, "right": 12, "top": 8, "bottom": 8 } }, "flags": { "stretch": false, // 背景类,四边拉伸(§2.4) "offscreen": false, // 坐标超出画布(§2.5) "animation": false // FrameStyle=1,整图动画(§3.5) }, "legacy": { // 原始信息,不参与渲染,但**承重**(§1.3) "objectId": 254, "imageFileId": 14, "frameIndex": 5, "frameStyle": 0, "groupId": 3, "voiceFileId": 0, "timerInterval": 0, "left": 320, "top": 200, // 原始左上坐标,供回溯对照 "events": ["mousedown", "mouseup"] // 校验:x = 320 + 150/2 − 640 = −245;y = 360 − 200 − 70/2 = 125 // cx = 320 + 75 = 395 ∈ [320, 960] → 水平居中,horizontalCenter = 395 − 640 = −245 } } ``` `kind: "label"` 的节点只有 `position` / `size` / `widget` / `legacy`,**没有样式字段**——依据 §0.2,样式在旧代码里,数据中不存在。字体字号颜色由 `theme.fonts` 提供(见 `2026-08-27-ui-asset-and-skin-design.md` §4)。 ### 1.3 `legacy.objectId` 是承重字段,不是纪念品 旧平台逻辑通过 **`set_self(精灵ID, 属性号, …)`** 操作 UI,全平台层 **1773 处**调用。**子系统 C 移植这些逻辑时,必须能由 ObjectID 定位到 Cocos 节点**——丢掉这个映射,那 1773 处调用就没有着落,C 会从「移植」退化成「重写」。 因此: - 每个节点保留完整 `legacy` 块 - `manifest.json` 额外维护一份全局索引:**ObjectID → { 界面, 节点路径 }** 同理保留 `groupId`(B 需要它理解容器语义)与 `events`(C 需要它接事件)。 ### 1.4 层级信息 ```jsonc { "layerId": 2, "name": "Login_Layer", "bucket": "login", // login | hall | room | common | unassigned(§3.3) "backgroundNodeId": 2, // 「层内首个 + 满屏」识别(40/52 命中) "groups": [ // 来自 GroupList,同组挂同一父节点(§2.1) { "groupId": 2, "nodeIds": [2, 405, 285, /* … */] } ], "nodes": [ /* 按 siblingIndex 排序 */ ] } ``` ### 1.5 本节有意不做的事 - **不推断按钮**。`Event` 含 `mousedown` 的对象包括背景图(对象 1 即是),据此判断交互性不可靠。事件原样存入 `legacy`,留给 C。 - **不识别模板/容器**。§0.2 表明存在运行时克隆的模板与容器,但要认出「哪个 ObjectID 是 `ConstVal.myRoomList.bgSp`」需要读平台层代码——那是 B/C 的范围。A 只需**忠实保留 ObjectID**,使后续可交叉引用。 --- ## 2. 坐标与锚点转换规则 ### 2.1 group → 父节点:父节点必须是满屏容器 子节点坐标相对父节点,而旧数据全是绝对坐标。若 group 父节点用包围盒或零尺寸,两件事会同时出问题:子坐标要重算,且 **子节点的 Widget 会锚到组边界而非屏幕边界,适配立刻失效**。 解法是让父节点**不产生坐标偏移、且与屏幕同尺寸**: ``` Layer 根节点 1600×720(设计分辨率画布),Widget 四边拉伸 └─ Group 节点 1600×720,Widget 四边拉伸,position (0,0) └─ 对象节点 §2.2 转换后的绝对坐标(以 1280 内容区基准换算) ``` 于是子对象坐标 = 转换后的绝对坐标(无需再减父偏移),其 Widget 锚定的满屏父节点等价于锚定屏幕边界。 ### 2.2 坐标系转换 节点锚点采用 **Cocos 惯例 `(0.5, 0.5)`**: ``` x = Left + Width / 2 − 640 y = 360 − Top − Height / 2 ``` 其中 `640`/`360` 是**内容区基准 1280×720 的一半**(`DESIGN_WIDTH / 2`),不是设计分辨率画布 1600 的一半。对象中心 `x = 0` 落在内容区中心,而 Layer/Group 根节点是 1600×720,两者中心重合于屏幕中心——所以居中对象天然居中,靠边对象再由 Widget 贴到画布边缘,无需为画布变宽重算坐标。 **为何不用 `(0, 1)`(左上)**:那样公式更简(`x = Left − 640`、`y = 360 − Top`)且与旧引擎语义一一对应,但用户明确会**在编辑器里人工审核并修改节点树**,而 Cocos 的编辑器、对齐工具、Widget 全部假定锚点居中。转换公式复杂一点是转换器一次性的成本,人工编辑的别扭是长期成本。 原始 `Left`/`Top` 存入 `legacy`,回溯对照随时可查。 ### 2.3 锚点推断:只需要水平方向 **`fitHeight` 下垂直方向永远精确撑满、不会溢出,因此不需要任何垂直适配**——垂直用固定位置即可。这是设计分辨率高度保持 720(与内容区基准高度一致)带来的实质简化。 水平方向按对象中心点 `cx = Left + Width / 2` 三区推断: | 条件 | 锚定 | Widget 值 | |---|---|---| | `cx < 320` | 靠左 | `left = Left` | | `cx > 960` | 靠右 | `right = 1280 − (Left + Width)` | | 其余 | 水平居中 | `horizontalCenter = cx − 640` | 分界取**内容区基准 1280** 的四等分点(与设计分辨率画布 1600 无关;`cx`/`Left`/`right` 里的 `1280`/`640` 均指内容区基准)。这是**机械规则,不追求完美**——用户已确认转换后会逐屏人工审核修正,规则只需「大部分对、且错了容易看出」。 ### 2.4 背景:四边拉伸 识别规则:**层内首个 + 满屏尺寸**(实测 40/52 命中)。此类节点 Widget 四边置 0 全拉伸,中间描述标 `flags.stretch = true`。 另外 12 个满屏但非层内首个的(如「创建房间遮罩」)同样四边拉伸——它们本就是覆盖全屏的遮罩,行为一致。 转换器**不做任何图片处理**:美术后续会手动更换为正确尺寸的图片。 ### 2.5 屏外对象 222 个对象坐标超出画布(`Left −286 ~ 1501`、`Top −318 ~ 1206`),多为动画入场的暂存位。 处理:**坐标原样转换,不裁剪、不钳制**。三区规则照常适用(`cx` 为负自然落入「靠左」)。额外标 `flags.offscreen = true`,供人工审核时快速筛出——它们最可能需要手工确认锚点是否合理。 --- ## 3. 多帧图切分与命名约定 ### 3.1 切分规则 见 §0.3 的公式与实证。139 张多帧图全部满足 `w × h = frame_all`,切分是纯机械操作。 ### 3.2 命名:保持 1 基,补零对齐字典序 ``` 00014.png (3×4) → 00014_01.png … 00014_12.png 00019.png (13×1) → 00019_01.png … 00019_13.png ``` **用 1 基而非 0 基**,使文件名与 `FrameIndex` 直接对齐:C 移植时代码里的 `set_self(spid, 帧属性号, 7)` 能一眼对上 `_07`,无需心算减一。这类 off-by-one 是最易反复踩的坑(§0.3 已经踩过一次统计口径)。 补零位数按 `frame_all` 决定,保证**字典序 = 帧序**(否则 `_10` 会排在 `_2` 前,图集打包与人工浏览都会乱)。 ### 3.3 图集归属 每张图按**使用它的对象所在层**决定 bucket;跨多个 bucket 的图归 `atlas-common`。 层 → bucket 用实测出的 **LayerID 段位规律** + 可覆盖的显式表: | LayerID 段 | bucket | 典型层 | |---|---|---| | 1–29 | `login` / `hall` | Logo/Login/Auth/Bind → login;MainMenu/Pay/Task/rankList/wareHouse → hall | | 50 / 202 / 4xx | `room` | MainScene / Player_Head_Score / Chat / Interact / btnPannel | | 6xx | `common` | inputPannel / Tips / Loading / Reconnect / Kick | 命名笼统的层(`Layer6` / `Layer13` / `Layer403` / `Layer411` / `Layer601` / `Layer603` / `Layer618`)标 `bucket: "unassigned"`,**不猜**,由人工归位。转换器遇到 `unassigned` 应告警而非静默归入 common。 ### 3.4 运行时切帧:`FrameSet` 组件 每个引用多帧图的节点挂一个 `FrameSet` 组件,持有该图**全部帧**的 SpriteFrame 数组,索引与 `FrameIndex` 同为 1 基: ```ts frameSet.setFrame(7); // ← C 移植时对应 set_self(spid, 帧属性号, 7) ``` 副作用是必要的:全部帧都被数组引用 → **都会进图集、不会被依赖树裁掉**,符合 §0.3「任何帧都不得因静态未引用而删除」。 ### 3.5 不切的情况 - **300 张单帧图**原样保留,仅按 bucket 分目录 - **`FrameStyle = 1` 的 54 个对象**(整图动画)照常切帧,节点额外标 `flags.animation = true`,供后续做逐帧动画识别 ### 3.6 物理组织与命名保留 ``` framework/ui/atlas-hall/00014_01.png … 00014_12.png framework/ui/atlas-login/00006_01.png … 00006_05.png ``` **沿用原编号 + 帧号,不重命名为语义名**。两个理由:① 原编号是与 `gameabc_Image.json` 对照回溯的唯一线索;② 实测证明**对象名与美术内容会脱节**(对象叫「更多」,图上是「我的」;对象叫「分享」,图上是「福利」),语义命名会把错误信息固化进文件名。 --- ## 4. 落地管线与验证 ### 4.1 管线形态 ``` gameabc_*.json ──[Node 转换器]──► 中间描述 55 份 + 散图 1306 张 │ ▼ [编辑器侧批量落地] │ ▼ framework/ui/*.prefab(55 个) ``` **Node 侧**为纯函数:读 JSON、算坐标、推锚点、切帧、分 bucket。可 TDD,沿用既有工具链约定(`node:test`、零第三方依赖、显式路径提交)。 **编辑器侧**先用 MCP 跑通**一个**界面(建议 `Login_Layer`,18 个对象,规模合适且链路完整),确认节点树、Widget、SpriteFrame 引用无误,**再决定**是否值得写编辑器扩展批量跑完其余 54 个。不预先写扩展——先证明单条链路成立。 **为何不直接用 Node 生成 `.prefab`**:那要手写 UUID 引用的序列化格式,直接违反 §0.5 红线;991 个对象 × 55 个 prefab,格式错一点就是「资源导入失败」且只能人工恢复。 ### 4.2 验证:分四层 991 个对象、1306 张图无法靠肉眼验证,故分层且尽量自动: **① 几何与规则层(自动,TDD)** 坐标换算、三区锚点推断、bucket 归属——纯函数,用已知样例断言(如「通知」按钮的 `Left`/`Top` → 期望的 Cocos 坐标与 Widget 值写死在测试中)。 **② 切图正确性(自动,且是完全验证)** 切分是**可逆**操作:把切出的帧按原网格重组,必须与原图**逐字节相同**。 ``` 切分(原图) → N 帧 → 重组(N 帧) ≡ 原图(逐字节) ``` 对全部 139 张多帧图跑重组校验,即**对全部 1006 帧的完全验证**,成本近零。行优先/1 基这类错误会被当场抓住——比靠读图判断可靠得多。 **③ prefab 结构(自动)** 落地后回读 prefab:节点数、层级归属(group → 父节点)、z 序、Widget 参数,与中间描述逐项比对,差异即报错。 **④ 视觉验证(半自动,抽样)** 用 MCP `cocos_capture` 截图,与原项目同界面截图并排比对。有 ①②③ 兜底后,这一层只需回答「规则本身定得对不对」(某按钮该锚右却锚了中),而非「转换器有没有 bug」。 ### 4.3 与已交付工具链的衔接 切出的 1306 张图落进 `framework/ui/atlas-*`,正是 `2026-08-27-ui-asset-and-skin-design.md` §3.1 定义的**可覆盖白名单那 8 个目录**。因此: - `check-skin` 的「框架可覆盖资源」将从**当前 0 个变为 1306 个**——换肤机制首次有真实对象 - 该 spec §6 中三条一直 ⏸ 的承重假设(Auto Atlas 构建期打包、meta 尺寸行为、Spine)首次有真实素材可验证 **A 落地时应顺手复测这些假设**,而非任其继续悬挂。 ### 4.4 幂等与可重跑 - 中间描述是纯函数产物,转换器可任意重跑 - prefab 落地以**层为单位**,支持局部重跑(改了某界面的规则只重落该层) - 散图切分同样幂等;重跑前需清理旧产物,避免残留帧文件混入图集 --- ## 5. 待验证项 | # | 待验证 | 验证方法 | 失败后的备选 | |---|---|---|---| | 1 | Auto Atlas 尺寸上限:1306 张图分 4 bucket,某 bucket 可能超单张图集 2048×2048 | 落地后构建,检查图集张数与尺寸 | 按加载时机进一步细分 bucket(如 `atlas-hall-1/2`) | | 2 | 包体:1306 张图全部进每个子游戏包 | 第一次真实构建后量实际包体 | 评估是否将低频界面(如 `Help`/`Protol`)转为远程 bundle | | 3 | MCP 批量建节点的可行性与耗时(991 节点) | 实测(2026-08-29):`Login_Layer` 18 节点落地共 **76 次 MCP 调用、约 20 分钟**。`builder.build` 1 次可建整棵树,但 cc.Widget「flag 使能即采纳当前 margin」会按瞬时尺寸污染 margin,需**逐节点 `set_property` 修正**(约 22 次、每次 ~3.5s)。按 991 节点外推仅 margin 修正即需 ~1000 次调用(≈58 分钟纯往返)。**结论:逐节点 MCP 不可行** | 已确认:修 builder 的 Widget 赋值时序(margin 在最终尺寸后设)或写编辑器扩展批量修正,二者取一 | | 4 | 三区锚点规则的实际命中率 | 抽样 5 个界面视觉比对 | 调整分界阈值,或改为逐元素人工标注 | > **margin 污染的事后修复方法(2026-08-30 实测确认)**:cc.Widget 四边拉伸下 `contentSize` 是**派生值**(`= 父尺寸 − left − right`),不是自由变量。修正时**必须 `set_property` 设公开属性 `left`/`right`**(触发 setter 正向对齐,`contentSize` 自动跟随);**切勿 set `_contentSize`**——那会触发 Widget 反算,把刚设好的 margin 打回污染值(`-160`),形成死循环(实测:set contentSize=1600 → 实际返回 1920 且 margin 弹回 -160)。正确路径实测:先恢复 `_enabled=true`,再设 `left=0`、`right=0`,`contentSize` 自动从 1920 收敛到 1600。已落地样板:`should_hide_in_hierarchy`(`left/right=0 + contentSize=1600 + originalWidth=0`),而 `Login_Layer`/`group-2` 污染时 `originalWidth=100`。 --- ## 6. 非目标(YAGNI) - **不做嵌套节点树的进一步细分**。转换只产出「Layer → Group → 对象」两层结构;更细的节点树由用户在编辑器中人工组织。 - **不识别按钮/交互组件**。事件信息保留在 `legacy`,由 C 处理。 - **不迁移任何平台逻辑**。本 spec 只做静态 UI;逻辑属 C。 - **不处理图片本身**(背景拉伸只设 Widget,不做缩放/重采样;美术后续手动更换)。 - **不重命名资源为语义名**(§3.6)。 - **不做逐帧动画的时间轴**。`flags.animation` 只做标记。 --- ## 7. 与其它 spec 的接口 - **`2026-08-27-ui-asset-and-skin-design.md`**:切出的散图落入其定义的 8 个可覆盖目录;bucket 划分沿用其 §2 规则 3 的目录表;label 样式取自其 §4 的 `theme.fonts`。 - **子系统 B(引擎机制映射)→ `2026-08-30-legacy-engine-api-mapping.md`**:需要 A 产出的 `legacy.objectId` / `groupId` / `events`;其产出的属性号对照表是 C 的前置。 - **子系统 C(平台逻辑移植)→ `2026-08-30-legacy-platform-logic-migration.md`**:依赖 A 的 ObjectID → 节点路径全局索引(§1.3)与 B 的 API 映射表;并定义四层架构映射、状态归属边界、核心流程一致性检查点。 - **组件替换 spec(待写)**:`SeatView` / 按人数布局。牌桌内的 `Player_Head_Score_Layer`(60 个对象)由 A 转出静态版本后,该 spec 决定哪些改为按人数动态生成。