@forgeax/engine-render 0.1.20 → 0.1.21

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 (372) hide show
  1. package/README.md +215 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/assembly/device-feature-admission.d.ts +18 -0
  4. package/dist/assembly/device-feature-admission.d.ts.map +1 -0
  5. package/dist/assembly/directional-shadow-inspection.d.ts +38 -0
  6. package/dist/assembly/directional-shadow-inspection.d.ts.map +1 -0
  7. package/dist/assembly/factory.d.ts +21 -18
  8. package/dist/assembly/factory.d.ts.map +1 -1
  9. package/dist/assembly/host-contract.d.ts +9 -0
  10. package/dist/assembly/host-contract.d.ts.map +1 -1
  11. package/dist/assembly/render-target-host.d.ts +46 -0
  12. package/dist/assembly/render-target-host.d.ts.map +1 -0
  13. package/dist/assets/render-asset-port.d.ts.map +1 -1
  14. package/dist/authoring.mjs +3 -0
  15. package/dist/authoring.mjs.map +1 -1
  16. package/dist/capture/cube-views.d.ts +21 -0
  17. package/dist/capture/cube-views.d.ts.map +1 -0
  18. package/dist/capture/scheduler.d.ts +54 -0
  19. package/dist/capture/scheduler.d.ts.map +1 -0
  20. package/dist/{chunk-4XINDLAY.mjs → chunk-5YADUCA7.mjs} +1552 -1361
  21. package/dist/chunk-5YADUCA7.mjs.map +1 -0
  22. package/dist/{chunk-TBQJOK2L.mjs → chunk-6OPXDQ2E.mjs} +364 -51
  23. package/dist/chunk-6OPXDQ2E.mjs.map +1 -0
  24. package/dist/{chunk-M2MP4ZOU.mjs → chunk-BXBQQKFQ.mjs} +3 -9
  25. package/dist/chunk-BXBQQKFQ.mjs.map +1 -0
  26. package/dist/{chunk-CDTWTHZR.mjs → chunk-FS2KNUQH.mjs} +548 -6
  27. package/dist/chunk-FS2KNUQH.mjs.map +1 -0
  28. package/dist/components/camera.d.ts +1 -0
  29. package/dist/components/camera.d.ts.map +1 -1
  30. package/dist/components/cube-camera.d.ts +20 -0
  31. package/dist/components/cube-camera.d.ts.map +1 -0
  32. package/dist/components/directional-light.d.ts +11 -2
  33. package/dist/components/directional-light.d.ts.map +1 -1
  34. package/dist/components/directional-shadow-filter.d.ts +25 -0
  35. package/dist/components/directional-shadow-filter.d.ts.map +1 -0
  36. package/dist/components/index.d.ts +2 -0
  37. package/dist/components/index.d.ts.map +1 -1
  38. package/dist/components/light-helpers.d.ts.map +1 -1
  39. package/dist/components/reflection-probe.d.ts +17 -0
  40. package/dist/components/reflection-probe.d.ts.map +1 -0
  41. package/dist/construct-renderer.mjs +5826 -1003
  42. package/dist/construct-renderer.mjs.map +1 -1
  43. package/dist/environment/frame.d.ts.map +1 -1
  44. package/dist/environment/lifecycle.d.ts +5 -0
  45. package/dist/environment/lifecycle.d.ts.map +1 -1
  46. package/dist/errors/render.d.ts +141 -93
  47. package/dist/errors/render.d.ts.map +1 -1
  48. package/dist/extract/directional-shadow-projection.d.ts +23 -0
  49. package/dist/extract/directional-shadow-projection.d.ts.map +1 -0
  50. package/dist/extract/environment.d.ts +14 -0
  51. package/dist/extract/environment.d.ts.map +1 -1
  52. package/dist/extract/visibility.d.ts.map +1 -1
  53. package/dist/features/fullscreen.d.ts.map +1 -1
  54. package/dist/features/targets.d.ts.map +1 -1
  55. package/dist/features/types.d.ts +10 -0
  56. package/dist/features/types.d.ts.map +1 -1
  57. package/dist/fullscreen-post-process-pass.d.ts +13 -2
  58. package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
  59. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  60. package/dist/gpu-driven/production-raster.d.ts +0 -1
  61. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  62. package/dist/gpu-scene.d.ts.map +1 -1
  63. package/dist/hdrp-buffers.d.ts +19 -17
  64. package/dist/hdrp-buffers.d.ts.map +1 -1
  65. package/dist/ibl/IblPipelineCache.d.ts +4 -0
  66. package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
  67. package/dist/ibl/kernel-cache.d.ts +36 -0
  68. package/dist/ibl/kernel-cache.d.ts.map +1 -0
  69. package/dist/index.d.ts +28 -3
  70. package/dist/index.d.ts.map +1 -1
  71. package/dist/index.mjs +4 -4
  72. package/dist/index.mjs.map +1 -1
  73. package/dist/inspection-types.d.ts +51 -0
  74. package/dist/inspection-types.d.ts.map +1 -1
  75. package/dist/instances-derived-bounds.d.ts +50 -0
  76. package/dist/instances-derived-bounds.d.ts.map +1 -0
  77. package/dist/internal.mjs +1 -1
  78. package/dist/light-buffer-layout.d.ts +10 -1
  79. package/dist/light-buffer-layout.d.ts.map +1 -1
  80. package/dist/mesh-material-bindings.d.ts +54 -0
  81. package/dist/mesh-material-bindings.d.ts.map +1 -1
  82. package/dist/pbr-pipeline.d.ts +14 -4
  83. package/dist/pbr-pipeline.d.ts.map +1 -1
  84. package/dist/pipeline/standard-forward-lane.d.ts +2 -1
  85. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  86. package/dist/pipeline/standard-lighting/errors.d.ts +4 -0
  87. package/dist/pipeline/standard-lighting/errors.d.ts.map +1 -0
  88. package/dist/pipeline/standard-lighting/graph.d.ts +24 -0
  89. package/dist/pipeline/standard-lighting/graph.d.ts.map +1 -0
  90. package/dist/pipeline/standard-lighting/inspection.d.ts +38 -0
  91. package/dist/pipeline/standard-lighting/inspection.d.ts.map +1 -0
  92. package/dist/pipeline/standard-lighting/layout.d.ts +28 -0
  93. package/dist/pipeline/standard-lighting/layout.d.ts.map +1 -0
  94. package/dist/pipeline/standard-lighting/light-frame.d.ts +33 -0
  95. package/dist/pipeline/standard-lighting/light-frame.d.ts.map +1 -0
  96. package/dist/pipeline/standard-lighting/membership.d.ts +14 -0
  97. package/dist/pipeline/standard-lighting/membership.d.ts.map +1 -0
  98. package/dist/pipeline/standard-lighting/prepare.d.ts +16 -0
  99. package/dist/pipeline/standard-lighting/prepare.d.ts.map +1 -0
  100. package/dist/pipeline/standard-lighting/topology.d.ts +45 -0
  101. package/dist/pipeline/standard-lighting/topology.d.ts.map +1 -0
  102. package/dist/pipeline/standard-lighting/transport.d.ts +36 -0
  103. package/dist/pipeline/standard-lighting/transport.d.ts.map +1 -0
  104. package/dist/pipeline/standard-pipeline.d.ts +4 -13
  105. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  106. package/dist/pipeline/standard-post.d.ts.map +1 -1
  107. package/dist/pipeline/standard-profile.d.ts +5 -26
  108. package/dist/pipeline/standard-profile.d.ts.map +1 -1
  109. package/dist/pipeline-spec.d.ts +40 -7
  110. package/dist/pipeline-spec.d.ts.map +1 -1
  111. package/dist/point-shadow-inspection.d.ts +21 -0
  112. package/dist/point-shadow-inspection.d.ts.map +1 -0
  113. package/dist/record/frame-lighting.d.ts +27 -32
  114. package/dist/record/frame-lighting.d.ts.map +1 -1
  115. package/dist/record/frame-snapshot.d.ts +28 -14
  116. package/dist/record/frame-snapshot.d.ts.map +1 -1
  117. package/dist/record/frame-targets.d.ts +5 -0
  118. package/dist/record/frame-targets.d.ts.map +1 -1
  119. package/dist/record/frame.d.ts +10 -4
  120. package/dist/record/frame.d.ts.map +1 -1
  121. package/dist/record/gpu-pass-timing/contract.d.ts +96 -0
  122. package/dist/record/gpu-pass-timing/contract.d.ts.map +1 -0
  123. package/dist/record/gpu-pass-timing/errors.d.ts +18 -0
  124. package/dist/record/gpu-pass-timing/errors.d.ts.map +1 -0
  125. package/dist/record/gpu-pass-timing/index.d.ts +8 -0
  126. package/dist/record/gpu-pass-timing/index.d.ts.map +1 -0
  127. package/dist/record/gpu-pass-timing/parser.d.ts +11 -0
  128. package/dist/record/gpu-pass-timing/parser.d.ts.map +1 -0
  129. package/dist/record/gpu-pass-timing/session.d.ts +43 -0
  130. package/dist/record/gpu-pass-timing/session.d.ts.map +1 -0
  131. package/dist/record/gpu-timing.d.ts +8 -3
  132. package/dist/record/gpu-timing.d.ts.map +1 -1
  133. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  134. package/dist/record/main-pass-material.d.ts +22 -1
  135. package/dist/record/main-pass-material.d.ts.map +1 -1
  136. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  137. package/dist/record/main-pass.d.ts +2 -0
  138. package/dist/record/main-pass.d.ts.map +1 -1
  139. package/dist/record/render-context.d.ts +43 -2
  140. package/dist/record/render-context.d.ts.map +1 -1
  141. package/dist/record/shadow-pass.d.ts.map +1 -1
  142. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  143. package/dist/record/typed-frame-graph.d.ts +72 -4
  144. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  145. package/dist/record/view-ubo.d.ts +7 -4
  146. package/dist/record/view-ubo.d.ts.map +1 -1
  147. package/dist/reflection/filter.d.ts +22 -0
  148. package/dist/reflection/filter.d.ts.map +1 -0
  149. package/dist/reflection/gpu-table.d.ts +25 -0
  150. package/dist/reflection/gpu-table.d.ts.map +1 -0
  151. package/dist/reflection/projection.d.ts +91 -0
  152. package/dist/reflection/projection.d.ts.map +1 -0
  153. package/dist/render-contract.d.ts +49 -7
  154. package/dist/render-contract.d.ts.map +1 -1
  155. package/dist/render-graph-primitives.d.ts +1 -0
  156. package/dist/render-graph-primitives.d.ts.map +1 -1
  157. package/dist/render-pipeline.d.ts +42 -2
  158. package/dist/render-pipeline.d.ts.map +1 -1
  159. package/dist/render-system-extract.d.ts +58 -17
  160. package/dist/render-system-extract.d.ts.map +1 -1
  161. package/dist/render-system.d.ts +17 -2
  162. package/dist/render-system.d.ts.map +1 -1
  163. package/dist/renderer.d.ts +43 -0
  164. package/dist/renderer.d.ts.map +1 -0
  165. package/dist/scene/render-scene.d.ts +14 -0
  166. package/dist/scene/render-scene.d.ts.map +1 -1
  167. package/dist/shadow-atlas.d.ts +3 -3
  168. package/dist/shadow-atlas.d.ts.map +1 -1
  169. package/dist/ssao-buffers.d.ts +3 -0
  170. package/dist/ssao-buffers.d.ts.map +1 -1
  171. package/dist/targets/contracts.d.ts +66 -0
  172. package/dist/targets/contracts.d.ts.map +1 -0
  173. package/dist/targets/graph-projection.d.ts +29 -0
  174. package/dist/targets/graph-projection.d.ts.map +1 -0
  175. package/dist/targets/material-source.d.ts +34 -0
  176. package/dist/targets/material-source.d.ts.map +1 -0
  177. package/dist/targets/owner.d.ts +32 -0
  178. package/dist/targets/owner.d.ts.map +1 -0
  179. package/dist/targets/physical.d.ts +18 -0
  180. package/dist/targets/physical.d.ts.map +1 -0
  181. package/dist/targets/readback.d.ts +39 -0
  182. package/dist/targets/readback.d.ts.map +1 -0
  183. package/dist/temporal/index.mjs +3 -3
  184. package/dist/typed-render-graph-primitives.d.ts +1 -1
  185. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  186. package/dist/typed-shadow-passes.d.ts +1 -1
  187. package/dist/typed-shadow-passes.d.ts.map +1 -1
  188. package/dist/volume/passes.d.ts +2 -2
  189. package/dist/volume/passes.d.ts.map +1 -1
  190. package/package.json +20 -20
  191. package/src/__tests__/bloom-exact-zero.unit.test.ts +48 -19
  192. package/src/__tests__/bloom-lifecycle.integration.test.ts +41 -13
  193. package/src/__tests__/camera-target-consumption.unit.test.ts +53 -0
  194. package/src/__tests__/cluster-binner.unit.test.ts +35 -0
  195. package/src/__tests__/cluster-grid-default-surface.unit.test.ts +7 -2
  196. package/src/__tests__/cube-camera-scheduler.unit.test.ts +70 -0
  197. package/src/__tests__/cube-camera-single-submit.perf.test.ts +75 -0
  198. package/src/__tests__/cube-camera-view-matrices.unit.test.ts +41 -0
  199. package/src/__tests__/cube-camera-vocabulary.unit.test.ts +47 -0
  200. package/src/__tests__/cube-camera.integration.test.ts +42 -0
  201. package/src/__tests__/device-feature-admission.unit.test.ts +73 -0
  202. package/src/__tests__/direct-light-snapshot.unit.test.ts +3 -3
  203. package/src/__tests__/docs-gate-green.unit.test.ts +38 -0
  204. package/src/__tests__/factory-contract.integration.test.ts +255 -29
  205. package/src/__tests__/fog-component.unit.test.ts +67 -0
  206. package/src/__tests__/fog.integration.test.ts +86 -0
  207. package/src/__tests__/fullscreen-feature-plan.unit.test.ts +37 -0
  208. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +10 -9
  209. package/src/__tests__/gpu-driven-indirect-raster.dawn.test.ts +17 -1
  210. package/src/__tests__/gpu-driven-production.integration.test.ts +16 -17
  211. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +12 -4
  212. package/src/__tests__/gpu-pass-timing-browser-runner.ts +281 -0
  213. package/src/__tests__/gpu-pass-timing-coexistence.integration.test.ts +80 -0
  214. package/src/__tests__/gpu-pass-timing-contract.unit.test.ts +243 -0
  215. package/src/__tests__/gpu-pass-timing-exact-zero.unit.test.ts +131 -0
  216. package/src/__tests__/gpu-pass-timing-lifecycle.integration.test.ts +230 -0
  217. package/src/__tests__/gpu-pass-timing-observation.unit.test.ts +233 -0
  218. package/src/__tests__/gpu-pass-timing-session.unit.test.ts +438 -0
  219. package/src/__tests__/gpu-pass-timing.browser.test.ts +26 -0
  220. package/src/__tests__/gpu-pass-timing.rhi-null.unit.test.ts +125 -0
  221. package/src/__tests__/hdrp-group2-visibility.unit.test.ts +24 -0
  222. package/src/__tests__/instances-culling.integration.test.ts +94 -0
  223. package/src/__tests__/instances-derived-bounds.unit.test.ts +118 -0
  224. package/src/__tests__/material-observation.integration.test.ts +101 -0
  225. package/src/__tests__/pbr-view-layout.unit.test.ts +16 -0
  226. package/src/__tests__/point-shadow-inspection.unit.test.ts +60 -0
  227. package/src/__tests__/public-surface.test-d.ts +10 -0
  228. package/src/__tests__/reflection-probe-authoring.unit.test.ts +71 -0
  229. package/src/__tests__/reflection-probe-filter.unit.test.ts +54 -0
  230. package/src/__tests__/reflection-probe-ibl-topology.unit.test.ts +45 -0
  231. package/src/__tests__/reflection-probe-pass-encode.integration.test.ts +101 -0
  232. package/src/__tests__/reflection-probe-performance.perf.test.ts +60 -0
  233. package/src/__tests__/reflection-probe-recovery.integration.test.ts +55 -0
  234. package/src/__tests__/reflection-probe-selection.unit.test.ts +46 -0
  235. package/src/__tests__/render-error-code-owner.test-d.ts +18 -3
  236. package/src/__tests__/render-feature-docs.unit.test.ts +1 -1
  237. package/src/__tests__/render-target-admission.unit.test.ts +80 -0
  238. package/src/__tests__/render-target-graph-projection.unit.test.ts +102 -0
  239. package/src/__tests__/render-target-inspection.unit.test.ts +52 -0
  240. package/src/__tests__/render-target-material-source.test-d.ts +30 -0
  241. package/src/__tests__/render-target-material-source.unit.test.ts +97 -0
  242. package/src/__tests__/render-target-owner-placement.unit.test.ts +203 -0
  243. package/src/__tests__/render-target-owner.unit.test.ts +122 -0
  244. package/src/__tests__/render-target-probe-soak.perf.test.ts +94 -0
  245. package/src/__tests__/render-target-public-schema.json +107 -0
  246. package/src/__tests__/render-target-public-schema.unit.test.ts +91 -0
  247. package/src/__tests__/render-target-public-surface.test-d.ts +83 -0
  248. package/src/__tests__/render-target-readback.integration.test.ts +90 -0
  249. package/src/__tests__/render-target-recovery.integration.test.ts +85 -0
  250. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +48 -0
  251. package/src/__tests__/renderer-lifecycle.integration.test.ts +14 -0
  252. package/src/__tests__/shader-stage-surface.unit.test.ts +19 -4
  253. package/src/__tests__/skin-motion-regression.unit.test.ts +132 -0
  254. package/src/__tests__/skinned-shadow-caster.test.ts +11 -2
  255. package/src/__tests__/standard-cluster-capability.integration.test.ts +63 -0
  256. package/src/__tests__/standard-cluster-transport.unit.test.ts +121 -0
  257. package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +232 -0
  258. package/src/__tests__/standard-clustered-pipeline.unit.test.ts +8 -5
  259. package/src/__tests__/standard-lighting-inspection.integration.test.ts +138 -0
  260. package/src/__tests__/standard-lighting-ssot.unit.test.ts +18 -0
  261. package/src/__tests__/standard-lighting-write.unit.test.ts +377 -0
  262. package/src/__tests__/standard-lighting.integration.test.ts +67 -0
  263. package/src/__tests__/standard-pipeline.dawn.test.ts +206 -5
  264. package/src/__tests__/standard-pipeline.integration.test.ts +181 -150
  265. package/src/__tests__/standard-probe-binding.unit.test.ts +25 -0
  266. package/src/__tests__/standard-profile-oracle.unit.test.ts +23 -38
  267. package/src/__tests__/typed-pipeline-topology.unit.test.ts +395 -14
  268. package/src/__tests__/urp-pipeline-targets.unit.test.ts +2 -2
  269. package/src/__tests__/visibility-resolver.unit.test.ts +15 -0
  270. package/src/assembly/device-feature-admission.ts +73 -0
  271. package/src/assembly/directional-shadow-inspection.ts +122 -0
  272. package/src/assembly/factory.ts +877 -286
  273. package/src/assembly/host-contract.ts +25 -0
  274. package/src/assembly/render-target-host.ts +847 -0
  275. package/src/assets/asset-decoders.ts +1 -1
  276. package/src/assets/render-asset-port.ts +7 -1
  277. package/src/capture/cube-views.ts +69 -0
  278. package/src/capture/scheduler.ts +265 -0
  279. package/src/components/__tests__/schema-contract.unit.test.ts +12 -0
  280. package/src/components/__tests__/schema-parity.unit.test.ts +9 -0
  281. package/src/components/camera.ts +4 -0
  282. package/src/components/cube-camera.ts +52 -0
  283. package/src/components/directional-light.ts +22 -2
  284. package/src/components/directional-shadow-filter.ts +57 -0
  285. package/src/components/index.ts +2 -0
  286. package/src/components/light-helpers.ts +81 -7
  287. package/src/components/reflection-probe.ts +49 -0
  288. package/src/environment/frame.ts +6 -24
  289. package/src/environment/lifecycle.ts +12 -0
  290. package/src/errors/render.ts +286 -142
  291. package/src/extract/directional-shadow-projection.ts +61 -0
  292. package/src/extract/environment.ts +57 -0
  293. package/src/extract/visibility.ts +25 -1
  294. package/src/features/fullscreen.ts +3 -0
  295. package/src/features/targets.ts +4 -1
  296. package/src/features/types.ts +10 -0
  297. package/src/fullscreen-post-process-pass.ts +43 -2
  298. package/src/gpu-driven/batch-topology.ts +1 -0
  299. package/src/gpu-driven/production-raster.ts +1 -3
  300. package/src/gpu-scene.ts +8 -1
  301. package/src/hdrp-buffers.ts +165 -59
  302. package/src/ibl/IblPipelineCache.ts +11 -0
  303. package/src/ibl/kernel-cache.ts +88 -0
  304. package/src/index.ts +148 -0
  305. package/src/inspection-types.ts +56 -0
  306. package/src/instances-derived-bounds.ts +171 -0
  307. package/src/light-buffer-layout.ts +20 -5
  308. package/src/mesh-material-bindings.ts +132 -0
  309. package/src/pbr-pipeline.ts +106 -17
  310. package/src/pipeline/standard-forward-lane.ts +46 -16
  311. package/src/pipeline/standard-lighting/errors.ts +6 -0
  312. package/src/pipeline/standard-lighting/graph.ts +157 -0
  313. package/src/pipeline/standard-lighting/inspection.ts +88 -0
  314. package/src/pipeline/standard-lighting/layout.ts +31 -0
  315. package/src/pipeline/standard-lighting/light-frame.ts +34 -0
  316. package/src/pipeline/standard-lighting/membership.ts +43 -0
  317. package/src/pipeline/standard-lighting/prepare.ts +37 -0
  318. package/src/pipeline/standard-lighting/topology.ts +120 -0
  319. package/src/pipeline/standard-lighting/transport.ts +77 -0
  320. package/src/pipeline/standard-pipeline.ts +122 -193
  321. package/src/pipeline/standard-post.ts +26 -7
  322. package/src/pipeline/standard-profile.ts +17 -47
  323. package/src/pipeline-spec.ts +84 -21
  324. package/src/point-shadow-inspection.ts +55 -0
  325. package/src/record/__tests__/temporal-payload.unit.test.ts +42 -1
  326. package/src/record/__tests__/transparent-group2-selection.unit.test.ts +73 -0
  327. package/src/record/__tests__/view-ubo-layout.unit.test.ts +40 -0
  328. package/src/record/frame-lighting.ts +367 -427
  329. package/src/record/frame-snapshot.ts +35 -27
  330. package/src/record/frame-targets.ts +17 -0
  331. package/src/record/frame.ts +261 -74
  332. package/src/record/gpu-pass-timing/contract.ts +180 -0
  333. package/src/record/gpu-pass-timing/errors.ts +34 -0
  334. package/src/record/gpu-pass-timing/index.ts +37 -0
  335. package/src/record/gpu-pass-timing/parser.ts +97 -0
  336. package/src/record/gpu-pass-timing/session.ts +938 -0
  337. package/src/record/gpu-timing.ts +21 -12
  338. package/src/record/main-pass-geometry.ts +120 -80
  339. package/src/record/main-pass-material.ts +95 -9
  340. package/src/record/main-pass-sprite-draws.ts +54 -25
  341. package/src/record/main-pass.ts +71 -15
  342. package/src/record/render-context.ts +72 -0
  343. package/src/record/shadow-pass.ts +17 -28
  344. package/src/record/skybox-post-pass.ts +20 -4
  345. package/src/record/typed-frame-graph.ts +893 -44
  346. package/src/record/view-ubo.ts +63 -28
  347. package/src/reflection/filter.ts +81 -0
  348. package/src/reflection/gpu-table.ts +44 -0
  349. package/src/reflection/projection.ts +281 -0
  350. package/src/render-contract.ts +76 -5
  351. package/src/render-graph-primitives.ts +9 -4
  352. package/src/render-pipeline.ts +117 -2
  353. package/src/render-system-extract.ts +557 -100
  354. package/src/render-system.ts +1095 -66
  355. package/src/renderer.ts +222 -0
  356. package/src/scene/render-scene.ts +115 -9
  357. package/src/shadow-atlas.ts +5 -5
  358. package/src/ssao-buffers.ts +9 -2
  359. package/src/targets/contracts.ts +182 -0
  360. package/src/targets/graph-projection.ts +169 -0
  361. package/src/targets/material-source.ts +143 -0
  362. package/src/targets/owner.ts +214 -0
  363. package/src/targets/physical.ts +18 -0
  364. package/src/targets/readback.ts +153 -0
  365. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +59 -20
  366. package/src/typed-render-graph-primitives.ts +5 -3
  367. package/src/typed-shadow-passes.ts +46 -40
  368. package/src/volume/passes.ts +15 -9
  369. package/dist/chunk-4XINDLAY.mjs.map +0 -1
  370. package/dist/chunk-CDTWTHZR.mjs.map +0 -1
  371. package/dist/chunk-M2MP4ZOU.mjs.map +0 -1
  372. package/dist/chunk-TBQJOK2L.mjs.map +0 -1
@@ -0,0 +1,88 @@
1
+ export const REFLECTION_PROBE_IBL_STAGES = ['raw', 'filtered', 'publish'] as const;
2
+ export type ReflectionProbeIblStage = (typeof REFLECTION_PROBE_IBL_STAGES)[number];
3
+
4
+ export interface IblKernelCache {
5
+ readonly generation: number;
6
+ readonly pipelineIdentity: object;
7
+ readonly samplerIdentity: object;
8
+ readonly brdfLutIdentity: object;
9
+ }
10
+
11
+ const kernelCaches = new Map<number, IblKernelCache>();
12
+
13
+ export function createIblKernelCache(generation: number): IblKernelCache {
14
+ const existing = kernelCaches.get(generation);
15
+ if (existing !== undefined) return existing;
16
+ const created: IblKernelCache = {
17
+ generation,
18
+ pipelineIdentity: {},
19
+ samplerIdentity: {},
20
+ brdfLutIdentity: {},
21
+ };
22
+ kernelCaches.set(generation, created);
23
+ return created;
24
+ }
25
+
26
+ export interface ProbeIblOutput {
27
+ readonly entityKey: number;
28
+ readonly resolution: number;
29
+ readonly deviceGeneration: number;
30
+ readonly raw: object;
31
+ readonly filtered: object;
32
+ readonly rawGeneration: number;
33
+ readonly filteredGeneration: number;
34
+ readonly lkg: boolean;
35
+ }
36
+
37
+ export interface ProbeIblUpdate extends ProbeIblOutput {
38
+ readonly stages: typeof REFLECTION_PROBE_IBL_STAGES;
39
+ readonly nextStage: ReflectionProbeIblStage;
40
+ readonly candidateGeneration: number;
41
+ }
42
+
43
+ export function createProbeIblOutput(input: {
44
+ readonly entityKey: number;
45
+ readonly resolution: number;
46
+ readonly deviceGeneration: number;
47
+ }): ProbeIblOutput {
48
+ return {
49
+ ...input,
50
+ raw: {},
51
+ filtered: {},
52
+ rawGeneration: 0,
53
+ filteredGeneration: 0,
54
+ lkg: true,
55
+ };
56
+ }
57
+
58
+ export function beginProbeIblUpdate(output: ProbeIblOutput): ProbeIblUpdate {
59
+ return {
60
+ ...output,
61
+ stages: REFLECTION_PROBE_IBL_STAGES,
62
+ nextStage: 'raw',
63
+ candidateGeneration: output.filteredGeneration + 1,
64
+ };
65
+ }
66
+
67
+ export function publishProbeIblOutput(
68
+ update: ProbeIblUpdate,
69
+ result: { readonly stage: ReflectionProbeIblStage; readonly ok: boolean },
70
+ ): ProbeIblOutput {
71
+ if (!result.ok || result.stage !== update.nextStage) return update;
72
+ if (result.stage === 'raw') {
73
+ return {
74
+ ...update,
75
+ rawGeneration: update.candidateGeneration,
76
+ };
77
+ }
78
+ if (result.stage === 'filtered') return update;
79
+ return {
80
+ ...update,
81
+ filteredGeneration: update.candidateGeneration,
82
+ lkg: true,
83
+ };
84
+ }
85
+
86
+ export function resetIblKernelCaches(): void {
87
+ kernelCaches.clear();
88
+ }
package/src/index.ts CHANGED
@@ -11,6 +11,13 @@ export {
11
11
  renderPipelineContribution,
12
12
  samplerContribution,
13
13
  } from './assets/asset-decoders';
14
+ // Runtime contract: leases, frame receipts, detached inspection, profile,
15
+ // lifecycle state, and the single renderer event stream.
16
+ export type {
17
+ CubeCameraFaceView,
18
+ CubeCameraFaceViewInput,
19
+ } from './capture/cube-views';
20
+ export { buildCubeCameraFaceViews } from './capture/cube-views';
14
21
  export { Atmosphere } from './components/atmosphere';
15
22
  // ECS render vocabulary and grouped closed values.
16
23
  export type {
@@ -42,6 +49,18 @@ export {
42
49
  TONEMAP_REINHARD,
43
50
  TONEMAP_REINHARD_EXTENDED,
44
51
  } from './components/camera';
52
+ export {
53
+ CUBE_CAMERA_FACE_ORDER,
54
+ CUBE_CAMERA_UPDATE_CONTINUOUS,
55
+ CUBE_CAMERA_UPDATE_ON_DEMAND,
56
+ CUBE_CAMERA_UPDATE_ONCE,
57
+ CubeCamera,
58
+ type CubeCameraData,
59
+ type CubeCameraFace,
60
+ type CubeCameraUpdateIntent,
61
+ cubeCameraUpdateIntentFromF32,
62
+ cubeCameraUpdateIntentToF32,
63
+ } from './components/cube-camera';
45
64
  export * from './components/directional-light';
46
65
  export { Fog } from './components/fog';
47
66
  export { Instances, type InstancesData } from './components/instances';
@@ -59,6 +78,16 @@ export {
59
78
  pointShapeFromU32,
60
79
  } from './components/points';
61
80
  export { PostProcessParams } from './components/post-process-params';
81
+ export {
82
+ REFLECTION_PROBE_UPDATE_CONTINUOUS,
83
+ REFLECTION_PROBE_UPDATE_ON_CHANGE,
84
+ REFLECTION_PROBE_UPDATE_ONCE,
85
+ ReflectionProbe,
86
+ type ReflectionProbeData,
87
+ type ReflectionProbeUpdateIntent,
88
+ reflectionProbeUpdateIntentFromF32,
89
+ reflectionProbeUpdateIntentToF32,
90
+ } from './components/reflection-probe';
62
91
  export { SceneInstance } from './components/scene-instance';
63
92
  export {
64
93
  SKYBOX_MODE_CUBEMAP,
@@ -84,8 +113,14 @@ export type {
84
113
  // Public structured operation failure union. Concrete error classes stay
85
114
  // behind the Renderer Result/event boundary.
86
115
  export type {
116
+ ReflectionProbeBudgetExceededDetail,
87
117
  RenderError,
88
118
  RenderErrorCode,
119
+ RenderIntentInvalidDetail,
120
+ RenderTargetCapabilityMissingDetail,
121
+ RenderTargetDescriptorInvalidDetail,
122
+ RenderTargetOperationFailedDetail,
123
+ RenderTargetStateInvalidDetail,
89
124
  SceneDataUnavailableDetail,
90
125
  SceneDataUnavailableReason,
91
126
  } from './errors/render';
@@ -131,13 +166,43 @@ export type {
131
166
  RenderFeatureStatus,
132
167
  RenderFeatureWorldVisibilitySnapshot,
133
168
  } from './features/types';
169
+ export {
170
+ beginProbeIblUpdate,
171
+ createIblKernelCache,
172
+ createProbeIblOutput,
173
+ type IblKernelCache,
174
+ type ProbeIblOutput,
175
+ type ProbeIblUpdate,
176
+ publishProbeIblOutput,
177
+ REFLECTION_PROBE_IBL_STAGES,
178
+ type ReflectionProbeIblStage,
179
+ resetIblKernelCaches,
180
+ } from './ibl/kernel-cache';
134
181
  export type {
135
182
  BloomInspection,
136
183
  MotionBlurInspection,
137
184
  MotionBlurInspectionStatus,
185
+ ReflectionProbeInspection,
186
+ ReflectionProbeSelectionInspection,
138
187
  TransmissionInspection,
139
188
  } from './inspection-types';
140
189
  export { Materials, MaterialTransmissionContractError } from './materials';
190
+ export {
191
+ type MeshMaterialBindingObservation,
192
+ type MeshMaterialBindingPreparationFailure,
193
+ type MeshMaterialBindingReadiness,
194
+ type MeshMaterialBindingResidency,
195
+ type MeshMaterialBindingSamplerObservation,
196
+ type MeshMaterialBindingSummary,
197
+ type MeshMaterialBindingTextureObservation,
198
+ projectMeshMaterialBindingObservation,
199
+ summarizeMeshMaterialBindings,
200
+ } from './mesh-material-bindings';
201
+ export type {
202
+ StandardClusterTransportInspection,
203
+ StandardLightingInspection,
204
+ StandardNoLocalLightingInspection,
205
+ } from './pipeline/standard-lighting/inspection';
141
206
  // The host-facing default is a stable profile value; implementation-only
142
207
  // pipeline helpers remain behind the package boundary.
143
208
  export {
@@ -146,6 +211,10 @@ export {
146
211
  type StandardVolumetricFogQuality,
147
212
  } from './pipeline/standard-profile';
148
213
  export { renderComponentsPlugin } from './plugin';
214
+ export {
215
+ inspectPointShadow,
216
+ type PointShadowInspection,
217
+ } from './point-shadow-inspection';
149
218
  export {
150
219
  admitPointsLines,
151
220
  type LinesStyleInput,
@@ -156,9 +225,67 @@ export {
156
225
  type PointsStyleInput,
157
226
  } from './points-lines/admission';
158
227
  export type { PointsLinesInspection } from './points-lines/inspection';
228
+ /**
229
+ * Public bounded GPU pass facts. Use the single `gpuPassTiming` opt-in,
230
+ * `draw()` receipt, and `observe(receipt, { include: ['timings'] })` route.
231
+ * Branch on the closed statuses `complete`, `partial`, `unavailable`, and
232
+ * `failed`; each reason/error exposes `code`, `expected`, `hint`, and `detail`.
233
+ * `latestKnownGood` is separate from current status and completeness. These
234
+ * facts describe pass durations, never frame latency. The bounded contract is
235
+ * in `record/gpu-pass-timing/contract.ts`; benchmark acceptance is fail-closed
236
+ * in `bench/gpu-pass-timing/validator.ts`, and recovery follows the producer's
237
+ * structured hint rather than a second timing API.
238
+ */
239
+ export type {
240
+ GpuPassTimingCapability,
241
+ GpuPassTimingEntry,
242
+ GpuPassTimingError,
243
+ GpuPassTimingErrorCode,
244
+ GpuPassTimingFrame,
245
+ GpuPassTimingMeasuredEntry,
246
+ GpuPassTimingMeasurementSource,
247
+ GpuPassTimingObservation,
248
+ GpuPassTimingOptions,
249
+ GpuPassTimingPassIdentity,
250
+ GpuPassTimingReason,
251
+ GpuPassTimingReasonCode,
252
+ GpuPassTimingRef,
253
+ GpuPassTimingUnmeasuredEntry,
254
+ } from './record/gpu-pass-timing/index.js';
159
255
  // Runtime contract: leases, frame receipts, detached inspection, profile,
160
256
  // lifecycle state, and the single renderer event stream.
257
+ export {
258
+ advanceProbeFilter,
259
+ boxProjectReflectionDirection,
260
+ commitProbeFilterStep,
261
+ createProbeFilterState,
262
+ type ProbeFilterState,
263
+ probeFilterIsSteady,
264
+ } from './reflection/filter';
265
+ export {
266
+ buildReflectionProbeTable,
267
+ type ReflectionProbeTable,
268
+ type ReflectionProbeTableRow,
269
+ reflectionProbeTableBytes,
270
+ } from './reflection/gpu-table';
271
+ export {
272
+ admitReflectionProbe,
273
+ DEFAULT_REFLECTION_PROBE_LIMITS,
274
+ estimateReflectionProbeBytes,
275
+ type ReflectionProbeAdmission,
276
+ type ReflectionProbeAdmissionLimits,
277
+ type ReflectionProbeFact,
278
+ type ReflectionProbeInput,
279
+ ReflectionProbeProjection,
280
+ type ReflectionProbeProjectionSnapshot,
281
+ type ReflectionProbeSelection,
282
+ type ReflectionProbeSelectionResult,
283
+ type SkylightSelection,
284
+ selectReflectionProbe,
285
+ validateReflectionProbeInput,
286
+ } from './reflection/projection';
161
287
  export type {
288
+ CubeCameraSnapshot,
162
289
  FrameCamera,
163
290
  FrameEnvironment,
164
291
  FrameObservationRequest,
@@ -179,6 +306,27 @@ export type {
179
306
  TemporalInspectionInput,
180
307
  TemporalInspectionStatus,
181
308
  } from './renderer-inspect';
309
+ export {
310
+ SHADOW_ATLAS_DEFAULT_FACE_SIZE,
311
+ SHADOW_ATLAS_DEFAULT_LAYERS,
312
+ ShadowAtlas,
313
+ } from './shadow-atlas';
314
+ export type {
315
+ RenderTarget,
316
+ RenderTargetAdmissionLimits,
317
+ RenderTargetDepthFormat,
318
+ RenderTargetDescriptor,
319
+ RenderTargetFormat,
320
+ RenderTargetMipLevels,
321
+ RenderTargetReadbackData,
322
+ RenderTargetReadbackRequest,
323
+ RenderTargetReadbackTicket,
324
+ RenderTargetSampleCount,
325
+ RenderTargetShape,
326
+ RenderTargetTextureAspect,
327
+ RenderTargetTextureSource,
328
+ RenderTargetTextureSourceOptions,
329
+ } from './targets/contracts';
182
330
  export type { TemporalInspection, TemporalResourceInspection } from './temporal/inspection';
183
331
  export {
184
332
  SCENE_DATA_TEMPORAL_V1_DESCRIPTOR,
@@ -4,6 +4,7 @@
4
4
  * extract/record dependency graph.
5
5
  */
6
6
 
7
+ import type { DirectionalShadowFilterLabel } from './components/directional-shadow-filter';
7
8
  import type { PointsLinesInspection } from './points-lines/inspection';
8
9
 
9
10
  export type BloomGraphStatus = 'empty' | 'valid' | 'invalid';
@@ -65,6 +66,61 @@ export interface GpuDrivenProductionInspection {
65
66
  readonly cpuFallbackDrawItems: number;
66
67
  }
67
68
 
69
+ export type DirectionalShadowEffectiveProfile =
70
+ | 'off'
71
+ | DirectionalShadowFilterLabel
72
+ | 'rhi-null-structural';
73
+
74
+ export type DirectionalShadowProfile = 'off' | DirectionalShadowFilterLabel;
75
+
76
+ export interface DirectionalShadowInspectionError {
77
+ readonly code: string;
78
+ readonly expected: string;
79
+ readonly hint: string;
80
+ readonly detail?: unknown;
81
+ }
82
+
83
+ /** Bounded, detached Directional shadow facts for AI-readable inspection. */
84
+ export interface DirectionalShadowInspection {
85
+ readonly requested: DirectionalShadowProfile;
86
+ readonly effective: DirectionalShadowEffectiveProfile;
87
+ readonly status: 'accepted' | 'fallback' | 'rejected';
88
+ readonly fallbackReason?: 'webgl2-unsupported' | 'rhi-null-structural' | 'candidate-failed';
89
+ readonly lastKnownGood: boolean;
90
+ readonly pixelEvidence: 'available' | 'not-available';
91
+ readonly cascadeCount: number;
92
+ readonly mapSize: number;
93
+ readonly atlasBytes: number;
94
+ readonly writerPasses: number;
95
+ readonly blockerTaps: number;
96
+ readonly filterTapUpperBound: number;
97
+ readonly seamTapUpperBound: number;
98
+ readonly shadowAngularRadius: number | undefined;
99
+ readonly maxPenumbraTexels: number | undefined;
100
+ readonly deviceGeneration: number;
101
+ readonly graphGeneration: number;
102
+ readonly error?: DirectionalShadowInspectionError;
103
+ }
104
+
105
+ export type ReflectionProbeSelectionInspection =
106
+ | { readonly kind: 'probe'; readonly worldId: number; readonly entityKey: number }
107
+ | { readonly kind: 'skylight' };
108
+
109
+ export interface ReflectionProbeInspection {
110
+ readonly selection: ReflectionProbeSelectionInspection;
111
+ readonly factCount: number;
112
+ readonly acceptedCount: number;
113
+ readonly activeCount: number;
114
+ readonly rawFacesCaptured: number;
115
+ readonly filteredStepsCompleted: number;
116
+ readonly filteredMipLevels: readonly number[];
117
+ readonly scheduledRawFaces: number;
118
+ readonly scheduledFilteredSteps: number;
119
+ readonly pipelineReady: boolean;
120
+ readonly pipelineWarmupAttempts: number;
121
+ readonly pipelineWarmupFailure?: string;
122
+ }
123
+
68
124
  /** Detached semantic descriptor for the Standard scene-temporal producer. */
69
125
  export interface TemporalTargetInspection {
70
126
  readonly identity: 'standard-scene-temporal';
@@ -0,0 +1,171 @@
1
+ import { box3, type Mat4Like, mat4 } from '@forgeax/engine-math';
2
+
3
+ /**
4
+ * Inputs owned by the render extract/persistent-scene boundary. Bounds are
5
+ * deliberately derived here instead of being added to the author-facing
6
+ * `Instances` component or written back to a Pack asset.
7
+ */
8
+ export interface DerivedInstancesUnionBoundsInput {
9
+ readonly meshAabb: ArrayLike<number> | undefined;
10
+ readonly entityWorld: ArrayLike<number>;
11
+ readonly transforms: ArrayLike<number> | undefined;
12
+ }
13
+
14
+ /**
15
+ * Renderer-owned cache key. Each generation is supplied by the owner that can
16
+ * observe that mutation: mesh/AABB publication, entity world transform, and
17
+ * the packed instance matrix array respectively.
18
+ */
19
+ export interface InstanceBoundsCacheKey {
20
+ /** Optional scene/world scope for composite scenes; omitted for standalone tests. */
21
+ readonly worldId?: number;
22
+ readonly entityKey: number;
23
+ readonly meshGeneration: number;
24
+ readonly transformGeneration: number;
25
+ readonly matrixGeneration: number;
26
+ }
27
+
28
+ export type InstanceBoundsCacheInput = InstanceBoundsCacheKey & DerivedInstancesUnionBoundsInput;
29
+
30
+ /**
31
+ * Stable bitwise fingerprint for detached numeric facts used by the cache.
32
+ * Float32 bits are hashed rather than decimal text so a real matrix/AABB
33
+ * change invalidates the projection without keeping another mutable ledger.
34
+ */
35
+ export function fingerprintNumericArray(values: ArrayLike<number>): number {
36
+ let hash = 0x811c9dc5;
37
+ const scalar = new Float32Array(1);
38
+ const bits = new Uint32Array(scalar.buffer);
39
+ for (let index = 0; index < values.length; index += 1) {
40
+ scalar[0] = Number(values[index]);
41
+ hash = Math.imul(hash ^ (bits[0] ?? 0), 0x01000193) >>> 0;
42
+ }
43
+ return hash >>> 0;
44
+ }
45
+
46
+ function finiteAabb(aabb: ArrayLike<number> | undefined): aabb is ArrayLike<number> {
47
+ if (aabb === undefined || aabb.length < 6) return false;
48
+ const minX = aabb[0] as number;
49
+ const minY = aabb[1] as number;
50
+ const minZ = aabb[2] as number;
51
+ const maxX = aabb[3] as number;
52
+ const maxY = aabb[4] as number;
53
+ const maxZ = aabb[5] as number;
54
+ return (
55
+ Number.isFinite(minX) &&
56
+ Number.isFinite(minY) &&
57
+ Number.isFinite(minZ) &&
58
+ Number.isFinite(maxX) &&
59
+ Number.isFinite(maxY) &&
60
+ Number.isFinite(maxZ) &&
61
+ minX <= maxX &&
62
+ minY <= maxY &&
63
+ minZ <= maxZ
64
+ );
65
+ }
66
+
67
+ function finiteMatrix(matrix: ArrayLike<number>): matrix is Mat4Like {
68
+ if (matrix.length < 16) return false;
69
+ for (let index = 0; index < 16; index += 1) {
70
+ if (!Number.isFinite(matrix[index] as number)) return false;
71
+ }
72
+ return true;
73
+ }
74
+
75
+ function includeBox(out: box3.Box3, candidate: box3.Box3Like): void {
76
+ for (const x of [candidate[0] as number, candidate[3] as number]) {
77
+ for (const y of [candidate[1] as number, candidate[4] as number]) {
78
+ for (const z of [candidate[2] as number, candidate[5] as number]) {
79
+ box3.expandByPoint(out, [x, y, z]);
80
+ }
81
+ }
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Derive the world-space AABB enclosing every instance of a mesh.
87
+ *
88
+ * `undefined` is a conservative signal: the caller must skip CPU culling
89
+ * when an input is missing, malformed, or empty. This prevents an empty
90
+ * `Instances` row from becoming a synthetic identity draw/bounds candidate.
91
+ */
92
+ export function deriveInstancesUnionBounds(
93
+ input: DerivedInstancesUnionBoundsInput,
94
+ ): Float32Array | undefined {
95
+ const { meshAabb, entityWorld, transforms } = input;
96
+ if (!finiteAabb(meshAabb) || !finiteMatrix(entityWorld) || transforms === undefined) {
97
+ return undefined;
98
+ }
99
+ if (transforms.length === 0 || transforms.length % 16 !== 0) return undefined;
100
+
101
+ const local = box3.create(
102
+ meshAabb[0] as number,
103
+ meshAabb[1] as number,
104
+ meshAabb[2] as number,
105
+ meshAabb[3] as number,
106
+ meshAabb[4] as number,
107
+ meshAabb[5] as number,
108
+ );
109
+ const union = box3.create();
110
+ const composed = mat4.create();
111
+ const transformed = box3.create();
112
+ for (let offset = 0; offset < transforms.length; offset += 16) {
113
+ const instance = new Float32Array(16);
114
+ for (let lane = 0; lane < 16; lane += 1) {
115
+ instance[lane] = transforms[offset + lane] as number;
116
+ }
117
+ if (!finiteMatrix(instance)) return undefined;
118
+ mat4.multiply(composed, entityWorld, instance);
119
+ box3.transformBox3(transformed, local, composed);
120
+ if (!finiteAabb(transformed)) return undefined;
121
+ includeBox(union, transformed);
122
+ }
123
+ return new Float32Array(union);
124
+ }
125
+
126
+ interface InstanceBoundsCacheEntry extends InstanceBoundsCacheKey {
127
+ readonly bounds: Float32Array | undefined;
128
+ }
129
+
130
+ /**
131
+ * Small renderer-owned per-entity cache. A changed generation replaces the
132
+ * entry atomically; packed-id reuse is safe when its owner supplies a new
133
+ * generation (the normal RenderScene slot generation contract).
134
+ */
135
+ export class InstanceBoundsCache {
136
+ private readonly entries = new Map<string, InstanceBoundsCacheEntry>();
137
+
138
+ private key(input: Pick<InstanceBoundsCacheKey, 'worldId' | 'entityKey'>): string {
139
+ return `${input.worldId === undefined ? '' : `${input.worldId}:`}${input.entityKey}`;
140
+ }
141
+
142
+ get(input: InstanceBoundsCacheInput): Float32Array | undefined {
143
+ const previous = this.entries.get(this.key(input));
144
+ if (
145
+ previous !== undefined &&
146
+ previous.meshGeneration === input.meshGeneration &&
147
+ previous.transformGeneration === input.transformGeneration &&
148
+ previous.matrixGeneration === input.matrixGeneration
149
+ ) {
150
+ return previous.bounds;
151
+ }
152
+ const bounds = deriveInstancesUnionBounds(input);
153
+ this.entries.set(this.key(input), {
154
+ ...(input.worldId === undefined ? {} : { worldId: input.worldId }),
155
+ entityKey: input.entityKey,
156
+ meshGeneration: input.meshGeneration,
157
+ transformGeneration: input.transformGeneration,
158
+ matrixGeneration: input.matrixGeneration,
159
+ bounds,
160
+ });
161
+ return bounds;
162
+ }
163
+
164
+ invalidate(entityKey?: number, worldId?: number): void {
165
+ if (entityKey === undefined) {
166
+ this.entries.clear();
167
+ } else {
168
+ this.entries.delete(this.key({ entityKey, ...(worldId === undefined ? {} : { worldId }) }));
169
+ }
170
+ }
171
+ }
@@ -156,6 +156,15 @@ export const SPOT_LIGHT_STD430_BYTES = 80;
156
156
  export const SPOT_LIGHT_SHADOW_TILE_SENTINEL = -1;
157
157
  /** Sentinel for a projector-only SpotLight with no shadow atlas tile. */
158
158
  export const SPOT_LIGHT_PROJECTOR_ONLY_SENTINEL = -2;
159
+ /**
160
+ * High-bit marker carried by the shared LightSlot identity lane when this
161
+ * SpotLight is the one projector selected for the frame. The lower 30 bits
162
+ * retain the shadow-atlas tile (0..3), or zero for projector-only lights, so
163
+ * one 64-byte slot carries both shadow and projector identity without growing
164
+ * the cluster UBO or inventing a second light list.
165
+ */
166
+ export const SPOT_LIGHT_PROJECTOR_FLAG = 0x40000000;
167
+ export const SPOT_LIGHT_TILE_MASK = SPOT_LIGHT_PROJECTOR_FLAG - 1;
159
168
  /** Byte size of the array header (count u32 + 12 B pad to 16 B stride). */
160
169
  export const LIGHT_ARRAY_HEADER_BYTES = 16;
161
170
  /** Maximum number of slots packed in the first-slice cap layout (D-S3). */
@@ -310,6 +319,7 @@ export interface PointShadowSlotInfo {
310
319
  export function packLightSlot(
311
320
  snap: PointLightSnapshot | SpotLightSnapshot,
312
321
  shadow?: PointShadowSlotInfo,
322
+ projectorSelected?: boolean,
313
323
  ): Float32Array {
314
324
  const out = new Float32Array(16);
315
325
  // position (vec3<f32> + invRangeSquared at lane .w)
@@ -340,12 +350,17 @@ export function packLightSlot(
340
350
  out[14] = shadow.near;
341
351
  out[15] = shadow.far;
342
352
  } else if (snap.kind === 'spot') {
343
- i32[13] =
344
- snap.shadowAtlasTile >= 0
353
+ // The optional argument is explicit at the record owner. Keeping the
354
+ // source-derived fallback preserves standalone packing fixtures while the
355
+ // production path marks only the accepted projector identity.
356
+ const hasProjector =
357
+ projectorSelected ?? (snap.projectorAsset !== undefined && snap.lightViewProj !== undefined);
358
+ const tile = snap.shadowAtlasTile >= 0 ? snap.shadowAtlasTile : 0;
359
+ i32[13] = hasProjector
360
+ ? SPOT_LIGHT_PROJECTOR_FLAG | (tile & SPOT_LIGHT_TILE_MASK)
361
+ : snap.shadowAtlasTile >= 0
345
362
  ? snap.shadowAtlasTile
346
- : snap.projectorAsset !== undefined && snap.lightViewProj !== undefined
347
- ? SPOT_LIGHT_PROJECTOR_ONLY_SENTINEL
348
- : SPOT_LIGHT_SHADOW_TILE_SENTINEL;
363
+ : SPOT_LIGHT_SHADOW_TILE_SENTINEL;
349
364
  out[14] = snap.shadowIntensity ?? 1;
350
365
  // HDRP has no separate 80-byte SpotLight payload. Reuse the otherwise
351
366
  // unused spot `.w` lane so the cluster path consumes the same authored PCF