@forgeax/engine-render 0.1.33 → 0.1.34

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 (518) hide show
  1. package/README.md +1010 -124
  2. package/dist/assembly/backend-contract.d.ts +2 -0
  3. package/dist/assembly/backend-contract.d.ts.map +1 -1
  4. package/dist/assembly/barrel-distortion-inspection.d.ts +3 -0
  5. package/dist/assembly/barrel-distortion-inspection.d.ts.map +1 -0
  6. package/dist/assembly/canvas-draw-guard.d.ts +3 -0
  7. package/dist/assembly/canvas-draw-guard.d.ts.map +1 -0
  8. package/dist/assembly/cloud-shadow-entry.d.ts +4 -0
  9. package/dist/assembly/cloud-shadow-entry.d.ts.map +1 -0
  10. package/dist/assembly/dynamic-geometry-host.d.ts +4 -1
  11. package/dist/assembly/dynamic-geometry-host.d.ts.map +1 -1
  12. package/dist/assembly/dynamic-geometry-runtime.d.ts +1 -1
  13. package/dist/assembly/dynamic-geometry-runtime.d.ts.map +1 -1
  14. package/dist/assembly/factory.d.ts +1 -1
  15. package/dist/assembly/factory.d.ts.map +1 -1
  16. package/dist/assembly/host-contract.d.ts +3 -3
  17. package/dist/assembly/host-contract.d.ts.map +1 -1
  18. package/dist/assembly/material/assembly.d.ts.map +1 -1
  19. package/dist/assembly/material/pipeline-helpers.d.ts +5 -0
  20. package/dist/assembly/material/pipeline-helpers.d.ts.map +1 -1
  21. package/dist/assembly/material-shader-policy.d.ts +16 -2
  22. package/dist/assembly/material-shader-policy.d.ts.map +1 -1
  23. package/dist/assembly/material-shader-prewarm.d.ts.map +1 -1
  24. package/dist/assembly/recovery/device-loss-fanout.d.ts.map +1 -1
  25. package/dist/assembly/recovery/renderer-recover.d.ts +2 -0
  26. package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -1
  27. package/dist/assembly/render-feature-post-processes.d.ts +6 -0
  28. package/dist/assembly/render-feature-post-processes.d.ts.map +1 -0
  29. package/dist/assembly/renderer-facade.d.ts.map +1 -1
  30. package/dist/assembly/renderer-feature-inspection.d.ts +1 -1
  31. package/dist/assembly/renderer-feature-inspection.d.ts.map +1 -1
  32. package/dist/assembly/renderer-frame-transaction.d.ts +12 -0
  33. package/dist/assembly/renderer-frame-transaction.d.ts.map +1 -1
  34. package/dist/assembly/renderer-inspection.d.ts +11 -0
  35. package/dist/assembly/renderer-inspection.d.ts.map +1 -1
  36. package/dist/assembly/webgpu-pbr-ready.d.ts +1 -0
  37. package/dist/assembly/webgpu-pbr-ready.d.ts.map +1 -1
  38. package/dist/assembly/webgpu-ready-per-pass.d.ts +39 -0
  39. package/dist/assembly/webgpu-ready-per-pass.d.ts.map +1 -0
  40. package/dist/assembly/webgpu-ready.d.ts +1 -1
  41. package/dist/assembly/webgpu-ready.d.ts.map +1 -1
  42. package/dist/assembly/webgpu-renderer-bootstrap.d.ts.map +1 -1
  43. package/dist/assembly/webgpu-renderer-observation-owner.d.ts +35 -0
  44. package/dist/assembly/webgpu-renderer-observation-owner.d.ts.map +1 -0
  45. package/dist/assembly/webgpu-renderer.d.ts +1 -1
  46. package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
  47. package/dist/authoring.mjs +1 -1
  48. package/dist/barrel-distortion.d.ts +47 -0
  49. package/dist/barrel-distortion.d.ts.map +1 -0
  50. package/dist/{chunk-PA5DRJNI.mjs → chunk-ELJFJ2BU.mjs} +2 -2
  51. package/dist/chunk-ELJFJ2BU.mjs.map +1 -0
  52. package/dist/{chunk-ZDQWGQW7.mjs → chunk-HEB564WF.mjs} +4 -4
  53. package/dist/{chunk-ZDQWGQW7.mjs.map → chunk-HEB564WF.mjs.map} +1 -1
  54. package/dist/{chunk-VOZWDM3L.mjs → chunk-MKDIT2EM.mjs} +1307 -188
  55. package/dist/chunk-MKDIT2EM.mjs.map +1 -0
  56. package/dist/{chunk-LEAXOMYJ.mjs → chunk-OCDLEI6I.mjs} +3 -3
  57. package/dist/{chunk-LEAXOMYJ.mjs.map → chunk-OCDLEI6I.mjs.map} +1 -1
  58. package/dist/chunk-ODZWR6JQ.mjs +4927 -0
  59. package/dist/chunk-ODZWR6JQ.mjs.map +1 -0
  60. package/dist/{chunk-3RTCJTH4.mjs → chunk-SHFUFJ5N.mjs} +184 -13
  61. package/dist/chunk-SHFUFJ5N.mjs.map +1 -0
  62. package/dist/{chunk-OO3DRYK7.mjs → chunk-UI5SFODX.mjs} +176 -96
  63. package/dist/chunk-UI5SFODX.mjs.map +1 -0
  64. package/dist/{chunk-PLAXOOX5.mjs → chunk-UINHLCZA.mjs} +561 -79
  65. package/dist/chunk-UINHLCZA.mjs.map +1 -0
  66. package/dist/{chunk-NB2S7ZRX.mjs → chunk-WEX45HKH.mjs} +127 -386
  67. package/dist/chunk-WEX45HKH.mjs.map +1 -0
  68. package/dist/cloud/density.d.ts +55 -0
  69. package/dist/cloud/density.d.ts.map +1 -0
  70. package/dist/cloud/extract.d.ts +25 -0
  71. package/dist/cloud/extract.d.ts.map +1 -0
  72. package/dist/cloud/feature.d.ts +57 -0
  73. package/dist/cloud/feature.d.ts.map +1 -0
  74. package/dist/cloud/frame-context.d.ts +8 -0
  75. package/dist/cloud/frame-context.d.ts.map +1 -0
  76. package/dist/cloud/inspection.d.ts +59 -0
  77. package/dist/cloud/inspection.d.ts.map +1 -0
  78. package/dist/cloud/optics.d.ts +56 -0
  79. package/dist/cloud/optics.d.ts.map +1 -0
  80. package/dist/cloud/parameters.d.ts +73 -0
  81. package/dist/cloud/parameters.d.ts.map +1 -0
  82. package/dist/cloud/resources.d.ts +60 -0
  83. package/dist/cloud/resources.d.ts.map +1 -0
  84. package/dist/cloud/shadow.d.ts +42 -0
  85. package/dist/cloud/shadow.d.ts.map +1 -0
  86. package/dist/cloud/temporal.d.ts +74 -0
  87. package/dist/cloud/temporal.d.ts.map +1 -0
  88. package/dist/components/barrel-distortion.d.ts +13 -0
  89. package/dist/components/barrel-distortion.d.ts.map +1 -0
  90. package/dist/components/cloud-layer.d.ts +29 -0
  91. package/dist/components/cloud-layer.d.ts.map +1 -0
  92. package/dist/components/index.d.ts +2 -0
  93. package/dist/components/index.d.ts.map +1 -1
  94. package/dist/components/instances.d.ts +20 -6
  95. package/dist/components/instances.d.ts.map +1 -1
  96. package/dist/construct-renderer.d.ts +1 -1
  97. package/dist/construct-renderer.d.ts.map +1 -1
  98. package/dist/construct-renderer.mjs +6759 -2875
  99. package/dist/construct-renderer.mjs.map +1 -1
  100. package/dist/dynamic-geometry.d.ts +5 -1
  101. package/dist/dynamic-geometry.d.ts.map +1 -1
  102. package/dist/errors/cloud.d.ts +59 -0
  103. package/dist/errors/cloud.d.ts.map +1 -0
  104. package/dist/errors/render.d.ts +18 -4
  105. package/dist/errors/render.d.ts.map +1 -1
  106. package/dist/examples/single-layer-medium-surface.d.ts +47 -0
  107. package/dist/examples/single-layer-medium-surface.d.ts.map +1 -0
  108. package/dist/extract/camera.d.ts.map +1 -1
  109. package/dist/extract/contracts.d.ts +5 -1
  110. package/dist/extract/contracts.d.ts.map +1 -1
  111. package/dist/features/barrel-distortion.d.ts +29 -0
  112. package/dist/features/barrel-distortion.d.ts.map +1 -0
  113. package/dist/features/error-reporting.d.ts +6 -2
  114. package/dist/features/error-reporting.d.ts.map +1 -1
  115. package/dist/features/host.d.ts +10 -0
  116. package/dist/features/host.d.ts.map +1 -1
  117. package/dist/features/plan.d.ts +8 -0
  118. package/dist/features/plan.d.ts.map +1 -1
  119. package/dist/features/render-graph-contribution.d.ts +5 -0
  120. package/dist/features/render-graph-contribution.d.ts.map +1 -1
  121. package/dist/features/render-graph-raster.d.ts +1 -0
  122. package/dist/features/render-graph-raster.d.ts.map +1 -1
  123. package/dist/features/targets.d.ts +2 -0
  124. package/dist/features/targets.d.ts.map +1 -1
  125. package/dist/features/types.d.ts +44 -0
  126. package/dist/features/types.d.ts.map +1 -1
  127. package/dist/fullscreen-post-process-pass.d.ts +15 -7
  128. package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
  129. package/dist/gpu-dirty-ranges.d.ts +11 -0
  130. package/dist/gpu-dirty-ranges.d.ts.map +1 -0
  131. package/dist/gpu-driven/batch-topology.d.ts +17 -0
  132. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  133. package/dist/gpu-driven/material-artifacts.d.ts +6 -0
  134. package/dist/gpu-driven/material-artifacts.d.ts.map +1 -1
  135. package/dist/gpu-driven/prepared-draw.d.ts.map +1 -1
  136. package/dist/gpu-driven/production-raster-material.d.ts +178 -0
  137. package/dist/gpu-driven/production-raster-material.d.ts.map +1 -0
  138. package/dist/gpu-driven/production-raster.d.ts +47 -3
  139. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  140. package/dist/gpu-driven/resource-allocation.d.ts +31 -0
  141. package/dist/gpu-driven/resource-allocation.d.ts.map +1 -0
  142. package/dist/gpu-driven/shadow-ownership.d.ts +14 -0
  143. package/dist/gpu-driven/shadow-ownership.d.ts.map +1 -0
  144. package/dist/gpu-driven/view-gpu.d.ts +26 -2
  145. package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
  146. package/dist/gpu-scene.d.ts +5 -0
  147. package/dist/gpu-scene.d.ts.map +1 -1
  148. package/dist/hdrp-buffers.d.ts +2 -2
  149. package/dist/hdrp-buffers.d.ts.map +1 -1
  150. package/dist/ibl/skylight-bind-group.d.ts +10 -1
  151. package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
  152. package/dist/index.d.ts +27 -7
  153. package/dist/index.d.ts.map +1 -1
  154. package/dist/index.mjs +258 -8
  155. package/dist/index.mjs.map +1 -1
  156. package/dist/inspection-types.d.ts +135 -1
  157. package/dist/inspection-types.d.ts.map +1 -1
  158. package/dist/instance-buffer-cache.d.ts +4 -4
  159. package/dist/instance-buffer-cache.d.ts.map +1 -1
  160. package/dist/instances.d.ts +48 -100
  161. package/dist/instances.d.ts.map +1 -1
  162. package/dist/internal.mjs +7 -7
  163. package/dist/pbr-pipeline.d.ts +19 -1
  164. package/dist/pbr-pipeline.d.ts.map +1 -1
  165. package/dist/pipeline/single-layer-medium-passes.d.ts +73 -0
  166. package/dist/pipeline/single-layer-medium-passes.d.ts.map +1 -0
  167. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  168. package/dist/pipeline/standard-output/graph.d.ts.map +1 -1
  169. package/dist/pipeline/standard-output/types.d.ts +4 -2
  170. package/dist/pipeline/standard-output/types.d.ts.map +1 -1
  171. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  172. package/dist/pipeline/standard-post.d.ts.map +1 -1
  173. package/dist/pipeline-builder.d.ts +1 -1
  174. package/dist/pipeline-builder.d.ts.map +1 -1
  175. package/dist/plugin.d.ts.map +1 -1
  176. package/dist/prepare/prepared-graphics-resolver.d.ts +2 -0
  177. package/dist/prepare/prepared-graphics-resolver.d.ts.map +1 -1
  178. package/dist/record/barrel-distortion-frame.d.ts +15 -0
  179. package/dist/record/barrel-distortion-frame.d.ts.map +1 -0
  180. package/dist/record/dynamic-geometry-consumption.d.ts +10 -2
  181. package/dist/record/dynamic-geometry-consumption.d.ts.map +1 -1
  182. package/dist/record/fold-buckets.d.ts +95 -0
  183. package/dist/record/fold-buckets.d.ts.map +1 -0
  184. package/dist/record/fold-instance-buffer.d.ts +6 -0
  185. package/dist/record/fold-instance-buffer.d.ts.map +1 -0
  186. package/dist/record/frame-lighting.d.ts +1 -0
  187. package/dist/record/frame-lighting.d.ts.map +1 -1
  188. package/dist/record/frame-snapshot.d.ts +37 -3
  189. package/dist/record/frame-snapshot.d.ts.map +1 -1
  190. package/dist/record/frame.d.ts +10 -61
  191. package/dist/record/frame.d.ts.map +1 -1
  192. package/dist/record/gpu-draw-receipts.d.ts +11 -0
  193. package/dist/record/gpu-draw-receipts.d.ts.map +1 -0
  194. package/dist/record/gpu-timing.d.ts +8 -0
  195. package/dist/record/gpu-timing.d.ts.map +1 -1
  196. package/dist/record/main-pass-geometry.d.ts +21 -5
  197. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  198. package/dist/record/main-pass-material.d.ts +7 -1
  199. package/dist/record/main-pass-material.d.ts.map +1 -1
  200. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  201. package/dist/record/main-pass.d.ts +13 -2
  202. package/dist/record/main-pass.d.ts.map +1 -1
  203. package/dist/record/motion-blur-receipt.d.ts +11 -0
  204. package/dist/record/motion-blur-receipt.d.ts.map +1 -0
  205. package/dist/record/probe-blend-buffer.d.ts +16 -0
  206. package/dist/record/probe-blend-buffer.d.ts.map +1 -0
  207. package/dist/record/recovery-pipeline.d.ts.map +1 -1
  208. package/dist/record/render-context.d.ts +22 -3
  209. package/dist/record/render-context.d.ts.map +1 -1
  210. package/dist/record/shadow-pass.d.ts +2 -0
  211. package/dist/record/shadow-pass.d.ts.map +1 -1
  212. package/dist/record/skybox-post-pass.d.ts +1 -1
  213. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  214. package/dist/record/typed-frame-graph.d.ts +22 -2
  215. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  216. package/dist/record/view-ubo.d.ts +11 -3
  217. package/dist/record/view-ubo.d.ts.map +1 -1
  218. package/dist/record/volume-params.d.ts +9 -0
  219. package/dist/record/volume-params.d.ts.map +1 -0
  220. package/dist/recovery/render-system-candidate.d.ts +10 -0
  221. package/dist/recovery/render-system-candidate.d.ts.map +1 -1
  222. package/dist/reflection/record-owner.d.ts.map +1 -1
  223. package/dist/render-contract.d.ts +42 -4
  224. package/dist/render-contract.d.ts.map +1 -1
  225. package/dist/render-graph-primitives.d.ts.map +1 -1
  226. package/dist/render-pipeline.d.ts +103 -2
  227. package/dist/render-pipeline.d.ts.map +1 -1
  228. package/dist/render-system-extract-tail.d.ts +3 -2
  229. package/dist/render-system-extract-tail.d.ts.map +1 -1
  230. package/dist/render-system-extract.d.ts +16 -9
  231. package/dist/render-system-extract.d.ts.map +1 -1
  232. package/dist/render-system-presentation.d.ts +23 -0
  233. package/dist/render-system-presentation.d.ts.map +1 -0
  234. package/dist/render-system-producer-roots.d.ts +10 -0
  235. package/dist/render-system-producer-roots.d.ts.map +1 -0
  236. package/dist/render-system-residency.d.ts +12 -0
  237. package/dist/render-system-residency.d.ts.map +1 -0
  238. package/dist/render-system.d.ts +19 -14
  239. package/dist/render-system.d.ts.map +1 -1
  240. package/dist/scene/probe-blend.d.ts +27 -0
  241. package/dist/scene/probe-blend.d.ts.map +1 -1
  242. package/dist/scene/render-scene.d.ts +12 -5
  243. package/dist/scene/render-scene.d.ts.map +1 -1
  244. package/dist/ssao-buffers.d.ts +9 -0
  245. package/dist/ssao-buffers.d.ts.map +1 -1
  246. package/dist/surface/dynamic-input.d.ts +133 -0
  247. package/dist/surface/dynamic-input.d.ts.map +1 -0
  248. package/dist/surface/gpu-driven.d.ts +41 -0
  249. package/dist/surface/gpu-driven.d.ts.map +1 -0
  250. package/dist/surface/submission-observation.d.ts +136 -0
  251. package/dist/surface/submission-observation.d.ts.map +1 -0
  252. package/dist/systems/skin-palette-allocator.d.ts.map +1 -1
  253. package/dist/temporal/gpu.d.ts +13 -1
  254. package/dist/temporal/gpu.d.ts.map +1 -1
  255. package/dist/temporal/index.mjs +5 -5
  256. package/dist/temporal/inspection.d.ts.map +1 -1
  257. package/dist/temporal/standard-scene-data.d.ts.map +1 -1
  258. package/dist/temporal/target-coverage-attachment.d.ts.map +1 -1
  259. package/dist/transmission/single-layer-medium.d.ts +89 -0
  260. package/dist/transmission/single-layer-medium.d.ts.map +1 -0
  261. package/dist/typed-render-graph-primitives.d.ts +14 -2
  262. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  263. package/dist/volume/component.d.ts +9 -4
  264. package/dist/volume/component.d.ts.map +1 -1
  265. package/dist/volume/extract.d.ts +1 -1
  266. package/dist/volume/extract.d.ts.map +1 -1
  267. package/dist/volume/inspection.d.ts +2 -0
  268. package/dist/volume/inspection.d.ts.map +1 -1
  269. package/dist/volume/passes.d.ts +4 -1
  270. package/dist/volume/passes.d.ts.map +1 -1
  271. package/dist/volume/resources.d.ts.map +1 -1
  272. package/package.json +22 -21
  273. package/src/__tests__/atmosphere-daylight.dawn.test.ts +72 -0
  274. package/src/__tests__/barrel-distortion-lkg.integration.test.ts +353 -0
  275. package/src/__tests__/barrel-distortion.dawn.test.ts +41 -0
  276. package/src/__tests__/barrel-distortion.unit.test.ts +364 -0
  277. package/src/__tests__/construct-renderer-identity.unit.test.ts +49 -0
  278. package/src/__tests__/detached-recovery-graph.unit.test.ts +277 -0
  279. package/src/__tests__/device-loss-fanout.unit.test.ts +36 -1
  280. package/src/__tests__/docs-gate-green.unit.test.ts +112 -1
  281. package/src/__tests__/factory-contract.integration.test.ts +295 -45
  282. package/src/__tests__/feature-depth-input.dawn.test.ts +129 -2
  283. package/src/__tests__/fullscreen-feature-plan.unit.test.ts +14 -3
  284. package/src/__tests__/gpu-dirty-ranges.unit.test.ts +22 -0
  285. package/src/__tests__/gpu-driven-baseline.characterization.test.ts +16 -3
  286. package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +12 -0
  287. package/src/__tests__/gpu-driven-material-artifacts.unit.test.ts +143 -0
  288. package/src/__tests__/gpu-driven-pbr.dawn.test.ts +2 -2
  289. package/src/__tests__/gpu-driven-production.integration.test.ts +482 -3
  290. package/src/__tests__/gpu-driven-shadow-views.dawn.test.ts +6 -5
  291. package/src/__tests__/gpu-driven-view-gpu-evidence.ts +94 -6
  292. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +18 -2
  293. package/src/__tests__/gpu-driven-view.browser.test.ts +5 -8
  294. package/src/__tests__/gpu-driven-view.dawn.test.ts +6 -2
  295. package/src/__tests__/gpu-driven-view.unit.test.ts +1 -1
  296. package/src/__tests__/gpu-pass-timing-lifecycle.integration.test.ts +1 -0
  297. package/src/__tests__/gpu-pass-timing.browser.test.ts +6 -4
  298. package/src/__tests__/gpu-pass-timing.rhi-null.unit.test.ts +1 -0
  299. package/src/__tests__/gpu-scene.unit.test.ts +119 -9
  300. package/src/__tests__/instance-allocation-failure.unit.test.ts +57 -0
  301. package/src/__tests__/instances-culling.integration.test.ts +10 -36
  302. package/src/__tests__/instances-store.unit.test.ts +97 -305
  303. package/src/__tests__/instances-world-ownership.integration.test.ts +62 -0
  304. package/src/__tests__/main-pass-material-medium-selection.unit.test.ts +146 -0
  305. package/src/__tests__/material-shader-prewarm.unit.test.ts +59 -0
  306. package/src/__tests__/material-snapshot-mutation.integration.test.ts +39 -1
  307. package/src/__tests__/morph-culling-reentry.dawn.test.ts +5 -5
  308. package/src/__tests__/morph-visual-evidence.integration.test.ts +8 -7
  309. package/src/__tests__/motion-blur-compute.dawn.test.ts +13 -7
  310. package/src/__tests__/motion-blur.pixel.integration.test.ts +2 -2
  311. package/src/__tests__/nineslice-handle-surface.unit.test.ts +4 -4
  312. package/src/__tests__/pbr-view-layout.unit.test.ts +9 -0
  313. package/src/__tests__/physical-clearcoat.integration.test.ts +58 -0
  314. package/src/__tests__/prepared-graphics-capability.unit.test.ts +66 -2
  315. package/src/__tests__/probe-bindings.unit.test.ts +582 -1
  316. package/src/__tests__/probe-scene-dirty.integration.test.ts +28 -1
  317. package/src/__tests__/receipt-observation-retirement.unit.test.ts +65 -0
  318. package/src/__tests__/recovery-candidate-prepare.contract.test.ts +69 -0
  319. package/src/__tests__/render-error-code-owner.test-d.ts +7 -0
  320. package/src/__tests__/render-feature-diagnostics.unit.test.ts +15 -2
  321. package/src/__tests__/render-feature-error-reporting.unit.test.ts +21 -1
  322. package/src/__tests__/render-feature-prepared-diagnostics.unit.test.ts +10 -1
  323. package/src/__tests__/render-feature-retry.unit.test.ts +12 -1
  324. package/src/__tests__/render-scene-mixed-updates.integration.test.ts +145 -19
  325. package/src/__tests__/render-scene-projection.unit.test.ts +36 -11
  326. package/src/__tests__/render-scene-temporal-retry.unit.test.ts +20 -20
  327. package/src/__tests__/render-target-probe-soak.perf.test.ts +2 -2
  328. package/src/__tests__/render-temporal-environment-consumer.browser.test.ts +3 -3
  329. package/src/__tests__/render-temporal-environment-consumer.dawn.test.ts +5 -2
  330. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +41 -7
  331. package/src/__tests__/renderer-frame-generation-fence.integration.test.ts +16 -0
  332. package/src/__tests__/renderer-frame-transaction.integration.test.ts +26 -10
  333. package/src/__tests__/resource-allocation.unit.test.ts +123 -0
  334. package/src/__tests__/single-layer-medium-passes.unit.test.ts +275 -0
  335. package/src/__tests__/skin-motion-regression.unit.test.ts +70 -1
  336. package/src/__tests__/skin-palette-recycling.dawn.test.ts +97 -0
  337. package/src/__tests__/skinned-shadow-caster.test.ts +15 -0
  338. package/src/__tests__/ssr-carrier-public-contract.unit.test.ts +21 -0
  339. package/src/__tests__/ssr-gpu-dispatch.browser.test.ts +4 -4
  340. package/src/__tests__/ssr-gpu-dispatch.dawn.test.ts +2 -2
  341. package/src/__tests__/ssr-gpu-dispatch.ts +1 -1
  342. package/src/__tests__/ssr-receiver-coverage.dawn.test.ts +6 -2
  343. package/src/__tests__/standard-output-chain-contract.unit.test.ts +28 -0
  344. package/src/__tests__/standard-pbr-artifact-assembly.unit.test.ts +46 -1
  345. package/src/__tests__/standard-pipeline.integration.test.ts +182 -1
  346. package/src/__tests__/surface-dynamic-buffer-lifecycle.unit.test.ts +506 -0
  347. package/src/__tests__/surface-submission-observation.unit.test.ts +812 -0
  348. package/src/__tests__/temporal-history-lifecycle.unit.test.ts +32 -0
  349. package/src/__tests__/temporal-performance.integration.test.ts +3 -3
  350. package/src/__tests__/visibility-instances.integration.test.ts +7 -19
  351. package/src/__tests__/visibility-producer-matrix.integration.test.ts +9 -20
  352. package/src/__tests__/volume-optics-oracle.unit.test.ts +4 -2
  353. package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +30 -20
  354. package/src/__tests__/volumetric-fog-errors.unit.test.ts +4 -3
  355. package/src/__tests__/volumetric-fog-multiple.unit.test.ts +55 -0
  356. package/src/__tests__/volumetric-fog-spot-frame-optics.integration.test.ts +1 -1
  357. package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +113 -9
  358. package/src/__tests__/volumetric-fog-topology.unit.test.ts +5 -3
  359. package/src/assembly/backend-contract.ts +30 -0
  360. package/src/assembly/barrel-distortion-inspection.ts +22 -0
  361. package/src/assembly/canvas-draw-guard.ts +11 -0
  362. package/src/assembly/cloud-shadow-entry.ts +22 -0
  363. package/src/assembly/dynamic-geometry-host.ts +65 -37
  364. package/src/assembly/dynamic-geometry-runtime.ts +7 -1
  365. package/src/assembly/factory.ts +2 -30
  366. package/src/assembly/host-contract.ts +3 -3
  367. package/src/assembly/material/assembly.ts +25 -13
  368. package/src/assembly/material/pipeline-helpers.ts +53 -12
  369. package/src/assembly/material-shader-policy.ts +55 -0
  370. package/src/assembly/material-shader-prewarm.ts +21 -10
  371. package/src/assembly/recovery/device-loss-fanout.ts +13 -0
  372. package/src/assembly/recovery/renderer-recover.ts +41 -18
  373. package/src/assembly/render-feature-post-processes.ts +54 -0
  374. package/src/assembly/renderer-facade.ts +4 -1
  375. package/src/assembly/renderer-feature-inspection.ts +8 -2
  376. package/src/assembly/renderer-frame-transaction.ts +23 -0
  377. package/src/assembly/renderer-inspection.ts +28 -0
  378. package/src/assembly/skin-palette-owner.ts +1 -1
  379. package/src/assembly/temporal-post-process-prewarm.ts +3 -3
  380. package/src/assembly/webgpu-pbr-ready.ts +9 -1
  381. package/src/assembly/webgpu-ready-per-pass.ts +104 -0
  382. package/src/assembly/webgpu-ready.ts +115 -61
  383. package/src/assembly/webgpu-renderer-bootstrap.ts +0 -12
  384. package/src/assembly/webgpu-renderer-observation-owner.ts +222 -0
  385. package/src/assembly/webgpu-renderer.ts +214 -252
  386. package/src/assembly/webgpu-vertex-layouts.ts +1 -1
  387. package/src/barrel-distortion.ts +247 -0
  388. package/src/cloud/__tests__/cloud-density-parity.dawn.test.ts +173 -0
  389. package/src/cloud/__tests__/cloud-layer-framebuffer-y.dawn.test.ts +265 -0
  390. package/src/cloud/__tests__/cloud-layer.browser.test.ts +432 -0
  391. package/src/cloud/__tests__/cloud-layer.unit.test.ts +634 -0
  392. package/src/cloud/density.ts +600 -0
  393. package/src/cloud/extract.ts +110 -0
  394. package/src/cloud/feature.ts +1810 -0
  395. package/src/cloud/frame-context.ts +42 -0
  396. package/src/cloud/inspection.ts +130 -0
  397. package/src/cloud/optics.ts +285 -0
  398. package/src/cloud/parameters.ts +255 -0
  399. package/src/cloud/resources.ts +122 -0
  400. package/src/cloud/shadow.ts +167 -0
  401. package/src/cloud/temporal.ts +226 -0
  402. package/src/components/barrel-distortion.ts +46 -0
  403. package/src/components/cloud-layer.ts +47 -0
  404. package/src/components/index.ts +2 -0
  405. package/src/components/instances.ts +18 -45
  406. package/src/components/sprite-instances.ts +1 -1
  407. package/src/construct-renderer.ts +1 -1
  408. package/src/dynamic-geometry.ts +12 -1
  409. package/src/errors/cloud.ts +103 -0
  410. package/src/errors/render.ts +44 -7
  411. package/src/examples/single-layer-medium-surface.ts +59 -0
  412. package/src/extract/camera.ts +15 -1
  413. package/src/extract/contracts.ts +5 -0
  414. package/src/features/barrel-distortion.ts +172 -0
  415. package/src/features/error-reporting.ts +26 -6
  416. package/src/features/host.ts +115 -14
  417. package/src/features/plan.ts +8 -0
  418. package/src/features/render-graph-contribution.ts +9 -0
  419. package/src/features/render-graph-raster.ts +45 -3
  420. package/src/features/targets.ts +66 -0
  421. package/src/features/types.ts +47 -0
  422. package/src/fullscreen-post-process-pass.ts +41 -9
  423. package/src/gpu-dirty-ranges.ts +48 -0
  424. package/src/gpu-driven/batch-topology.ts +53 -0
  425. package/src/gpu-driven/material-artifacts.ts +33 -25
  426. package/src/gpu-driven/prepared-draw.ts +12 -4
  427. package/src/gpu-driven/production-raster-material.ts +1894 -0
  428. package/src/gpu-driven/production-raster.ts +825 -784
  429. package/src/gpu-driven/resource-allocation.ts +142 -0
  430. package/src/gpu-driven/shadow-ownership.ts +71 -0
  431. package/src/gpu-driven/view-gpu.ts +164 -10
  432. package/src/gpu-scene.ts +75 -84
  433. package/src/hdrp-buffers.ts +4 -3
  434. package/src/ibl/skylight-bind-group.ts +49 -7
  435. package/src/index.ts +196 -9
  436. package/src/inspection-types.ts +162 -0
  437. package/src/instance-buffer-cache.ts +4 -4
  438. package/src/instances.ts +149 -569
  439. package/src/pbr-pipeline.ts +219 -13
  440. package/src/pipeline/single-layer-medium-passes.ts +267 -0
  441. package/src/pipeline/standard-forward-lane.ts +313 -57
  442. package/src/pipeline/standard-output/graph.ts +8 -2
  443. package/src/pipeline/standard-output/types.ts +12 -1
  444. package/src/pipeline/standard-pipeline.ts +291 -18
  445. package/src/pipeline/standard-post.ts +90 -24
  446. package/src/pipeline-builder.ts +1 -1
  447. package/src/plugin.ts +4 -0
  448. package/src/points-lines/__tests__/points-lines.bench.ts +17 -8
  449. package/src/prepare/prepared-graphics-resolver.ts +2 -0
  450. package/src/record/__tests__/main-pass-geometry.unit.test.ts +30 -0
  451. package/src/record/__tests__/sprite-material-abi.unit.test.ts +1 -1
  452. package/src/record/__tests__/temporal-payload.unit.test.ts +1 -1
  453. package/src/record/__tests__/transparent-group2-selection.unit.test.ts +11 -0
  454. package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +1 -0
  455. package/src/record/__tests__/view-ubo-layout.unit.test.ts +4 -3
  456. package/src/record/barrel-distortion-frame.ts +110 -0
  457. package/src/record/dynamic-geometry-consumption.ts +39 -9
  458. package/src/record/fold-buckets.ts +852 -0
  459. package/src/record/fold-instance-buffer.ts +51 -0
  460. package/src/record/frame-lighting.ts +36 -2
  461. package/src/record/frame-snapshot.ts +39 -3
  462. package/src/record/frame.ts +217 -1084
  463. package/src/record/gpu-draw-receipts.ts +38 -0
  464. package/src/record/gpu-timing.ts +48 -5
  465. package/src/record/main-pass-geometry.ts +251 -82
  466. package/src/record/main-pass-material.ts +66 -4
  467. package/src/record/main-pass-sprite-draws.ts +7 -65
  468. package/src/record/main-pass.ts +156 -20
  469. package/src/record/motion-blur-receipt.ts +91 -0
  470. package/src/record/probe-blend-buffer.ts +180 -0
  471. package/src/record/recovery-pipeline.ts +35 -3
  472. package/src/record/render-context.ts +39 -3
  473. package/src/record/shadow-pass.ts +30 -1
  474. package/src/record/skybox-post-pass.ts +28 -4
  475. package/src/record/typed-frame-graph.ts +770 -134
  476. package/src/record/view-ubo.ts +26 -3
  477. package/src/record/volume-params.ts +225 -0
  478. package/src/recovery/render-system-candidate.ts +235 -42
  479. package/src/reflection/record-owner.ts +7 -3
  480. package/src/render-contract.ts +48 -4
  481. package/src/render-graph-primitives.ts +7 -1
  482. package/src/render-pipeline.ts +130 -0
  483. package/src/render-system-extract-tail.ts +57 -61
  484. package/src/render-system-extract.ts +74 -76
  485. package/src/render-system-presentation.ts +56 -0
  486. package/src/render-system-producer-roots.ts +96 -0
  487. package/src/render-system-residency.ts +159 -0
  488. package/src/render-system.ts +347 -363
  489. package/src/scene/probe-blend.ts +96 -4
  490. package/src/scene/render-scene.ts +349 -280
  491. package/src/ssao-buffers.ts +95 -2
  492. package/src/surface/__tests__/dynamic-input.unit.test.ts +374 -0
  493. package/src/surface/__tests__/gpu-driven.unit.test.ts +87 -0
  494. package/src/surface/dynamic-input.ts +658 -0
  495. package/src/surface/gpu-driven.ts +147 -0
  496. package/src/surface/submission-observation.ts +517 -0
  497. package/src/systems/skin-palette-allocator.ts +4 -1
  498. package/src/temporal/gpu.ts +101 -17
  499. package/src/temporal/inspection.ts +7 -1
  500. package/src/temporal/standard-scene-data.ts +5 -0
  501. package/src/temporal/target-coverage-attachment.ts +4 -0
  502. package/src/transmission/__tests__/single-layer-medium.unit.test.ts +149 -0
  503. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +12 -11
  504. package/src/transmission/single-layer-medium.ts +322 -0
  505. package/src/typed-render-graph-primitives.ts +70 -2
  506. package/src/volume/component.ts +10 -9
  507. package/src/volume/extract.ts +11 -5
  508. package/src/volume/inspection.ts +3 -0
  509. package/src/volume/passes.ts +87 -4
  510. package/src/volume/resources.ts +2 -1
  511. package/dist/chunk-3RTCJTH4.mjs.map +0 -1
  512. package/dist/chunk-NB2S7ZRX.mjs.map +0 -1
  513. package/dist/chunk-NNQL7CB3.mjs +0 -1713
  514. package/dist/chunk-NNQL7CB3.mjs.map +0 -1
  515. package/dist/chunk-OO3DRYK7.mjs.map +0 -1
  516. package/dist/chunk-PA5DRJNI.mjs.map +0 -1
  517. package/dist/chunk-PLAXOOX5.mjs.map +0 -1
  518. package/dist/chunk-VOZWDM3L.mjs.map +0 -1
package/src/instances.ts CHANGED
@@ -1,52 +1,26 @@
1
+ import type { World } from '@forgeax/engine-ecs';
2
+ import { InstanceTransformsStrideMismatchError } from '@forgeax/engine-ecs/projection';
1
3
  import type { RhiErrorCode } from '@forgeax/engine-rhi';
2
- import { err, ok, type Result } from '@forgeax/engine-types';
3
4
 
4
5
  const MATRIX_STRIDE = 16;
5
- const INITIAL_CAPACITY = 1;
6
6
 
7
+ /** Renderer projection identity; never stored in a World or SceneAsset. */
7
8
  export type InstanceCollectionId = number & {
8
9
  readonly __forgeaxInstanceCollectionId: unique symbol;
9
10
  };
10
-
11
- export interface InstanceCollectionData {
11
+ export interface InstanceCollectionInfo {
12
12
  readonly collectionId: InstanceCollectionId;
13
- }
14
-
15
- export interface InstanceCollectionInfo extends InstanceCollectionData {
16
13
  readonly count: number;
17
- readonly capacity: number;
18
14
  readonly revision: number;
19
15
  }
20
-
21
16
  export interface InstanceDirtyRange {
22
17
  start: number;
23
18
  end: number;
24
19
  }
25
-
26
- /**
27
- * Choose the ranges that initialize one resident GPU allocation.
28
- *
29
- * A cache miss is not a delta upload: recovery, reassociation, and any other
30
- * new resident has uninitialized device memory. The canonical collection must
31
- * therefore be copied in full even when the current authoring revision only
32
- * records a partial dirty range. Existing residents keep the narrower ranges.
33
- */
34
- export function instanceUploadRangesForResident(
35
- activeIsNew: boolean,
36
- dirtyRanges: readonly InstanceDirtyRange[],
37
- fullRange: InstanceDirtyRange,
38
- ): readonly InstanceDirtyRange[] {
39
- return activeIsNew ? [fullRange] : dirtyRanges;
40
- }
41
-
42
20
  export interface InstanceCollectionSnapshot extends InstanceCollectionInfo {
43
21
  readonly transforms: Float32Array;
44
- /** Unique identity generations that travel with an instance through reorder. */
45
22
  readonly generations: Uint32Array;
46
- readonly dirtyRanges: readonly InstanceDirtyRange[];
47
23
  }
48
-
49
- /** The renderer-owned submission lane selected for one collection. */
50
24
  export type InstanceSubmissionLane =
51
25
  | 'unresident'
52
26
  | 'direct-storage'
@@ -54,14 +28,8 @@ export type InstanceSubmissionLane =
54
28
  | 'direct-uniform'
55
29
  | 'chunked-uniform'
56
30
  | 'unavailable';
57
-
58
- /** Backend identity carried by collection residency and failure facts. */
59
31
  export type InstanceBackendKind = 'webgpu' | 'wgpu-native' | 'wgpu-webgl2' | 'null' | 'unknown';
60
-
61
- /** Closed record-stage failure vocabulary for collection residency. */
62
32
  export type InstanceCollectionFailureCode = RhiErrorCode | 'queue-write-buffer-failed';
63
-
64
- /** Typed producer facts retained when a collection cannot be admitted. */
65
33
  export interface InstanceCollectionFailureFacts {
66
34
  readonly requestedBytes: number;
67
35
  readonly supportedBytes: number | undefined;
@@ -70,29 +38,9 @@ export interface InstanceCollectionFailureFacts {
70
38
  readonly cause: string;
71
39
  readonly recovery: string;
72
40
  }
73
-
74
- function instanceCollectionFailureFacts(
75
- cause: string,
76
- recovery: string,
77
- requestedBytes = 0,
78
- supportedBytes: number | undefined = undefined,
79
- backend: InstanceBackendKind = 'unknown',
80
- ): InstanceCollectionFailureFacts {
81
- return {
82
- requestedBytes,
83
- supportedBytes,
84
- backend,
85
- owner: 'renderer.instances',
86
- cause,
87
- recovery,
88
- };
89
- }
90
-
91
- /** Detached, bounded collection evidence exposed by Renderer.inspect(). */
92
41
  export interface InstanceCollectionInspection extends InstanceCollectionInfo {
93
42
  readonly residentGeneration: number | undefined;
94
43
  readonly lane: InstanceSubmissionLane;
95
- readonly dirtyRanges: readonly InstanceDirtyRange[];
96
44
  readonly uploadRanges: readonly InstanceDirtyRange[];
97
45
  readonly uploadedBytes: number;
98
46
  readonly requestedBytes: number;
@@ -109,82 +57,46 @@ export interface InstanceCollectionInspection extends InstanceCollectionInfo {
109
57
  | undefined;
110
58
  }
111
59
 
112
- export interface InstanceCollectionCreateInput {
113
- readonly transforms: Float32Array;
114
- }
115
-
116
- export interface InstanceCollectionUpdateInput {
117
- /** Matrix ordinal of the first matrix to replace. */
118
- readonly start: number;
119
- readonly transforms: Float32Array;
120
- }
121
-
122
- export interface InstanceCollectionInvalidDetail {
123
- readonly collectionId?: number;
124
- readonly operation: 'create' | 'replace' | 'update' | 'inspect' | 'release';
125
- readonly expected: string;
126
- readonly actual: number | string;
127
- }
128
-
129
- export type InstanceCollectionErrorCode =
130
- | 'instance-collection-invalid'
131
- | 'instance-collection-not-found'
132
- | 'instance-collection-released';
133
-
134
- export class InstanceCollectionError extends Error {
135
- readonly code: InstanceCollectionErrorCode;
136
- readonly expected: string;
137
- readonly hint: string;
138
- readonly detail: InstanceCollectionInvalidDetail | undefined;
139
- readonly owner = 'renderer.instances' as const;
140
- readonly recovery: string;
141
- readonly facts: InstanceCollectionFailureFacts;
142
-
60
+ export class InstanceTransformsError extends Error {
61
+ readonly code = 'instance-transforms-invalid' as const;
62
+ readonly expected = 'finite column-major mat4 values, with transforms.length a multiple of 16';
63
+ readonly hint = 'repair Instances.transforms in the World or source SceneAsset and retry';
143
64
  constructor(
144
- code: InstanceCollectionErrorCode,
145
- expected: string,
146
- hint: string,
147
- detail?: InstanceCollectionInvalidDetail,
148
- facts?: InstanceCollectionFailureFacts,
65
+ readonly detail: { readonly actualLength: number; readonly nonFiniteIndex?: number },
149
66
  ) {
150
- super(`${code}: ${expected}`);
151
- this.name = 'InstanceCollectionError';
152
- this.code = code;
153
- this.expected = expected;
154
- this.hint = hint;
155
- this.detail = detail;
156
- this.facts =
157
- facts ??
158
- instanceCollectionFailureFacts(
159
- code,
160
- hint,
161
- detail?.operation === 'create' || detail?.operation === 'replace'
162
- ? typeof detail.actual === 'number' && Number.isFinite(detail.actual)
163
- ? detail.actual * Float32Array.BYTES_PER_ELEMENT
164
- : 0
165
- : 0,
166
- );
167
- this.recovery = this.facts.recovery;
67
+ super('Instances.transforms must contain complete finite matrices');
68
+ this.name = 'InstanceTransformsError';
168
69
  }
169
70
  }
71
+ export function validateInstanceTransforms(
72
+ transforms: ArrayLike<number>,
73
+ ): InstanceTransformsError | InstanceTransformsStrideMismatchError | undefined {
74
+ if (transforms.length % MATRIX_STRIDE !== 0)
75
+ return new InstanceTransformsStrideMismatchError(transforms.length);
76
+ for (let index = 0; index < transforms.length; index++) {
77
+ if (!Number.isFinite(transforms[index]))
78
+ return new InstanceTransformsError({
79
+ actualLength: transforms.length,
80
+ nonFiniteIndex: index,
81
+ });
82
+ }
83
+ return undefined;
84
+ }
170
85
 
171
- export type InstanceCollectionResult<T> = Result<T, InstanceCollectionError>;
172
-
173
- interface CollectionRecord {
174
- readonly id: InstanceCollectionId;
175
- transforms: Float32Array;
176
- generations: Uint32Array;
177
- count: number;
178
- capacity: number;
179
- revision: number;
180
- dirtyRanges: InstanceDirtyRange[];
181
- /** Last revision detached for extraction; repeated reads of that revision
182
- * retain the same ranges for multiple entities sharing one collection. */
183
- lastSnapshotRevision: number;
184
- released: boolean;
86
+ /** A new allocation must receive the complete payload, including after recovery. */
87
+ export function instanceUploadRangesForResident(
88
+ activeIsNew: boolean,
89
+ dirtyRanges: readonly InstanceDirtyRange[],
90
+ fullRange: InstanceDirtyRange,
91
+ ): readonly InstanceDirtyRange[] {
92
+ return activeIsNew ? [fullRange] : dirtyRanges;
185
93
  }
186
94
 
187
- interface CollectionResidency {
95
+ interface Projection extends InstanceCollectionSnapshot {
96
+ readonly world: World;
97
+ readonly entity: number;
98
+ }
99
+ interface Residency {
188
100
  readonly frameNumber: number;
189
101
  readonly residentGeneration: number | undefined;
190
102
  readonly lane: InstanceSubmissionLane;
@@ -196,300 +108,120 @@ interface CollectionResidency {
196
108
  readonly error: InstanceCollectionInspection['error'];
197
109
  }
198
110
 
199
- function nextCapacity(required: number): number {
200
- let capacity = INITIAL_CAPACITY;
201
- while (capacity < required) capacity *= 2;
202
- return capacity;
203
- }
204
-
205
- function invalid(
206
- operation: InstanceCollectionInvalidDetail['operation'],
207
- expected: string,
208
- actual: number | string,
209
- collectionId?: number,
210
- context?: {
211
- readonly requestedBytes?: number;
212
- readonly supportedBytes?: number;
213
- readonly cause?: string;
214
- readonly recovery?: string;
215
- },
216
- ): InstanceCollectionError {
217
- const hint = 'repair the collection payload and retry the same authoring operation';
218
- return new InstanceCollectionError(
219
- 'instance-collection-invalid',
220
- expected,
221
- hint,
222
- { operation, expected, actual, ...(collectionId === undefined ? {} : { collectionId }) },
223
- instanceCollectionFailureFacts(
224
- context?.cause ?? `invalid-${operation}-authoring`,
225
- context?.recovery ?? hint,
226
- context?.requestedBytes ?? 0,
227
- context?.supportedBytes,
228
- ),
229
- );
230
- }
231
-
232
- function validateTransforms(
233
- operation: InstanceCollectionInvalidDetail['operation'],
234
- transforms: Float32Array,
235
- collectionId?: number,
236
- ): InstanceCollectionError | undefined {
237
- if (!(transforms instanceof Float32Array)) {
238
- return invalid(operation, 'transforms is a Float32Array', typeof transforms, collectionId, {
239
- cause: 'invalid-transform-type',
240
- });
241
- }
242
- if (transforms.length % MATRIX_STRIDE !== 0) {
243
- return invalid(
244
- operation,
245
- `transforms.length is a multiple of ${MATRIX_STRIDE}`,
246
- transforms.length,
247
- collectionId,
248
- {
249
- requestedBytes: transforms.byteLength,
250
- cause: 'invalid-transform-stride',
251
- },
252
- );
253
- }
254
- for (const value of transforms) {
255
- if (!Number.isFinite(value)) {
256
- return invalid(operation, 'every transform value is finite', value, collectionId, {
257
- requestedBytes: transforms.byteLength,
258
- cause: 'invalid-transform-value',
259
- });
260
- }
261
- }
262
- return undefined;
263
- }
264
-
265
- function appendRange(ranges: InstanceDirtyRange[], start: number, end: number): void {
266
- if (start >= end) return;
267
- // Updates are allowed to arrive in any order. Insert at the first range
268
- // whose end reaches the new interval, then consume every range that starts
269
- // before the merged end. This preserves the complete union for both
270
- // adjacent and overlapping writes (for example [10, 12) then [8, 11)).
271
- let index = 0;
272
- while (index < ranges.length) {
273
- const existing = ranges[index];
274
- if (existing === undefined || existing.end >= start) break;
275
- index += 1;
276
- }
277
- while (index < ranges.length) {
278
- const existing = ranges[index];
279
- if (existing === undefined || existing.start > end) break;
280
- start = Math.min(start, existing.start);
281
- end = Math.max(end, existing.end);
282
- ranges.splice(index, 1);
283
- }
284
- ranges.splice(index, 0, { start, end });
285
- }
286
-
287
- function appendDirtyRange(record: CollectionRecord, start: number, end: number): void {
288
- appendRange(record.dirtyRanges, start, end);
289
- }
290
-
291
- function missing(
292
- operation: InstanceCollectionInvalidDetail['operation'],
293
- id: InstanceCollectionId,
294
- ): InstanceCollectionError {
295
- return new InstanceCollectionError(
296
- 'instance-collection-not-found',
297
- `instance collection ${Number(id)} is live and owned by this Renderer`,
298
- 'create a collection with renderer.instances.create or retain the original collection id',
299
- { operation, expected: 'a live instance collection id', actual: Number(id), collectionId: id },
300
- );
301
- }
302
-
303
- /**
304
- * Renderer-owned canonical storage for explicit instance transforms.
305
- *
306
- * ECS carries only `Instances.collectionId`; this owner keeps the copied
307
- * matrices, revision and dirty intervals independent of archetype capacity.
308
- * Snapshots are detached before they cross into extraction, so callers cannot
309
- * mutate the canonical source behind the revision protocol.
310
- */
311
- export class InstanceCollectionStore {
111
+ /** Rebuildable renderer projection. All authoring and entity lifetime belong to World. */
112
+ export class InstanceProjectionStore {
312
113
  private nextId = 1;
313
- private nextInstanceGeneration = 1;
314
- private epochValue = 0;
315
- private disposed = false;
316
- private readonly records = new Map<InstanceCollectionId, CollectionRecord>();
317
- private readonly residency = new Map<InstanceCollectionId, CollectionResidency>();
318
-
319
- get epoch(): number {
320
- return this.epochValue;
321
- }
322
-
323
- create(input: InstanceCollectionCreateInput): InstanceCollectionResult<InstanceCollectionInfo> {
324
- if (this.disposed) {
325
- return err(
326
- new InstanceCollectionError(
327
- 'instance-collection-released',
328
- 'the Renderer-owned instance collection store is live',
329
- 'create a new Renderer before authoring instance collections',
330
- ),
331
- );
332
- }
333
- const invalidPayload = validateTransforms('create', input.transforms);
334
- if (invalidPayload !== undefined) return err(invalidPayload);
335
- const id = this.nextId++ as InstanceCollectionId;
336
- const count = input.transforms.length / MATRIX_STRIDE;
337
- const generations = this.allocateGenerations(count);
338
- const record: CollectionRecord = {
339
- id,
340
- transforms: new Float32Array(input.transforms),
114
+ private nextGeneration = 1;
115
+ private identities = new WeakMap<World, Map<number, InstanceCollectionId>>();
116
+ private readonly records = new Map<InstanceCollectionId, Projection>();
117
+ private readonly residency = new Map<InstanceCollectionId, Residency>();
118
+
119
+ project(world: World, entity: number, transforms: ArrayLike<number>): InstanceCollectionSnapshot {
120
+ const collectionId =
121
+ this.identities.get(world)?.get(entity) ?? (this.nextId++ as InstanceCollectionId);
122
+ const previous = this.records.get(collectionId);
123
+ if (
124
+ previous !== undefined &&
125
+ previous.transforms.length === transforms.length &&
126
+ previous.transforms.every((value, index) => Object.is(value, transforms[index]))
127
+ )
128
+ return previous;
129
+ const count = transforms.length / MATRIX_STRIDE;
130
+ // Ordinals are identities within the authored array. Pose edits retain them;
131
+ // resizing seeds topology anew so new instances cannot inherit old motion.
132
+ const generations =
133
+ previous?.count === count
134
+ ? previous.generations
135
+ : Uint32Array.from({ length: count }, () => this.nextGeneration++);
136
+ const record: Projection = {
137
+ collectionId,
138
+ world,
139
+ entity,
140
+ transforms: new Float32Array(transforms),
341
141
  generations,
342
142
  count,
343
- capacity: nextCapacity(Math.max(INITIAL_CAPACITY, count)),
344
- revision: 1,
345
- dirtyRanges: count === 0 ? [] : [{ start: 0, end: count }],
346
- lastSnapshotRevision: 0,
347
- released: false,
143
+ revision: (previous?.revision ?? 0) + 1,
348
144
  };
349
- this.records.set(id, record);
350
- this.epochValue += 1;
351
- return ok(this.info(record));
145
+ return record;
352
146
  }
353
147
 
354
- replace(
355
- id: InstanceCollectionId,
356
- transforms: Float32Array,
357
- /** Prior identity generations in the replacement order; omitted rows are new. */
358
- generations?: Uint32Array,
359
- ): InstanceCollectionResult<InstanceCollectionInfo> {
360
- const record = this.live('replace', id);
361
- if (!record.ok) return record;
362
- const invalidPayload = validateTransforms('replace', transforms, id);
363
- if (invalidPayload !== undefined) return err(invalidPayload);
364
- const count = transforms.length / MATRIX_STRIDE;
365
- const generationResult =
366
- generations === undefined
367
- ? ok(this.allocateGenerations(count))
368
- : this.validateReplacementGenerations(generations, count, id);
369
- if (!generationResult.ok) return generationResult;
370
- // Replacement is a topology boundary by default. A producer that keeps
371
- // logical instance identity through compaction/reorder may pass the prior
372
- // generation tokens in their new order; the record stage then maps the
373
- // real previous transform by token instead of by draw ordinal.
374
- const replacementGenerations = generationResult.value;
375
- record.value.transforms = new Float32Array(transforms);
376
- record.value.generations = replacementGenerations;
377
- record.value.count = count;
378
- record.value.capacity = Math.max(record.value.capacity, nextCapacity(Math.max(1, count)));
379
- record.value.revision += 1;
380
- record.value.dirtyRanges = count === 0 ? [] : [{ start: 0, end: count }];
381
- record.value.lastSnapshotRevision = record.value.revision - 1;
382
- this.epochValue += 1;
383
- return ok(this.info(record.value));
148
+ /** Called only after the owning RenderScene accepts its prepared changes. */
149
+ accept(
150
+ world: World,
151
+ entity: number,
152
+ snapshot: {
153
+ readonly collectionId?: InstanceCollectionId;
154
+ readonly revision?: number;
155
+ readonly transforms: Float32Array;
156
+ readonly generations?: Uint32Array;
157
+ },
158
+ ): void {
159
+ const { collectionId, revision, transforms, generations } = snapshot;
160
+ if (collectionId === undefined || revision === undefined || generations === undefined) return;
161
+ let entities = this.identities.get(world);
162
+ if (entities === undefined) {
163
+ entities = new Map();
164
+ this.identities.set(world, entities);
165
+ }
166
+ entities.set(entity, collectionId);
167
+ this.records.set(collectionId, {
168
+ collectionId,
169
+ revision,
170
+ transforms,
171
+ generations,
172
+ count: transforms.length / MATRIX_STRIDE,
173
+ world,
174
+ entity,
175
+ });
384
176
  }
385
177
 
386
- update(
387
- id: InstanceCollectionId,
388
- input: InstanceCollectionUpdateInput,
389
- ): InstanceCollectionResult<InstanceCollectionInfo> {
390
- const record = this.live('update', id);
391
- if (!record.ok) return record;
392
- const invalidPayload = validateTransforms('update', input.transforms, id);
393
- if (invalidPayload !== undefined) return err(invalidPayload);
394
- if (!Number.isInteger(input.start) || input.start < 0) {
395
- return err(
396
- invalid('update', 'start is a non-negative matrix ordinal', input.start, id, {
397
- requestedBytes: input.transforms.byteLength,
398
- supportedBytes: record.value.capacity * MATRIX_STRIDE * Float32Array.BYTES_PER_ELEMENT,
399
- cause: 'invalid-update-range-start',
400
- }),
401
- );
402
- }
403
- const span = input.transforms.length / MATRIX_STRIDE;
404
- const attemptedEnd = input.start + span;
405
- if (attemptedEnd > record.value.count) {
406
- return err(
407
- invalid(
408
- 'update',
409
- `start + matrix count is <= collection count (${record.value.count})`,
410
- attemptedEnd,
411
- id,
412
- {
413
- requestedBytes: attemptedEnd * MATRIX_STRIDE * Float32Array.BYTES_PER_ELEMENT,
414
- supportedBytes: record.value.count * MATRIX_STRIDE * Float32Array.BYTES_PER_ELEMENT,
415
- cause: 'invalid-update-range-end',
416
- },
417
- ),
418
- );
419
- }
420
- if (record.value.lastSnapshotRevision === record.value.revision) {
421
- record.value.dirtyRanges = [];
178
+ release(world: World, entity: number): void {
179
+ const entities = this.identities.get(world);
180
+ const id = entities?.get(entity);
181
+ if (id === undefined) return;
182
+ entities?.delete(entity);
183
+ this.records.delete(id);
184
+ this.residency.delete(id);
185
+ }
186
+ retain(ids: ReadonlySet<InstanceCollectionId>): void {
187
+ for (const record of this.records.values()) {
188
+ if (!ids.has(record.collectionId)) this.release(record.world, record.entity);
422
189
  }
423
- record.value.transforms.set(input.transforms, input.start * MATRIX_STRIDE);
424
- // An update changes a pose while retaining the same ordinal identity.
425
- // Keep its generation stable so the record stage can pair this current
426
- // transform with the last submitted transform and expose real velocity.
427
- // `replace` is the explicit topology boundary that advances generations
428
- // for compaction, reorder, retirement, and newly occupied ordinals.
429
- record.value.revision += 1;
430
- appendDirtyRange(record.value, input.start, input.start + span);
431
- this.epochValue += 1;
432
- return ok(this.info(record.value));
433
190
  }
434
-
435
- inspect(id: InstanceCollectionId): InstanceCollectionResult<InstanceCollectionInfo> {
436
- const record = this.live('inspect', id);
437
- return record.ok ? ok(this.info(record.value)) : record;
191
+ dispose(): void {
192
+ this.records.clear();
193
+ this.residency.clear();
194
+ this.identities = new WeakMap();
438
195
  }
439
-
440
- /**
441
- * Return detached residency facts for every live collection.
442
- *
443
- * @internal Record-stage evidence is reported through the two methods below;
444
- * callers only receive immutable PODs and never GPU handles.
445
- */
446
- _inspections(): readonly InstanceCollectionInspection[] {
447
- return Object.freeze(
448
- [...this.records.values()].map((record) => this.projectInspection(record)),
449
- );
196
+ _resetResidency(): void {
197
+ this.residency.clear();
450
198
  }
451
199
 
452
- /** @internal Renderer record owner reports the accepted collection lane. */
453
- _reportResidency(input: {
454
- readonly collectionId: InstanceCollectionId;
455
- readonly frameNumber: number;
456
- readonly residentGeneration: number;
457
- readonly lane: Exclude<InstanceSubmissionLane, 'unresident' | 'unavailable'>;
458
- readonly uploadRanges: readonly InstanceDirtyRange[];
459
- readonly uploadedBytes: number;
460
- readonly requestedBytes: number;
461
- readonly supportedBytes: number | undefined;
462
- readonly backend: InstanceBackendKind;
463
- }): void {
200
+ _reportResidency(
201
+ input: Omit<Residency, 'error' | 'lane'> & {
202
+ readonly collectionId: InstanceCollectionId;
203
+ readonly lane: Exclude<InstanceSubmissionLane, 'unresident' | 'unavailable'>;
204
+ },
205
+ ): void {
464
206
  if (!this.records.has(input.collectionId)) return;
465
207
  const previous = this.residency.get(input.collectionId);
466
- const sameFrame =
467
- previous !== undefined &&
468
- previous.frameNumber === input.frameNumber &&
469
- previous.error === undefined;
470
- const uploadRanges = sameFrame
471
- ? [...previous.uploadRanges].reduce(
472
- (ranges, range) => {
473
- appendRange(ranges, range.start, range.end);
474
- return ranges;
475
- },
476
- input.uploadRanges.map((range) => ({ ...range })),
477
- )
478
- : input.uploadRanges.map((range) => ({ ...range }));
208
+ const sameFrame = previous?.frameNumber === input.frameNumber && previous.error === undefined;
209
+ const ranges = [...(sameFrame ? previous.uploadRanges : []), ...input.uploadRanges]
210
+ .map((range) => ({ ...range }))
211
+ .sort((a, b) => a.start - b.start);
212
+ const uploadRanges: InstanceDirtyRange[] = [];
213
+ for (const range of ranges) {
214
+ const last = uploadRanges.at(-1);
215
+ if (last !== undefined && range.start <= last.end) last.end = Math.max(last.end, range.end);
216
+ else uploadRanges.push(range);
217
+ }
479
218
  this.residency.set(input.collectionId, {
480
- frameNumber: input.frameNumber,
481
- residentGeneration: input.residentGeneration,
482
- lane: input.lane,
219
+ ...input,
483
220
  uploadRanges,
484
- uploadedBytes: sameFrame ? previous.uploadedBytes + input.uploadedBytes : input.uploadedBytes,
485
- requestedBytes: input.requestedBytes,
486
- supportedBytes: input.supportedBytes,
487
- backend: input.backend,
221
+ uploadedBytes: input.uploadedBytes + (sameFrame ? previous.uploadedBytes : 0),
488
222
  error: undefined,
489
223
  });
490
224
  }
491
-
492
- /** @internal Renderer record owner reports a typed admission failure. */
493
225
  _reportFailure(input: {
494
226
  readonly collectionId: InstanceCollectionId;
495
227
  readonly code: InstanceCollectionFailureCode;
@@ -515,176 +247,24 @@ export class InstanceCollectionStore {
515
247
  },
516
248
  });
517
249
  }
518
-
519
- /** @internal A lost device invalidates every renderer-owned GPU resident. */
520
- _resetResidency(): void {
521
- this.residency.clear();
522
- }
523
-
524
- snapshot(id: InstanceCollectionId): InstanceCollectionResult<InstanceCollectionSnapshot> {
525
- const record = this.live('inspect', id);
526
- if (!record.ok) return record;
527
- const value = record.value;
528
- value.lastSnapshotRevision = value.revision;
529
- const result = {
530
- ...this.info(value),
531
- transforms: new Float32Array(value.transforms),
532
- generations: new Uint32Array(value.generations),
533
- dirtyRanges: value.dirtyRanges.map((range) => ({ ...range })),
534
- };
535
- // Keep the current ranges stable for repeated reads by entities sharing
536
- // this collection. The next authoring mutation starts a fresh interval
537
- // set once this revision has been observed.
538
- return ok(result);
539
- }
540
-
541
- /**
542
- * Read a detached matrix view without advancing the render-consumption
543
- * cursor. Inspection and picking may call this between authoring updates;
544
- * the next renderer snapshot must still receive the complete dirty union.
545
- */
546
- peek(id: InstanceCollectionId): InstanceCollectionResult<InstanceCollectionSnapshot> {
547
- const record = this.live('inspect', id);
548
- return record.ok ? ok(this.detachedSnapshot(record.value)) : record;
549
- }
550
-
551
- release(id: InstanceCollectionId): InstanceCollectionResult<void> {
552
- const record = this.records.get(id);
553
- if (record === undefined) return err(missing('release', id));
554
- if (record.released) {
555
- return err(
556
- new InstanceCollectionError(
557
- 'instance-collection-released',
558
- `instance collection ${Number(id)} has not already been released`,
559
- 'do not reuse a released collection id; create a new collection',
560
- {
561
- operation: 'release',
562
- expected: 'a live instance collection id',
563
- actual: Number(id),
564
- collectionId: id,
565
- },
566
- ),
567
- );
568
- }
569
- record.released = true;
570
- this.records.delete(id);
571
- this.residency.delete(id);
572
- this.epochValue += 1;
573
- return ok(undefined);
574
- }
575
-
576
- /** Drop all canonical matrices during Renderer disposal. */
577
- dispose(): void {
578
- if (this.disposed) return;
579
- this.disposed = true;
580
- for (const record of this.records.values()) record.released = true;
581
- this.records.clear();
582
- this.residency.clear();
583
- this.epochValue += 1;
584
- }
585
-
586
- private live(
587
- operation: InstanceCollectionInvalidDetail['operation'],
588
- id: InstanceCollectionId,
589
- ): InstanceCollectionResult<CollectionRecord> {
590
- if (!Number.isInteger(id) || id <= 0) {
591
- return err(invalid(operation, 'collectionId is a positive integer', id, id));
592
- }
593
- if (this.disposed) {
594
- return err(
595
- new InstanceCollectionError(
596
- 'instance-collection-released',
597
- 'the Renderer-owned instance collection store is live',
598
- 'create a new Renderer before using instance collections',
599
- { operation, expected: 'a live collection store', actual: 'disposed', collectionId: id },
600
- ),
601
- );
602
- }
603
- const record = this.records.get(id);
604
- return record === undefined ? err(missing(operation, id)) : ok(record);
605
- }
606
-
607
- private detachedSnapshot(record: CollectionRecord): InstanceCollectionSnapshot {
608
- return {
609
- ...this.info(record),
610
- transforms: new Float32Array(record.transforms),
611
- generations: new Uint32Array(record.generations),
612
- dirtyRanges: record.dirtyRanges.map((range) => ({ ...range })),
613
- };
614
- }
615
-
616
- private info(record: CollectionRecord): InstanceCollectionInfo {
617
- return Object.freeze({
618
- collectionId: record.id,
619
- count: record.count,
620
- capacity: record.capacity,
621
- revision: record.revision,
622
- });
623
- }
624
-
625
- private allocateGenerations(count: number): Uint32Array {
626
- const generations = new Uint32Array(count);
627
- for (let index = 0; index < count; index += 1) {
628
- generations[index] = this.nextInstanceGeneration;
629
- this.nextInstanceGeneration = (this.nextInstanceGeneration + 1) >>> 0 || 1;
630
- }
631
- return generations;
632
- }
633
-
634
- private validateReplacementGenerations(
635
- generations: Uint32Array,
636
- count: number,
637
- collectionId: InstanceCollectionId,
638
- ): InstanceCollectionResult<Uint32Array> {
639
- if (!(generations instanceof Uint32Array) || generations.length !== count) {
640
- return err(
641
- invalid(
642
- 'replace',
643
- `generations is a Uint32Array with ${count} entries`,
644
- generations instanceof Uint32Array ? generations.length : typeof generations,
645
- collectionId,
646
- { requestedBytes: generations?.byteLength ?? 0, cause: 'invalid-generation-shape' },
647
- ),
648
- );
649
- }
650
- const seen = new Set<number>();
651
- for (const generation of generations) {
652
- if (generation === 0 || seen.has(generation)) {
653
- return err(
654
- invalid(
655
- 'replace',
656
- 'generations contain unique non-zero identity tokens',
657
- generation,
658
- collectionId,
659
- {
660
- requestedBytes: generations.byteLength,
661
- cause: 'invalid-generation-identity',
662
- },
663
- ),
664
- );
665
- }
666
- seen.add(generation);
667
- if (generation >= this.nextInstanceGeneration) {
668
- this.nextInstanceGeneration = (generation + 1) >>> 0 || 1;
669
- }
670
- }
671
- return ok(new Uint32Array(generations));
672
- }
673
-
674
- private projectInspection(record: CollectionRecord): InstanceCollectionInspection {
675
- const residency = this.residency.get(record.id);
676
- return Object.freeze({
677
- ...this.info(record),
678
- residentGeneration: residency?.residentGeneration,
679
- lane: residency?.lane ?? 'unresident',
680
- dirtyRanges: Object.freeze(record.dirtyRanges.map((range) => ({ ...range }))),
681
- uploadRanges: Object.freeze(residency?.uploadRanges.map((range) => ({ ...range })) ?? []),
682
- uploadedBytes: residency?.uploadedBytes ?? 0,
683
- requestedBytes: residency?.requestedBytes ?? record.count * MATRIX_STRIDE * 4,
684
- supportedBytes: residency?.supportedBytes,
685
- backend: residency?.backend ?? 'unknown',
686
- owner: 'renderer.instances',
687
- error: residency?.error,
250
+ _inspections(frameNumber: number): readonly InstanceCollectionInspection[] {
251
+ return [...this.records.values()].map((record) => {
252
+ const resident = this.residency.get(record.collectionId);
253
+ const current = resident?.frameNumber === frameNumber;
254
+ return {
255
+ collectionId: record.collectionId,
256
+ count: record.count,
257
+ revision: record.revision,
258
+ residentGeneration: resident?.residentGeneration,
259
+ lane: resident?.lane ?? 'unresident',
260
+ uploadRanges: current ? resident.uploadRanges.map((range) => ({ ...range })) : [],
261
+ uploadedBytes: current ? resident.uploadedBytes : 0,
262
+ requestedBytes: resident?.requestedBytes ?? record.transforms.byteLength,
263
+ supportedBytes: resident?.supportedBytes,
264
+ backend: resident?.backend ?? 'unknown',
265
+ owner: 'renderer.instances',
266
+ error: resident?.error,
267
+ };
688
268
  });
689
269
  }
690
270
  }