@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
package/README.md ADDED
@@ -0,0 +1,491 @@
1
+ # @forgeax/engine-assets-runtime
2
+
3
+ ## MaterialAsset 唯一成功路径
4
+
5
+ Runtime 负责 `paramSchema -> derive -> compile/reflect -> cook/load -> extract/record`
6
+ 中的 load 与 extract/record 消费:按 GUID 读取 producer 已 cook 的 artifact、
7
+ receipt、`coordinateSet` 与 `physicalUvScale`,验证 `layoutIdentity` 后交给
8
+ render owner。缺失或 stale 时 inspect、修复 producer 并 recook;不会在 runtime
9
+ 编译 WGSL 或补写 app 侧坐标。
10
+
11
+ > [!CAUTION]
12
+ > 只按结构化 `code`、`detail`、`hint` 恢复;不要复制 source-owned error union,
13
+ > 也不要从 URL 或数组顺序重建 asset identity。
14
+
15
+ ## Authoring and recovery index
16
+
17
+ ## Material contract index
18
+
19
+ The runtime loader reads the Pack publication by GUID and exposes a read-only
20
+ projection to render. Runtime values and module slots are data, while compiler
21
+ context is a closed input; no material macro, feature define, cook, or DDC
22
+ write belongs here. Preserve the layered identities
23
+ `materialContractDigest`, `sourceClosureDigest`, `layoutIdentity`,
24
+ `programIdentity`, `cookIdentity`, and `materialPublicationIdentity`, and
25
+ compare `current` with `generation` before sending the first producer
26
+ divergence back through cold-cook and receipt/artifact verification.
27
+
28
+ The shortest static consumer path is `configurePackIndex(url)` followed by
29
+ `loadByGuid<ConcreteAsset>(guid)`. The public barrel exposes the concrete
30
+ 16-kind types and returns durable payloads, dependency `refs`, and local
31
+ `artifacts`; it does not mint a generic GUID-to-handle materializer. Animation,
32
+ tileset, render, audio, and VFX owners perform their own World or Host
33
+ projection after load.
34
+
35
+ Catalog source is selected once per host mode: `configureRuntimeBinding(binding)`
36
+ owns the scope/generation-bound catalog used by Vite development servers, while
37
+ `configurePackIndex(url)` owns the static catalog emitted by a production build.
38
+ They are alternatives, not a sequence. App demos can use the shared
39
+ `configureRuntimeAssetCatalog` helper from `@forgeax/apps-shared` to keep this
40
+ selection as one SSOT and avoid overwriting a scoped development URL with
41
+ `/pack-index.json`.
42
+
43
+ Read structured errors by `code` and use `hint`/`detail` to inspect, rebuild or
44
+ cold-cook, refresh LKG, or attach a capability. Host capability loss affects
45
+ install/play/execute only; descriptor loading stays available.
46
+
47
+ The runtime matrix is the same 16 durable kinds as `SCRIPTABLE_PACK_ASSET_KINDS`:
48
+ `mesh`, `material`, `scene`, `texture`, `equirect`, `sampler`, `font`,
49
+ `render-pipeline`, `tileset`, `video`, `skeleton`, `skin`, `animation-clip`,
50
+ `animation-graph`, `audio`, and `particle-effect`.
51
+
52
+ The authority map is [`asset-authority.schema.json`](../../asset-authority.schema.json). Runtime reads Catalog decisions and validated Pack/DDC projections by GUID; it never writes Pack, Meta, DDC, or authoring state.
53
+
54
+ ## Material publication inspection
55
+
56
+ `inspectMaterialRuntime(ready)` is the read-only, GUID-addressed inspection
57
+ projection after the `MaterialReady` gate. Its first-level identity is
58
+ `materialGuid`, `publicationGeneration`, `specializationKey`,
59
+ `artifactDigest`, and `readiness`. Expand `sourceClosure`,
60
+ `parameterContract`, `refs`, and `receipt` only when owner debugging requires
61
+ them. The returned `status: 'Ready'` is retained for compatibility with the
62
+ runtime result; `readiness: 'ready'` is the machine-readable state field.
63
+
64
+ Material failure consumers branch on stable kebab-case codes:
65
+ `shader-module-not-found`, `material-reflection-binding-mismatch`,
66
+ `material-specialization-not-cooked`, `asset-artifact-missing`,
67
+ `asset-artifact-integrity-mismatch`, and `material-cook-record-invalid`.
68
+ Read `expected`, `actual`, `hint`, `retryable`, and `recoveryActions` when
69
+ present. Do not infer readiness from a transport URL, a shader manifest, a
70
+ natural-language message, or a fallback material.
71
+
72
+ ## Plugin-owned loader registrations
73
+
74
+ `assetsPlugin(registry)` provides the renderer-owned registry to a Cordis
75
+ realm. `assetLoaderPlugin(loader)` and `packLoaderPlugin(loader)` register one
76
+ loader as a reversible Fiber effect, so removing a feature cannot leave a stale
77
+ kind handler behind. GUID, Catalog, Pack, and payload identity remain owned by
78
+ `AssetRegistry`; the plugin controls only the registration lifetime.
79
+
80
+ ```ts
81
+ const feature = await app.pluginContext.plugin(assetLoaderPlugin(dialogueLoader));
82
+ const dialogue = await app.renderer.assets.loadByGuid<Dialogue>(guid);
83
+ await feature.dispose();
84
+ ```
85
+
86
+ Use `registry.loaders.register(...)` directly only in a lower-level host that
87
+ owns the matching disposer itself. App/game capability composition should use
88
+ the plugin helpers so registration and teardown share one Cordis lifecycle.
89
+
90
+ | Lifecycle | Runtime action | Recovery |
91
+ |:--|:--|:--|
92
+ | `current` | Load the validated projection | Inspect evidence if a dependency fails |
93
+ | `missing` / `cooking` | Return structured not-ready failure | Rebuild in the studio/build host |
94
+ | `stale` / `failed` | Do not use raw source | Cold cook, or explicitly preview last-known-good |
95
+
96
+ Use [`check-asset-authority-audit.mjs`](../../scripts/forgeax/check-asset-authority-audit.mjs) to inspect the owner and runtime-source conclusion before changing a producer.
97
+
98
+ > [!IMPORTANT]
99
+ > Runtime material consumption is `configurePackIndex` -> `loadByGuid<MaterialAsset>` -> cooked readiness -> `world.internSharedRef`. The registry returns the loaded payload and follows its dependency graph; it does not create an app-owned shader artifact or a parallel material authoring surface. Interning preserves one handle per catalogued payload identity inside a World; explicit `world.allocSharedRef` calls remain independent resources.
100
+
101
+ ## MaterialAsset runtime recovery
102
+
103
+ Load a root and any inherited child through the same GUID catalog. A ready material must have its effective `passes`, `values`, `parent` chain, per-slot `coordinates`, references, artifact, and receipt. For a failure, switch on the structured code, read `detail` and `hint`, repair the package or cook output, and retry the same GUID. A missing cook is not a valid fallback to runtime compilation.
104
+
105
+ The runtime asset layer: catalogue an asset by GUID, load its payload + all
106
+ transitively-referenced sub-assets, resolve a `Handle` back to its payload, and
107
+ wire the default loader set. Tier 2.1 package extracted from
108
+ `@forgeax/engine-runtime` (feat-20260705-runtime-tier2-decomposition M1) so an AI
109
+ user loads only the asset-cluster concept surface — not the whole renderer — when
110
+ the task is "get an asset into the World".
111
+
112
+ ## AssetEvidence SDK boundary
113
+
114
+ `AssetRegistry.inspect(guid)` and `verifyByGuid(guid)` expose the injected SDK side of the same `AssetEvidence` chain: source declaration, catalog `packageUrl`/`cookReceiptUrl`, producer `CookReceipt`, Pack v2 artifact verification, and optional runtime state. The registry does not import the Node CLI, Vite, or filesystem policy. Call `configureAssetEvidence(source)` in a host that can supply those facts; without it the result is explicit `unknown`/capability-missing evidence.
115
+
116
+ Cook states are not interchangeable: `notCooked`, `ready/current`, `ready/stale`, and `unknown` each describe a different recovery action. Package/artifact verification separately reports `notChecked`, `passed`, or `failed`. Follow `.code` and `.hint`, repair the producer or package, and rerun the probe; never convert a missing capability into passed evidence.
117
+
118
+ ## 30-second self-introduction
119
+
120
+ - **`AssetRegistry`** — instance-per-engine GUID -> payload catalogue. `catalog` /
121
+ `loadByGuid` / `lookup` / `parseGuid` / `inspect` / `resolveName` / `packageOf` /
122
+ `rename` / `invalidate` / `invalidateAll` / `instantiate`. Post-D-17 it stores
123
+ the PAYLOAD and mints no handles (scene GUID resolution interns column handles
124
+ on the World via `world.internSharedRef('Kind', payload)`). `Renderer.assets` is an `AssetRegistry`
125
+ assembled by `createRenderer` (which injects the post-spawn hook and concrete
126
+ Web Audio loader; video is a default loader — see D-1 / D-2).
127
+ - **`HANDLE_CUBE` / `HANDLE_TRIANGLE` / `HANDLE_QUAD` / `HANDLE_SPHERE` /
128
+ `HANDLE_CYLINDER` / `HANDLE_NINESLICE_QUAD`** — process-static builtin mesh
129
+ handles (reserved ids 1-6, `< BUILTIN_BASE`), resolved through
130
+ `BuiltinAssetRegistry` (never reference-counted). Pair with `MeshFilter`.
131
+ - **`resolveAssetHandle(world, handle)`** — two-tier (builtin / user-tier
132
+ `world.sharedRefs`) handle -> payload resolution; returns a closed-union error
133
+ (`shared-ref-stale` / `shared-ref-released` / `asset-not-found`) so callers
134
+ distinguish "re-acquire handle" from "re-load asset" from "check GUID".
135
+ - **`LoaderRegistry` + `wireDefaultLoaders(registry, extraLoaders?)` +
136
+ `createDefaultLoaderRegistry(extraLoaders?)`** — the default set covers the
137
+ complete 16-kind durable `Asset` union, including both inline and artifact
138
+ forms. `createRenderer` may replace the durable audio descriptor loader with
139
+ its concrete Web Audio catalog-entry loader; the registry still has one
140
+ owner per kind.
141
+
142
+ ### 30s hands-on example
143
+
144
+ ```ts
145
+ import { AssetRegistry, HANDLE_CUBE } from '@forgeax/engine-assets-runtime';
146
+ import { MeshFilter, MeshRenderer } from '@forgeax/engine-render';
147
+ import { Transform } from '@forgeax/engine-scene';
148
+
149
+ // A builtin mesh handle needs no registration ceremony:
150
+ world.spawn(
151
+ { component: Transform, data: { pos: [0, 0, 0], quat: [0, 0, 0, 1], scale: [1, 1, 1] } },
152
+ { component: MeshFilter, data: { assetHandle: HANDLE_CUBE } },
153
+ { component: MeshRenderer, data: { materials: [matHandle] } },
154
+ ).unwrap();
155
+
156
+ // Imported meshes normally use an empty override vector. AssetRegistry
157
+ // recursively loads MeshAsset.materialSlots defaults; Render inherits them.
158
+ world.spawn(
159
+ { component: Transform, data: {} },
160
+ { component: MeshFilter, data: { assetHandle: importedMeshHandle } },
161
+ { component: MeshRenderer, data: { materials: [] } },
162
+ ).unwrap();
163
+
164
+ // Catalogue + load a GUID-addressed asset (dev / inline path):
165
+ const guid = assets.parseGuid('cbe42beb-8975-5096-b3a1-3dda4cb4c077');
166
+ const res = await assets.loadByGuid(guid); // -> Result<payload> (D-17: payload, not handle)
167
+ ```
168
+
169
+ ## API surface
170
+
171
+ | Symbol | Kind | One-line |
172
+ |:--|:--|:--|
173
+ | `AssetRegistry` | class | GUID -> payload catalogue + loader dispatch + scene instantiate |
174
+ | `HANDLE_CUBE` / `HANDLE_TRIANGLE` / `HANDLE_QUAD` / `HANDLE_SPHERE` / `HANDLE_CYLINDER` / `HANDLE_NINESLICE_QUAD` | const | builtin mesh handles (ids 1-6) |
175
+ | `BuiltinAssetRegistry` / `BUILTIN_*` / `BUILTIN_BASE` | const | process-static builtin payloads and reserved slot boundary; the shared vertex-layout SSOT is `PROCEDURAL_FLOATS_PER_VERTEX` from `@forgeax/engine-geometry` |
176
+ | `resolveAssetHandle` / `walkMaterialPassesOverSharedRefs` | fn | two-tier handle -> payload resolution |
177
+ | `LoaderRegistry` | class | kind -> loader dispatch table |
178
+ | `wireDefaultLoaders` / `createDefaultLoaderRegistry` | fn | wire all 16 durable Asset loaders + caller `extraLoaders` |
179
+ | `assetsPlugin` / `assetLoaderPlugin` / `packLoaderPlugin` | fn | Provide a registry and bind loader registrations to a Cordis Fiber lifetime |
180
+ | `DynamicTextureStore` / `DynamicTextureDevice` | class/type | per-frame dynamic texture upload store; replacement devices invalidate stale transient textures before the next upload |
181
+ | `unpackMeshBin` / `UnpackedMeshBin` | fn/type | strict mesh-binary v4 sidecar decode with geometry projection verification |
182
+ | `validateTilesetPayload` / `TilesetValidateOptions` | fn/type | register-time tileset payload gate |
183
+ | `PostSpawnHook` / `SkinJointResolver` | type | post-spawn hook contract (D-1; runtime injects `postSpawnResolveJoints`) |
184
+ | `Asset` / `MeshAsset` | type | re-exported asset union shapes (SSOT `@forgeax/engine-types`) |
185
+
186
+ Full `AssetRegistry` surface + signatures: source SSOT
187
+ `packages/assets-runtime/src/asset-registry.ts`. The load + DDC / pack-fetch
188
+ pipeline lives in `packages/assets-runtime/src/registry/load-by-guid.ts`; the
189
+ instantiate cluster + hook types in `registry/instantiate.ts`; material
190
+ validation in `registry/validate-material.ts`.
191
+
192
+ ## Mesh binary v4 loading
193
+
194
+ [`loaders/mesh-bin.ts`](src/loaders/mesh-bin.ts) is the sole runtime decoder.
195
+ It rebuilds the geometry projection from the wire mask, compares schema version,
196
+ stride, and digest, validates every byte/cardinality bound, then publishes the
197
+ mesh and metadata as one result. Inline-pack loading uses the same loader.
198
+
199
+ | Failure | Runtime behavior | Next action |
200
+ |:--|:--|:--|
201
+ | v2/v3 or malformed bytes | closed `MeshBinAssetError` with sourceKey, expected/actual, and recovery hint | Re-cook through the owning importer |
202
+ | invalid refs or metadata | no MeshAsset publication | Repair producer refs/Meta and retry |
203
+ | failed replacement after a prior load | retain the catalogued last-known-good payload | Inspect evidence, cold-cook, then publish atomically |
204
+
205
+ > [!WARNING]
206
+ > Runtime has no legacy decoder and no re-cook fallback. A white/default mesh
207
+ > is not a valid recovery for a malformed artifact.
208
+
209
+ ### Scene instantiation failure atomicity
210
+
211
+ > [!IMPORTANT]
212
+ > `assets.instantiate` and `assets.instantiateFlat` run the renderer-injected
213
+ > post-spawn hook as part of one transaction. If joint wiring returns the
214
+ > existing `skin-joint-path-unresolved` or `skin-asset-unresolved` result, the
215
+ > method returns that exact structured error and removes only the entities,
216
+ > hierarchy, mount state, joint publication, and shared-reference grants
217
+ > created by that call. Repair the catalogued asset or joint path, then retry
218
+ > with the same registry and World; no process or World reconstruction is
219
+ > required.
220
+
221
+ ## Catalog source: enumerate first-class asset rows, then observe row changes
222
+
223
+ > [!IMPORTANT]
224
+ > A catalog is an enumerable complete row set plus subscribable row changes.
225
+ > Wire the source, **subscribe before enumerating**, then keep the local view
226
+ > keyed by GUID. `CatalogDelta` reports facts only; it never chooses a page
227
+ > reload or an editor update policy.
228
+
229
+ `CatalogSource` is the runtime boundary between `AssetRegistry` and a concrete
230
+ catalog transport. Its two operations are `enumerate()` and `subscribe()`;
231
+ the public row and delta shapes are `CatalogEntry` and `CatalogDelta` from
232
+ `@forgeax/engine-types`. Read those exported types for their complete fields
233
+ instead of copying a second schema into a consumer.
234
+
235
+ ### Producer fact parity
236
+
237
+ > [!IMPORTANT]
238
+ > Static entries and URL/fetch entries have the same neutral `CatalogEntry`
239
+ > shape. `parseCatalog()` preserves `packageId`, `provenance`, `revision`,
240
+ > `sourceKey`, `sourceIndex`, `relations`, and `diagnostics`; it never derives
241
+ > them from `relativeUrl`, `sourcePath`, or array position.
242
+
243
+ | Runtime input | What is checked | What is not done |
244
+ |:--|:--|:--|
245
+ | Static `entries` | Optional expected revision continuity | No payload decode or re-import |
246
+ | URL `fetch` | JSON shape, required locator, and optional expected revision | No identity reconstruction from the URL |
247
+ | Malformed row | Structured `asset-parse-failed` with `expected` and `hint` | Never converted into an empty successful catalog |
248
+ | Revision mismatch | Structured failure with expected and actual revisions | Never applied over the last verified snapshot |
249
+
250
+ The source is a neutral facts boundary, not an asset decoder. Decode, upload,
251
+ and GPU work remain in their existing loader paths; the catalog parser only
252
+ enumerates facts and locators.
253
+
254
+ ```ts
255
+ const unsubscribe = assets.subscribeCatalog((delta) => {
256
+ // Delete first, then replace added/changed rows by their stable GUID.
257
+ for (const guid of delta.removed) rowsByGuid.delete(guid.toLowerCase());
258
+ for (const row of [...delta.added, ...delta.changed]) {
259
+ rowsByGuid.set(row.guid.toLowerCase(), row);
260
+ }
261
+ });
262
+
263
+ const snapshot = await assets.enumerateCatalog();
264
+ if (!snapshot.ok) {
265
+ console.error(snapshot.error.code, snapshot.error.hint);
266
+ // Fix the source configuration or catalog endpoint, then call enumerateCatalog() again.
267
+ } else {
268
+ for (const row of snapshot.value) rowsByGuid.set(row.guid.toLowerCase(), row);
269
+ }
270
+
271
+ // Safe to call more than once.
272
+ unsubscribe();
273
+ unsubscribe();
274
+ ```
275
+
276
+ ### Delta and refresh ownership
277
+
278
+ | Delta set | Carries | Consumer action |
279
+ |:--|:--|:--|
280
+ | `added` | complete new `CatalogEntry` rows | insert/replace by lowercase GUID |
281
+ | `changed` | complete replacement `CatalogEntry` rows | replace by lowercase GUID |
282
+ | `removed` | stable GUID strings | remove by lowercase GUID |
283
+
284
+ The producer emits no delta when its final catalog projection is unchanged.
285
+ Source-only byte changes likewise are not forged into a row change. A browser
286
+ host that needs those bytes to refresh explicitly selects its own policy (for
287
+ the Vite adapter, `reloadAssetHost()`); an editor can instead merge the delta
288
+ without recreating its realm. The registry does not import Vite or infer either
289
+ policy.
290
+
291
+ ### Recovery and static sources
292
+
293
+ Call `setCatalogSource(source)` before enumeration. Replacing a source disposes the
294
+ previous producer subscription; call `clearCatalogSource()` during host teardown
295
+ to remove the active transport and replica listeners without clearing payload caches.
296
+ Without a source,
297
+ `enumerateCatalog()` returns the structured `catalog-source-unconfigured`
298
+ error; endpoint and parse failures remain structured results as well. Inspect
299
+ `.code` and `.hint`, repair the external condition, then enumerate again — a
300
+ failed enumeration is not a permanent result.
301
+
302
+ When a source returns `asset-parse-failed`, branch on `.expected`, `.hint`, and
303
+ `.detail` rather than `.message`. For a stale revision, keep the last verified
304
+ catalog, repair or re-fetch the producer revision, and enumerate again.
305
+
306
+ Subscribe before the first enumeration to avoid the consumer-side missed-event
307
+ window. If a consumer subscribed late or needs to resynchronise after a
308
+ transport interruption, enumerate again and merge the returned complete rows
309
+ by GUID. A static source may safely return an idempotent no-op unsubscribe: it
310
+ has no continuing change transport and must not manufacture deltas.
311
+
312
+ ## Error model
313
+
314
+ `AssetRuntimeErrorCode` is the package's closed error-code SSOT (exhaustive
315
+ `switch (err.code)` without `default`; TS guards completeness). Read the source,
316
+ don't duplicate the member list — `packages/assets-runtime/src/errors/asset.ts`
317
+ (grep `export type AssetRuntimeErrorCode`). Each error object carries
318
+ `.code` / `.expected` / `.hint` / `.detail`. `RendererError` (in
319
+ `@forgeax/engine-runtime`) composes `AssetRuntimeError` into its onError fan-out
320
+ union, so a dropped arm is a compile error.
321
+
322
+ ## Dependencies
323
+
324
+ `@forgeax/engine-{codec, ecs, geometry, graphics-extras, image, pack, rhi, shader, types}`.
325
+ Never imports `@forgeax/engine-runtime` or an audio backend: runtime injects the
326
+ post-spawn hook and concrete audio catalog-entry loader at `createRenderer`.
327
+
328
+ ## Runtime image bytes decoder (`decodeImageBytes`)
329
+
330
+ `decodeImageBytes(bytes, mime, opts?)` is the runtime SDK entry for AI users
331
+ who already hold image bytes in memory (fetched from a URL, embedded as
332
+ base64, produced by an out-of-tree decoder, etc.) and want to feed them into
333
+ `world.allocSharedRef('TextureAsset', pod)` + `GpuResourceStore.ensureResident`
334
+ without the disk-side importer / pack build pipeline in the loop. It is the
335
+ runtime counterpart to the build-time `.bin` / `.ktx2` texture loaders --
336
+ those stay authoritative for shipped assets; `decodeImageBytes` covers the
337
+ "bytes only exist at runtime" case that static loaders cannot serve
338
+ (tweak-20260714).
339
+
340
+ ### Signature
341
+
342
+ ```ts
343
+ export async function decodeImageBytes(
344
+ bytes: Uint8Array | ArrayBuffer,
345
+ mime: string,
346
+ opts?: { colorSpace?: 'srgb' | 'linear'; mipmap?: boolean },
347
+ ): Promise<Result<TextureAsset, ImageError>>;
348
+ ```
349
+
350
+ - `bytes` -- encoded image byte stream (PNG or JPEG). Both `Uint8Array` and
351
+ `ArrayBuffer` accepted; the function does not take ownership.
352
+ - `mime` -- byte-stream mime type. v1 whitelist: `'image/png' | 'image/jpeg'`
353
+ (see boundaries below).
354
+ - `opts.colorSpace` -- `'srgb'` (default) or `'linear'`. Derives POD `format`:
355
+ `srgb -> 'rgba8unorm-srgb'`, `linear -> 'rgba8unorm'` (mirrors the
356
+ build-time `packages/image/src/image-importer.ts` `colorSpaceToFormat`
357
+ rule -- one SSOT, no drift).
358
+ - `opts.mipmap` -- `true` (default) or `false`. When `true`, `mipLevelCount`
359
+ is computed by the existing `numMipLevels({ width, height })`; when
360
+ `false`, `mipLevelCount === 1`.
361
+
362
+ ### v1 boundaries (explicit non-goals)
363
+
364
+ The function is intentionally a thin bridge from bytes to a `TextureAsset`
365
+ POD. What it does NOT do:
366
+
367
+ - **No network I/O.** `decodeImageBytes` never `fetch`es; the caller supplies
368
+ bytes.
369
+ - **No GPU upload.** The POD is fed into the existing
370
+ `world.allocSharedRef('TextureAsset', pod)` +
371
+ `GpuResourceStore.ensureResident` path -- the upload primitives are not
372
+ duplicated or replaced.
373
+ - **v1 supports PNG / JPEG only.** GIF / WebP / SVG / AVIF / KTX2 / HDR
374
+ (`.hdr`) fall to `image-format-unsupported`; convert offline (or
375
+ reach for the build-time importer, which handles a wider set) rather
376
+ than expanding this API's mime table.
377
+ - **Not a replacement for the static texture loader.** Shipped `.bin` /
378
+ `.ktx2` continue to flow through the pack pipeline (`loadByGuid`); this
379
+ API only covers the runtime-only-bytes case (progressive disclosure --
380
+ AI user sees the smaller, more focused surface).
381
+ - **Not a Node / server-side decoder.** Requires an environment with
382
+ `createImageBitmap` + `OffscreenCanvas` (browser main thread or Worker).
383
+ Missing capability surfaces as a structured `image-decode-failed` error
384
+ (never a silent broken POD).
385
+
386
+ ### Error codes (closed union subset)
387
+
388
+ `decodeImageBytes` only ever produces the four base `ImageErrorCode` members
389
+ listed here; the other atlas / HDR members of the union are not reachable
390
+ from this API. Every error object carries `.code` / `.expected` /
391
+ `.hint` / `.detail`; `.detail` narrows per `.code` (discriminated union).
392
+ Read the source, do not duplicate the member list --
393
+ `packages/types/src/index.ts` (grep `export type ImageErrorCode`).
394
+
395
+ | code | trigger | `.detail` narrows to |
396
+ |:--|:--|:--|
397
+ | `image-format-unsupported` | mime not in `['image/png', 'image/jpeg']` | `{ actualMime, path?, formatColorSpaceConflict? }` |
398
+ | `image-decode-failed` | decoder rejected bytes, or env lacks `createImageBitmap` | `{ reason, path? }` |
399
+ | `image-dimension-out-of-bounds` | reserved; transparent pass-through if the underlying decoder ever surfaces it | `{ requested: {width,height}, limit }` |
400
+ | `image-meta-missing` | reserved; not raised by this API in v1 (kept in the union for a single grep-discoverable SSOT) | `{ sourcePath, expectedSidecarPath }` |
401
+
402
+ ### Error self-recovery paradigm
403
+
404
+ Structured errors with copy-pastable hints -- AI users consume via property
405
+ access, never by parsing `.message` (charter P3 explicit failure + P4
406
+ consistent abstraction; AGENTS.md Error model). Exhaustive `switch
407
+ (err.code)` needs no `default` -- TypeScript guards union completeness at
408
+ compile time, so future minor adds to `ImageErrorCode` surface as a
409
+ localised type error rather than a silent miss.
410
+
411
+ ```ts
412
+ import { decodeImageBytes } from '@forgeax/engine-assets-runtime';
413
+
414
+ const bytes = new Uint8Array(await (await fetch(url)).arrayBuffer());
415
+ const result = await decodeImageBytes(bytes, 'image/png');
416
+ if (!result.ok) {
417
+ const err = result.error;
418
+ // .hint carries an executable recovery instruction (see IMAGE_ERROR_HINTS
419
+ // SSOT in packages/types/src/index.ts); no string parsing needed.
420
+ console.error(err.code, err.hint);
421
+ // ImageError correlates the envelope code with its detail shape, so one
422
+ // discriminant drives both recovery routing and IDE narrowing.
423
+ switch (err.code) {
424
+ case 'image-format-unsupported':
425
+ // err.detail.actualMime -- rejected mime; convert offline
426
+ console.error('bad mime:', err.detail.actualMime);
427
+ break;
428
+ case 'image-decode-failed':
429
+ // err.detail.reason -- underlying decoder message (or "env lacks
430
+ // createImageBitmap" when the platform capability is missing)
431
+ console.error('decode reason:', err.detail.reason);
432
+ break;
433
+ case 'image-dimension-out-of-bounds':
434
+ console.error('too big:', err.detail.requested, err.detail.limit);
435
+ break;
436
+ case 'image-meta-missing':
437
+ console.error('missing sidecar:', err.detail.expectedSidecarPath);
438
+ break;
439
+ }
440
+ return;
441
+ }
442
+
443
+ // Bytes in, POD out -- charter P4 one abstraction, same POD shape as the
444
+ // build-time texture loader emits, so downstream does not care about the
445
+ // byte source (progressive disclosure: allocSharedRef + ensureResident is
446
+ // the same call site as static assets).
447
+ const handle = world.allocSharedRef('TextureAsset', result.value);
448
+ ```
449
+
450
+ ### Isolation gate boundary
451
+
452
+ `decode-image-bytes.ts` is the SINGLE file in `@forgeax/engine-assets-runtime`
453
+ allowed to statically import `@forgeax/engine-image`. The
454
+ `scripts/check-image-pipeline-isolation.mjs` (a.2-anti) rule pins this
455
+ exact path as its whitelist; the wider runtime and the rest of
456
+ assets-runtime remain gated so a future accidental static import falls
457
+ loud, not silent.
458
+
459
+ ## Route map
460
+
461
+ - Import images / glTF / fonts, wire `loadByGuid`, author sidecars: skill
462
+ `forgeax-engine-assets`.
463
+ - Full asset-chain narrative (sidecar -> import -> pack-index -> loadByGuid):
464
+ `packages/pack/README.md` + `forgeax-engine-assets/README.md`.
465
+ - Runtime image bytes decoding (this package, runtime-only-bytes case):
466
+ see the `decodeImageBytes` section above.
467
+ # Static asset evidence
468
+
469
+ > [!IMPORTANT]
470
+ > Static assets load from Pack v2 through `packageUrl`. Runtime-only bytes are the separate exception.
471
+
472
+ `AssetEvidence` joins `packageUrl`, cook freshness, artifact verification, and optional runtime state. Use `lookup/verify --guid --project --catalog --json` or the SDK `inspect(guid)` / `verifyByGuid(guid)` surface. `notCooked`, `stale`, and `unknown` are distinct recovery states; `unknown` is not verification success.
473
+
474
+ ## Indexed consumer recovery
475
+
476
+ The runtime consumer follows a read-only handoff:
477
+
478
+ 1. **Inspect** with `assets.inspect(guid)` or `assets.verifyByGuid(guid)` and
479
+ branch on the structured result's `code`, `detail`, and evidence states.
480
+ 2. **Repair and rebuild or cold-cook** in the producer host. The runtime never
481
+ writes source Meta, Pack, DDC, receipts, or Catalog authority.
482
+ 3. **Verify** source freshness, receipt, package integrity, and artifact
483
+ status through the injected evidence source or Pack CLI.
484
+ 4. **Retry** the same GUID after a verified Catalog projection is available.
485
+ Do not parse log strings, compile raw source in the player, or substitute a
486
+ custom mesh/material for a missing engine product.
487
+
488
+ `AssetRegistry` consumes a producer projection and exposes structured not-ready
489
+ errors. It does not register importers, run `runImport`, own DDC lifecycle, or
490
+ expand the runtime transport surface. `unknown` means that required evidence
491
+ was unavailable; it is never a successful verification result.