@forgeax/engine-render 0.1.27 → 0.1.29

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 (655) hide show
  1. package/README.md +842 -124
  2. package/dist/assembly/atmosphere-shader-prewarm.d.ts +6 -0
  3. package/dist/assembly/atmosphere-shader-prewarm.d.ts.map +1 -0
  4. package/dist/assembly/dynamic-geometry-host.d.ts +52 -0
  5. package/dist/assembly/dynamic-geometry-host.d.ts.map +1 -0
  6. package/dist/assembly/dynamic-geometry-runtime.d.ts +20 -0
  7. package/dist/assembly/dynamic-geometry-runtime.d.ts.map +1 -0
  8. package/dist/assembly/factory.d.ts +3 -1
  9. package/dist/assembly/factory.d.ts.map +1 -1
  10. package/dist/assembly/gpu-driven-owner.d.ts +10 -0
  11. package/dist/assembly/gpu-driven-owner.d.ts.map +1 -0
  12. package/dist/assembly/host-contract.d.ts +19 -0
  13. package/dist/assembly/host-contract.d.ts.map +1 -1
  14. package/dist/assembly/material/assembly.d.ts +7 -0
  15. package/dist/assembly/material/assembly.d.ts.map +1 -1
  16. package/dist/assembly/material-shader-policy.d.ts +3 -1
  17. package/dist/assembly/material-shader-policy.d.ts.map +1 -1
  18. package/dist/assembly/recovery/failure-location.d.ts +12 -0
  19. package/dist/assembly/recovery/failure-location.d.ts.map +1 -0
  20. package/dist/assembly/recovery/renderer-recover.d.ts +6 -4
  21. package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -1
  22. package/dist/assembly/renderer-facade.d.ts.map +1 -1
  23. package/dist/assembly/renderer-feature-inspection.d.ts +5 -0
  24. package/dist/assembly/renderer-feature-inspection.d.ts.map +1 -0
  25. package/dist/assembly/renderer-frame-transaction.d.ts +19 -0
  26. package/dist/assembly/renderer-frame-transaction.d.ts.map +1 -1
  27. package/dist/assembly/renderer-helpers.d.ts +3 -0
  28. package/dist/assembly/renderer-helpers.d.ts.map +1 -1
  29. package/dist/assembly/renderer-inspection.d.ts +4 -0
  30. package/dist/assembly/renderer-inspection.d.ts.map +1 -1
  31. package/dist/assembly/shader-prewarm-policy.d.ts +6 -0
  32. package/dist/assembly/shader-prewarm-policy.d.ts.map +1 -1
  33. package/dist/assembly/skin-palette-owner.d.ts +8 -0
  34. package/dist/assembly/skin-palette-owner.d.ts.map +1 -0
  35. package/dist/assembly/ssr-shader-prewarm.d.ts +6 -0
  36. package/dist/assembly/ssr-shader-prewarm.d.ts.map +1 -0
  37. package/dist/assembly/webgpu-device-loss.d.ts +15 -0
  38. package/dist/assembly/webgpu-device-loss.d.ts.map +1 -0
  39. package/dist/assembly/webgpu-pbr-ready.d.ts +38 -0
  40. package/dist/assembly/webgpu-pbr-ready.d.ts.map +1 -0
  41. package/dist/assembly/webgpu-ready-mipmap.d.ts +11 -0
  42. package/dist/assembly/webgpu-ready-mipmap.d.ts.map +1 -0
  43. package/dist/assembly/webgpu-ready.d.ts +7 -3
  44. package/dist/assembly/webgpu-ready.d.ts.map +1 -1
  45. package/dist/assembly/webgpu-renderer-bootstrap.d.ts +10 -0
  46. package/dist/assembly/webgpu-renderer-bootstrap.d.ts.map +1 -0
  47. package/dist/assembly/webgpu-renderer-contract.d.ts +62 -0
  48. package/dist/assembly/webgpu-renderer-contract.d.ts.map +1 -0
  49. package/dist/assembly/webgpu-renderer-guards.d.ts +9 -0
  50. package/dist/assembly/webgpu-renderer-guards.d.ts.map +1 -0
  51. package/dist/assembly/webgpu-renderer-observation.d.ts +6 -0
  52. package/dist/assembly/webgpu-renderer-observation.d.ts.map +1 -0
  53. package/dist/assembly/webgpu-renderer-recovery-failure.d.ts +12 -0
  54. package/dist/assembly/webgpu-renderer-recovery-failure.d.ts.map +1 -0
  55. package/dist/assembly/webgpu-renderer.d.ts +7 -141
  56. package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
  57. package/dist/assembly/webgpu-vertex-layouts.d.ts +28 -84
  58. package/dist/assembly/webgpu-vertex-layouts.d.ts.map +1 -1
  59. package/dist/authoring.mjs +3 -3
  60. package/dist/{chunk-ADHHVYLW.mjs → chunk-33XZFTQU.mjs} +245 -39
  61. package/dist/chunk-33XZFTQU.mjs.map +1 -0
  62. package/dist/{chunk-GF523LHF.mjs → chunk-BLXGONR2.mjs} +30 -12
  63. package/dist/chunk-BLXGONR2.mjs.map +1 -0
  64. package/dist/{chunk-KM2NOX2I.mjs → chunk-BQVQOQJH.mjs} +958 -254
  65. package/dist/chunk-BQVQOQJH.mjs.map +1 -0
  66. package/dist/{chunk-HKXTW355.mjs → chunk-BTWH7BP4.mjs} +386 -17
  67. package/dist/chunk-BTWH7BP4.mjs.map +1 -0
  68. package/dist/{chunk-TMIASV2N.mjs → chunk-FUODTTIA.mjs} +3934 -676
  69. package/dist/chunk-FUODTTIA.mjs.map +1 -0
  70. package/dist/{chunk-FJ6P52EE.mjs → chunk-ILQ5MEZF.mjs} +3 -17
  71. package/dist/chunk-ILQ5MEZF.mjs.map +1 -0
  72. package/dist/{chunk-E34VL5VI.mjs → chunk-JQTSEI3Z.mjs} +3 -3
  73. package/dist/{chunk-E34VL5VI.mjs.map → chunk-JQTSEI3Z.mjs.map} +1 -1
  74. package/dist/{chunk-GE3SDD72.mjs → chunk-NICXSHAT.mjs} +16 -38
  75. package/dist/chunk-NICXSHAT.mjs.map +1 -0
  76. package/dist/{chunk-ZZ4YQ474.mjs → chunk-NRNXBLIJ.mjs} +577 -7
  77. package/dist/chunk-NRNXBLIJ.mjs.map +1 -0
  78. package/dist/{chunk-JDWARUOI.mjs → chunk-PA5DRJNI.mjs} +17 -3
  79. package/dist/chunk-PA5DRJNI.mjs.map +1 -0
  80. package/dist/{chunk-XDP6XE5V.mjs → chunk-S4DNKQYQ.mjs} +1118 -446
  81. package/dist/chunk-S4DNKQYQ.mjs.map +1 -0
  82. package/dist/components/camera.d.ts +62 -2
  83. package/dist/components/camera.d.ts.map +1 -1
  84. package/dist/components/index.d.ts +1 -0
  85. package/dist/components/index.d.ts.map +1 -1
  86. package/dist/components/instances.d.ts +6 -20
  87. package/dist/components/instances.d.ts.map +1 -1
  88. package/dist/components/scene-instance.d.ts +2 -1
  89. package/dist/components/scene-instance.d.ts.map +1 -1
  90. package/dist/components/screen-space-reflection.d.ts +14 -0
  91. package/dist/components/screen-space-reflection.d.ts.map +1 -0
  92. package/dist/construct-renderer.mjs +13805 -7186
  93. package/dist/construct-renderer.mjs.map +1 -1
  94. package/dist/device/gpu-residency.d.ts +11 -17
  95. package/dist/device/gpu-residency.d.ts.map +1 -1
  96. package/dist/device/mesh-residency-lifetime.d.ts +21 -0
  97. package/dist/device/mesh-residency-lifetime.d.ts.map +1 -0
  98. package/dist/dynamic-geometry.d.ts +125 -0
  99. package/dist/dynamic-geometry.d.ts.map +1 -0
  100. package/dist/environment/background.d.ts +6 -0
  101. package/dist/environment/background.d.ts.map +1 -0
  102. package/dist/errors/gpu-driven.d.ts +6 -3
  103. package/dist/errors/gpu-driven.d.ts.map +1 -1
  104. package/dist/errors/render.d.ts +7 -4
  105. package/dist/errors/render.d.ts.map +1 -1
  106. package/dist/extract/camera.d.ts.map +1 -1
  107. package/dist/extract/gpu-driven.d.ts +33 -3
  108. package/dist/extract/gpu-driven.d.ts.map +1 -1
  109. package/dist/features/host.d.ts +23 -1
  110. package/dist/features/host.d.ts.map +1 -1
  111. package/dist/features/noise-texture.d.ts +17 -0
  112. package/dist/features/noise-texture.d.ts.map +1 -0
  113. package/dist/features/plan.d.ts +83 -2
  114. package/dist/features/plan.d.ts.map +1 -1
  115. package/dist/features/prepared-gpu-work.d.ts +37 -4
  116. package/dist/features/prepared-gpu-work.d.ts.map +1 -1
  117. package/dist/features/prepared-graphics-store.d.ts.map +1 -1
  118. package/dist/features/prepared-graphics.d.ts +5 -0
  119. package/dist/features/prepared-graphics.d.ts.map +1 -1
  120. package/dist/features/render-graph-compute.d.ts +5 -3
  121. package/dist/features/render-graph-compute.d.ts.map +1 -1
  122. package/dist/features/render-graph-contribution.d.ts +11 -3
  123. package/dist/features/render-graph-contribution.d.ts.map +1 -1
  124. package/dist/features/render-graph-raster.d.ts +19 -3
  125. package/dist/features/render-graph-raster.d.ts.map +1 -1
  126. package/dist/features/types.d.ts +11 -0
  127. package/dist/features/types.d.ts.map +1 -1
  128. package/dist/fullscreen-post-process-pass.d.ts +2 -0
  129. package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
  130. package/dist/gpu-driven/batch-topology.d.ts +34 -1
  131. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  132. package/dist/gpu-driven/pbr-program.d.ts +10 -0
  133. package/dist/gpu-driven/pbr-program.d.ts.map +1 -0
  134. package/dist/gpu-driven/prepared-draw.d.ts.map +1 -1
  135. package/dist/gpu-driven/production-raster.d.ts +144 -23
  136. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  137. package/dist/gpu-driven/shadow-views.d.ts +126 -0
  138. package/dist/gpu-driven/shadow-views.d.ts.map +1 -0
  139. package/dist/gpu-driven/view-gpu.d.ts +18 -2
  140. package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
  141. package/dist/gpu-scene-schema.d.ts +2 -8
  142. package/dist/gpu-scene-schema.d.ts.map +1 -1
  143. package/dist/gpu-scene.d.ts +14 -0
  144. package/dist/gpu-scene.d.ts.map +1 -1
  145. package/dist/gpu-texture-usage.d.ts +1 -0
  146. package/dist/gpu-texture-usage.d.ts.map +1 -1
  147. package/dist/hdrp-buffers.d.ts +6 -0
  148. package/dist/hdrp-buffers.d.ts.map +1 -1
  149. package/dist/ibl/frame-resources.d.ts +8 -0
  150. package/dist/ibl/frame-resources.d.ts.map +1 -0
  151. package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
  152. package/dist/index.d.ts +21 -8
  153. package/dist/index.d.ts.map +1 -1
  154. package/dist/index.mjs +8 -7
  155. package/dist/index.mjs.map +1 -1
  156. package/dist/inspection-types.d.ts +164 -0
  157. package/dist/inspection-types.d.ts.map +1 -1
  158. package/dist/instances-derived-bounds.d.ts +11 -0
  159. package/dist/instances-derived-bounds.d.ts.map +1 -1
  160. package/dist/instances.d.ts +89 -44
  161. package/dist/instances.d.ts.map +1 -1
  162. package/dist/internal.d.ts +3 -0
  163. package/dist/internal.d.ts.map +1 -1
  164. package/dist/internal.mjs +118 -10
  165. package/dist/internal.mjs.map +1 -1
  166. package/dist/lifecycle.d.ts +2 -1
  167. package/dist/lifecycle.d.ts.map +1 -1
  168. package/dist/material-row.d.ts +5 -0
  169. package/dist/material-row.d.ts.map +1 -0
  170. package/dist/materials.d.ts +15 -0
  171. package/dist/materials.d.ts.map +1 -1
  172. package/dist/pbr-pipeline.d.ts +21 -5
  173. package/dist/pbr-pipeline.d.ts.map +1 -1
  174. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  175. package/dist/pipeline/standard-output/auto-exposure/capability.d.ts +12 -0
  176. package/dist/pipeline/standard-output/auto-exposure/capability.d.ts.map +1 -0
  177. package/dist/pipeline/standard-output/auto-exposure/gpu.d.ts +70 -0
  178. package/dist/pipeline/standard-output/auto-exposure/gpu.d.ts.map +1 -0
  179. package/dist/pipeline/standard-output/auto-exposure/graph.d.ts +75 -0
  180. package/dist/pipeline/standard-output/auto-exposure/graph.d.ts.map +1 -0
  181. package/dist/pipeline/standard-output/auto-exposure/inspection.d.ts +111 -0
  182. package/dist/pipeline/standard-output/auto-exposure/inspection.d.ts.map +1 -0
  183. package/dist/pipeline/standard-output/auto-exposure/oracle.d.ts +16 -0
  184. package/dist/pipeline/standard-output/auto-exposure/oracle.d.ts.map +1 -0
  185. package/dist/pipeline/standard-output/auto-exposure/preset.d.ts +14 -0
  186. package/dist/pipeline/standard-output/auto-exposure/preset.d.ts.map +1 -0
  187. package/dist/pipeline/standard-output/auto-exposure/state.d.ts +46 -0
  188. package/dist/pipeline/standard-output/auto-exposure/state.d.ts.map +1 -0
  189. package/dist/pipeline/standard-output/color-transform.d.ts +27 -0
  190. package/dist/pipeline/standard-output/color-transform.d.ts.map +1 -0
  191. package/dist/pipeline/standard-output/graph.d.ts +15 -0
  192. package/dist/pipeline/standard-output/graph.d.ts.map +1 -0
  193. package/dist/pipeline/standard-output/lut-admission.d.ts +29 -0
  194. package/dist/pipeline/standard-output/lut-admission.d.ts.map +1 -0
  195. package/dist/pipeline/standard-output/lut-gpu.d.ts +48 -0
  196. package/dist/pipeline/standard-output/lut-gpu.d.ts.map +1 -0
  197. package/dist/pipeline/standard-output/lut-state.d.ts +68 -0
  198. package/dist/pipeline/standard-output/lut-state.d.ts.map +1 -0
  199. package/dist/pipeline/standard-output/resources.d.ts +10 -0
  200. package/dist/pipeline/standard-output/resources.d.ts.map +1 -0
  201. package/dist/pipeline/standard-output/types.d.ts +39 -0
  202. package/dist/pipeline/standard-output/types.d.ts.map +1 -0
  203. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  204. package/dist/pipeline/standard-post.d.ts +2 -2
  205. package/dist/pipeline/standard-post.d.ts.map +1 -1
  206. package/dist/pipeline-builder.d.ts +2 -1
  207. package/dist/pipeline-builder.d.ts.map +1 -1
  208. package/dist/pipeline-spec-types.d.ts +4 -0
  209. package/dist/pipeline-spec-types.d.ts.map +1 -1
  210. package/dist/pipeline-spec.d.ts +1 -9
  211. package/dist/pipeline-spec.d.ts.map +1 -1
  212. package/dist/points-lines/standard-owner.d.ts +1 -1
  213. package/dist/points-lines/standard-owner.d.ts.map +1 -1
  214. package/dist/prepare/prepared-graphics-resolver.d.ts +6 -1
  215. package/dist/prepare/prepared-graphics-resolver.d.ts.map +1 -1
  216. package/dist/record/dynamic-geometry-consumption.d.ts +15 -0
  217. package/dist/record/dynamic-geometry-consumption.d.ts.map +1 -0
  218. package/dist/record/frame-lighting.d.ts +3 -3
  219. package/dist/record/frame-lighting.d.ts.map +1 -1
  220. package/dist/record/frame-snapshot.d.ts +106 -5
  221. package/dist/record/frame-snapshot.d.ts.map +1 -1
  222. package/dist/record/frame-targets.d.ts.map +1 -1
  223. package/dist/record/frame.d.ts +39 -4
  224. package/dist/record/frame.d.ts.map +1 -1
  225. package/dist/record/main-pass-geometry.d.ts +7 -8
  226. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  227. package/dist/record/main-pass-material.d.ts +38 -1
  228. package/dist/record/main-pass-material.d.ts.map +1 -1
  229. package/dist/record/main-pass-sprite-draws.d.ts +2 -10
  230. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  231. package/dist/record/main-pass.d.ts +12 -2
  232. package/dist/record/main-pass.d.ts.map +1 -1
  233. package/dist/record/mesh-ssbo.d.ts +15 -6
  234. package/dist/record/mesh-ssbo.d.ts.map +1 -1
  235. package/dist/record/prepared-material-bindings.d.ts +19 -0
  236. package/dist/record/prepared-material-bindings.d.ts.map +1 -0
  237. package/dist/record/recovery-pipeline.d.ts +2 -9
  238. package/dist/record/recovery-pipeline.d.ts.map +1 -1
  239. package/dist/record/render-context.d.ts +76 -26
  240. package/dist/record/render-context.d.ts.map +1 -1
  241. package/dist/record/shadow-pass.d.ts +62 -7
  242. package/dist/record/shadow-pass.d.ts.map +1 -1
  243. package/dist/record/skybox-post-pass.d.ts +1 -1
  244. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  245. package/dist/record/typed-frame-graph.d.ts +35 -7
  246. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  247. package/dist/record/view-ubo.d.ts +1 -0
  248. package/dist/record/view-ubo.d.ts.map +1 -1
  249. package/dist/recovery/render-system-candidate.d.ts +10 -2
  250. package/dist/recovery/render-system-candidate.d.ts.map +1 -1
  251. package/dist/recovery/render-system-roots.d.ts +11 -0
  252. package/dist/recovery/render-system-roots.d.ts.map +1 -0
  253. package/dist/recovery/types.d.ts +76 -0
  254. package/dist/recovery/types.d.ts.map +1 -0
  255. package/dist/reflection/record-owner.d.ts +0 -2
  256. package/dist/reflection/record-owner.d.ts.map +1 -1
  257. package/dist/render-contract.d.ts +185 -30
  258. package/dist/render-contract.d.ts.map +1 -1
  259. package/dist/render-graph-primitives.d.ts +1 -0
  260. package/dist/render-graph-primitives.d.ts.map +1 -1
  261. package/dist/render-pipeline.d.ts +64 -5
  262. package/dist/render-pipeline.d.ts.map +1 -1
  263. package/dist/render-system-extract-tail.d.ts +7 -5
  264. package/dist/render-system-extract-tail.d.ts.map +1 -1
  265. package/dist/render-system-extract.d.ts +101 -41
  266. package/dist/render-system-extract.d.ts.map +1 -1
  267. package/dist/render-system-projections.d.ts +22 -0
  268. package/dist/render-system-projections.d.ts.map +1 -0
  269. package/dist/render-system.d.ts +34 -77
  270. package/dist/render-system.d.ts.map +1 -1
  271. package/dist/scene/render-scene-types.d.ts +14 -7
  272. package/dist/scene/render-scene-types.d.ts.map +1 -1
  273. package/dist/scene/render-scene.d.ts +29 -11
  274. package/dist/scene/render-scene.d.ts.map +1 -1
  275. package/dist/scene/shadow-visibility.d.ts +5 -0
  276. package/dist/scene/shadow-visibility.d.ts.map +1 -0
  277. package/dist/ssr/admission.d.ts +70 -1
  278. package/dist/ssr/admission.d.ts.map +1 -1
  279. package/dist/ssr/compose.d.ts +13 -0
  280. package/dist/ssr/compose.d.ts.map +1 -0
  281. package/dist/ssr/errors.d.ts +35 -0
  282. package/dist/ssr/errors.d.ts.map +1 -1
  283. package/dist/ssr/graph.d.ts +60 -0
  284. package/dist/ssr/graph.d.ts.map +1 -0
  285. package/dist/ssr/history.d.ts +97 -0
  286. package/dist/ssr/history.d.ts.map +1 -0
  287. package/dist/ssr/hiz.d.ts +37 -0
  288. package/dist/ssr/hiz.d.ts.map +1 -0
  289. package/dist/ssr/inspection.d.ts +42 -0
  290. package/dist/ssr/inspection.d.ts.map +1 -0
  291. package/dist/ssr/resources.d.ts +58 -0
  292. package/dist/ssr/resources.d.ts.map +1 -0
  293. package/dist/ssr/temporal.d.ts +38 -0
  294. package/dist/ssr/temporal.d.ts.map +1 -0
  295. package/dist/systems/skin-palette-allocator.d.ts +32 -10
  296. package/dist/systems/skin-palette-allocator.d.ts.map +1 -1
  297. package/dist/systems/skin-palette-types.d.ts +15 -0
  298. package/dist/systems/skin-palette-types.d.ts.map +1 -1
  299. package/dist/systems/transparent-dispatch.d.ts +9 -0
  300. package/dist/systems/transparent-dispatch.d.ts.map +1 -0
  301. package/dist/targets/material-source.d.ts +7 -0
  302. package/dist/targets/material-source.d.ts.map +1 -1
  303. package/dist/temporal/frame-coordinator.d.ts +7 -0
  304. package/dist/temporal/frame-coordinator.d.ts.map +1 -1
  305. package/dist/temporal/gpu.d.ts +8 -0
  306. package/dist/temporal/gpu.d.ts.map +1 -1
  307. package/dist/temporal/history.d.ts.map +1 -1
  308. package/dist/temporal/index.mjs +6 -6
  309. package/dist/temporal/inspection.d.ts.map +1 -1
  310. package/dist/temporal/standard-scene-data.d.ts +2 -1
  311. package/dist/temporal/standard-scene-data.d.ts.map +1 -1
  312. package/dist/temporal/temporal-view.d.ts +2 -0
  313. package/dist/temporal/temporal-view.d.ts.map +1 -1
  314. package/dist/typed-render-graph-primitives.d.ts +35 -1
  315. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  316. package/dist/typed-shadow-passes.d.ts +3 -1
  317. package/dist/typed-shadow-passes.d.ts.map +1 -1
  318. package/dist/volume/temporal.d.ts +6 -0
  319. package/dist/volume/temporal.d.ts.map +1 -1
  320. package/package.json +21 -20
  321. package/src/__tests__/auto-exposure-gpu-evidence.ts +291 -0
  322. package/src/__tests__/auto-exposure-inspection.unit.test.ts +80 -0
  323. package/src/__tests__/auto-exposure-transaction.integration.test.ts +99 -0
  324. package/src/__tests__/auto-exposure.browser.test.ts +33 -0
  325. package/src/__tests__/auto-exposure.dawn.test.ts +33 -0
  326. package/src/__tests__/detached-inspection.integration.test.ts +5 -0
  327. package/src/__tests__/device-scope-lifecycle.unit.test.ts +18 -0
  328. package/src/__tests__/device-scope-stale-matrix.unit.test.ts +18 -0
  329. package/src/__tests__/docs-gate-green.unit.test.ts +3 -3
  330. package/src/__tests__/dynamic-geometry-host.unit.test.ts +1123 -0
  331. package/src/__tests__/dynamic-geometry.unit.test.ts +233 -0
  332. package/src/__tests__/factory-contract.integration.test.ts +73 -22
  333. package/src/__tests__/fallback-row-stride-surface.unit.test.ts +19 -0
  334. package/src/__tests__/feature-depth-input.dawn.test.ts +121 -0
  335. package/src/__tests__/feature-noise.unit.test.ts +38 -0
  336. package/src/__tests__/fixtures/taa-resolve-pinned-baseline.wgsl +301 -0
  337. package/src/__tests__/frame-camera-selection.unit.test.ts +131 -0
  338. package/src/__tests__/frame-plan-contract.unit.test.ts +24 -0
  339. package/src/__tests__/frame-targets.unit.test.ts +24 -0
  340. package/src/__tests__/fullscreen-feature-plan.unit.test.ts +3 -0
  341. package/src/__tests__/gpu-driven-baseline.characterization.test.ts +79 -0
  342. package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +287 -6
  343. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +122 -142
  344. package/src/__tests__/gpu-driven-indirect-raster.browser.test.ts +10 -1
  345. package/src/__tests__/gpu-driven-lod-cache.unit.test.ts +264 -0
  346. package/src/__tests__/gpu-driven-mesh-world-identity.unit.test.ts +349 -0
  347. package/src/__tests__/gpu-driven-pbr.dawn.test.ts +1417 -0
  348. package/src/__tests__/gpu-driven-pbr.integration.test.ts +21 -0
  349. package/src/__tests__/gpu-driven-production-fixture.ts +96 -0
  350. package/src/__tests__/gpu-driven-production.integration.test.ts +1075 -93
  351. package/src/__tests__/gpu-driven-scaling.unit.test.ts +1 -3
  352. package/src/__tests__/gpu-driven-shadow-views.browser.test.ts +63 -0
  353. package/src/__tests__/gpu-driven-shadow-views.dawn.test.ts +294 -0
  354. package/src/__tests__/gpu-driven-shadow-views.integration.test.ts +97 -0
  355. package/src/__tests__/gpu-driven-shadow-views.unit.test.ts +168 -0
  356. package/src/__tests__/gpu-driven-skin-contract.test-d.ts +11 -0
  357. package/src/__tests__/gpu-driven-view-gpu-evidence.ts +72 -20
  358. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +30 -9
  359. package/src/__tests__/gpu-driven-view.unit.test.ts +159 -52
  360. package/src/__tests__/gpu-pass-timing.browser.test.ts +6 -1
  361. package/src/__tests__/gpu-resource-store-stride.unit.test.ts +42 -0
  362. package/src/__tests__/gpu-scene-render-graph-gpu.ts +19 -5
  363. package/src/__tests__/gpu-scene.dawn.test.ts +40 -13
  364. package/src/__tests__/gpu-scene.unit.test.ts +559 -33
  365. package/src/__tests__/instances-culling.integration.test.ts +36 -10
  366. package/src/__tests__/instances-derived-bounds.unit.test.ts +54 -0
  367. package/src/__tests__/instances-store.unit.test.ts +242 -39
  368. package/src/__tests__/ktx2-basis-gpu-consumer.dawn.test.ts +6 -0
  369. package/src/__tests__/lighting-scene-roundtrip.integration.test.ts +13 -16
  370. package/src/__tests__/lut-lkg-transaction.integration.test.ts +122 -0
  371. package/src/__tests__/material-contract-inventory.unit.test.ts +5 -6
  372. package/src/__tests__/material-dispatch-shader-id.unit.test.ts +51 -0
  373. package/src/__tests__/material-lane-selection.unit.test.ts +60 -0
  374. package/src/__tests__/material-numeric-upload.unit.test.ts +53 -0
  375. package/src/__tests__/material-texture-handles.unit.test.ts +18 -0
  376. package/src/__tests__/material-texture-source.unit.test.ts +87 -0
  377. package/src/__tests__/materials-authoring-validation.unit.test.ts +41 -0
  378. package/src/__tests__/materials-standard-contract.unit.test.ts +52 -2
  379. package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +4 -0
  380. package/src/__tests__/mesh-submission-lifetime.unit.test.ts +53 -0
  381. package/src/__tests__/normal-fallback.dawn.test.ts +122 -0
  382. package/src/__tests__/particle-material-input-layout.unit.test.ts +104 -0
  383. package/src/__tests__/physical-clearcoat.integration.test.ts +37 -3
  384. package/src/__tests__/pipeline-texture-specialization.unit.test.ts +138 -0
  385. package/src/__tests__/point-shadow-inspection.unit.test.ts +2 -0
  386. package/src/__tests__/prepared-gpu-driven-pbr-contract.test-d.ts +31 -0
  387. package/src/__tests__/prepared-gpu-driven-pbr-contract.unit.test.ts +39 -0
  388. package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +35 -4
  389. package/src/__tests__/prepared-graphics-capability.unit.test.ts +21 -0
  390. package/src/__tests__/prepared-graphics-pipeline-warmup.unit.test.ts +6 -2
  391. package/src/__tests__/profiler-phase-catalog.test.ts +5 -0
  392. package/src/__tests__/public-surface.test-d.ts +3 -0
  393. package/src/__tests__/recovery-candidate-prepare.contract.test.ts +30 -12
  394. package/src/__tests__/reflection-fallback-async-publication.unit.test.ts +104 -0
  395. package/src/__tests__/reflection-probe-record-owner.characterization.unit.test.ts +2 -1
  396. package/src/__tests__/render-error-code-owner.test-d.ts +4 -0
  397. package/src/__tests__/render-error-detail-ssot.test-d.ts +22 -0
  398. package/src/__tests__/render-error-exhaustive.test-d.ts +15 -0
  399. package/src/__tests__/render-feature-gpu-work.integration.test.ts +285 -2
  400. package/src/__tests__/render-feature-plan.unit.test.ts +245 -0
  401. package/src/__tests__/render-feature-shadow-order.unit.test.ts +228 -0
  402. package/src/__tests__/render-feature-zero-work.unit.test.ts +35 -1
  403. package/src/__tests__/render-graph-fallback.integration.test.ts +2 -2
  404. package/src/__tests__/render-owner-cohesion.unit.test.ts +55 -0
  405. package/src/__tests__/render-scene-mixed-updates.integration.test.ts +426 -0
  406. package/src/__tests__/render-scene-projection.unit.test.ts +914 -23
  407. package/src/__tests__/render-scene-resource-updates.integration.test.ts +214 -0
  408. package/src/__tests__/render-scene-skin-lifecycle.integration.test.ts +173 -0
  409. package/src/__tests__/render-scene-temporal-retry.unit.test.ts +133 -0
  410. package/src/__tests__/render-target-material-source.test-d.ts +27 -3
  411. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +61 -1
  412. package/src/__tests__/renderer-frame-transaction.integration.test.ts +923 -5
  413. package/src/__tests__/shader-manifest-fixture.ts +39 -0
  414. package/src/__tests__/skin-motion-regression.unit.test.ts +0 -33
  415. package/src/__tests__/skin-palette-persistent.integration.test.ts +28 -0
  416. package/src/__tests__/skin-palette-persistent.unit.test.ts +247 -0
  417. package/src/__tests__/skinned-shadow-caster.test.ts +118 -14
  418. package/src/__tests__/specular-environment-scale.dawn.test.ts +156 -0
  419. package/src/__tests__/ssr-admission-matrix.integration.test.ts +7 -1
  420. package/src/__tests__/ssr-confidence-compose.unit.test.ts +84 -0
  421. package/src/__tests__/ssr-coordinate-oracle.unit.test.ts +28 -0
  422. package/src/__tests__/ssr-format-probe-gating.integration.test.ts +82 -0
  423. package/src/__tests__/ssr-gpu-dispatch.browser.test.ts +91 -0
  424. package/src/__tests__/ssr-gpu-dispatch.dawn.test.ts +112 -0
  425. package/src/__tests__/ssr-gpu-dispatch.ts +946 -0
  426. package/src/__tests__/ssr-graph-topology.unit.test.ts +104 -0
  427. package/src/__tests__/ssr-history-filter.dawn.test.ts +198 -0
  428. package/src/__tests__/ssr-hit-color.dawn.test.ts +273 -0
  429. package/src/__tests__/ssr-hiz-odd-reduction.dawn.test.ts +569 -0
  430. package/src/__tests__/ssr-hiz-rescue.dawn.test.ts +279 -0
  431. package/src/__tests__/ssr-hiz.browser.test.ts +150 -0
  432. package/src/__tests__/ssr-hiz.dawn.test.ts +150 -0
  433. package/src/__tests__/ssr-hiz.unit.test.ts +56 -0
  434. package/src/__tests__/ssr-inspection.unit.test.ts +105 -0
  435. package/src/__tests__/ssr-memory-budget.unit.test.ts +45 -0
  436. package/src/__tests__/ssr-performance-sequence-aggregation.unit.test.ts +15 -0
  437. package/src/__tests__/ssr-public-surface.test-d.ts +37 -0
  438. package/src/__tests__/ssr-receiver-coverage.dawn.test.ts +387 -0
  439. package/src/__tests__/ssr-reflection-mip-reducer.dawn.test.ts +309 -0
  440. package/src/__tests__/ssr-reprojection-probe.ts +90 -0
  441. package/src/__tests__/ssr-roughness-mip.dawn.test.ts +163 -0
  442. package/src/__tests__/ssr-shader-prewarm.unit.test.ts +59 -0
  443. package/src/__tests__/ssr-spatial.integration.test.ts +141 -0
  444. package/src/__tests__/ssr-stable-lattice.dawn.test.ts +475 -0
  445. package/src/__tests__/ssr-temporal-history.unit.test.ts +202 -0
  446. package/src/__tests__/ssr-texel-crossing.dawn.test.ts +448 -0
  447. package/src/__tests__/ssr-zero-work.integration.test.ts +87 -0
  448. package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +44 -12
  449. package/src/__tests__/standard-clustered-pipeline.unit.test.ts +29 -0
  450. package/src/__tests__/standard-output-chain-contract.unit.test.ts +99 -0
  451. package/src/__tests__/standard-output-chain.integration.test.ts +73 -0
  452. package/src/__tests__/standard-output-gpu-evidence.ts +205 -0
  453. package/src/__tests__/standard-output.browser.test.ts +15 -0
  454. package/src/__tests__/standard-output.dawn.test.ts +15 -0
  455. package/src/__tests__/standard-pbr-artifact-assembly.unit.test.ts +38 -0
  456. package/src/__tests__/standard-pipeline.integration.test.ts +320 -3
  457. package/src/__tests__/standard-profile-oracle.unit.test.ts +22 -0
  458. package/src/__tests__/standard-surface-lighting.unit.test.ts +41 -0
  459. package/src/__tests__/standard-texture-specialization.dawn.test.ts +141 -0
  460. package/src/__tests__/surface-adjacent-regressions.integration.test.ts +6 -3
  461. package/src/__tests__/taa-coverage-edge.dawn.test.ts +193 -0
  462. package/src/__tests__/taa-history-clipping.dawn.test.ts +328 -0
  463. package/src/__tests__/taa-neighborhood-equivalence.dawn.test.ts +778 -0
  464. package/src/__tests__/taa-stability-feedback.browser.test.ts +23 -0
  465. package/src/__tests__/taa-stability-feedback.dawn.test.ts +68 -0
  466. package/src/__tests__/taa-stability-feedback.ts +341 -0
  467. package/src/__tests__/temporal-history-lifecycle.unit.test.ts +49 -7
  468. package/src/__tests__/temporal-reset-matrix.unit.test.ts +9 -0
  469. package/src/__tests__/temporal-transaction.integration.test.ts +28 -0
  470. package/src/__tests__/texture-usage-surface.unit.test.ts +4 -2
  471. package/src/__tests__/typed-pipeline-topology.unit.test.ts +109 -4
  472. package/src/__tests__/typed-scene-pass-material-cache.integration.test.ts +126 -0
  473. package/src/__tests__/vertex-layouts-ssot.unit.test.ts +47 -0
  474. package/src/__tests__/visibility-extract.perf.test.ts +20 -8
  475. package/src/__tests__/visibility-instances.integration.test.ts +19 -7
  476. package/src/__tests__/visibility-producer-matrix.integration.test.ts +20 -9
  477. package/src/assembly/atmosphere-shader-prewarm.ts +38 -0
  478. package/src/assembly/dynamic-geometry-host.ts +887 -0
  479. package/src/assembly/dynamic-geometry-runtime.ts +71 -0
  480. package/src/assembly/factory.ts +4 -0
  481. package/src/assembly/gpu-driven-owner.ts +29 -0
  482. package/src/assembly/host-contract.ts +36 -0
  483. package/src/assembly/material/assembly.ts +38 -0
  484. package/src/assembly/material-shader-policy.ts +71 -28
  485. package/src/assembly/recovery/failure-location.ts +62 -0
  486. package/src/assembly/recovery/renderer-recover.ts +19 -17
  487. package/src/assembly/renderer-facade.ts +29 -0
  488. package/src/assembly/renderer-feature-inspection.ts +13 -0
  489. package/src/assembly/renderer-frame-transaction.ts +63 -0
  490. package/src/assembly/renderer-helpers.ts +26 -0
  491. package/src/assembly/renderer-inspection.ts +6 -0
  492. package/src/assembly/shader-prewarm-policy.ts +32 -0
  493. package/src/assembly/skin-palette-owner.ts +21 -0
  494. package/src/assembly/ssr-shader-prewarm.ts +41 -0
  495. package/src/assembly/webgpu-device-loss.ts +75 -0
  496. package/src/assembly/webgpu-pbr-ready.ts +232 -0
  497. package/src/assembly/webgpu-ready-mipmap.ts +26 -0
  498. package/src/assembly/webgpu-ready.ts +142 -121
  499. package/src/assembly/webgpu-renderer-bootstrap.ts +85 -0
  500. package/src/assembly/webgpu-renderer-contract.ts +69 -0
  501. package/src/assembly/webgpu-renderer-guards.ts +32 -0
  502. package/src/assembly/webgpu-renderer-observation.ts +43 -0
  503. package/src/assembly/webgpu-renderer-recovery-failure.ts +62 -0
  504. package/src/assembly/webgpu-renderer.ts +675 -614
  505. package/src/assembly/webgpu-vertex-layouts.ts +233 -87
  506. package/src/components/__tests__/camera-exposure.contract.unit.test.ts +153 -0
  507. package/src/components/camera.ts +287 -7
  508. package/src/components/index.ts +1 -0
  509. package/src/components/instances.ts +45 -18
  510. package/src/components/scene-instance.ts +2 -1
  511. package/src/components/screen-space-reflection.ts +21 -0
  512. package/src/components/sprite-animation.ts +3 -3
  513. package/src/components/sprite-instances.ts +1 -1
  514. package/src/device/gpu-residency.ts +96 -93
  515. package/src/device/mesh-residency-lifetime.ts +73 -0
  516. package/src/dynamic-geometry.ts +736 -0
  517. package/src/environment/background.ts +263 -0
  518. package/src/errors/gpu-driven.ts +39 -5
  519. package/src/errors/render.ts +27 -3
  520. package/src/extract/camera.ts +26 -1
  521. package/src/extract/gpu-driven.ts +185 -10
  522. package/src/features/host.ts +237 -19
  523. package/src/features/noise-texture.ts +91 -0
  524. package/src/features/plan.ts +553 -24
  525. package/src/features/prepared-gpu-work.ts +631 -49
  526. package/src/features/prepared-graphics-store.ts +31 -0
  527. package/src/features/prepared-graphics.ts +5 -0
  528. package/src/features/render-graph-compute.ts +41 -8
  529. package/src/features/render-graph-contribution.ts +141 -3
  530. package/src/features/render-graph-raster.ts +143 -30
  531. package/src/features/types.ts +11 -0
  532. package/src/fullscreen-post-process-pass.ts +3 -0
  533. package/src/gpu-driven/batch-topology.ts +424 -102
  534. package/src/gpu-driven/pbr-program.ts +31 -0
  535. package/src/gpu-driven/prepared-draw.ts +29 -4
  536. package/src/gpu-driven/production-raster.ts +2267 -694
  537. package/src/gpu-driven/shadow-views.ts +539 -0
  538. package/src/gpu-driven/view-gpu.ts +188 -41
  539. package/src/gpu-scene-schema.ts +52 -11
  540. package/src/gpu-scene.ts +186 -99
  541. package/src/gpu-texture-usage.ts +1 -0
  542. package/src/hdrp-buffers.ts +72 -36
  543. package/src/ibl/frame-resources.ts +38 -0
  544. package/src/ibl/skylight-bind-group.ts +5 -4
  545. package/src/index.ts +125 -1
  546. package/src/inspection-types.ts +191 -0
  547. package/src/instances-derived-bounds.ts +258 -34
  548. package/src/instances.ts +459 -150
  549. package/src/internal.ts +22 -0
  550. package/src/lifecycle.ts +2 -0
  551. package/src/material-row.ts +145 -0
  552. package/src/materials.ts +44 -8
  553. package/src/pbr-pipeline.ts +159 -17
  554. package/src/pipeline/standard-forward-lane.ts +52 -24
  555. package/src/pipeline/standard-output/__tests__/lut-admission.unit.test.ts +103 -0
  556. package/src/pipeline/standard-output/auto-exposure/__tests__/capability.unit.test.ts +40 -0
  557. package/src/pipeline/standard-output/auto-exposure/__tests__/gpu.contract.unit.test.ts +125 -0
  558. package/src/pipeline/standard-output/auto-exposure/__tests__/graph.contract.unit.test.ts +94 -0
  559. package/src/pipeline/standard-output/auto-exposure/__tests__/oracle.test.ts +108 -0
  560. package/src/pipeline/standard-output/auto-exposure/__tests__/preset.test.ts +26 -0
  561. package/src/pipeline/standard-output/auto-exposure/capability.ts +38 -0
  562. package/src/pipeline/standard-output/auto-exposure/gpu.ts +567 -0
  563. package/src/pipeline/standard-output/auto-exposure/graph.ts +221 -0
  564. package/src/pipeline/standard-output/auto-exposure/inspection.ts +231 -0
  565. package/src/pipeline/standard-output/auto-exposure/oracle.ts +291 -0
  566. package/src/pipeline/standard-output/auto-exposure/preset.ts +28 -0
  567. package/src/pipeline/standard-output/auto-exposure/state.ts +156 -0
  568. package/src/pipeline/standard-output/color-transform.ts +124 -0
  569. package/src/pipeline/standard-output/graph.ts +139 -0
  570. package/src/pipeline/standard-output/lut-admission.ts +121 -0
  571. package/src/pipeline/standard-output/lut-gpu.ts +399 -0
  572. package/src/pipeline/standard-output/lut-state.ts +176 -0
  573. package/src/pipeline/standard-output/resources.ts +121 -0
  574. package/src/pipeline/standard-output/types.ts +58 -0
  575. package/src/pipeline/standard-pipeline.ts +192 -59
  576. package/src/pipeline/standard-post.ts +186 -27
  577. package/src/pipeline-builder.ts +16 -6
  578. package/src/pipeline-spec-types.ts +4 -0
  579. package/src/pipeline-spec.ts +37 -29
  580. package/src/points-lines/standard-owner.ts +6 -14
  581. package/src/prepare/prepared-graphics-resolver.ts +9 -2
  582. package/src/record/__tests__/material-slot-plan.unit.test.ts +48 -0
  583. package/src/record/__tests__/mesh-ssbo.unit.test.ts +40 -1
  584. package/src/record/__tests__/recovery-pipeline.unit.test.ts +2 -29
  585. package/src/record/__tests__/sprite-material-abi.unit.test.ts +36 -5
  586. package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +45 -0
  587. package/src/record/__tests__/view-ubo-layout.unit.test.ts +5 -0
  588. package/src/record/dynamic-geometry-consumption.ts +59 -0
  589. package/src/record/frame-lighting.ts +12 -11
  590. package/src/record/frame-snapshot.ts +133 -23
  591. package/src/record/frame-targets.ts +9 -0
  592. package/src/record/frame.ts +727 -133
  593. package/src/record/main-pass-geometry.ts +162 -98
  594. package/src/record/main-pass-material.ts +115 -26
  595. package/src/record/main-pass-sprite-draws.ts +18 -107
  596. package/src/record/main-pass.ts +78 -8
  597. package/src/record/material-uniforms.ts +1 -1
  598. package/src/record/mesh-ssbo.ts +59 -15
  599. package/src/record/prepared-material-bindings.ts +234 -0
  600. package/src/record/recovery-pipeline.ts +59 -136
  601. package/src/record/render-context.ts +92 -22
  602. package/src/record/shadow-pass.ts +1231 -209
  603. package/src/record/skybox-post-pass.ts +14 -3
  604. package/src/record/typed-frame-graph.ts +1006 -53
  605. package/src/record/view-ubo.ts +11 -0
  606. package/src/recovery/render-system-candidate.ts +90 -66
  607. package/src/recovery/render-system-roots.ts +113 -0
  608. package/src/recovery/types.ts +77 -0
  609. package/src/reflection/record-owner.ts +26 -54
  610. package/src/render-contract.ts +219 -25
  611. package/src/render-graph-primitives.ts +6 -2
  612. package/src/render-pipeline.ts +70 -1
  613. package/src/render-system-extract-tail.ts +409 -160
  614. package/src/render-system-extract.ts +376 -102
  615. package/src/render-system-projections.ts +241 -0
  616. package/src/render-system.ts +692 -791
  617. package/src/scene/render-scene-types.ts +13 -4
  618. package/src/scene/render-scene.ts +569 -355
  619. package/src/scene/shadow-visibility.ts +21 -0
  620. package/src/ssr/admission.ts +380 -14
  621. package/src/ssr/compose.ts +144 -0
  622. package/src/ssr/errors.ts +48 -0
  623. package/src/ssr/graph.ts +777 -0
  624. package/src/ssr/history.ts +436 -0
  625. package/src/ssr/hiz.ts +124 -0
  626. package/src/ssr/inspection.ts +124 -0
  627. package/src/ssr/resources.ts +202 -0
  628. package/src/ssr/temporal.ts +164 -0
  629. package/src/systems/skin-palette-allocator.ts +495 -148
  630. package/src/systems/skin-palette-types.ts +17 -0
  631. package/src/systems/transparent-dispatch.ts +90 -0
  632. package/src/targets/material-source.ts +34 -0
  633. package/src/temporal/coverage.ts +1 -1
  634. package/src/temporal/frame-coordinator.ts +23 -2
  635. package/src/temporal/gpu.ts +74 -12
  636. package/src/temporal/history.ts +3 -1
  637. package/src/temporal/inspection.ts +2 -1
  638. package/src/temporal/standard-scene-data.ts +3 -1
  639. package/src/temporal/temporal-view.ts +3 -0
  640. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +204 -139
  641. package/src/typed-render-graph-primitives.ts +266 -74
  642. package/src/typed-shadow-passes.ts +122 -12
  643. package/src/volume/temporal.ts +10 -0
  644. package/dist/chunk-ADHHVYLW.mjs.map +0 -1
  645. package/dist/chunk-FJ6P52EE.mjs.map +0 -1
  646. package/dist/chunk-GE3SDD72.mjs.map +0 -1
  647. package/dist/chunk-GF523LHF.mjs.map +0 -1
  648. package/dist/chunk-HKXTW355.mjs.map +0 -1
  649. package/dist/chunk-JDWARUOI.mjs.map +0 -1
  650. package/dist/chunk-KM2NOX2I.mjs.map +0 -1
  651. package/dist/chunk-TMIASV2N.mjs.map +0 -1
  652. package/dist/chunk-XDP6XE5V.mjs.map +0 -1
  653. package/dist/chunk-ZZ4YQ474.mjs.map +0 -1
  654. package/src/__tests__/instances-world-ownership.integration.test.ts +0 -62
  655. package/src/scene/__tests__/changed-block-summary.candidate.test.ts +0 -36
@@ -1,17 +1,38 @@
1
1
  import { World } from '@forgeax/engine-ecs';
2
2
  import { createRenderReadLease } from '@forgeax/engine-ecs/projection';
3
+ import { vec3 } from '@forgeax/engine-math';
3
4
  import { AssetGuid } from '@forgeax/engine-pack/guid';
4
- import { Transform } from '@forgeax/engine-scene';
5
- import { describe, expect, it } from 'vitest';
6
- import { Camera, MotionBlur } from '../components';
5
+ import { GlobalTransform, propagateTransforms, Transform } from '@forgeax/engine-scene';
6
+ import { describe, expect, it, vi } from 'vitest';
7
+ import {
8
+ Camera,
9
+ DirectionalLight,
10
+ Instances,
11
+ LightProbe,
12
+ MeshFilter,
13
+ MeshRenderer,
14
+ MotionBlur,
15
+ PointLight,
16
+ PointLightShadow,
17
+ SpotLight,
18
+ } from '../components';
19
+ import { InstanceCollectionStore } from '../instances';
7
20
  import { makeZeroCameraFallbackSnapshot } from '../record/frame-snapshot';
8
21
  import type {
22
+ DispatchEntry,
9
23
  ExtractedFrame,
10
24
  MaterialSnapshot,
11
25
  RenderableSnapshot,
26
+ ShadowCasterMembership,
12
27
  } from '../render-system-extract';
13
- import { PersistentRenderScene, RenderScene } from '../scene/render-scene';
28
+ import { extractFrames } from '../render-system-extract';
29
+ import {
30
+ type PersistentRenderCandidateRequest,
31
+ PersistentRenderScene,
32
+ RenderScene,
33
+ } from '../scene/render-scene';
14
34
  import { primitiveKey, viewKey } from '../scene/visibility/types';
35
+ import type { SkinPaletteReceipt } from '../systems/skin-palette-types';
15
36
  import { classifySceneDataCoverage } from '../temporal/coverage';
16
37
 
17
38
  const material = {} as MaterialSnapshot;
@@ -24,6 +45,19 @@ function guid(value: string) {
24
45
 
25
46
  const LOD_MESH_GUID = guid('019d0000-0000-7000-8000-000000000001');
26
47
 
48
+ const SKIN_RECEIPT = {
49
+ identity: 'skin:0:1',
50
+ generation: 1,
51
+ fence: 1,
52
+ customDataStart: 0,
53
+ storageOrUniform: 'storage',
54
+ jointCount: 1,
55
+ byteOffset: 0,
56
+ buffer: {} as never,
57
+ dirtyRanges: [],
58
+ uploadBytes: 0,
59
+ } satisfies SkinPaletteReceipt;
60
+
27
61
  function snapshot(entityKey: number, translationX: number): RenderableSnapshot {
28
62
  const world = new Float32Array(16);
29
63
  world[0] = 1;
@@ -42,7 +76,67 @@ function snapshot(entityKey: number, translationX: number): RenderableSnapshot {
42
76
  };
43
77
  }
44
78
 
79
+ function updateSnapshot(value: RenderableSnapshot) {
80
+ return {
81
+ kind: 'update' as const,
82
+ worldId: value.worldId,
83
+ entityKey: value.entityKey,
84
+ snapshot: value,
85
+ };
86
+ }
87
+
88
+ function updateInstances(value: RenderableSnapshot) {
89
+ if (value.instances === undefined) throw new Error('expected Instances payload');
90
+ return {
91
+ kind: 'update' as const,
92
+ worldId: value.worldId,
93
+ entityKey: value.entityKey,
94
+ instances: value.instances,
95
+ };
96
+ }
97
+
98
+ function instancesSnapshot(
99
+ entityKey: number,
100
+ translationX: number,
101
+ instanceCount = 1,
102
+ ): RenderableSnapshot {
103
+ const base = snapshot(entityKey, 0);
104
+ const transforms = new Float32Array(instanceCount * 16);
105
+ for (let index = 0; index < instanceCount; index += 1) {
106
+ const offset = index * 16;
107
+ transforms[offset] = 1;
108
+ transforms[offset + 5] = 1;
109
+ transforms[offset + 10] = 1;
110
+ transforms[offset + 15] = 1;
111
+ transforms[offset + 12] = translationX + index;
112
+ }
113
+ return {
114
+ ...base,
115
+ instances: {
116
+ transforms,
117
+ instanceCount,
118
+ cacheKey: entityKey,
119
+ archVersion: 0,
120
+ revision: translationX + instanceCount,
121
+ },
122
+ };
123
+ }
124
+
45
125
  describe('RenderScene canonical owner', () => {
126
+ it('rejects a mismatched source identity before publishing any operation', () => {
127
+ const scene = new RenderScene();
128
+ scene.apply([updateSnapshot(snapshot(1, 0))]);
129
+ expect(() =>
130
+ scene.apply([
131
+ updateSnapshot(snapshot(1, 3)),
132
+ { kind: 'update', worldId: 0, entityKey: 2, snapshot: snapshot(3, 4) },
133
+ ]),
134
+ ).toThrow('identity must match');
135
+ expect(scene.slot(0, 1)?.snapshot.transform.world[12]).toBe(0);
136
+ expect(scene.slot(0, 2)).toBeUndefined();
137
+ expect(scene.slot(0, 3)).toBeUndefined();
138
+ });
139
+
46
140
  it('rebuilds the retained composition when Motion Blur is toggled', () => {
47
141
  const world = new World();
48
142
  const camera = world
@@ -66,6 +160,7 @@ describe('RenderScene canonical owner', () => {
66
160
  cameras: [{ ...makeZeroCameraFallbackSnapshot(), worldId: 0, entityKey: camera as number }],
67
161
  renderables: [],
68
162
  dispatch: [],
163
+ hiddenEntityReports: [],
69
164
  } as unknown as ExtractedFrame;
70
165
  const persistent = new PersistentRenderScene();
71
166
  let builds = 0;
@@ -105,6 +200,7 @@ describe('RenderScene canonical owner', () => {
105
200
  cameras: [camera],
106
201
  renderables,
107
202
  dispatch: [],
203
+ hiddenEntityReports: [],
108
204
  } as unknown as ExtractedFrame;
109
205
  const persistent = new PersistentRenderScene();
110
206
  let builds = 0;
@@ -154,8 +250,8 @@ describe('RenderScene canonical owner', () => {
154
250
  updatedWorld[12] = 9;
155
251
 
156
252
  const result = projection.apply([
157
- { kind: 'create', snapshot: initial },
158
- { kind: 'update-transform', worldId: 0, entityKey: 7, world: updatedWorld },
253
+ updateSnapshot(initial),
254
+ { kind: 'update', worldId: 0, entityKey: 7, world: updatedWorld },
159
255
  ]);
160
256
 
161
257
  expect(result).toMatchObject({ created: 1, updated: 0, removed: 0, recreated: 0 });
@@ -165,20 +261,136 @@ describe('RenderScene canonical owner', () => {
165
261
  expect(projection.materialize()[0]?.transform.world[12]).toBe(9);
166
262
  });
167
263
 
264
+ it('keeps mixed Instances and transform updates on the instance lane', () => {
265
+ const initial = instancesSnapshot(7, 1);
266
+ const nextInstances = instancesSnapshot(7, 4);
267
+ const nextWorld = new Float32Array(initial.transform.world);
268
+ nextWorld[12] = 9;
269
+ nextInstances.transform.world[12] = 9;
270
+
271
+ for (const operations of [
272
+ [
273
+ updateInstances(nextInstances),
274
+ { kind: 'update' as const, worldId: 0, entityKey: 7, world: nextWorld },
275
+ ],
276
+ [
277
+ { kind: 'update' as const, worldId: 0, entityKey: 7, world: nextWorld },
278
+ updateInstances(nextInstances),
279
+ ],
280
+ ]) {
281
+ const projection = new RenderScene();
282
+ projection.apply([updateSnapshot(initial)]);
283
+ const result = projection.apply(operations);
284
+ expect(result.instanceUpdatedSlots).toHaveLength(1);
285
+ expect(result.contentUpdatedSlots).toHaveLength(0);
286
+ expect(projection.materialize()[0]?.transform.world[12]).toBe(9);
287
+ expect(projection.materialize()[0]?.instances?.transforms[12]).toBe(4);
288
+ }
289
+ });
290
+
291
+ it('does not downgrade a full update followed by an Instances update', () => {
292
+ const projection = new RenderScene();
293
+ projection.apply([updateSnapshot(instancesSnapshot(7, 1))]);
294
+ const result = projection.apply([
295
+ updateSnapshot(instancesSnapshot(7, 2)),
296
+ updateInstances(instancesSnapshot(7, 3)),
297
+ ]);
298
+
299
+ expect(result.instanceUpdatedSlots).toHaveLength(0);
300
+ expect(result.contentUpdatedSlots).toHaveLength(1);
301
+ });
302
+
303
+ it('classifies an Instances-only collection revision without widening the content update', () => {
304
+ const world = new World();
305
+ const instanceCollections = new InstanceCollectionStore();
306
+ const initialTransforms = new Float32Array(16);
307
+ initialTransforms[0] = 1;
308
+ initialTransforms[5] = 1;
309
+ initialTransforms[10] = 1;
310
+ initialTransforms[15] = 1;
311
+ const collection = instanceCollections.create({ transforms: initialTransforms }).unwrap();
312
+ const entity = world
313
+ .spawn(
314
+ {
315
+ component: Transform,
316
+ data: { pos: [0, 0, 0], quat: [0, 0, 0, 1], scale: [1, 1, 1] },
317
+ },
318
+ { component: MeshFilter, data: { assetHandle: 1 as never } },
319
+ { component: MeshRenderer, data: { materials: [] } },
320
+ { component: Instances, data: { collectionId: collection.collectionId } },
321
+ )
322
+ .unwrap();
323
+ const lease = createRenderReadLease(world);
324
+ const requests: PersistentRenderCandidateRequest[] = [];
325
+ const build = (request: PersistentRenderCandidateRequest): ExtractedFrame => {
326
+ requests.push(request);
327
+ const detached = instanceCollections.snapshot(collection.collectionId).unwrap();
328
+ return {
329
+ cameras: [{ ...makeZeroCameraFallbackSnapshot(), worldId: 0, entityKey: 1 }],
330
+ renderables: [
331
+ {
332
+ ...snapshot(entity as number, 0),
333
+ instances: {
334
+ ...detached,
335
+ instanceCount: detached.count,
336
+ cacheKey: entity as number,
337
+ archVersion: 0,
338
+ },
339
+ },
340
+ ],
341
+ dispatch: [],
342
+ hiddenEntityReports: [],
343
+ } as unknown as ExtractedFrame;
344
+ };
345
+ const persistent = new PersistentRenderScene({ instanceCollections });
346
+ try {
347
+ persistent.extractComposition([world], { cameraOwner: 0, resourceOwner: 0 }, 0, build, [
348
+ lease,
349
+ ]);
350
+ const nextTransforms = new Float32Array(initialTransforms);
351
+ nextTransforms[12] = 4;
352
+ instanceCollections.replace(collection.collectionId, nextTransforms).unwrap();
353
+ persistent.extractComposition([world], { cameraOwner: 0, resourceOwner: 0 }, 0, build, [
354
+ lease,
355
+ ]);
356
+
357
+ let partial: Extract<PersistentRenderCandidateRequest, { kind: 'partial' }> | undefined;
358
+ for (let index = requests.length - 1; index >= 0; index -= 1) {
359
+ const request = requests[index];
360
+ if (
361
+ request !== undefined &&
362
+ request !== 'full' &&
363
+ request !== 'none' &&
364
+ request.kind === 'partial'
365
+ ) {
366
+ partial = request;
367
+ break;
368
+ }
369
+ }
370
+ expect(partial?.kind).toBe('partial');
371
+ if (partial?.kind !== 'partial') return;
372
+ expect(partial.entitiesByWorld?.[0]?.has(entity as number)).toBe(true);
373
+ } finally {
374
+ lease.dispose();
375
+ persistent.dispose();
376
+ instanceCollections.dispose();
377
+ }
378
+ });
379
+
168
380
  it('updates cached dynamic world bounds without replacing the bounds object', () => {
169
381
  const projection = new RenderScene();
170
382
  const initial = {
171
383
  ...snapshot(7, 1),
172
384
  localAabb: new Float32Array([-1, -1, -1, 1, 1, 1]),
173
385
  };
174
- projection.apply([{ kind: 'create', snapshot: initial }]);
386
+ projection.apply([updateSnapshot(initial)]);
175
387
  const retained = projection.materialize()[0];
176
388
  if (retained === undefined) throw new Error('expected retained snapshot');
177
389
  const first = projection.cullingWorldBounds(retained);
178
390
  const moved = new Float32Array(initial.transform.world);
179
391
  moved[12] = 9;
180
392
 
181
- projection.applyTransformSpans([{ worldId: 0, entities: new Uint32Array([7]), worlds: moved }]);
393
+ projection.apply([], [{ worldId: 0, entities: new Uint32Array([7]), worlds: moved }]);
182
394
 
183
395
  const next = projection.cullingWorldBounds(retained);
184
396
  expect(next).toBe(first);
@@ -190,7 +402,7 @@ describe('RenderScene canonical owner', () => {
190
402
  const projection = new RenderScene();
191
403
 
192
404
  const result = projection.apply([
193
- { kind: 'create', snapshot: snapshot(3, 1) },
405
+ updateSnapshot(snapshot(3, 1)),
194
406
  { kind: 'remove', worldId: 0, entityKey: 3 },
195
407
  ]);
196
408
 
@@ -200,13 +412,13 @@ describe('RenderScene canonical owner', () => {
200
412
 
201
413
  it('bumps generation when remove and recreate reuse a slot', () => {
202
414
  const projection = new RenderScene();
203
- projection.apply([{ kind: 'create', snapshot: snapshot(5, 1) }]);
415
+ projection.apply([updateSnapshot(snapshot(5, 1))]);
204
416
  const before = projection.inspect().records[0];
205
417
 
206
418
  const result = projection.apply([
207
419
  { kind: 'remove', worldId: 0, entityKey: 5 },
208
- { kind: 'create', snapshot: snapshot(5, 2) },
209
- { kind: 'update-transform', worldId: 0, entityKey: 5, world: snapshot(5, 7).transform.world },
420
+ updateSnapshot(snapshot(5, 2)),
421
+ { kind: 'update', worldId: 0, entityKey: 5, world: snapshot(5, 7).transform.world },
210
422
  ]);
211
423
 
212
424
  const after = projection.inspect().records[0];
@@ -218,11 +430,11 @@ describe('RenderScene canonical owner', () => {
218
430
 
219
431
  it('ignores an update that arrives after removal', () => {
220
432
  const projection = new RenderScene();
221
- projection.apply([{ kind: 'create', snapshot: snapshot(9, 1) }]);
433
+ projection.apply([updateSnapshot(snapshot(9, 1))]);
222
434
 
223
435
  const result = projection.apply([
224
436
  { kind: 'remove', worldId: 0, entityKey: 9 },
225
- { kind: 'update-transform', worldId: 0, entityKey: 9, world: snapshot(9, 4).transform.world },
437
+ { kind: 'update', worldId: 0, entityKey: 9, world: snapshot(9, 4).transform.world },
226
438
  ]);
227
439
 
228
440
  expect(result).toMatchObject({ removed: 1, ignoredLateUpdates: 1 });
@@ -232,8 +444,8 @@ describe('RenderScene canonical owner', () => {
232
444
  it('keeps identical entity handles isolated by World identity', () => {
233
445
  const projection = new RenderScene();
234
446
  projection.apply([
235
- { kind: 'create', snapshot: snapshot(1, 3) },
236
- { kind: 'create', snapshot: { ...snapshot(1, 8), worldId: 1 } },
447
+ updateSnapshot(snapshot(1, 3)),
448
+ updateSnapshot({ ...snapshot(1, 8), worldId: 1 }),
237
449
  ]);
238
450
 
239
451
  expect(projection.materialize().map((record) => record.transform.world[12])).toEqual([3, 8]);
@@ -270,6 +482,7 @@ describe('RenderScene canonical owner', () => {
270
482
  cameras: [camera],
271
483
  dispatch: [dispatch],
272
484
  renderables: [candidate],
485
+ hiddenEntityReports: [],
273
486
  } as unknown as ExtractedFrame;
274
487
  const persistent = new PersistentRenderScene();
275
488
  const lease = createRenderReadLease(world);
@@ -364,6 +577,7 @@ describe('RenderScene canonical owner', () => {
364
577
  cameras: [cameraA],
365
578
  dispatch: [dispatchFor(1)],
366
579
  renderables: [ordinaryA, candidateB],
580
+ hiddenEntityReports: [],
367
581
  } as unknown as ExtractedFrame;
368
582
  const persistent = new PersistentRenderScene();
369
583
  persistent.extractComposition(
@@ -405,6 +619,7 @@ describe('RenderScene canonical owner', () => {
405
619
  cameras: [cameraAReordered],
406
620
  dispatch: [dispatchFor(0)],
407
621
  renderables: [reorderedCandidateB, reorderedOrdinaryA],
622
+ hiddenEntityReports: [],
408
623
  } as unknown as ExtractedFrame;
409
624
  persistent.extractComposition(
410
625
  [worldB, worldA],
@@ -455,6 +670,7 @@ describe('RenderScene canonical owner', () => {
455
670
  cameras: [camera],
456
671
  dispatch: [dispatch],
457
672
  renderables: [candidate],
673
+ hiddenEntityReports: [],
458
674
  } as unknown as ExtractedFrame;
459
675
  const persistent = new PersistentRenderScene();
460
676
  persistent.extractComposition([world], { cameraOwner: 0, resourceOwner: 0 }, 0, () => frame, [
@@ -499,6 +715,7 @@ describe('RenderScene canonical owner', () => {
499
715
  cameras: [camera],
500
716
  dispatch: [dispatch, { ...dispatch, entityIndex: 8, renderableIndex: 1 }],
501
717
  renderables: [candidate, extra],
718
+ hiddenEntityReports: [],
502
719
  } as unknown as ExtractedFrame;
503
720
  persistent.extractComposition(
504
721
  [world],
@@ -513,6 +730,623 @@ describe('RenderScene canonical owner', () => {
513
730
  );
514
731
  lease.dispose();
515
732
  });
733
+ it('notifies the GPU residency owner for shared-ref payload mutations', () => {
734
+ const world = new World();
735
+ const lease = createRenderReadLease(world);
736
+ const handle = world.allocSharedRef('MeshAsset', { kind: 'mesh' });
737
+ const onSharedRefMutation = vi.fn();
738
+ const persistent = new PersistentRenderScene({ onSharedRefMutation });
739
+ const frame = {
740
+ cameras: [],
741
+ renderables: [],
742
+ dispatch: [],
743
+ hiddenEntityReports: [],
744
+ } as unknown as ExtractedFrame;
745
+
746
+ persistent.extractComposition([world], { cameraOwner: 0, resourceOwner: 0 }, 0, () => frame, [
747
+ lease,
748
+ ]);
749
+ world.sharedRefs.markChanged(handle).unwrap();
750
+ persistent.extractComposition([world], { cameraOwner: 0, resourceOwner: 0 }, 0, () => frame, [
751
+ lease,
752
+ ]);
753
+
754
+ expect(onSharedRefMutation).toHaveBeenCalledWith(0, handle);
755
+ lease.dispose();
756
+ persistent.dispose();
757
+ });
758
+
759
+ it('reuses a stable skinned composition and rebuilds when a joint changes', () => {
760
+ const world = new World();
761
+ const renderable = world
762
+ .spawn({
763
+ component: Transform,
764
+ data: { pos: [0, 0, 0], quat: [0, 0, 0, 1], scale: [1, 1, 1] },
765
+ })
766
+ .unwrap();
767
+ const joint = world
768
+ .spawn({
769
+ component: Transform,
770
+ data: { pos: [0, 0, 0], quat: [0, 0, 0, 1], scale: [1, 1, 1] },
771
+ })
772
+ .unwrap();
773
+ const lease = createRenderReadLease(world);
774
+ const candidate = {
775
+ ...snapshot(renderable as number, 0),
776
+ skin: SKIN_RECEIPT,
777
+ skinJointEntities: [joint as number],
778
+ } satisfies RenderableSnapshot;
779
+ const frame = (): ExtractedFrame =>
780
+ ({
781
+ cameras: [],
782
+ auxiliaryCameras: [],
783
+ cubeCameras: [],
784
+ lights: {
785
+ directional: undefined,
786
+ directionalCount: 0,
787
+ point: [],
788
+ spot: [],
789
+ pointShadow: [],
790
+ },
791
+ environment: undefined,
792
+ environmentReady: true,
793
+ renderables: [candidate],
794
+ dispatch: [],
795
+ shadowCasterEntityKeys: new Set<number>(),
796
+ shadowCasterDrawKeys: new Set<string>(),
797
+ skylight: undefined,
798
+ skylightCount: 0,
799
+ skybox: undefined,
800
+ skyboxCount: 0,
801
+ fog: undefined,
802
+ frustumStats: { culled: 0, total: 1 },
803
+ visibilityStats: { explicitlyHidden: 0 },
804
+ postProcessParams: new Map(),
805
+ visibilitySnapshots: [],
806
+ featureVisibilitySnapshots: [],
807
+ hiddenEntityReports: [],
808
+ }) as unknown as ExtractedFrame;
809
+ const persistent = new PersistentRenderScene();
810
+ let builds = 0;
811
+ const buildCandidate = (): ExtractedFrame => {
812
+ builds += 1;
813
+ return frame();
814
+ };
815
+
816
+ persistent.extractComposition(
817
+ [world],
818
+ { cameraOwner: 0, resourceOwner: 0 },
819
+ 0,
820
+ buildCandidate,
821
+ [lease],
822
+ );
823
+ persistent.extractComposition(
824
+ [world],
825
+ { cameraOwner: 0, resourceOwner: 0 },
826
+ 0,
827
+ buildCandidate,
828
+ [lease],
829
+ );
830
+ expect(builds).toBe(2);
831
+ expect(persistent.inspect()).toMatchObject({
832
+ fullRebuilds: 1,
833
+ worldEntitiesScanned: 0,
834
+ });
835
+
836
+ world.set(joint, Transform, { pos: [1, 0, 0] }).unwrap();
837
+ propagateTransforms(world).unwrap();
838
+ world.update().unwrap();
839
+ persistent.extractComposition(
840
+ [world],
841
+ { cameraOwner: 0, resourceOwner: 0 },
842
+ 0,
843
+ buildCandidate,
844
+ [lease],
845
+ );
846
+ expect(builds).toBe(4);
847
+ expect(persistent.inspect()).toMatchObject({
848
+ fullRebuilds: 1,
849
+ worldEntitiesScanned: 0,
850
+ });
851
+ lease.dispose();
852
+ persistent.dispose();
853
+ });
854
+
855
+ it('refreshes camera and light facts through normal extraction with stable scene identity', () => {
856
+ const world = new World();
857
+ const renderable = world.spawn({ component: Transform, data: { pos: [0, 0, 0] } }).unwrap();
858
+ const light = world
859
+ .spawn(
860
+ { component: Transform, data: { pos: [1, 2, 3] } },
861
+ { component: PointLight, data: { intensity: 1, range: 10 } },
862
+ )
863
+ .unwrap();
864
+ const camera = world
865
+ .spawn(
866
+ { component: Transform, data: { pos: [0, 0, 5] } },
867
+ { component: Camera, data: { fov: Math.PI / 3, aspect: 1, near: 0.1, far: 100 } },
868
+ )
869
+ .unwrap();
870
+ propagateTransforms(world).unwrap();
871
+ const lease = createRenderReadLease(world);
872
+ const candidate = {
873
+ ...snapshot(renderable as number, 0),
874
+ localAabb: new Float32Array([-1, -1, -1, 1, 1, 1]),
875
+ } satisfies RenderableSnapshot;
876
+ let builds = 0;
877
+ const buildCandidate = (): ExtractedFrame => {
878
+ builds += 1;
879
+ const lightTransform = world.get(light, GlobalTransform).unwrap();
880
+ const cameraTransform = world.get(camera, GlobalTransform).unwrap();
881
+ return {
882
+ cameras: [
883
+ {
884
+ ...makeZeroCameraFallbackSnapshot(),
885
+ worldId: 0,
886
+ entityKey: camera as number,
887
+ world: new Float32Array(cameraTransform.world),
888
+ },
889
+ ],
890
+ auxiliaryCameras: [],
891
+ cubeCameras: [],
892
+ lights: {
893
+ directional: undefined,
894
+ directionalCount: 0,
895
+ point: [
896
+ {
897
+ kind: 'point',
898
+ entity: light,
899
+ worldId: 0,
900
+ position: new Float32Array([
901
+ lightTransform.world[12] ?? 0,
902
+ lightTransform.world[13] ?? 0,
903
+ lightTransform.world[14] ?? 0,
904
+ ]),
905
+ color: new Float32Array([1, 1, 1]),
906
+ intensity: 1,
907
+ invRangeSquared: 0.01,
908
+ },
909
+ ],
910
+ spot: [],
911
+ pointShadow: [],
912
+ rect: [],
913
+ },
914
+ environment: undefined,
915
+ environmentReady: true,
916
+ renderables: [candidate],
917
+ dispatch: [],
918
+ shadowCasterEntityKeys: new Set<number>(),
919
+ shadowCasterDrawKeys: new Set<string>(),
920
+ skylight: undefined,
921
+ skylightCount: 0,
922
+ skybox: undefined,
923
+ skyboxCount: 0,
924
+ fog: undefined,
925
+ frustumStats: { culled: 0, total: 1 },
926
+ visibilityStats: { explicitlyHidden: 0 },
927
+ postProcessParams: new Map(),
928
+ visibilitySnapshots: [],
929
+ featureVisibilitySnapshots: [],
930
+ hiddenEntityReports: [],
931
+ } as unknown as ExtractedFrame;
932
+ };
933
+ const persistent = new PersistentRenderScene();
934
+ try {
935
+ const initial = persistent.extractComposition(
936
+ [world],
937
+ { cameraOwner: 0, resourceOwner: 0 },
938
+ 0,
939
+ buildCandidate,
940
+ [lease],
941
+ );
942
+ const before = persistent.compositionSlots()[0];
943
+ expect(before).toBeDefined();
944
+ world.set(light, Transform, { pos: [4, 2, 3] }).unwrap();
945
+ propagateTransforms(world).unwrap();
946
+ const moved = persistent.extractComposition(
947
+ [world],
948
+ { cameraOwner: 0, resourceOwner: 0 },
949
+ 0,
950
+ buildCandidate,
951
+ [lease],
952
+ );
953
+ expect(moved.lights.point[0]?.position[0]).toBeCloseTo(4);
954
+ expect(builds).toBe(2);
955
+
956
+ world.set(camera, Transform, { pos: [0, 1, 5] }).unwrap();
957
+ propagateTransforms(world).unwrap();
958
+ const cameraMoved = persistent.extractComposition(
959
+ [world],
960
+ { cameraOwner: 0, resourceOwner: 0 },
961
+ 0,
962
+ buildCandidate,
963
+ [lease],
964
+ );
965
+ expect(cameraMoved.cameras[0]?.world[13]).toBeCloseTo(1);
966
+ const after = persistent.compositionSlots()[0];
967
+ expect(after).toMatchObject({ slot: before?.slot, generation: before?.generation });
968
+ const projected = persistent.projectVisibility(
969
+ [world],
970
+ cameraMoved.cameras[0],
971
+ cameraMoved.renderables,
972
+ cameraMoved.dispatch,
973
+ );
974
+ expect(projected.renderables).toEqual(cameraMoved.renderables);
975
+ expect(projected.dispatch).toEqual(cameraMoved.dispatch);
976
+ expect(initial.renderables).toHaveLength(1);
977
+ expect(persistent.inspect()).toMatchObject({ fullRebuilds: 1, worldEntitiesScanned: 0 });
978
+ } finally {
979
+ lease.dispose();
980
+ persistent.dispose();
981
+ }
982
+ });
983
+
984
+ it('refreshes camera, light, and shadow facts through normal extraction per World identity', () => {
985
+ const makeWorld = (position: [number, number, number]) => {
986
+ const world = new World();
987
+ const entity = world
988
+ .spawn(
989
+ { component: Transform, data: { pos: position } },
990
+ { component: Camera, data: { fov: Math.PI / 3, aspect: 1, near: 0.1, far: 100 } },
991
+ { component: DirectionalLight, data: { direction: [0.2, -1, 0.3] } },
992
+ { component: PointLight, data: {} },
993
+ { component: PointLightShadow, data: {} },
994
+ { component: SpotLight, data: { direction: [0, -1, 0] } },
995
+ {
996
+ component: LightProbe,
997
+ data: { irradiance: new Float32Array(27).fill(0.25), radius: 10 },
998
+ },
999
+ )
1000
+ .unwrap();
1001
+ propagateTransforms(world).unwrap();
1002
+ world.update().unwrap();
1003
+ return { world, entity };
1004
+ };
1005
+ const first = makeWorld([1, 2, 3]);
1006
+ const second = makeWorld([20, 2, 3]);
1007
+ expect(Number(first.entity)).toBe(Number(second.entity));
1008
+ const worlds = [first.world, second.world] as const;
1009
+ const leases = [
1010
+ createRenderReadLease(first.world),
1011
+ createRenderReadLease(second.world),
1012
+ ] as const;
1013
+ const persistent = new PersistentRenderScene();
1014
+ const build = (request: PersistentRenderCandidateRequest) =>
1015
+ extractFrames(worlds, { cameraOwner: 0, resourceOwner: 0 }, undefined, undefined, undefined, {
1016
+ cull: 'none',
1017
+ renderables: request,
1018
+ });
1019
+ try {
1020
+ const previous = persistent.extractComposition(
1021
+ worlds,
1022
+ { cameraOwner: 0, resourceOwner: 0 },
1023
+ 0,
1024
+ build,
1025
+ leases,
1026
+ );
1027
+ const previousPointShadow = previous.lights.pointShadow.find(
1028
+ (shadow) => shadow.worldId === 0,
1029
+ );
1030
+ const previousSpot = previous.lights.spot.find((light) => light.worldId === 0);
1031
+ const previousProbe = previous.lightProbes?.find((probe) => probe.worldId === 0);
1032
+ const previousWorldOnePoint = previous.lights.point.find((light) => light.worldId === 1);
1033
+ expect(previousPointShadow).toBeDefined();
1034
+ expect(previousSpot).toBeDefined();
1035
+ expect(previousProbe).toBeDefined();
1036
+ expect(previous.cameras[0]).toBeDefined();
1037
+ expect(previous.lights.lightViewProj).toBeDefined();
1038
+ expect(previousWorldOnePoint).toBeDefined();
1039
+
1040
+ first.world.set(first.entity, Transform, { pos: [4, 5, 6] }).unwrap();
1041
+ propagateTransforms(first.world).unwrap();
1042
+ first.world.update().unwrap();
1043
+ const refreshed = persistent.extractComposition(
1044
+ worlds,
1045
+ { cameraOwner: 0, resourceOwner: 0 },
1046
+ 0,
1047
+ build,
1048
+ leases,
1049
+ );
1050
+ const point = refreshed.lights.point.find((light) => light.worldId === 0);
1051
+ const pointShadow = refreshed.lights.pointShadow.find((shadow) => shadow.worldId === 0);
1052
+ const spot = refreshed.lights.spot.find((light) => light.worldId === 0);
1053
+ const probe = refreshed.lightProbes?.find((candidate) => candidate.worldId === 0);
1054
+ const worldOnePoint = refreshed.lights.point.find((light) => light.worldId === 1);
1055
+ expect(point?.position).toEqual(new Float32Array([4, 5, 6]));
1056
+ expect(pointShadow?.position).toEqual(new Float32Array([4, 5, 6]));
1057
+ expect(pointShadow?.shadowMatrices).not.toEqual(previousPointShadow?.shadowMatrices);
1058
+ expect(spot?.position).toEqual(new Float32Array([4, 5, 6]));
1059
+ expect(spot?.lightViewProj).not.toEqual(previousSpot?.lightViewProj);
1060
+ expect(probe?.position).toEqual([4, 5, 6]);
1061
+ expect(refreshed.cameras[0]?.world[12]).toBeCloseTo(4);
1062
+ expect(refreshed.cameras[0]?.world[13]).toBeCloseTo(5);
1063
+ expect(refreshed.lights.lightViewProj).not.toEqual(previous.lights.lightViewProj);
1064
+ expect(worldOnePoint?.position).toEqual(previousWorldOnePoint?.position);
1065
+ expect(refreshed.lights.point.find((light) => light.worldId === 1)?.position).not.toEqual(
1066
+ point?.position,
1067
+ );
1068
+ } finally {
1069
+ leases[0].dispose();
1070
+ leases[1].dispose();
1071
+ persistent.dispose();
1072
+ }
1073
+ });
1074
+
1075
+ it('retains the complete shadow dispatch across mixed camera and renderable refresh', () => {
1076
+ const world = new World();
1077
+ const camera = world
1078
+ .spawn(
1079
+ {
1080
+ component: Transform,
1081
+ data: { pos: [0, 0, 5], quat: [0, 0, 0, 1], scale: [1, 1, 1] },
1082
+ },
1083
+ {
1084
+ component: Camera,
1085
+ data: { fov: Math.PI / 3, aspect: 1, near: 0.1, far: 100 },
1086
+ },
1087
+ )
1088
+ .unwrap();
1089
+ const visibleEntity = world
1090
+ .spawn({
1091
+ component: Transform,
1092
+ data: { pos: [0, 0, 0], quat: [0, 0, 0, 1], scale: [1, 1, 1] },
1093
+ })
1094
+ .unwrap();
1095
+ const hiddenEntity = world
1096
+ .spawn({
1097
+ component: Transform,
1098
+ data: { pos: [100, 0, 0], quat: [0, 0, 0, 1], scale: [1, 1, 1] },
1099
+ })
1100
+ .unwrap();
1101
+ propagateTransforms(world).unwrap();
1102
+ world.update().unwrap();
1103
+
1104
+ const cameraSnapshot = (): ExtractedFrame['cameras'][number] => {
1105
+ const transform = world.get(camera, GlobalTransform).unwrap();
1106
+ return {
1107
+ ...makeZeroCameraFallbackSnapshot(),
1108
+ worldId: 0,
1109
+ entityKey: camera as number,
1110
+ position: vec3.create(
1111
+ transform.world[12] ?? 0,
1112
+ transform.world[13] ?? 0,
1113
+ transform.world[14] ?? 0,
1114
+ ),
1115
+ world: new Float32Array(transform.world),
1116
+ fov: Math.PI / 3,
1117
+ aspect: 1,
1118
+ near: 0.1,
1119
+ far: 100,
1120
+ };
1121
+ };
1122
+ const renderables = [
1123
+ {
1124
+ ...snapshot(visibleEntity as number, 0),
1125
+ localAabb: new Float32Array([-1, -1, -1, 1, 1, 1]),
1126
+ },
1127
+ {
1128
+ ...snapshot(hiddenEntity as number, 100),
1129
+ localAabb: new Float32Array([-1, -1, -1, 1, 1, 1]),
1130
+ },
1131
+ ];
1132
+ const dispatch: DispatchEntry[] = renderables.map((entry, renderableIndex) => ({
1133
+ entityIndex: entry.entityKey,
1134
+ materialHandle: 0,
1135
+ renderableIndex,
1136
+ passIndex: 0,
1137
+ queue: 2000,
1138
+ layer: 0,
1139
+ tags: { LightMode: 'ShadowCaster' },
1140
+ renderState: undefined,
1141
+ defines: undefined,
1142
+ vertexEntry: 'vs_main',
1143
+ fragmentEntry: undefined,
1144
+ materialShaderId: 'forgeax::default-shadow-caster',
1145
+ paramSnapshot: {},
1146
+ }));
1147
+ const shadowCasterMembership: ShadowCasterMembership[] = renderables.map(
1148
+ (entry, renderableIndex) => ({
1149
+ worldEntity: entry.entityKey,
1150
+ renderableIndex,
1151
+ drawItemIndex: 0,
1152
+ materialHandle: 0,
1153
+ passIndex: 0,
1154
+ }),
1155
+ );
1156
+ const frame = (): ExtractedFrame =>
1157
+ ({
1158
+ cameras: [cameraSnapshot()],
1159
+ auxiliaryCameras: [],
1160
+ cubeCameras: [],
1161
+ lights: {
1162
+ directional: undefined,
1163
+ directionalCount: 0,
1164
+ point: [],
1165
+ spot: [],
1166
+ pointShadow: [],
1167
+ },
1168
+ environment: undefined,
1169
+ environmentReady: true,
1170
+ renderables,
1171
+ dispatch,
1172
+ shadowCasterEntityKeys: new Set(renderables.map((entry) => entry.entityKey)),
1173
+ shadowCasterDrawKeys: new Set(
1174
+ shadowCasterMembership.map(
1175
+ (entry) =>
1176
+ `${entry.worldEntity}:${entry.materialHandle}:${entry.drawItemIndex}:${entry.passIndex}`,
1177
+ ),
1178
+ ),
1179
+ shadowCasterMembership,
1180
+ skylight: undefined,
1181
+ skylightCount: 0,
1182
+ skybox: undefined,
1183
+ skyboxCount: 0,
1184
+ fog: undefined,
1185
+ frustumStats: { culled: 0, total: 2 },
1186
+ visibilityStats: { explicitlyHidden: 0 },
1187
+ postProcessParams: new Map(),
1188
+ visibilitySnapshots: [],
1189
+ featureVisibilitySnapshots: [],
1190
+ hiddenEntityReports: [],
1191
+ }) as unknown as ExtractedFrame;
1192
+ const lease = createRenderReadLease(world);
1193
+ const persistent = new PersistentRenderScene();
1194
+ const initial = persistent.extractComposition(
1195
+ [world],
1196
+ { cameraOwner: 0, resourceOwner: 0 },
1197
+ 0,
1198
+ frame,
1199
+ [lease],
1200
+ );
1201
+ expect(initial.renderables).toHaveLength(1);
1202
+ expect(initial.dispatch).toHaveLength(1);
1203
+ expect(persistent.shadowCasterProjection()?.dispatch).toHaveLength(2);
1204
+
1205
+ world.set(camera, Transform, { pos: [0, 0, 4] }).unwrap();
1206
+ propagateTransforms(world).unwrap();
1207
+ world.update().unwrap();
1208
+ const moved = persistent.extractComposition(
1209
+ [world],
1210
+ { cameraOwner: 0, resourceOwner: 0 },
1211
+ 0,
1212
+ frame,
1213
+ [lease],
1214
+ );
1215
+
1216
+ expect(moved.renderables).toHaveLength(1);
1217
+ expect(moved.dispatch).toHaveLength(1);
1218
+ expect(persistent.shadowCasterProjection()?.dispatch).toHaveLength(2);
1219
+
1220
+ // A renderable and the camera can change in the same ECS tick. Normal
1221
+ // extraction updates both through the retained projection, so the complete
1222
+ // shadow dispatch remains available after the delta updates the slot.
1223
+ world.set(camera, Transform, { pos: [0, 0, 3] }).unwrap();
1224
+ world.set(visibleEntity, Transform, { pos: [1, 0, 0] }).unwrap();
1225
+ propagateTransforms(world).unwrap();
1226
+ world.update().unwrap();
1227
+ const mixed = persistent.extractComposition(
1228
+ [world],
1229
+ { cameraOwner: 0, resourceOwner: 0 },
1230
+ 0,
1231
+ frame,
1232
+ [lease],
1233
+ );
1234
+
1235
+ expect(mixed.renderables).toHaveLength(1);
1236
+ expect(mixed.dispatch).toHaveLength(1);
1237
+ expect(persistent.shadowCasterProjection()?.dispatch).toHaveLength(2);
1238
+ lease.dispose();
1239
+ persistent.dispose();
1240
+ });
1241
+
1242
+ it('rebuilds probe records when a LightProbe transform changes', () => {
1243
+ const world = new World();
1244
+ const renderableEntity = world
1245
+ .spawn({
1246
+ component: Transform,
1247
+ data: { pos: [0, 0, 0], quat: [0, 0, 0, 1], scale: [1, 1, 1] },
1248
+ })
1249
+ .unwrap();
1250
+ const nearProbe = world
1251
+ .spawn(
1252
+ {
1253
+ component: Transform,
1254
+ data: { pos: [0, 0, 0], quat: [0, 0, 0, 1], scale: [1, 1, 1] },
1255
+ },
1256
+ {
1257
+ component: LightProbe,
1258
+ data: { irradiance: new Float32Array(27).fill(1), radius: 2 },
1259
+ },
1260
+ )
1261
+ .unwrap();
1262
+ const farProbe = world
1263
+ .spawn(
1264
+ {
1265
+ component: Transform,
1266
+ data: { pos: [4, 0, 0], quat: [0, 0, 0, 1], scale: [1, 1, 1] },
1267
+ },
1268
+ {
1269
+ component: LightProbe,
1270
+ data: { irradiance: new Float32Array(27), radius: 10 },
1271
+ },
1272
+ )
1273
+ .unwrap();
1274
+ propagateTransforms(world).unwrap();
1275
+ const lease = createRenderReadLease(world);
1276
+ const renderable = {
1277
+ ...snapshot(renderableEntity as number, 0),
1278
+ localAabb: new Float32Array([-1, -1, -1, 1, 1, 1]),
1279
+ };
1280
+ const probeFact = (entity: typeof nearProbe | typeof farProbe) => {
1281
+ const transform = world.get(entity, GlobalTransform).unwrap();
1282
+ const probe = world.get(entity, LightProbe).unwrap();
1283
+ return {
1284
+ identity: String(entity),
1285
+ worldId: 0,
1286
+ position: [
1287
+ transform.world[12] ?? 0,
1288
+ transform.world[13] ?? 0,
1289
+ transform.world[14] ?? 0,
1290
+ ] as [number, number, number],
1291
+ radius: probe.radius,
1292
+ irradiance: new Float32Array(probe.irradiance),
1293
+ admitted: true,
1294
+ };
1295
+ };
1296
+ const buildFrame = (): ExtractedFrame =>
1297
+ ({
1298
+ cameras: [],
1299
+ lights: {
1300
+ directional: undefined,
1301
+ directionalCount: 0,
1302
+ point: [],
1303
+ spot: [],
1304
+ pointShadow: [],
1305
+ },
1306
+ environment: undefined,
1307
+ environmentReady: true,
1308
+ renderables: [renderable],
1309
+ dispatch: [],
1310
+ lightProbes: [probeFact(nearProbe), probeFact(farProbe)],
1311
+ skylight: undefined,
1312
+ skylightCount: 0,
1313
+ skybox: undefined,
1314
+ skyboxCount: 0,
1315
+ fog: undefined,
1316
+ frustumStats: { culled: 0, total: 1 },
1317
+ visibilityStats: { explicitlyHidden: 0 },
1318
+ postProcessParams: new Map(),
1319
+ visibilitySnapshots: [],
1320
+ featureVisibilitySnapshots: [],
1321
+ hiddenEntityReports: [],
1322
+ }) as unknown as ExtractedFrame;
1323
+ const persistent = new PersistentRenderScene();
1324
+ const initial = persistent.extractComposition(
1325
+ [world],
1326
+ { cameraOwner: 0, resourceOwner: 0 },
1327
+ 0,
1328
+ buildFrame,
1329
+ [lease],
1330
+ );
1331
+ const initialRecord = initial.renderables[0]?.probeBlendRecord;
1332
+ expect(initialRecord).toBeDefined();
1333
+
1334
+ world.set(nearProbe, Transform, { pos: [4, 0, 0] }).unwrap();
1335
+ propagateTransforms(world).unwrap();
1336
+ world.update().unwrap();
1337
+ const moved = persistent.extractComposition(
1338
+ [world],
1339
+ { cameraOwner: 0, resourceOwner: 0 },
1340
+ 0,
1341
+ buildFrame,
1342
+ [lease],
1343
+ );
1344
+
1345
+ expect(persistent.inspect()).toMatchObject({ fullRebuilds: 1 });
1346
+ expect(moved.renderables[0]?.probeBlendRecord?.bytes).not.toEqual(initialRecord?.bytes);
1347
+ lease.dispose();
1348
+ persistent.dispose();
1349
+ });
516
1350
  });
517
1351
 
518
1352
  describe('RenderScene', () => {
@@ -535,6 +1369,7 @@ describe('RenderScene', () => {
535
1369
  admitted: true,
536
1370
  },
537
1371
  ],
1372
+ hiddenEntityReports: [],
538
1373
  } as unknown as ExtractedFrame;
539
1374
 
540
1375
  const projected = scene.extractComposition(
@@ -573,8 +1408,8 @@ describe('RenderScene', () => {
573
1408
  it('keeps one stable slot across reorder and reports no-change without scanning', () => {
574
1409
  const scene = new RenderScene();
575
1410
  scene.apply([
576
- { kind: 'create', snapshot: snapshot(7, 1) },
577
- { kind: 'create', snapshot: { ...snapshot(8, 2), worldId: 1 } },
1411
+ updateSnapshot(snapshot(7, 1)),
1412
+ updateSnapshot({ ...snapshot(8, 2), worldId: 1 }),
578
1413
  ]);
579
1414
 
580
1415
  const before = scene.slot(0, 7);
@@ -589,13 +1424,12 @@ describe('RenderScene', () => {
589
1424
  const scene = new RenderScene();
590
1425
  scene.apply([
591
1426
  {
592
- kind: 'create',
593
- snapshot: {
1427
+ ...updateSnapshot({
594
1428
  ...snapshot(11, 4),
595
1429
  localAabb: new Float32Array([-1, -1, -1, 1, 1, 1]),
596
1430
  material: { ...material, materialHandle: 42 },
597
1431
  materials: [{ ...material, materialHandle: 42 }],
598
- },
1432
+ }),
599
1433
  },
600
1434
  ]);
601
1435
 
@@ -611,15 +1445,72 @@ describe('RenderScene', () => {
611
1445
  ...snapshot(12, 0),
612
1446
  localAabb: new Float32Array([-1, -1, -1, 1, 1, 1]),
613
1447
  };
614
- scene.apply([{ kind: 'create', snapshot: initial }]);
1448
+ scene.apply([updateSnapshot(initial)]);
615
1449
 
616
1450
  expect(scene.querySpatial({ min: [-2, -2, -2], max: [2, 2, 2] })).toHaveLength(1);
617
1451
 
618
1452
  const moved = new Float32Array(initial.transform.world);
619
1453
  moved[12] = 10;
620
- scene.apply([{ kind: 'update-transform', worldId: 0, entityKey: 12, world: moved }]);
1454
+ scene.apply([{ kind: 'update', worldId: 0, entityKey: 12, world: moved }]);
621
1455
 
622
1456
  expect(scene.querySpatial({ min: [-2, -2, -2], max: [2, 2, 2] })).toEqual([]);
623
1457
  expect(scene.querySpatial({ min: [9, -2, -2], max: [11, 2, 2] })).toHaveLength(1);
624
1458
  });
625
1459
  });
1460
+
1461
+ describe('projected bounds query', () => {
1462
+ it('uses World identity, instance transforms, and detached current projection data', () => {
1463
+ const scene = new PersistentRenderScene(),
1464
+ worldA = new World(),
1465
+ worldB = new World();
1466
+ const a = { ...snapshot(7, 10), localAabb: new Float32Array([100, -1, -2, 102, 1, 2]) };
1467
+ const b = {
1468
+ ...snapshot(7, 900),
1469
+ worldId: 1,
1470
+ localAabb: new Float32Array([-1, -1, -1, 1, 1, 1]),
1471
+ };
1472
+ const frame = (renderables: RenderableSnapshot[]) =>
1473
+ ({ cameras: [], renderables, dispatch: [] }) as unknown as ExtractedFrame;
1474
+ expect(scene.bounds(worldA, 7)).toBeUndefined();
1475
+ scene.extractComposition([worldA, worldB], { cameraOwner: 0, resourceOwner: 0 }, 0, () =>
1476
+ frame([a, b]),
1477
+ );
1478
+ expect(scene.bounds(worldA, 7)).toEqual({ min: [110, -1, -2], max: [112, 1, 2] });
1479
+ expect(scene.bounds(worldB, 7)).toEqual({ min: [899, -1, -1], max: [901, 1, 1] });
1480
+ expect(scene.bounds(new World(), 7)).toBeUndefined();
1481
+ expect(scene.bounds(worldA, 99)).toBeUndefined();
1482
+ const first = scene.bounds(worldA, 7);
1483
+ if (first === undefined) throw new Error('expected projected bounds');
1484
+ (first.min as number[])[0] = -999;
1485
+ expect(scene.bounds(worldA, 7)?.min[0]).toBe(110);
1486
+ const instanceA = snapshot(1, 0).transform.world,
1487
+ instanceB = snapshot(2, 20).transform.world;
1488
+ const instanced = {
1489
+ ...a,
1490
+ instances: {
1491
+ transforms: new Float32Array([...instanceA, ...instanceB]),
1492
+ instanceCount: 2,
1493
+ cacheKey: 7,
1494
+ archVersion: 1,
1495
+ },
1496
+ };
1497
+ scene.extractComposition([worldA, worldB], { cameraOwner: 0, resourceOwner: 0 }, 1, () =>
1498
+ frame([instanced, b]),
1499
+ );
1500
+ expect(scene.bounds(worldA, 7)).toEqual({ min: [110, -1, -2], max: [132, 1, 2] });
1501
+ scene.extractComposition([worldB, worldA], { cameraOwner: 0, resourceOwner: 0 }, 2, () =>
1502
+ frame([
1503
+ { ...b, worldId: 0 },
1504
+ { ...instanced, worldId: 1 },
1505
+ ]),
1506
+ );
1507
+ expect(scene.bounds(worldA, 7)?.max[0]).toBe(132);
1508
+ expect(scene.bounds(worldB, 7)?.max[0]).toBe(901);
1509
+ scene.extractComposition([worldA], { cameraOwner: 0, resourceOwner: 0 }, 3, () =>
1510
+ frame([{ ...a, localAabb: undefined }]),
1511
+ );
1512
+ expect(scene.bounds(worldA, 7)).toBeUndefined();
1513
+ expect(scene.bounds(worldB, 7)).toBeUndefined();
1514
+ scene.dispose();
1515
+ });
1516
+ });