@forgeax/engine-assets-runtime 0.1.4 → 0.1.6

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