@forgeax/engine-assets-runtime 0.0.0-dev.8d955ade1c79

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 (320) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +491 -0
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/__tests__/aabb.unit.test.d.ts +2 -0
  5. package/dist/__tests__/aabb.unit.test.d.ts.map +1 -0
  6. package/dist/__tests__/artifact-integrity.unit.test.d.ts +2 -0
  7. package/dist/__tests__/artifact-integrity.unit.test.d.ts.map +1 -0
  8. package/dist/__tests__/artifact-io.unit.test.d.ts +2 -0
  9. package/dist/__tests__/artifact-io.unit.test.d.ts.map +1 -0
  10. package/dist/__tests__/asset-evidence-sdk.integration.test.d.ts +2 -0
  11. package/dist/__tests__/asset-evidence-sdk.integration.test.d.ts.map +1 -0
  12. package/dist/__tests__/asset-evidence.unit.test.d.ts +2 -0
  13. package/dist/__tests__/asset-evidence.unit.test.d.ts.map +1 -0
  14. package/dist/__tests__/asset-kind.unit.test.d.ts +2 -0
  15. package/dist/__tests__/asset-kind.unit.test.d.ts.map +1 -0
  16. package/dist/__tests__/asset-registry-catalog-reconcile.integration.test.d.ts +2 -0
  17. package/dist/__tests__/asset-registry-catalog-reconcile.integration.test.d.ts.map +1 -0
  18. package/dist/__tests__/asset-registry-catalog-reconcile.unit.test.d.ts +2 -0
  19. package/dist/__tests__/asset-registry-catalog-reconcile.unit.test.d.ts.map +1 -0
  20. package/dist/__tests__/asset-registry-catalog.unit.test.d.ts +2 -0
  21. package/dist/__tests__/asset-registry-catalog.unit.test.d.ts.map +1 -0
  22. package/dist/__tests__/asset-registry.pack-cache.test.d.ts +2 -0
  23. package/dist/__tests__/asset-registry.pack-cache.test.d.ts.map +1 -0
  24. package/dist/__tests__/asset-registry.unit.test.d.ts +2 -0
  25. package/dist/__tests__/asset-registry.unit.test.d.ts.map +1 -0
  26. package/dist/__tests__/asset-runtime-error-code-owner.test-d.d.ts +2 -0
  27. package/dist/__tests__/asset-runtime-error-code-owner.test-d.d.ts.map +1 -0
  28. package/dist/__tests__/catalog-replica.unit.test.d.ts +2 -0
  29. package/dist/__tests__/catalog-replica.unit.test.d.ts.map +1 -0
  30. package/dist/__tests__/catalog-source-v2.unit.test.d.ts +2 -0
  31. package/dist/__tests__/catalog-source-v2.unit.test.d.ts.map +1 -0
  32. package/dist/__tests__/catalog-source.unit.test.d.ts +2 -0
  33. package/dist/__tests__/catalog-source.unit.test.d.ts.map +1 -0
  34. package/dist/__tests__/catalog-subscription-order.unit.test.d.ts +2 -0
  35. package/dist/__tests__/catalog-subscription-order.unit.test.d.ts.map +1 -0
  36. package/dist/__tests__/catalog-subscription.unit.test.d.ts +2 -0
  37. package/dist/__tests__/catalog-subscription.unit.test.d.ts.map +1 -0
  38. package/dist/__tests__/decode-image-bytes-pass-through.test.d.ts +2 -0
  39. package/dist/__tests__/decode-image-bytes-pass-through.test.d.ts.map +1 -0
  40. package/dist/__tests__/decode-image-bytes.browser.test.d.ts +2 -0
  41. package/dist/__tests__/decode-image-bytes.browser.test.d.ts.map +1 -0
  42. package/dist/__tests__/decode-image-bytes.test-d.d.ts +2 -0
  43. package/dist/__tests__/decode-image-bytes.test-d.d.ts.map +1 -0
  44. package/dist/__tests__/decode-image-bytes.test.d.ts +2 -0
  45. package/dist/__tests__/decode-image-bytes.test.d.ts.map +1 -0
  46. package/dist/__tests__/decode-image-mime-owner.test.d.ts +2 -0
  47. package/dist/__tests__/decode-image-mime-owner.test.d.ts.map +1 -0
  48. package/dist/__tests__/dynamic-texture-store.unit.test.d.ts +2 -0
  49. package/dist/__tests__/dynamic-texture-store.unit.test.d.ts.map +1 -0
  50. package/dist/__tests__/errors-asset.unit.test.d.ts +2 -0
  51. package/dist/__tests__/errors-asset.unit.test.d.ts.map +1 -0
  52. package/dist/__tests__/inline-pack-loaders.unit.test.d.ts +2 -0
  53. package/dist/__tests__/inline-pack-loaders.unit.test.d.ts.map +1 -0
  54. package/dist/__tests__/instantiate-context.unit.test.d.ts +2 -0
  55. package/dist/__tests__/instantiate-context.unit.test.d.ts.map +1 -0
  56. package/dist/__tests__/instantiate-publication-fence.unit.test.d.ts +2 -0
  57. package/dist/__tests__/instantiate-publication-fence.unit.test.d.ts.map +1 -0
  58. package/dist/__tests__/instantiate.unit.test.d.ts +2 -0
  59. package/dist/__tests__/instantiate.unit.test.d.ts.map +1 -0
  60. package/dist/__tests__/loader-input-contract.unit.test.d.ts +2 -0
  61. package/dist/__tests__/loader-input-contract.unit.test.d.ts.map +1 -0
  62. package/dist/__tests__/loader-registry.unit.test.d.ts +2 -0
  63. package/dist/__tests__/loader-registry.unit.test.d.ts.map +1 -0
  64. package/dist/__tests__/material-dependency-invalidation.unit.test.d.ts +2 -0
  65. package/dist/__tests__/material-dependency-invalidation.unit.test.d.ts.map +1 -0
  66. package/dist/__tests__/material-generation-cache.unit.test.d.ts +2 -0
  67. package/dist/__tests__/material-generation-cache.unit.test.d.ts.map +1 -0
  68. package/dist/__tests__/material-load-consumer.test-d.d.ts +2 -0
  69. package/dist/__tests__/material-load-consumer.test-d.d.ts.map +1 -0
  70. package/dist/__tests__/material-ready.integration.test.d.ts +2 -0
  71. package/dist/__tests__/material-ready.integration.test.d.ts.map +1 -0
  72. package/dist/__tests__/material-stale-generation.unit.test.d.ts +2 -0
  73. package/dist/__tests__/material-stale-generation.unit.test.d.ts.map +1 -0
  74. package/dist/__tests__/mesh-bin-morph-v3.test.d.ts +2 -0
  75. package/dist/__tests__/mesh-bin-morph-v3.test.d.ts.map +1 -0
  76. package/dist/__tests__/mesh-bin.unit.test.d.ts +2 -0
  77. package/dist/__tests__/mesh-bin.unit.test.d.ts.map +1 -0
  78. package/dist/__tests__/mipmap-generator.unit.test.d.ts +2 -0
  79. package/dist/__tests__/mipmap-generator.unit.test.d.ts.map +1 -0
  80. package/dist/__tests__/pack-artifact-transcode-model-owner.test.d.ts +2 -0
  81. package/dist/__tests__/pack-artifact-transcode-model-owner.test.d.ts.map +1 -0
  82. package/dist/__tests__/pack-basis-load.integration.test.d.ts +2 -0
  83. package/dist/__tests__/pack-basis-load.integration.test.d.ts.map +1 -0
  84. package/dist/__tests__/payload-validate.unit.test.d.ts +2 -0
  85. package/dist/__tests__/payload-validate.unit.test.d.ts.map +1 -0
  86. package/dist/__tests__/plugin.test.d.ts +2 -0
  87. package/dist/__tests__/plugin.test.d.ts.map +1 -0
  88. package/dist/__tests__/registry-concurrency.bench.test.d.ts +2 -0
  89. package/dist/__tests__/registry-concurrency.bench.test.d.ts.map +1 -0
  90. package/dist/__tests__/registry-failure-retry.integration.test.d.ts +2 -0
  91. package/dist/__tests__/registry-failure-retry.integration.test.d.ts.map +1 -0
  92. package/dist/__tests__/registry-load-graph.integration.test.d.ts +2 -0
  93. package/dist/__tests__/registry-load-graph.integration.test.d.ts.map +1 -0
  94. package/dist/__tests__/registry-load-state.unit.test.d.ts +2 -0
  95. package/dist/__tests__/registry-load-state.unit.test.d.ts.map +1 -0
  96. package/dist/__tests__/registry-package-cache.unit.test.d.ts +2 -0
  97. package/dist/__tests__/registry-package-cache.unit.test.d.ts.map +1 -0
  98. package/dist/__tests__/registry-public-ready.unit.test.d.ts +2 -0
  99. package/dist/__tests__/registry-public-ready.unit.test.d.ts.map +1 -0
  100. package/dist/__tests__/resolve-asset-handle.unit.test.d.ts +2 -0
  101. package/dist/__tests__/resolve-asset-handle.unit.test.d.ts.map +1 -0
  102. package/dist/__tests__/scene-handle-fields.unit.test.d.ts +2 -0
  103. package/dist/__tests__/scene-handle-fields.unit.test.d.ts.map +1 -0
  104. package/dist/__tests__/scene-payload.unit.test.d.ts +2 -0
  105. package/dist/__tests__/scene-payload.unit.test.d.ts.map +1 -0
  106. package/dist/__tests__/scene-publication-fence.test.d.ts +2 -0
  107. package/dist/__tests__/scene-publication-fence.test.d.ts.map +1 -0
  108. package/dist/__tests__/scriptable-pack-public-surface.test-d.d.ts +2 -0
  109. package/dist/__tests__/scriptable-pack-public-surface.test-d.d.ts.map +1 -0
  110. package/dist/__tests__/static-rig.integration.test.d.ts +2 -0
  111. package/dist/__tests__/static-rig.integration.test.d.ts.map +1 -0
  112. package/dist/__tests__/validate-material.unit.test.d.ts +2 -0
  113. package/dist/__tests__/validate-material.unit.test.d.ts.map +1 -0
  114. package/dist/__tests__/video-pack-loader.integration.test.d.ts +2 -0
  115. package/dist/__tests__/video-pack-loader.integration.test.d.ts.map +1 -0
  116. package/dist/aabb.d.ts +3 -0
  117. package/dist/aabb.d.ts.map +1 -0
  118. package/dist/asset-kind.d.ts +4 -0
  119. package/dist/asset-kind.d.ts.map +1 -0
  120. package/dist/asset-registry.d.ts +554 -0
  121. package/dist/asset-registry.d.ts.map +1 -0
  122. package/dist/builtin-asset-registry.d.ts +22 -0
  123. package/dist/builtin-asset-registry.d.ts.map +1 -0
  124. package/dist/catalog-source.d.ts +24 -0
  125. package/dist/catalog-source.d.ts.map +1 -0
  126. package/dist/decode-image-bytes.d.ts +57 -0
  127. package/dist/decode-image-bytes.d.ts.map +1 -0
  128. package/dist/dynamic-texture-store.d.ts +89 -0
  129. package/dist/dynamic-texture-store.d.ts.map +1 -0
  130. package/dist/errors/asset.d.ts +201 -0
  131. package/dist/errors/asset.d.ts.map +1 -0
  132. package/dist/handles.d.ts +143 -0
  133. package/dist/handles.d.ts.map +1 -0
  134. package/dist/image-error.d.ts +6 -0
  135. package/dist/image-error.d.ts.map +1 -0
  136. package/dist/index.d.ts +34 -0
  137. package/dist/index.d.ts.map +1 -0
  138. package/dist/index.mjs +8359 -0
  139. package/dist/index.mjs.map +1 -0
  140. package/dist/internal/artifact-cache.d.ts +17 -0
  141. package/dist/internal/artifact-cache.d.ts.map +1 -0
  142. package/dist/internal/asset-graph.d.ts +70 -0
  143. package/dist/internal/asset-graph.d.ts.map +1 -0
  144. package/dist/internal/catalog-session.d.ts +60 -0
  145. package/dist/internal/catalog-session.d.ts.map +1 -0
  146. package/dist/internal/decoder-registry.d.ts +19 -0
  147. package/dist/internal/decoder-registry.d.ts.map +1 -0
  148. package/dist/internal/immutable-payload.d.ts +9 -0
  149. package/dist/internal/immutable-payload.d.ts.map +1 -0
  150. package/dist/internal/load-asset.d.ts +31 -0
  151. package/dist/internal/load-asset.d.ts.map +1 -0
  152. package/dist/internal/pack-reader.d.ts +18 -0
  153. package/dist/internal/pack-reader.d.ts.map +1 -0
  154. package/dist/internal/validate-runtime-row.d.ts +7 -0
  155. package/dist/internal/validate-runtime-row.d.ts.map +1 -0
  156. package/dist/internal.d.ts +3 -0
  157. package/dist/internal.d.ts.map +1 -0
  158. package/dist/internal.mjs +329 -0
  159. package/dist/internal.mjs.map +1 -0
  160. package/dist/loader-registry.d.ts +63 -0
  161. package/dist/loader-registry.d.ts.map +1 -0
  162. package/dist/loaders/inline-pack.d.ts +72 -0
  163. package/dist/loaders/inline-pack.d.ts.map +1 -0
  164. package/dist/loaders/mesh-bin.d.ts +21 -0
  165. package/dist/loaders/mesh-bin.d.ts.map +1 -0
  166. package/dist/loaders/pack-artifact.d.ts +20 -0
  167. package/dist/loaders/pack-artifact.d.ts.map +1 -0
  168. package/dist/material/dependency-graph.d.ts +9 -0
  169. package/dist/material/dependency-graph.d.ts.map +1 -0
  170. package/dist/material/generation-cache.d.ts +24 -0
  171. package/dist/material/generation-cache.d.ts.map +1 -0
  172. package/dist/material/inspection.d.ts +24 -0
  173. package/dist/material/inspection.d.ts.map +1 -0
  174. package/dist/material/loader.d.ts +59 -0
  175. package/dist/material/loader.d.ts.map +1 -0
  176. package/dist/material/runtime-shader.d.ts +14 -0
  177. package/dist/material/runtime-shader.d.ts.map +1 -0
  178. package/dist/mesh-bin.d.ts +3 -0
  179. package/dist/mesh-bin.d.ts.map +1 -0
  180. package/dist/mipmap-generator.d.ts +132 -0
  181. package/dist/mipmap-generator.d.ts.map +1 -0
  182. package/dist/payload-validate.d.ts +60 -0
  183. package/dist/payload-validate.d.ts.map +1 -0
  184. package/dist/plugin.d.ts +16 -0
  185. package/dist/plugin.d.ts.map +1 -0
  186. package/dist/registry/artifact-io.d.ts +16 -0
  187. package/dist/registry/artifact-io.d.ts.map +1 -0
  188. package/dist/registry/asset-evidence.d.ts +13 -0
  189. package/dist/registry/asset-evidence.d.ts.map +1 -0
  190. package/dist/registry/catalog-state.d.ts +47 -0
  191. package/dist/registry/catalog-state.d.ts.map +1 -0
  192. package/dist/registry/catalog.d.ts +48 -0
  193. package/dist/registry/catalog.d.ts.map +1 -0
  194. package/dist/registry/instantiate.d.ts +153 -0
  195. package/dist/registry/instantiate.d.ts.map +1 -0
  196. package/dist/registry/load-by-guid.d.ts +247 -0
  197. package/dist/registry/load-by-guid.d.ts.map +1 -0
  198. package/dist/registry/load-state.d.ts +26 -0
  199. package/dist/registry/load-state.d.ts.map +1 -0
  200. package/dist/registry/runtime-projection.d.ts +16 -0
  201. package/dist/registry/runtime-projection.d.ts.map +1 -0
  202. package/dist/registry/scene-publication-fence.d.ts +38 -0
  203. package/dist/registry/scene-publication-fence.d.ts.map +1 -0
  204. package/dist/registry/validate-material.d.ts +77 -0
  205. package/dist/registry/validate-material.d.ts.map +1 -0
  206. package/dist/resolve-asset-handle.d.ts +43 -0
  207. package/dist/resolve-asset-handle.d.ts.map +1 -0
  208. package/dist/resolve-tileset-runtime.d.ts +23 -0
  209. package/dist/resolve-tileset-runtime.d.ts.map +1 -0
  210. package/dist/scene-handle-fields.d.ts +88 -0
  211. package/dist/scene-handle-fields.d.ts.map +1 -0
  212. package/dist/scene-payload.d.ts +15 -0
  213. package/dist/scene-payload.d.ts.map +1 -0
  214. package/dist/wire-default-loaders.d.ts +29 -0
  215. package/dist/wire-default-loaders.d.ts.map +1 -0
  216. package/package.json +76 -0
  217. package/src/__tests__/aabb.unit.test.ts +97 -0
  218. package/src/__tests__/artifact-integrity.unit.test.ts +99 -0
  219. package/src/__tests__/artifact-io.unit.test.ts +127 -0
  220. package/src/__tests__/asset-evidence-sdk.integration.test.ts +32 -0
  221. package/src/__tests__/asset-evidence.unit.test.ts +32 -0
  222. package/src/__tests__/asset-kind.unit.test.ts +14 -0
  223. package/src/__tests__/asset-registry-catalog-reconcile.integration.test.ts +74 -0
  224. package/src/__tests__/asset-registry-catalog-reconcile.unit.test.ts +89 -0
  225. package/src/__tests__/asset-registry-catalog.unit.test.ts +170 -0
  226. package/src/__tests__/asset-registry.pack-cache.test.ts +96 -0
  227. package/src/__tests__/asset-registry.unit.test.ts +511 -0
  228. package/src/__tests__/asset-runtime-error-code-owner.test-d.ts +63 -0
  229. package/src/__tests__/catalog-replica.unit.test.ts +196 -0
  230. package/src/__tests__/catalog-source-v2.unit.test.ts +158 -0
  231. package/src/__tests__/catalog-source.unit.test.ts +126 -0
  232. package/src/__tests__/catalog-subscription-order.unit.test.ts +65 -0
  233. package/src/__tests__/catalog-subscription.unit.test.ts +28 -0
  234. package/src/__tests__/decode-image-bytes-pass-through.test.ts +34 -0
  235. package/src/__tests__/decode-image-bytes.browser.test.ts +102 -0
  236. package/src/__tests__/decode-image-bytes.test-d.ts +108 -0
  237. package/src/__tests__/decode-image-bytes.test.ts +90 -0
  238. package/src/__tests__/decode-image-mime-owner.test.ts +38 -0
  239. package/src/__tests__/dynamic-texture-store.unit.test.ts +115 -0
  240. package/src/__tests__/errors-asset.unit.test.ts +92 -0
  241. package/src/__tests__/inline-pack-loaders.unit.test.ts +548 -0
  242. package/src/__tests__/instantiate-context.unit.test.ts +65 -0
  243. package/src/__tests__/instantiate-publication-fence.unit.test.ts +158 -0
  244. package/src/__tests__/instantiate.unit.test.ts +684 -0
  245. package/src/__tests__/loader-input-contract.unit.test.ts +89 -0
  246. package/src/__tests__/loader-registry.unit.test.ts +101 -0
  247. package/src/__tests__/material-dependency-invalidation.unit.test.ts +14 -0
  248. package/src/__tests__/material-generation-cache.unit.test.ts +46 -0
  249. package/src/__tests__/material-load-consumer.test-d.ts +42 -0
  250. package/src/__tests__/material-ready.integration.test.ts +508 -0
  251. package/src/__tests__/material-stale-generation.unit.test.ts +147 -0
  252. package/src/__tests__/mesh-bin-morph-v3.test.ts +22 -0
  253. package/src/__tests__/mesh-bin.unit.test.ts +152 -0
  254. package/src/__tests__/mipmap-generator.unit.test.ts +202 -0
  255. package/src/__tests__/pack-artifact-transcode-model-owner.test.ts +32 -0
  256. package/src/__tests__/pack-basis-load.integration.test.ts +168 -0
  257. package/src/__tests__/payload-validate.unit.test.ts +285 -0
  258. package/src/__tests__/plugin.test.ts +55 -0
  259. package/src/__tests__/registry-concurrency.bench.test.ts +40 -0
  260. package/src/__tests__/registry-failure-retry.integration.test.ts +69 -0
  261. package/src/__tests__/registry-load-graph.integration.test.ts +246 -0
  262. package/src/__tests__/registry-load-state.unit.test.ts +35 -0
  263. package/src/__tests__/registry-package-cache.unit.test.ts +53 -0
  264. package/src/__tests__/registry-public-ready.unit.test.ts +38 -0
  265. package/src/__tests__/resolve-asset-handle.unit.test.ts +238 -0
  266. package/src/__tests__/scene-handle-fields.unit.test.ts +129 -0
  267. package/src/__tests__/scene-payload.unit.test.ts +151 -0
  268. package/src/__tests__/scene-publication-fence.test.ts +129 -0
  269. package/src/__tests__/scriptable-pack-public-surface.test-d.ts +27 -0
  270. package/src/__tests__/static-rig.integration.test.ts +8 -0
  271. package/src/__tests__/validate-material.unit.test.ts +359 -0
  272. package/src/__tests__/video-pack-loader.integration.test.ts +83 -0
  273. package/src/aabb.ts +107 -0
  274. package/src/asset-kind.ts +6 -0
  275. package/src/asset-registry.ts +2045 -0
  276. package/src/builtin-asset-registry.ts +164 -0
  277. package/src/catalog-source.ts +89 -0
  278. package/src/decode-image-bytes.ts +126 -0
  279. package/src/dynamic-texture-store.ts +234 -0
  280. package/src/errors/asset.ts +347 -0
  281. package/src/handles.ts +186 -0
  282. package/src/image-error.ts +45 -0
  283. package/src/index.ts +189 -0
  284. package/src/internal/artifact-cache.ts +65 -0
  285. package/src/internal/asset-graph.ts +420 -0
  286. package/src/internal/catalog-session.ts +345 -0
  287. package/src/internal/decoder-registry.ts +175 -0
  288. package/src/internal/immutable-payload.ts +20 -0
  289. package/src/internal/load-asset.ts +254 -0
  290. package/src/internal/pack-reader.ts +183 -0
  291. package/src/internal/validate-runtime-row.ts +51 -0
  292. package/src/internal.ts +6 -0
  293. package/src/loader-registry.ts +157 -0
  294. package/src/loaders/inline-pack.ts +837 -0
  295. package/src/loaders/mesh-bin.ts +304 -0
  296. package/src/loaders/pack-artifact.ts +665 -0
  297. package/src/material/dependency-graph.ts +46 -0
  298. package/src/material/generation-cache.ts +177 -0
  299. package/src/material/inspection.ts +46 -0
  300. package/src/material/loader.ts +326 -0
  301. package/src/material/runtime-shader.ts +133 -0
  302. package/src/mesh-bin.ts +5 -0
  303. package/src/mipmap-generator.ts +450 -0
  304. package/src/payload-validate.ts +573 -0
  305. package/src/plugin.ts +48 -0
  306. package/src/registry/artifact-io.ts +259 -0
  307. package/src/registry/asset-evidence.ts +51 -0
  308. package/src/registry/catalog-state.ts +409 -0
  309. package/src/registry/catalog.ts +369 -0
  310. package/src/registry/instantiate.ts +1056 -0
  311. package/src/registry/load-by-guid.ts +1657 -0
  312. package/src/registry/load-state.ts +115 -0
  313. package/src/registry/runtime-projection.ts +63 -0
  314. package/src/registry/scene-publication-fence.ts +256 -0
  315. package/src/registry/validate-material.ts +385 -0
  316. package/src/resolve-asset-handle.ts +147 -0
  317. package/src/resolve-tileset-runtime.ts +72 -0
  318. package/src/scene-handle-fields.ts +221 -0
  319. package/src/scene-payload.ts +207 -0
  320. package/src/wire-default-loaders.ts +79 -0
@@ -0,0 +1,152 @@
1
+ import { deriveVertexLayoutProjection } from '@forgeax/engine-geometry';
2
+ import {
3
+ MESH_BIN_HEADER_V4_BYTES,
4
+ type MeshBinHeaderV4,
5
+ writeMeshBinHeader,
6
+ } from '@forgeax/engine-pack';
7
+ import { describe, expect, it } from 'vitest';
8
+ import { unpackMeshBinV4 } from '../loaders/mesh-bin';
9
+
10
+ function makeArtifact(
11
+ options: {
12
+ readonly attributes?: Record<string, Float32Array | Uint16Array>;
13
+ readonly vertices?: Float32Array;
14
+ readonly indices?: Uint16Array | Uint32Array;
15
+ readonly json?: Record<string, unknown>;
16
+ readonly version?: number;
17
+ readonly trailingBytes?: number;
18
+ } = {},
19
+ ): Uint8Array {
20
+ const attributes = options.attributes ?? {
21
+ position: new Float32Array(3),
22
+ normal: new Float32Array(3),
23
+ uv: new Float32Array(2),
24
+ tangent: new Float32Array(4),
25
+ };
26
+ const projection = deriveVertexLayoutProjection(attributes);
27
+ const vertices = options.vertices ?? new Float32Array(projection.arrayStride / 4);
28
+ const indices = options.indices;
29
+ const json = new TextEncoder().encode(
30
+ JSON.stringify(
31
+ options.json ?? {
32
+ submeshes: [{ indexOffset: 0, indexCount: indices?.length ?? 0, materialSlot: 0 }],
33
+ materialSlots: [{ slotName: 'Default' }],
34
+ },
35
+ ),
36
+ );
37
+ const indexWidth = indices === undefined ? 0 : indices.BYTES_PER_ELEMENT;
38
+ const header: MeshBinHeaderV4 = {
39
+ version: 4,
40
+ projectionVersion: projection.schemaVersion,
41
+ mask: projection.mask,
42
+ digest: projection.digest,
43
+ stride: projection.arrayStride,
44
+ vertexCount: vertices.byteLength / projection.arrayStride,
45
+ vertexBytes: vertices.byteLength,
46
+ indexCount: indices?.length ?? 0,
47
+ indexWidth: indexWidth as 0 | 2 | 4,
48
+ indexBytes: indices?.byteLength ?? 0,
49
+ jsonBytes: json.byteLength,
50
+ };
51
+ const bytes = new Uint8Array(
52
+ MESH_BIN_HEADER_V4_BYTES + vertices.byteLength + (indices?.byteLength ?? 0) + json.byteLength,
53
+ );
54
+ writeMeshBinHeader(header, bytes);
55
+ if (options.version !== undefined) new DataView(bytes.buffer).setUint32(0, options.version, true);
56
+ let offset = MESH_BIN_HEADER_V4_BYTES;
57
+ bytes.set(new Uint8Array(vertices.buffer, vertices.byteOffset, vertices.byteLength), offset);
58
+ offset += vertices.byteLength;
59
+ if (indices !== undefined) {
60
+ bytes.set(new Uint8Array(indices.buffer, indices.byteOffset, indices.byteLength), offset);
61
+ offset += indices.byteLength;
62
+ }
63
+ bytes.set(json, offset);
64
+ if (options.trailingBytes !== undefined)
65
+ return new Uint8Array([...bytes, ...new Uint8Array(options.trailingBytes)]);
66
+ return bytes;
67
+ }
68
+
69
+ describe('unpackMeshBin v4 happy path', () => {
70
+ it('decodes vertices, indices, submeshes, and aabb metadata', () => {
71
+ const vertices = new Float32Array(12);
72
+ vertices[0] = 1.5;
73
+ const out = unpackMeshBinV4(
74
+ makeArtifact({
75
+ vertices,
76
+ indices: Uint16Array.of(0, 1, 0),
77
+ json: {
78
+ submeshes: [{ indexOffset: 0, indexCount: 3, materialSlot: 0 }],
79
+ materialSlots: [{ slotName: 'Default' }],
80
+ aabb: [0, 0, 0, 1, 1, 1],
81
+ },
82
+ }),
83
+ 'mesh/happy',
84
+ );
85
+ expect(out.ok).toBe(true);
86
+ if (!out.ok) return;
87
+ expect(out.value.vertices[0]).toBeCloseTo(1.5);
88
+ expect(out.value.indices).toBeInstanceOf(Uint16Array);
89
+ expect(out.value.submeshes).toHaveLength(1);
90
+ expect(Array.from(out.value.aabb ?? [])).toEqual([0, 0, 0, 1, 1, 1]);
91
+ });
92
+
93
+ it('decodes Uint32 indices and reconstructs projection attributes', () => {
94
+ const out = unpackMeshBinV4(
95
+ makeArtifact({
96
+ indices: Uint32Array.of(0, 0, 0),
97
+ attributes: {
98
+ position: new Float32Array(3),
99
+ normal: new Float32Array(3),
100
+ uv: new Float32Array(2),
101
+ tangent: new Float32Array(4),
102
+ color: new Float32Array(4),
103
+ },
104
+ }),
105
+ 'mesh/uint32',
106
+ );
107
+ expect(out.ok).toBe(true);
108
+ if (!out.ok) return;
109
+ expect(out.value.indices).toBeInstanceOf(Uint32Array);
110
+ expect(out.value.attributes.color).toBeInstanceOf(Float32Array);
111
+ });
112
+
113
+ it('decodes skin streams from the canonical interleaved projection', () => {
114
+ const out = unpackMeshBinV4(
115
+ makeArtifact({
116
+ attributes: {
117
+ position: new Float32Array(3),
118
+ normal: new Float32Array(3),
119
+ uv: new Float32Array(2),
120
+ tangent: new Float32Array(4),
121
+ skinIndex: new Uint16Array(4),
122
+ skinWeight: new Float32Array(4),
123
+ },
124
+ }),
125
+ 'mesh/skin',
126
+ );
127
+ expect(out.ok).toBe(true);
128
+ if (!out.ok) return;
129
+ expect(out.value.attributes.skinIndex).toBeInstanceOf(Uint16Array);
130
+ expect(out.value.attributes.skinWeight).toBeInstanceOf(Float32Array);
131
+ });
132
+ });
133
+
134
+ describe('unpackMeshBin v4 fail-closed', () => {
135
+ it('rejects legacy versions and truncated headers', () => {
136
+ expect(unpackMeshBinV4(makeArtifact({ version: 3 }), 'mesh/legacy').ok).toBe(false);
137
+ expect(unpackMeshBinV4(new Uint8Array(10), 'mesh/truncated').ok).toBe(false);
138
+ });
139
+
140
+ it('rejects trailing bytes and malformed metadata with recovery facts', () => {
141
+ const trailing = unpackMeshBinV4(makeArtifact({ trailingBytes: 1 }), 'mesh/trailing');
142
+ expect(trailing.ok).toBe(false);
143
+ const malformed = unpackMeshBinV4(
144
+ makeArtifact({ json: { submeshes: [], materialSlots: [] } }),
145
+ 'mesh/metadata',
146
+ );
147
+ expect(malformed.ok).toBe(false);
148
+ if (malformed.ok) return;
149
+ expect(malformed.error.sourceKey).toBe('mesh/metadata');
150
+ expect(malformed.error.recovery).toContain('re-cook');
151
+ });
152
+ });
@@ -0,0 +1,202 @@
1
+ // @forgeax/engine-assets-runtime -- mipmap-generator coverage (fix issue #709).
2
+ // numMipLevels formula + per-device pipeline cache + generateMipmaps (async
3
+ // build) + blitMipmapsSync (prewarmed) exercised against an in-memory device
4
+ // stub (no real GPU). Mirrors the runtime-project mipmap tests, but imports the
5
+ // relative `../mipmap-generator` so coverage attributes to src/, not dist/.
6
+
7
+ import { ok as rhiOk } from '@forgeax/engine-rhi';
8
+ import { describe, expect, it } from 'vitest';
9
+ import {
10
+ blitMipmapsSync,
11
+ generateMipmaps,
12
+ getOrCreateMipmapPipeline,
13
+ type MipmapBlitDevice,
14
+ type MipmapShaderModuleFactory,
15
+ mipmapCacheSize,
16
+ numMipLevels,
17
+ } from '../mipmap-generator';
18
+
19
+ let nextId = 0;
20
+ // The device-surface shims are loosely typed (`any` descriptors) so a plain
21
+ // { ok, value } POD satisfies them; the shader factory returns a real Result.
22
+ function ok<T>(value: T) {
23
+ return { ok: true as const, value };
24
+ }
25
+
26
+ function makeBlitDevice(renderPasses?: unknown[]): MipmapBlitDevice {
27
+ const pass = {
28
+ setPipeline: () => {},
29
+ setBindGroup: () => {},
30
+ draw: () => {},
31
+ end: () => {},
32
+ };
33
+ const encoder = {
34
+ beginRenderPass: (desc: unknown) => {
35
+ renderPasses?.push(desc);
36
+ return pass;
37
+ },
38
+ finish: () => ok({ tag: `cmdbuf-${nextId++}` }),
39
+ };
40
+ return {
41
+ createSampler: () => ok({ tag: `sampler-${nextId++}` }),
42
+ createBindGroupLayout: () => ok({ tag: `bgl-${nextId++}` }),
43
+ createPipelineLayout: () => ok({ tag: `pl-${nextId++}` }),
44
+ createRenderPipeline: (desc: { fragment?: { targets?: ReadonlyArray<{ format?: string }> } }) =>
45
+ ok({ tag: `pipeline-${nextId++}`, format: desc.fragment?.targets?.[0]?.format }),
46
+ createTexture: () => ok({ tag: `tex-${nextId++}` }),
47
+ createBindGroup: () => ok({ tag: `bg-${nextId++}` }),
48
+ createTextureView: () => ok({ tag: `view-${nextId++}` }),
49
+ createCommandEncoder: () => ok(encoder),
50
+ queue: { submit: () => ok(undefined) },
51
+ } as unknown as MipmapBlitDevice;
52
+ }
53
+
54
+ const stubShaderFactory: MipmapShaderModuleFactory = async () =>
55
+ rhiOk({ tag: `shader-${nextId++}` } as unknown);
56
+
57
+ describe('numMipLevels', () => {
58
+ it('256x256 -> 9', () => expect(numMipLevels({ width: 256, height: 256 })).toBe(9));
59
+ it('1x1 -> 1', () => expect(numMipLevels({ width: 1, height: 1 })).toBe(1));
60
+ it('0x0 -> 1 (max<=1 guard)', () => expect(numMipLevels({ width: 0, height: 0 })).toBe(1));
61
+ it('non-square 300x200 -> 9', () => expect(numMipLevels({ width: 300, height: 200 })).toBe(9));
62
+ it('17x5 -> 5', () => expect(numMipLevels({ width: 17, height: 5 })).toBe(5));
63
+ });
64
+
65
+ describe('getOrCreateMipmapPipeline + mipmapCacheSize', () => {
66
+ it('fresh device starts at cache size 0', () => {
67
+ expect(mipmapCacheSize(makeBlitDevice())).toBe(0);
68
+ });
69
+
70
+ it('same format twice -> cache hit (identical pipeline, size 1)', async () => {
71
+ const device = makeBlitDevice();
72
+ const a = await getOrCreateMipmapPipeline(device, 'rgba8unorm', stubShaderFactory);
73
+ const b = await getOrCreateMipmapPipeline(device, 'rgba8unorm', stubShaderFactory);
74
+ expect(a.ok && b.ok).toBe(true);
75
+ if (!a.ok || !b.ok) return;
76
+ expect(a.value).toBe(b.value);
77
+ expect(mipmapCacheSize(device)).toBe(1);
78
+ });
79
+
80
+ it('different formats -> separate slots (size 2)', async () => {
81
+ const device = makeBlitDevice();
82
+ await getOrCreateMipmapPipeline(device, 'rgba8unorm', stubShaderFactory);
83
+ await getOrCreateMipmapPipeline(device, 'rgba8unorm-srgb', stubShaderFactory);
84
+ expect(mipmapCacheSize(device)).toBe(2);
85
+ });
86
+
87
+ it('two devices keep independent caches', async () => {
88
+ const d1 = makeBlitDevice();
89
+ const d2 = makeBlitDevice();
90
+ const r1 = await getOrCreateMipmapPipeline(d1, 'rgba8unorm', stubShaderFactory);
91
+ const r2 = await getOrCreateMipmapPipeline(d2, 'rgba8unorm', stubShaderFactory);
92
+ if (!r1.ok || !r2.ok) throw new Error('expected ok');
93
+ expect(r1.value).not.toBe(r2.value);
94
+ });
95
+
96
+ it('propagates a shader-module factory failure', async () => {
97
+ const device = makeBlitDevice();
98
+ const failing: MipmapShaderModuleFactory = async () =>
99
+ ({ ok: false, error: { code: 'x' } }) as never;
100
+ const res = await getOrCreateMipmapPipeline(device, 'rgba8unorm', failing);
101
+ expect(res.ok).toBe(false);
102
+ });
103
+ });
104
+
105
+ describe('generateMipmaps (async build path)', () => {
106
+ it('returns ok immediately when levels <= 1', async () => {
107
+ const device = makeBlitDevice();
108
+ const res = await generateMipmaps(
109
+ device,
110
+ {},
111
+ { format: 'rgba8unorm', width: 1, height: 1 },
112
+ stubShaderFactory,
113
+ );
114
+ expect(res.ok).toBe(true);
115
+ expect(mipmapCacheSize(device)).toBe(0); // never built a pipeline
116
+ });
117
+
118
+ it('builds + blits the full mip chain for a 4x4 texture', async () => {
119
+ const device = makeBlitDevice();
120
+ const res = await generateMipmaps(
121
+ device,
122
+ { tag: 'tex' },
123
+ { format: 'rgba8unorm', width: 4, height: 4 },
124
+ stubShaderFactory,
125
+ );
126
+ expect(res.ok).toBe(true);
127
+ expect(mipmapCacheSize(device)).toBe(1);
128
+ });
129
+
130
+ it('uses the WebGPU GPUColor object shape for clear values', async () => {
131
+ const renderPasses: unknown[] = [];
132
+ const device = makeBlitDevice(renderPasses);
133
+ const res = await generateMipmaps(
134
+ device,
135
+ { tag: 'tex' },
136
+ { format: 'rgba8unorm', width: 4, height: 4 },
137
+ stubShaderFactory,
138
+ );
139
+ expect(res.ok).toBe(true);
140
+ expect(renderPasses).toHaveLength(2);
141
+ for (const pass of renderPasses) {
142
+ const clearValue = (pass as { colorAttachments: Array<{ clearValue: unknown }> })
143
+ .colorAttachments[0]?.clearValue;
144
+ expect(clearValue).toEqual({ r: 0, g: 0, b: 0, a: 1 });
145
+ }
146
+
147
+ const syncPasses: unknown[] = [];
148
+ const syncDevice = makeBlitDevice(syncPasses);
149
+ const prewarm = await getOrCreateMipmapPipeline(syncDevice, 'rgba8unorm', stubShaderFactory);
150
+ expect(prewarm.ok).toBe(true);
151
+ expect(
152
+ blitMipmapsSync(syncDevice, { tag: 'tex' }, { format: 'rgba8unorm', width: 4, height: 4 }).ok,
153
+ ).toBe(true);
154
+ for (const pass of syncPasses) {
155
+ const clearValue = (pass as { colorAttachments: Array<{ clearValue: unknown }> })
156
+ .colorAttachments[0]?.clearValue;
157
+ expect(clearValue).toEqual({ r: 0, g: 0, b: 0, a: 1 });
158
+ }
159
+ });
160
+ });
161
+
162
+ describe('blitMipmapsSync (prewarmed path)', () => {
163
+ it('returns ok immediately when levels <= 1', () => {
164
+ const res = blitMipmapsSync(
165
+ makeBlitDevice(),
166
+ {},
167
+ { format: 'rgba8unorm', width: 1, height: 1 },
168
+ );
169
+ expect(res.ok).toBe(true);
170
+ });
171
+
172
+ it('errors when the device cache was never prewarmed', () => {
173
+ const res = blitMipmapsSync(
174
+ makeBlitDevice(),
175
+ {},
176
+ { format: 'rgba8unorm', width: 4, height: 4 },
177
+ );
178
+ expect(res.ok).toBe(false);
179
+ if (res.ok) return;
180
+ expect(res.error.code).toBe('rhi-not-available');
181
+ });
182
+
183
+ it('errors when the requested format was not prewarmed', async () => {
184
+ const device = makeBlitDevice();
185
+ await getOrCreateMipmapPipeline(device, 'rgba8unorm', stubShaderFactory);
186
+ const res = blitMipmapsSync(device, {}, { format: 'bgra8unorm', width: 4, height: 4 });
187
+ expect(res.ok).toBe(false);
188
+ if (res.ok) return;
189
+ expect(res.error.code).toBe('rhi-not-available');
190
+ });
191
+
192
+ it('blits the full chain once the format is prewarmed', async () => {
193
+ const device = makeBlitDevice();
194
+ await getOrCreateMipmapPipeline(device, 'rgba8unorm', stubShaderFactory);
195
+ const res = blitMipmapsSync(
196
+ device,
197
+ { tag: 'tex' },
198
+ { format: 'rgba8unorm', width: 8, height: 8 },
199
+ );
200
+ expect(res.ok).toBe(true);
201
+ });
202
+ });
@@ -0,0 +1,32 @@
1
+ /// <reference types="node" />
2
+
3
+ import { readFileSync } from 'node:fs';
4
+ import type { TranscodeModel } from '@forgeax/engine-codec';
5
+ import type { Loader } from '@forgeax/engine-types';
6
+ import { describe, expect, expectTypeOf, it } from 'vitest';
7
+ import type { textureLoader } from '../loaders/pack-artifact.js';
8
+
9
+ const packArtifactSource = readFileSync(
10
+ new URL('../loaders/pack-artifact.ts', import.meta.url),
11
+ 'utf8',
12
+ );
13
+
14
+ describe('assets-runtime transcode model owner', () => {
15
+ it('keeps the public texture loader and model vocabulary intact', () => {
16
+ expectTypeOf<typeof textureLoader>().toEqualTypeOf<Loader>();
17
+ expectTypeOf<TranscodeModel>().toEqualTypeOf<'etc1s' | 'uastc-ldr' | 'uastc-hdr'>();
18
+
19
+ const models: readonly TranscodeModel[] = ['etc1s', 'uastc-ldr', 'uastc-hdr'];
20
+ expect(models).toEqual(['etc1s', 'uastc-ldr', 'uastc-hdr']);
21
+ });
22
+
23
+ it('derives the private model view from the codec owner', () => {
24
+ expect(packArtifactSource).toContain('import type { CodecError, TranscodeModel }');
25
+ expect(packArtifactSource).toContain(
26
+ 'function transcodeModel(profile: string): TranscodeModel | undefined',
27
+ );
28
+ expect(packArtifactSource).not.toContain(
29
+ "function transcodeModel(profile: string): 'etc1s' | 'uastc-ldr' | 'uastc-hdr' | undefined",
30
+ );
31
+ });
32
+ });
@@ -0,0 +1,168 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { fileURLToPath } from 'node:url';
3
+ import type { LoadContext, TextureAsset } from '@forgeax/engine-types';
4
+ import { describe, expect, expectTypeOf, it } from 'vitest';
5
+ import type {
6
+ BasisEncoderModule,
7
+ BasisModuleFactory,
8
+ } from '../../../codec/src/wasm/basis-types.js';
9
+ import type { CodecContextFailure } from '../loaders/pack-artifact.js';
10
+ import { PACK_ARTIFACT_LOADERS, textureLoader } from '../loaders/pack-artifact.js';
11
+
12
+ const TRANSCODER_GLUE = new URL('../../../codec/pkg/basis_transcoder.mjs', import.meta.url);
13
+ const ENCODER_GLUE = new URL('../../../codec/pkg/encode/basis_encoder.mjs', import.meta.url);
14
+ const pkgBuilt =
15
+ existsSync(fileURLToPath(TRANSCODER_GLUE)) && existsSync(fileURLToPath(ENCODER_GLUE));
16
+
17
+ async function loadEncoder(): Promise<BasisEncoderModule> {
18
+ const factory = (
19
+ (await import(/* @vite-ignore */ ENCODER_GLUE.href)) as {
20
+ default: BasisModuleFactory<BasisEncoderModule>;
21
+ }
22
+ ).default;
23
+ const mod = await factory({
24
+ locateFile: () => new URL('../../../codec/pkg/encode/basis_encoder.wasm', import.meta.url).href,
25
+ });
26
+ mod.initializeBasis();
27
+ return mod;
28
+ }
29
+
30
+ async function makeBasis(): Promise<Uint8Array> {
31
+ const mod = await loadEncoder();
32
+ const encoder = new mod.BasisEncoder();
33
+ try {
34
+ encoder.setSliceSourceImage(0, new Uint8Array(4 * 4 * 4).fill(127), 4, 4, 0);
35
+ encoder.setCreateKTX2File(false);
36
+ encoder.setFormatMode(mod.basis_tex_format.cUASTC_LDR_4x4.value);
37
+ encoder.setPerceptual(false);
38
+ encoder.setMipGen(false);
39
+ const bytes = new Uint8Array(1 << 20);
40
+ const length = encoder.encode(bytes);
41
+ if (length <= 0) throw new Error('raw Basis encode failed');
42
+ return bytes.slice(0, length);
43
+ } finally {
44
+ encoder.delete();
45
+ }
46
+ }
47
+
48
+ const context: LoadContext = {
49
+ fetchBinary: async () => ({ ok: false as const, error: new Error('not used') }),
50
+ resolveRef: async () => ({ ok: false as const, error: new Error('not used') }),
51
+ transcodeCaps: { bc: false, etc2: false, astc: false },
52
+ device: undefined,
53
+ };
54
+
55
+ describe.skipIf(!pkgBuilt)('Pack runtime raw Basis loader', () => {
56
+ it('keeps context failure code and detail correlated as a closed union', () => {
57
+ type ExpectedContextFailure =
58
+ | {
59
+ readonly code: 'basis-profile-unsupported';
60
+ readonly detail: { readonly profile: string };
61
+ }
62
+ | {
63
+ readonly code: 'ktx2-color-space-mismatch';
64
+ readonly detail: {
65
+ readonly authoredColorSpace: 'srgb' | 'linear';
66
+ readonly dfdColorSpace: 'srgb' | 'linear' | 'unknown';
67
+ };
68
+ };
69
+
70
+ expectTypeOf<CodecContextFailure>().toEqualTypeOf<ExpectedContextFailure>();
71
+ });
72
+
73
+ it('transcodes a raw Basis artifact using its explicit container identity', async () => {
74
+ const bytes = await makeBasis();
75
+ const loadPack = textureLoader.loadPack;
76
+ if (loadPack === undefined) throw new Error('textureLoader.loadPack must be registered');
77
+
78
+ const result = (await loadPack(
79
+ {
80
+ guid: '019f0000-0000-7000-8000-000000000304',
81
+ kind: 'texture',
82
+ payload: { width: 4, height: 4, colorSpace: 'linear' },
83
+ refs: [],
84
+ artifacts: {
85
+ body: {
86
+ bytes,
87
+ descriptor: {
88
+ path: 'texture.basis',
89
+ mediaType: 'image/basis',
90
+ assetCodec: { name: 'basis', container: 'basis', profile: 'uastc-ldr' },
91
+ },
92
+ },
93
+ },
94
+ },
95
+ context,
96
+ )) as
97
+ | { readonly ok: true; readonly value: TextureAsset }
98
+ | { readonly ok: false; readonly error: unknown };
99
+
100
+ expect(result.ok).toBe(true);
101
+ if (!result.ok) return;
102
+ expect(result.value).toMatchObject({
103
+ kind: 'texture',
104
+ width: 4,
105
+ height: 4,
106
+ format: 'rgba8unorm',
107
+ colorSpace: 'linear',
108
+ mipmap: false,
109
+ mipLevelCount: 1,
110
+ });
111
+ expect(result.value.data.byteLength).toBeGreaterThan(0);
112
+ });
113
+
114
+ it('preserves codec context for a malformed KTX2 Pack artifact', async () => {
115
+ const loadPack = textureLoader.loadPack;
116
+ if (loadPack === undefined) throw new Error('textureLoader.loadPack must be registered');
117
+
118
+ const result = (await loadPack(
119
+ {
120
+ guid: '019f0000-0000-7000-8000-000000000305',
121
+ kind: 'texture',
122
+ payload: { width: 4, height: 4, colorSpace: 'srgb' },
123
+ refs: [],
124
+ artifacts: {
125
+ body: {
126
+ bytes: new Uint8Array([0, 1, 2, 3]),
127
+ descriptor: {
128
+ path: 'texture.ktx2',
129
+ mediaType: 'image/ktx2',
130
+ assetCodec: { name: 'basis', container: 'ktx2', profile: 'uastc-ldr' },
131
+ },
132
+ },
133
+ },
134
+ },
135
+ context,
136
+ )) as
137
+ | { readonly ok: true; readonly value: TextureAsset }
138
+ | {
139
+ readonly ok: false;
140
+ readonly error: {
141
+ readonly code: string;
142
+ readonly hint: string;
143
+ readonly detail?: unknown;
144
+ };
145
+ };
146
+
147
+ expect(result.ok).toBe(false);
148
+ if (result.ok) return;
149
+ expect(result.error.code).toBe('asset-parse-failed');
150
+ expect(result.error.hint).toContain('KTX2');
151
+ expect(result.error.detail).toMatchObject({
152
+ sourcePath: '019f0000-0000-7000-8000-000000000305',
153
+ container: 'ktx2',
154
+ profile: 'uastc-ldr',
155
+ codecCode: 'ktx2-parse-failed',
156
+ capabilities: { bc: false, etc2: false, astc: false },
157
+ });
158
+ expect(result.error.detail).toHaveProperty('codecDetail.reason');
159
+ });
160
+ });
161
+
162
+ describe('Pack artifact loader matrix', () => {
163
+ it('registers every artifact-backed ordinary asset kind', () => {
164
+ expect(PACK_ARTIFACT_LOADERS.map((loader) => loader.kind).sort()).toEqual(
165
+ ['equirect', 'font', 'render-pipeline', 'texture', 'tileset'].sort(),
166
+ );
167
+ });
168
+ });