# 切包计划:把素材从插件主体拆出去 状态:**规划中,未实施** 提出原因:1.1.0 的 npm tarball 已达 **198.9 MB**,npm 单包上限 200 MB, 再加任何素材就会发布失败。这不是"以后再优化"的问题,而是**下一次改动就会 撞墙**的硬约束。 --- ## 一、问题量化 ### 当前包内容(1.1.0 实测) | 组件 | 未压缩 | 说明 | |---|---:|---| | `expansion_v1/presets/` | 151 MB | 10 套造型的帧(含 120 帧烘焙拖拽) | | `expansion_v1/actions/` | 37 MB | 默认外观的 64 个扩展动作 | | `library_v1/` | 48 MB | 默认皮肤(23 个动作 + live rig + drag poses) | | `pet/mimi_app/src/` | 2.1 MB | Python 运行时 | | `docs/` + `scripts/` + `lib/` | 2.2 MB | 文档、管线脚本、JS 插件层 | | **合计(会进包)** | **≈240 MB** | 压缩后 198.9 MB | ### 关键事实 - npm **压缩后** 上限 200 MB,1.1.0 是 198.9 MB → **仅剩 1.1 MB 余量** - 单套造型 14–16 MB,也就是说**再加一套造型就发不出去** - 已排除项:`*.bak-*`、`*.tmp`、`__pycache__`、`node_modules`、`.trimmed/`、测试 ### 为什么不选"再压一压" | 尝试 | 结果 | |---|---| | 烘焙帧降到 6 帧(从 24) | 已做,省约 4 MB | | webp 降 quality 92→85 | 既有素材已是高质量压缩,重压**反而变大**(实测 104–120%) | | 透明区填统一 RGB | 无收益(实测 99–100%) | | 删未使用动作 | 库内 23 个默认动作**全部**被源码引用,无可删 | **结论:素材总量本身已经压不动了,必须拆包。** --- ## 二、目标与非目标 ### 目标 1. 主包(`mimi-desktop-pet`)回到 **20 MB 以内**,留出充足迭代空间 2. 素材独立发布、独立版本号,可以只更新造型不动代码 3. **现有用户升级无感**:不手动执行任何命令也能用上造型 4. 离线/弱网可用:素材下载失败时插件仍能启动(降级到默认皮肤) ### 非目标 - 不做细粒度按套安装(用户不该关心"我要装哪几套") - 不引第三方 CDN(素材变更频率低,npm 足够;且要避免外部依赖) - 不改素材格式/分辨率(本次只搬位置,不动内容) --- ## 三、拆包方案 ### 3.1 包划分 | 包名 | 内容 | 预估体积 | |---|---|---:| | `mimi-desktop-pet` | `lib/`、Python 运行时、docs、管线脚本 | **≈6 MB** | | `mimi-desktop-pet-assets` | `expansion_v1/`(presets 151 + actions 37) | 188 MB → 压后 ≈155 MB | | `mimi-desktop-pet-skin` | `library_v1/`(默认皮肤) | 48 MB → 压后 ≈40 MB | > 备选:`assets` 与 `skin` 合成一个 `mimi-assets` 包(≈195 MB 压缩后), > 仍贴近上限且不利于分批更新,**不推荐**。分两个包各自都有余量。 ### 3.2 分辨率与版本 - 三个包**同版本号**发布(`1.2.0`),但 `assets`/`skin` 允许单独 patch 升级 - 主包在 `package.json` 里声明**可选依赖**: ```json "optionalDependencies": { "mimi-desktop-pet-assets": "^1.2.0", "mimi-desktop-pet-skin": "^1.2.0" } ``` 用 `optionalDependencies` 而不是 `dependencies` 的理由:安装失败时 npm 不会让整个插件装不上,符合"素材缺失仍能启动"的目标。 ### 3.3 运行时查找顺序 素材路径解析集中在一处(当前是 `config.load_config()` 的 `MIMI_ASSET_ROOT` / `asset_manifest`),改为按序探测: 1. `MIMI_ASSET_ROOT` 环境变量(手动指定,最高优先级,保持现有行为) 2. `node_modules/mimi-desktop-pet-assets/expansion_v1/`(npm 装好时) 3. `/mimi-assets/expansion_v1/`(插件自动下载缓存) 4. 包内 `pet/assets/...`(开发期 / 已内置的旧版本) 5. 都没有 → 只用默认皮肤,造型菜单显示"素材未安装" 每级命中都记一行 `dbg()`,便于用户排查。 ### 3.4 首次启动自动下载(关键决策) 两种做法,**推荐 B**: | | A. 纯 optionalDependencies | B. optionalDeps + 插件兜底下载 | |---|---|---| | 依赖 npm 装成功 | 是 | 否 | | 装包体积 | 装的时候就 200 MB | 主包 6 MB,素材按需拉 | | 失败可恢复 | 需用户重装 | 插件可重试/提示 | | 实现复杂度 | 低 | 中(需下载 + 校验 + 解压 + 断点续传) | **推荐 B**:主包保持轻量发布,`optionalDependencies` 只作为"顺手装好"的快路径; 真正保证是插件启动时检查素材目录,缺失就后台从 npm registry 拉 tarball (`https://registry.npmjs.org/mimi-desktop-pet-assets/-/mimi-desktop-pet-assets-.tgz`), 校验 shasum 后解压到 `/mimi-assets/`。 进度反馈:头顶气泡显示"正在准备造型素材…",失败则降级并给一次性提示 (不重复打扰),设置页提供"重新下载素材"按钮。 --- ## 四、实施步骤 按依赖顺序,每步可独立验证、可回滚。 ### 阶段 0:准备(不改行为) 1. 抽出 `AssetLocator`:把现在散在 `config.load_config()`、 `qt_app.run()` 的素材路径拼装收敛到一个模块 2. 加日志:每一级探测都打印命中/未命中原因 3. **这一步不改包结构**,先确保现有 210 项测试全绿 ### 阶段 1:拆包骨架 4. 新建 `packages/assets/`、`packages/skin/`,`git mv` 素材进去, 各自写 `package.json`(含 `files`、`version`、`license`) 5. 主包 `files` 移除 `pet/assets/characters/mimi/expansion_v1/**` 等 6. 主包加 `optionalDependencies` 7. 本地用 `npm pack` 验证三个包体积符合预期 ### 阶段 2:运行时 8. `AssetLocator` 按 3.3 的顺序探测,覆盖所有调用点 (`action_library`、`outfits`、`image_cache`、`asset_prefetch`、`rig_model`) 9. 素材缺失时的降级路径: - 造型菜单显示"素材未安装"而非空列表 - `ActionLibrary` 只用内置的默认动作,不崩 10. 测试:模拟"只有主包"的目录结构,断言能启动、能交互、不抛异常 ### 阶段 3:下载器(若采用方案 B) 11. 后台线程下载 tarball + 校验 + 解压(注意:不能阻塞主线程, 现有 `AssetPrefetcher` 已经是这个模式,可复用) 12. 断点续传/重试(至少 3 次退避) 13. 设置页入口 + 气泡提示 ### 阶段 4:发布 14. 三包同版本发布(先 assets/skin,后主包,避免主包先更新时找不到依赖) 15. 主包 CHANGELOG 明确写"素材拆包 + 自动下载" 16. 保留 `1.1.0` 作为 rollback 点(它是最后一个"全内置"版本) --- ## 五、风险与对策 | 风险 | 影响 | 对策 | |---|---|---| | 下载失败 / 公司网络拦截 npm | 只剩默认皮肤 | 降级可用 + 可重试 + 文档提供手动安装说明 | | 素材包版本与主包不匹配 | 动作 id 对不上 | 主包启动时校验 assets 的 `manifest` schema_version;不匹配则拒绝加载并提示 | | 用户已有旧版(素材在内置路径) | 路径冲突 | 探测顺序里包内路径优先级低于外部,避免旧素材盖新素材 | | 下载中途崩溃留下半个目录 | 下次启动崩 | 解压到临时目录,校验完成后 `os.replace` 原子换入 | | npm 上两个新包命名冲突 | 发布失败 | ✅ 已确认可用(2026-10-31 查 registry 均返回 404):`mimi-desktop-pet-assets`、`mimi-desktop-pet-skin` | | 主包先发、素材包后发 | 用户装不到素材 | 发布顺序:assets/skin → 主包;CI 里加前置检查 | --- ## 六、验收标准 切包完成后必须全部满足: - [ ] 主包 tarball **≤ 10 MB**(目标 6 MB) - [ ] `npm i mimi-desktop-pet` 后首次启动能自动拉到素材并正常显示造型 - [ ] 断网启动:不崩、能交互、造型菜单给出明确提示 - [ ] 手动删除素材目录后再启动:能自愈 - [ ] 手动指定 `MIMI_ASSET_ROOT` 仍可覆盖(保持开发者工作流) - [ ] 210 项单元测试 + 24 项验收全过 - [ ] 造型 × 交互矩阵仍是 242/242 - [ ] 从 1.1.0 升级到 1.2.0:用户无需手动操作 --- ## 七、待决策项(需要拍板) 1. **下载 vs 纯 optionalDependencies**:推荐 B(自动下载),但要接受 多一个网络依赖和一套下载组件。若希望"零网络行为",选 A。 2. **素材包粒度**:两个包(assets / skin)还是一个(合成 ≈195 MB,仍偏大)。 推荐两个。 3. **版本策略**:三包严格同版本,还是允许素材独立演进? 推荐"同版本发布、素材可单独 patch"。 4. **旧版兼容**:1.1.0 是最后一个全内置版本,是否在 1.2.0 里保留 内置素材 6 个月作为回退?(会让主包重新变大,倾向**不保留**) --- ## 八、附:为什么不干脆压缩素材 已实测,全部无效或副作用: | 手段 | 实测结果 | |---|---| | 烘焙拖拽帧 24 → 6 帧 | 已采用,省 ≈4 MB;6 帧帧间差 6.7 反比 8 帧的 8.1 平滑 | | webp quality 92 → 85/80/75 | 单帧 99%/81%/72%/66%,但既有素材已是高质量编码,重压 104–120%(**变大**) | | 透明区 RGB 统一 | 99–100%,无收益 | | 删除未引用动作 | 默认库 23 个动作全部被源码引用,删不得 | | lossless → lossy | 已是 lossy | 素材是 1.6 万张 webp,总量已接近该分辨率的合理下限。 **唯一可行方向就是拆包。**