# 旧 gameabc 渲染/精灵 API → Cocos 映射(子系统 B·渲染子集) > 状态:已与用户确认。 > 适用工程:`cocoscreator_projects/YouleNexus`(框架真源)。 > 数据源:`projects/Game_Surface_3/js/gameabc.min.js`(逐行逆向,行号见各节引用)。 > 关联:`2026-08-28-legacy-ui-migration-design.md`(子系统 A)——帧序/切帧/`FrameSet`/图集命名 的**权威定义在 A 的 §0.3、§3.2、§3.4**,本文只引用、不复写。 > 范围:子系统 B 中「渲染与精灵」相关的一小撮 `ifast_*` API + 支撑它们的 `set_self`/`get_self` 属性号 + 图片资源映射。**不含**网络(`ifast_ws`/`ifast_tcp_*`)、输入(`ifast_input`)、工具(`ifast_split` 等)——那些归 B 的后续章节,另立。 --- ## 0. 最高原则(两条并列,贯穿全文) 旧引擎是**单 Canvas 立即模式**:每个精灵每帧把它的图 `drawImage` 到共享画布,`ifast_*` 都是「即时画一笔」。Cocos 是**保留式场景图 + 组件**:节点建一次,改值只改属性。 > **原则一 · 原理等价**:把「每帧即时绘制」翻译成「一次性建节点/挂组件 + 按需改属性」;能用一个现成组件的,绝不搬旧引擎的手工绘制。 > **原则二 · 表现等效,原理自由**:**一律采用 Cocos 高效、现代化、行业标准的处理方式和解决方案,不受旧引擎实现原理的束缚。** 只要做到**等效旧引擎的表现**即可——原理、处理方式可以完全不同。旧引擎的手工做法(逐位画数字、手算进度条源宽、手动 `spaceY*i` 排列表项)是「无引擎能力」的替代品,Cocos 有原生组件时**必须用原生组件**,不照搬替代品。 本文 §5 的每一条映射都给出「平替」与「更优(Cocos 行业标准)」两档,落地**默认取更优档**;「平替」仅在原生组件确实不适用时作为兜底。典型落实:进度条用 `ProgressBar`(§5.1)、图片数字用 `Label`+BMFont(§5.4)、列表用 `Layout`+`ScrollView`+`NodePool`(§5.5)。 --- ## 1. 渲染模型差异(这是所有映射的根) | 维度 | 旧引擎(gameabc) | Cocos Creator 3.8 | |---|---|---| | 渲染方式 | 立即模式,每帧重画全部精灵 | 保留式,场景图 + 组件,脏标记局部重绘 | | 精灵本体 | `GameObject`(`gameabc.min.js:1534`),字段全平铺 | `cc.Node` + 组件(`Sprite`/`Label`/`UIOpacity`…) | | 定位 | `obj.x/y/w/h`(左上原点,Y 向下) | `node.position` + `UITransform.contentSize`(中心原点,Y 向上) | | 图片 | `obj.image`(HTML `Image`),按 `recid` 从 `ImageFileList` 取 | `Sprite.spriteFrame`(`SpriteFrame`,来自 Auto Atlas) | | 文字 | `obj.caption`,引擎 `fillText` 画 | `Label.string` | | 显隐 | `obj.visbale` | `node.active` | | 帧动画 | `obj.frame`(1 基帧号),画对应源矩形 | `FrameSet.setFrame(n)`(A §3.4) | | 「精灵上再画一张图」 | `ifast_mydrawbmp` 即时叠加 | **子 `Sprite` 节点**(一个 Sprite 只能一张图) | 坐标系换算(左上→中心)不是本文职责,权威公式在 A §2.2;本文只负责「API 语义 → Cocos 等价物」。 --- ## 2. `GameObject` ↔ `cc.Node`(数据结构映射) `GameObject`(`gameabc.min.js:1534-1719`)是一个大而全的平铺对象;`ifast_getobj(spid)`(`:3086` / `:3888`)返回 `gameabc_Object["sp"+id]`(`id<1` 时是画布根 `gameabc_face.obj`)。**`spid` 就是 `ObjectID`**,与 A 的 `legacy.objectId` 同一体系。 对应关系(只列渲染相关,其余字段见 §3 属性号): | GameObject 字段 | 含义 | Cocos | |---|---|---| | `x` / `y` | 左上坐标 | `node.position`(注意坐标变换,见 A §2.2) | | `w` / `h` | 显示宽高 | `UITransform.width / height` | | `image` | 当前 HTML `Image` | `Sprite.spriteFrame` | | `recid` | 图片资源号 | `SpriteFrame` 的来源(§4) | | `caption` | 文字 | `Label.string` | | `frame` | 当前帧号(1 基) | `FrameSet`(A §3.4) | | `visbale` | 可见 | `node.active` | | `canclick` | 可点击 | `Button.interactable` | | `z_Order` / `z_index` | 绘制序 | 节点树 sibling 序(A 用 `siblingIndex`) | | `scale` / `ap` | 缩放 / 透明度 | `node.scale` / `UIOpacity.opacity`(§3) | > 命名陷阱(重要):`ImageFileList[recid]` 里的 `w`/`h` 是**网格列数/行数**,`w1`/`h1` 是**每帧像素宽高**(见 `gameabc_getrect2` `:3958-3961`);而 `gameabc_Object["sp"+id]` 里的 `w`/`h` 是**精灵显示宽高**。两个对象字段同名、含义不同,写映射代码时**必须**分清「资源元数据」和「精灵实例」。 --- ## 3. `get_self` / `set_self` 属性号对照表 `set_self(id, cpid, val, mode, val2)`(`:3261`)与 `get_self(id, cpid, val, mode, val2)`(`:3154`)是平台层 1773 处调用的通用读写。`cpid` 是数字属性号,`mode` 经 `gamabc_getval(val, val2, mode)`(`:3895`)决定 `val2` 如何作用到当前值: | mode | 语义 | 说明 | |---|---|---| | 0 | 直接赋值(`set`)/ 读原值(`get`,内部转 `-1`) | 最常用;三参调用 `set_self(id,cpid,val)` 默认 mode=0 | | 1 | `当前 + val2` | 相对位移 | | 2 | `当前 − val2` | 相对位移 | | 3 | `⌊当前 × val2⌋` | 相对缩放 | | 4 | `⌊当前 ÷ val2⌋` | 相对缩放 | **mode 实证结论(2026-08-30)**: - 全平台层 1773 处 `set_self` 中,**只有 mode=1(加法)被使用**,约 27 处(21 处 x/y 坐标相对位移 + 6 处 caption 的 mode 无效);`mode=2/3/4`(减/乘/除)**零使用**;`get_self` 亦无 mode 相对运算。 - mode=1 全部是坐标相对位移:`set_self(spid, 18/19, offmovex/offmovey, 1, 0)` → `node.x += offmovex`。**C 侧无需专门相对运算辅助方法**,直接 `node.x += off` 即可。 - ⚠️ **`set_self` 的 `switch` 分两类**:`case 6/7/16/28/58` 是**直接赋值、不走 `gamabc_getval`**,它们的 `mode`/`val2` 参数**无效**。最典型是 `set_self(spid, 7, text, 1, 0)`(caption)——那个 `1` 是历史冗余,与 `set_self(spid, 7, text)` 等价。C 移植时**只对「走 `gamabc_getval` 的 case」关心 mode**,其余一律当直接赋值。 属性号 → Cocos 映射(**✅ 已实证** = 由 `get_self`/`set_self` 的 `switch` 逐 case 确认;**⚠️ 推断** = 由字段默认值/命名推断,落地前须确认;**❓ 待逆向** = 语义未明,不得臆测,C 移植时按需补齐): | cpid | 旧语义 | 读 | 写 | Cocos | 置信 | |---|---|---|---|---|---| | 1 | 图片资源号 `recid` | `gamabc_getval(obj.recid,…)` | 设 `recid` 并触发 `gameabc_load_img2` 换 `image`(`:3311-3320`) | `sprite.spriteFrame = 帧`(recid→帧见 §4) | ✅ | | 7 | 文字 `caption` | 返回 `caption`,或 `measureText` | `val.toString()` 存入 `caption`(`:3297-3309`) | `label.string` | ✅ | | 18 | 坐标 x | `obj.x` | `obj.x` | `node.x`(经 A §2.2 换算) | ✅ | | 19 | 坐标 y | `obj.y` | `obj.y` | `node.y` | ✅ | | 20 | 宽 w | `obj.w` | `obj.w` | `UITransform.width` | ✅ | | 21 | 高 h | `obj.h` | `obj.h` | `UITransform.height` | ✅ | | 28 | 矩形 `{x,y,w,h}` | 返回四值对象 | 一次设四值(`:3279-3286`) | 同时设 `position` + `contentSize` | ✅ | | 37 | 可见 `visbale` | `obj.visbale` | `obj.visbale` | `node.active` | ✅ | | 43 | 当前帧 `frame`(1 基) | `obj.frame` | `obj.frame` | `FrameSet.setFrame(n)` | ✅ | | 44 | 分组 `groupid` | `obj.groupid` | `obj.groupid` | A §2.1 已并入父节点,无需单独映射 | ✅ | | 58 | 颜色 `color` | `obj.color` | `obj.color` | `Label.color` / `Sprite.color`(按节点类型) | ✅ | | 33 | 缩放 `scale`(百分比,100=原始) | `obj.scale` | `obj.scale` | `node.scale`(`= val / 100`) | ✅(`gameabc_charge:4963-4967` `ctx.scale(val/100)`) | | 41 | 可点击 `canclick` | `obj.canclick` | `obj.canclick` | `Button.interactable` | ✅ | | 16 | 引用 `f`(父/关联对象) | `obj.f` | `val<=0→0`,否则 `ifast_getobj(val)` | 节点引用(父节点 `node.parent` 或业务引用) | ⚠️ 推断(引用语义,具体用途待 C 侧确认) | | 6 | 图片对象 `image`(底层) | `obj.image` | 直接塞 HTML `Image` | 一般不用;用 cpid=1 换 `spriteFrame` 即可 | ✅(但少用) | | 35 | 透明度 `ap`(0–255,默认 255) | `obj.ap` | `obj.ap` | `UIOpacity.opacity` | ✅(`gameabc_charge:4949-4956` `globalAlpha=ap/255`;**平台层实际使用**:按钮按下/选中态) | | 34 | 旋转 `arge`(度,绕中心) | `obj.arge` | `obj.arge` | `node.angle` | ✅(`gameabc_charge:4944-4946` `rotate(arge·π/180)`;引擎已实现,本项目无调用) | | 36 | `hu` | `obj.hu` | `obj.hu` | **忽略** | ✅(引擎**空字段**:`gameabc_charge:4962` `if(obj.hu!=0){}` 无任何作用) | | 45 / 46 | 变换中心偏移 `cx` / `cy`(像素,默认 0) | `obj.cx` / `obj.cy` | 同左 | 调整 `anchorPoint`(≈ `(0.5+cx/w, 0.5−cy/h)`,注意 Y 向) | ✅(`gameabc_toclient:4926-4930` `translate(w/2+cx, h/2+cy)`;本项目无调用) | | 51 | 镜像 `jx`:1=垂直翻转、2=水平翻转 | `obj.jx` | `obj.jx` | `node.scale.y=-1` / `node.scale.x=-1` | ✅(`gameabc_charge:4969-4980` `scale(1,-1)`/`scale(-1,1)`;本项目无调用) | | 57 | 定时器间隔 `ontime`(毫秒,0=停) | `obj.ontime` | 设 `ontime`,为 0 时清 `click_time` | `component.schedule(cb, ms/1000)` / `unschedule` | ✅(`drawone:1878-1898` 触发 `ontimer`/`ontimer_`;**平台层实际使用**:登录等待/VIP 刷新/倒计时) | > **逆向结论(2026-08-30)**:上表属性号已全部逆向。其中 `arge`(34) / `hu`(36) / `cx`·`cy`(45/46) / `jx`(51) 引擎**实现了渲染逻辑但本项目未用**——`gameabc_Object.json` 静态数据无这些字段、平台层与子游戏层也**没有** `set_self`/`get_self` 调用(已 grep 全量确认)。属「引擎预留能力」,C 移植只需在碰到调用时按上表映射即可,无需专门适配。真正仍属「未定」的只有 `default` 分支的 `other[cpid]`(业务自定义属性,按需在 `gameabc.min.js` 对应处补齐后回填)。 --- ## 4. 图片资源映射:`recid` / `ImageFileList` → `SpriteFrame` 旧引擎按资源号 `recid` 从 `gameabc_Image.ImageFileList[recid]` 取图(`gameabc_load_img2` `:4597`),图可能是单帧或多帧网格。A 已把多帧图切成散图、单帧图原样保留,并约定: - 资源 `recid` 的多帧图 `00014.png`(3×4)→ 散图 `00014_01.png … 00014_12.png`(**1 基、补零对齐字典序**,A §3.2) - 散图按 bucket 落进 `framework/ui/atlas-*`,构建期由 Auto Atlas 打包(A §3.3) - 每张散图在 Cocos 里就是一张 `SpriteFrame` **帧号 ↔ 源矩形** 的换算(用于把 `ifast_mydrawbmp` 的 `(bmp_x, bmp_y, bmp_w, bmp_h)` 反推成帧号): ``` 帧宽 w1 = ImageFileList[recid].w1;帧高 h1 = .h1 网格列数 cols = .w;网格行数 rows = .h 帧号 frame(1 基)→ 源矩形: 列 = (frame − 1) % cols,行 = ⌊(frame − 1) / cols⌋ 源矩形 = (列 × w1, 行 × h1, w1, h1) 源矩形 → 帧号(逆运算,供 mydrawbmp 反推): 列 = bmp_x / w1,行 = bmp_y / h1 frame = 行 × cols + 列 + 1 ``` 权威公式在 A §0.3(已实证:通知=10、设置=11、战绩=4、背包=5、仓库=6、任务=3 六项全命中)。本文复述仅为自洽,**改动以 A 为准**。 --- ## 5. 核心 API 逐条映射 ### 5.1 `ifast_mydrawbmp(spid, recid, sp_x, sp_y, sp_w, sp_h, bmp_x, bmp_y, bmp_w, bmp_h)` — 在精灵上叠加绘制图片 **旧语义**(`:2627`):在精灵 `spid` 上、其坐标系内偏移 `(sp_x, sp_y)` 处、以 `(sp_w, sp_h)` 尺寸,叠加绘制图 `recid` 的子矩形 `(bmp_x, bmp_y, bmp_w, bmp_h)`。**不动 spid 自己的图**。`bmp_w <= 0` 时退化为 `ifast_drawtext`(画文字,§5.2)。 关键证据:`gameabc_load_img2(recid)` 加载的是 **recid** 的图,`drawImage(img, bmp_x, …, obj.x + sp_x, obj.y + sp_y, sp_w, sp_h)`(`:2639`)——坐标加的是 spid 偏移,spid 的 `image` 全程未改。 **Cocos 对应**:保留式场景图里一个 `Sprite` 只能显示一个 `spriteFrame`,所以「精灵之上再叠一张图」不能合并到同一 Sprite,**必须挂一个子 `Sprite` 节点**: ``` spid (Sprite,自己的 spriteFrame 不变) └─ childSprite spriteFrame = 子矩形对应的 SpriteFrame(§4 反推帧号) position = (sp_x, sp_y) size = (sp_w, sp_h) ``` ```ts // 伪代码(Cocos 3.8) function myDrawBmp(spid: Node, recid: number, spX: number, spY: number, spW: number, spH: number, bmpX: number, bmpY: number, bmpW: number, bmpH: number) { const frame = rectToFrame(recid, bmpX, bmpY, bmpW, bmpH); // §4 逆运算 const child = new Node('drawbmp'); child.setPosition(spX, spY); const ui = child.addComponent(UITransform); ui.setContentSize(spW, spH); const sprite = child.addComponent(Sprite); sprite.spriteFrame = loadFrame(recid, frame); // 散图 SpriteFrame spid.addChild(child); } ``` **两种典型动态用法 → 更优方案**: | 旧用法 | 旧写法 | Cocos 更优解 | |---|---|---| | **进度条**(子矩形宽随进度变,`11_GameUI.js:2912`) | `ifast_mydrawbmp(spid,96,…, bmp_w = LoadCount*416/100, …)` | `cc.ProgressBar` + `Sprite.FillType.FILLED`/`SLICED`,设 `progress∈[0,1]`,**不手算源宽** | | **动态图标/帧片段**(`11_GameUI.js:2896` 画 47 的两个子矩形) | 多次 `ifast_mydrawbmp` 叠加 | 多个子 `Sprite`(语义等价),或若就是换帧则用 `FrameSet.setFrame`(A §3.4) | > 判断「是叠加还是换帧」:看目标精灵 `spid` 自己有没有图。若 `spid` 自身无图、`mydrawbmp` 是唯一内容,等价于「给 spid 换帧」,用 `FrameSet`/`spriteFrame`;若 `spid` 有图、`mydrawbmp` 是叠加层,用子 `Sprite`。 ### 5.2 `ifast_mydrawtext(spid, recid, sp_x, sp_y, sp_w, sp_h)` — 在精灵上绘制文字 **旧语义**(`:2643`):在精灵 `spid` 上画 `gameabc_GameTxt.GameTxtList[recid]` 的 `Text`(颜色 `Color`,字号 `sp_h`,`textBaseline=top`)。`spid<=0` 时画在画布 `(sp_x, sp_y)`。 **Cocos 对应**:`cc.Label`。若文字要「贴」在某个精灵上,就挂一个子 `Label` 节点(与 §5.1 同理);若文字就是精灵自己的 caption,直接 `set_self(spid, 7, text)` → `label.string`(§3 cpid=7)。 > 注意:`recid` 在这里是 **`GameTxtList` 的索引(文字 id)**,不是图片资源号——与 `ifast_mydrawbmp` 的 `recid` 是两套 id。`set_rec`/`get_rec`(`:3089`/`:3092`)读写的就是这份文字列表(§5.6)。 ### 5.3 `ifast_mydrawsprite(spid, spidsourse, sp_x, sp_y)` — 在精灵上绘制另一个精灵 **旧语义**(`:2596`):把精灵 `spidsourse` 画到 `spid` 的偏移 `(sp_x, sp_y)` 处(临时改 `spidsourse` 坐标、`draw`、恢复)。 **Cocos 对应**:直接把 `spidsourse` 作为 `spid` 的**子节点**,设 `position = (sp_x, sp_y)`。保留式场景图下「一个节点画在另一个节点内」就是**父子关系**,无需每次临时改坐标再恢复。 ### 5.4 数字图片精灵 旧引擎**没有专门的数字 API**,数字只有两条路,Cocos 各对应: | 旧路径 | 旧实现 | Cocos 对应 | |---|---|---| | **文字数字** | `set_self(spid, 7, "积分:" + score)`(caption)→ 引擎 `fillText` | `Label.string`(系统字体 / TTF)——**平台层绝大多数数字走这条,1:1 平替** | | **图片数字** | `ifast_mydrawbmp` 逐位切数字图集的帧、手算 `bmp_x = 位×字宽` | **`Label` + BMFont(`.fnt`)**——原生数字图片精灵:一个节点、自动逐位取字模、自动对齐/字距/缩放,**彻底取代手动画子图 + 手算偏移** | **BMFont 落地的注意点**:旧数字图集是「多帧网格 PNG」(`w × h = frame_all`,A §0.3),**不是 BMFont 格式**。要用 BMFont,须把 0–9(及可能的小数点/`+`/`-`/`:`)字模重打包成 `.fnt`+`.png`。若美术暂不重打包,可降级用「逐位 `Sprite` + 数字图集帧」手动排——**这是把旧引擎手工做法搬过来,不推荐**,只作为过渡。 ### 5.5 `ifast_addtospritefromspritecopy(fspid, copyspid, x, y, tag)` / `ifast_dllpritefromspritecopy(fspid, tag)` **旧语义**(`:3556`,核心 `GameObject.addtospritefromspritecopy` `:1624`):把模板精灵 `copyspid` **深拷贝**(`copyme` 复制全部字段、共享 `image`/`uidata`)出一份副本,挂到父精灵 `fspid` 下、放到 `(x, y)`、打 `tag`、入 `addlist`,返回 id 串 `fspid+'add'+tag`。配套 `ifast_dllpritefromspritecopy`(`:3564`)按 `tag` 删除副本。 典型用法是**列表渲染**:一个模板 + 循环 N 份,`y = 首项 + spaceY*i`(房间列表 `11_GameUI.js:3707`、任务列表 `:4679`、VIP 榜 `:9629`)。 **Cocos 对应**(三层,从平替到更优): | 层级 | Cocos 做法 | 说明 | |---|---|---| | 平替 | `cc.instantiate(prefab)` + `node.setParent(parent)` + `node.setPosition(x, y)`;删除 `node.destroy()` / `node.removeFromParent()` | 模板做成 **prefab**,等价深拷贝 | | 更优 | `cc.Layout`(垂直/水平/网格) | 自动排布,**干掉 `spaceY*i` 手算** | | 更优 | `cc.ScrollView` + `NodePool`(复用,不反复 new/destroy) | 长列表滚动 + 对象池回收 | ```ts // 平替(等价深拷贝 + 定位) const item = instantiate(templatePrefab); item.setParent(listParent); item.setPosition(x, y); // 更优:对象池复用(长列表) const item = pool.get() ?? instantiate(templatePrefab); item.setParent(listParent); item.setPosition(x, y); // 交给 Layout 则连这行都省 // 用完:pool.put(item) ``` `ifast_dllpritefromspritecopy(fspid, tag)` → `node.destroy()`;要复用 → `NodePool.put(node)` / `NodePool.get()`。 > 与 A 的衔接:A §0.2 指出「991 个对象里一部分是模板、一部分是容器」,且 A **有意不识别模板/容器**(A §1.5)。识别「哪个 ObjectID 是 `ConstVal.myRoomList.bgSp` 这类模板」是 **C(平台逻辑移植)** 的职责;B 只负责给出「克隆模板 → instantiate/Layout/Pool」这条映射规则。 ### 5.6 `set_rec(stringid, stringv)` / `get_rec(stringid)` — 读写文字表 **旧语义**(`:3089`/`:3092`):读写 `gameabc_GameTxt.GameTxtList[stringid].Text`——一份**全局文字串表**(资源号是文字 id,非图片)。 **Cocos 对应**:`get_rec(id)` 读取的文本最终进某个 `Label`。文本常量本身在 C 里应由 `theme.fonts` / 文案表提供(见 `2026-08-27-ui-asset-and-skin-design.md` §4),不散落各处。**不要**为 `GameTxtList` 单独建一份全局可变文字表——它是旧引擎「无富文本能力」的产物,Cocos 用 `Label` 直接承载。 --- ## 6. 落地约定(C 移植时遵守) 1. **`spid` = `ObjectID` = `legacy.objectId`**(A §1.3),三者同源;C 侧通过 A 的 `manifest.json`(ObjectID → 节点路径)由 `spid` 定位节点。 2. **换帧统一走 `FrameSet`**(A §3.4):`set_self(spid, 43, n)` → `frameSet.setFrame(n)`,帧号同为 1 基,无需心算减一。 3. **坐标/尺寸**一律经 A §2.2 的换算后再赋值;本规范不重复坐标公式。 4. **叠加绘制(§5.1/5.2)永远用子节点**,不得试图把多张图塞进一个 `Sprite`。 5. **列表渲染(§5.5)默认上 `Layout` + `NodePool`**;只有一次性、数量少、非滚动的场景才用裸 `instantiate`。 6. 遇到 ❓ 属性号或本表未列的 API,**回到 `gameabc.min.js` 对应 `switch`/函数补齐**,再回填本表——**禁止臆测**(第二准则)。 --- ## 7. 红线与待验证 **红线** - 不手写 `.prefab`/`.scene`/`.meta`(CLAUDE.md)——本规范的落地产物仍走 MCP / 编辑器。 - 不臆测属性号语义;未定项以 ❓ 标记,C 移植时按需逆向。 - 帧序/切帧/`FrameSet`/图集命名 只以 A §0.3/§3 为权威,本文不复写。 **已解决(本次逆向坐实,2026-08-30)** - 属性号 34/35/36/45/46/51/57 语义已逆向完成并回填 §3(由 `gameabc_charge` / `gameabc_toclient` / `drawone` 源码坐实)。`ap`=透明度(0–255 → `UIOpacity.opacity`)**实证成立**,不再是推断。 - `mode` 相对运算占比已统计:全平台层仅 `mode=1`(加法)约 27 处(其中 6 处 caption 的 mode 无效),`mode=2/3/4` 零使用 → **C 侧无需相对运算辅助方法**(详见 §3)。 **待验证(落地前)** | # | 待验证 | 方法 | |---|---|---| | 1 | 图片数字是否真用 BMFont(需美术重打包字模),还是暂用逐位 Sprite 过渡 | 看子游戏层数字素材现状,与美术确认 | | 2 | `ifast_mydrawbmp` 的「叠加 vs 换帧」判定在 C 侧是否要更细(有些 spid 自身无图但语义上是「画内容」而非「换帧」) | C 移植首屏时抽样核对 |