@forgeax/engine-assets-runtime 0.1.4 → 0.1.7

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 (311) hide show
  1. package/README.md +453 -175
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/__tests__/aabb.unit.test.d.ts +2 -0
  4. package/dist/__tests__/aabb.unit.test.d.ts.map +1 -0
  5. package/dist/__tests__/artifact-integrity.unit.test.d.ts +2 -0
  6. package/dist/__tests__/artifact-integrity.unit.test.d.ts.map +1 -0
  7. package/dist/__tests__/artifact-io.unit.test.d.ts +2 -0
  8. package/dist/__tests__/artifact-io.unit.test.d.ts.map +1 -0
  9. package/dist/__tests__/asset-evidence-sdk.integration.test.d.ts +2 -0
  10. package/dist/__tests__/asset-evidence-sdk.integration.test.d.ts.map +1 -0
  11. package/dist/__tests__/asset-evidence.unit.test.d.ts +2 -0
  12. package/dist/__tests__/asset-evidence.unit.test.d.ts.map +1 -0
  13. package/dist/__tests__/asset-kind.unit.test.d.ts +2 -0
  14. package/dist/__tests__/asset-kind.unit.test.d.ts.map +1 -0
  15. package/dist/__tests__/asset-registry-catalog-reconcile.integration.test.d.ts +2 -0
  16. package/dist/__tests__/asset-registry-catalog-reconcile.integration.test.d.ts.map +1 -0
  17. package/dist/__tests__/asset-registry-catalog-reconcile.unit.test.d.ts +2 -0
  18. package/dist/__tests__/asset-registry-catalog-reconcile.unit.test.d.ts.map +1 -0
  19. package/dist/__tests__/asset-registry-catalog.unit.test.d.ts +2 -0
  20. package/dist/__tests__/asset-registry-catalog.unit.test.d.ts.map +1 -0
  21. package/dist/__tests__/asset-registry.pack-cache.test.d.ts +2 -0
  22. package/dist/__tests__/asset-registry.pack-cache.test.d.ts.map +1 -0
  23. package/dist/__tests__/asset-registry.unit.test.d.ts +2 -0
  24. package/dist/__tests__/asset-registry.unit.test.d.ts.map +1 -0
  25. package/dist/__tests__/asset-runtime-error-code-owner.test-d.d.ts +2 -0
  26. package/dist/__tests__/asset-runtime-error-code-owner.test-d.d.ts.map +1 -0
  27. package/dist/__tests__/catalog-replica.unit.test.d.ts +2 -0
  28. package/dist/__tests__/catalog-replica.unit.test.d.ts.map +1 -0
  29. package/dist/__tests__/catalog-source-v2.unit.test.d.ts +2 -0
  30. package/dist/__tests__/catalog-source-v2.unit.test.d.ts.map +1 -0
  31. package/dist/__tests__/catalog-source.unit.test.d.ts +2 -0
  32. package/dist/__tests__/catalog-source.unit.test.d.ts.map +1 -0
  33. package/dist/__tests__/catalog-subscription-order.unit.test.d.ts +2 -0
  34. package/dist/__tests__/catalog-subscription-order.unit.test.d.ts.map +1 -0
  35. package/dist/__tests__/catalog-subscription.unit.test.d.ts +2 -0
  36. package/dist/__tests__/catalog-subscription.unit.test.d.ts.map +1 -0
  37. package/dist/__tests__/decode-image-bytes-pass-through.test.d.ts +2 -0
  38. package/dist/__tests__/decode-image-bytes-pass-through.test.d.ts.map +1 -0
  39. package/dist/__tests__/decode-image-bytes.browser.test.d.ts +2 -0
  40. package/dist/__tests__/decode-image-bytes.browser.test.d.ts.map +1 -0
  41. package/dist/__tests__/decode-image-bytes.test-d.d.ts +2 -0
  42. package/dist/__tests__/decode-image-bytes.test-d.d.ts.map +1 -0
  43. package/dist/__tests__/decode-image-bytes.test.d.ts +2 -0
  44. package/dist/__tests__/decode-image-bytes.test.d.ts.map +1 -0
  45. package/dist/__tests__/dynamic-texture-store.unit.test.d.ts +2 -0
  46. package/dist/__tests__/dynamic-texture-store.unit.test.d.ts.map +1 -0
  47. package/dist/__tests__/errors-asset.unit.test.d.ts +2 -0
  48. package/dist/__tests__/errors-asset.unit.test.d.ts.map +1 -0
  49. package/dist/__tests__/inline-pack-loaders.unit.test.d.ts +2 -0
  50. package/dist/__tests__/inline-pack-loaders.unit.test.d.ts.map +1 -0
  51. package/dist/__tests__/instantiate-context.unit.test.d.ts +2 -0
  52. package/dist/__tests__/instantiate-context.unit.test.d.ts.map +1 -0
  53. package/dist/__tests__/instantiate-publication-fence.unit.test.d.ts +2 -0
  54. package/dist/__tests__/instantiate-publication-fence.unit.test.d.ts.map +1 -0
  55. package/dist/__tests__/instantiate.unit.test.d.ts +2 -0
  56. package/dist/__tests__/instantiate.unit.test.d.ts.map +1 -0
  57. package/dist/__tests__/loader-input-contract.unit.test.d.ts +2 -0
  58. package/dist/__tests__/loader-input-contract.unit.test.d.ts.map +1 -0
  59. package/dist/__tests__/loader-registry.unit.test.d.ts +2 -0
  60. package/dist/__tests__/loader-registry.unit.test.d.ts.map +1 -0
  61. package/dist/__tests__/material-dependency-invalidation.unit.test.d.ts +2 -0
  62. package/dist/__tests__/material-dependency-invalidation.unit.test.d.ts.map +1 -0
  63. package/dist/__tests__/material-generation-cache.unit.test.d.ts +2 -0
  64. package/dist/__tests__/material-generation-cache.unit.test.d.ts.map +1 -0
  65. package/dist/__tests__/material-load-consumer.test-d.d.ts +2 -0
  66. package/dist/__tests__/material-load-consumer.test-d.d.ts.map +1 -0
  67. package/dist/__tests__/material-ready.integration.test.d.ts +2 -0
  68. package/dist/__tests__/material-ready.integration.test.d.ts.map +1 -0
  69. package/dist/__tests__/material-stale-generation.unit.test.d.ts +2 -0
  70. package/dist/__tests__/material-stale-generation.unit.test.d.ts.map +1 -0
  71. package/dist/__tests__/mesh-bin-morph-v3.test.d.ts +2 -0
  72. package/dist/__tests__/mesh-bin-morph-v3.test.d.ts.map +1 -0
  73. package/dist/__tests__/mesh-bin.unit.test.d.ts +2 -0
  74. package/dist/__tests__/mesh-bin.unit.test.d.ts.map +1 -0
  75. package/dist/__tests__/mipmap-generator.unit.test.d.ts +2 -0
  76. package/dist/__tests__/mipmap-generator.unit.test.d.ts.map +1 -0
  77. package/dist/__tests__/pack-artifact-transcode-model-owner.test.d.ts +2 -0
  78. package/dist/__tests__/pack-artifact-transcode-model-owner.test.d.ts.map +1 -0
  79. package/dist/__tests__/pack-basis-load.integration.test.d.ts +2 -0
  80. package/dist/__tests__/pack-basis-load.integration.test.d.ts.map +1 -0
  81. package/dist/__tests__/payload-validate.unit.test.d.ts +2 -0
  82. package/dist/__tests__/payload-validate.unit.test.d.ts.map +1 -0
  83. package/dist/__tests__/plugin.test.d.ts +2 -0
  84. package/dist/__tests__/plugin.test.d.ts.map +1 -0
  85. package/dist/__tests__/registry-concurrency.bench.test.d.ts +2 -0
  86. package/dist/__tests__/registry-concurrency.bench.test.d.ts.map +1 -0
  87. package/dist/__tests__/registry-failure-retry.integration.test.d.ts +2 -0
  88. package/dist/__tests__/registry-failure-retry.integration.test.d.ts.map +1 -0
  89. package/dist/__tests__/registry-load-graph.integration.test.d.ts +2 -0
  90. package/dist/__tests__/registry-load-graph.integration.test.d.ts.map +1 -0
  91. package/dist/__tests__/registry-load-state.unit.test.d.ts +2 -0
  92. package/dist/__tests__/registry-load-state.unit.test.d.ts.map +1 -0
  93. package/dist/__tests__/registry-package-cache.unit.test.d.ts +2 -0
  94. package/dist/__tests__/registry-package-cache.unit.test.d.ts.map +1 -0
  95. package/dist/__tests__/registry-public-ready.unit.test.d.ts +2 -0
  96. package/dist/__tests__/registry-public-ready.unit.test.d.ts.map +1 -0
  97. package/dist/__tests__/resolve-asset-handle.unit.test.d.ts +2 -0
  98. package/dist/__tests__/resolve-asset-handle.unit.test.d.ts.map +1 -0
  99. package/dist/__tests__/scene-handle-fields.unit.test.d.ts +2 -0
  100. package/dist/__tests__/scene-handle-fields.unit.test.d.ts.map +1 -0
  101. package/dist/__tests__/scene-payload.unit.test.d.ts +2 -0
  102. package/dist/__tests__/scene-payload.unit.test.d.ts.map +1 -0
  103. package/dist/__tests__/scene-publication-fence.test.d.ts +2 -0
  104. package/dist/__tests__/scene-publication-fence.test.d.ts.map +1 -0
  105. package/dist/__tests__/scriptable-pack-public-surface.test-d.d.ts +2 -0
  106. package/dist/__tests__/scriptable-pack-public-surface.test-d.d.ts.map +1 -0
  107. package/dist/__tests__/static-rig.integration.test.d.ts +2 -0
  108. package/dist/__tests__/static-rig.integration.test.d.ts.map +1 -0
  109. package/dist/__tests__/validate-material.unit.test.d.ts +2 -0
  110. package/dist/__tests__/validate-material.unit.test.d.ts.map +1 -0
  111. package/dist/__tests__/video-pack-loader.integration.test.d.ts +2 -0
  112. package/dist/__tests__/video-pack-loader.integration.test.d.ts.map +1 -0
  113. package/dist/aabb.d.ts +3 -0
  114. package/dist/aabb.d.ts.map +1 -0
  115. package/dist/asset-registry.d.ts +554 -0
  116. package/dist/asset-registry.d.ts.map +1 -0
  117. package/dist/builtin-asset-registry.d.ts +22 -0
  118. package/dist/builtin-asset-registry.d.ts.map +1 -0
  119. package/dist/catalog-source.d.ts +2 -1
  120. package/dist/catalog-source.d.ts.map +1 -1
  121. package/dist/decode-image-bytes.d.ts +57 -0
  122. package/dist/decode-image-bytes.d.ts.map +1 -0
  123. package/dist/dynamic-texture-store.d.ts +89 -0
  124. package/dist/dynamic-texture-store.d.ts.map +1 -0
  125. package/dist/errors/asset.d.ts +201 -0
  126. package/dist/errors/asset.d.ts.map +1 -0
  127. package/dist/handles.d.ts +143 -0
  128. package/dist/handles.d.ts.map +1 -0
  129. package/dist/image-error.d.ts +6 -0
  130. package/dist/image-error.d.ts.map +1 -0
  131. package/dist/index.d.ts +32 -3
  132. package/dist/index.d.ts.map +1 -1
  133. package/dist/index.mjs +7233 -114
  134. package/dist/index.mjs.map +1 -1
  135. package/dist/internal.d.ts +1 -1
  136. package/dist/internal.d.ts.map +1 -1
  137. package/dist/loader-registry.d.ts +63 -0
  138. package/dist/loader-registry.d.ts.map +1 -0
  139. package/dist/loaders/inline-pack.d.ts +72 -0
  140. package/dist/loaders/inline-pack.d.ts.map +1 -0
  141. package/dist/loaders/mesh-bin.d.ts +21 -0
  142. package/dist/loaders/mesh-bin.d.ts.map +1 -0
  143. package/dist/loaders/pack-artifact.d.ts +20 -0
  144. package/dist/loaders/pack-artifact.d.ts.map +1 -0
  145. package/dist/material/dependency-graph.d.ts +9 -0
  146. package/dist/material/dependency-graph.d.ts.map +1 -0
  147. package/dist/material/generation-cache.d.ts +24 -0
  148. package/dist/material/generation-cache.d.ts.map +1 -0
  149. package/dist/material/inspection.d.ts +24 -0
  150. package/dist/material/inspection.d.ts.map +1 -0
  151. package/dist/material/loader.d.ts +59 -0
  152. package/dist/material/loader.d.ts.map +1 -0
  153. package/dist/material/runtime-shader.d.ts +14 -0
  154. package/dist/material/runtime-shader.d.ts.map +1 -0
  155. package/dist/mesh-bin.d.ts +3 -0
  156. package/dist/mesh-bin.d.ts.map +1 -0
  157. package/dist/mipmap-generator.d.ts +132 -0
  158. package/dist/mipmap-generator.d.ts.map +1 -0
  159. package/dist/payload-validate.d.ts +60 -0
  160. package/dist/payload-validate.d.ts.map +1 -0
  161. package/dist/plugin.d.ts +16 -0
  162. package/dist/plugin.d.ts.map +1 -0
  163. package/dist/registry/artifact-io.d.ts +16 -0
  164. package/dist/registry/artifact-io.d.ts.map +1 -0
  165. package/dist/registry/asset-evidence.d.ts +13 -0
  166. package/dist/registry/asset-evidence.d.ts.map +1 -0
  167. package/dist/registry/catalog-state.d.ts +47 -0
  168. package/dist/registry/catalog-state.d.ts.map +1 -0
  169. package/dist/registry/catalog.d.ts +48 -0
  170. package/dist/registry/catalog.d.ts.map +1 -0
  171. package/dist/registry/instantiate.d.ts +153 -0
  172. package/dist/registry/instantiate.d.ts.map +1 -0
  173. package/dist/registry/load-by-guid.d.ts +247 -0
  174. package/dist/registry/load-by-guid.d.ts.map +1 -0
  175. package/dist/registry/load-state.d.ts +26 -0
  176. package/dist/registry/load-state.d.ts.map +1 -0
  177. package/dist/registry/runtime-projection.d.ts +16 -0
  178. package/dist/registry/runtime-projection.d.ts.map +1 -0
  179. package/dist/registry/scene-publication-fence.d.ts +38 -0
  180. package/dist/registry/scene-publication-fence.d.ts.map +1 -0
  181. package/dist/registry/validate-material.d.ts +77 -0
  182. package/dist/registry/validate-material.d.ts.map +1 -0
  183. package/dist/resolve-asset-handle.d.ts +43 -0
  184. package/dist/resolve-asset-handle.d.ts.map +1 -0
  185. package/dist/resolve-tileset-runtime.d.ts +23 -0
  186. package/dist/resolve-tileset-runtime.d.ts.map +1 -0
  187. package/dist/scene-handle-fields.d.ts +88 -0
  188. package/dist/scene-handle-fields.d.ts.map +1 -0
  189. package/dist/scene-payload.d.ts +15 -0
  190. package/dist/scene-payload.d.ts.map +1 -0
  191. package/dist/wire-default-loaders.d.ts +29 -0
  192. package/dist/wire-default-loaders.d.ts.map +1 -0
  193. package/package.json +22 -9
  194. package/src/__tests__/aabb.unit.test.ts +97 -0
  195. package/src/__tests__/artifact-integrity.unit.test.ts +99 -0
  196. package/src/__tests__/artifact-io.unit.test.ts +127 -0
  197. package/src/__tests__/asset-evidence-sdk.integration.test.ts +32 -0
  198. package/src/__tests__/asset-evidence.unit.test.ts +32 -0
  199. package/src/__tests__/{asset-kind.test.ts → asset-kind.unit.test.ts} +1 -1
  200. package/src/__tests__/asset-registry-catalog-reconcile.integration.test.ts +74 -0
  201. package/src/__tests__/asset-registry-catalog-reconcile.unit.test.ts +89 -0
  202. package/src/__tests__/asset-registry-catalog.unit.test.ts +170 -0
  203. package/src/__tests__/asset-registry.pack-cache.test.ts +96 -0
  204. package/src/__tests__/asset-registry.unit.test.ts +511 -0
  205. package/src/__tests__/asset-runtime-error-code-owner.test-d.ts +63 -0
  206. package/src/__tests__/catalog-replica.unit.test.ts +196 -0
  207. package/src/__tests__/catalog-source-v2.unit.test.ts +158 -0
  208. package/src/__tests__/catalog-source.unit.test.ts +126 -0
  209. package/src/__tests__/catalog-subscription-order.unit.test.ts +65 -0
  210. package/src/__tests__/catalog-subscription.unit.test.ts +28 -0
  211. package/src/__tests__/decode-image-bytes-pass-through.test.ts +34 -0
  212. package/src/__tests__/decode-image-bytes.browser.test.ts +102 -0
  213. package/src/__tests__/decode-image-bytes.test-d.ts +108 -0
  214. package/src/__tests__/decode-image-bytes.test.ts +90 -0
  215. package/src/__tests__/decode-image-mime-owner.test.ts +2 -5
  216. package/src/__tests__/dynamic-texture-store.unit.test.ts +115 -0
  217. package/src/__tests__/errors-asset.unit.test.ts +92 -0
  218. package/src/__tests__/inline-pack-loaders.unit.test.ts +548 -0
  219. package/src/__tests__/instantiate-context.unit.test.ts +65 -0
  220. package/src/__tests__/instantiate-publication-fence.unit.test.ts +158 -0
  221. package/src/__tests__/instantiate.unit.test.ts +684 -0
  222. package/src/__tests__/loader-input-contract.unit.test.ts +89 -0
  223. package/src/__tests__/loader-registry.unit.test.ts +101 -0
  224. package/src/__tests__/material-dependency-invalidation.unit.test.ts +14 -0
  225. package/src/__tests__/material-generation-cache.unit.test.ts +46 -0
  226. package/src/__tests__/material-load-consumer.test-d.ts +42 -0
  227. package/src/__tests__/material-ready.integration.test.ts +508 -0
  228. package/src/__tests__/material-stale-generation.unit.test.ts +147 -0
  229. package/src/__tests__/mesh-bin-morph-v3.test.ts +22 -0
  230. package/src/__tests__/mesh-bin.unit.test.ts +152 -0
  231. package/src/__tests__/mipmap-generator.unit.test.ts +202 -0
  232. package/src/__tests__/pack-artifact-transcode-model-owner.test.ts +32 -0
  233. package/src/__tests__/pack-basis-load.integration.test.ts +168 -0
  234. package/src/__tests__/payload-validate.unit.test.ts +285 -0
  235. package/src/__tests__/plugin.test.ts +55 -0
  236. package/src/__tests__/registry-concurrency.bench.test.ts +40 -0
  237. package/src/__tests__/registry-failure-retry.integration.test.ts +69 -0
  238. package/src/__tests__/registry-load-graph.integration.test.ts +246 -0
  239. package/src/__tests__/registry-load-state.unit.test.ts +35 -0
  240. package/src/__tests__/registry-package-cache.unit.test.ts +53 -0
  241. package/src/__tests__/registry-public-ready.unit.test.ts +38 -0
  242. package/src/__tests__/resolve-asset-handle.unit.test.ts +238 -0
  243. package/src/__tests__/scene-handle-fields.unit.test.ts +129 -0
  244. package/src/__tests__/scene-payload.unit.test.ts +151 -0
  245. package/src/__tests__/scene-publication-fence.test.ts +129 -0
  246. package/src/__tests__/scriptable-pack-public-surface.test-d.ts +27 -0
  247. package/src/__tests__/static-rig.integration.test.ts +8 -0
  248. package/src/__tests__/validate-material.unit.test.ts +359 -0
  249. package/src/__tests__/video-pack-loader.integration.test.ts +83 -0
  250. package/src/aabb.ts +107 -0
  251. package/src/asset-registry.ts +2045 -0
  252. package/src/builtin-asset-registry.ts +164 -0
  253. package/src/catalog-source.ts +14 -70
  254. package/src/decode-image-bytes.ts +126 -0
  255. package/src/dynamic-texture-store.ts +234 -0
  256. package/src/errors/asset.ts +347 -0
  257. package/src/handles.ts +186 -0
  258. package/src/image-error.ts +45 -0
  259. package/src/index.ts +183 -16
  260. package/src/internal.ts +1 -0
  261. package/src/loader-registry.ts +157 -0
  262. package/src/loaders/inline-pack.ts +837 -0
  263. package/src/loaders/mesh-bin.ts +304 -0
  264. package/src/loaders/pack-artifact.ts +665 -0
  265. package/src/material/dependency-graph.ts +46 -0
  266. package/src/material/generation-cache.ts +177 -0
  267. package/src/material/inspection.ts +46 -0
  268. package/src/material/loader.ts +326 -0
  269. package/src/material/runtime-shader.ts +133 -0
  270. package/src/mesh-bin.ts +5 -0
  271. package/src/mipmap-generator.ts +450 -0
  272. package/src/payload-validate.ts +573 -0
  273. package/src/plugin.ts +48 -0
  274. package/src/registry/artifact-io.ts +259 -0
  275. package/src/registry/asset-evidence.ts +51 -0
  276. package/src/registry/catalog-state.ts +409 -0
  277. package/src/registry/catalog.ts +369 -0
  278. package/src/registry/instantiate.ts +1056 -0
  279. package/src/registry/load-by-guid.ts +1657 -0
  280. package/src/registry/load-state.ts +115 -0
  281. package/src/registry/runtime-projection.ts +63 -0
  282. package/src/registry/scene-publication-fence.ts +256 -0
  283. package/src/registry/validate-material.ts +385 -0
  284. package/src/resolve-asset-handle.ts +147 -0
  285. package/src/resolve-tileset-runtime.ts +72 -0
  286. package/src/scene-handle-fields.ts +221 -0
  287. package/src/scene-payload.ts +207 -0
  288. package/src/wire-default-loaders.ts +79 -0
  289. package/dist/__tests__/asset-graph-red.integration.test.d.ts +0 -2
  290. package/dist/__tests__/asset-graph-red.integration.test.d.ts.map +0 -1
  291. package/dist/__tests__/asset-kind.test.d.ts +0 -2
  292. package/dist/__tests__/asset-kind.test.d.ts.map +0 -1
  293. package/dist/__tests__/asset-registry-core.integration.test.d.ts +0 -2
  294. package/dist/__tests__/asset-registry-core.integration.test.d.ts.map +0 -1
  295. package/dist/__tests__/asset-registry-public-api.test-d.d.ts +0 -2
  296. package/dist/__tests__/asset-registry-public-api.test-d.d.ts.map +0 -1
  297. package/dist/__tests__/asset-runtime-core-lifecycle.integration.test.d.ts +0 -2
  298. package/dist/__tests__/asset-runtime-core-lifecycle.integration.test.d.ts.map +0 -1
  299. package/dist/__tests__/asset-runtime-snapshot.unit.test.d.ts +0 -2
  300. package/dist/__tests__/asset-runtime-snapshot.unit.test.d.ts.map +0 -1
  301. package/dist/__tests__/catalog-session-red.unit.test.d.ts +0 -2
  302. package/dist/__tests__/catalog-session-red.unit.test.d.ts.map +0 -1
  303. package/dist/__tests__/registry-lifecycle-red.integration.test.d.ts +0 -2
  304. package/dist/__tests__/registry-lifecycle-red.integration.test.d.ts.map +0 -1
  305. package/src/__tests__/asset-graph-red.integration.test.ts +0 -113
  306. package/src/__tests__/asset-registry-core.integration.test.ts +0 -161
  307. package/src/__tests__/asset-registry-public-api.test-d.ts +0 -31
  308. package/src/__tests__/asset-runtime-core-lifecycle.integration.test.ts +0 -79
  309. package/src/__tests__/asset-runtime-snapshot.unit.test.ts +0 -23
  310. package/src/__tests__/catalog-session-red.unit.test.ts +0 -276
  311. package/src/__tests__/registry-lifecycle-red.integration.test.ts +0 -80
@@ -0,0 +1,102 @@
1
+ // @forgeax/engine-assets-runtime -- decodeImageBytes browser e2e test
2
+ // (tweak-20260714-runtime-image-bytes-decoder-add-decodeimagebytes M3 / m3-1).
3
+ //
4
+ // Runs under the vitest browser project (chromium + real createImageBitmap +
5
+ // OffscreenCanvas 2D). Anchors the end-to-end "bytes -> POD -> world column
6
+ // handle" happy path that the Node-side unit test cannot exercise (Node has
7
+ // no createImageBitmap; unit test only covers the structured-failure arms).
8
+ //
9
+ // Coverage:
10
+ // - AC-01: real PNG bytes -> ok(TextureAsset) with all POD fields populated
11
+ // (kind / width / height / format / data / colorSpace / mipmap /
12
+ // mipLevelCount); default (opts omitted) drives srgb + mipmap=true, and a
13
+ // second run with opts.colorSpace='linear' + opts.mipmap=false confirms
14
+ // the two POD fields the opts derive (format + mipLevelCount).
15
+ // - AC-02: the returned POD is accepted by `world.allocSharedRef(
16
+ // 'TextureAsset', pod)` -- the SSOT runtime column-handle path (charter
17
+ // P4 consistent abstraction: the same POD any static loader would
18
+ // produce). The handle is a live u32 whose resolve returns payload
19
+ // identity (structural equivalence, not deep-copy).
20
+ // - AC-03 (regression): running this file alongside the existing browser
21
+ // project confirms the pre-existing static loader path is unaffected
22
+ // (checked by the M3 boundary sweep `pnpm test:browser`, no new arms
23
+ // added here).
24
+ //
25
+ // Fixture: 1x1 red PNG inlined as base64 (67 bytes) -- keeps the whole
26
+ // contract self-contained in this one file so an AI user grep-discovering
27
+ // the test does not need to chase a binary fixture (charter F1 single-page).
28
+
29
+ import { World } from '@forgeax/engine-ecs';
30
+ import type { TextureAsset } from '@forgeax/engine-types';
31
+ import { describe, expect, it } from 'vitest';
32
+ import { decodeImageBytes } from '../decode-image-bytes';
33
+ import { numMipLevels } from '../mipmap-generator';
34
+
35
+ // 1x1 red PNG (RGBA 8-bit). Universally decodable by browser createImageBitmap.
36
+ const PNG_1X1_RED_BASE64 =
37
+ 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==';
38
+
39
+ function pngBytes(): Uint8Array {
40
+ const bin = atob(PNG_1X1_RED_BASE64);
41
+ const bytes = new Uint8Array(bin.length);
42
+ for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
43
+ return bytes;
44
+ }
45
+
46
+ describe('decodeImageBytes browser real-PNG happy path (AC-01)', () => {
47
+ it('AC-01: real PNG bytes -> ok(TextureAsset) with all POD fields populated (default srgb + mipmap)', async () => {
48
+ const result = await decodeImageBytes(pngBytes(), 'image/png');
49
+ expect(result.ok).toBe(true);
50
+ if (!result.ok) return;
51
+ const pod = result.value;
52
+ expect(pod.kind).toBe('texture');
53
+ expect(pod.width).toBeGreaterThan(0);
54
+ expect(pod.height).toBeGreaterThan(0);
55
+ // RGBA8 tight-packed: width * height * 4 bytes.
56
+ expect(pod.data.length).toBe(pod.width * pod.height * 4);
57
+ expect(pod.format).toBe('rgba8unorm-srgb');
58
+ expect(pod.colorSpace).toBe('srgb');
59
+ expect(pod.mipmap).toBe(true);
60
+ expect(pod.mipLevelCount).toBe(numMipLevels({ width: pod.width, height: pod.height }));
61
+ });
62
+
63
+ it('AC-01: opts.colorSpace="linear" + opts.mipmap=false narrows format + mipLevelCount', async () => {
64
+ const result = await decodeImageBytes(pngBytes(), 'image/png', {
65
+ colorSpace: 'linear',
66
+ mipmap: false,
67
+ });
68
+ expect(result.ok).toBe(true);
69
+ if (!result.ok) return;
70
+ const pod = result.value;
71
+ expect(pod.format).toBe('rgba8unorm');
72
+ expect(pod.colorSpace).toBe('linear');
73
+ expect(pod.mipmap).toBe(false);
74
+ expect(pod.mipLevelCount).toBe(1);
75
+ });
76
+ });
77
+
78
+ describe('decodeImageBytes browser structural equivalence with static loader path (AC-02)', () => {
79
+ it('AC-02: POD is accepted by world.allocSharedRef and resolves back to the same identity', async () => {
80
+ const result = await decodeImageBytes(pngBytes(), 'image/png');
81
+ expect(result.ok).toBe(true);
82
+ if (!result.ok) return;
83
+ const pod = result.value;
84
+
85
+ const world = new World();
86
+ const handle = world.allocSharedRef<'TextureAsset', TextureAsset>('TextureAsset', pod);
87
+ // Handle is a branded u32 -- must be a finite non-negative integer.
88
+ expect(typeof handle).toBe('number');
89
+ expect(Number.isFinite(handle as unknown as number)).toBe(true);
90
+ expect(handle as unknown as number).toBeGreaterThan(0);
91
+
92
+ // Structural equivalence: the shared-ref store round-trips the SAME POD
93
+ // reference (identity preserved, no deep-copy) -- what any static
94
+ // texture loader path produces after allocSharedRef.
95
+ const resolved = world.sharedRefs.resolve<'TextureAsset', TextureAsset>(handle);
96
+ expect(resolved.ok).toBe(true);
97
+ if (!resolved.ok) return;
98
+ expect(resolved.value).toBe(pod);
99
+ expect(resolved.value.kind).toBe('texture');
100
+ expect(resolved.value.format).toBe('rgba8unorm-srgb');
101
+ });
102
+ });
@@ -0,0 +1,108 @@
1
+ // @forgeax/engine-assets-runtime -- decodeImageBytes type-only contract test
2
+ // (tweak-20260714-runtime-image-bytes-decoder-add-decodeimagebytes M1 / m1-1).
3
+ //
4
+ // AC-05 + AC-06 compile-time narrowing anchor. The vitest typecheck project
5
+ // runs `.test-d.ts` files so any drift in the exported signature / Result
6
+ // narrowing / ImageErrorDetail per-code shape trips CI (charter P4 explicit
7
+ // failure -- typechecker acts as the reviewer).
8
+ //
9
+ // The SSOT `ImageError` type correlates the envelope `.code` with its
10
+ // per-code `.detail` shape, so consumers switch on one discriminant.
11
+
12
+ import type { ImageError, Result, TextureAsset } from '@forgeax/engine-types';
13
+ import { describe, expectTypeOf, it } from 'vitest';
14
+ import { decodeImageBytes } from '../index';
15
+
16
+ describe('decodeImageBytes signature (AC-05 / AC-06 / opts compat)', () => {
17
+ it('returns Promise<Result<TextureAsset, ImageError>>', () => {
18
+ expectTypeOf(decodeImageBytes).returns.toEqualTypeOf<
19
+ Promise<Result<TextureAsset, ImageError>>
20
+ >();
21
+ });
22
+
23
+ it('AC-05: `if (r.ok)` narrows r.value to TextureAsset, else r.error to ImageError', async () => {
24
+ const r = await decodeImageBytes(new Uint8Array(), 'image/png');
25
+ if (r.ok) {
26
+ expectTypeOf(r.value).toEqualTypeOf<TextureAsset>();
27
+ } else {
28
+ expectTypeOf(r.error).toEqualTypeOf<ImageError>();
29
+ }
30
+ });
31
+
32
+ it('AC-06: `switch (err.code)` narrows err.detail per-code (exhaustive)', async () => {
33
+ const r = await decodeImageBytes(new Uint8Array(), 'image/png');
34
+ if (r.ok) return;
35
+ // Exhaustive over the correlated ImageError union so a future minor-add
36
+ // to ImageErrorCode fails compilation here until this file catches up.
37
+ switch (r.error.code) {
38
+ case 'image-decode-failed': {
39
+ expectTypeOf(r.error.detail.reason).toBeString();
40
+ expectTypeOf(r.error.detail.path).toEqualTypeOf<string | undefined>();
41
+ break;
42
+ }
43
+ case 'image-format-unsupported': {
44
+ expectTypeOf(r.error.detail.actualMime).toBeString();
45
+ break;
46
+ }
47
+ case 'image-dimension-out-of-bounds': {
48
+ expectTypeOf(r.error.detail.requested.width).toBeNumber();
49
+ expectTypeOf(r.error.detail.limit).toBeNumber();
50
+ break;
51
+ }
52
+ case 'image-meta-missing': {
53
+ expectTypeOf(r.error.detail.sourcePath).toBeString();
54
+ expectTypeOf(r.error.detail.expectedSidecarPath).toBeString();
55
+ break;
56
+ }
57
+ case 'image-hdr-decode-failed': {
58
+ expectTypeOf(r.error.detail.reason).toBeString();
59
+ break;
60
+ }
61
+ case 'atlas-empty-input': {
62
+ expectTypeOf(r.error.detail.receivedCount).toBeNumber();
63
+ break;
64
+ }
65
+ case 'atlas-size-exceeded': {
66
+ expectTypeOf(r.error.detail.maxAtlasSize).toBeNumber();
67
+ break;
68
+ }
69
+ case 'atlas-region-mismatch': {
70
+ expectTypeOf(r.error.detail.atlasPixels).toBeNumber();
71
+ break;
72
+ }
73
+ }
74
+ });
75
+
76
+ it('opts compat: all optional, partial, and full forms compile', async () => {
77
+ await decodeImageBytes(new Uint8Array(), 'image/png');
78
+ await decodeImageBytes(new Uint8Array(), 'image/png', { colorSpace: 'linear' });
79
+ await decodeImageBytes(new Uint8Array(), 'image/png', { mipmap: false });
80
+ await decodeImageBytes(new Uint8Array(), 'image/png', {
81
+ colorSpace: 'srgb',
82
+ mipmap: true,
83
+ });
84
+ await decodeImageBytes(new ArrayBuffer(0), 'image/png');
85
+ });
86
+
87
+ it('AC-05 negative: `if (!r.ok)` MUST NOT let r.value type-check (@ts-expect-error)', async () => {
88
+ const r = await decodeImageBytes(new Uint8Array(), 'image/png');
89
+ if (!r.ok) {
90
+ // @ts-expect-error -- on the err branch `r` is `ResultErr<ImageError>`
91
+ // and has no `.value` property; if this line ever compiles, the
92
+ // Result narrowing broke.
93
+ const _v: TextureAsset = r.value;
94
+ void _v;
95
+ }
96
+ });
97
+
98
+ it('AC-06 negative: `reason` is not on the image-format-unsupported detail arm (@ts-expect-error)', async () => {
99
+ const r = await decodeImageBytes(new Uint8Array(), 'image/png');
100
+ if (r.ok) return;
101
+ if (r.error.detail.code === 'image-format-unsupported') {
102
+ // @ts-expect-error -- `reason` belongs to the image-decode-failed arm;
103
+ // it must NOT be reachable on the image-format-unsupported arm.
104
+ const _r: string = r.error.detail.reason;
105
+ void _r;
106
+ }
107
+ });
108
+ });
@@ -0,0 +1,90 @@
1
+ // @forgeax/engine-assets-runtime -- decodeImageBytes unit test skeleton
2
+ // (tweak-20260714-runtime-image-bytes-decoder-add-decodeimagebytes M1 / m1-1).
3
+ //
4
+ // Red skeleton at M1: the M1 stub throws 'not-implemented', so any behavioural
5
+ // assertion here is red until M2 fills the function body. Two forever-tests
6
+ // still hold at M1:
7
+ // - AC-07 (a): the symbol is importable from the @forgeax/engine-assets-
8
+ // runtime main barrel (grep-discoverable single-hop entry point).
9
+ // - AC-07 (b): the source file leads with a `/**` TSDoc block on the
10
+ // exported function (charter F1: single-page contract).
11
+ //
12
+ // Behavioural AC-04 / AC-08 / AC-09 arms are declared here in the exhaustive
13
+ // switch skeleton so the M1 red matches the M2 green surface without churn
14
+ // (charter P3 explicit failure -- red arms declare the target shape).
15
+
16
+ /// <reference types="node" />
17
+
18
+ import { readFileSync } from 'node:fs';
19
+ import { dirname, join } from 'node:path';
20
+ import { fileURLToPath } from 'node:url';
21
+ import { describe, expect, it } from 'vitest';
22
+ import { decodeImageBytes } from '../index';
23
+
24
+ const here = dirname(fileURLToPath(import.meta.url));
25
+
26
+ describe('decodeImageBytes barrel + TSDoc contract (AC-07)', () => {
27
+ it('is exported from the @forgeax/engine-assets-runtime main barrel', () => {
28
+ expect(typeof decodeImageBytes).toBe('function');
29
+ });
30
+
31
+ it('carries a leading `/**` TSDoc block on the exported function', () => {
32
+ const src = readFileSync(join(here, '..', 'decode-image-bytes.ts'), 'utf8');
33
+ // The exported function must be immediately preceded by a `*/` closing
34
+ // its TSDoc block; the `/**` opener must appear before that. This is a
35
+ // structural (not textual) check on the AI user's IDE-hover surface.
36
+ const exportIdx = src.indexOf('export async function decodeImageBytes(');
37
+ expect(exportIdx).toBeGreaterThan(0);
38
+ const preface = src.slice(0, exportIdx);
39
+ const jsdocClose = preface.lastIndexOf('*/');
40
+ const jsdocOpen = preface.lastIndexOf('/**');
41
+ expect(jsdocOpen).toBeGreaterThan(0);
42
+ expect(jsdocClose).toBeGreaterThan(jsdocOpen);
43
+ });
44
+ });
45
+
46
+ describe('decodeImageBytes structured failure surface (AC-04 / AC-08 / AC-09)', () => {
47
+ it('AC-08: unsupported mime -> err with image-format-unsupported', async () => {
48
+ const result = await decodeImageBytes(new Uint8Array([1]), 'image/gif');
49
+ expect(result.ok).toBe(false);
50
+ if (result.ok) return;
51
+ const expected =
52
+ "mime is one of ['image/png', 'image/jpeg', 'image/x-tga']; texture format <-> colorSpace family agrees";
53
+ const hint =
54
+ 'supports PNG / JPG / TGA true-color sources; convert unsupported formats with: magick convert <input> <output>.png; check importSettings.colorSpace consistency with format family if formatColorSpaceConflict present';
55
+ expect(result.error.code).toBe('image-format-unsupported');
56
+ expect(result.error.detail).toEqual({
57
+ code: 'image-format-unsupported',
58
+ actualMime: 'image/gif',
59
+ });
60
+ expect(result.error.expected).toBe(expected);
61
+ expect(result.error.hint).toBe(hint);
62
+ expect(result.error.name).toBe('ImageError');
63
+ expect(result.error.message).toBe(
64
+ `[ImageError image-format-unsupported] expected: ${expected}; hint: ${hint}`,
65
+ );
66
+ });
67
+
68
+ it('AC-09: corrupt bytes with declared mime -> err with image-decode-failed', async () => {
69
+ // Node env has no createImageBitmap; the decoder returns the structured
70
+ // env-missing failure through the same `image-decode-failed` arm as a
71
+ // real decoder rejection in the browser (charter P3 explicit failure --
72
+ // AI users switch on one arm regardless of environment).
73
+ const result = await decodeImageBytes(new Uint8Array([0, 0, 0]), 'image/png');
74
+ expect(result.ok).toBe(false);
75
+ if (result.ok) return;
76
+ expect(result.error.code).toBe('image-decode-failed');
77
+ if (result.error.detail.code === 'image-decode-failed') {
78
+ expect(typeof result.error.detail.reason).toBe('string');
79
+ expect(result.error.detail.reason.length).toBeGreaterThan(0);
80
+ }
81
+ });
82
+ });
83
+
84
+ describe('runtime-only bytes remain orthogonal to static pack loading', () => {
85
+ it('keeps the bytes decoder available without accepting static catalog rows', async () => {
86
+ const result = await decodeImageBytes(new Uint8Array([0]), 'image/gif');
87
+ expect(result.ok).toBe(false);
88
+ if (!result.ok) expect(result.error.code).toBe('image-format-unsupported');
89
+ });
90
+ });
@@ -1,16 +1,13 @@
1
1
  /// <reference types="node" />
2
2
 
3
3
  import { readFileSync } from 'node:fs';
4
- import type { decodeImageBytes } from '@forgeax/engine-image';
5
4
  import { describe, expect, expectTypeOf, it } from 'vitest';
5
+ import type { decodeImageBytes } from '../index';
6
6
 
7
7
  type ExpectedSupportedMime = 'image/png' | 'image/jpeg';
8
8
  type PublicMime = Parameters<typeof decodeImageBytes>[1];
9
9
 
10
- const decoderSource = readFileSync(
11
- new URL('../../../image/src/runtime/decode-image-bytes.ts', import.meta.url),
12
- 'utf8',
13
- );
10
+ const decoderSource = readFileSync(new URL('../decode-image-bytes.ts', import.meta.url), 'utf8');
14
11
 
15
12
  describe('decodeImageBytes supported MIME owner', () => {
16
13
  it('keeps the supported MIME vocabulary and public boundary exact', () => {
@@ -0,0 +1,115 @@
1
+ // @forgeax/engine-assets-runtime -- DynamicTextureStore coverage (fix issue #709).
2
+ // Transient per-frame video texture store: configureGpuDevice / uploadFrame
3
+ // (allocate-once + resize-realloc + copy) / getView / destroyAll, driven by a
4
+ // small in-memory device stub (no GPU).
5
+
6
+ import type { Result, RhiError, Texture, TextureView } from '@forgeax/engine-rhi';
7
+ import { ok } from '@forgeax/engine-rhi';
8
+ import type { Handle } from '@forgeax/engine-types';
9
+ import { toShared } from '@forgeax/engine-types';
10
+ import { describe, expect, it } from 'vitest';
11
+ import {
12
+ type CopyExternalImageSource,
13
+ type DynamicTextureDevice,
14
+ DynamicTextureStore,
15
+ } from '../dynamic-texture-store';
16
+
17
+ interface Counters {
18
+ created: number;
19
+ destroyed: number;
20
+ copies: number;
21
+ }
22
+
23
+ function makeDevice(counters: Counters): DynamicTextureDevice {
24
+ let nextTex = 0;
25
+ return {
26
+ createTexture: () => {
27
+ counters.created++;
28
+ return ok({ tag: `tex-${nextTex++}` } as unknown as Texture);
29
+ },
30
+ createTextureView: (tex: Texture) =>
31
+ ok({ tag: `view-of-${(tex as unknown as { tag: string }).tag}` } as unknown as TextureView),
32
+ destroyTexture: () => {
33
+ counters.destroyed++;
34
+ return ok(undefined) as Result<void, RhiError>;
35
+ },
36
+ queue: {
37
+ copyExternalImageToTexture: () => {
38
+ counters.copies++;
39
+ return ok(undefined) as Result<void, RhiError>;
40
+ },
41
+ },
42
+ };
43
+ }
44
+
45
+ const CLIP = toShared<'VideoAsset'>(2000) as Handle<'VideoAsset', 'shared'>;
46
+ const SOURCE = {} as CopyExternalImageSource;
47
+
48
+ describe('DynamicTextureStore', () => {
49
+ it('returns undefined before a device is wired', () => {
50
+ const store = new DynamicTextureStore();
51
+ expect(store.uploadFrame(CLIP, SOURCE, 16, 16)).toBeUndefined();
52
+ });
53
+
54
+ it('returns undefined for a non-positive source size', () => {
55
+ const store = new DynamicTextureStore();
56
+ store.configureGpuDevice(makeDevice({ created: 0, destroyed: 0, copies: 0 }));
57
+ expect(store.uploadFrame(CLIP, SOURCE, 0, 16)).toBeUndefined();
58
+ expect(store.uploadFrame(CLIP, SOURCE, 16, -1)).toBeUndefined();
59
+ });
60
+
61
+ it('allocates once, then re-uploads in place for a steady-size clip', () => {
62
+ const counters = { created: 0, destroyed: 0, copies: 0 };
63
+ const store = new DynamicTextureStore();
64
+ store.configureGpuDevice(makeDevice(counters));
65
+
66
+ const a = store.uploadFrame(CLIP, SOURCE, 32, 32);
67
+ const b = store.uploadFrame(CLIP, SOURCE, 32, 32);
68
+ expect(a?.ok).toBe(true);
69
+ expect(b?.ok).toBe(true);
70
+ expect(counters.created).toBe(1); // allocate-once
71
+ expect(counters.copies).toBe(2); // re-uploaded each frame
72
+ if (a?.ok && b?.ok) expect(a.value).toBe(b.value); // same view reused
73
+ });
74
+
75
+ it('reallocates (destroys old, creates new) when the source size changes', () => {
76
+ const counters = { created: 0, destroyed: 0, copies: 0 };
77
+ const store = new DynamicTextureStore();
78
+ store.configureGpuDevice(makeDevice(counters));
79
+ store.uploadFrame(CLIP, SOURCE, 32, 32);
80
+ store.uploadFrame(CLIP, SOURCE, 64, 64);
81
+ expect(counters.created).toBe(2);
82
+ expect(counters.destroyed).toBe(1);
83
+ });
84
+
85
+ it('getView returns the current view after an upload, undefined before', () => {
86
+ const store = new DynamicTextureStore();
87
+ expect(store.getView(CLIP)).toBeUndefined();
88
+ store.configureGpuDevice(makeDevice({ created: 0, destroyed: 0, copies: 0 }));
89
+ store.uploadFrame(CLIP, SOURCE, 8, 8);
90
+ expect(store.getView(CLIP)).toBeDefined();
91
+ });
92
+
93
+ it('destroyAll destroys every transient texture and clears the map', () => {
94
+ const counters = { created: 0, destroyed: 0, copies: 0 };
95
+ const store = new DynamicTextureStore();
96
+ store.configureGpuDevice(makeDevice(counters));
97
+ store.uploadFrame(CLIP, SOURCE, 8, 8);
98
+ store.uploadFrame(toShared<'VideoAsset'>(2001) as Handle<'VideoAsset', 'shared'>, SOURCE, 8, 8);
99
+ store.destroyAll();
100
+ expect(counters.destroyed).toBe(2);
101
+ expect(store.getView(CLIP)).toBeUndefined();
102
+ });
103
+
104
+ it('surfaces a structured error when texture allocation fails', () => {
105
+ const store = new DynamicTextureStore();
106
+ store.configureGpuDevice({
107
+ createTexture: () => ({ ok: false, error: { code: 'rhi-not-available' } }) as never,
108
+ createTextureView: () => ok({} as unknown as TextureView),
109
+ destroyTexture: () => ok(undefined) as Result<void, RhiError>,
110
+ queue: { copyExternalImageToTexture: () => ok(undefined) as Result<void, RhiError> },
111
+ });
112
+ const res = store.uploadFrame(CLIP, SOURCE, 8, 8);
113
+ expect(res?.ok).toBe(false);
114
+ });
115
+ });
@@ -0,0 +1,92 @@
1
+ // @forgeax/engine-assets-runtime -- asset-cluster error class coverage
2
+ // (fix issue #709). Exercises every constructor branch + .code / .expected /
3
+ // .hint / .detail four-field surface of the closed AssetRuntimeError union.
4
+
5
+ import { describe, expect, it } from 'vitest';
6
+ import {
7
+ MaterialResolvedEmptyPassesError,
8
+ MeshSsboCapacityExceededError,
9
+ MeshSsboCeilingReachedError,
10
+ SceneCollectAssetGuidUnresolvedError,
11
+ SceneCollectEntityRefOutOfClosureError,
12
+ } from '../errors/asset';
13
+
14
+ describe('MaterialResolvedEmptyPassesError', () => {
15
+ it('missing-parent reason carries the parent handle in expected/hint/detail', () => {
16
+ const e = new MaterialResolvedEmptyPassesError('mat-guid', 'missing-parent', 42);
17
+ expect(e.code).toBe('material-resolved-empty-passes');
18
+ expect(e.name).toBe('MaterialResolvedEmptyPassesError');
19
+ expect(e.detail).toEqual({
20
+ materialGuid: 'mat-guid',
21
+ reason: 'missing-parent',
22
+ missingParentHandle: 42,
23
+ });
24
+ expect(e.expected).toContain('42');
25
+ expect(e.hint).toContain('mat-guid');
26
+ expect(e.message).toContain('42');
27
+ });
28
+
29
+ it('no-pass-in-chain reason omits missingParentHandle from detail', () => {
30
+ const e = new MaterialResolvedEmptyPassesError('mat-guid', 'no-pass-in-chain');
31
+ expect(e.detail).toEqual({ materialGuid: 'mat-guid', reason: 'no-pass-in-chain' });
32
+ expect('missingParentHandle' in e.detail).toBe(false);
33
+ expect(e.expected).toContain('at least one material');
34
+ expect(e.message).toContain('zero passes');
35
+ });
36
+
37
+ it('missing-parent with undefined handle still omits it from detail', () => {
38
+ const e = new MaterialResolvedEmptyPassesError('g', 'missing-parent', undefined);
39
+ expect('missingParentHandle' in e.detail).toBe(false);
40
+ });
41
+ });
42
+
43
+ describe('MeshSsboCapacityExceededError', () => {
44
+ it('surfaces requested / capacity / ceiling across all four fields', () => {
45
+ const e = new MeshSsboCapacityExceededError(100, 64, 1024);
46
+ expect(e.code).toBe('mesh-ssbo-capacity-exceeded');
47
+ expect(e.name).toBe('MeshSsboCapacityExceededError');
48
+ expect(e.detail).toEqual({ requested: 100, capacity: 64, ceiling: 1024 });
49
+ expect(e.expected).toContain('100');
50
+ expect(e.hint).toContain('100');
51
+ expect(e.message).toContain('100');
52
+ });
53
+ });
54
+
55
+ describe('MeshSsboCeilingReachedError', () => {
56
+ it('surfaces requested / capacity / ceiling across all four fields', () => {
57
+ const e = new MeshSsboCeilingReachedError(200, 128, 2048);
58
+ expect(e.code).toBe('mesh-ssbo-ceiling-reached');
59
+ expect(e.name).toBe('MeshSsboCeilingReachedError');
60
+ expect(e.detail).toEqual({ requested: 200, capacity: 128, ceiling: 2048 });
61
+ expect(e.expected).toContain('2048');
62
+ expect(e.hint).toContain('2048');
63
+ expect(e.message).toContain('200');
64
+ });
65
+ });
66
+
67
+ describe('SceneCollectEntityRefOutOfClosureError', () => {
68
+ it('carries entity / field / target and composes an expected+hint message', () => {
69
+ const e = new SceneCollectEntityRefOutOfClosureError(3, 'target', 99);
70
+ expect(e.code).toBe('scene-collect-entity-ref-out-of-closure');
71
+ expect(e.name).toBe('SceneCollectEntityRefOutOfClosureError');
72
+ expect(e.detail).toEqual({ entity: 3, field: 'target', target: 99 });
73
+ expect(e.message).toBe(`${e.expected} — ${e.hint}`);
74
+ });
75
+ });
76
+
77
+ describe('SceneCollectAssetGuidUnresolvedError', () => {
78
+ it('numeric ref (collect path) populates detail.handle only', () => {
79
+ const e = new SceneCollectAssetGuidUnresolvedError('meshHandle', 7);
80
+ expect(e.code).toBe('scene-collect-asset-guid-unresolved');
81
+ expect(e.detail).toEqual({ field: 'meshHandle', handle: 7 });
82
+ expect('guid' in e.detail).toBe(false);
83
+ expect(e.expected).toContain('handle 7');
84
+ });
85
+
86
+ it('string ref (serialize path) populates detail.guid only', () => {
87
+ const e = new SceneCollectAssetGuidUnresolvedError('meshHandle', 'abc-guid');
88
+ expect(e.detail).toEqual({ field: 'meshHandle', guid: 'abc-guid' });
89
+ expect('handle' in e.detail).toBe(false);
90
+ expect(e.expected).toContain("guid 'abc-guid'");
91
+ });
92
+ });