modelstage 0.0.0-stage → 0.5.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (286) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +267 -2
  3. package/THIRD_PARTY_NOTICES.md +90 -0
  4. package/bin/modelstage.mjs +101 -0
  5. package/config/diag-upload.example.json +7 -0
  6. package/config/models/live2d.assetbundls.json +1 -0
  7. package/config/models/live2d.azurlane.json +1 -0
  8. package/config/models/live2d.azurlane_bundle.json +1 -0
  9. package/config/models/live2d.azurlane_spine.json +1 -0
  10. package/config/models/live2d.bd2.json +1 -0
  11. package/config/models/live2d.bluearchive.json +1 -0
  12. package/config/models/live2d.live2d.json +1 -0
  13. package/config/models/live2d.nikke.json +1 -0
  14. package/config/models/live2d.nikke_2.json +1 -0
  15. package/config/models/live2d.npc.json +1 -0
  16. package/config/models/spine.assetbundls.json +1 -0
  17. package/config/models/spine.azurlane.json +1 -0
  18. package/config/models/spine.azurlane_bundle.json +1 -0
  19. package/config/models/spine.azurlane_spine.json +1 -0
  20. package/config/models/spine.bd2.json +1 -0
  21. package/config/models/spine.bluearchive.json +1 -0
  22. package/config/models/spine.live2d.json +1 -0
  23. package/config/models/spine.nikke.json +1 -0
  24. package/config/models/spine.nikke_2.json +1 -0
  25. package/config/models/spine.npc.json +1 -0
  26. package/config/names/NIKKE.json +1058 -0
  27. package/config/names/README.md +47 -0
  28. package/config/names//345/244/251/345/221/275/344/271/213/345/255/220.json +42897 -0
  29. package/config/names//346/243/225/350/211/262/345/260/230/345/237/2032.json +4016 -0
  30. package/config/names//347/242/247/350/223/235/350/210/252/347/272/277.json +38342 -0
  31. package/config/plugins.json +13 -0
  32. package/config/resources.example.json +40 -0
  33. package/package.json +58 -4
  34. package/plugins/unity-live2d-bundle/README.md +98 -0
  35. package/plugins/unity-live2d-bundle/index.js +891 -0
  36. package/plugins/unity-live2d-bundle/lib/anim-to-motion.js +402 -0
  37. package/plugins/unity-live2d-bundle/lib/bundle-probe.js +244 -0
  38. package/plugins/unity-live2d-bundle/lib/bytes.js +189 -0
  39. package/plugins/unity-live2d-bundle/lib/common-strings.js +46 -0
  40. package/plugins/unity-live2d-bundle/lib/dxt.js +197 -0
  41. package/plugins/unity-live2d-bundle/lib/errors.js +71 -0
  42. package/plugins/unity-live2d-bundle/lib/etc2.js +371 -0
  43. package/plugins/unity-live2d-bundle/lib/extract.js +242 -0
  44. package/plugins/unity-live2d-bundle/lib/lzma-d-min.LICENSE.txt +21 -0
  45. package/plugins/unity-live2d-bundle/lib/lzma-d-min.js +34 -0
  46. package/plugins/unity-live2d-bundle/lib/model3.js +137 -0
  47. package/plugins/unity-live2d-bundle/lib/png.js +424 -0
  48. package/plugins/unity-live2d-bundle/lib/serialized.js +233 -0
  49. package/plugins/unity-live2d-bundle/lib/texdecode.js +190 -0
  50. package/plugins/unity-live2d-bundle/lib/typetree.js +215 -0
  51. package/plugins/unity-live2d-bundle/lib/unityfs.js +396 -0
  52. package/plugins/unity-live2d-bundle/plugin.json +81 -0
  53. package/plugins/unity-live2d-bundle/profiles/unityfs-live2d.json +26 -0
  54. package/plugins/unity-spine-bundle/index.js +535 -0
  55. package/plugins/unity-spine-bundle/lib/bytes.js +189 -0
  56. package/plugins/unity-spine-bundle/lib/common-strings.js +46 -0
  57. package/plugins/unity-spine-bundle/lib/dxt.js +197 -0
  58. package/plugins/unity-spine-bundle/lib/errors.js +79 -0
  59. package/plugins/unity-spine-bundle/lib/etc2.js +371 -0
  60. package/plugins/unity-spine-bundle/lib/extract.js +242 -0
  61. package/plugins/unity-spine-bundle/lib/lzma-d-min.LICENSE.txt +21 -0
  62. package/plugins/unity-spine-bundle/lib/lzma-d-min.js +34 -0
  63. package/plugins/unity-spine-bundle/lib/png.js +424 -0
  64. package/plugins/unity-spine-bundle/lib/serialized.js +233 -0
  65. package/plugins/unity-spine-bundle/lib/texdecode.js +190 -0
  66. package/plugins/unity-spine-bundle/lib/typetree.js +215 -0
  67. package/plugins/unity-spine-bundle/lib/unityfs.js +396 -0
  68. package/plugins/unity-spine-bundle/plugin.json +75 -0
  69. package/src/server/README.md +69 -0
  70. package/src/server/assets/browse.mjs +56 -0
  71. package/src/server/assets/bundle-probe.mjs +94 -0
  72. package/src/server/assets/probe.mjs +184 -0
  73. package/src/server/core/config.mjs +173 -0
  74. package/src/server/core/env-num.mjs +60 -0
  75. package/src/server/core/env.mjs +49 -0
  76. package/src/server/core/fs-portable.mjs +37 -0
  77. package/src/server/core/log.mjs +107 -0
  78. package/src/server/core/paths.mjs +44 -0
  79. package/src/server/data/catalog.mjs +1091 -0
  80. package/src/server/data/entry-states.mjs +499 -0
  81. package/src/server/data/live2d.mjs +272 -0
  82. package/src/server/data/manifest-cache.mjs +58 -0
  83. package/src/server/data/manifest-stamp.mjs +135 -0
  84. package/src/server/data/model-meta.mjs +114 -0
  85. package/src/server/data/names.mjs +106 -0
  86. package/src/server/data/shape-probe-worker.mjs +33 -0
  87. package/src/server/data/shape-probe.mjs +626 -0
  88. package/src/server/data/spine.mjs +336 -0
  89. package/src/server/data/tree.mjs +126 -0
  90. package/src/server/http/atomic-json.mjs +145 -0
  91. package/src/server/http/background-solids.mjs +270 -0
  92. package/src/server/http/backgrounds.mjs +388 -0
  93. package/src/server/http/diag-upload.mjs +264 -0
  94. package/src/server/http/diag.mjs +173 -0
  95. package/src/server/http/ready.mjs +167 -0
  96. package/src/server/http/static.mjs +312 -0
  97. package/src/server/http/write.mjs +647 -0
  98. package/src/server/index.mjs +114 -0
  99. package/src/server/plugins/builtin-providers.json +30 -0
  100. package/src/server/plugins/registry.mjs +650 -0
  101. package/src/server/plugins/slot-catalog.mjs +286 -0
  102. package/src/server/routes/background-solids.mjs +56 -0
  103. package/src/server/routes/backgrounds.mjs +77 -0
  104. package/src/server/routes/config.mjs +38 -0
  105. package/src/server/routes/data.mjs +313 -0
  106. package/src/server/routes/diag.mjs +37 -0
  107. package/src/server/routes/entries.mjs +170 -0
  108. package/src/server/routes/ready.mjs +122 -0
  109. package/src/server/serve.mjs +334 -0
  110. package/src/server/unpack/capacity.mjs +217 -0
  111. package/src/server/unpack/mem-probe.mjs +27 -0
  112. package/src/server/unpack/plugin-runner.mjs +529 -0
  113. package/src/server/unpack/plugin-store.mjs +736 -0
  114. package/src/server/unpack/probe-store.mjs +73 -0
  115. package/src/server/unpack/routes.mjs +359 -0
  116. package/src/server/unpack/runtime/exec.mjs +558 -0
  117. package/src/server/unpack/runtime/index.mjs +141 -0
  118. package/src/server/unpack/runtime/node-vm.mjs +111 -0
  119. package/src/server/unpack/unpack-worker.mjs +105 -0
  120. package/src/server/unpack/worker-client.mjs +184 -0
  121. package/src/server/unpack/write-result.mjs +84 -0
  122. package/src/shared/bundle-shape.mjs +83 -0
  123. package/src/shared/entry-reasons.mjs +119 -0
  124. package/src/shared/names.mjs +45 -0
  125. package/src/web/README.md +193 -0
  126. package/src/web/app/anim-list.js +222 -0
  127. package/src/web/app/background-solids.js +150 -0
  128. package/src/web/app/backgrounds.js +152 -0
  129. package/src/web/app/camera-log.js +28 -0
  130. package/src/web/app/context.js +72 -0
  131. package/src/web/app/dir-picker.js +242 -0
  132. package/src/web/app/entry-report.js +71 -0
  133. package/src/web/app/memory-bars.js +91 -0
  134. package/src/web/app/memory-budget.js +228 -0
  135. package/src/web/app/memory-guard.js +204 -0
  136. package/src/web/app/model-actions.js +682 -0
  137. package/src/web/app/renderer-setting.js +70 -0
  138. package/src/web/app/root-manager.js +254 -0
  139. package/src/web/app/status.js +138 -0
  140. package/src/web/app/topbar-actions.js +357 -0
  141. package/src/web/app/transport-actions.js +297 -0
  142. package/src/web/app/ui-state.js +559 -0
  143. package/src/web/app/url-params.js +171 -0
  144. package/src/web/app/view-settings.js +186 -0
  145. package/src/web/components.css +465 -0
  146. package/src/web/debug/diag-hud.js +95 -0
  147. package/src/web/debug/drag.js +369 -0
  148. package/src/web/debug/log.js +320 -0
  149. package/src/web/debug/model-cmp.js +292 -0
  150. package/src/web/debug/panel.css +474 -0
  151. package/src/web/debug/panel.js +712 -0
  152. package/src/web/debug/race-probe.js +82 -0
  153. package/src/web/debug/upload.js +130 -0
  154. package/src/web/engine/adapters/atlas.js +47 -0
  155. package/src/web/engine/adapters/index.js +22 -0
  156. package/src/web/engine/adapters/renderer.js +223 -0
  157. package/src/web/engine/adapters/skeleton.js +105 -0
  158. package/src/web/engine/adapters/texture.js +64 -0
  159. package/src/web/engine/animation.js +181 -0
  160. package/src/web/engine/appearance.js +136 -0
  161. package/src/web/engine/camera-adapter.js +212 -0
  162. package/src/web/engine/effect-chain.js +138 -0
  163. package/src/web/engine/engine-state.js +120 -0
  164. package/src/web/engine/filters.js +224 -0
  165. package/src/web/engine/fit.js +197 -0
  166. package/src/web/engine/frame-gate.js +33 -0
  167. package/src/web/engine/gl-driver.js +56 -0
  168. package/src/web/engine/gl.js +77 -0
  169. package/src/web/engine/index.js +20 -0
  170. package/src/web/engine/loader.js +55 -0
  171. package/src/web/engine/model-info.js +26 -0
  172. package/src/web/engine/pipeline.js +200 -0
  173. package/src/web/engine/player.js +427 -0
  174. package/src/web/engine/pma/blend.js +76 -0
  175. package/src/web/engine/pma/detect.js +118 -0
  176. package/src/web/engine/pma/index.js +8 -0
  177. package/src/web/engine/pma/resolve.js +112 -0
  178. package/src/web/engine/pma/upload.js +127 -0
  179. package/src/web/engine/pma.js +5 -0
  180. package/src/web/engine/render-frame.js +93 -0
  181. package/src/web/engine/runtime-capabilities.js +209 -0
  182. package/src/web/engine/runtime.js +37 -0
  183. package/src/web/engine/skins.js +165 -0
  184. package/src/web/engine/textures.js +174 -0
  185. package/src/web/index.html +115 -0
  186. package/src/web/infra/api.js +531 -0
  187. package/src/web/infra/diag.js +509 -0
  188. package/src/web/infra/document.js +269 -0
  189. package/src/web/infra/dom.js +31 -0
  190. package/src/web/infra/events.mjs +27 -0
  191. package/src/web/infra/log.js +153 -0
  192. package/src/web/infra/mem-stats.js +112 -0
  193. package/src/web/infra/plugin-host-registration.mjs +285 -0
  194. package/src/web/infra/plugin-host-report.mjs +32 -0
  195. package/src/web/infra/plugin-host-unload.mjs +79 -0
  196. package/src/web/infra/plugin-host-unpack.mjs +899 -0
  197. package/src/web/infra/plugin-host-warmup.mjs +180 -0
  198. package/src/web/infra/plugin-host.js +628 -0
  199. package/src/web/infra/ready.js +461 -0
  200. package/src/web/infra/schedule.js +51 -0
  201. package/src/web/infra/shortcuts.js +129 -0
  202. package/src/web/infra/slot-registry.js +402 -0
  203. package/src/web/infra/vfs.js +247 -0
  204. package/src/web/live2d/bridge.js +400 -0
  205. package/src/web/live2d/model.js +321 -0
  206. package/src/web/live2d/overlay.js +142 -0
  207. package/src/web/live2d/pixi-app.js +111 -0
  208. package/src/web/live2d/playback-probe.js +92 -0
  209. package/src/web/live2d/probe-geometry.js +329 -0
  210. package/src/web/live2d/probe.js +119 -0
  211. package/src/web/live2d/release.js +66 -0
  212. package/src/web/live2d/runtime-adapter.js +289 -0
  213. package/src/web/live2d/stage-ctx.js +152 -0
  214. package/src/web/live2d/stage.js +204 -0
  215. package/src/web/live2d/vfs-settings.js +174 -0
  216. package/src/web/live2d/view.js +89 -0
  217. package/src/web/main.js +629 -0
  218. package/src/web/panel/background-list.js +373 -0
  219. package/src/web/panel/bg-source.js +291 -0
  220. package/src/web/panel/color-picker.css +199 -0
  221. package/src/web/panel/color-picker.js +278 -0
  222. package/src/web/panel/dialog.css +116 -0
  223. package/src/web/panel/dialog.js +173 -0
  224. package/src/web/panel/dom.js +35 -0
  225. package/src/web/panel/icons.js +58 -0
  226. package/src/web/panel/memory-bar.js +195 -0
  227. package/src/web/panel/panel-settings.js +1263 -0
  228. package/src/web/panel/panel.css +1304 -0
  229. package/src/web/panel/render-choice.js +104 -0
  230. package/src/web/panel/schema.js +784 -0
  231. package/src/web/panel/settings-io.js +291 -0
  232. package/src/web/panel/shell-controls.js +274 -0
  233. package/src/web/panel/slider.js +210 -0
  234. package/src/web/panel/toolbar-schema.js +111 -0
  235. package/src/web/panel/toolbar.js +195 -0
  236. package/src/web/panel/transport-picker.js +375 -0
  237. package/src/web/panel/transport.js +321 -0
  238. package/src/web/panel/view.js +553 -0
  239. package/src/web/plugin/unpack-worker.js +185 -0
  240. package/src/web/render/camera/constants.js +60 -0
  241. package/src/web/render/camera/index.js +123 -0
  242. package/src/web/render/camera/spine.js +124 -0
  243. package/src/web/render/capture.js +30 -0
  244. package/src/web/render/interaction.js +527 -0
  245. package/src/web/render/live2d.js +627 -0
  246. package/src/web/render/playback.js +47 -0
  247. package/src/web/render/reset-view.js +29 -0
  248. package/src/web/render/spine.js +386 -0
  249. package/src/web/render/surface-switch.js +176 -0
  250. package/src/web/render/surface.js +571 -0
  251. package/src/web/render/surfaces.js +70 -0
  252. package/src/web/roster/filter.js +31 -0
  253. package/src/web/roster/list.js +276 -0
  254. package/src/web/roster/persist.js +52 -0
  255. package/src/web/roster/reveal.js +77 -0
  256. package/src/web/roster/rows.js +248 -0
  257. package/src/web/roster/snapshot.js +37 -0
  258. package/src/web/roster/view.js +670 -0
  259. package/src/web/spine/canvas-api-groups.js +267 -0
  260. package/src/web/spine/canvas-api.js +97 -0
  261. package/src/web/spine/canvas-document.js +80 -0
  262. package/src/web/spine/canvas-engine.js +193 -0
  263. package/src/web/spine/canvas-entry.js +28 -0
  264. package/src/web/spine/canvas-pixels.js +36 -0
  265. package/src/web/spine/canvas-runtime.js +127 -0
  266. package/src/web/spine/canvas-view.js +108 -0
  267. package/src/web/spine/canvas.js +7 -0
  268. package/src/web/spine/runtime-hint.js +73 -0
  269. package/src/web/spine/runtimes.js +212 -0
  270. package/src/web/style.css +493 -0
  271. package/tool/lib/bundle-probe-node.mjs +121 -0
  272. package/tool/lib/check-perms.mjs +114 -0
  273. package/tool/lib/contract-checks.mjs +582 -0
  274. package/tool/lib/cubism-core.mjs +122 -0
  275. package/tool/lib/export-bundle.mjs +328 -0
  276. package/tool/lib/paths.mjs +20 -0
  277. package/tool/lib/runtime-guides.mjs +65 -0
  278. package/tool/lib/runtime-package.mjs +403 -0
  279. package/tool/lib/spine-meta.mjs +227 -0
  280. package/tool/lib/spine-motions.mjs +104 -0
  281. package/tool/lib/spine-runtime.mjs +131 -0
  282. package/tool/lib/test-lock.mjs +178 -0
  283. package/tool/lib/test-report.mjs +133 -0
  284. package/tool/parse-jsonc.mjs +86 -0
  285. package/tool/resolve-live2d-runtimes.mjs +276 -0
  286. package/tool/spine-motion-rules.json +101 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 EIGHTfs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,268 @@
1
- # Temporary Holding Version
1
+ # ModelStage
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ **多游戏通用的 Spine / Live2D 动画预览器。** 把不同游戏导出的骨骼动画与 Live2D 模型放在同一个界面里浏览:
4
+ 左栏按「游戏 → 角色 → 变体」组织,右侧面板调渲染参数,底栏控制播放。
5
+
6
+ ![ModelStage 界面](https://raw.githubusercontent.com/EIGHTfs/ModelStage/main/docs/assets/ui-main.png)
7
+
8
+ 支持两类内容、两种资源形态:
9
+
10
+ | 维度 | 支持 |
11
+ |---|---|
12
+ | 内容类型 | **Spine**(3.6 / 3.7 / 3.8 / 4.1 运行时)· **Live2D**(Cubism 2 / 4) |
13
+ | 资源形态 | **目录型**(散装 skel/atlas/贴图 或 model3.json)· **容器型**(Unity 资源包,由插件解包) |
14
+
15
+ > **维护状态**:项目已进入**维护为主**阶段——修 bug、保持可用、跟进运行时版本;
16
+ > 新增功能频率会明显降低。长期方向的探索性工作(例如 3D 支持)不在当前范围内。
17
+
18
+ ---
19
+
20
+ ## 主要能力
21
+
22
+ | 能力 | 说明 |
23
+ |---|---|
24
+ | 三级名册 | 游戏 → 角色 → 变体;支持搜索、懒加载、展开态与上次选择记忆 |
25
+ | 两套渲染面 | Spine 与 Live2D 共用同一套相机语义与面板;切换只有唯一入口,不会叠加 |
26
+ | 面板按能力生成 | 面板只问「当前渲染器支持什么」,不支持的项**不构建**(而非灰化占位) |
27
+ | 动作与循环 | 动作列表带时长、当前项高亮;「混合模式」与「循环播放」两个开关正交 |
28
+ | 资源包解包 | UnityFS 单文件容器(Spine / Live2D 两种)由插件解包:服务端命中缓存直接取产物,未命中则由**浏览器本地解包**(不受服务端内存预算限制);产物带指纹缓存,插件或版本一变即失效 |
29
+ | 插件运行时 | **node(vm 沙盒)与 exec(子进程)两种**:`exec` 支持非 JS 插件(如 Python),按行 JSON 帧协议通信、双向超时必杀、崩溃时在途请求全部判失败;门禁默认关闭(`config/plugin-runtime.json`)。首个 exec 插件 `unitypy-bundle` 用 UnityPy 解容器包,可覆盖 JS 插件暂不支持的纹理格式(如 ASTC) |
30
+ | 资源包自动判型 | 容器先解包、再按**产物**确定内容类型(`entry.json` 含 `skeletonFile` ⇒ Spine;含 `*.model3.json` ⇒ Live2D),不按扩展名猜 |
31
+ | 背景 | 棋盘格 / 纯色 / 透明 / 自定义图;纯色预设可增删改名(服务端存储) |
32
+ | 调试与诊断 | 浮层日志(四档多选)、就绪信号(给机器读的阶段状态机)、诊断探针落盘 |
33
+ | 依赖形态 | **主项目不把零依赖当硬约束**:现状仍是"无 `node_modules`、无锁文件,全部用 Node 内置能力",但不以此为红线 —— 为功能需要可引入必要依赖;`package.json` 目前只提供 `bin` / `engines` 等元数据。**插件另算**:按独立项目对待,可有自己的 `package.json` 与依赖,唯一硬要求是依赖必须以「vm 沙盒能加载的形态」落地(IIFE / UMD 或构建产物),不能靠运行时 `import` |
34
+
35
+ ### 资源包(UnityFS 容器)直接打开
36
+
37
+ 游戏客户端的**单个加密/打包文件**不用手工解包也能看:登记资源根后,容器条目会被识别成
38
+ 「资源包」,点开时由插件解出骨架 + 图集 + 贴图,再交给渲染层。
39
+
40
+ 能力细节与当前限制见 **[docs/架构/能力层.md](https://github.com/EIGHTfs/ModelStage/blob/main/docs/架构/能力层.md)**。
41
+
42
+ ---
43
+
44
+ ## 快速开始
45
+
46
+ ### 方式一:本地启停脚本(推荐日常使用)
47
+
48
+ ```bash
49
+ bash web.sh start # 启动(默认端口 8804)
50
+ bash web.sh status # 查看状态
51
+ bash web.sh restart # 重启
52
+ bash web.sh stop # 停止
53
+ ```
54
+
55
+ 启动后打开 `http://<本机IP>:8804/`。
56
+
57
+ ### 方式二:npx(不 clone 也能跑)
58
+
59
+ ```bash
60
+ npx . --port 8804 --data-root ~/modelstage-data # 在项目根直接跑
61
+ npx github:EIGHTfs/ModelStage --port 8804 --data-root ~/modelstage-data # 从 GitHub 直跑,不用 clone
62
+ npx modelstage --port 8804 --data-root ~/modelstage-data # 发布到 registry 后可用(**当前未发布**)
63
+ ```
64
+
65
+ 参数与环境变量等价,两条路都通:
66
+
67
+ | 参数 | 环境变量 | 作用 |
68
+ |---|---|---|
69
+ | `--port <n>` | `PORT` | 监听端口(默认 8804) |
70
+ | `--data-root <path>` | `MODELSTAGE_DATA_ROOT` | 数据根:日志、解包缓存、配置的持久化位置;缺省 = 项目根 |
71
+ | `-h` / `--help`、`-v` / `--version` | — | 用法与版本号 |
72
+
73
+ ### 调参(内存准入 / 后台探测,全部可 env 覆盖)
74
+
75
+ 这些系数决定"要不要解包""要不要后台探测"。**非法值(NaN / 越界)一律回落缺省并打一条 warn**,
76
+ 不会静默用错值;进程内改 env 立即生效,进程外改 env 需重启(`process.env` 是启动快照)。
77
+
78
+ | 环境变量 | 缺省 | 作用 |
79
+ |---|---|---|
80
+ | `MODELSTAGE_ADMIT_HEAP_RATIO` | `0.85` | 前端 JS 堆占用上限比例(超过就拒绝装载)。前端读不到 env,由服务端经 `GET /api/web-config` 的 `admitHeapRatio` 下发 |
81
+ | `MODELSTAGE_ADMIT_CAPACITY_RATIO` | `0.5` | 服务端解包容量系数:预算 = 可用内存 × 该比例(给系统与渲染留余量) |
82
+ | `MODELSTAGE_PROBE_MEM_BASELINE_MB` | `256` | 后台形态探测前要留的可用内存余量(MB) |
83
+ | `MODELSTAGE_PROBE_MEM_FACTOR` | `3` | 探测时"包体积 → 预估占用"的倍数(解包产物通常比包大,不要按 1 估) |
84
+ | `MODELSTAGE_PROBE_MAX_BYTES` | `25165824`(24MB) | 超过该体积的包不做后台探测(保持 unknown,留待点击验证) |
85
+ | `MODELSTAGE_PROBE_IDLE_GAP_MS` | `2000` | 探测批次开始前要求的静默时长(有请求在飞时探测主动让路) |
86
+ | `MODELSTAGE_PROBE_MAX_WAIT_MS` | `20000` | 一批最多等空闲多久;超时把剩余条目放回队列 |
87
+ | `MODELSTAGE_PROBE_WORKER` | `1` | `0` = 关掉探测的解析线程,回落主线程同步解析(受限环境用) |
88
+ | `MODELSTAGE_CGROUP_MEMORY_MAX_PATH` | `/sys/fs/cgroup/memory.max` | cgroup v2 限额文件路径(同时读同目录 `memory.current`)。用于非标准挂载或测试注入;读不到或为 `max` ⇒ 回落 `/proc/meminfo` |
89
+
90
+ `--data-root` 必须指向一个**可写且持久**的目录:npx 运行时"项目根"是 npm 的临时缓存目录(会被清理),
91
+ 把日志与解包缓存写在里面会丢。数据根下的 `runtime/`、`config/` 由服务自行创建与维护。
92
+
93
+ > **首次运行**:数据根里还没有 `config/resources.json`(它是本地配置,不进包),
94
+ > 服务会打一条 ERROR 并已空资源根启动 —— 页面能开,但名册是空的。
95
+ > 照 `config/resources.example.json` 复制一份、把 `path` 指向你的模型目录即可(也可以在页面里
96
+ > 「浏览服务端目录,登记为资源根」直接点选)。
97
+
98
+ **前置**:Node.js ≥ 20(代码用到 `AbortSignal.timeout`、顶层 `await`、原生 ESM —— Node 17.3+ 才有前者)。
99
+
100
+ 运行时源码**不入库、也不在 npm 包内**(授权与体积原因),需自备 —— 缺失时会显式报错并给出修复指引:
101
+
102
+ | 运行时 | 放哪里 | 说明 |
103
+ |---|---|---|
104
+ | Spine | `src/spine-runtimes/` | 或用 `SPINE_RUNTIME_DIR`,或工作区共享目录 `../spine-runtimes/` |
105
+ | Live2D | `src/live2d-runtimes/<版本>/` | 与 `src/spine-runtimes/` 并列;**多版本共存**(如 `v6-6.5.3`、`v8-8.19.0`),由 pixi 主版本优选;或用 `LIVE2D_SEARCH_ROOTS` 指定搜索根 |
106
+
107
+ > **用 npx 直跑时**:包里没有运行时目录,必须自行准备,并用 `SPINE_RUNTIME_DIR` /
108
+ > `LIVE2D_SEARCH_ROOTS` 指到你放运行时的位置(多个搜索根用路径分隔符隔开)。
109
+ > 否则页面能起来,但装载模型时会明确报"找不到运行时"。
110
+ >
111
+ > **已知限制(npx)**:`npx` 在安装阶段会对 bin 做 `chmod`,而 **CIFS/SMB 挂载目录不支持 chmod**
112
+ > (报 `EPERM`)。所以把项目放在网络挂载盘上时 `npx .` 会失败 —— 这与包内容无关,
113
+ > 换到本地磁盘即可;在挂载盘上仍可直接 `node bin/modelstage.mjs`(跳过 npm 安装环节)。
114
+
115
+ Live2D 的版本差异(`Cubism4ModelSettings` ↔ `CubismModelSettings` 之类的命名变化、PIXI 6/8 的构造差异)
116
+ 统一收在 `src/web/live2d/runtime-adapter.js` 一处,业务代码不直接触碰 PIXI/Live2D 的 API 形状 ——
117
+ 新增一个版本只需拷一个子目录 + 在兼容表里加一行。详见 **[docs/设计/Live2D运行时统一入口.md](https://github.com/EIGHTfs/ModelStage/blob/main/docs/设计/Live2D运行时统一入口.md)**。
118
+
119
+ ---
120
+
121
+ ## 配置资源根
122
+
123
+ 资源根指「本机某个游戏资源目录」。在 `config/resources.json` 里登记,界面即可看到。
124
+ 每个根可配 `profile`(游戏差异适配,如动作分组与循环规则)。
125
+
126
+ 新增资源根的完整步骤见 **[docs/参考/接口清单.md](https://github.com/EIGHTfs/ModelStage/blob/main/docs/参考/接口清单.md)** §三。
127
+
128
+ **仓库内不含任何游戏提取资源** —— 模型文件均不入库,需自备。
129
+
130
+ ---
131
+
132
+ ## 目录结构
133
+
134
+ ```
135
+ src/
136
+ web/ 前端:main.js + app / engine / render / spine / live2d / roster / panel / debug / infra
137
+ server/ 服务端:serve.mjs + routes / http / data / unpack
138
+ plugins/ 插件(容器解析):unity-live2d-bundle
139
+ tool/ 工具与门禁脚本(含回归入口 test-all.mjs)
140
+ test/ 回归套件
141
+ config/ 资源根与插件配置
142
+ data/ 运行时数据(不入库)
143
+ docs/ 文档
144
+ web.sh 启停脚本
145
+ ```
146
+
147
+ 模块划分与职责边界见 **[docs/架构/总览.md](https://github.com/EIGHTfs/ModelStage/blob/main/docs/架构/总览.md)**。
148
+
149
+ ---
150
+
151
+ ## 文档
152
+
153
+ 完整索引:**[docs/README.md](https://github.com/EIGHTfs/ModelStage/blob/main/docs/README.md)**
154
+
155
+ | 想了解 | 看这里 |
156
+ |---|---|
157
+ | 能做什么、有什么限制 | [docs/架构/能力层.md](https://github.com/EIGHTfs/ModelStage/blob/main/docs/架构/能力层.md) |
158
+ | 架构分层与模块职责 | [docs/架构/总览.md](https://github.com/EIGHTfs/ModelStage/blob/main/docs/架构/总览.md) |
159
+ | 改代码不能碰的红线 | [docs/契约/](https://github.com/EIGHTfs/ModelStage/blob/main/docs/契约/README.md)(13 条不变量 + 各层契约) |
160
+ | 相机、界面、状态的设计 | [docs/设计/](https://github.com/EIGHTfs/ModelStage/blob/main/docs/设计/) |
161
+ | 渲染接入与 API 参考 | [docs/参考/](https://github.com/EIGHTfs/ModelStage/blob/main/docs/参考/) |
162
+ | 还没解决的问题 | [docs/架构/能力层.md](https://github.com/EIGHTfs/ModelStage/blob/main/docs/架构/能力层.md) §四「已知限制」 |
163
+
164
+ ---
165
+
166
+ ## 常用工具
167
+
168
+ ```bash
169
+ node tool/test-all.mjs # 全量回归(含前置静态门禁)
170
+ node tool/check-single-source.mjs # 单一数据源门禁
171
+ node tool/check-plugin.mjs # 插件契约检查
172
+ node tool/export-bundle.mjs --root=<id> --path=<资源> # 资源包 → 标准模型目录
173
+ node tool/resolve-live2d-runtimes.mjs # 查看 Live2D 运行时探测结果(排查用)
174
+ ```
175
+
176
+ 导出与工具细节见 **[docs/参考/接口清单.md](https://github.com/EIGHTfs/ModelStage/blob/main/docs/参考/接口清单.md)** §五。
177
+
178
+ ---
179
+
180
+ ## 版本记录
181
+
182
+ > **版本号唯一权威 = `package.json` 的 `version`**。`bin/modelstage.mjs --version` 直接读它,
183
+ > 这里只是变更留痕。升版时改 `package.json` + 在本表追加一行,不需要再找第三个地方。
184
+
185
+ | 版本 | 日期 | 要点 |
186
+ |---|---|---|
187
+ | 0.5.24 | 2026-11-01 | 发布准备:补 `THIRD_PARTY_NOTICES.md`、README「合规状态」扩到 Live2D Cubism 与 pixi.js 并注明「服务端把运行时报给浏览器 = 分发行为」;补 author/keywords/bugs、清理 `files` 无效条目;去掉 `private` 以便发布;README 标注项目进入维护为主阶段 |
188
+ | 0.5.23 | 2026-11-01 | 解包节点判据由「名字前缀」改为「结构形式」(`/^CAB-[0-9a-f]{32}$/i`,四处统一 + 防漂移单测):`.resS` / `.resource`(FSB5 音频银行)不再被误当 serialized file;顺带修好两个包的不完整提取(假节点抛错曾中断循环,真节点没被处理);测试套件 29 → 30 |
189
+ | 0.5.22 | 2026-11-01 | `tool/lib/runtime-package.mjs` 的 `writeAtomic` 写前自建父目录(原子写函数不依赖调用方建目录);`src/server/http/atomic-json.mjs` 核实后本就有 `ensureDir`,未改动 |
190
+ | 0.5.21 | 2026-11-01 | 修 `document.files` 的 catalog 请求无超时:加 `DOC_FILES_TIMEOUT_MS = 6000` + `AbortSignal.timeout`,`pending` 清理移入 `finally`(挂起后可重试,不再永久占位),失败记 warn 含 rootId/url/reason |
191
+ | 0.5.20 | 2026-10-31 | 全量审计后清理真实硬编码:插件清单 `depsDir` 改 `${MODELSTAGE_DEPS_DIR}` 占位符(换机只改 env)、删 `interpreter` 交给探测链、MIME 字面量各文件提常量、6 个脚本统一 `here(import.meta.url)`、清 5 处注释日期 |
192
+ | 0.5.19 | 2026-10-31 | 分区编号 R1~R6 改为职责名(基础工具与统计发布 / 注册入口组装 / 装载与卸载回收 / 资源取用与解包 / 装载编排),六个文件 R 编号清零;分隔线按显示宽度 96 列对齐。拆分工作到此收口 |
193
+ | 0.5.18 | 2026-10-31 | 去掉函数索引(43 条带行号条目,−113 行注释),换成 7 行模块地图 + 6 个分区概述(+46 行);`plugin-host.js` 694 → 628 行,全部为注释改动;其余 5 个模块头部本无索引,未改动 |
194
+ | 0.5.17 | 2026-10-31 | 拆分 B4:解包区整体搬出为 `plugin-host-unpack.mjs`(20 函数 / 487 代码行,3 个窄参数),`plugin-host.js` **1538 → 694 行**;workerPool 归属明确(unpack 创建持有、unload 清理);B3(装载与窗口区)评估后不动,依据记入调研文档 |
195
+ | 0.5.16 | 2026-10-31 | 拆分 B2:抽出「注册与校验」区为 `plugin-host-registration.mjs`(5 个窄参数、不塞整块 ctx),plugin-host 净减 215 行;7 类错误码文案与校验顺序原样随迁、对外 API 形状不变 |
196
+ | 0.5.15 | 2026-10-31 | 事件名收敛为常量 `EVENTS`(3 个 spineviewer 事件 / 7 处字面量清零),并新增「src/web 下不许再出现事件名字面量」的扫描单测(已自证会红);防的是批量改名再次静默改坏协议字符串 |
197
+ | 0.5.14 | 2026-10-31 | 拆分 B1:抽出 report / unloadPlugin 链 / warmup 链三个独立模块(plugin-host 净减 201 行);**修 0.5.13 的静默回归**——批量重命名把事件名 `spineviewer:plugins-loaded` 改成了 `plugins-ctx.loaded`(2 处),导致卸载后面板不重建、document.files 就绪后不重绘 |
198
+ | 0.5.13 | 2026-10-31 | 拆分 B0:9 个高频共享状态收敛为单一 `ctx` 对象(145 处访问点改写、行为零变化),为后续模块化铺路;文件头状态表改为 ctx 形态并列出「留在闭包」的 17 个低频状态与理由 |
199
+ | 0.5.12 | 2026-10-31 | `plugin-host.js` 加分区注释与函数索引(126 行纯注释、0 行代码变动):6 个分区 / 43 条索引(函数名·行号·代码行·职责)+ 9 个高频共享状态读写表;`fetchFile`(133 行、三职责)列为下一轮最该拆的内部函数 |
200
+ | 0.5.11 | 2026-10-31 | 插件按钮支持按下态(`spec.pressed` getter,与内置按钮同一套 aria 与样式);顶栏插件按钮改入**横向可滚动区**(不再"封顶丢弃",`PLUGIN_TOOLBAR_MAX` 3→20 降级为安全阀),内置 5 个保持固定、顶栏高度不变、键盘可达 |
201
+ | 0.5.10 | 2026-10-31 | 修 `document.files` 就绪后的重绘事件派发目标(`document` → `window`,监听器挂在 window 上,原写法重绘静默不生效);files 缓存键加 prefix 段(容器 `vfs:<prefix>` / 容器未就绪 `vfs:` / 非容器 `none`) |
202
+ | 0.5.9 | 2026-10-31 | 替换型控件强制自带设置键(`missing-setting` 校验):替换后值存插件自己的键、内置键只读且全程不被写;卸载无需"恢复"(从未被改);插件改值域时用清单 default 兜底 |
203
+ | 0.5.8 | 2026-10-31 | `document.files` 补齐:容器条目走 VFS 前缀列举(同步)、目录条目走 catalog 异步补全;未就绪返回 **null**(与"空目录返回 []"可区分),就绪后自动重建一次;缓存按文档键、重复渲染不重复列举 |
204
+ | 0.5.7 | 2026-10-31 | 键值插槽 `panel.control.<key>` 与单值插槽 `panel.brand`:替换内置控件(保留其 id/能力键/设置键)、按目标判抢占、卸载原样恢复;品牌按全局注册序生效、卸载三级回退(上一个注册者 → 内置)、textContent 写入不解析标记 |
205
+ | 0.5.6 | 2026-10-31 | 阶段抬到 S5:`panel.brand` 与 `panel.control.<key>` 开放;`stage.overlay` 改为 **S6 占位档**(尚未排期,不是第六阶段),判定逻辑零改动;阶段相关测试全部改为动态取样 |
206
+ | 0.5.5 | 2026-10-31 | 顶栏声明式化 + `toolbar.actions` 插槽:内置 5 个按钮迁成声明表(id/aria/title 与旧 HTML 逐字一致)、插件按钮排在内置之后按 order 升序、图标仅收白名单图标名(拿不到则纯文本)、封顶 3 个 |
207
+ | 0.5.4 | 2026-10-31 | 插槽阶段抬到 S3:`toolbar.actions` 开放(`panel.group` 已开放;`panel.brand` / `panel.control.<key>` 仍 S5、`stage.overlay` 仍 S4);顺序用例改为动态挑未开放样本 |
208
+ | 0.5.3 | 2026-10-31 | 补设置键→控件反向映射:一个设置键可被多个控件绑定(值为控件 id 数组),外部改设置键会触发所有绑定控件重绘;tick 级合并去重(`__PANEL_REPAINT__` 可核对);卸载后惰性跳过 |
209
+ | 0.5.2 | 2026-10-31 | 清理老链:删除 `controlSections` / `mergePluginControls` / `normalizePluginSection` 等死路径(schema 179 行 + plugin-host 6 处),注册通路收敛为 `slots.register` 一条(`registerControls` 仅作兼容层);顺带修 `mergeSlotControls` 的 onReject 默认值取值 bug |
210
+ | 0.5.1 | 2026-10-31 | 插槽 owner props 补齐并改为 getter(settings / geometry / activeSurface / document);新增 `spec.setting` 统一控件存值键(值直接落在清单声明键上,未声明则注册期拒绝);`rebuildPanelFor` 拆成 5 个函数(111 行 → 全部 ≤50);老链标 deprecated |
211
+ | 0.5.0 | 2026-10-31 | 老接口 `registerControls` 迁移到插槽注册(一条通路):标题→id 派生映射、老控件 id 不强制前缀、`implicitDeclare` 隐式声明(只跳过 undeclared,阶段/前缀/重名/风险仍全查);`via` 标注控件来源;老控件与 `slots.register` 的控件共用同一合并/渲染/回收链路 |
212
+ | 0.4.9 | 2026-10-31 | 补提交 S3-a 遗漏的接线:`plugin-host.js` 从 schema 取内置分组 id 传给插槽注册表(据此区分"引用内置组"与"插件新建组",新建组必须带 `plugin.<pluginId>.` 前缀) |
213
+ | 0.4.8 | 2026-10-31 | 修插件控件 kind 两套词汇缺映射(`toggle`/`button` 曾落进 `ctl-note` 空行,表现为"控件消失",连正常态都不渲染);新增唯一映射表 `PLUGIN_KIND_TO_VIEW` 并补 kind 映射完备性单测;孤儿能力键检查改合并口径(三源 declared + 跳过 disabled + DOM 漂移护栏 + 判据自检) |
214
+ | 0.4.7 | 2026-10-31 | 插件插槽 S3-a:允许插件新建分组(id 强制 `plugin.<pluginId>.` 前缀、order 缺省 1000、排在内置组之后、与内置组同名允许并提示);分组存在性判定由标题集合改为 id 优先/title 兜底;分组 id 规则前移到注册期(`bad-group-id`) |
215
+ | 0.4.6 | 2026-10-31 | 插件插槽 S2-b2:阶段改读服务端 `currentStage`(删前端常量与两个自测开关)、`onAction` 派发(异常隔离)、`props.settings` 传当前值或声明默认值 |
216
+ | 0.4.5 | 2026-10-31 | `/api/plugins` 下发 `currentStage`(与 `slotCatalog[i].stage` 是两个概念,不合并),消灭阶段的两处表达;补路由层用例(含"目录里必须仍有未开放插槽"这条防扁平化断言) |
217
+ | 0.4.4 | 2026-10-31 | 插件插槽 S2-a:插件控件真正渲染进右栏(装配期聚合,不写回静态表)、按 groupId 匹配与 order 排序、id 冲突拒绝、设置键并入能力白名单、`panelWired` 与合并结果同源;卸载后 DOM 节点消失 |
218
+ | 0.4.3 | 2026-10-31 | 插槽阶段抬到 S2:`panel.group.<groupId>` 开放(其余四类仍按各自阶段拒绝);阶段文档补 S2 参考清单(验证用插件的 plugin.json 与脚本全文) |
219
+ | 0.4.2 | 2026-10-31 | 插件插槽 S1-b2:`slot-registry.js`(DOM-free,四类校验固定顺序)+ `slots.register` 只登记不渲染+ disposer 接入 effect 容器(`unloadPlugin` 一并回收)+ `window.__SLOT_STATS__`;修服务端 `matchSlot` 模板串双重转义缺陷(含字母 s 的插槽被误判 unknown-slot) |
220
+ | 0.4.1 | 2026-10-31 | 插件设置键强制 `plugin.<pluginId>.<key>` 前缀(能力键白名单全局合并,同名键会撞车;别的插件 id 同样不合法)+ 修 registry 里校验块早于 `const pid` 的 TDZ 缺陷 |
221
+ | 0.4.0 | 2026-10-31 | 插件插槽 S1-b1(前端):插件级 effect 容器与 `unloadPlugin`(逆序回收 + 兜底清理 + `effectsReport`);窗口外注册由静默丢弃改为显式拒绝(带可读原因与 likelyOwner);面板重建保留手风琴展开态(累积表,覆盖换渲染面路径);渲染写 `data-group-id` |
222
+ | 0.3.9 | 2026-10-31 | 插件插槽 S1-a(服务端):插槽目录常量表 + 清单校验(unknown-slot / slot-not-open / undeclared-slot / risk-mismatch 四类,逐项降级不整体拒绝)+ `/api/plugins` 下发 `slotCatalog` 与每插件 `slots`/`slotErrors`/`settings`/`settingsErrors`;老插件零改动可用 |
223
+ | 0.3.8 | 2026-10-31 | SSE 补发档名改由 `PROGRESS_PHASES` 派生(消除第三处档名字面量,缺 msField 测试期即失败);准入系数与 cgroup 限额全部 env 可覆盖(`MODELSTAGE_ADMIT_HEAP_RATIO` / `_CAPACITY_RATIO` / `MODELSTAGE_CGROUP_MEMORY_MAX_PATH`),cgroup v2 优先、缺失回落 `/proc/meminfo`,非法值回落缺省并告警一次 |
224
+ | 0.3.7 | 2026-10-31 | 大包首帧优化第一步:容器包改由解包 worker **自己按 URL 拉取**,主线程不再整包读取与结构化拷贝(实测读包 152/172ms → 0、投喂 13ms → 0);新增 `?workerFetch=0` 同机 A/B 开关与 `unpack.main-read`/`unpack.worker-post` 打点 |
225
+ | 0.3.6 | 2026-10-31 | exec 进度档名收敛为单一来源(`PROGRESS_PHASES` 表,档名与语义同处);修 exec 默认 logger 未接线(未知档的 debug 原本在生产里根本不输出);两份设计稿改为指向该表、不复述名单 |
226
+ | 0.3.5 | 2026-10-31 | exec 插件解包进度变成**真实时**:帧协议新增进度帧(read/resolve/write),插件在三个自然边界上报、宿主即时 fire `phase` 事件、收尾只补未发档;node 插件事件顺序与时刻不变 |
227
+ | 0.3.4 | 2026-10-31 | 探测增加可用内存判据:`可用 ≥ 基线 + 包体积 × 系数` 才探,不满足则保持 unknown 并单独计数 `skippedMemory`(与"包太大"分开);MemAvailable 优先、`os.freemem()` 回落、取不到则放行;阈值走 env 且逐次重读 |
228
+ | 0.3.3 | 2026-10-31 | catalog 的 `warnings` 收敛为空数组:能力说明只在 `capabilityNotes`、根级问题只在 `rootIssues`/`rootWarning`(兼容期结束,消除同一事实两份表达) |
229
+ | 0.3.2 | 2026-10-31 | 修复 exec 插件的假告警:前端不再把 `runtime:"exec"` 的插件当 JS 插件注入脚本(其 `files[]` 为空 ⇒ 必然误报「已加载但没有调用 register()」),改为只登记声明、不计入"需装载"分母 |
230
+ | 0.3.1 | 2026-10-31 | 探测不再阻塞事件循环:两级空闲门控(有请求在飞不解析)+ 单包 24MB 上限 + 解析移入 worker 线程;新增显式生命周期 `close()` 与 SIGINT/SIGTERM 收尾(修复 worker 悬挂导致测试进程不退出);实测事件循环 max 683→104ms、吞吐 28.5→63.9/min |
231
+ | 0.3.0 | 2026-10-31 | 插件运行时双形态落地:`exec`(子进程 + 行 JSON 帧协议 + 双向超时必杀 + 崩溃传播)与首个 Python 插件 `unitypy-bundle`(UnityPy 解包,覆盖 ASTC 等 JS 侧缺口);前端本地解包失败时兜底走服务端解包;补 spine 3.8 skel 版本探测(原按 4.x 解析导致装载报错) |
232
+ | 0.2.3 | 2026-10-31 | 清单缓存加「生成者指纹」:由解析器文件内容哈希派生(spine/live2d/probe),改解析代码后落盘清单自动失效重建,不再需要手工删 config/models/*.json;live2d 清单写盘改原子写(与 spine 同口径) |
233
+ | 0.2.2 | 2026-10-31 | 后台探测可观测:启动/完成/暂停三条日志与触发来源(catalog/fingerprint/manual)可辨;`GET /api/entries/probe` 新增 lastTrigger/lastBatchMs/lastQueueAfter,catalog 响应附加 `probeRemaining`(只增不改);前端 `state.probe` 数据面就绪(UI 未实现) |
234
+ | 0.2.1 | 2026-10-31 | 条目状态落地:`entry-states` 存储与 `/api/entries/result` 写读一体、catalog 三字段(display/support/loadResult)收口、前端只渲染字段并删除 localStorage 失败缓存;新增后台形态探测(事件驱动、按根串行限速、`POST /api/entries/probe`)与 S6 指纹接线三处补齐;`listCatalog` 拆分为 12 个函数(原 123 行) |
235
+ | 0.2.0 | 2026-10-31 | 条目状态契约定稿:展示归属/可装载预判/最近装载结果三项收归服务端唯一权威,内置 spine/live2d 装载器与插件同表登记(builtin-*);ETC2_RGBA1 纹理解码补齐、美术依赖包可读判定、徽标配色改由插件自声明;名册加载计时与 `/api/catalog/stats` |
236
+ | 0.1.0 | 2026-10-28 | 首个可用版本:三级名册、Spine / Live2D 双渲染面、UnityFS 资源包解包、插件能力过滤面板 |
237
+
238
+ ---
239
+
240
+ ## 合规状态
241
+
242
+ | 项目 | 许可证 | 说明 |
243
+ |---|---|---|
244
+ | **本项目自有代码** | **MIT**(见 [LICENSE](LICENSE)) | — |
245
+ | **Boneview**([仓库](https://github.com/souling01/spine-viewer-webgl)) | **无 LICENSE**(默认保留全部权利) | 仅**前端布局**参考;本项目代码 90% 以上自研,不含其代码 |
246
+ | **Spine Runtimes** | 自定义协议 | 运行时源码**不入库**、需自备;**不受本项目 MIT 覆盖** |
247
+ | **Live2D Cubism SDK** | Live2D Proprietary Software License | 运行时(`live2dcubismcore` 等)**不入库**、需自备;文件头自称 "Redistributable Code" 指可随应用分发,**不等于**可放进公开仓库再分发 |
248
+ | **pixi.js** | MIT | 仅当自备的 Live2D 运行时目录内自带时存在;同样不入库 |
249
+
250
+ > 本项目使用官方 Spine Runtimes([Esoteric Software](https://esotericsoftware.com)),受 [Spine Runtimes License Agreement](https://esotericsoftware.com/spine-runtimes-license) 约束。集成预览可以,但每个使用 Spine 数据的终端用户仍需拥有 Spine 编辑器授权。
251
+
252
+ **服务端把运行时报给浏览器 = 分发行为**:本项目会把使用者本地放置的运行时文件发给访问者(`/spine-runtimes/*` 等)。
253
+ 因此**部署为公开可访问的服务**时,请自行确认所用运行时的许可是否覆盖这一场景。
254
+
255
+ 完整的第三方组件清单与逐项说明见 [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md)。
256
+
257
+ **仓库内不含任何游戏提取资源** —— 模型、纹理等素材均不入库,需自备。
258
+
259
+ ---
260
+
261
+ ## 免责
262
+
263
+ 许可证说明基于开源许可一般原理,**不构成法律意见**。对外分发前建议咨询专业 IP 律师。
264
+
265
+ ## 导出物版权
266
+
267
+ `tool/export-bundle.mjs` 导出的 `moc3`、`physics3`、贴图 PNG 等均为**游戏原始素材**,
268
+ 仅供本地研究与个人使用,请勿再分发。导出产物默认写入 `data/export/`(该目录已在 `.gitignore`)。
@@ -0,0 +1,90 @@
1
+ # 第三方组件与授权声明(THIRD PARTY NOTICES)
2
+
3
+ 本项目(ModelStage)自身代码以 **MIT** 发布,见 [LICENSE](LICENSE)。
4
+ 以下第三方组件的授权**不由本项目授予**,各自遵循其原始协议。本项目**不随仓库、也不随 npm 包分发这些运行时的源码**。
5
+
6
+ ---
7
+
8
+ ## 1. Spine Runtimes(Esoteric Software)
9
+
10
+ ```
11
+ 组件 Spine Runtimes(spine-webgl / spine-ts,多版本:3.6 / 3.7 / 3.8 / 4.1)
12
+ 权利方 Esoteric Software LLC
13
+ 协议 Spine Runtimes License Agreement
14
+ 项目位置 不入库(.gitignore:src/spine-runtimes/),也不在 npm 包的 files 白名单内
15
+ 获取方式 由使用者在本地自行放置;项目不下载、不内嵌、不再分发
16
+ ```
17
+
18
+ **使用者需要注意的两点:**
19
+
20
+ 1. **持有许可**:将包含 Spine Runtimes 的软件分发给他人的,需要有效的 Spine 编辑器许可
21
+ (Spine Essential 等,按 Esoteric Software 现行条款)。
22
+ 2. **"服务端把运行时报给浏览器"属于分发**:本项目的服务会把本地运行时的文件发给访问者
23
+ (`/spine-runtimes/*`)。因此**部署为公开可访问的服务**时,请自行确认上述条款是否覆盖该场景。
24
+
25
+ 协议全文与最新条款见 Esoteric Software 官方站点(Spine Runtimes License Agreement)。
26
+ 本文件不复制其条款正文,以避免版本过期造成误读。
27
+
28
+ ---
29
+
30
+ ## 2. Live2D Cubism SDK(Live2D Inc.)
31
+
32
+ ```
33
+ 组件 Live2D Cubism Core / Cubism Framework(以及配套的 Live2D 运行时目录)
34
+ 权利方 Live2D Inc.
35
+ 协议 Live2D Proprietary Software License Agreement
36
+ 项目位置 不入库(.gitignore:src/live2d-runtimes/),也不在 npm 包的 files 白名单内
37
+ 获取方式 由使用者在本地自行放置;项目不下载、不内嵌、不再分发
38
+ ```
39
+
40
+ 运行时文件头自带声明(示例):
41
+
42
+ ```
43
+ /**
44
+ * Live2D Cubism Core
45
+ * (C) 2019 Live2D Inc. All rights reserved.
46
+ *
47
+ * This file is licensed pursuant to the license agreement below.
48
+ * This file corresponds to the "Redistributable Code" in the agreement.
49
+ * https://www.live2d.com/eula/live2d-proprietary-software-license-agreement_en.html
50
+ */
51
+ ```
52
+
53
+ **要点**:
54
+
55
+ - 该文件属于协议中的 **"Redistributable Code"** —— 允许随**你的应用**分发,但**不等于**可以把它
56
+ 放进公开源码仓库对外再分发。本项目据此把它排除在仓库与 npm 包之外。
57
+ - **商业使用**(含以 Live2D 内容提供服务的场景)需要按 Live2D 的条款取得相应授权。
58
+ - 分发时需保留上述版权与协议声明,不得移除。
59
+
60
+ ---
61
+
62
+ ## 3. pixi.js(仅当使用者自备的 Live2D 运行时目录内含它)
63
+
64
+ ```
65
+ 组件 pixi.js(部分 Live2D 运行时目录会带 pixi.min.js)
66
+ 协议 MIT
67
+ ```
68
+
69
+ 如果使用者的 Live2D 运行时目录里含 `pixi.min.js`,其 MIT 许可与版权归 pixi.js 作者所有;
70
+ 它同样位于不入库的运行时目录内,不随本项目分发。
71
+
72
+ ---
73
+
74
+ ## 4. Node.js 内置模块
75
+
76
+ 项目为零运行时依赖(不引入 npm 依赖包),仅使用 Node.js 内置模块,其许可归 Node.js 项目所有。
77
+
78
+ ---
79
+
80
+ ## 5. 上游数据与各自游戏的资源
81
+
82
+ 本项目是一个**查看器/解包器**:它读取使用者本地的资源包并渲染。
83
+ 资源包中的美术、模型、动画等内容的版权归**各自的权利方**(游戏厂商/素材作者)所有,
84
+ 本项目不提供、也不分发任何此类内容。使用者需自行确认其使用场景的合法性。
85
+
86
+ ---
87
+
88
+ ## 维护说明
89
+
90
+ 本声明随项目维护更新。若你发现遗漏的第三方组件,请提交 issue 指出。
@@ -0,0 +1,101 @@
1
+ #!/usr/bin/env node
2
+ // bin/modelstage.mjs —— npx / 全局安装时的启动入口(薄封装)
3
+ //
4
+ // 职责只有三件:解析命令行参数 → 翻译成 serve.mjs 认识的环境变量 → import 真正的服务入口。
5
+ // 为什么不在 bin 里复制启动逻辑:src/server/serve.mjs 是唯一的服务入口(路由表、插件宿主、
6
+ // 静态托管、就绪信号都在里面)。复制一份就等于有两套启动语义,迟早漂移 ——
7
+ // 本项目已经因为"两套实现"返工过多次(两套调度、两份解码副本)。
8
+ //
9
+ // 环境变量与参数的对应关系(与 web.sh 完全一致,两种方式等价):
10
+ // --port <n> → PORT
11
+ // --data-root <path> → MODELSTAGE_DATA_ROOT
12
+
13
+ import { readFileSync } from 'node:fs';
14
+ import { fileURLToPath } from 'node:url';
15
+ import os from 'node:os';
16
+ import path from 'node:path';
17
+ import { here } from '../tool/lib/paths.mjs';
18
+
19
+ // 本文件所在目录(写法见 tool/lib/paths.mjs,6 个脚本共用一处实现)
20
+ const HERE = here(import.meta.url);
21
+ const PKG_PATH = path.join(HERE, '..', 'package.json');
22
+
23
+ const USAGE = `用法:modelstage [选项]
24
+
25
+ --port <n> 监听端口(默认 8804,等价于环境变量 PORT)
26
+ --data-root <path> 数据根目录:日志 / 解包缓存 / 配置的持久化位置
27
+ (等价于环境变量 MODELSTAGE_DATA_ROOT;缺省 = 项目根)
28
+ 用 npx 运行时建议显式指定一个**可写且持久**的目录
29
+ -h, --help 显示本帮助
30
+ -v, --version 显示版本号(读 package.json,它是版本号唯一权威)
31
+
32
+ 示例:
33
+ npx . --port 8804 --data-root ~/modelstage-data
34
+ PORT=8804 MODELSTAGE_DATA_ROOT=~/modelstage-data npx .
35
+
36
+ 运行时库(Spine / Live2D 的 runtimes)不随包分发:npx 直跑需自行准备运行时目录,
37
+ 或用 SPINE_RUNTIME_DIR / LIVE2D_SEARCH_ROOTS 指定已有的搜索根。`;
38
+
39
+ /** 参数错误:打一行原因 + 完整用法,退出码 1(不用异常栈吓人) */
40
+ function fail(message) {
41
+ process.stderr.write(`modelstage: ${message}\n\n${USAGE}\n`);
42
+ process.exit(1);
43
+ }
44
+
45
+ /**
46
+ * 解析 argv。只认两个带值的长选项与 -h/-v,不引第三方参数解析库。
47
+ * 同时支持 `--port 8804` 与 `--port=8804` 两种写法(后者在 npx 传参时更常见)。
48
+ */
49
+ function parseArgs(argv) {
50
+ const parsed = { port: null, dataRoot: null, help: false, version: false };
51
+ for (let i = 0; i < argv.length; i += 1) {
52
+ const arg = argv[i];
53
+ if (arg === '-h' || arg === '--help') parsed.help = true;
54
+ else if (arg === '-v' || arg === '--version') parsed.version = true;
55
+ else if (arg === '--port') { const v = argv[(i += 1)]; if (!v) fail('--port 缺少取值'); parsed.port = v; }
56
+ else if (arg.startsWith('--port=')) parsed.port = arg.slice('--port='.length);
57
+ else if (arg === '--data-root') { const v = argv[(i += 1)]; if (!v) fail('--data-root 缺少取值'); parsed.dataRoot = v; }
58
+ else if (arg.startsWith('--data-root=')) parsed.dataRoot = arg.slice('--data-root='.length);
59
+ else fail(`未知参数:${arg}`);
60
+ }
61
+ return parsed;
62
+ }
63
+
64
+ const args = parseArgs(process.argv.slice(2));
65
+
66
+ if (args.help) {
67
+ process.stdout.write(`${USAGE}\n`);
68
+ process.exit(0);
69
+ }
70
+
71
+ if (args.version) {
72
+ // 用 fs 读而不是 `import pkg from './package.json' with { type: 'json' }`:
73
+ // import attributes 在 Node 20/22 之间行为不一致(实验特性),fs 读在任何版本都稳定
74
+ const pkg = JSON.parse(readFileSync(PKG_PATH, 'utf8'));
75
+ process.stdout.write(`${pkg.version}\n`);
76
+ process.exit(0);
77
+ }
78
+
79
+ if (args.port !== null) {
80
+ // 端口必须能转成整数:serve.mjs 用的是 Number(process.env.PORT) || 8804,
81
+ // 传个 "abc" 会静默回落到 8804 —— 看上去启动了、其实端口不是期望的那个,这里提前拦下
82
+ if (!/^\d+$/.test(args.port)) fail(`--port 必须是整数,收到 ${args.port}`);
83
+ process.env.PORT = args.port;
84
+ }
85
+
86
+ if (args.dataRoot) {
87
+ // 转成绝对路径:serve.mjs 会把它当数据根拼日志与缓存路径,相对路径会随 cwd 漂移
88
+ process.env.MODELSTAGE_DATA_ROOT = path.resolve(args.dataRoot);
89
+ } else if (!process.env.MODELSTAGE_DATA_ROOT) {
90
+ // 没给 --data-root、也没有环境变量 ⇒ **装成包运行时落用户目录,不是包目录**。
91
+ //
92
+ // 为什么必须在这里注入、而不是改 serve.mjs 的默认值:
93
+ // serve.mjs 的缺省是"数据根 = 项目根",从 git 仓库跑(web.sh)时那正是对的 ——
94
+ // 数据就在仓库里,行为一字不能变。但装成 npm 包后"项目根"= node_modules/modelstage/,
95
+ // 日志、解包缓存、runtimes 全落进包目录:升级即丢,且包目录可能只读。
96
+ // ⇒ 只在 bin 层(包运行专用的入口)注入,仓库运行路径完全不受影响。
97
+ process.env.MODELSTAGE_DATA_ROOT = path.join(os.homedir(), '.modelstage');
98
+ }
99
+
100
+ // 交给真正的服务入口:它是模块顶层直接 listen 的,import 即启动,无需再 await 返回值
101
+ await import('../src/server/serve.mjs');
@@ -0,0 +1,7 @@
1
+ {
2
+ // 调试日志上传的上限(复制为 config/diag-upload.json 生效;缺失时用代码内默认值)
3
+ // maxCount:单次上传最多保留多少条;超出时只保留最新的 N 条,并在文件里标 "truncated": true
4
+ "maxCount": 5000,
5
+ // maxBytes:单次请求体字节上限;超出服务端直接回 413(不会先读进内存再判断)
6
+ "maxBytes": 5242880
7
+ }
@@ -0,0 +1 @@
1
+ {"version":1,"rootId":"assetbundls","ts":1793427353401,"dirStamps":{"root":1711299670977,"dirs":{"[Yukinwo]":1711299010924,"[林大大 lin da da]":1708372445243,"[离弦]":1706951924648}},"generator":"0f60b6bf","models":[]}