@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
@@ -0,0 +1,177 @@
1
+ import {
2
+ createMaterialError,
3
+ err,
4
+ type MaterialErrorFor,
5
+ type MaterialGenerationVector,
6
+ ok,
7
+ type Result,
8
+ } from '@forgeax/engine-types';
9
+
10
+ export type { MaterialGenerationVector } from '@forgeax/engine-types';
11
+
12
+ export interface MaterialCachedArtifact {
13
+ readonly bytes: Uint8Array;
14
+ }
15
+
16
+ export type MaterialStaleGenerationError =
17
+ MaterialErrorFor<'material-specialization-stale-generation'>;
18
+
19
+ function sameVector(left: MaterialGenerationVector, right: MaterialGenerationVector): boolean {
20
+ const names = new Set([...Object.keys(left.dependencies), ...Object.keys(right.dependencies)]);
21
+ return [...names].every((name) => left.dependencies[name] === right.dependencies[name]);
22
+ }
23
+
24
+ export class MaterialGenerationCache {
25
+ readonly #resolved = new Map<string, Promise<unknown>>();
26
+ readonly #resolvedByMaterial = new Map<string, Set<string>>();
27
+ readonly #resolvedKeys = new Map<string, string>();
28
+ readonly #artifacts = new Map<string, MaterialCachedArtifact>();
29
+ readonly #generations = new Map<string, number>();
30
+ readonly #errors = new Map<string, MaterialStaleGenerationError>();
31
+ readonly #materialDependencies = new Map<string, readonly string[]>();
32
+ readonly #dependents = new Map<string, Set<string>>();
33
+
34
+ resolve<T>(
35
+ materialGuid: string,
36
+ specializationKey: string,
37
+ load: () => Promise<T>,
38
+ publicationGeneration = 0,
39
+ ): Promise<T> {
40
+ const cacheKey = `${materialGuid}:${specializationKey}:${publicationGeneration}`;
41
+ const previous = this.#resolved.get(cacheKey);
42
+ if (previous !== undefined) return previous as Promise<T>;
43
+ const promise = load();
44
+ this.#resolved.set(cacheKey, promise);
45
+ const resolvedKeys = this.#resolvedByMaterial.get(materialGuid) ?? new Set<string>();
46
+ resolvedKeys.add(cacheKey);
47
+ this.#resolvedByMaterial.set(materialGuid, resolvedKeys);
48
+ void promise.then(
49
+ (value) => {
50
+ if (isStaleGenerationResult(value)) this.removeResolved(materialGuid, cacheKey, promise);
51
+ },
52
+ () => this.removeResolved(materialGuid, cacheKey, promise),
53
+ );
54
+ return promise;
55
+ }
56
+
57
+ linkResolved(materialGuid: string, specializationKey: string): void {
58
+ this.#resolvedKeys.set(materialGuid, specializationKey);
59
+ }
60
+
61
+ getResolvedKey(materialGuid: string): string | undefined {
62
+ return this.#resolvedKeys.get(materialGuid);
63
+ }
64
+
65
+ storeArtifact(key: string, artifact: MaterialCachedArtifact): void {
66
+ this.#artifacts.set(key, artifact);
67
+ }
68
+
69
+ getArtifact(key: string): MaterialCachedArtifact | undefined {
70
+ return this.#artifacts.get(key);
71
+ }
72
+
73
+ bump(dependency: string): number {
74
+ const generation = (this.#generations.get(dependency) ?? 0) + 1;
75
+ this.#generations.set(dependency, generation);
76
+ for (const materialGuid of this.#dependents.get(dependency) ?? []) {
77
+ for (const cacheKey of this.#resolvedByMaterial.get(materialGuid) ?? []) {
78
+ this.#resolved.delete(cacheKey);
79
+ }
80
+ this.#resolvedByMaterial.delete(materialGuid);
81
+ }
82
+ return generation;
83
+ }
84
+
85
+ generationError(materialGuid: string): MaterialStaleGenerationError | undefined {
86
+ return this.#errors.get(materialGuid);
87
+ }
88
+
89
+ async loadWithGeneration<T>(
90
+ materialGuid: string,
91
+ dependencies: readonly string[],
92
+ load: (
93
+ generation: MaterialGenerationVector,
94
+ ) => Promise<{ readonly generation: MaterialGenerationVector; readonly value: T }>,
95
+ ): Promise<Result<T, MaterialStaleGenerationError>> {
96
+ const dependencySet = Object.freeze([...dependencies]);
97
+ this.trackDependencies(materialGuid, dependencySet);
98
+ for (let attempt = 0; attempt < 2; attempt += 1) {
99
+ const generation = this.vector(dependencySet);
100
+ const loaded = await load(generation);
101
+ const observed = snapshotVector(loaded.generation);
102
+ const current = this.vector(dependencySet);
103
+ if (sameVector(observed, current)) {
104
+ this.#errors.delete(materialGuid);
105
+ return ok(loaded.value);
106
+ }
107
+ if (attempt === 1) {
108
+ const error = staleGenerationError(materialGuid, dependencySet, observed, current);
109
+ this.#errors.set(materialGuid, error);
110
+ return err(error);
111
+ }
112
+ }
113
+ const current = this.vector(dependencySet);
114
+ return err(staleGenerationError(materialGuid, dependencySet, current, current));
115
+ }
116
+
117
+ private trackDependencies(materialGuid: string, dependencies: readonly string[]): void {
118
+ const previous = this.#materialDependencies.get(materialGuid);
119
+ if (previous !== undefined) {
120
+ for (const dependency of previous) this.#dependents.get(dependency)?.delete(materialGuid);
121
+ }
122
+ this.#materialDependencies.set(materialGuid, dependencies);
123
+ for (const dependency of dependencies) {
124
+ const dependents = this.#dependents.get(dependency) ?? new Set<string>();
125
+ dependents.add(materialGuid);
126
+ this.#dependents.set(dependency, dependents);
127
+ }
128
+ }
129
+
130
+ private removeResolved(materialGuid: string, cacheKey: string, promise: Promise<unknown>): void {
131
+ if (this.#resolved.get(cacheKey) !== promise) return;
132
+ this.#resolved.delete(cacheKey);
133
+ const resolvedKeys = this.#resolvedByMaterial.get(materialGuid);
134
+ if (resolvedKeys === undefined) return;
135
+ resolvedKeys.delete(cacheKey);
136
+ if (resolvedKeys.size === 0) this.#resolvedByMaterial.delete(materialGuid);
137
+ }
138
+
139
+ private vector(dependencies: readonly string[]): MaterialGenerationVector {
140
+ return snapshotVector({
141
+ dependencies: Object.fromEntries(
142
+ dependencies.map((dependency) => [dependency, this.#generations.get(dependency) ?? 0]),
143
+ ),
144
+ });
145
+ }
146
+ }
147
+
148
+ function snapshotVector(vector: MaterialGenerationVector): MaterialGenerationVector {
149
+ return Object.freeze({ dependencies: Object.freeze({ ...vector.dependencies }) });
150
+ }
151
+
152
+ function staleGenerationError(
153
+ material: string,
154
+ dependencies: readonly string[],
155
+ observed: MaterialGenerationVector,
156
+ current: MaterialGenerationVector,
157
+ ): MaterialStaleGenerationError {
158
+ const detail = Object.freeze({
159
+ code: 'material-specialization-stale-generation' as const,
160
+ material,
161
+ dependencies,
162
+ observed,
163
+ current,
164
+ });
165
+ return Object.freeze(createMaterialError('material-specialization-stale-generation', detail));
166
+ }
167
+
168
+ function isStaleGenerationResult(value: unknown): boolean {
169
+ if (value === null || typeof value !== 'object') return false;
170
+ const result = value as { readonly ok?: unknown; readonly error?: unknown };
171
+ if (result.ok !== false || result.error === null || typeof result.error !== 'object')
172
+ return false;
173
+ return (
174
+ (result.error as { readonly code?: unknown }).code ===
175
+ 'material-specialization-stale-generation'
176
+ );
177
+ }
@@ -0,0 +1,46 @@
1
+ import type { MaterialReady } from './loader.js';
2
+
3
+ /**
4
+ * Read-only facts for one GUID after the MaterialReady gate succeeds.
5
+ * `status` is retained for the existing runtime API; `readiness` is the
6
+ * machine-readable first-level field for inspection consumers. This owner
7
+ * does not invent transport provenance or compare transport URLs.
8
+ */
9
+ export interface MaterialRuntimeInfo {
10
+ readonly materialGuid: string;
11
+ readonly readiness: 'ready';
12
+ readonly publicationGeneration: number;
13
+ readonly specializationKey: string;
14
+ readonly artifactDigest: string;
15
+ readonly layoutIdentity: string;
16
+ readonly dependencies: readonly string[];
17
+ readonly profile: string;
18
+ readonly sourceClosure: readonly string[];
19
+ readonly parameterContract: MaterialReady['parameterContract'];
20
+ readonly refs: MaterialReady['record']['refs'];
21
+ readonly receipt: MaterialReady['record']['receipt'];
22
+ readonly status: 'Ready';
23
+ }
24
+
25
+ export function inspectMaterialRuntime(ready: MaterialReady): MaterialRuntimeInfo {
26
+ return {
27
+ materialGuid: ready.materialGuid,
28
+ readiness: 'ready',
29
+ publicationGeneration: ready.publicationGeneration,
30
+ specializationKey: ready.specializationKey,
31
+ artifactDigest: ready.artifactDigest,
32
+ layoutIdentity: ready.record.receipt.layoutIdentity,
33
+ dependencies: [
34
+ ...ready.record.refs.parent,
35
+ ...ready.record.refs.textures,
36
+ ...ready.record.refs.samplers,
37
+ ...ready.record.refs.modules,
38
+ ],
39
+ profile: ready.record.receipt.profile,
40
+ sourceClosure: ready.sourceClosure,
41
+ parameterContract: ready.parameterContract,
42
+ refs: ready.record.refs,
43
+ receipt: ready.record.receipt,
44
+ status: 'Ready',
45
+ };
46
+ }
@@ -0,0 +1,326 @@
1
+ import {
2
+ type CookedMaterialRecord,
3
+ createMaterialArtifactDigest,
4
+ validateCookedMaterialRecord,
5
+ } from '@forgeax/engine-pack';
6
+
7
+ export interface MaterialLoadRequest {
8
+ readonly guid: string;
9
+ readonly specializationKey: string;
10
+ }
11
+
12
+ export interface MaterialReady {
13
+ readonly status: 'Ready';
14
+ readonly guid: string;
15
+ readonly materialGuid: string;
16
+ readonly publicationGeneration: number;
17
+ readonly specializationKey: string;
18
+ readonly artifactDigest: string;
19
+ readonly sourceClosure: readonly string[];
20
+ readonly parameterContract: NonNullable<CookedMaterialRecord['parameterContract']>;
21
+ readonly record: CookedMaterialRecord;
22
+ readonly artifact: CookedMaterialRecord['artifact'];
23
+ }
24
+
25
+ export interface MaterialPublication {
26
+ readonly guid: string;
27
+ readonly record: unknown;
28
+ readonly artifactError?: {
29
+ readonly code: 'asset-artifact-missing' | 'asset-artifact-integrity-mismatch';
30
+ readonly expected: string;
31
+ readonly actual?: string;
32
+ };
33
+ readonly artifact?: {
34
+ readonly bytes: Uint8Array;
35
+ readonly digest?: string;
36
+ };
37
+ }
38
+
39
+ export type MaterialLoadErrorCode =
40
+ | 'material-specialization-not-cooked'
41
+ | 'asset-artifact-missing'
42
+ | 'asset-artifact-integrity-mismatch'
43
+ | 'material-cook-record-invalid'
44
+ | 'material-reference-not-ready';
45
+
46
+ export interface MaterialLoadErrorDetail {
47
+ readonly guid: string;
48
+ readonly specializationKey: string;
49
+ readonly publicationGeneration?: number;
50
+ readonly field?: string;
51
+ readonly missing?: readonly string[];
52
+ readonly expected?: string;
53
+ readonly actual?: string;
54
+ }
55
+
56
+ export interface MaterialLoadError {
57
+ readonly status: 'Error';
58
+ readonly error: {
59
+ readonly code: MaterialLoadErrorCode;
60
+ readonly expected: string;
61
+ readonly hint: string;
62
+ readonly retryable: boolean;
63
+ readonly recoveryActions: readonly string[];
64
+ readonly detail: MaterialLoadErrorDetail;
65
+ };
66
+ }
67
+
68
+ export interface MaterialLoaderOptions {
69
+ readonly loadPublication: (
70
+ guid: string,
71
+ specializationKey: string,
72
+ ) => Promise<MaterialPublication | undefined>;
73
+ readonly loadReference?: (guid: string) => Promise<boolean>;
74
+ }
75
+
76
+ function materialError(
77
+ request: MaterialLoadRequest,
78
+ code: MaterialLoadErrorCode,
79
+ expected: string,
80
+ hint: string,
81
+ detail: Omit<MaterialLoadErrorDetail, 'guid' | 'specializationKey'> = {},
82
+ retryable = false,
83
+ ): MaterialLoadError {
84
+ return {
85
+ status: 'Error',
86
+ error: {
87
+ code,
88
+ expected,
89
+ hint,
90
+ retryable,
91
+ recoveryActions: retryable ? ['retry-material-load'] : ['recook-material-publication'],
92
+ detail: { guid: request.guid, specializationKey: request.specializationKey, ...detail },
93
+ },
94
+ };
95
+ }
96
+
97
+ function missingCook(request: MaterialLoadRequest): MaterialLoadError {
98
+ return materialError(
99
+ request,
100
+ 'material-specialization-not-cooked',
101
+ 'a cooked material specialization record and artifact',
102
+ 'run the build-time material cooker for this specialization before loading it at runtime',
103
+ {},
104
+ true,
105
+ );
106
+ }
107
+
108
+ function recordError(
109
+ request: MaterialLoadRequest,
110
+ field: string,
111
+ expected = 'a complete material-cook/2 publication record',
112
+ ): MaterialLoadError {
113
+ return materialError(
114
+ request,
115
+ 'material-cook-record-invalid',
116
+ expected,
117
+ 're-publish the material record and its artifact as one immutable publication',
118
+ { field },
119
+ );
120
+ }
121
+
122
+ function immutableBytes(bytes: Uint8Array): Uint8Array {
123
+ return new Uint8Array(bytes);
124
+ }
125
+
126
+ function immutableParameterContract(
127
+ parameterContract: NonNullable<CookedMaterialRecord['parameterContract']>,
128
+ ): NonNullable<CookedMaterialRecord['parameterContract']> {
129
+ return Object.freeze({
130
+ parameters: Object.freeze([...parameterContract.parameters]),
131
+ values: Object.freeze({ ...parameterContract.values }),
132
+ });
133
+ }
134
+
135
+ function sameBytes(left: Uint8Array, right: Uint8Array): boolean {
136
+ return left.byteLength === right.byteLength && left.every((byte, index) => byte === right[index]);
137
+ }
138
+
139
+ function completeTupleField(record: CookedMaterialRecord): string | undefined {
140
+ if (
141
+ typeof record.materialGuid !== 'string' ||
142
+ !Number.isSafeInteger(record.publicationGeneration) ||
143
+ (record.publicationGeneration as number) < 1
144
+ )
145
+ return 'publicationGeneration';
146
+ if (typeof record.specializationKey !== 'string' || record.specializationKey.length === 0)
147
+ return 'specializationKey';
148
+ if (typeof record.artifactDigest !== 'string' || record.artifactDigest.length === 0)
149
+ return 'artifactDigest';
150
+ if (
151
+ !Array.isArray(record.sourceClosure) ||
152
+ record.sourceClosure.some((path) => typeof path !== 'string' || path.length === 0)
153
+ )
154
+ return 'sourceClosure';
155
+ const parameterContract = record.parameterContract;
156
+ if (
157
+ parameterContract === undefined ||
158
+ !Array.isArray(parameterContract.parameters) ||
159
+ parameterContract.values === null ||
160
+ typeof parameterContract.values !== 'object' ||
161
+ Array.isArray(parameterContract.values)
162
+ )
163
+ return 'parameterContract';
164
+ const receipt = record.receipt;
165
+ if (
166
+ !Array.isArray(receipt.sourceClosure) ||
167
+ receipt.sourceClosure.some((path) => typeof path !== 'string' || path.length === 0) ||
168
+ receipt.sourceClosure.length !== record.sourceClosure.length ||
169
+ receipt.sourceClosure.some((path, index) => path !== record.sourceClosure?.[index])
170
+ )
171
+ return 'receipt.sourceClosure';
172
+ if (
173
+ typeof receipt.profile !== 'string' ||
174
+ receipt.profile.length === 0 ||
175
+ typeof receipt.compilerVersion !== 'string' ||
176
+ receipt.compilerVersion.length === 0 ||
177
+ typeof receipt.inputDigest !== 'string' ||
178
+ receipt.inputDigest.length === 0 ||
179
+ receipt.outputDigest !== record.artifactDigest ||
180
+ receipt.publicationGeneration !== record.publicationGeneration
181
+ )
182
+ return 'receipt';
183
+ if (
184
+ typeof receipt.layoutIdentity !== 'string' ||
185
+ receipt.layoutIdentity.length === 0 ||
186
+ receipt.derivedInterface?.layoutIdentity !== receipt.layoutIdentity
187
+ )
188
+ return 'receipt.layoutIdentity';
189
+ if (
190
+ typeof record.artifact.mediaType !== 'string' ||
191
+ record.artifact.mediaType.length === 0 ||
192
+ typeof record.artifact.path !== 'string' ||
193
+ record.artifact.path.length === 0
194
+ )
195
+ return 'artifact';
196
+ return undefined;
197
+ }
198
+
199
+ export function createMaterialLoader(options: MaterialLoaderOptions) {
200
+ return {
201
+ async load(request: MaterialLoadRequest): Promise<MaterialReady | MaterialLoadError> {
202
+ const publication = await options.loadPublication(request.guid, request.specializationKey);
203
+ if (publication === undefined) return missingCook(request);
204
+ if (publication.artifactError !== undefined) {
205
+ return materialError(
206
+ request,
207
+ publication.artifactError.code,
208
+ publication.artifactError.expected,
209
+ 'restore the published material artifact and retry the load',
210
+ {
211
+ expected: publication.artifactError.expected,
212
+ ...(publication.artifactError.actual === undefined
213
+ ? {}
214
+ : { actual: publication.artifactError.actual }),
215
+ },
216
+ true,
217
+ );
218
+ }
219
+ const parsed = validateCookedMaterialRecord(publication.record);
220
+ if (!parsed.ok) return recordError(request, parsed.error.detail.field);
221
+ const record = parsed.value;
222
+ const invalidTupleField = completeTupleField(record);
223
+ if (invalidTupleField !== undefined) return recordError(request, invalidTupleField);
224
+ const publicationGeneration = record.publicationGeneration;
225
+ const sourceClosure = record.sourceClosure;
226
+ const parameterContract = record.parameterContract;
227
+ const materialGuid = record.materialGuid;
228
+ const recordSpecializationKey = record.specializationKey;
229
+ const artifactDigest = record.artifactDigest;
230
+ if (publicationGeneration === undefined) return recordError(request, 'publicationGeneration');
231
+ if (sourceClosure === undefined) return recordError(request, 'sourceClosure');
232
+ if (parameterContract === undefined) return recordError(request, 'parameterContract');
233
+ if (materialGuid === undefined) return recordError(request, 'materialGuid');
234
+ if (recordSpecializationKey === undefined) return recordError(request, 'specializationKey');
235
+ if (artifactDigest === undefined) return recordError(request, 'artifactDigest');
236
+ if (
237
+ publication.guid.toLowerCase() !== request.guid.toLowerCase() ||
238
+ record.guid.toLowerCase() !== request.guid.toLowerCase()
239
+ ) {
240
+ return recordError(
241
+ request,
242
+ 'guid',
243
+ `record GUID ${request.guid} to match the requested GUID`,
244
+ );
245
+ }
246
+ if (materialGuid.toLowerCase() !== request.guid.toLowerCase())
247
+ return recordError(request, 'materialGuid');
248
+ if (recordSpecializationKey !== request.specializationKey) return missingCook(request);
249
+ if (publication.artifact === undefined) {
250
+ return materialError(
251
+ request,
252
+ 'asset-artifact-missing',
253
+ `published artifact for material ${request.guid}`,
254
+ 'publish the immutable material artifact before loading the specialization',
255
+ { publicationGeneration },
256
+ true,
257
+ );
258
+ }
259
+ const artifactBytes = immutableBytes(publication.artifact.bytes);
260
+ const actualDigest = createMaterialArtifactDigest(artifactBytes);
261
+ if (
262
+ actualDigest !== artifactDigest ||
263
+ (publication.artifact.digest !== undefined && publication.artifact.digest !== actualDigest)
264
+ ) {
265
+ return materialError(
266
+ request,
267
+ 'asset-artifact-integrity-mismatch',
268
+ `artifact digest ${artifactDigest}`,
269
+ 'restore the published artifact bytes or re-publish the matching material record',
270
+ {
271
+ publicationGeneration,
272
+ expected: artifactDigest,
273
+ actual: actualDigest,
274
+ },
275
+ );
276
+ }
277
+ if (!sameBytes(record.artifact.bytes, artifactBytes)) {
278
+ return recordError(
279
+ request,
280
+ 'artifact.bytes',
281
+ 'record artifact bytes to match the published artifact bytes',
282
+ );
283
+ }
284
+ const refs = [
285
+ ...record.refs.parent,
286
+ ...record.refs.textures,
287
+ ...record.refs.samplers,
288
+ ...record.refs.modules,
289
+ ];
290
+ const missing = options.loadReference
291
+ ? (
292
+ await Promise.all(
293
+ refs.map(async (reference) =>
294
+ (await options.loadReference?.(reference)) ? undefined : reference,
295
+ ),
296
+ )
297
+ ).filter((reference): reference is string => reference !== undefined)
298
+ : [];
299
+ if (missing.length > 0) {
300
+ return materialError(
301
+ request,
302
+ 'material-reference-not-ready',
303
+ 'all cooked material references to be available',
304
+ 'load referenced parent, texture, sampler, and module assets before publishing Ready',
305
+ { publicationGeneration, missing },
306
+ true,
307
+ );
308
+ }
309
+ return {
310
+ status: 'Ready',
311
+ guid: request.guid,
312
+ materialGuid,
313
+ publicationGeneration,
314
+ specializationKey: request.specializationKey,
315
+ artifactDigest,
316
+ sourceClosure: Object.freeze([...sourceClosure]),
317
+ parameterContract: immutableParameterContract(parameterContract),
318
+ record,
319
+ artifact: Object.freeze({
320
+ ...record.artifact,
321
+ bytes: artifactBytes,
322
+ }),
323
+ };
324
+ },
325
+ };
326
+ }
@@ -0,0 +1,133 @@
1
+ import type { ShaderRegistry } from '@forgeax/engine-shader';
2
+ import type { MaterialParameter, ParamSchemaEntry } from '@forgeax/engine-types';
3
+ import type { MaterialReady } from './loader';
4
+
5
+ const ENGINE_INJECTED_TEXTURE_FIELDS = new Set(['emissiveTexture', 'occlusionTexture']);
6
+
7
+ function isStandardPbrMaterialShader(shaderId: string): boolean {
8
+ return (
9
+ shaderId === 'forgeax::default-standard-pbr' ||
10
+ shaderId === 'forgeax::pbr-skin' ||
11
+ shaderId === 'forgeax::default-standard-pbr-skin'
12
+ );
13
+ }
14
+
15
+ /** Project authored MaterialAsset parameters into the runtime shader schema. */
16
+ export function materialParametersToParamSchema(
17
+ parameters: readonly MaterialParameter[],
18
+ shaderId: string | undefined,
19
+ ): readonly ParamSchemaEntry[] {
20
+ const engineInjectedTextureFields =
21
+ shaderId !== undefined && isStandardPbrMaterialShader(shaderId)
22
+ ? ENGINE_INJECTED_TEXTURE_FIELDS
23
+ : undefined;
24
+ return parameters.flatMap((parameter): ParamSchemaEntry[] => {
25
+ if (parameter.static || parameter.type === 'bool') return [];
26
+ if (parameter.type === 'texture') {
27
+ if (engineInjectedTextureFields?.has(parameter.name) === true) return [];
28
+ return [{ name: parameter.name, type: 'texture2d' }];
29
+ }
30
+ const defaultValue = parameter.default;
31
+ const numericDefault =
32
+ typeof defaultValue === 'number' ||
33
+ (Array.isArray(defaultValue) && defaultValue.every((item) => typeof item === 'number'))
34
+ ? { default: defaultValue }
35
+ : {};
36
+ return [
37
+ {
38
+ name: parameter.name,
39
+ type: parameter.type,
40
+ ...(parameter.colorSpace === undefined ? {} : { colorSpace: parameter.colorSpace }),
41
+ ...numericDefault,
42
+ },
43
+ ];
44
+ });
45
+ }
46
+
47
+ /** Project authored pass module ids onto the renderer's canonical ids. */
48
+ export function runtimeMaterialShaderId(
49
+ module: string | undefined,
50
+ passName?: string,
51
+ ): string | undefined {
52
+ if (
53
+ passName === 'shadow-caster' &&
54
+ (module === 'forgeax_material::standard' ||
55
+ module === 'forgeax_material::unlit' ||
56
+ module === 'forgeax::default-standard-pbr' ||
57
+ module === 'forgeax::default-unlit')
58
+ ) {
59
+ return 'forgeax::default-shadow-caster';
60
+ }
61
+ switch (module) {
62
+ case 'forgeax_material::standard':
63
+ return 'forgeax::default-standard-pbr';
64
+ case 'forgeax_material::unlit':
65
+ return 'forgeax::default-unlit';
66
+ case 'forgeax_material::sprite':
67
+ return 'forgeax::sprite';
68
+ case 'forgeax_material::sprite-lit':
69
+ return 'forgeax::sprite-lit';
70
+ default:
71
+ return module;
72
+ }
73
+ }
74
+
75
+ function sameSchema(
76
+ left: readonly ParamSchemaEntry[],
77
+ right: readonly ParamSchemaEntry[],
78
+ ): boolean {
79
+ return JSON.stringify(left) === JSON.stringify(right);
80
+ }
81
+
82
+ function isManifestVariantSource(
83
+ shaderRegistry: ShaderRegistry,
84
+ identifier: string,
85
+ source: string,
86
+ ): boolean {
87
+ const entry = Array.from(shaderRegistry.materialShaderManifestEntries()).find(
88
+ (candidate) => candidate.identifier === identifier,
89
+ );
90
+ return (
91
+ entry?.composedWgsl === source ||
92
+ entry?.variants.some((variant) => variant.composedWgsl === source) === true
93
+ );
94
+ }
95
+
96
+ /**
97
+ * Install every authored pass module from one validated MaterialReady tuple.
98
+ * The publication artifact is the only runtime shader source; no host entry
99
+ * point is allowed to assemble or register the material separately.
100
+ */
101
+ export function installMaterialReadyShaders(
102
+ shaderRegistry: ShaderRegistry,
103
+ readiness: MaterialReady,
104
+ ): void {
105
+ const source = new TextDecoder().decode(readiness.artifact.bytes);
106
+ if (source.length === 0) {
107
+ throw new Error(
108
+ `MaterialReady ${readiness.guid} published an empty ${readiness.artifact.mediaType} shader artifact`,
109
+ );
110
+ }
111
+ for (const pass of readiness.record.resolved.passes) {
112
+ const identifier = runtimeMaterialShaderId(pass.program.module, pass.name);
113
+ if (identifier === undefined) continue;
114
+ const paramSchema = materialParametersToParamSchema(
115
+ readiness.parameterContract.parameters,
116
+ identifier,
117
+ );
118
+ const existing = shaderRegistry.findMaterialArtifact(identifier);
119
+ if (existing.ok) {
120
+ if (
121
+ !sameSchema(existing.value.paramSchema, paramSchema) ||
122
+ (existing.value.source !== source &&
123
+ !isManifestVariantSource(shaderRegistry, identifier, source))
124
+ ) {
125
+ throw new Error(
126
+ `MaterialReady ${readiness.guid} conflicts with the registered shader '${identifier}'`,
127
+ );
128
+ }
129
+ continue;
130
+ }
131
+ shaderRegistry.installMaterialArtifact(identifier, { source, paramSchema });
132
+ }
133
+ }
@@ -0,0 +1,5 @@
1
+ export { MeshBinAssetError } from './errors/asset.js';
2
+ export {
3
+ type UnpackedMeshBin,
4
+ unpackMeshBinV4,
5
+ } from './loaders/mesh-bin.js';