# 2.5D runtime 接入说明(v2.0.0 · 2.5D-only) > 自 v2.0.0 起换装间**只有 2.5D 渲染这一条可见路径**:DOM 分层纸娃娃(v6/v7 的 `drawOrder`/`layers`/B1-lite 脸部补丁)、 > 双模式开关(`config.dressUp.render` / `?render=`)、`render-config.json` 端点、静态兜底图均已删除(用户拍板的高风险方案 B)。 > v1.0.0 的双模式实现见 git 历史(`feat/2.5d-runtime` 分支 / v1.0.0 tag)。 ## 1. 行为与开关 | 场景 | 行为 | |---|---| | 浏览器支持 WebGL2 | 进入换装间即启动 2.5D runtime(按需加载当前身形 rig + 图集);无可切换的「DOM 模式」 | | 浏览器不支持 WebGL2 / context lost(永久失败的 context) | **只显示纯文本提示 + 重试按钮**(「当前浏览器/设备不支持 WebGL2,换装间需要 WebGL2;请升级浏览器或开启硬件加速(GPU 渲染)」);**不做静态图/人物兜底**,不保留第二套渲染 | | `prefers-reduced-motion: reduce` | 关闭随机眨眼/笑眼等自动动画与互动动作曲线(画面仍静态渲染;互动台词/HUD 正常) | | 宿主互动开关 | `config.dressUp.interaction.enabled`(默认 `true`)→ `client-config.json` 下发 interaction 端点;关闭/静态托管 → 页面 local-only(只播台词/动作,不扣鱼粮) | 页面 boot 不再读任何渲染配置;`client-config.json` 只含 `{version:2, interaction:{enabled,stateUrl,actionUrl}}`。 ## 2. 资产与加载 ``` docs/dress-up/art/2d5// ├── rig.json # 紧凑 rig(v2.5 cutout:65 骨;chibi ≈1.93MB raw / ≈355KB gzip;girl ≈1.86MB / ≈341KB) ├── atlas.webp # lossless 图集(v2.5 cutout 生产:chibi 4090×1739 / girl 4064×1779;单页;girl 新增 hair-back-girl/hair-arm-girl;双手 1x 精修) ├── atlas-2.webp # 可选第二页(build_2d5 多页能力保留;当前 production 为单页,无此文件) └── build-report.json ``` - **按需加载**:页面 boot `import('./runtime-2d5.js?v=21')` → `fetch(art/2d5//rig.json?v=17)` + `atlas.webp?v=17` (多页 rig 会再取 `atlas-2.webp?v=17`,请求 +1);只加载当前身形,切身形时加载目标 → 激活 → 释放旧 GL 资源(参数/物理/时间保留)。 - **资产版本**:rig/atlas 的 fetch URL 带 `?v=17`(由 app.js 的 `RUNTIME_2D5_ASSET_VERSION` 传给 runtime);页面模块版本 **v21**。 资产内容不变时不必改资产版本;替换资产(M4 production / 后续)时同步提升资产版本。 - **构建(W0-a 起管线入仓,资产可复现)**: ```bash npm run fetch:sources # 下载 assets-src-v1.tar.zst(GitHub Release tag assets-v1,~94MB / 解包 155MB) # → 解到 .cache/assets-src/(tarball sha256 + manifest 逐文件校验,gitignored) npm run build:assets # tools/art/build_prod.py --build-atlas # → tools/art/dist/{chibi,girl}/(gitignored;中间产物 .cache/build/) ``` `tools/art/build_prod.py` 从源根 `/` 读 art(M2 cutout)/ m4(production 模板)/ feet(P2)/ hands(T2)/ prod(P3 seamfill+rest 参考),组装 cutout source rig 后调用 `tools/art/build_2d5.py` (best-fit 图集打包 + UV 重映射 + 定点化 + 逐页 `pixelEqual`/UV/引用完整性校验;失败退出码 1,不要手改产物)。 构建确定性:同源双跑 `rig.json`/`atlas.webp` md5 一致;与官方 `docs/dress-up/art/2d5` 逐字节一致 (`npm run verify:full` 校验,源缺失时跳过)。源包布局/命令/已知限制见 `tools/art/README.md`。 多页(cutout 备选):输入 rig 提供 `atlasPages` + `drawables[].atlas`(≤2 页、单张 ≤4096×4096), 旧单图集输入输出形状不变;cutout 全画布纹理可用 `--trim-alpha` 按 alpha bbox 裁剪。 - 首屏 payload(headless,gzip,单页图集,v2.5):runtime 2 个 JS ≈32KB + rig **≈356KB** + atlas **≈1.91MiB ≈ 2.29MiB**(chibi)/ rig 341KB + atlas 1.62MiB ≈ 1.98MiB(girl); 请求数 **4**(2 JS + rig + atlas)。 - **DIY 参考掩膜**:`art/diy/masks/chibi/{body,hair,arms}.webp` 是 DIY 本地成层算法(fill-under-hair/拆袖)的不可见输入, 不是渲染资产/人物图层;只有 `docs/dress-up/diy-*.js` 引用。 ## 3. runtime API(`docs/dress-up/runtime-2d5.js`) ```js import { createRuntime2D5 } from './runtime-2d5.js?v=14'; const rt = await createRuntime2D5({ canvas, assetRoot: 'art/2d5/', assetVersion: '13', quality: 'auto', device }); await rt.setShape('chibi'); // 按需加载 + 激活(切身形释放旧资源,参数/物理/时间保留) rt.setLook({ outfit, top, bottom }); // 套装优先;否则上衣+下装动态组合(缺件回退 + warnings) rt.setParams({ headY: 6, breath: 0.8 }); // 参数总线(见 §4) rt.setHeadFollow(nx, ny); // 指针跟随(-1..1;null=回正) rt.triggerBlink(); rt.triggerSmile(); rt.triggerTalk(1.2); rt.setTalking(true); rt.tap(); rt.setQuality('low'|'medium'|'high'|'auto'); rt.setDPR(1.5); rt.setPhysicsHz(30); rt.setMaskPass(false); rt.setMipmaps(true); rt.setStatic(true); rt.setAuto(false); rt.start(); rt.stop(); rt.setPaused(hidden); rt.freeze(true); // 冻结后手动 step/advance 确定性截图 rt.step(1/60); rt.advance(60, 1/60); rt.renderFrame(); rt.renderToCanvas(ctx2d, x, y, w, h); // 立绘卡合成(当前帧 → 2D canvas) rt.toDataURL({ width: 184, height: 328 }); // 悬浮鲸鱼小图(透明底 dataURL) rt.getMemory(); rt.getQuality(); rt.getState(); rt.getAssetInfo(); rt.getTickInfo(); rt.destroy(); ``` 纯逻辑(画质档/参数钳制/换装组合/可见 drawable/face 状态机/固定步长)在 `runtime-2d5-core.js`,node 单测覆盖。 ## 4. 参数总线(M5 互动接口) `setParams(partial)` 接受键(越界钳制,未知键忽略):`headX` / `headY` / `headZ`(±25°)、`breath`(0–1)、 `eyeOpen` / `eyeSmile`(0–1)、`mouthOpen`(0–1)、`hairSway` / `skirtSway`(0–1)。 另有临时通道:`setTalking(true)` 口型循环、`triggerBlink/Smile/Talk`、`tap()`(轻点头+笑眼+眨眼)。 参数曲线/动作状态机在 `interaction-model.js`,由 app.js 的 `playAction()` 驱动。 ## 5. DIY 静态贴片(v2) - DIY 本地衣柜逻辑不变(IndexedDB 记录 → 注入 `manifest.items`,`item.layers[shape][slot] = blobURL`)。 - 选中 DIY 条目时:runtime `freeze(true)`(暂停物理/动画)+ canvas 2D overlay 按槽位顺序 (`bottom → topBody → sleeve`,见 `wardrobe-core.diyLayerUrls`)叠加全画布 675×1200 位图; UI 提示「DIY 造型为静态,不支持骨骼动画」;**不允许退回 DOM**。 - 立绘卡/悬浮鲸鱼会把静态帧 + DIY 贴片一起合成;切回非 DIY 条目恢复动态。 - DIY 记录 `shape=chibi`:girl 身形下选中 DIY 条目会提示「DIY 单品只做了 Q 版」并用普通 2.5D 造型。 ## 6. 页面接线(`docs/dress-up/app.js`) - `bootRuntime()`:2.5D 是唯一路径;失败 → `showRuntimeUnavailable()` 只显示文本提示(可重试)。 - `applyState()` → `sync2d5()`(setShape/setLook/DiyStatic/renderFrame)+ 背景/特效/衣柜/立绘卡元信息刷新。 - `say()` → runtime 口型;`pointermove` → head follow;角色点击 → 头部摸头/身体 tap;`saveCard()` → `renderToCanvas` 合成; `composePetImage()` → runtime 帧(+ DIY 贴片)→ `toDataURL` 写悬浮鲸鱼小图。 - E2E/调试入口:`window.__feiyu2d5`(ready/runtime/failure/unavailable/retry/getState/getMemory/diyMode/ diyOverlayVisible/simulateDiy/clearDiy/freeze/setAuto/advance/step/resources)。 ## 6.5 cutout 槽位与多图集(v2.2 起已合入 production 资产;v2.5 为 girl 全量拆发/双手精修资产) B 方案(Live2D/Spine 式 cutout 素体重制)已落地:v2.2 production 资产(M4b)使用 **单页图集** (v2.5:**65 骨**(59 前缀 + 6 legBones,原索引不变)、chibi 4090×1739 / 85 drawables、girl 4064×1779 / 86(+hair-back-girl、hair-arm-girl)、单页 pixelEqual),runtime/core 同时保留 **多图集(≤2 页)能力**(`rig.atlases` + `drawables[].atlas`)供后续 cutout 拆分/扩展使用。v2.1 旧 rig 与旧 DIY pack 继续兼容。 | 槽位 | z | 语义 | |---|---:|---| | `legs` | 9 | 腿/脚 cutout 部件(先绑 hips,裙子后面) | | `body` | 10 | 躯干(原 body 槽位语义不变) | | `armUpper` / `armFore` / `hand` | 45 / 48 / 65 | 上臂 / 前臂 / 手(服装 sleeve 之下/上按 z 排) | | `collar` | 56 | 领/胸饰/围裙等独立层(服装槽位,随 outfit) | | `sleeveUpper` / `sleeveFore` | 60 / 62 | cutout 袖子分段(手 z65 在袖口外) | - `BODY_DRAW_SLOTS`:`hairBack/strands/body/legs/arms/armUpper/armFore/hand/hair/hairFront/collar`(来自 `rig.slots` 常驻层)。 - `OUTFIT_SLOTS`:旧 `topUnder/bottom/topBody/sleeve` + 新 `sleeveUpper/sleeveFore/collar`;outfit 可同时给多槽位。 - **多图集**:rig 用 `atlases: [{id,file,width,height}, …]`(≤2 张);每个 drawable 用 `atlas` 指定 id/下标/文件名, 缺省第一张。旧 rig 的 `atlas: {file,width,height}` 单图集继续加载;旧 `getState().look.slots` 的 4 个键语义不变 (cutout 新槽位仅在非空时追加,旧路径字节级不变)。加载请求 +1/张图集(首屏 ≤2 张)。 - `resolveLook` 输出 slots 为 7 键全集;runtime `getState().look.slots` 对旧 rig 仍只返回 4 键。 - DIY 增量 pack 兼容:`topBody/bottom/sleeve/topUnder` 语义不变;新槽位 `sleeveUpper/sleeveFore/collar` 可直接写入 pack, 旧别名 `arms/arm/sleeves → sleeve` 自动映射;`addOutfitPack/removeOutfitPack` 与多图集 scene 兼容(DIY 纹理独立)。 - 校验:`normalizeAtlases`(≤2、重复 id/缺 file 报错)、`resolveDrawableAtlas`;core 单测覆盖。 ## 7. 已知限制(v2 初版 / v2.2 cutout 补充) 1. **无 WebGL2 用户失去换装间**(用户已接受):只有文字提示,无静态人物/兜底图。 2. **DIY 为静态贴片**:贴片叠在 2.5D 静态帧最上层,可能盖住个别手臂/发丝细节;无骨骼动画/物理。 3. **真机性能未测**:所有 FPS/CPU/内存为服务器侧 headless(SwANGLE 无 GPU)数据,不可代表真机;不做 60fps 承诺。 4. 背景/特效仍在 DOM/CSS 层(透明 canvas 只画角色);换背景不触发 canvas 重绘。 5. 历史文档(PLAN-2.5D-RIG / LAYER-MODEL / DIY-UPLOAD 等)已归档到 `docs/archive/`(DOM 时代章节退役,仅作历史参考)。 ### v2.2 cutout 补充(数字为 headless 测量,真机未测) - 显存:单身形图集 chibi **27.13MiB**(含 mipmap 36.18)/+115% vs v2.1 17.14MiB;girl 23.86MiB(mipmap 31.81)。 已有 quality 降级档;真机显存/带宽未测。 - 静置未解释差异:chibi 0.21–0.33% / girl 0.16–0.19%(1–2px 边缘级;4x 无可见异常,已接受)。 - 4x 存在 1px 细接缝(1x 不可见,已接受);girl yaw/arm inside-body 225–281px(0.028%,已接受)。 - 多页图集构建能力可用,但当前 production 为单页;若后续拆分两页会多 1 次请求与打包留白开销(M3b 实测 +40% 像素)。 - 真机未测;DIY 增量 pack 的 59 骨顺序与 v2.1 一致(已核对),加载/删除回归通过。 ### v2.3 手/脚精修补充(m2-rig P2 QA,headless) - 静置 6×2 洞 0;脚部重切仅 RGB 改变、alpha 与轮廓不变;手脚 4x 无明显异常(真机未测)。 - 资产尺寸/drawables/显存与 v2.2 相同(chibi 27.13MiB、girl 23.86MiB;atlas 字节数略变,+3.6KB/+8.2KB)。 - 资产版本 15、页面模块 v19;旧 DIY pack(v2.2 refs)与旧 rig 兼容策略不变。 ### v2.4 legBones / girl 后发(m2-rig P2/P3 QA,headless) - 骨架 65 骨(59+`thighL/shinL/footL/thighR/shinR/footR`,原 59 骨索引不变);runtime shader `MAX_BONES 64→80`。 旧 59 骨 DIY pack 在 65 骨场景按「前缀骨架」兼容(`legacyBones=true`),超出/骨名不匹配仍拒绝。 - 腿部 capsule collider 跟骨字段对齐 m2-rig:`aBone/bBone + aLocal/bLocal + legBoneChain`(legacy `a/b/radius` 保留); runtime 支持端点绑骨,无字段时行为不变。 - girl 新增 `hair-back-girl`(slot hairBack,z=10.5):从 body 部件拆出 **5,117 px**(其余 ~19k 发色 px 在 tail/前发/衣下, **不是全量后发独立层**);rest 与 v2.3 差 girl 106–110px(>16/255,0.013%,新发层 AA 边)、chibi 0 px; yaw/roll ±25 QA 洞 0(m2-rig POC runner 证据)。 - 膝部水平细线为 M4b 腿件拼合缝(非 legBones 引入),±30° 更明显;真机未测。 ### v2.5 girl 全量拆发 / 双手精修(T1+T2 合并,headless QA) - **girl 新增 `hair-arm-girl`**(slot `strands`,z=66,role strand,图集裁剪 414×116):把 production `arm-upper-L` 纹理里 被烘焙的 **3,207px 侧发碎片**(13 个 alpha 岛,蓝色发丝+阴影/描边)拆成独立发层,权重/deform 取最近 production 发束顶点, **不新增骨**(65 骨不变);头转/歪头 ±25° 时碎片与相邻发束的位移差从均值 **46.5px → 0.5px**(yaw/roll ±25,浏览器外 rig 级 LBS + 4x 证据)。`arm-upper-L` 岛 alpha 保留、RGB 按「拿掉该发层后的 rest 合成色」重绘 → 不留背景洞。 - **P3 被遮区补画 bug 修复**:旧 BFS 源缓冲不收敛导致底层残留蓝色污染(~4.9k hidden px);改为收敛 nearest-fill + Telea fallback, `hair-back-girl` mesh deform 按真实蒙皮权重重算(P3 留 headW=0)。rest 可见区恢复 pre-P3 原色(girl rest 与 v2.4.0 差 215–222px/帧 >16/255,未解释 0–2px、alpha 差 0,≤300 门禁 PASS)。 - **tail 不拆**:预扫的 18,826「发色」px 实为鲸鱼尾鳍本体(应随尾链动);tail 纹理内真发仅 527px old-white-hair 垫层, 静置 521/527 被静态层盖住,**本轮保留**(删掉后尾摆可能露洞,未做尾摆洋红门禁)。 - **双手 1x 精修**(T2):alpha 逐像素不变(1x alpha IoU=1.0),仅手部 mask 内 RGB 替换(chibi 446px/纹理、girl 714px/纹理 >6/255), 预期手区域 114px(chibi)/57px(girl)渲染变化,无重影/双描边/轮廓漂移;**收益温和**(1x 几乎无感,如实)。 AI 整块重绘因轮廓 IoU 0.927–0.956(<0.98 门禁)被拒绝,只作内部高频细节源。 - **契约**:girl drawables 85→**86**、图集 4094×1721→**4064×1779**、girl 纹理 26.88→**27.58MiB**(mipmap 35.84→36.77)、 rig +18,553B / gzip +4,034B;chibi 仅手部 RGB(drawables/图集尺寸/显存不变)。资产版本 **17**、页面模块 **v21**; 65 骨/槽位/物理/旧 DIY pack 前缀兼容全部不变。 - **未做/风险**:手部 2x 实验变体(A2)因收益≈噪声级且 VRAM +0.88–1.10MiB 未进发布;tail 大幅摆动下的洋红洞门禁未做; 真机未测(headless SwANGLE)。