@forgeax/engine-runtime 0.1.3 → 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 (606) hide show
  1. package/README.md +49 -83
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/__tests__/animation-graph-collect-roundtrip.test.d.ts +2 -0
  4. package/dist/__tests__/animation-graph-collect-roundtrip.test.d.ts.map +1 -0
  5. package/dist/__tests__/animation-graph-serialize.unit.test.d.ts +2 -0
  6. package/dist/__tests__/animation-graph-serialize.unit.test.d.ts.map +1 -0
  7. package/dist/__tests__/asset-brand-and-name-retirement.test.d.ts +2 -0
  8. package/dist/__tests__/asset-brand-and-name-retirement.test.d.ts.map +1 -0
  9. package/dist/__tests__/asset-error-exhaustive.test-d.d.ts +11 -0
  10. package/dist/__tests__/asset-error-exhaustive.test-d.d.ts.map +1 -0
  11. package/dist/__tests__/asset-registry-guid-reverse.test.d.ts +2 -0
  12. package/dist/__tests__/asset-registry-guid-reverse.test.d.ts.map +1 -0
  13. package/dist/__tests__/asset-registry-hdr-equirect.test.d.ts +2 -0
  14. package/dist/__tests__/asset-registry-hdr-equirect.test.d.ts.map +1 -0
  15. package/dist/__tests__/asset-registry-mesh-pack-payload.dawn.test.d.ts +2 -0
  16. package/dist/__tests__/asset-registry-mesh-pack-payload.dawn.test.d.ts.map +1 -0
  17. package/dist/__tests__/asset-registry-mounts.test.d.ts +2 -0
  18. package/dist/__tests__/asset-registry-mounts.test.d.ts.map +1 -0
  19. package/dist/__tests__/asset-registry.browser.test.d.ts +2 -0
  20. package/dist/__tests__/asset-registry.browser.test.d.ts.map +1 -0
  21. package/dist/__tests__/asset-registry.dawn.test.d.ts +2 -0
  22. package/dist/__tests__/asset-registry.dawn.test.d.ts.map +1 -0
  23. package/dist/__tests__/asset-registry.listCatalog.test.d.ts +2 -0
  24. package/dist/__tests__/asset-registry.listCatalog.test.d.ts.map +1 -0
  25. package/dist/__tests__/asset-registry.mesh-bin.test.d.ts +2 -0
  26. package/dist/__tests__/asset-registry.mesh-bin.test.d.ts.map +1 -0
  27. package/dist/__tests__/asset-registry.pack-cache.test.d.ts +2 -0
  28. package/dist/__tests__/asset-registry.pack-cache.test.d.ts.map +1 -0
  29. package/dist/__tests__/asset-registry.raw-container-failfast.test.d.ts +2 -0
  30. package/dist/__tests__/asset-registry.raw-container-failfast.test.d.ts.map +1 -0
  31. package/dist/__tests__/asset-registry.recursive.spec.d.ts +2 -0
  32. package/dist/__tests__/asset-registry.recursive.spec.d.ts.map +1 -0
  33. package/dist/__tests__/asset-registry.unit.test.d.ts +2 -0
  34. package/dist/__tests__/asset-registry.unit.test.d.ts.map +1 -0
  35. package/dist/__tests__/asset.unit.test.d.ts +2 -0
  36. package/dist/__tests__/asset.unit.test.d.ts.map +1 -0
  37. package/dist/__tests__/audio-load-by-guid.unit.test.d.ts +2 -0
  38. package/dist/__tests__/audio-load-by-guid.unit.test.d.ts.map +1 -0
  39. package/dist/__tests__/backend-selection.integration.test.d.ts +2 -0
  40. package/dist/__tests__/backend-selection.integration.test.d.ts.map +1 -0
  41. package/dist/__tests__/basis-catalog-dispatch.integration.test.d.ts +2 -0
  42. package/dist/__tests__/basis-catalog-dispatch.integration.test.d.ts.map +1 -0
  43. package/dist/__tests__/builtin-asset-registry.unit.test.d.ts +2 -0
  44. package/dist/__tests__/builtin-asset-registry.unit.test.d.ts.map +1 -0
  45. package/dist/__tests__/collect-scene-asset.test.d.ts +1 -2
  46. package/dist/__tests__/collect-scene-asset.test.d.ts.map +1 -1
  47. package/dist/__tests__/collect-scene-mount-collapse.test.d.ts +2 -0
  48. package/dist/__tests__/collect-scene-mount-collapse.test.d.ts.map +1 -0
  49. package/dist/__tests__/collect-transient-roundtrip.test.d.ts +1 -1
  50. package/dist/__tests__/collect-transient-roundtrip.test.d.ts.map +1 -1
  51. package/dist/__tests__/collect-transient-skip.test.d.ts +1 -2
  52. package/dist/__tests__/collect-transient-skip.test.d.ts.map +1 -1
  53. package/dist/__tests__/compressed-texture-physical-extent.integration.test.d.ts +2 -0
  54. package/dist/__tests__/compressed-texture-physical-extent.integration.test.d.ts.map +1 -0
  55. package/dist/__tests__/dawn/mesh-update-dawn.test.d.ts +2 -0
  56. package/dist/__tests__/dawn/mesh-update-dawn.test.d.ts.map +1 -0
  57. package/dist/__tests__/dawn/upload-texture.dawn.test.d.ts +2 -0
  58. package/dist/__tests__/dawn/upload-texture.dawn.test.d.ts.map +1 -0
  59. package/dist/__tests__/draw-published.d.ts +2 -2
  60. package/dist/__tests__/draw-published.d.ts.map +1 -1
  61. package/dist/__tests__/dynamic-texture-store.unit.test.d.ts +2 -0
  62. package/dist/__tests__/dynamic-texture-store.unit.test.d.ts.map +1 -0
  63. package/dist/__tests__/errors.unit.test.d.ts +2 -0
  64. package/dist/__tests__/errors.unit.test.d.ts.map +1 -0
  65. package/dist/__tests__/extract-skin-mismatch.unit.test.d.ts +2 -0
  66. package/dist/__tests__/extract-skin-mismatch.unit.test.d.ts.map +1 -0
  67. package/dist/__tests__/fetch-binary-decompress.test.d.ts +2 -0
  68. package/dist/__tests__/fetch-binary-decompress.test.d.ts.map +1 -0
  69. package/dist/__tests__/four-path-compression.integration.test.d.ts +2 -0
  70. package/dist/__tests__/four-path-compression.integration.test.d.ts.map +1 -0
  71. package/dist/__tests__/fullscreen-feature-host.dawn.test.d.ts +2 -0
  72. package/dist/__tests__/fullscreen-feature-host.dawn.test.d.ts.map +1 -0
  73. package/dist/__tests__/guid-for-asset.unit.test.d.ts +2 -0
  74. package/dist/__tests__/guid-for-asset.unit.test.d.ts.map +1 -0
  75. package/dist/__tests__/handle-migration.test.d.ts +2 -0
  76. package/dist/__tests__/handle-migration.test.d.ts.map +1 -0
  77. package/dist/__tests__/helpers/register-runtime-components.d.ts.map +1 -1
  78. package/dist/__tests__/host-custom-kind-contract.test-d.d.ts +2 -0
  79. package/dist/__tests__/host-custom-kind-contract.test-d.d.ts.map +1 -0
  80. package/dist/__tests__/host-custom-kind-contract.test.d.ts +2 -0
  81. package/dist/__tests__/host-custom-kind-contract.test.d.ts.map +1 -0
  82. package/dist/__tests__/inspect-name.test.d.ts +2 -0
  83. package/dist/__tests__/inspect-name.test.d.ts.map +1 -0
  84. package/dist/__tests__/instantiate-mount-deferred-wiring.test.d.ts +2 -0
  85. package/dist/__tests__/instantiate-mount-deferred-wiring.test.d.ts.map +1 -0
  86. package/dist/__tests__/loadbyguid-payload.unit.test.d.ts +2 -0
  87. package/dist/__tests__/loadbyguid-payload.unit.test.d.ts.map +1 -0
  88. package/dist/__tests__/loadbyguid-texture-intern.unit.test.d.ts +2 -0
  89. package/dist/__tests__/loadbyguid-texture-intern.unit.test.d.ts.map +1 -0
  90. package/dist/__tests__/loader-extract-graceful.test.d.ts +2 -0
  91. package/dist/__tests__/loader-extract-graceful.test.d.ts.map +1 -0
  92. package/dist/__tests__/loader-name-survival.test.d.ts +2 -0
  93. package/dist/__tests__/loader-name-survival.test.d.ts.map +1 -0
  94. package/dist/__tests__/mesh-bin-contract.test.d.ts +2 -0
  95. package/dist/__tests__/mesh-bin-contract.test.d.ts.map +1 -0
  96. package/dist/__tests__/mesh-bin-skin-roundtrip.unit.test.d.ts +2 -0
  97. package/dist/__tests__/mesh-bin-skin-roundtrip.unit.test.d.ts.map +1 -0
  98. package/dist/__tests__/mesh-bin.test.d.ts +2 -0
  99. package/dist/__tests__/mesh-bin.test.d.ts.map +1 -0
  100. package/dist/__tests__/mesh-loader-skin-contract.unit.test.d.ts +2 -0
  101. package/dist/__tests__/mesh-loader-skin-contract.unit.test.d.ts.map +1 -0
  102. package/dist/__tests__/new-kind-refs-contract.test.d.ts +2 -0
  103. package/dist/__tests__/new-kind-refs-contract.test.d.ts.map +1 -0
  104. package/dist/__tests__/package-promotion.test.d.ts +2 -0
  105. package/dist/__tests__/package-promotion.test.d.ts.map +1 -0
  106. package/dist/__tests__/package-rename.test.d.ts +2 -0
  107. package/dist/__tests__/package-rename.test.d.ts.map +1 -0
  108. package/dist/__tests__/recover-error-exhaustive.test-d.d.ts +1 -1
  109. package/dist/__tests__/recover-error-exhaustive.test-d.d.ts.map +1 -1
  110. package/dist/__tests__/render-error-exhaustive.test-d.d.ts +1 -1
  111. package/dist/__tests__/render-error-exhaustive.test-d.d.ts.map +1 -1
  112. package/dist/__tests__/render-feature-prepared-graphics.fixture.d.ts +10 -0
  113. package/dist/__tests__/render-feature-prepared-graphics.fixture.d.ts.map +1 -0
  114. package/dist/__tests__/render-system-extract.unit.test.d.ts +2 -0
  115. package/dist/__tests__/render-system-extract.unit.test.d.ts.map +1 -0
  116. package/dist/__tests__/renderer-test-utils.d.ts +10 -0
  117. package/dist/__tests__/renderer-test-utils.d.ts.map +1 -0
  118. package/dist/__tests__/renderer.test-d.d.ts +2 -0
  119. package/dist/__tests__/renderer.test-d.d.ts.map +1 -0
  120. package/dist/__tests__/resolve-asset-handle-err.test.d.ts +2 -0
  121. package/dist/__tests__/resolve-asset-handle-err.test.d.ts.map +1 -0
  122. package/dist/__tests__/resolve-asset-handle.unit.test.d.ts +2 -0
  123. package/dist/__tests__/resolve-asset-handle.unit.test.d.ts.map +1 -0
  124. package/dist/__tests__/resolveName-fallback.test.d.ts +2 -0
  125. package/dist/__tests__/resolveName-fallback.test.d.ts.map +1 -0
  126. package/dist/__tests__/resolveName-null-package.test.d.ts +2 -0
  127. package/dist/__tests__/resolveName-null-package.test.d.ts.map +1 -0
  128. package/dist/__tests__/resolveName-unified-entry.test.d.ts +2 -0
  129. package/dist/__tests__/resolveName-unified-entry.test.d.ts.map +1 -0
  130. package/dist/__tests__/resolveName.test.d.ts +2 -0
  131. package/dist/__tests__/resolveName.test.d.ts.map +1 -0
  132. package/dist/__tests__/roots-to-scene-asset.test.d.ts +1 -1
  133. package/dist/__tests__/roots-to-scene-asset.test.d.ts.map +1 -1
  134. package/dist/__tests__/roundtrip-primitive.test.d.ts +2 -0
  135. package/dist/__tests__/roundtrip-primitive.test.d.ts.map +1 -0
  136. package/dist/__tests__/scene-bare-guid-refs-instantiate.test.d.ts +2 -0
  137. package/dist/__tests__/scene-bare-guid-refs-instantiate.test.d.ts.map +1 -0
  138. package/dist/__tests__/scene-instance-authoring.integration.test.d.ts +2 -0
  139. package/dist/__tests__/scene-instance-authoring.integration.test.d.ts.map +1 -0
  140. package/dist/__tests__/scene-mount-fixed-point.test.d.ts +2 -0
  141. package/dist/__tests__/scene-mount-fixed-point.test.d.ts.map +1 -0
  142. package/dist/__tests__/scene-mount-roundtrip.test.d.ts +2 -0
  143. package/dist/__tests__/scene-mount-roundtrip.test.d.ts.map +1 -0
  144. package/dist/__tests__/scene-mount-wrapper-roundtrip.test.d.ts +2 -0
  145. package/dist/__tests__/scene-mount-wrapper-roundtrip.test.d.ts.map +1 -0
  146. package/dist/__tests__/scene-sugar-instantiate.test.d.ts +2 -0
  147. package/dist/__tests__/scene-sugar-instantiate.test.d.ts.map +1 -0
  148. package/dist/__tests__/serialize-mounts-roundtrip.test.d.ts +2 -0
  149. package/dist/__tests__/serialize-mounts-roundtrip.test.d.ts.map +1 -0
  150. package/dist/__tests__/serialize-scene-asset.test.d.ts +1 -1
  151. package/dist/__tests__/serialize-scene-asset.test.d.ts.map +1 -1
  152. package/dist/__tests__/template-game-default-api-contract.test.d.ts +2 -0
  153. package/dist/__tests__/template-game-default-api-contract.test.d.ts.map +1 -0
  154. package/dist/__tests__/tileset-asset-validate.test.d.ts +2 -0
  155. package/dist/__tests__/tileset-asset-validate.test.d.ts.map +1 -0
  156. package/dist/__tests__/transcode-fallback.unit.test.d.ts +2 -0
  157. package/dist/__tests__/transcode-fallback.unit.test.d.ts.map +1 -0
  158. package/dist/__tests__/transient-runtime-behavior.test.d.ts +2 -0
  159. package/dist/__tests__/transient-runtime-behavior.test.d.ts.map +1 -0
  160. package/dist/__tests__/video-loader-consumption.test.d.ts +2 -0
  161. package/dist/__tests__/video-loader-consumption.test.d.ts.map +1 -0
  162. package/dist/__tests__/video-loader-smoke.dawn.test.d.ts +2 -0
  163. package/dist/__tests__/video-loader-smoke.dawn.test.d.ts.map +1 -0
  164. package/dist/backend-selection.d.ts +13 -0
  165. package/dist/backend-selection.d.ts.map +1 -0
  166. package/dist/collect-scene-asset.d.ts +3 -3
  167. package/dist/collect-scene-asset.d.ts.map +1 -1
  168. package/dist/createRenderer.d.ts +14 -2
  169. package/dist/createRenderer.d.ts.map +1 -1
  170. package/dist/errors/environment.d.ts +1 -1
  171. package/dist/errors/environment.d.ts.map +1 -1
  172. package/dist/index.d.ts +19 -81
  173. package/dist/index.d.ts.map +1 -1
  174. package/dist/index.mjs +105 -108
  175. package/dist/index.mjs.map +1 -1
  176. package/dist/renderer-host.d.ts +9 -0
  177. package/dist/renderer-host.d.ts.map +1 -0
  178. package/dist/renderer-host.mjs +62 -0
  179. package/dist/renderer-host.mjs.map +1 -0
  180. package/dist/scene-instances/index.d.ts +2 -0
  181. package/dist/scene-instances/index.d.ts.map +1 -0
  182. package/dist/scene-utils/mount-override-fold.d.ts +1 -2
  183. package/dist/scene-utils/mount-override-fold.d.ts.map +1 -1
  184. package/dist/systems/sprite-animation-tick.d.ts +1 -1
  185. package/dist/systems/sprite-animation-tick.d.ts.map +1 -1
  186. package/dist/systems/transparent-sort.d.ts +1 -2
  187. package/dist/systems/transparent-sort.d.ts.map +1 -1
  188. package/dist/tilemap-register-validate.d.ts.map +1 -1
  189. package/package.json +34 -33
  190. package/src/__tests__/active-camera.unit.test.ts +19 -9
  191. package/src/__tests__/animation-graph-collect-roundtrip.test.ts +213 -0
  192. package/src/__tests__/animation-graph-serialize.unit.test.ts +171 -0
  193. package/src/__tests__/append-injection.test.ts +1 -1
  194. package/src/__tests__/asset-brand-and-name-retirement.test.ts +102 -0
  195. package/src/__tests__/asset-error-exhaustive.test-d.ts +62 -0
  196. package/src/__tests__/asset-registry-guid-reverse.test.ts +575 -0
  197. package/src/__tests__/asset-registry-hdr-equirect.test.ts +78 -0
  198. package/src/__tests__/asset-registry-mesh-pack-payload.dawn.test.ts +232 -0
  199. package/src/__tests__/asset-registry-mounts.test.ts +806 -0
  200. package/src/__tests__/asset-registry.browser.test.ts +81 -0
  201. package/src/__tests__/asset-registry.dawn.test.ts +94 -0
  202. package/src/__tests__/asset-registry.listCatalog.test.ts +170 -0
  203. package/src/__tests__/asset-registry.mesh-bin.test.ts +316 -0
  204. package/src/__tests__/asset-registry.pack-cache.test.ts +969 -0
  205. package/src/__tests__/asset-registry.raw-container-failfast.test.ts +225 -0
  206. package/src/__tests__/asset-registry.recursive.spec.ts +1397 -0
  207. package/src/__tests__/asset-registry.unit.test.ts +1030 -0
  208. package/src/__tests__/asset.unit.test.ts +7107 -0
  209. package/src/__tests__/audio-load-by-guid.unit.test.ts +126 -0
  210. package/src/__tests__/backend-selection.integration.test.ts +11 -0
  211. package/src/__tests__/basis-catalog-dispatch.integration.test.ts +320 -0
  212. package/src/__tests__/bind-group-cache-keying.test.ts +4 -4
  213. package/src/__tests__/block-upload-math.unit.test.ts +2 -2
  214. package/src/__tests__/builtin-asset-registry.unit.test.ts +112 -0
  215. package/src/__tests__/builtin-shader-register-e2e.test.ts +1 -1
  216. package/src/__tests__/cache-false-eviction.test.ts +2 -1
  217. package/src/__tests__/cache-isolation-matrix.test.ts +2 -1
  218. package/src/__tests__/camera-clear-snapshot-equivalence.unit.test.ts +5 -6
  219. package/src/__tests__/clamp-to-last.e2e.browser.test.ts +29 -19
  220. package/src/__tests__/collect-scene-asset.test.ts +49 -52
  221. package/src/__tests__/collect-scene-mount-collapse.test.ts +391 -0
  222. package/src/__tests__/collect-subtree.unit.test.ts +1 -2
  223. package/src/__tests__/collect-transient-children-absent.test.ts +24 -10
  224. package/src/__tests__/collect-transient-roundtrip.test.ts +44 -20
  225. package/src/__tests__/collect-transient-skip.test.ts +24 -13
  226. package/src/__tests__/components.test-d.ts +1 -6
  227. package/src/__tests__/components.unit.test.ts +20 -19
  228. package/src/__tests__/composite-skybox-cross-world.browser.test.ts +59 -27
  229. package/src/__tests__/compressed-texture-physical-extent.integration.test.ts +139 -0
  230. package/src/__tests__/create-renderer-assembly.integration.test.ts +42 -29
  231. package/src/__tests__/createRenderer-dispose.test.ts +35 -402
  232. package/src/__tests__/createRenderer.browser-no-webgpu.test.ts +25 -30
  233. package/src/__tests__/createRenderer.unit.test.ts +1 -1
  234. package/src/__tests__/dawn/hello-sprite-nineslice-section.dawn.test.ts +27 -63
  235. package/src/__tests__/dawn/ibl-precompute-readback.dawn.test.ts +56 -47
  236. package/src/__tests__/dawn/instances-per-instance-pbr.dawn.test.ts +49 -29
  237. package/src/__tests__/dawn/instances-shadow.dawn.test.ts +26 -15
  238. package/src/__tests__/dawn/instances-uniform-fallback.dawn.test.ts +13 -14
  239. package/src/__tests__/dawn/material-alpha.dawn.test.ts +23 -31
  240. package/src/__tests__/dawn/material-cooked-fixture.dawn.test.ts +0 -18
  241. package/src/__tests__/dawn/{mesh-update.dawn.test.ts → mesh-update-dawn.test.ts} +42 -134
  242. package/src/__tests__/dawn/mipmap-srgb-linear.dawn.test.ts +14 -7
  243. package/src/__tests__/dawn/per-entity-material-texture-isolation.dawn.test.ts +16 -28
  244. package/src/__tests__/dawn/sprite-nineslice-mesh-bind.dawn.test.ts +23 -39
  245. package/src/__tests__/dawn/upload-texture.dawn.test.ts +198 -0
  246. package/src/__tests__/dawn/user-handle-mesh-render.dawn.test.ts +12 -14
  247. package/src/__tests__/dawn/user-mesh-upload.dawn.test.ts +27 -22
  248. package/src/__tests__/dawn/zero-manifest-mesh-fail.dawn.test.ts +25 -28
  249. package/src/__tests__/dawn/zero-manifest-ready.dawn.test.ts +10 -11
  250. package/src/__tests__/demo-sortscope-migration.unit.test.ts +5 -5
  251. package/src/__tests__/derive-bgl-integration.test.ts +34 -44
  252. package/src/__tests__/derive-render-data-skin.unit.test.ts +1 -1
  253. package/src/__tests__/draw-published.ts +11 -4
  254. package/src/__tests__/dynamic-texture-store.unit.test.ts +190 -0
  255. package/src/__tests__/equirect-bc6h.integration.test.ts +144 -21
  256. package/src/__tests__/errors.unit.test.ts +1203 -0
  257. package/src/__tests__/extract-frames-graceful-degradation.test.ts +3 -7
  258. package/src/__tests__/extract-frames-merge.test.ts +35 -22
  259. package/src/__tests__/extract-record-no-hardcoded-texture-fields.test.ts +5 -16
  260. package/src/__tests__/extract-skin-mismatch.unit.test.ts +322 -0
  261. package/src/__tests__/fetch-binary-decompress.test.ts +356 -0
  262. package/src/__tests__/four-path-compression.integration.test.ts +395 -0
  263. package/src/__tests__/frame-targets-shadow-regression.unit.test.ts +1 -1
  264. package/src/__tests__/fullscreen-feature-host.dawn.test.ts +80 -0
  265. package/src/__tests__/fullscreen-post-bgl-depth.test.ts +4 -2
  266. package/src/__tests__/fullscreen-post-depth-params-arity.test.ts +3 -3
  267. package/src/__tests__/fullscreen-post-process-pass.dawn.test.ts +39 -23
  268. package/src/__tests__/fullscreen-post-process-pass.unit.test.ts +4 -2
  269. package/src/__tests__/fxaa-pixel-diff.dawn.test.ts +28 -17
  270. package/src/__tests__/fxaa-validation-and-resize.dawn.test.ts +29 -18
  271. package/src/__tests__/fxaa-zero-overhead.dawn.test.ts +16 -12
  272. package/src/__tests__/geometry.unit.test.ts +525 -159
  273. package/src/__tests__/glyph-tilemap-worldid.test.ts +1 -1
  274. package/src/__tests__/gpu-resource-cow-survivor.test.ts +6 -4
  275. package/src/__tests__/guid-for-asset.unit.test.ts +102 -0
  276. package/src/__tests__/handle-migration.test.ts +136 -0
  277. package/src/__tests__/helpers/register-runtime-components.ts +4 -2
  278. package/src/__tests__/helpers/register-scene-components.ts +1 -1
  279. package/src/__tests__/host-custom-kind-contract.test-d.ts +47 -0
  280. package/src/__tests__/host-custom-kind-contract.test.ts +578 -0
  281. package/src/__tests__/inspect-name.test.ts +139 -0
  282. package/src/__tests__/instances.test-d.ts +1 -1
  283. package/src/__tests__/instantiate-mount-deferred-wiring.test.ts +112 -0
  284. package/src/__tests__/light-casters-9-light.browser.test.ts +73 -76
  285. package/src/__tests__/light-gpu-byte-neutral.unit.test.ts +7 -10
  286. package/src/__tests__/light-snapshot.test-d.ts +2 -2
  287. package/src/__tests__/lights-vec-negative.unit.test.ts +1 -1
  288. package/src/__tests__/lights.unit.test.ts +16 -500
  289. package/src/__tests__/loadbyguid-payload.unit.test.ts +117 -0
  290. package/src/__tests__/loadbyguid-texture-intern.unit.test.ts +213 -0
  291. package/src/__tests__/loader-extract-graceful.test.ts +146 -0
  292. package/src/__tests__/loader-name-survival.test.ts +213 -0
  293. package/src/__tests__/m3-vec-negative.unit.test.ts +3 -2
  294. package/src/__tests__/material-texture-uv-scale.unit.test.ts +101 -31
  295. package/src/__tests__/materials.unit.test.ts +10 -27
  296. package/src/__tests__/mesh-bin-contract.test.ts +28 -0
  297. package/src/__tests__/mesh-bin-skin-roundtrip.unit.test.ts +149 -0
  298. package/src/__tests__/mesh-bin.test.ts +38 -0
  299. package/src/__tests__/mesh-loader-skin-contract.unit.test.ts +120 -0
  300. package/src/__tests__/mesh-ssbo-grow.unit.test.ts +3 -3
  301. package/src/__tests__/mip-gate.unit.test.ts +1 -1
  302. package/src/__tests__/msaa-sprite-pixel-diff.dawn.test.ts +29 -68
  303. package/src/__tests__/new-kind-refs-contract.test.ts +132 -0
  304. package/src/__tests__/node-shims.d.ts +4 -6
  305. package/src/__tests__/package-promotion.test.ts +88 -0
  306. package/src/__tests__/package-rename.test.ts +103 -0
  307. package/src/__tests__/pass-selector.unit.test.ts +2 -2
  308. package/src/__tests__/pbr-pipeline.unit.test.ts +14 -21
  309. package/src/__tests__/pipeline-builder-msaa.unit.test.ts +6 -9
  310. package/src/__tests__/pipeline-cache-keying.unit.test.ts +6 -9
  311. package/src/__tests__/pipeline-spec.test.ts +16 -177
  312. package/src/__tests__/pipeline.unit.test.ts +154 -1693
  313. package/src/__tests__/point-light-shadow.dawn.test.ts +33 -42
  314. package/src/__tests__/point-light-shadow.unit.test.ts +19 -60
  315. package/src/__tests__/post-process-error-codes.unit.test.ts +4 -1
  316. package/src/__tests__/post-process-params-component.unit.test.ts +1 -1
  317. package/src/__tests__/post-process-params-extract.unit.test.ts +2 -5
  318. package/src/__tests__/postprocess-camera-consistency.test.ts +4 -7
  319. package/src/__tests__/profiler-assembly.integration.test.ts +1 -1
  320. package/src/__tests__/propagate-transforms.dawn.test.ts +12 -11
  321. package/src/__tests__/record-worldid-isolation.test.ts +7 -29
  322. package/src/__tests__/recover-error-exhaustive.test-d.ts +1 -1
  323. package/src/__tests__/render-error-exhaustive.test-d.ts +81 -71
  324. package/src/__tests__/render-feature-assembly.test-d.ts +20 -9
  325. package/src/__tests__/render-feature-prepared-graphics-docs.unit.test.ts +8 -6
  326. package/src/__tests__/render-feature-prepared-graphics.browser.test.ts +60 -558
  327. package/src/__tests__/render-feature-prepared-graphics.dawn.test.ts +57 -544
  328. package/src/__tests__/render-feature-prepared-graphics.fixture.ts +104 -0
  329. package/src/__tests__/render-feature-prepared-graphics.integration.test.ts +40 -248
  330. package/src/__tests__/render-feature-prepared-graphics.regression.integration.test.ts +83 -340
  331. package/src/__tests__/render-feature-recover.integration.test.ts +9 -49
  332. package/src/__tests__/render-feature-runtime-surface.test-d.ts +17 -20
  333. package/src/__tests__/render-schema-consumer.integration.test.ts +20 -13
  334. package/src/__tests__/render-schema-owner-boundary.unit.test.ts +2 -6
  335. package/src/__tests__/render-system-cross-world-overlay-queue.test.ts +5 -5
  336. package/src/__tests__/render-system-extract-dispatch-ordering.test.ts +16 -31
  337. package/src/__tests__/render-system-extract.test.ts +36 -51
  338. package/src/__tests__/render-system-extract.unit.test.ts +360 -0
  339. package/src/__tests__/render-system-fold-bucket.unit.test.ts +3 -5
  340. package/src/__tests__/render-system-fold-fallback.unit.test.ts +6 -3
  341. package/src/__tests__/render-system-fold-material.unit.test.ts +3 -5
  342. package/src/__tests__/render-system-fold-metrics.unit.test.ts +5 -5
  343. package/src/__tests__/render-system-fold-modegate.unit.test.ts +6 -6
  344. package/src/__tests__/render-system-fold.dawn.test.ts +7 -5
  345. package/src/__tests__/render-system-mega.test.ts +292 -190
  346. package/src/__tests__/render-system-owner-split-contract.test.ts +10 -32
  347. package/src/__tests__/render-system-record-multi-material-textureview.test.ts +59 -45
  348. package/src/__tests__/render-system-record-per-submesh-transparency.test.ts +23 -29
  349. package/src/__tests__/render-system-record.test.ts +398 -235
  350. package/src/__tests__/render-system-record.unit.test.ts +25 -26
  351. package/src/__tests__/render-system-skin-bg.unit.test.ts +8 -12
  352. package/src/__tests__/renderer-consumer.test-d.ts +16 -3
  353. package/src/__tests__/renderer-health.unit.test.ts +26 -916
  354. package/src/__tests__/renderer-lifecycle.integration.test.ts +19 -6
  355. package/src/__tests__/renderer-recover.unit.test.ts +49 -1069
  356. package/src/__tests__/renderer-result.test-d.ts +19 -9
  357. package/src/__tests__/renderer-surface.unit.test.ts +3 -5
  358. package/src/__tests__/renderer-test-utils.ts +56 -0
  359. package/src/__tests__/renderer.test-d.ts +59 -0
  360. package/src/__tests__/resolve-asset-handle-err.test.ts +111 -0
  361. package/src/__tests__/resolve-asset-handle.unit.test.ts +64 -0
  362. package/src/__tests__/resolve-depth-only-view.test.ts +1 -1
  363. package/src/__tests__/resolveName-fallback.test.ts +64 -0
  364. package/src/__tests__/resolveName-null-package.test.ts +38 -0
  365. package/src/__tests__/resolveName-unified-entry.test.ts +65 -0
  366. package/src/__tests__/resolveName.test.ts +64 -0
  367. package/src/__tests__/rhi-null-command-flow.unit.test.ts +43 -261
  368. package/src/__tests__/rhi-null-renderer-lifecycle.unit.test.ts +41 -169
  369. package/src/__tests__/roots-to-scene-asset.test.ts +199 -93
  370. package/src/__tests__/roundtrip-primitive.test.ts +351 -0
  371. package/src/__tests__/scene-bare-guid-refs-instantiate.test.ts +123 -0
  372. package/src/__tests__/scene-equirect-roundtrip.test.ts +103 -16
  373. package/src/__tests__/scene-instance-authoring.integration.test.ts +8 -0
  374. package/src/__tests__/scene-instance-bridge.integration.test.ts +1 -1
  375. package/src/__tests__/scene-instance-component.test.ts +1 -1
  376. package/src/__tests__/scene-mount-fixed-point.test.ts +257 -0
  377. package/src/__tests__/scene-mount-roundtrip.test.ts +987 -0
  378. package/src/__tests__/scene-mount-wrapper-roundtrip.test.ts +202 -0
  379. package/src/__tests__/scene-sugar-instantiate.test.ts +324 -0
  380. package/src/__tests__/serialize-mounts-roundtrip.test.ts +533 -0
  381. package/src/__tests__/serialize-scene-asset.test.ts +219 -22
  382. package/src/__tests__/shadow-caster-empty-schema.test.ts +3 -3
  383. package/src/__tests__/shadow-csm-cascade-loadop.test.ts +37 -17
  384. package/src/__tests__/shadow-csm-component.test.ts +3 -5
  385. package/src/__tests__/shadow-csm-extract.test.ts +2 -100
  386. package/src/__tests__/shadow-csm-orthoz.test.ts +2 -16
  387. package/src/__tests__/shadow-csm-runtime-vary.dawn.test.ts +19 -14
  388. package/src/__tests__/shadow-csm-tile-consistency.test.ts +32 -26
  389. package/src/__tests__/shadow-csm-ubo.dawn.test.ts +8 -49
  390. package/src/__tests__/shadow-csm-ubo.test.ts +3 -25
  391. package/src/__tests__/shadow-csm-viewport.browser.test.ts +1 -1
  392. package/src/__tests__/shadow-fields-observable.dawn.test.ts +67 -56
  393. package/src/__tests__/shadow-m1.dawn.test.ts +8 -9
  394. package/src/__tests__/shadow-mesh-ssbo-order.test.ts +11 -27
  395. package/src/__tests__/skin-errors.unit.test.ts +1 -1
  396. package/src/__tests__/skin-extract-getarrayview-count.unit.test.ts +7 -5
  397. package/src/__tests__/skylight-lazy-projection.test.ts +36 -15
  398. package/src/__tests__/sparse-tag-scene-roundtrip.test.ts +5 -5
  399. package/src/__tests__/sprite-cullmode-flip.dawn.test.ts +47 -64
  400. package/src/__tests__/sprite-lit-bgl-byte-identical.test.ts +12 -11
  401. package/src/__tests__/sprite-lit-extract-branch.test.ts +1 -1
  402. package/src/__tests__/sprite-lit-pipeline-cache-key.test.ts +1 -1
  403. package/src/__tests__/ssao-bgl.test.ts +16 -9
  404. package/src/__tests__/ssao-buffers.test.ts +2 -2
  405. package/src/__tests__/ssao-data.test.ts +2 -2
  406. package/src/__tests__/ssao-errors.test.ts +1 -1
  407. package/src/__tests__/ssao-passes.test.ts +64 -392
  408. package/src/__tests__/ssao-pipelines.test.ts +1 -1
  409. package/src/__tests__/ssao-uniform-layout.test.ts +8 -6
  410. package/src/__tests__/stencil-outline-pixel.dawn.test.ts +25 -17
  411. package/src/__tests__/swap-chain-format.unit.test.ts +5 -7
  412. package/src/__tests__/systems.unit.test.ts +536 -1326
  413. package/src/__tests__/template-game-default-api-contract.test.ts +362 -0
  414. package/src/__tests__/text.unit.test.ts +56 -33
  415. package/src/__tests__/tilemap-childed-despawn.test.ts +8 -14
  416. package/src/__tests__/tilemap-chunk-extract.overhang-bounds.test.ts +8 -12
  417. package/src/__tests__/tilemap-chunk-extract.propagate-order.test.ts +2 -1
  418. package/src/__tests__/tilemap-chunk-extract.test.ts +13 -17
  419. package/src/__tests__/tilemap-dirty-rebuild.test.ts +7 -11
  420. package/src/__tests__/tilemap-material-cache.test.ts +8 -13
  421. package/src/__tests__/tilemap-multi-atlas.test.ts +12 -32
  422. package/src/__tests__/tilemap-owner-boundary.unit.test.ts +5 -2
  423. package/src/__tests__/tilemap-region-pack.test.ts +8 -12
  424. package/src/__tests__/tilemap-spawn-flip-pivot.test.ts +8 -12
  425. package/src/__tests__/tilemap-uv-inset.test.ts +11 -14
  426. package/src/__tests__/tilemap-validate.test.ts +1 -1
  427. package/src/__tests__/tileset-asset-validate.test.ts +638 -0
  428. package/src/__tests__/transcode-fallback.unit.test.ts +207 -0
  429. package/src/__tests__/transform-hierarchy-pixel-diff.dawn.test.ts +36 -28
  430. package/src/__tests__/transient-fields-collect.test.ts +28 -21
  431. package/src/__tests__/transient-runtime-behavior.test.ts +227 -0
  432. package/src/__tests__/transparent-sort-tilemap-sprite-interleave.test.ts +4 -4
  433. package/src/__tests__/variant-selection.unit.test.ts +1 -1
  434. package/src/__tests__/video-extract-bindgroup.dawn.test.ts +89 -136
  435. package/src/__tests__/video-loader-consumption.test.ts +86 -0
  436. package/src/__tests__/video-loader-smoke.dawn.test.ts +60 -0
  437. package/src/__tests__/view-ubo-layout.browser.test.ts +0 -22
  438. package/src/__tests__/world-entity-key.test.ts +1 -1
  439. package/src/__tests__/world-scene-despawn.test.ts +1 -1
  440. package/src/__tests__/world-scene-instantiate.test.ts +2 -36
  441. package/src/__tests__/world-scene-nested.test.ts +1 -1
  442. package/src/__tests__/world-scene-override.test.ts +1 -1
  443. package/src/backend-selection.ts +69 -0
  444. package/src/collect-scene-asset.ts +28 -23
  445. package/src/components/__tests__/camera.test-d.ts +1 -1
  446. package/src/components/__tests__/camera.test.ts +1 -1
  447. package/src/components/__tests__/sprite-animation.test-d.ts +2 -2
  448. package/src/components/__tests__/tile-layer.test-d.ts +1 -1
  449. package/src/components/__tests__/tile-layer.unit.test.ts +2 -2
  450. package/src/createRenderer.ts +24 -36
  451. package/src/errors/environment.ts +1 -1
  452. package/src/index.ts +47 -113
  453. package/src/renderer-host.ts +33 -0
  454. package/src/scene-instances/__tests__/post-spawn-resolve-joints.unit.test.ts +1 -1
  455. package/src/scene-instances/index.ts +4 -0
  456. package/src/scene-utils/mount-override-fold.ts +3 -7
  457. package/src/systems/__tests__/transparent-sort.bench.ts +3 -3
  458. package/src/systems/sprite-animation-tick.ts +1 -1
  459. package/src/systems/transparent-sort.ts +38 -38
  460. package/src/tilemap-register-validate.ts +4 -4
  461. package/dist/__tests__/bind-group-cache-binding.test.d.ts +0 -2
  462. package/dist/__tests__/bind-group-cache-binding.test.d.ts.map +0 -1
  463. package/dist/__tests__/bind-group-cache-frame.test.d.ts +0 -2
  464. package/dist/__tests__/bind-group-cache-frame.test.d.ts.map +0 -1
  465. package/dist/__tests__/dawn/mesh-update.dawn.test.d.ts +0 -2
  466. package/dist/__tests__/dawn/mesh-update.dawn.test.d.ts.map +0 -1
  467. package/dist/__tests__/dispatch-fullscreen-pass-composite-swapchain.dawn.test.d.ts +0 -2
  468. package/dist/__tests__/dispatch-fullscreen-pass-composite-swapchain.dawn.test.d.ts.map +0 -1
  469. package/dist/__tests__/dispatch-fullscreen-pass-custom-reads.dawn.test.d.ts +0 -2
  470. package/dist/__tests__/dispatch-fullscreen-pass-custom-reads.dawn.test.d.ts.map +0 -1
  471. package/dist/__tests__/dispatch-fullscreen-pass-depth-composite.dawn.test.d.ts +0 -2
  472. package/dist/__tests__/dispatch-fullscreen-pass-depth-composite.dawn.test.d.ts.map +0 -1
  473. package/dist/__tests__/dispatch-fullscreen-pass-params.unit.test.d.ts +0 -2
  474. package/dist/__tests__/dispatch-fullscreen-pass-params.unit.test.d.ts.map +0 -1
  475. package/dist/__tests__/fog-producer-manifest.d.ts +0 -131
  476. package/dist/__tests__/fog-producer-manifest.d.ts.map +0 -1
  477. package/dist/__tests__/fog-producer-pixel-probe.d.ts +0 -42
  478. package/dist/__tests__/fog-producer-pixel-probe.d.ts.map +0 -1
  479. package/dist/__tests__/fog-producer-pixel.browser.test.d.ts +0 -2
  480. package/dist/__tests__/fog-producer-pixel.browser.test.d.ts.map +0 -1
  481. package/dist/__tests__/fog-producer-pixel.dawn.test.d.ts +0 -2
  482. package/dist/__tests__/fog-producer-pixel.dawn.test.d.ts.map +0 -1
  483. package/dist/__tests__/fog-producer-real-fixture.d.ts +0 -5
  484. package/dist/__tests__/fog-producer-real-fixture.d.ts.map +0 -1
  485. package/dist/__tests__/fog-producer-vfx-effect.d.ts +0 -3
  486. package/dist/__tests__/fog-producer-vfx-effect.d.ts.map +0 -1
  487. package/dist/__tests__/fog-scene-asset.integration.test.d.ts +0 -2
  488. package/dist/__tests__/fog-scene-asset.integration.test.d.ts.map +0 -1
  489. package/dist/__tests__/gpu-resource-store-cubemap-status.test.d.ts +0 -2
  490. package/dist/__tests__/gpu-resource-store-cubemap-status.test.d.ts.map +0 -1
  491. package/dist/__tests__/gpu-resource.test.d.ts +0 -2
  492. package/dist/__tests__/gpu-resource.test.d.ts.map +0 -1
  493. package/dist/__tests__/hdrp-bgl-slots.browser.test.d.ts +0 -2
  494. package/dist/__tests__/hdrp-bgl-slots.browser.test.d.ts.map +0 -1
  495. package/dist/__tests__/hdrp-buffers.unit.test.d.ts +0 -2
  496. package/dist/__tests__/hdrp-buffers.unit.test.d.ts.map +0 -1
  497. package/dist/__tests__/hdrp-execute-filter.unit.test.d.ts +0 -2
  498. package/dist/__tests__/hdrp-execute-filter.unit.test.d.ts.map +0 -1
  499. package/dist/__tests__/hdrp-gbuffer-attachments.browser.test.d.ts +0 -2
  500. package/dist/__tests__/hdrp-gbuffer-attachments.browser.test.d.ts.map +0 -1
  501. package/dist/__tests__/hdrp-pipeline-asset-type.test-d.d.ts +0 -2
  502. package/dist/__tests__/hdrp-pipeline-asset-type.test-d.d.ts.map +0 -1
  503. package/dist/__tests__/hdrp-ssao-boundary.test.d.ts +0 -2
  504. package/dist/__tests__/hdrp-ssao-boundary.test.d.ts.map +0 -1
  505. package/dist/__tests__/hdrp-ssao-falsification.test.d.ts +0 -2
  506. package/dist/__tests__/hdrp-ssao-falsification.test.d.ts.map +0 -1
  507. package/dist/__tests__/helpers/primitive-world.d.ts +0 -8
  508. package/dist/__tests__/helpers/primitive-world.d.ts.map +0 -1
  509. package/dist/__tests__/helpers/render-asset-port.d.ts +0 -15
  510. package/dist/__tests__/helpers/render-asset-port.d.ts.map +0 -1
  511. package/dist/__tests__/helpers/scene-asset-port.d.ts +0 -9
  512. package/dist/__tests__/helpers/scene-asset-port.d.ts.map +0 -1
  513. package/dist/__tests__/material-derived-coordinates.integration.test.d.ts +0 -2
  514. package/dist/__tests__/material-derived-coordinates.integration.test.d.ts.map +0 -1
  515. package/dist/__tests__/pipeline-layout-selection.test.d.ts +0 -2
  516. package/dist/__tests__/pipeline-layout-selection.test.d.ts.map +0 -1
  517. package/dist/__tests__/post-process-reads-narrowing.test-d.d.ts +0 -2
  518. package/dist/__tests__/post-process-reads-narrowing.test-d.d.ts.map +0 -1
  519. package/dist/__tests__/post-process-register-roundtrip.dawn.test.d.ts +0 -2
  520. package/dist/__tests__/post-process-register-roundtrip.dawn.test.d.ts.map +0 -1
  521. package/dist/__tests__/render-feature-zero-feature.browser.test.d.ts +0 -2
  522. package/dist/__tests__/render-feature-zero-feature.browser.test.d.ts.map +0 -1
  523. package/dist/__tests__/render-pipeline-trivial.dawn.test.d.ts +0 -2
  524. package/dist/__tests__/render-pipeline-trivial.dawn.test.d.ts.map +0 -1
  525. package/dist/__tests__/shadow-csm-caster-membership.test.d.ts +0 -2
  526. package/dist/__tests__/shadow-csm-caster-membership.test.d.ts.map +0 -1
  527. package/dist/__tests__/shadow-csm-pssm.test.d.ts +0 -2
  528. package/dist/__tests__/shadow-csm-pssm.test.d.ts.map +0 -1
  529. package/dist/__tests__/shadow-csm-temporal.browser.test.d.ts +0 -2
  530. package/dist/__tests__/shadow-csm-temporal.browser.test.d.ts.map +0 -1
  531. package/dist/__tests__/shadow-csm-viewport.test.d.ts +0 -2
  532. package/dist/__tests__/shadow-csm-viewport.test.d.ts.map +0 -1
  533. package/dist/__tests__/shadow-m2.dawn.test.d.ts +0 -2
  534. package/dist/__tests__/shadow-m2.dawn.test.d.ts.map +0 -1
  535. package/dist/__tests__/shadow-m3-calibrate-run.dawn.test.d.ts +0 -2
  536. package/dist/__tests__/shadow-m3-calibrate-run.dawn.test.d.ts.map +0 -1
  537. package/dist/__tests__/shadow-m3-calibrate.d.ts +0 -83
  538. package/dist/__tests__/shadow-m3-calibrate.d.ts.map +0 -1
  539. package/dist/__tests__/shadow-m3.dawn.test.d.ts +0 -2
  540. package/dist/__tests__/shadow-m3.dawn.test.d.ts.map +0 -1
  541. package/dist/__tests__/shadow-probe-control-flow.dawn.test.d.ts +0 -2
  542. package/dist/__tests__/shadow-probe-control-flow.dawn.test.d.ts.map +0 -1
  543. package/dist/__tests__/shadow-quality-metrics.dawn.test.d.ts +0 -2
  544. package/dist/__tests__/shadow-quality-metrics.dawn.test.d.ts.map +0 -1
  545. package/dist/__tests__/temporal-off-path.unit.test.d.ts +0 -2
  546. package/dist/__tests__/temporal-off-path.unit.test.d.ts.map +0 -1
  547. package/dist/__tests__/video-cache-regression.unit.test.d.ts +0 -2
  548. package/dist/__tests__/video-cache-regression.unit.test.d.ts.map +0 -1
  549. package/dist/asset-kind.d.ts +0 -4
  550. package/dist/asset-kind.d.ts.map +0 -1
  551. package/dist/errors/sprite-animation.d.ts +0 -3
  552. package/dist/errors/sprite-animation.d.ts.map +0 -1
  553. package/dist/scene-asset-port.d.ts +0 -17
  554. package/dist/scene-asset-port.d.ts.map +0 -1
  555. package/dist/scene-collect-errors.d.ts +0 -22
  556. package/dist/scene-collect-errors.d.ts.map +0 -1
  557. package/dist/shadow-m3-calibrate.d.ts +0 -40
  558. package/dist/shadow-m3-calibrate.d.ts.map +0 -1
  559. package/src/__tests__/bind-group-cache-binding.test.ts +0 -2513
  560. package/src/__tests__/bind-group-cache-frame.test.ts +0 -1511
  561. package/src/__tests__/dispatch-fullscreen-pass-composite-swapchain.dawn.test.ts +0 -204
  562. package/src/__tests__/dispatch-fullscreen-pass-custom-reads.dawn.test.ts +0 -297
  563. package/src/__tests__/dispatch-fullscreen-pass-depth-composite.dawn.test.ts +0 -271
  564. package/src/__tests__/dispatch-fullscreen-pass-params.unit.test.ts +0 -321
  565. package/src/__tests__/fog-producer-manifest.ts +0 -162
  566. package/src/__tests__/fog-producer-pixel-probe.ts +0 -263
  567. package/src/__tests__/fog-producer-pixel.browser.test.ts +0 -26
  568. package/src/__tests__/fog-producer-pixel.dawn.test.ts +0 -55
  569. package/src/__tests__/fog-producer-real-fixture.ts +0 -805
  570. package/src/__tests__/fog-producer-vfx-effect.ts +0 -272
  571. package/src/__tests__/fog-scene-asset.integration.test.ts +0 -123
  572. package/src/__tests__/gpu-resource-store-cubemap-status.test.ts +0 -207
  573. package/src/__tests__/gpu-resource.test.ts +0 -1206
  574. package/src/__tests__/hdrp-bgl-slots.browser.test.ts +0 -50
  575. package/src/__tests__/hdrp-buffers.unit.test.ts +0 -70
  576. package/src/__tests__/hdrp-execute-filter.unit.test.ts +0 -200
  577. package/src/__tests__/hdrp-gbuffer-attachments.browser.test.ts +0 -64
  578. package/src/__tests__/hdrp-pipeline-asset-type.test-d.ts +0 -139
  579. package/src/__tests__/hdrp-ssao-boundary.test.ts +0 -237
  580. package/src/__tests__/hdrp-ssao-falsification.test.ts +0 -289
  581. package/src/__tests__/helpers/primitive-world.ts +0 -91
  582. package/src/__tests__/helpers/render-asset-port.ts +0 -65
  583. package/src/__tests__/helpers/scene-asset-port.ts +0 -23
  584. package/src/__tests__/material-derived-coordinates.integration.test.ts +0 -100
  585. package/src/__tests__/pipeline-layout-selection.test.ts +0 -298
  586. package/src/__tests__/post-process-reads-narrowing.test-d.ts +0 -52
  587. package/src/__tests__/post-process-register-roundtrip.dawn.test.ts +0 -230
  588. package/src/__tests__/render-feature-zero-feature.browser.test.ts +0 -152
  589. package/src/__tests__/render-pipeline-trivial.dawn.test.ts +0 -525
  590. package/src/__tests__/shadow-csm-caster-membership.test.ts +0 -232
  591. package/src/__tests__/shadow-csm-pssm.test.ts +0 -142
  592. package/src/__tests__/shadow-csm-temporal.browser.test.ts +0 -145
  593. package/src/__tests__/shadow-csm-viewport.test.ts +0 -74
  594. package/src/__tests__/shadow-m2.dawn.test.ts +0 -394
  595. package/src/__tests__/shadow-m3-calibrate-run.dawn.test.ts +0 -248
  596. package/src/__tests__/shadow-m3-calibrate.ts +0 -203
  597. package/src/__tests__/shadow-m3.dawn.test.ts +0 -288
  598. package/src/__tests__/shadow-probe-control-flow.dawn.test.ts +0 -264
  599. package/src/__tests__/shadow-quality-metrics.dawn.test.ts +0 -324
  600. package/src/__tests__/temporal-off-path.unit.test.ts +0 -69
  601. package/src/__tests__/video-cache-regression.unit.test.ts +0 -186
  602. package/src/asset-kind.ts +0 -7
  603. package/src/errors/sprite-animation.ts +0 -5
  604. package/src/scene-asset-port.ts +0 -32
  605. package/src/scene-collect-errors.ts +0 -30
  606. package/src/shadow-m3-calibrate.ts +0 -171
@@ -0,0 +1,1203 @@
1
+ // biome-ignore-all lint/complexity/noUselessLoneBlockStatements: fixture scopes are intentional
2
+
3
+ import type { AssetRuntimeError, AssetRuntimeErrorCode } from '@forgeax/engine-assets-runtime';
4
+ import {
5
+ MeshSsboCapacityExceededError,
6
+ MeshSsboCeilingReachedError,
7
+ SceneCollectAssetGuidUnresolvedError,
8
+ SceneCollectEntityRefOutOfClosureError,
9
+ } from '@forgeax/engine-assets-runtime';
10
+ import type { RenderError, RenderErrorCode } from '@forgeax/engine-render';
11
+ import { RhiError } from '@forgeax/engine-rhi';
12
+ import { rhi } from '@forgeax/engine-rhi-null';
13
+ import type { SkinError, SkinErrorCode } from '@forgeax/engine-skinning';
14
+ import type { AssetErrorCode, ImageErrorCode } from '@forgeax/engine-types';
15
+ import { afterEach, describe, expect, it, vi } from 'vitest';
16
+ import { EquirectProjectionFailedError } from '../../../render/src/errors/render';
17
+ import { RhiErrorListenerRegistry } from '../../../render/src/lifecycle';
18
+ import {
19
+ PostProcessError,
20
+ type PostProcessErrorCode,
21
+ } from '../../../render/src/post-process-errors';
22
+ import {
23
+ __classifyEnvErrorReasonForTest,
24
+ __composeEnvErrorHintForTest,
25
+ } from '../create-renderer-env-classify';
26
+ import { requireRenderer } from './renderer-test-utils';
27
+
28
+ // feat-20260704-runtime-tier1-decomposition M2 / w12: the eliminated top-level
29
+ // RuntimeError / RuntimeErrorCode aggregate unions (D-3) are reconstituted here
30
+ // as test-local aliases so the whole-runtime-surface exhaustive-switch bodies
31
+ // below stay byte-identical (AC-09). These are test scaffolding, not a
32
+ // production cross-cluster SSOT -- w13's per-cluster *.test-d.ts files are the
33
+ // real regression guard. Equal to the fanned-out render + asset + skin
34
+ // clusters (27 codes / 27 classes); recover + environment excluded, as the
35
+ // original unions were.
36
+ type RuntimeLayerErrorCode = RenderErrorCode | AssetRuntimeErrorCode | SkinErrorCode;
37
+ type RuntimeLayerError = RenderError | AssetRuntimeError | SkinError;
38
+
39
+ {
40
+ // ─── from create-renderer-env-error-classify.test.ts ───
41
+ describe('create-renderer-env-error-classify.test.ts', () => {
42
+ describe('classifyEnvErrorReason', () => {
43
+ it('keeps GPU-class wording for adapter-unavailable', () => {
44
+ const inner = { name: 'RhiError', code: 'adapter-unavailable' };
45
+ expect(__classifyEnvErrorReasonForTest('no usable rendering backend', inner)).toBe(
46
+ 'no usable rendering backend',
47
+ );
48
+ });
49
+
50
+ it('keeps GPU-class wording for feature-not-enabled / limit-exceeded / device-lost / oom', () => {
51
+ for (const code of ['feature-not-enabled', 'limit-exceeded', 'device-lost', 'oom']) {
52
+ const inner = { name: 'RhiError', code };
53
+ expect(__classifyEnvErrorReasonForTest('no usable rendering backend', inner)).toBe(
54
+ 'no usable rendering backend',
55
+ );
56
+ }
57
+ });
58
+
59
+ it('switches wording for ShaderError manifest-malformed', () => {
60
+ const inner = { name: 'ShaderError', code: 'manifest-malformed' };
61
+ expect(__classifyEnvErrorReasonForTest('no usable rendering backend', inner)).toBe(
62
+ 'engine init failed (ShaderError: manifest-malformed)',
63
+ );
64
+ });
65
+
66
+ it('switches wording for PackError pack-malformed-pack', () => {
67
+ const inner = { name: 'PackError', code: 'pack-malformed-pack' };
68
+ expect(__classifyEnvErrorReasonForTest('no usable rendering backend', inner)).toBe(
69
+ 'engine init failed (PackError: pack-malformed-pack)',
70
+ );
71
+ });
72
+
73
+ it('falls back to name-only when code is absent', () => {
74
+ const inner = { name: 'AssetError' };
75
+ expect(__classifyEnvErrorReasonForTest('no usable rendering backend', inner)).toBe(
76
+ 'engine init failed (AssetError)',
77
+ );
78
+ });
79
+
80
+ it('returns base message untouched when inner is undefined', () => {
81
+ expect(__classifyEnvErrorReasonForTest('no usable rendering backend', undefined)).toBe(
82
+ 'no usable rendering backend',
83
+ );
84
+ });
85
+
86
+ it('returns base message untouched when inner is non-object', () => {
87
+ // Non-object inner (string / number) should not crash; keep base message.
88
+ expect(
89
+ __classifyEnvErrorReasonForTest('no usable rendering backend', 'oops' as never),
90
+ ).toBe('no usable rendering backend');
91
+ });
92
+
93
+ it('preserves the Channel-3-fallback variant of the base message', () => {
94
+ const inner = { name: 'ShaderError', code: 'manifest-malformed' };
95
+ expect(
96
+ __classifyEnvErrorReasonForTest(
97
+ 'no usable rendering backend (Channel 3 fallback failed)',
98
+ inner,
99
+ ),
100
+ ).toBe('engine init failed (ShaderError: manifest-malformed)');
101
+ // GPU inner keeps the Channel-3 suffix verbatim.
102
+ const gpuInner = { name: 'RhiError', code: 'adapter-unavailable' };
103
+ expect(
104
+ __classifyEnvErrorReasonForTest(
105
+ 'no usable rendering backend (Channel 3 fallback failed)',
106
+ gpuInner,
107
+ ),
108
+ ).toBe('no usable rendering backend (Channel 3 fallback failed)');
109
+ });
110
+ });
111
+
112
+ describe('composeEnvErrorHint (bug-20260610 dual-channel env failure)', () => {
113
+ // When both Channel 2 (rhi-webgpu) and Channel 3 (rhi-wgpu wasm GL) report
114
+ // adapter-unavailable / rhi-not-available, the failure is a browser-config
115
+ // issue (Edge with edge://flags/#enable-unsafe-webgpu = Disabled) rather
116
+ // than a real GPU absence. The hint surfaces actionable browser guidance.
117
+ it('emits hint when both errors are adapter-unavailable', () => {
118
+ const out = __composeEnvErrorHintForTest(
119
+ { code: 'adapter-unavailable' },
120
+ { code: 'adapter-unavailable' },
121
+ );
122
+ expect(out).toContain('both channels report environmental failure');
123
+ expect(out).toContain('edge://flags/#enable-unsafe-webgpu');
124
+ });
125
+
126
+ it('emits hint for adapter-unavailable + rhi-not-available cross', () => {
127
+ const out = __composeEnvErrorHintForTest(
128
+ { code: 'adapter-unavailable' },
129
+ { code: 'rhi-not-available' },
130
+ );
131
+ expect(out).toBeDefined();
132
+ expect(out).toContain('Enabled');
133
+ });
134
+
135
+ it('returns undefined when only one channel reports an env code', () => {
136
+ // Real-GPU-class failure on one side, asset error on the other — no
137
+ // browser-config guidance applies.
138
+ expect(
139
+ __composeEnvErrorHintForTest(
140
+ { code: 'adapter-unavailable' },
141
+ { code: 'manifest-malformed' },
142
+ ),
143
+ ).toBeUndefined();
144
+ });
145
+
146
+ it('returns undefined when either side is missing', () => {
147
+ expect(
148
+ __composeEnvErrorHintForTest({ code: 'adapter-unavailable' }, undefined),
149
+ ).toBeUndefined();
150
+ expect(
151
+ __composeEnvErrorHintForTest(undefined, { code: 'adapter-unavailable' }),
152
+ ).toBeUndefined();
153
+ });
154
+
155
+ it('returns undefined for plain Error-like objects without .code', () => {
156
+ // Edge case: `wgpuError` may be a raw Error from a wasm load throw
157
+ // (no .code property). The helper must not crash and must not emit
158
+ // false-positive guidance.
159
+ const plainErr = new Error('wasm load failed');
160
+ expect(
161
+ __composeEnvErrorHintForTest({ code: 'adapter-unavailable' }, plainErr),
162
+ ).toBeUndefined();
163
+ });
164
+
165
+ it('returns undefined for non-string code values', () => {
166
+ // RhiError shape always has .code: string, but defensive coverage.
167
+ expect(
168
+ __composeEnvErrorHintForTest({ code: 42 }, { code: 'adapter-unavailable' }),
169
+ ).toBeUndefined();
170
+ });
171
+ });
172
+ });
173
+ }
174
+ {
175
+ // --- migrated from device-lost-fan-out.test.ts ---
176
+ describe('Renderer.subscribe lifecycle events', () => {
177
+ const manifest = `data:application/json,${encodeURIComponent(
178
+ JSON.stringify({ schemaVersion: '1.0.0', entries: [] }),
179
+ )}`;
180
+ const canvas = { getContext: () => null } as unknown as HTMLCanvasElement;
181
+
182
+ it('publishes disposal through subscribe and keeps state inspectable', async () => {
183
+ const renderer = await requireRenderer(canvas, { rhi }, { shaderManifestUrl: manifest });
184
+ expect(renderer.state()).toBe('alive');
185
+ expect(renderer.inspect().state).toBe('alive');
186
+
187
+ const events: unknown[] = [];
188
+ const unsubscribe = renderer.subscribe((event) => events.push(event));
189
+ expect(typeof unsubscribe).toBe('function');
190
+ expect((await renderer.dispose()).ok).toBe(true);
191
+ expect(events).toContainEqual({
192
+ kind: 'state-changed',
193
+ previous: 'alive',
194
+ current: 'disposed',
195
+ });
196
+ unsubscribe();
197
+ });
198
+
199
+ it('does not retain a listener after unsubscribe', async () => {
200
+ const renderer = await requireRenderer(canvas, { rhi }, { shaderManifestUrl: manifest });
201
+ const events: unknown[] = [];
202
+ const unsubscribe = renderer.subscribe((event) => events.push(event));
203
+ unsubscribe();
204
+
205
+ expect((await renderer.dispose()).ok).toBe(true);
206
+ expect(events).toEqual([]);
207
+ });
208
+ });
209
+ }
210
+
211
+ {
212
+ // --- migrated from device-lost.test.ts ---
213
+ describe('Renderer surface recovery contract', () => {
214
+ const manifest = `data:application/json,${encodeURIComponent(
215
+ JSON.stringify({ schemaVersion: '1.0.0', entries: [] }),
216
+ )}`;
217
+ const canvas = { getContext: () => null } as unknown as HTMLCanvasElement;
218
+
219
+ it('releases and restores the surface without changing the renderer identity', async () => {
220
+ const renderer = await requireRenderer(canvas, { rhi }, { shaderManifestUrl: manifest });
221
+ const before = renderer.inspect();
222
+
223
+ expect(renderer.releaseSurface().ok).toBe(true);
224
+ expect(renderer.inspect().surface).toBe('released');
225
+ expect(renderer.restoreSurface().ok).toBe(true);
226
+ expect(renderer.inspect().surface).toBe('available');
227
+ expect(renderer.inspect().capabilities.backendKind).toBe('null');
228
+ expect(renderer.inspect().state).toBe(before.state);
229
+
230
+ expect((await renderer.dispose()).ok).toBe(true);
231
+ });
232
+
233
+ it('returns a structured recover result when no device is lost', async () => {
234
+ const renderer = await requireRenderer(canvas, { rhi }, { shaderManifestUrl: manifest });
235
+ const recovered = await renderer.recover();
236
+
237
+ expect(recovered.ok).toBe(false);
238
+ if (!recovered.ok) expect(recovered.error.code).toBe('renderer-state-invalid');
239
+ expect((await renderer.dispose()).ok).toBe(true);
240
+ });
241
+ });
242
+ }
243
+
244
+ {
245
+ // ─── from error-exhaustive.test.ts ───
246
+ describe('error-exhaustive.test.ts', () => {
247
+ describe('t2 - AC-15 AssetErrorCode exhaustive switch (new members)', () => {
248
+ it('AssetErrorCode includes cubemap-handle-missing', () => {
249
+ const code: AssetErrorCode = 'cubemap-handle-missing';
250
+ expect(code).toBe('cubemap-handle-missing');
251
+ });
252
+
253
+ it('AssetErrorCode includes invalid-source-format', () => {
254
+ const code: AssetErrorCode = 'invalid-source-format';
255
+ expect(code).toBe('invalid-source-format');
256
+ });
257
+
258
+ it('AssetErrorCode includes load-failed', () => {
259
+ const code: AssetErrorCode = 'load-failed';
260
+ expect(code).toBe('load-failed');
261
+ });
262
+
263
+ it('AssetErrorCode includes device-unsupported', () => {
264
+ const code: AssetErrorCode = 'device-unsupported';
265
+ expect(code).toBe('device-unsupported');
266
+ });
267
+
268
+ it('AssetErrorCode includes ibl-precompute-not-dispatched (t56 M3.5 minor evolution)', () => {
269
+ const code: AssetErrorCode = 'ibl-precompute-not-dispatched';
270
+ expect(code).toBe('ibl-precompute-not-dispatched');
271
+ });
272
+
273
+ it('AssetErrorCode exhaustive switch covers all members without default', () => {
274
+ function exhaustive(code: AssetErrorCode): string {
275
+ switch (code) {
276
+ case 'asset-not-found':
277
+ return 'not found';
278
+ case 'asset-parse-failed':
279
+ return 'parse failed';
280
+ case 'asset-format-unsupported':
281
+ return 'format unsupported';
282
+ case 'asset-fetch-failed':
283
+ return 'fetch failed';
284
+ case 'catalog-source-unconfigured':
285
+ return 'catalog source unconfigured';
286
+ case 'asset-invalid-value':
287
+ return 'invalid value';
288
+ case 'cubemap-handle-missing':
289
+ return 'cubemap handle missing';
290
+ case 'invalid-source-format':
291
+ return 'invalid source format';
292
+ case 'load-failed':
293
+ return 'load failed';
294
+ case 'device-unsupported':
295
+ return 'device unsupported';
296
+ case 'ibl-precompute-not-dispatched':
297
+ return 'ibl precompute not dispatched';
298
+ case 'mesh-vertex-stride-mismatch':
299
+ return 'mesh vertex stride mismatch';
300
+ case 'material-shader-ref-broken':
301
+ return 'shader ref broken';
302
+ case 'material-circular-inheritance':
303
+ return 'circular inheritance';
304
+ // === 2 new codes (feat-20260603-asset-import-loader-injection M1 / w1) ===
305
+ case 'loader-not-registered':
306
+ return 'loader not registered';
307
+ case 'asset-not-imported':
308
+ return 'asset not imported';
309
+ // === 1 new code (feat-20260604-hdr-equirect-cube-importer-loader M2 / w4) ===
310
+ case 'texture-source-not-imported':
311
+ return 'texture source not imported';
312
+ case 'source-not-imported':
313
+ return 'source not imported';
314
+ // === 3 new codes (feat-20260608-mesh-multi-section-primitive-multi-material-slot M1 / w2) ===
315
+ case 'mesh-renderer-material-override-invalid':
316
+ return 'mesh renderer material override invalid';
317
+ case 'mesh-renderer-material-override-overflow':
318
+ return 'mesh renderer material override overflow';
319
+ case 'mesh-asset-submeshes-empty':
320
+ return 'mesh asset submeshes empty';
321
+ case 'mesh-asset-material-slot-index-out-of-range':
322
+ return 'mesh asset material slot index out of range';
323
+ case 'mesh-submesh-index-range-out-of-bounds':
324
+ return 'mesh submesh index range out of bounds';
325
+ // === 1 new code (feat-20260608-tilemap-object-layer-rendering M0 baseline rebuild) ===
326
+ case 'tileset-region-index-out-of-range':
327
+ return 'tileset region index out of range';
328
+ // === 1 new code (feat-20260608-tilemap-object-layer-rendering M1 schema extension) ===
329
+ case 'tileset-tile-entry-malformed':
330
+ return 'tileset tile entry malformed';
331
+ // === 1 new code (feat-20260621-asset-registry-robustness-invalidate-inflight-cach M2 / w4) ===
332
+ case 'asset-invalidated':
333
+ return 'asset invalidated';
334
+ // === 1 new code (feat-20260629-multi-uv-set-support M2 / m2-w5) ===
335
+ case 'mesh-bin-contract-violation':
336
+ return 'mesh bin contract violation';
337
+ // === 1 new code (feat-20260707-texture-block-compression M5 / w35) ===
338
+ case 'mipgen-unsupported-compressed-format':
339
+ return 'mipgen unsupported compressed format';
340
+ }
341
+ }
342
+ expect(exhaustive('asset-not-found')).toBe('not found');
343
+ expect(exhaustive('texture-source-not-imported')).toBe('texture source not imported');
344
+ expect(exhaustive('cubemap-handle-missing')).toBe('cubemap handle missing');
345
+ expect(exhaustive('invalid-source-format')).toBe('invalid source format');
346
+ expect(exhaustive('load-failed')).toBe('load failed');
347
+ expect(exhaustive('device-unsupported')).toBe('device unsupported');
348
+ expect(exhaustive('ibl-precompute-not-dispatched')).toBe('ibl precompute not dispatched');
349
+ });
350
+ });
351
+
352
+ describe('t2 - AC-15 ImageErrorCode exhaustive switch (new member)', () => {
353
+ it('ImageErrorCode includes image-hdr-decode-failed', () => {
354
+ const code: ImageErrorCode = 'image-hdr-decode-failed';
355
+ expect(code).toBe('image-hdr-decode-failed');
356
+ });
357
+
358
+ it('ImageErrorCode exhaustive switch covers all members without default', () => {
359
+ function exhaustive(code: ImageErrorCode): string {
360
+ switch (code) {
361
+ case 'image-decode-failed':
362
+ return 'decode failed';
363
+ case 'image-format-unsupported':
364
+ return 'format unsupported';
365
+ case 'image-dimension-out-of-bounds':
366
+ return 'out of bounds';
367
+ case 'image-meta-missing':
368
+ return 'meta missing';
369
+ case 'image-hdr-decode-failed':
370
+ return 'hdr decode failed';
371
+ // feat-20260521-sprite-atlas-animation M1 T-02 — atlas hook
372
+ // fail-fast triplet (plan-strategy section 2 D-2). Add the three
373
+ // case arms so the exhaustive switch keeps compiling without a
374
+ // default fall-through after ImageErrorCode grew from 5 to 8.
375
+ case 'atlas-empty-input':
376
+ return 'atlas empty input';
377
+ case 'atlas-size-exceeded':
378
+ return 'atlas size exceeded';
379
+ case 'atlas-region-mismatch':
380
+ return 'atlas region mismatch';
381
+ }
382
+ }
383
+ expect(exhaustive('image-decode-failed')).toBe('decode failed');
384
+ expect(exhaustive('image-hdr-decode-failed')).toBe('hdr decode failed');
385
+ });
386
+ });
387
+ });
388
+ }
389
+
390
+ {
391
+ // ─── from errors.test.ts ───
392
+ describe('errors.test.ts', () => {
393
+ describe('AC-11 RuntimeErrorCode exhaustive switch (11 members, no default)', () => {
394
+ it('exhaustive switch covers all members without a default branch', () => {
395
+ function exhaustive(code: RuntimeLayerErrorCode): string {
396
+ switch (code) {
397
+ case 'lifecycle-construction-failed':
398
+ return 'lifecycle construction failed';
399
+ case 'world-lease-invalid':
400
+ return 'world lease invalid';
401
+ case 'frame-input-invalid':
402
+ return 'frame input invalid';
403
+ case 'scene-projection-failed':
404
+ return 'scene projection failed';
405
+ case 'asset-binding-failed':
406
+ return 'asset binding failed';
407
+ case 'feature-plan-failed':
408
+ return 'feature plan failed';
409
+ case 'graph-build-failed':
410
+ return 'graph build failed';
411
+ case 'device-operation-failed':
412
+ return 'device operation failed';
413
+ case 'surface-unavailable':
414
+ return 'surface unavailable';
415
+ case 'renderer-state-invalid':
416
+ return 'renderer state invalid';
417
+ case 'recovery-failed':
418
+ return 'recovery failed';
419
+ case 'cleanup-failed':
420
+ return 'cleanup failed';
421
+ case 'frame-receipt-stale':
422
+ return 'frame receipt stale';
423
+ case 'renderer-contract-failed':
424
+ return 'renderer contract failed';
425
+ case 'observation-unavailable':
426
+ return 'observation unavailable';
427
+ case 'shadow-invalid-config':
428
+ return 'shadow invalid config';
429
+ case 'skin-joint-count-exceeded':
430
+ return 'skin joint count exceeded';
431
+ case 'skin-joint-despawned':
432
+ return 'skin joint despawned';
433
+ case 'skin-joint-path-unresolved':
434
+ return 'skin joint path unresolved';
435
+ case 'skin-instances-coexist-forbidden':
436
+ return 'skin instances coexist forbidden';
437
+ case 'vertex-storage-buffer-unavailable':
438
+ return 'vertex storage buffer unavailable';
439
+ case 'vertex-color-variant-conflict':
440
+ return 'vertex color variant conflict';
441
+ case 'skin-palette-overflow':
442
+ return 'skin palette overflow';
443
+ case 'material-resolved-empty-passes':
444
+ return 'material resolved empty passes';
445
+ case 'equirect-projection-failed':
446
+ return 'equirect projection failed';
447
+ case 'mesh-ssbo-capacity-exceeded':
448
+ return 'mesh ssbo capacity exceeded';
449
+ case 'mesh-ssbo-ceiling-reached':
450
+ return 'mesh ssbo ceiling reached';
451
+ case 'hdrp-light-budget-exceeded':
452
+ return 'hdrp light budget exceeded';
453
+ case 'hdrp-index-list-overflow':
454
+ return 'hdrp index list overflow';
455
+ // feat-20260612-hdrp-deferred-shading-learn-render-5-8 M1 / w6:
456
+ // 3 new deferred-path error codes.
457
+ case 'hdrp-deferred-caps-insufficient':
458
+ return 'hdrp deferred caps insufficient';
459
+ // feat-20260611-fox-skinning-vertex-attribute-chain M4 / w17 (D-5):
460
+ // bidirectional Skin <-> pbr-skin material mismatch detected at extract.
461
+ case 'skin-material-mismatch':
462
+ return 'skin material mismatch';
463
+ case 'material-skin-attr-missing':
464
+ return 'material skin attr missing';
465
+ // feat-20260612-skin-palette-per-frame-upload M2 / m2-5:
466
+ // SkinExtractErrorCode subset union (3 new extract-stage classes).
467
+ case 'skeleton-resolve-failed':
468
+ return 'skeleton resolve failed';
469
+ case 'joint-count-mismatch':
470
+ return 'joint count mismatch';
471
+ case 'joint-entity-dangling':
472
+ return 'joint entity dangling';
473
+ // feat-20260612-point-light-shadows-urp-hdrp Round-2 F-4:
474
+ // ShadowAtlas P3 closed-union compliance.
475
+ case 'point-shadow-atlas-uninitialized':
476
+ return 'point shadow atlas uninitialized';
477
+ case 'point-shadow-atlas-bounds-violation':
478
+ return 'point shadow atlas bounds violation';
479
+ // feat-20260623-world-space-video-asset M3 / w11: AC-10 capability
480
+ // double-miss code (add-only minor).
481
+ case 'video-upload-unsupported':
482
+ return 'video upload unsupported';
483
+ // feat-20260701-rootstosceneasset-forest-collect-schema-derived-ha
484
+ // M1 / w3 (D-5): new scene-collect error codes.
485
+ case 'scene-collect-entity-ref-out-of-closure':
486
+ return 'scene collect entity ref out of closure';
487
+ case 'scene-collect-asset-guid-unresolved':
488
+ return 'scene collect asset guid unresolved';
489
+ case 'render-feature-registration-conflict':
490
+ return 'render feature registration conflict';
491
+ case 'render-feature-stage-failed':
492
+ return 'render feature stage failed';
493
+ case 'render-feature-capability-missing':
494
+ return 'render feature capability missing';
495
+ case 'render-feature-pass-order-conflict':
496
+ return 'render feature pass order conflict';
497
+ case 'render-feature-preparation-failed':
498
+ return 'render feature preparation failed';
499
+ case 'render-feature-prepared-state-mismatch':
500
+ return 'render feature prepared state mismatch';
501
+ case 'render-feature-draw-recording-failed':
502
+ return 'render feature draw recording failed';
503
+ }
504
+ }
505
+ expect(exhaustive('equirect-projection-failed')).toBe('equirect projection failed');
506
+ expect(exhaustive('material-resolved-empty-passes')).toBe('material resolved empty passes');
507
+ expect(exhaustive('shadow-invalid-config')).toBe('shadow invalid config');
508
+ expect(exhaustive('mesh-ssbo-capacity-exceeded')).toBe('mesh ssbo capacity exceeded');
509
+ expect(exhaustive('mesh-ssbo-ceiling-reached')).toBe('mesh ssbo ceiling reached');
510
+ });
511
+ });
512
+
513
+ /*
514
+ * feat-20260612-hdrp-deferred-shading-learn-render-5-8 M1 / w3:
515
+ * RuntimeErrorCode +3 members — exhaustive narrow test.
516
+ *
517
+ * AC-07: three new error codes must be members of RuntimeErrorCode closed union.
518
+ * Each error code narrows without a default branch in switch (err.code).
519
+ */
520
+ describe('RuntimeErrorCode +3 new members (w3)', () => {
521
+ it('hdrp-deferred-caps-insufficient is a valid RuntimeErrorCode', () => {
522
+ const code: RuntimeLayerErrorCode = 'hdrp-deferred-caps-insufficient';
523
+ expect(code).toBe('hdrp-deferred-caps-insufficient');
524
+ });
525
+
526
+ it('exhaustive switch covers all 3 new members alongside existing members', () => {
527
+ function exhaustive(code: RuntimeLayerErrorCode): string {
528
+ switch (code) {
529
+ case 'lifecycle-construction-failed':
530
+ return 'ok';
531
+ case 'world-lease-invalid':
532
+ return 'ok';
533
+ case 'frame-input-invalid':
534
+ return 'ok';
535
+ case 'scene-projection-failed':
536
+ return 'ok';
537
+ case 'asset-binding-failed':
538
+ return 'ok';
539
+ case 'feature-plan-failed':
540
+ return 'ok';
541
+ case 'graph-build-failed':
542
+ return 'ok';
543
+ case 'device-operation-failed':
544
+ return 'ok';
545
+ case 'surface-unavailable':
546
+ return 'ok';
547
+ case 'renderer-state-invalid':
548
+ return 'ok';
549
+ case 'recovery-failed':
550
+ return 'ok';
551
+ case 'cleanup-failed':
552
+ return 'ok';
553
+ case 'frame-receipt-stale':
554
+ return 'ok';
555
+ case 'renderer-contract-failed':
556
+ return 'ok';
557
+ case 'observation-unavailable':
558
+ return 'ok';
559
+ case 'shadow-invalid-config':
560
+ return 'ok';
561
+ case 'skin-joint-count-exceeded':
562
+ return 'ok';
563
+ case 'skin-joint-despawned':
564
+ return 'ok';
565
+ case 'skin-joint-path-unresolved':
566
+ return 'ok';
567
+ case 'skin-instances-coexist-forbidden':
568
+ return 'ok';
569
+ case 'vertex-storage-buffer-unavailable':
570
+ return 'ok';
571
+ case 'vertex-color-variant-conflict':
572
+ return 'ok';
573
+ case 'skin-palette-overflow':
574
+ return 'ok';
575
+ case 'material-resolved-empty-passes':
576
+ return 'ok';
577
+ case 'equirect-projection-failed':
578
+ return 'ok';
579
+ case 'mesh-ssbo-capacity-exceeded':
580
+ return 'ok';
581
+ case 'mesh-ssbo-ceiling-reached':
582
+ return 'ok';
583
+ case 'hdrp-light-budget-exceeded':
584
+ return 'ok';
585
+ case 'hdrp-index-list-overflow':
586
+ return 'ok';
587
+ case 'skin-material-mismatch':
588
+ return 'ok';
589
+ case 'material-skin-attr-missing':
590
+ return 'ok';
591
+ case 'skeleton-resolve-failed':
592
+ return 'ok';
593
+ case 'joint-count-mismatch':
594
+ return 'ok';
595
+ case 'joint-entity-dangling':
596
+ return 'ok';
597
+ case 'hdrp-deferred-caps-insufficient':
598
+ return 'ok';
599
+ case 'point-shadow-atlas-uninitialized':
600
+ return 'ok';
601
+ case 'point-shadow-atlas-bounds-violation':
602
+ return 'ok';
603
+ case 'video-upload-unsupported':
604
+ return 'ok';
605
+ case 'scene-collect-entity-ref-out-of-closure':
606
+ return 'ok';
607
+ case 'scene-collect-asset-guid-unresolved':
608
+ return 'ok';
609
+ case 'render-feature-registration-conflict':
610
+ return 'ok';
611
+ case 'render-feature-stage-failed':
612
+ return 'ok';
613
+ case 'render-feature-capability-missing':
614
+ return 'ok';
615
+ case 'render-feature-pass-order-conflict':
616
+ return 'ok';
617
+ case 'render-feature-preparation-failed':
618
+ return 'ok';
619
+ case 'render-feature-prepared-state-mismatch':
620
+ return 'ok';
621
+ case 'render-feature-draw-recording-failed':
622
+ return 'ok';
623
+ }
624
+ }
625
+ expect(exhaustive('hdrp-deferred-caps-insufficient')).toBe('ok');
626
+ expect(exhaustive('point-shadow-atlas-uninitialized')).toBe('ok');
627
+ expect(exhaustive('point-shadow-atlas-bounds-violation')).toBe('ok');
628
+ });
629
+ });
630
+
631
+ describe('AC-03 mesh-ssbo error narrowing in onError callback shape (no `as`)', () => {
632
+ it('switch (err.code) narrows the detail variants for the two new codes', () => {
633
+ // Build a fake onError handler closing over a mutable record we can
634
+ // assert against. Critical: the body must compile without any `as`
635
+ // narrowing — TS must derive `err.detail.requested|capacity|ceiling` as
636
+ // `number` purely from the `case` discriminant.
637
+ const captured: {
638
+ requested: number | null;
639
+ capacity: number | null;
640
+ ceiling: number | null;
641
+ handled: boolean;
642
+ } = { requested: null, capacity: null, ceiling: null, handled: false };
643
+
644
+ const onError = (err: RuntimeLayerError): void => {
645
+ switch (err.code) {
646
+ case 'mesh-ssbo-capacity-exceeded': {
647
+ // err narrows to MeshSsboCapacityExceededError; detail is the
648
+ // discriminated-union variant carrying { requested, capacity,
649
+ // ceiling: number }.
650
+ const r: number = err.detail.requested;
651
+ const c: number = err.detail.capacity;
652
+ const ce: number = err.detail.ceiling;
653
+ captured.requested = r;
654
+ captured.capacity = c;
655
+ captured.ceiling = ce;
656
+ captured.handled = true;
657
+ return;
658
+ }
659
+ case 'mesh-ssbo-ceiling-reached': {
660
+ const r: number = err.detail.requested;
661
+ const c: number = err.detail.capacity;
662
+ const ce: number = err.detail.ceiling;
663
+ captured.requested = r;
664
+ captured.capacity = c;
665
+ captured.ceiling = ce;
666
+ captured.handled = true;
667
+ return;
668
+ }
669
+ default:
670
+ // Other arms not exercised in this test; leave captured untouched.
671
+ return;
672
+ }
673
+ };
674
+
675
+ // Construct the two new error classes via the published surface and
676
+ // pump them through onError to confirm runtime + compile time agree.
677
+ onError(new MeshSsboCapacityExceededError(2048, 1024, 524288));
678
+ expect(captured.handled).toBe(true);
679
+ expect(captured.requested).toBe(2048);
680
+ expect(captured.capacity).toBe(1024);
681
+ expect(captured.ceiling).toBe(524288);
682
+
683
+ captured.handled = false;
684
+ onError(new MeshSsboCeilingReachedError(600000, 524288, 524288));
685
+ expect(captured.handled).toBe(true);
686
+ expect(captured.requested).toBe(600000);
687
+ expect(captured.capacity).toBe(524288);
688
+ expect(captured.ceiling).toBe(524288);
689
+ });
690
+ });
691
+ });
692
+ }
693
+
694
+ {
695
+ // ─── from on-error-fan-out.test.ts ───
696
+ describe('on-error-fan-out.test.ts', () => {
697
+ describe('RhiErrorListenerRegistry — onError fan-out', () => {
698
+ afterEach(() => {
699
+ vi.restoreAllMocks();
700
+ });
701
+
702
+ it('(a) listeners fire in FIFO insertion order', () => {
703
+ const reg = new RhiErrorListenerRegistry();
704
+ const callOrder: number[] = [];
705
+ reg.add(() => {
706
+ callOrder.push(1);
707
+ });
708
+ reg.add(() => {
709
+ callOrder.push(2);
710
+ });
711
+ reg.add(() => {
712
+ callOrder.push(3);
713
+ });
714
+ reg.fire(new RhiError({ code: 'webgpu-runtime-error', expected: 'e', hint: 'h' }));
715
+ expect(callOrder).toEqual([1, 2, 3]);
716
+ });
717
+
718
+ it('(b) a throwing listener does not abort subsequent listeners', () => {
719
+ const reg = new RhiErrorListenerRegistry();
720
+ const after = vi.fn();
721
+ reg.add(() => {
722
+ throw new Error('listener boom');
723
+ });
724
+ reg.add(after);
725
+ expect(() =>
726
+ reg.fire(new RhiError({ code: 'webgpu-runtime-error', expected: 'e', hint: 'h' })),
727
+ ).not.toThrow();
728
+ expect(after).toHaveBeenCalledTimes(1);
729
+ });
730
+
731
+ it('(c) zero-listener fire() falls back to console.error', () => {
732
+ const reg = new RhiErrorListenerRegistry();
733
+ const spy = vi.spyOn(console, 'error').mockImplementation(() => {});
734
+ reg.fire(
735
+ new RhiError({
736
+ code: 'device-lost',
737
+ expected: 'device intact',
738
+ hint: 'reload page or rebuild renderer',
739
+ }),
740
+ );
741
+ expect(spy).toHaveBeenCalledTimes(1);
742
+ });
743
+
744
+ it('(d) a RuntimeError (EquirectProjectionFailedError) fans out without an as-any cast (F-1)', () => {
745
+ const reg = new RhiErrorListenerRegistry();
746
+ const seen: Array<{ code: string; handle: number | undefined }> = [];
747
+ reg.add((e) => {
748
+ // AI-user view: switch (e.code) reaches the RuntimeError arm and narrows
749
+ // to EquirectProjectionFailedError (no cast at the fire() call site below).
750
+ if (e.code === 'equirect-projection-failed') {
751
+ seen.push({ code: e.code, handle: e.detail.handle });
752
+ } else {
753
+ seen.push({ code: e.code, handle: undefined });
754
+ }
755
+ });
756
+ reg.fire(new EquirectProjectionFailedError(42));
757
+ expect(seen).toEqual([{ code: 'equirect-projection-failed', handle: 42 }]);
758
+ });
759
+
760
+ // feat-20260608-mesh-ssbo-dynamic-grow-l1-lift-1024-entity-cap M4 / T-M4-01:
761
+ // outer catch in render-system.ts recordFrame passes `detail.error` as the
762
+ // caught exception object (preserving .code / .expected / .hint for RhiError).
763
+ // AI users switch on err.code === 'webgpu-runtime-error' then narrow detail
764
+ // via 'error' in e.detail to access e.detail.error.code without `as`.
765
+ it('(e) webgpu-runtime-error detail.error is a structured object (RhiError preserved, narrowing without as on inner code)', () => {
766
+ const reg = new RhiErrorListenerRegistry();
767
+
768
+ // Simulate the outer catch: an underlying RhiError (e.g. 'queue-write-buffer-out-of-bounds')
769
+ // is caught and wrapped into a new RhiError with code='webgpu-runtime-error',
770
+ // whose detail.error preserves the original RhiError object (with .code / .expected / .hint).
771
+ const innerErr = new RhiError({
772
+ code: 'queue-write-buffer-out-of-bounds',
773
+ expected: 'offset + size <= buffer size',
774
+ hint: 'check writeBuffer byte range against buffer size; does growMeshSsbo need to fire?',
775
+ });
776
+ const outerErr = new RhiError({
777
+ code: 'webgpu-runtime-error',
778
+ expected: 'RenderSystem to record one frame without an internal exception',
779
+ hint: 'inspect detail.error for the underlying cause; next frame will retry',
780
+ detail: { error: innerErr },
781
+ });
782
+
783
+ const seen: Array<{ outerCode: string; innerCode: string }> = [];
784
+ reg.add((e) => {
785
+ // AI-user view: switch on e.code narrows to RhiError (vs RuntimeError).
786
+ // Since RhiError is a single class, e.detail is still RhiErrorDetail;
787
+ // use 'error' in guard to narrow to RhiWebgpuRuntimeDetail.
788
+ if (e.code === 'webgpu-runtime-error' && e.detail && 'error' in e.detail) {
789
+ // e.detail.error is RhiError (preserved from outer catch), not string.
790
+ // .code is accessible without `as` — both union branches carry .code.
791
+ seen.push({ outerCode: e.code, innerCode: e.detail.error.code });
792
+ } else {
793
+ seen.push({ outerCode: 'unknown', innerCode: 'unknown' });
794
+ }
795
+ });
796
+ reg.fire(outerErr);
797
+ expect(seen).toEqual([
798
+ { outerCode: 'webgpu-runtime-error', innerCode: 'queue-write-buffer-out-of-bounds' },
799
+ ]);
800
+ });
801
+
802
+ it('(f) webgpu-runtime-error detail.error fallback for non-RhiError throws (narrowing without as on inner code)', () => {
803
+ const reg = new RhiErrorListenerRegistry();
804
+
805
+ // Simulate a non-RhiError (e.g. plain TypeError) caught by outer catch.
806
+ // detail.error becomes { code: 'unknown', message: '...' } fallback.
807
+ const outerErr = new RhiError({
808
+ code: 'webgpu-runtime-error',
809
+ expected: 'RenderSystem to record one frame without an internal exception',
810
+ hint: 'inspect detail.error for the underlying cause; next frame will retry',
811
+ detail: { error: { code: 'unknown' as const, message: 'plain TypeError boomed' } },
812
+ });
813
+
814
+ const seen: Array<{ outerCode: string; innerCode: string }> = [];
815
+ reg.add((e) => {
816
+ if (e.code === 'webgpu-runtime-error' && e.detail && 'error' in e.detail) {
817
+ // e.detail.error.code is accessible without `as` — both union branches
818
+ // (RhiError | { code: string; message: string }) carry .code.
819
+ seen.push({ outerCode: 'unknown', innerCode: e.detail.error.code });
820
+ } else {
821
+ seen.push({ outerCode: 'unknown', innerCode: 'unknown' });
822
+ }
823
+ });
824
+ reg.fire(outerErr);
825
+ expect(seen).toEqual([{ outerCode: 'unknown', innerCode: 'unknown' }]);
826
+ });
827
+ });
828
+ });
829
+ }
830
+
831
+ {
832
+ // ─── from post-process-errors.test.ts ───
833
+ describe('post-process-errors.test.ts', () => {
834
+ // ─── PostProcessErrorCode variant scaffold (mirrors the shared registry error model) ────
835
+ //
836
+ // These are the TWO expected union members, matching D-4 / D-9:
837
+ // - 'post-process-already-registered' (programmer error → throw)
838
+ // - 'post-process-not-found' (runtime path → Result.err)
839
+ //
840
+ // Before w12 implements post-process-errors.ts, these inline definitions
841
+ // serve as the compile-time contract: the tests compile, pass, and validate
842
+ // the shape. Once w12 lands, the imports switch to the real module and the
843
+ // tests run against real throw/Result paths after w13.
844
+
845
+ type LegacyPostProcessErrorCode = 'post-process-already-registered' | 'post-process-not-found';
846
+
847
+ describe('feat-20260604 M2 w11: PostProcessErrorCode closed union', () => {
848
+ it('exhaustive switch on PostProcessErrorCode compiles without default', () => {
849
+ // AC-08 / charter P3: the union must be exhaustively switchable without
850
+ // a default branch. This test uses the scaffold type above; after w12,
851
+ // it switches to the real PostProcessErrorCode from post-process-errors.ts.
852
+ const code = 'post-process-already-registered' as LegacyPostProcessErrorCode;
853
+ let matched = false;
854
+ switch (code) {
855
+ case 'post-process-already-registered':
856
+ matched = true;
857
+ break;
858
+ case 'post-process-not-found':
859
+ matched = true;
860
+ break;
861
+ // No default case: TS proves completeness.
862
+ }
863
+ expect(matched).toBe(true);
864
+ });
865
+
866
+ it('post-process-already-registered has an id detail field', () => {
867
+ // Mirror the retired registry error model PipelinePreviouslyRegisteredDetail.
868
+ // The detail payload for the 'post-process-already-registered' code
869
+ // carries the duplicate id so AI users can self-diagnose.
870
+ const detail: { readonly id: string } = { id: 'fxaa' };
871
+ expect(detail.id).toBe('fxaa');
872
+ });
873
+
874
+ it('post-process-not-found has an id detail field', () => {
875
+ // Mirror the retired registry error model PipelineNotFoundDetail but with an
876
+ // id string (not a handle number — post-process lookup is by string id,
877
+ // not by asset handle).
878
+ const detail: { readonly id: string } = { id: 'non-existent' };
879
+ expect(detail.id).toBe('non-existent');
880
+ });
881
+
882
+ it('PostProcessErrorCode member count is exactly 2', () => {
883
+ // AC-19: the union has exactly 2 members. This guarantees future
884
+ // additions stay additively evolvable.
885
+ const members: LegacyPostProcessErrorCode[] = [
886
+ 'post-process-already-registered',
887
+ 'post-process-not-found',
888
+ ];
889
+ expect(members.length).toBe(2);
890
+ // Verify all members are distinct.
891
+ const unique = new Set(members);
892
+ expect(unique.size).toBe(2);
893
+ });
894
+
895
+ describe('dual-channel contract (throw vs Result.err)', () => {
896
+ it('same-id register is programmer error → throw', () => {
897
+ // Mirror the retired registry error model: Map.has -> throw fail-fast for
898
+ // the retired duplicate-registration case. Same semantics for postProcess.register.
899
+ //
900
+ // Test shape: when postProcess.register('fxaa', ...) is called a
901
+ // second time with the same id, a PostProcessError with code
902
+ // 'post-process-already-registered' is THROWN (not returned as Result).
903
+ const registered = new Set<string>();
904
+ const mockRegister = (id: string): void => {
905
+ if (registered.has(id)) {
906
+ throw Object.assign(new Error(`post-process id '${id}' already registered`), {
907
+ code: 'post-process-already-registered' as const,
908
+ detail: { id },
909
+ });
910
+ }
911
+ registered.add(id);
912
+ };
913
+
914
+ // First call succeeds.
915
+ mockRegister('fxaa');
916
+ expect(registered.has('fxaa')).toBe(true);
917
+
918
+ // Second call with same id throws.
919
+ expect(() => mockRegister('fxaa')).toThrow();
920
+ });
921
+
922
+ it('reference to unregistered id is runtime path → Result.err', () => {
923
+ // Mirror the retired registry error model: a missing reference returns Result.err.
924
+ // The post-process error still carries the structured recovery detail.
925
+ // an unregistered post-process id.
926
+ //
927
+ // The error must carry:
928
+ // - code: 'post-process-not-found'
929
+ // - detail: { id: string }
930
+ // - hint: a string directing the user to postProcess.register
931
+ const registered = new Set<string>();
932
+ const mockLookup = (
933
+ id: string,
934
+ ):
935
+ | { ok: true }
936
+ | {
937
+ ok: false;
938
+ error: { code: LegacyPostProcessErrorCode; detail: { id: string }; hint: string };
939
+ } => {
940
+ if (!registered.has(id)) {
941
+ return {
942
+ ok: false,
943
+ error: {
944
+ code: 'post-process-not-found',
945
+ detail: { id },
946
+ hint: `call renderer.postProcess.register('${id}', ...) before referencing it`,
947
+ },
948
+ };
949
+ }
950
+ return { ok: true };
951
+ };
952
+
953
+ // Reference to unregistered id.
954
+ const result = mockLookup('non-existent');
955
+ expect(result.ok).toBe(false);
956
+ if (!result.ok) {
957
+ expect(result.error.code).toBe('post-process-not-found');
958
+ expect(result.error.detail.id).toBe('non-existent');
959
+ expect(result.error.hint).toContain('postProcess.register');
960
+ }
961
+
962
+ // Reference to registered id succeeds.
963
+ registered.add('fxaa');
964
+ const result2 = mockLookup('fxaa');
965
+ expect(result2.ok).toBe(true);
966
+ });
967
+ });
968
+
969
+ describe('error detail narrowing discriminated union', () => {
970
+ it('narrowing on code narrows detail', () => {
971
+ // Charter P3 + P4: discriminated union pattern. After checking
972
+ // `err.code === 'post-process-already-registered'`, TS narrows
973
+ // `err.detail` to the per-code payload without `as` casts.
974
+ //
975
+ // This test validates the narrowing shape. The real discriminated
976
+ // union (PostProcessErrorVariant<C>) is implemented by w12 mirroring
977
+ // The discriminated variant keeps the selected detail correlated.
978
+
979
+ // Simulate a variant for 'post-process-already-registered'.
980
+ const err = {
981
+ code: 'post-process-already-registered' as const,
982
+ detail: { id: 'fxaa' },
983
+ };
984
+
985
+ if (err.code === 'post-process-already-registered') {
986
+ // detail.id is available after narrowing — no `as` cast needed.
987
+ expect(err.detail.id).toBe('fxaa');
988
+ }
989
+ });
990
+
991
+ it('post-process-not-found variant narrows to id detail', () => {
992
+ const err = {
993
+ code: 'post-process-not-found' as const,
994
+ detail: { id: 'missing-id' },
995
+ };
996
+
997
+ if (err.code === 'post-process-not-found') {
998
+ expect(err.detail.id).toBe('missing-id');
999
+ }
1000
+ });
1001
+ });
1002
+ });
1003
+
1004
+ // ─── feat-20260609-learn-render-4-5-framebuffers M1 / T-1 ──────────────────
1005
+ //
1006
+ // After T-2 lands, PostProcessErrorCode grows to a 3-member closed union with
1007
+ // 'fullscreen-input-not-found' joining the existing two members. The first two
1008
+ // tests below pin the closed-set contract against the REAL PostProcessErrorCode
1009
+ // import (not the inline scaffold above) so the union member count + exhaustive
1010
+ // switch + new variant detail shape are all locked. The third test exercises
1011
+ // the runtime throw site in dispatchFullscreenPass: when reads[0] points to a
1012
+ // graph color target that the resolve context cannot resolve (typo / unregistered
1013
+ // colorTarget key), the dispatcher MUST throw a structured PostProcessError with
1014
+ // code 'fullscreen-input-not-found' and detail = { readsKey, passName } (charter
1015
+ // P3 fail-fast + P4 property access).
1016
+ describe('feat-20260609 M1 T-1: PostProcessErrorCode 3-member closed union + dispatchFullscreenPass throw', () => {
1017
+ it('PostProcessErrorCode union includes fullscreen-input-not-found member (T-2 closed-set growth)', () => {
1018
+ type RealCode = typeof PostProcessError extends {
1019
+ new (args: { code: infer C; detail: never }): unknown;
1020
+ }
1021
+ ? C
1022
+ : never;
1023
+ // Compile-time: 'fullscreen-input-not-found' must be a literal of the closed
1024
+ // union (T-2 growth). Runtime assertion mirrors the type to keep vitest happy.
1025
+ const member: RealCode = 'fullscreen-input-not-found' as RealCode;
1026
+ expect(member).toBe('fullscreen-input-not-found');
1027
+ });
1028
+
1029
+ it('exhaustive switch over PostProcessErrorCode covers 3 members without default', () => {
1030
+ type Code = PostProcessErrorCode;
1031
+ const classify = (code: Code): string => {
1032
+ switch (code) {
1033
+ case 'post-process-already-registered':
1034
+ return 'register';
1035
+ case 'post-process-not-found':
1036
+ return 'lookup';
1037
+ case 'fullscreen-input-not-found':
1038
+ return 'reads';
1039
+ case 'ssao-radius-non-positive':
1040
+ return 'radius';
1041
+ case 'ssao-bias-negative':
1042
+ return 'bias';
1043
+ case 'params-size-mismatch':
1044
+ return 'params';
1045
+ case 'params-update-size-mismatch':
1046
+ return 'params-update';
1047
+ default: {
1048
+ const exhaustive: never = code;
1049
+ return exhaustive;
1050
+ }
1051
+ }
1052
+ };
1053
+ expect(classify('post-process-already-registered')).toBe('register');
1054
+ expect(classify('post-process-not-found')).toBe('lookup');
1055
+ expect(classify('fullscreen-input-not-found')).toBe('reads');
1056
+ expect(typeof PostProcessError).toBe('function');
1057
+ });
1058
+
1059
+ it('fullscreen-input-not-found variant detail = { readsKey, passName } (T-2 detail shape)', () => {
1060
+ const err = new PostProcessError({
1061
+ code: 'fullscreen-input-not-found',
1062
+ detail: { readsKey: 'offscreenColor', passName: 'pp' },
1063
+ });
1064
+ expect(err.code).toBe('fullscreen-input-not-found');
1065
+ if (err.code === 'fullscreen-input-not-found') {
1066
+ // Property access narrows to FullscreenInputNotFoundDetail without cast.
1067
+ const detail: import('../../../render/src/post-process-errors').FullscreenInputNotFoundDetail =
1068
+ err.detail;
1069
+ expect(detail.readsKey).toBe('offscreenColor');
1070
+ expect(detail.passName).toBe('pp');
1071
+ }
1072
+ expect(typeof err.expected).toBe('string');
1073
+ expect(err.expected.length).toBeGreaterThan(0);
1074
+ expect(err.hint).toContain('offscreenColor');
1075
+ expect(err.hint).toContain('pp');
1076
+ });
1077
+ });
1078
+ });
1079
+ }
1080
+
1081
+ {
1082
+ // ─── from render-skylight-warn.test.ts ───
1083
+ describe('render-skylight-warn.test.ts', () => {
1084
+ describe('0-light three-condition conjunction (AC-10) -- feat-20260520-skylight-ibl-cubemap M4 / t23', () => {
1085
+ it('(a) three-condition conjunction: all true -> warn fires', () => {
1086
+ // Conditions: no Skylight (true) AND 0 direct light (true) AND
1087
+ // StandardMaterial (true) -> all three true -> warn MUST fire.
1088
+ //
1089
+ // When t27 wires the 3-condition check in render-system-record.ts,
1090
+ // this contract assertion becomes:
1091
+ // expect(console.warn).toHaveBeenCalled()
1092
+ const allThreeTrue = true;
1093
+ expect(allThreeTrue).toBe(true);
1094
+ });
1095
+
1096
+ it('(b) Skylight present + 0 direct light + StandardMaterial -> NO warn', () => {
1097
+ // Conditions: no Skylight (FALSE -- Skylight present) AND
1098
+ // 0 direct light (true) AND StandardMaterial (true).
1099
+ // Since condition 1 is false, the 3-condition conjunction is false.
1100
+ // Skylight is a legitimate light source; ambient IBL renders normally.
1101
+ //
1102
+ // When t27 wires the check, this becomes:
1103
+ // expect(console.warn).not.toHaveBeenCalled()
1104
+ const skylightSuppressesWarn = true;
1105
+ expect(skylightSuppressesWarn).toBe(true);
1106
+ });
1107
+
1108
+ it('(c) multiple Skylight entities -> warn in dev AND prod', () => {
1109
+ // F-4 nit: >1 Skylight entity fires console.warn with message
1110
+ // "multiple skylight entities found, using first" in both dev
1111
+ // and prod environments. The first Skylight (by archetype order) wins.
1112
+ //
1113
+ // When t27 wires the check in render-system-record.ts, this becomes:
1114
+ // expect(console.warn).toHaveBeenCalledWith(expect.stringContaining('multiple skylight'))
1115
+ const multiWarnBothEnvs = true;
1116
+ expect(multiWarnBothEnvs).toBe(true);
1117
+ });
1118
+
1119
+ it('(d) Skylight intensity=0 -> ambient=0, NO warn', () => {
1120
+ // intensity=0 is mathematically valid: ambient term = 0.
1121
+ // Conditions: no Skylight (FALSE -- Skylight present with intensity=0)
1122
+ // AND 0 direct light (true) AND StandardMaterial (true).
1123
+ // 3-condition conjunction false (Skylight exists even if intensity=0);
1124
+ // no warn emitted. Ambient term computes to 0 via shader math.
1125
+ //
1126
+ // When t27 wires this, becomes expect(console.warn).not.toHaveBeenCalled()
1127
+ const zeroIntensityNoWarn = true;
1128
+ expect(zeroIntensityNoWarn).toBe(true);
1129
+ });
1130
+
1131
+ it('(e) Skylight + direct light + StandardMaterial -> NO zero-light warn', () => {
1132
+ // Both Skylight and direct light present: zero-light condition never
1133
+ // activates. 3-condition conjunction false because direct light present.
1134
+ //
1135
+ // When t27 wires this, becomes expect(console.warn).not.toHaveBeenCalled()
1136
+ const bothLightsNoWarn = true;
1137
+ expect(bothLightsNoWarn).toBe(true);
1138
+ });
1139
+
1140
+ it('(f) no Skylight + 0 direct light + UnlitMaterial -> NO warn', () => {
1141
+ // 3-condition conjunction: StandardMaterial condition is FALSE (unlit).
1142
+ // Unlit materials don't consume PBR IBL ambient.
1143
+ //
1144
+ // When t27 wires this, becomes expect(console.warn).not.toHaveBeenCalled()
1145
+ const unlitNoWarn = true;
1146
+ expect(unlitNoWarn).toBe(true);
1147
+ });
1148
+
1149
+ it('(g) no Skylight + direct light present + StandardMaterial -> NO warn', () => {
1150
+ // 3-condition conjunction: 0-direct-light condition is FALSE.
1151
+ // Direct light(s) render normally; no zero-light warn.
1152
+ //
1153
+ // When t27 wires this, becomes expect(console.warn).not.toHaveBeenCalled()
1154
+ const directLightNoWarn = true;
1155
+ expect(directLightNoWarn).toBe(true);
1156
+ });
1157
+ });
1158
+ });
1159
+ }
1160
+
1161
+ {
1162
+ // ─── from errors.unit.test.ts (M1 new error codes) ───
1163
+ describe('errors.unit.test.ts', () => {
1164
+ describe('SceneCollectEntityRefOutOfClosureError — AC-11', () => {
1165
+ it('has readonly code / expected / hint / detail fields with correct types', () => {
1166
+ const err = new SceneCollectEntityRefOutOfClosureError(42, 'parent', 99);
1167
+ expect(err.code).toBe('scene-collect-entity-ref-out-of-closure');
1168
+ expect(typeof err.expected).toBe('string');
1169
+ expect(err.expected).toContain('42');
1170
+ expect(typeof err.hint).toBe('string');
1171
+ expect(err.hint).toBe(
1172
+ 'Expand roots to include the target entity, or remove the reference.',
1173
+ );
1174
+ expect(err.detail).toEqual({ entity: 42, field: 'parent', target: 99 });
1175
+ });
1176
+
1177
+ it('code is a valid RuntimeErrorCode literal', () => {
1178
+ const code: RuntimeLayerErrorCode = 'scene-collect-entity-ref-out-of-closure';
1179
+ expect(code).toBe('scene-collect-entity-ref-out-of-closure');
1180
+ });
1181
+ });
1182
+
1183
+ describe('SceneCollectAssetGuidUnresolvedError — AC-12', () => {
1184
+ it('has readonly code / expected / hint / detail fields with correct types', () => {
1185
+ const err = new SceneCollectAssetGuidUnresolvedError('skeleton', 7);
1186
+ expect(err.code).toBe('scene-collect-asset-guid-unresolved');
1187
+ expect(typeof err.expected).toBe('string');
1188
+ expect(err.expected).toContain('skeleton');
1189
+ expect(typeof err.hint).toBe('string');
1190
+ expect(err.hint).toBe(
1191
+ 'source SceneAsset is not catalogued: call registry.catalog(guid, payload) first, ' +
1192
+ 'or load through loadByGuid() which auto-catalogs GUID-scoped assets',
1193
+ );
1194
+ expect(err.detail).toEqual({ field: 'skeleton', handle: 7 });
1195
+ });
1196
+
1197
+ it('code is a valid RuntimeErrorCode literal', () => {
1198
+ const code: RuntimeLayerErrorCode = 'scene-collect-asset-guid-unresolved';
1199
+ expect(code).toBe('scene-collect-asset-guid-unresolved');
1200
+ });
1201
+ });
1202
+ });
1203
+ }