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
@@ -0,0 +1,77 @@
1
+ // engine/gl.js —— WebGL 上下文、像素尺寸、DPR 与渲染器探测
2
+ //
3
+ // 边界:只管"画布与上下文",不碰骨架/动画/相机(相机在 camera-adapter,B2 落地)。
4
+ // DPR 语义auto 时软件渲染钳 1,否则 min(deviceDpr, 2)。
5
+
6
+ /** 软件渲染探测正则 */
7
+ const SOFTWARE_RE = /swiftshader|llvmpipe|softpipe|basic render|software|warp|mesa offscreen/i;
8
+
9
+ /**
10
+ * 建上下文。
11
+ * @param {HTMLCanvasElement} canvas
12
+ * @param {{antialias?: boolean}} opts
13
+ * @returns {{gl: WebGLRenderingContext|null, isSoftware: boolean, rendererStr: string}}
14
+ */
15
+ export function createContext(canvas, opts = {}) {
16
+ const attrs = {
17
+ premultipliedAlpha: true,
18
+ antialias: opts.antialias === true,
19
+ alpha: true,
20
+ preserveDrawingBuffer: true,
21
+ powerPreference: 'high-performance',
22
+ };
23
+ let gl = null;
24
+ try {
25
+ gl = canvas.getContext('webgl2', attrs) || canvas.getContext('webgl', attrs) || canvas.getContext('experimental-webgl', attrs);
26
+ } catch {
27
+ gl = null;
28
+ }
29
+ if (!gl) return { gl: null, isSoftware: false, rendererStr: '' };
30
+ let rendererStr = '';
31
+ let isSoftware = false;
32
+ try {
33
+ const ext = gl.getExtension('WEBGL_debug_renderer_info');
34
+ rendererStr = ext ? String(gl.getParameter(ext.UNMASKED_RENDERER_WEBGL) || '') : '';
35
+ isSoftware = SOFTWARE_RE.test(rendererStr.toLowerCase());
36
+ } catch {
37
+ /* 取不到就当硬件渲染 */
38
+ }
39
+ return { gl, isSoftware, rendererStr };
40
+ }
41
+
42
+ /**
43
+ * 生效 DPR。
44
+ * @param {string} dprMode 'auto' | '1' | '1.5' | '2'
45
+ * @param {boolean} isSoftware 软件渲染时 auto 钳到 1
46
+ * @param {number} deviceDpr
47
+ * @returns {number}
48
+ */
49
+ export function effectiveDpr(dprMode, isSoftware, deviceDpr) {
50
+ if (dprMode !== 'auto') {
51
+ const n = parseFloat(dprMode);
52
+ return Number.isFinite(n) && n > 0 ? n : 1;
53
+ }
54
+ const cap = isSoftware ? 1 : 2;
55
+ return Math.min(Number(deviceDpr) || 1, cap);
56
+ }
57
+
58
+ /**
59
+ * 把逻辑尺寸 + DPR 落到画布 backing store,并同步 GL viewport。
60
+ * @param {HTMLCanvasElement} canvas
61
+ * @param {WebGLRenderingContext} gl
62
+ * @param {{logicalW: number, logicalH: number, dpr: number}} box
63
+ * @returns {{w: number, h: number, changed: boolean}}
64
+ */
65
+ export function applyPixelSize(canvas, gl, box) {
66
+ const w = Math.max(1, Math.floor((box.logicalW || 0) * (box.dpr || 1)));
67
+ const h = Math.max(1, Math.floor((box.logicalH || 0) * (box.dpr || 1)));
68
+ const changed = canvas.width !== w || canvas.height !== h;
69
+ if (changed) {
70
+ canvas.width = w;
71
+ canvas.height = h;
72
+ }
73
+ if (gl) {
74
+ try { gl.viewport(0, 0, w, h); } catch { /* 上下文丢失时忽略 */ }
75
+ }
76
+ return { w, h, changed };
77
+ }
@@ -0,0 +1,20 @@
1
+ // engine/index.js —— 引擎唯一对外入口
2
+ //
3
+ // 只暴露 `SpineEngine`与工厂 `createEngine`;
4
+ // `spine/canvas.js` 只依赖本文件,不直接 import 内部模块(分层约束)。
5
+ // B1 范围:装载/显示/帧循环/相机读写/动画切换/设置项最小实现;外观类(PMA/滤镜/纹理重建)在 B4。
6
+
7
+ import { SpineEngine } from './player.js';
8
+
9
+ export { SpineEngine };
10
+
11
+ /**
12
+ * 工厂(等价 `new SpineEngine(opts)`,便于测试替换)。
13
+ * @param {object} opts { log, onLoadComplete, onError, runtimeManager? }
14
+ * @returns {SpineEngine}
15
+ */
16
+ export function createEngine(opts = {}) {
17
+ return new SpineEngine(opts);
18
+ }
19
+
20
+ export default createEngine;
@@ -0,0 +1,55 @@
1
+ // engine/loader.js —— 描述符(骨架/图集/纹理 ArrayBuffer)→ 可渲染对象
2
+ //
3
+ // 贴图解码/PMA/上传全部交给 pipeline.js,本文件只负责:
4
+ // ① 把 pipeline 产出的 atlas 接到骨架解析上;② 不出现任何版本判断(类名解析在 adapters/runtime-capabilities)。
5
+
6
+ import { prepareAtlas } from './pipeline.js';
7
+
8
+ /** 骨架是否 JSON(首个非空白字节为 `{`) */
9
+ function sniffJson(buf) {
10
+ const bytes = new Uint8Array(buf);
11
+ for (let i = 0; i < Math.min(bytes.length, 64); i += 1) { // dsh-skip-quality 探测 skel 头部的固定长度,既有代码
12
+ const c = bytes[i];
13
+ if (c === 9 || c === 10 || c === 13 || c === 32) continue; // dsh-skip-quality 空白字符码点,既有代码
14
+ return c === 0x7B; // dsh-skip-quality "{" 的 ASCII 码,既有代码
15
+ }
16
+ return false;
17
+ }
18
+
19
+ /** 解析骨架数据 */
20
+ function readSkeletonData(adapters, descriptor, attachmentLoader) {
21
+ const buf = descriptor.skeletonFile.content;
22
+ if (sniffJson(buf)) {
23
+ const text = new TextDecoder().decode(buf);
24
+ return adapters.skeleton.readJson(attachmentLoader, JSON.parse(text.replace(/,(\s*[}\]])/g, '$1')));
25
+ }
26
+ return adapters.skeleton.readBinary(attachmentLoader, buf);
27
+ }
28
+
29
+ /**
30
+ * 建 atlas + 骨架。
31
+ * @param {{adapters: object, spine: object, gl: WebGLRenderingContext, descriptor: object,
32
+ * pmaMode: string, textureFilter: string, supportsStraight: boolean, log: Function}} ctx
33
+ */
34
+ export async function buildModel(ctx) {
35
+ const { adapters, spine, gl, descriptor, log } = ctx;
36
+ const prepared = await prepareAtlas({
37
+ spine, gl, descriptor,
38
+ pmaMode: ctx.pmaMode || 'auto',
39
+ textureFilter: ctx.textureFilter || 'linear',
40
+ supportsStraight: ctx.supportsStraight !== false,
41
+ log,
42
+ });
43
+ const attachmentLoader = adapters.skeleton.attachmentLoader(prepared.atlas);
44
+ const skeletonData = readSkeletonData(adapters, descriptor, attachmentLoader);
45
+ const runtime = adapters.skeleton.createRuntime(skeletonData);
46
+ return {
47
+ atlas: prepared.atlas,
48
+ textures: prepared.textures,
49
+ pmaInfo: prepared.pmaInfo,
50
+ pmaDetection: prepared.pmaDetection,
51
+ premultiplied: prepared.pmaInfo.blendMode === 'premultiplied',
52
+ skeletonData,
53
+ ...runtime,
54
+ };
55
+ }
@@ -0,0 +1,26 @@
1
+ // engine/model-info.js —— 装载结果摘要
2
+ //
3
+ // 单独成文件的原因:摘要字段(动作/皮肤/骨骼/插槽计数)是"给上层看的形状",
4
+ // 与渲染实现无关,放在 player 里会让主类继续膨胀。
5
+
6
+ /**
7
+ * 生成装载结果摘要。
8
+ * @param {{engine: object, descriptor: object}} ctx
9
+ * @returns {{name: string, version: string, animations: string[], skins: string[],
10
+ * boneCount: number, slotCount: number, durations: object}}
11
+ */
12
+ export function describeModel({ engine, descriptor }) {
13
+ const data = engine.skeletonData;
14
+ const anims = (data && data.animations) || [];
15
+ const skins = (data && data.skins) || [];
16
+ const skeleton = engine.skeleton;
17
+ return {
18
+ name: descriptor.name,
19
+ version: engine.runtimeVersion || descriptor.version,
20
+ animations: anims.map((a) => a.name),
21
+ skins: skins.map((s) => s.name),
22
+ boneCount: (skeleton && skeleton.bones && skeleton.bones.length) || 0,
23
+ slotCount: (skeleton && skeleton.slots && skeleton.slots.length) || 0,
24
+ durations: {},
25
+ };
26
+ }
@@ -0,0 +1,200 @@
1
+ // engine/pipeline.js —— 贴图装载流水线:解码 → PMA 判定 → 上传 → 建图集
2
+ //
3
+ // 顺序与判定条件:
4
+ // 解码每页 →(auto 档)取像素做检测 → 三分支定 PMA → 决定 GPU/CPU 预乘 → 上传纹理 → 用页名匹配建 atlas
5
+ // 业务层(player / loader)只调本文件,不碰运行时版本差异(版本相关的取类走 runtime-capabilities + adapters)。
6
+
7
+ import {
8
+ SMALL_PAGE_PX, parseAtlasPageSizes, decodeImageSource, sampleSourceImageData,
9
+ uploadTextures, makePlaceholderTexture,
10
+ } from './textures.js';
11
+ import { atlasDeclaredPma, filenamePmaHint, detectPma, resolvePmaInfo } from './pma.js';
12
+ import { pickMember } from './runtime-capabilities.js';
13
+ import { createLogger } from '../infra/log.js';
14
+
15
+ /** PMA 诊断日志通道(debug 级:点分事件名 + 结构化 detail,机器可解析;不进 error 位、不刷屏) */
16
+ const pmaLog = createLogger('pma');
17
+
18
+ /** 透明占位页(保留尺寸,不上传真实像素) */
19
+ function transparentEntry(file, size) {
20
+ const canvas = document.createElement('canvas');
21
+ canvas.width = Math.max(1, Math.floor(size.width || 1));
22
+ canvas.height = Math.max(1, Math.floor(size.height || 1));
23
+ return { name: file.name, width: canvas.width, height: canvas.height, img: null, canvas, transparent: true };
24
+ }
25
+
26
+ /** 页名 → 已上传纹理:精确 → 去扩展名 → 互相包含 */
27
+ function matchTexture(map, pageName) {
28
+ if (map.has(pageName)) return map.get(pageName);
29
+ const key = String(pageName || '').split(/[\\/]/).pop().toLowerCase();
30
+ const stripped = key.replace(/\.[^.]+$/, '');
31
+ for (const [name, tex] of map) {
32
+ const n = String(name).split(/[\\/]/).pop().toLowerCase();
33
+ if (n === key || n.replace(/\.[^.]+$/, '') === stripped) return tex;
34
+ }
35
+ for (const [name, tex] of map) {
36
+ const n = String(name).replace(/\.[^.]+$/, '').toLowerCase();
37
+ if (stripped && (n.includes(stripped) || stripped.includes(n))) return tex;
38
+ }
39
+ return null;
40
+ }
41
+
42
+ /**
43
+ * 解码全部贴图页。多页图集里 <100px 的页按做法用透明画布跳过
44
+ * (这类页通常是占位,参与像素检测会把结论带偏)。
45
+ */
46
+ async function decodePages({ descriptor, pageSizes, multiPage, wantSamples, log }) {
47
+ const entries = new Map();
48
+ const samples = [];
49
+ for (const file of (descriptor.textureFiles || [])) {
50
+ try {
51
+ const declared = multiPage ? pageSizes.get(file.name) || pageSizes.get(findPageName(pageSizes, file.name)) : null;
52
+ if (declared && (declared.width < SMALL_PAGE_PX || declared.height < SMALL_PAGE_PX)) {
53
+ entries.set(file.name, transparentEntry(file, declared));
54
+ log('info', `检测到小尺寸图集页 ${file.name}(${declared.width}x${declared.height}),已用透明贴图跳过`, 'loader');
55
+ continue;
56
+ }
57
+ const decoded = await decodeImageSource(file);
58
+ if (multiPage && (decoded.width < SMALL_PAGE_PX || decoded.height < SMALL_PAGE_PX)) {
59
+ entries.set(file.name, transparentEntry(file, decoded));
60
+ log('info', `检测到小尺寸图集页 ${file.name}(${decoded.width}x${decoded.height}),已用透明贴图跳过`, 'loader');
61
+ continue;
62
+ }
63
+ const entry = { name: file.name, width: decoded.width, height: decoded.height, img: decoded.img, canvas: null, transparent: false };
64
+ entries.set(file.name, entry);
65
+ if (wantSamples) {
66
+ // 同步 canvas 取样
67
+ const sampled = sampleSourceImageData(entry);
68
+ if (sampled) samples.push(sampled);
69
+ }
70
+ } catch (e) {
71
+ log('error', `加载贴图 ${file && file.name} 失败: ${e && e.message ? e.message : e}`, 'loader');
72
+ }
73
+ }
74
+ return { entries, samples };
75
+ }
76
+
77
+ /**
78
+ * PMA 判定链:atlas 声明 → 文件名线索 → 像素检测 → 生效模式。
79
+ *
80
+ * 四个判定串成一条链、彼此是数据依赖(后一步吃前一步的结论),所以放一起;
81
+ * 但整段**不产生产物、不抛错**:全部走返回值,由调用方决定怎么记日志。
82
+ * 与日志分开的理由:日志是副作用、判定是纯逻辑 —— 混在一起时
83
+ * "流程走到哪一步"会被十几个日志字段淹没(拆分前就是这个问题)。
84
+ * @returns {{atlasDeclared: boolean|null, filenameHint: boolean|null, detection: object, pmaInfo: object}}
85
+ */
86
+ function detectPmaStage({ atlasText, descriptor, pmaMode, supportsStraight, samples, log }) {
87
+ const atlasDeclared = atlasDeclaredPma(atlasText);
88
+ const firstName = (descriptor.textureFiles && descriptor.textureFiles[0] && descriptor.textureFiles[0].name)
89
+ || descriptor.atlasFile.name;
90
+ const filenameHint = filenamePmaHint(firstName);
91
+ const detection = detectPma({ atlasDeclared, filenameHint, samples }, log);
92
+ const pmaInfo = resolvePmaInfo(pmaMode, detection, supportsStraight);
93
+ return { atlasDeclared, filenameHint, detection, pmaInfo };
94
+ }
95
+
96
+ /** 贴图文件名 ↔ atlas 页名(页名带相对路径时也能对上) */
97
+ function findPageName(pageSizes, file) {
98
+ const want = String(file.name || '').split(/[\\/]/).pop().toLowerCase();
99
+ for (const page of pageSizes.keys()) {
100
+ if (String(page).split(/[\\/]/).pop().toLowerCase() === want) return page;
101
+ }
102
+ return null;
103
+ }
104
+
105
+ /**
106
+ * PMA 的三条日志,按固定顺序发:debug(结构化诊断)→ degraded 时 warn → info(人读汇总)。
107
+ *
108
+ * 顺序不是随意的:现场排障时是拿 info 那一行去和 debug 里的 `pixelDetected/reason` 对拉,
109
+ * warn 夹在中间是为了"降级"这件事在日志流里紧跟着它的原因出现。改顺序会破坏这个对拉关系。
110
+ *
111
+ * 为什么必须记这条 debug:切模式时那条 info 只覆盖"用户手动切换",而**首次装载 / 刷新后走 auto 的那一次**
112
+ * 以前完全没有记录 —— 现场"刷新后自动档似乎没按预乘处理"就卡在这个盲区里。
113
+ *
114
+ * 本函数**不改变任何行为**(不写状态、不改 pmaInfo),是整条流水线里唯一可以完全跳过的一段。
115
+ */
116
+ function logPmaStage({ descriptor, pmaMode, atlasDeclared, filenameHint, detection, pmaInfo, supportsStraight, log }) {
117
+ pmaLog.debug('pma.resolve', {
118
+ model: descriptor && (descriptor.id || descriptor.rel || descriptor.name) || null,
119
+ mode: pmaMode,
120
+ detected: detection ? detection.finalIsPremultiplied : null, // 探测最终结论
121
+ pixelDetected: detection ? detection.pixelDetected : null, // 纯像素结论(null=像素不足)
122
+ atlasDeclared: detection ? detection.atlasDeclared : null,
123
+ filenameHint: detection ? detection.filenameHint : null,
124
+ reason: detection ? detection.reason : null,
125
+ effective: pmaInfo.effectiveMode,
126
+ needPremultiply: pmaInfo.needPremultiply,
127
+ blend: pmaInfo.blendMode,
128
+ degraded: !!pmaInfo.degraded,
129
+ supportsStraight,
130
+ runtimeSupportsStraight: pmaInfo.runtimeSupportsStraight,
131
+ });
132
+ if (pmaInfo.degraded) log('warn', '运行时不支持非预乘直通混合,已自动转为转预乘', 'pma');
133
+ // 上传口径已固定:**一律 UNPACK_PREMULTIPLY_ALPHA_WEBGL=false(不再乘)**,
134
+ // 理由与实测数字见 pma.js uploadTextures 的注释(源 HTMLImageElement 解码即预乘,再乘是双乘)。
135
+ // 所以这里不再计算 gpuUnpack —— 它已经不影响任何行为,留着会误导下一个人。
136
+ // 与同文案(生效口径 + 运行时是否支持直通),便于两引擎日志直接对拉
137
+ log('info', `PMA: 用户=${pmaMode}, atlas声明=${atlasDeclared === null ? '未声明' : String(atlasDeclared)}, `
138
+ + `文件名=${filenameHint === null ? '无线索' : String(filenameHint)}, 像素检测=${detection.pixelDetected === null ? '未检测' : String(detection.pixelDetected)}, `
139
+ + `生效=${pmaInfo.effectiveMode}, 运行时直通=${pmaInfo.runtimeSupportsStraight ? '支持' : '不支持'}`, 'pma');
140
+ }
141
+
142
+ /**
143
+ * 用已上传的纹理建 TextureAtlas,并把页对象上的 texture 补齐。
144
+ *
145
+ * 两个运行时形态差异就在这里(调用方不需要知道):
146
+ * · 3.x:`new TextureAtlas(atlasText, resolver)` 构造时就用 resolver 把每页填好;
147
+ * · 4.x:构造函数不吃 resolver,页对象上留 `setTexture()`,需在构造后逐页补。
148
+ * 缺页时 resolver 落到 `makePlaceholderTexture()`(1×1 透明),**每个缺页各建一个**——
149
+ * 不要改成共享同一个:placeholder 是 1×1 canvas,次数变化不会报错,只会多/少一个 GPU 纹理。
150
+ * @returns {object} 已就绪的 atlas
151
+ */
152
+ function buildAtlasFromTextures({ spine, atlasText, textures }) {
153
+ const TextureAtlas = pickMember(spine, 'TextureAtlas');
154
+ if (!TextureAtlas) throw new Error('当前运行时不支持 TextureAtlas');
155
+ const resolvePage = (pageName) => matchTexture(textures, pageName) || makePlaceholderTexture();
156
+ // 3.x 构造吃 resolver;4.x 不吃 → 构造后再逐页补齐(匹配规则相同)
157
+ const atlas = new TextureAtlas(atlasText, resolvePage);
158
+ for (const page of (atlas.pages || [])) {
159
+ if (page.texture) continue;
160
+ const tex = resolvePage(page.name);
161
+ if (typeof page.setTexture === 'function') page.setTexture(tex);
162
+ else page.texture = tex;
163
+ }
164
+ return atlas;
165
+ }
166
+
167
+ /**
168
+ * 走完整流水线,产出可直接交给 AtlasAttachmentLoader 的 atlas。
169
+ * @param {{spine: object, gl: WebGLRenderingContext, descriptor: object, pmaMode: string,
170
+ * textureFilter: string, supportsStraight: boolean, log?: Function}} ctx
171
+ */
172
+ export async function prepareAtlas(ctx) {
173
+ const { spine, gl, descriptor, pmaMode, textureFilter, supportsStraight, log = () => {} } = ctx;
174
+ const atlasText = new TextDecoder().decode(descriptor.atlasFile.content);
175
+ if (!atlasText) throw new Error('atlas 文件为空或无法读取');
176
+ const pageSizes = parseAtlasPageSizes(atlasText);
177
+ const multiPage = (descriptor.textureFiles || []).length > 1;
178
+ // ① 解码全部页(只有 auto 档才取样,供后面的像素检测用)
179
+ const { entries, samples } = await decodePages({ descriptor, pageSizes, multiPage, wantSamples: pmaMode === 'auto', log });
180
+ if (entries.size === 0) throw new Error('没有成功加载任何贴图文件,无法渲染模型');
181
+ // ② PMA 判定(纯逻辑)与诊断日志(副作用)分两步,后者完全不影响返回值
182
+ const pmaStage = detectPmaStage({ atlasText, descriptor, pmaMode, supportsStraight, samples, log });
183
+ logPmaStage({ descriptor, pmaMode, ...pmaStage, supportsStraight, log });
184
+ // ③ 上传纹理(口径固定不乘,见 pma.js uploadTextures 注释)
185
+ // 空判定不在这里重复:uploadTextures 自己就在 out.size === 0 时抛
186
+ // 「没有成功上传任何贴图到 GPU,无法创建 TextureAtlas」—— 它永不返回空 Map,
187
+ // 此处再判一次是**不可达的死判定**(同一条件、同一文案)。权威放在产出侧 pma.js:
188
+ // 只有它知道"实际传上了几页",消费侧无从复核,也不该有第二份口径。
189
+ const textures = await uploadTextures({
190
+ gl, GLTextureCtor: pickMember(spine, 'GLTexture'), entries, textureFilter, log,
191
+ effectiveMode: pmaStage.pmaInfo.effectiveMode, needPremultiply: pmaStage.pmaInfo.needPremultiply,
192
+ });
193
+ // ④ 建 atlas(3.x 构造吃 resolver / 4.x 构造后补页的差异在内部消化)
194
+ const atlas = buildAtlasFromTextures({ spine, atlasText, textures });
195
+ // 不再返回 gpuUnpack:上传口径固定不乘(见 pma.js uploadTextures 注释),保留字段名会误导
196
+ return {
197
+ atlas, entries, textures: [...textures.values()],
198
+ pmaInfo: pmaStage.pmaInfo, pmaDetection: pmaStage.detection, atlasText,
199
+ };
200
+ }