@forgeax/engine-render 0.1.33 → 0.1.35

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 (624) hide show
  1. package/README.md +1062 -127
  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/device-feature-admission.d.ts +2 -2
  11. package/dist/assembly/dynamic-geometry-host.d.ts +4 -1
  12. package/dist/assembly/dynamic-geometry-host.d.ts.map +1 -1
  13. package/dist/assembly/dynamic-geometry-runtime.d.ts +1 -1
  14. package/dist/assembly/dynamic-geometry-runtime.d.ts.map +1 -1
  15. package/dist/assembly/factory.d.ts +1 -1
  16. package/dist/assembly/factory.d.ts.map +1 -1
  17. package/dist/assembly/host-contract.d.ts +6 -5
  18. package/dist/assembly/host-contract.d.ts.map +1 -1
  19. package/dist/assembly/material/assembly.d.ts.map +1 -1
  20. package/dist/assembly/material/pipeline-helpers.d.ts +5 -0
  21. package/dist/assembly/material/pipeline-helpers.d.ts.map +1 -1
  22. package/dist/assembly/material-shader-policy.d.ts +16 -2
  23. package/dist/assembly/material-shader-policy.d.ts.map +1 -1
  24. package/dist/assembly/material-shader-prewarm.d.ts.map +1 -1
  25. package/dist/assembly/recovery/device-loss-fanout.d.ts.map +1 -1
  26. package/dist/assembly/recovery/renderer-recover.d.ts +2 -0
  27. package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -1
  28. package/dist/assembly/render-feature-post-processes.d.ts +6 -0
  29. package/dist/assembly/render-feature-post-processes.d.ts.map +1 -0
  30. package/dist/assembly/render-target-host.d.ts +5 -0
  31. package/dist/assembly/render-target-host.d.ts.map +1 -1
  32. package/dist/assembly/renderer-facade.d.ts.map +1 -1
  33. package/dist/assembly/renderer-feature-inspection.d.ts +1 -1
  34. package/dist/assembly/renderer-feature-inspection.d.ts.map +1 -1
  35. package/dist/assembly/renderer-frame-transaction.d.ts +12 -0
  36. package/dist/assembly/renderer-frame-transaction.d.ts.map +1 -1
  37. package/dist/assembly/renderer-inspection.d.ts +11 -0
  38. package/dist/assembly/renderer-inspection.d.ts.map +1 -1
  39. package/dist/assembly/webgpu-pbr-ready.d.ts +1 -0
  40. package/dist/assembly/webgpu-pbr-ready.d.ts.map +1 -1
  41. package/dist/assembly/webgpu-ready-per-pass.d.ts +39 -0
  42. package/dist/assembly/webgpu-ready-per-pass.d.ts.map +1 -0
  43. package/dist/assembly/webgpu-ready.d.ts +1 -1
  44. package/dist/assembly/webgpu-ready.d.ts.map +1 -1
  45. package/dist/assembly/webgpu-renderer-bootstrap.d.ts.map +1 -1
  46. package/dist/assembly/webgpu-renderer-observation-owner.d.ts +35 -0
  47. package/dist/assembly/webgpu-renderer-observation-owner.d.ts.map +1 -0
  48. package/dist/assembly/webgpu-renderer.d.ts +2 -1
  49. package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
  50. package/dist/authoring.mjs +2 -4
  51. package/dist/authoring.mjs.map +1 -1
  52. package/dist/barrel-distortion.d.ts +47 -0
  53. package/dist/barrel-distortion.d.ts.map +1 -0
  54. package/dist/chunk-3XUBWRY2.mjs +1842 -0
  55. package/dist/chunk-3XUBWRY2.mjs.map +1 -0
  56. package/dist/{chunk-OO3DRYK7.mjs → chunk-G6HDNGY7.mjs} +2400 -1767
  57. package/dist/chunk-G6HDNGY7.mjs.map +1 -0
  58. package/dist/{chunk-ZDQWGQW7.mjs → chunk-LM5IE7GF.mjs} +4 -4
  59. package/dist/{chunk-ZDQWGQW7.mjs.map → chunk-LM5IE7GF.mjs.map} +1 -1
  60. package/dist/{chunk-VOZWDM3L.mjs → chunk-LNIUIKQ7.mjs} +1514 -498
  61. package/dist/chunk-LNIUIKQ7.mjs.map +1 -0
  62. package/dist/chunk-MXTWKZO4.mjs +3268 -0
  63. package/dist/chunk-MXTWKZO4.mjs.map +1 -0
  64. package/dist/chunk-MZ4C7EHF.mjs +6942 -0
  65. package/dist/chunk-MZ4C7EHF.mjs.map +1 -0
  66. package/dist/chunk-RIPAEGI6.mjs +48 -0
  67. package/dist/chunk-RIPAEGI6.mjs.map +1 -0
  68. package/dist/{chunk-LEAXOMYJ.mjs → chunk-SGPUJW3F.mjs} +117 -5
  69. package/dist/chunk-SGPUJW3F.mjs.map +1 -0
  70. package/dist/{chunk-PA5DRJNI.mjs → chunk-X2KA6WHM.mjs} +29 -4
  71. package/dist/chunk-X2KA6WHM.mjs.map +1 -0
  72. package/dist/{chunk-BLXGONR2.mjs → chunk-XSFB7XXQ.mjs} +51 -4
  73. package/dist/chunk-XSFB7XXQ.mjs.map +1 -0
  74. package/dist/cloud/density.d.ts +55 -0
  75. package/dist/cloud/density.d.ts.map +1 -0
  76. package/dist/cloud/extract.d.ts +25 -0
  77. package/dist/cloud/extract.d.ts.map +1 -0
  78. package/dist/cloud/feature.d.ts +57 -0
  79. package/dist/cloud/feature.d.ts.map +1 -0
  80. package/dist/cloud/frame-context.d.ts +8 -0
  81. package/dist/cloud/frame-context.d.ts.map +1 -0
  82. package/dist/cloud/inspection.d.ts +59 -0
  83. package/dist/cloud/inspection.d.ts.map +1 -0
  84. package/dist/cloud/optics.d.ts +56 -0
  85. package/dist/cloud/optics.d.ts.map +1 -0
  86. package/dist/cloud/parameters.d.ts +73 -0
  87. package/dist/cloud/parameters.d.ts.map +1 -0
  88. package/dist/cloud/resources.d.ts +60 -0
  89. package/dist/cloud/resources.d.ts.map +1 -0
  90. package/dist/cloud/shadow.d.ts +42 -0
  91. package/dist/cloud/shadow.d.ts.map +1 -0
  92. package/dist/cloud/temporal.d.ts +74 -0
  93. package/dist/cloud/temporal.d.ts.map +1 -0
  94. package/dist/components/barrel-distortion.d.ts +13 -0
  95. package/dist/components/barrel-distortion.d.ts.map +1 -0
  96. package/dist/components/cloud-layer.d.ts +29 -0
  97. package/dist/components/cloud-layer.d.ts.map +1 -0
  98. package/dist/components/index.d.ts +2 -0
  99. package/dist/components/index.d.ts.map +1 -1
  100. package/dist/components/instances.d.ts +20 -6
  101. package/dist/components/instances.d.ts.map +1 -1
  102. package/dist/construct-renderer.d.ts +1 -1
  103. package/dist/construct-renderer.d.ts.map +1 -1
  104. package/dist/construct-renderer.mjs +8406 -5785
  105. package/dist/construct-renderer.mjs.map +1 -1
  106. package/dist/device/gpu-residency.d.ts +19 -19
  107. package/dist/device/gpu-residency.d.ts.map +1 -1
  108. package/dist/dynamic-geometry.d.ts +5 -1
  109. package/dist/dynamic-geometry.d.ts.map +1 -1
  110. package/dist/errors/cloud.d.ts +59 -0
  111. package/dist/errors/cloud.d.ts.map +1 -0
  112. package/dist/errors/render.d.ts +20 -5
  113. package/dist/errors/render.d.ts.map +1 -1
  114. package/dist/examples/single-layer-medium-surface.d.ts +47 -0
  115. package/dist/examples/single-layer-medium-surface.d.ts.map +1 -0
  116. package/dist/extract/camera.d.ts.map +1 -1
  117. package/dist/extract/contracts.d.ts +5 -1
  118. package/dist/extract/contracts.d.ts.map +1 -1
  119. package/dist/extract/material-context.d.ts +7 -0
  120. package/dist/extract/material-context.d.ts.map +1 -0
  121. package/dist/features/barrel-distortion.d.ts +29 -0
  122. package/dist/features/barrel-distortion.d.ts.map +1 -0
  123. package/dist/features/error-reporting.d.ts +6 -2
  124. package/dist/features/error-reporting.d.ts.map +1 -1
  125. package/dist/features/host.d.ts +15 -0
  126. package/dist/features/host.d.ts.map +1 -1
  127. package/dist/features/plan.d.ts +10 -0
  128. package/dist/features/plan.d.ts.map +1 -1
  129. package/dist/features/render-graph-contribution.d.ts +5 -0
  130. package/dist/features/render-graph-contribution.d.ts.map +1 -1
  131. package/dist/features/render-graph-raster.d.ts +1 -0
  132. package/dist/features/render-graph-raster.d.ts.map +1 -1
  133. package/dist/features/targets.d.ts +2 -0
  134. package/dist/features/targets.d.ts.map +1 -1
  135. package/dist/features/types.d.ts +48 -0
  136. package/dist/features/types.d.ts.map +1 -1
  137. package/dist/fullscreen-post-process-pass.d.ts +15 -7
  138. package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
  139. package/dist/glyph-text-layout-system.d.ts +1 -1
  140. package/dist/glyph-text-layout-system.d.ts.map +1 -1
  141. package/dist/gpu-dirty-ranges.d.ts +11 -0
  142. package/dist/gpu-dirty-ranges.d.ts.map +1 -0
  143. package/dist/gpu-driven/batch-topology.d.ts +17 -0
  144. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  145. package/dist/gpu-driven/material-artifacts.d.ts +6 -0
  146. package/dist/gpu-driven/material-artifacts.d.ts.map +1 -1
  147. package/dist/gpu-driven/prepared-draw.d.ts.map +1 -1
  148. package/dist/gpu-driven/production-raster-material.d.ts +178 -0
  149. package/dist/gpu-driven/production-raster-material.d.ts.map +1 -0
  150. package/dist/gpu-driven/production-raster.d.ts +47 -3
  151. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  152. package/dist/gpu-driven/resource-allocation.d.ts +31 -0
  153. package/dist/gpu-driven/resource-allocation.d.ts.map +1 -0
  154. package/dist/gpu-driven/shadow-ownership.d.ts +14 -0
  155. package/dist/gpu-driven/shadow-ownership.d.ts.map +1 -0
  156. package/dist/gpu-driven/view-gpu.d.ts +26 -2
  157. package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
  158. package/dist/gpu-scene.d.ts +5 -0
  159. package/dist/gpu-scene.d.ts.map +1 -1
  160. package/dist/hdrp-buffers.d.ts +2 -2
  161. package/dist/hdrp-buffers.d.ts.map +1 -1
  162. package/dist/ibl/frame-resources.d.ts.map +1 -1
  163. package/dist/ibl/skylight-bind-group.d.ts +14 -5
  164. package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
  165. package/dist/index.d.ts +30 -7
  166. package/dist/index.d.ts.map +1 -1
  167. package/dist/index.mjs +780 -9
  168. package/dist/index.mjs.map +1 -1
  169. package/dist/inspection-types.d.ts +135 -1
  170. package/dist/inspection-types.d.ts.map +1 -1
  171. package/dist/instance-buffer-cache.d.ts +4 -4
  172. package/dist/instance-buffer-cache.d.ts.map +1 -1
  173. package/dist/instances.d.ts +47 -100
  174. package/dist/instances.d.ts.map +1 -1
  175. package/dist/internal.mjs +7 -8
  176. package/dist/internal.mjs.map +1 -1
  177. package/dist/pbr-pipeline.d.ts +22 -4
  178. package/dist/pbr-pipeline.d.ts.map +1 -1
  179. package/dist/pipeline/single-layer-medium-passes.d.ts +73 -0
  180. package/dist/pipeline/single-layer-medium-passes.d.ts.map +1 -0
  181. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  182. package/dist/pipeline/standard-output/graph.d.ts.map +1 -1
  183. package/dist/pipeline/standard-output/lut-gpu.d.ts +2 -2
  184. package/dist/pipeline/standard-output/lut-gpu.d.ts.map +1 -1
  185. package/dist/pipeline/standard-output/resources.d.ts +2 -2
  186. package/dist/pipeline/standard-output/resources.d.ts.map +1 -1
  187. package/dist/pipeline/standard-output/types.d.ts +4 -2
  188. package/dist/pipeline/standard-output/types.d.ts.map +1 -1
  189. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  190. package/dist/pipeline/standard-post.d.ts.map +1 -1
  191. package/dist/pipeline-builder.d.ts +1 -1
  192. package/dist/pipeline-builder.d.ts.map +1 -1
  193. package/dist/plugin.d.ts.map +1 -1
  194. package/dist/prepare/prepared-graphics-resolver.d.ts +2 -0
  195. package/dist/prepare/prepared-graphics-resolver.d.ts.map +1 -1
  196. package/dist/publication/contract.d.ts +77 -0
  197. package/dist/publication/contract.d.ts.map +1 -0
  198. package/dist/publication/dependencies.d.ts +7 -0
  199. package/dist/publication/dependencies.d.ts.map +1 -0
  200. package/dist/publication/prepare-geometry.d.ts +9 -0
  201. package/dist/publication/prepare-geometry.d.ts.map +1 -0
  202. package/dist/publication/programs.d.ts +5 -0
  203. package/dist/publication/programs.d.ts.map +1 -0
  204. package/dist/publication/publisher.d.ts +33 -0
  205. package/dist/publication/publisher.d.ts.map +1 -0
  206. package/dist/publication/receiver.d.ts +29 -0
  207. package/dist/publication/receiver.d.ts.map +1 -0
  208. package/dist/publication/resource-scope.d.ts +18 -0
  209. package/dist/publication/resource-scope.d.ts.map +1 -0
  210. package/dist/publication/targets.d.ts +33 -0
  211. package/dist/publication/targets.d.ts.map +1 -0
  212. package/dist/publication/video.d.ts +5 -0
  213. package/dist/publication/video.d.ts.map +1 -0
  214. package/dist/record/barrel-distortion-frame.d.ts +15 -0
  215. package/dist/record/barrel-distortion-frame.d.ts.map +1 -0
  216. package/dist/record/dynamic-geometry-consumption.d.ts +10 -2
  217. package/dist/record/dynamic-geometry-consumption.d.ts.map +1 -1
  218. package/dist/record/fold-buckets.d.ts +95 -0
  219. package/dist/record/fold-buckets.d.ts.map +1 -0
  220. package/dist/record/fold-instance-buffer.d.ts +6 -0
  221. package/dist/record/fold-instance-buffer.d.ts.map +1 -0
  222. package/dist/record/frame-lighting.d.ts +1 -0
  223. package/dist/record/frame-lighting.d.ts.map +1 -1
  224. package/dist/record/frame-snapshot.d.ts +43 -9
  225. package/dist/record/frame-snapshot.d.ts.map +1 -1
  226. package/dist/record/frame.d.ts +15 -65
  227. package/dist/record/frame.d.ts.map +1 -1
  228. package/dist/record/gpu-draw-receipts.d.ts +11 -0
  229. package/dist/record/gpu-draw-receipts.d.ts.map +1 -0
  230. package/dist/record/gpu-timing.d.ts +8 -0
  231. package/dist/record/gpu-timing.d.ts.map +1 -1
  232. package/dist/record/helpers.d.ts +2 -2
  233. package/dist/record/helpers.d.ts.map +1 -1
  234. package/dist/record/main-pass-geometry.d.ts +22 -6
  235. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  236. package/dist/record/main-pass-material.d.ts +14 -10
  237. package/dist/record/main-pass-material.d.ts.map +1 -1
  238. package/dist/record/main-pass-sprite-draws.d.ts +3 -16
  239. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  240. package/dist/record/main-pass.d.ts +13 -2
  241. package/dist/record/main-pass.d.ts.map +1 -1
  242. package/dist/record/motion-blur-receipt.d.ts +11 -0
  243. package/dist/record/motion-blur-receipt.d.ts.map +1 -0
  244. package/dist/record/prepared-material-bindings.d.ts +3 -3
  245. package/dist/record/prepared-material-bindings.d.ts.map +1 -1
  246. package/dist/record/probe-blend-buffer.d.ts +16 -0
  247. package/dist/record/probe-blend-buffer.d.ts.map +1 -0
  248. package/dist/record/recovery-pipeline.d.ts.map +1 -1
  249. package/dist/record/render-context.d.ts +24 -5
  250. package/dist/record/render-context.d.ts.map +1 -1
  251. package/dist/record/shadow-pass.d.ts +2 -0
  252. package/dist/record/shadow-pass.d.ts.map +1 -1
  253. package/dist/record/skybox-post-pass.d.ts +1 -1
  254. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  255. package/dist/record/sprite-instance-buffer.d.ts +31 -0
  256. package/dist/record/sprite-instance-buffer.d.ts.map +1 -0
  257. package/dist/record/target-capture-graph.d.ts +19 -0
  258. package/dist/record/target-capture-graph.d.ts.map +1 -0
  259. package/dist/record/typed-frame-graph.d.ts +24 -15
  260. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  261. package/dist/record/view-ubo.d.ts +11 -3
  262. package/dist/record/view-ubo.d.ts.map +1 -1
  263. package/dist/record/volume-params.d.ts +9 -0
  264. package/dist/record/volume-params.d.ts.map +1 -0
  265. package/dist/recovery/render-system-candidate.d.ts +14 -4
  266. package/dist/recovery/render-system-candidate.d.ts.map +1 -1
  267. package/dist/reflection/record-owner.d.ts.map +1 -1
  268. package/dist/render-contract.d.ts +46 -5
  269. package/dist/render-contract.d.ts.map +1 -1
  270. package/dist/render-graph-primitives.d.ts.map +1 -1
  271. package/dist/render-pipeline.d.ts +103 -2
  272. package/dist/render-pipeline.d.ts.map +1 -1
  273. package/dist/render-system-extract-tail.d.ts +3 -2
  274. package/dist/render-system-extract-tail.d.ts.map +1 -1
  275. package/dist/render-system-extract.d.ts +18 -9
  276. package/dist/render-system-extract.d.ts.map +1 -1
  277. package/dist/render-system-presentation.d.ts +23 -0
  278. package/dist/render-system-presentation.d.ts.map +1 -0
  279. package/dist/render-system-producer-roots.d.ts +10 -0
  280. package/dist/render-system-producer-roots.d.ts.map +1 -0
  281. package/dist/render-system-projections.d.ts +3 -2
  282. package/dist/render-system-projections.d.ts.map +1 -1
  283. package/dist/render-system-residency.d.ts +12 -0
  284. package/dist/render-system-residency.d.ts.map +1 -0
  285. package/dist/render-system.d.ts +22 -21
  286. package/dist/render-system.d.ts.map +1 -1
  287. package/dist/scene/probe-blend.d.ts +27 -0
  288. package/dist/scene/probe-blend.d.ts.map +1 -1
  289. package/dist/scene/render-scene.d.ts +18 -7
  290. package/dist/scene/render-scene.d.ts.map +1 -1
  291. package/dist/scene/render-source.d.ts +42 -0
  292. package/dist/scene/render-source.d.ts.map +1 -0
  293. package/dist/scene/source-systems.d.ts +6 -0
  294. package/dist/scene/source-systems.d.ts.map +1 -0
  295. package/dist/ssao-buffers.d.ts +9 -0
  296. package/dist/ssao-buffers.d.ts.map +1 -1
  297. package/dist/surface/dynamic-input.d.ts +133 -0
  298. package/dist/surface/dynamic-input.d.ts.map +1 -0
  299. package/dist/surface/gpu-driven.d.ts +41 -0
  300. package/dist/surface/gpu-driven.d.ts.map +1 -0
  301. package/dist/surface/submission-observation.d.ts +136 -0
  302. package/dist/surface/submission-observation.d.ts.map +1 -0
  303. package/dist/systems/skin-palette-allocator.d.ts.map +1 -1
  304. package/dist/systems/skin-palette-types.d.ts +10 -0
  305. package/dist/systems/skin-palette-types.d.ts.map +1 -1
  306. package/dist/systems/transparent-dispatch.d.ts +2 -2
  307. package/dist/systems/transparent-dispatch.d.ts.map +1 -1
  308. package/dist/systems/transparent-sort-config.d.ts +2 -1
  309. package/dist/systems/transparent-sort-config.d.ts.map +1 -1
  310. package/dist/temporal/gpu.d.ts +13 -1
  311. package/dist/temporal/gpu.d.ts.map +1 -1
  312. package/dist/temporal/index.mjs +6 -7
  313. package/dist/temporal/inspection.d.ts.map +1 -1
  314. package/dist/temporal/standard-scene-data.d.ts.map +1 -1
  315. package/dist/temporal/target-coverage-attachment.d.ts.map +1 -1
  316. package/dist/transmission/single-layer-medium.d.ts +89 -0
  317. package/dist/transmission/single-layer-medium.d.ts.map +1 -0
  318. package/dist/typed-render-graph-primitives.d.ts +14 -2
  319. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  320. package/dist/volume/component.d.ts +9 -4
  321. package/dist/volume/component.d.ts.map +1 -1
  322. package/dist/volume/extract.d.ts +1 -1
  323. package/dist/volume/extract.d.ts.map +1 -1
  324. package/dist/volume/inspection.d.ts +2 -0
  325. package/dist/volume/inspection.d.ts.map +1 -1
  326. package/dist/volume/passes.d.ts +4 -1
  327. package/dist/volume/passes.d.ts.map +1 -1
  328. package/dist/volume/resources.d.ts.map +1 -1
  329. package/package.json +22 -21
  330. package/src/__tests__/atmosphere-daylight.dawn.test.ts +72 -0
  331. package/src/__tests__/auto-exposure-transaction.integration.test.ts +4 -9
  332. package/src/__tests__/barrel-distortion-lkg.integration.test.ts +353 -0
  333. package/src/__tests__/barrel-distortion.dawn.test.ts +41 -0
  334. package/src/__tests__/barrel-distortion.unit.test.ts +364 -0
  335. package/src/__tests__/construct-renderer-identity.unit.test.ts +49 -0
  336. package/src/__tests__/detached-recovery-graph.unit.test.ts +277 -0
  337. package/src/__tests__/device-feature-admission.unit.test.ts +3 -2
  338. package/src/__tests__/device-loss-fanout.unit.test.ts +36 -1
  339. package/src/__tests__/docs-gate-green.unit.test.ts +112 -1
  340. package/src/__tests__/factory-contract.integration.test.ts +295 -45
  341. package/src/__tests__/feature-depth-input.dawn.test.ts +129 -2
  342. package/src/__tests__/fullscreen-feature-plan.unit.test.ts +14 -3
  343. package/src/__tests__/gpu-dirty-ranges.unit.test.ts +22 -0
  344. package/src/__tests__/gpu-driven-baseline.characterization.test.ts +16 -3
  345. package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +12 -0
  346. package/src/__tests__/gpu-driven-material-artifacts.unit.test.ts +143 -0
  347. package/src/__tests__/gpu-driven-pbr.dawn.test.ts +5 -6
  348. package/src/__tests__/gpu-driven-production.integration.test.ts +482 -3
  349. package/src/__tests__/gpu-driven-shadow-views.dawn.test.ts +6 -5
  350. package/src/__tests__/gpu-driven-view-gpu-evidence.ts +94 -6
  351. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +18 -2
  352. package/src/__tests__/gpu-driven-view.browser.test.ts +5 -8
  353. package/src/__tests__/gpu-driven-view.dawn.test.ts +6 -2
  354. package/src/__tests__/gpu-driven-view.unit.test.ts +1 -1
  355. package/src/__tests__/gpu-pass-timing-lifecycle.integration.test.ts +1 -0
  356. package/src/__tests__/gpu-pass-timing.browser.test.ts +6 -4
  357. package/src/__tests__/gpu-pass-timing.rhi-null.unit.test.ts +1 -0
  358. package/src/__tests__/gpu-scene.unit.test.ts +119 -9
  359. package/src/__tests__/instance-allocation-failure.unit.test.ts +57 -0
  360. package/src/__tests__/instances-culling.integration.test.ts +10 -36
  361. package/src/__tests__/instances-store.unit.test.ts +97 -305
  362. package/src/__tests__/instances-world-ownership.integration.test.ts +62 -0
  363. package/src/__tests__/ktx2-basis-gpu-consumer.dawn.test.ts +4 -1
  364. package/src/__tests__/main-pass-material-medium-selection.unit.test.ts +145 -0
  365. package/src/__tests__/material-shader-prewarm.unit.test.ts +59 -0
  366. package/src/__tests__/material-snapshot-mutation.integration.test.ts +39 -1
  367. package/src/__tests__/materials-standard-contract.unit.test.ts +0 -1
  368. package/src/__tests__/morph-culling-reentry.dawn.test.ts +5 -5
  369. package/src/__tests__/morph-visual-evidence.integration.test.ts +8 -7
  370. package/src/__tests__/motion-blur-compute.dawn.test.ts +13 -7
  371. package/src/__tests__/motion-blur.pixel.integration.test.ts +2 -2
  372. package/src/__tests__/nineslice-handle-surface.unit.test.ts +4 -4
  373. package/src/__tests__/pbr-view-layout.unit.test.ts +9 -0
  374. package/src/__tests__/physical-clearcoat.integration.test.ts +66 -8
  375. package/src/__tests__/physical-material-budget.unit.test.ts +40 -0
  376. package/src/__tests__/prepared-graphics-capability.unit.test.ts +66 -2
  377. package/src/__tests__/probe-bindings.unit.test.ts +582 -1
  378. package/src/__tests__/probe-scene-dirty.integration.test.ts +28 -1
  379. package/src/__tests__/receipt-observation-retirement.unit.test.ts +65 -0
  380. package/src/__tests__/recovery-candidate-prepare.contract.test.ts +69 -0
  381. package/src/__tests__/render-error-code-owner.test-d.ts +8 -0
  382. package/src/__tests__/render-feature-diagnostics.unit.test.ts +15 -2
  383. package/src/__tests__/render-feature-error-reporting.unit.test.ts +21 -1
  384. package/src/__tests__/render-feature-prepared-diagnostics.unit.test.ts +10 -1
  385. package/src/__tests__/render-feature-retry.unit.test.ts +12 -1
  386. package/src/__tests__/render-scene-mixed-updates.integration.test.ts +145 -19
  387. package/src/__tests__/render-scene-projection.unit.test.ts +36 -11
  388. package/src/__tests__/render-scene-temporal-retry.unit.test.ts +20 -20
  389. package/src/__tests__/render-target-probe-soak.perf.test.ts +2 -2
  390. package/src/__tests__/render-temporal-environment-consumer.browser.test.ts +3 -3
  391. package/src/__tests__/render-temporal-environment-consumer.dawn.test.ts +5 -2
  392. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +42 -8
  393. package/src/__tests__/renderer-frame-generation-fence.integration.test.ts +16 -0
  394. package/src/__tests__/renderer-frame-transaction.integration.test.ts +26 -10
  395. package/src/__tests__/resource-allocation.unit.test.ts +123 -0
  396. package/src/__tests__/single-layer-medium-passes.unit.test.ts +275 -0
  397. package/src/__tests__/skin-motion-regression.unit.test.ts +70 -1
  398. package/src/__tests__/skin-palette-recycling.dawn.test.ts +97 -0
  399. package/src/__tests__/skinned-shadow-caster.test.ts +15 -0
  400. package/src/__tests__/ssr-carrier-public-contract.unit.test.ts +21 -0
  401. package/src/__tests__/ssr-gpu-dispatch.browser.test.ts +4 -4
  402. package/src/__tests__/ssr-gpu-dispatch.dawn.test.ts +2 -2
  403. package/src/__tests__/ssr-gpu-dispatch.ts +1 -1
  404. package/src/__tests__/ssr-receiver-coverage.dawn.test.ts +6 -2
  405. package/src/__tests__/standard-output-chain-contract.unit.test.ts +28 -0
  406. package/src/__tests__/standard-pbr-artifact-assembly.unit.test.ts +46 -1
  407. package/src/__tests__/standard-pipeline.integration.test.ts +182 -1
  408. package/src/__tests__/surface-dynamic-buffer-lifecycle.unit.test.ts +506 -0
  409. package/src/__tests__/surface-submission-observation.unit.test.ts +812 -0
  410. package/src/__tests__/temporal-history-lifecycle.unit.test.ts +32 -0
  411. package/src/__tests__/temporal-performance.integration.test.ts +3 -3
  412. package/src/__tests__/visibility-instances.integration.test.ts +7 -19
  413. package/src/__tests__/visibility-producer-matrix.integration.test.ts +9 -20
  414. package/src/__tests__/volume-optics-oracle.unit.test.ts +4 -2
  415. package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +30 -20
  416. package/src/__tests__/volumetric-fog-errors.unit.test.ts +4 -3
  417. package/src/__tests__/volumetric-fog-multiple.unit.test.ts +55 -0
  418. package/src/__tests__/volumetric-fog-spot-frame-optics.integration.test.ts +1 -1
  419. package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +113 -9
  420. package/src/__tests__/volumetric-fog-topology.unit.test.ts +5 -3
  421. package/src/assembly/backend-contract.ts +30 -0
  422. package/src/assembly/barrel-distortion-inspection.ts +22 -0
  423. package/src/assembly/canvas-draw-guard.ts +11 -0
  424. package/src/assembly/cloud-shadow-entry.ts +22 -0
  425. package/src/assembly/device-feature-admission.ts +2 -2
  426. package/src/assembly/dynamic-geometry-host.ts +65 -37
  427. package/src/assembly/dynamic-geometry-runtime.ts +7 -1
  428. package/src/assembly/factory.ts +2 -30
  429. package/src/assembly/host-contract.ts +8 -5
  430. package/src/assembly/material/assembly.ts +25 -13
  431. package/src/assembly/material/pipeline-helpers.ts +53 -12
  432. package/src/assembly/material-shader-policy.ts +55 -0
  433. package/src/assembly/material-shader-prewarm.ts +21 -10
  434. package/src/assembly/recovery/device-loss-fanout.ts +13 -0
  435. package/src/assembly/recovery/renderer-recover.ts +50 -18
  436. package/src/assembly/render-feature-post-processes.ts +54 -0
  437. package/src/assembly/render-target-host.ts +57 -47
  438. package/src/assembly/renderer-facade.ts +7 -2
  439. package/src/assembly/renderer-feature-inspection.ts +8 -2
  440. package/src/assembly/renderer-frame-transaction.ts +23 -0
  441. package/src/assembly/renderer-inspection.ts +28 -0
  442. package/src/assembly/skin-palette-owner.ts +1 -1
  443. package/src/assembly/temporal-post-process-prewarm.ts +3 -3
  444. package/src/assembly/webgpu-pbr-ready.ts +9 -1
  445. package/src/assembly/webgpu-ready-per-pass.ts +104 -0
  446. package/src/assembly/webgpu-ready.ts +116 -62
  447. package/src/assembly/webgpu-renderer-bootstrap.ts +0 -12
  448. package/src/assembly/webgpu-renderer-observation-owner.ts +222 -0
  449. package/src/assembly/webgpu-renderer.ts +303 -313
  450. package/src/assembly/webgpu-vertex-layouts.ts +1 -1
  451. package/src/barrel-distortion.ts +247 -0
  452. package/src/cloud/__tests__/cloud-density-parity.dawn.test.ts +173 -0
  453. package/src/cloud/__tests__/cloud-layer-framebuffer-y.dawn.test.ts +265 -0
  454. package/src/cloud/__tests__/cloud-layer.browser.test.ts +432 -0
  455. package/src/cloud/__tests__/cloud-layer.unit.test.ts +634 -0
  456. package/src/cloud/density.ts +600 -0
  457. package/src/cloud/extract.ts +110 -0
  458. package/src/cloud/feature.ts +1810 -0
  459. package/src/cloud/frame-context.ts +42 -0
  460. package/src/cloud/inspection.ts +130 -0
  461. package/src/cloud/optics.ts +285 -0
  462. package/src/cloud/parameters.ts +255 -0
  463. package/src/cloud/resources.ts +122 -0
  464. package/src/cloud/shadow.ts +167 -0
  465. package/src/cloud/temporal.ts +226 -0
  466. package/src/components/barrel-distortion.ts +46 -0
  467. package/src/components/cloud-layer.ts +47 -0
  468. package/src/components/index.ts +2 -0
  469. package/src/components/instances.ts +18 -45
  470. package/src/components/sprite-instances.ts +1 -1
  471. package/src/construct-renderer.ts +1 -1
  472. package/src/device/gpu-residency.ts +26 -25
  473. package/src/dynamic-geometry.ts +12 -1
  474. package/src/errors/cloud.ts +103 -0
  475. package/src/errors/render.ts +47 -8
  476. package/src/examples/single-layer-medium-surface.ts +59 -0
  477. package/src/extract/camera.ts +15 -1
  478. package/src/extract/contracts.ts +5 -0
  479. package/src/extract/material-context.ts +21 -0
  480. package/src/features/barrel-distortion.ts +172 -0
  481. package/src/features/error-reporting.ts +26 -6
  482. package/src/features/host.ts +128 -19
  483. package/src/features/plan.ts +10 -0
  484. package/src/features/render-graph-contribution.ts +9 -0
  485. package/src/features/render-graph-raster.ts +45 -3
  486. package/src/features/targets.ts +66 -0
  487. package/src/features/types.ts +51 -0
  488. package/src/fullscreen-post-process-pass.ts +41 -9
  489. package/src/glyph-text-layout-system.ts +2 -2
  490. package/src/gpu-dirty-ranges.ts +48 -0
  491. package/src/gpu-driven/batch-topology.ts +53 -0
  492. package/src/gpu-driven/material-artifacts.ts +33 -25
  493. package/src/gpu-driven/prepared-draw.ts +12 -4
  494. package/src/gpu-driven/production-raster-material.ts +1894 -0
  495. package/src/gpu-driven/production-raster.ts +825 -784
  496. package/src/gpu-driven/resource-allocation.ts +142 -0
  497. package/src/gpu-driven/shadow-ownership.ts +71 -0
  498. package/src/gpu-driven/view-gpu.ts +164 -10
  499. package/src/gpu-scene.ts +75 -84
  500. package/src/hdrp-buffers.ts +4 -3
  501. package/src/ibl/frame-resources.ts +0 -1
  502. package/src/ibl/skylight-bind-group.ts +62 -32
  503. package/src/index.ts +205 -9
  504. package/src/inspection-types.ts +162 -0
  505. package/src/instance-buffer-cache.ts +4 -4
  506. package/src/instances.ts +152 -569
  507. package/src/pbr-pipeline.ts +230 -30
  508. package/src/pipeline/single-layer-medium-passes.ts +267 -0
  509. package/src/pipeline/standard-forward-lane.ts +313 -57
  510. package/src/pipeline/standard-output/graph.ts +8 -2
  511. package/src/pipeline/standard-output/lut-gpu.ts +3 -3
  512. package/src/pipeline/standard-output/resources.ts +2 -2
  513. package/src/pipeline/standard-output/types.ts +12 -1
  514. package/src/pipeline/standard-pipeline.ts +291 -18
  515. package/src/pipeline/standard-post.ts +90 -24
  516. package/src/pipeline-builder.ts +1 -1
  517. package/src/plugin.ts +4 -0
  518. package/src/points-lines/__tests__/points-lines.bench.ts +17 -8
  519. package/src/prepare/prepared-graphics-resolver.ts +2 -0
  520. package/src/publication/__tests__/derived-geometry.integration.test.ts +177 -0
  521. package/src/publication/__tests__/features.integration.test.ts +122 -0
  522. package/src/publication/__tests__/programs.unit.test.ts +25 -0
  523. package/src/publication/__tests__/publication.integration.test.ts +370 -0
  524. package/src/publication/__tests__/targets.integration.test.ts +120 -0
  525. package/src/publication/__tests__/video.browser.test.ts +50 -0
  526. package/src/publication/contract.ts +104 -0
  527. package/src/publication/dependencies.ts +54 -0
  528. package/src/publication/prepare-geometry.ts +94 -0
  529. package/src/publication/programs.ts +45 -0
  530. package/src/publication/publisher.ts +618 -0
  531. package/src/publication/receiver.ts +297 -0
  532. package/src/publication/resource-scope.ts +31 -0
  533. package/src/publication/targets.ts +182 -0
  534. package/src/publication/video.ts +33 -0
  535. package/src/record/__tests__/main-pass-geometry.unit.test.ts +30 -0
  536. package/src/record/__tests__/material-bg-cache.unit.test.ts +15 -1
  537. package/src/record/__tests__/sprite-material-abi.unit.test.ts +9 -11
  538. package/src/record/__tests__/temporal-payload.unit.test.ts +1 -1
  539. package/src/record/__tests__/transparent-group2-selection.unit.test.ts +11 -0
  540. package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +1 -0
  541. package/src/record/__tests__/video-texture-view.unit.test.ts +9 -9
  542. package/src/record/__tests__/view-ubo-layout.unit.test.ts +4 -3
  543. package/src/record/barrel-distortion-frame.ts +110 -0
  544. package/src/record/dynamic-geometry-consumption.ts +39 -9
  545. package/src/record/fold-buckets.ts +855 -0
  546. package/src/record/fold-instance-buffer.ts +51 -0
  547. package/src/record/frame-lighting.ts +36 -2
  548. package/src/record/frame-snapshot.ts +45 -9
  549. package/src/record/frame.ts +258 -1105
  550. package/src/record/gpu-draw-receipts.ts +38 -0
  551. package/src/record/gpu-timing.ts +48 -5
  552. package/src/record/helpers.ts +2 -2
  553. package/src/record/main-pass-geometry.ts +272 -84
  554. package/src/record/main-pass-material.ts +102 -36
  555. package/src/record/main-pass-sprite-draws.ts +13 -217
  556. package/src/record/main-pass.ts +164 -27
  557. package/src/record/motion-blur-receipt.ts +91 -0
  558. package/src/record/prepared-material-bindings.ts +11 -6
  559. package/src/record/probe-blend-buffer.ts +180 -0
  560. package/src/record/recovery-pipeline.ts +35 -3
  561. package/src/record/render-context.ts +41 -5
  562. package/src/record/shadow-pass.ts +30 -4
  563. package/src/record/skybox-post-pass.ts +28 -4
  564. package/src/record/sprite-instance-buffer.ts +155 -0
  565. package/src/record/target-capture-graph.ts +258 -0
  566. package/src/record/typed-frame-graph.ts +776 -340
  567. package/src/record/view-ubo.ts +27 -4
  568. package/src/record/volume-params.ts +225 -0
  569. package/src/recovery/render-system-candidate.ts +243 -50
  570. package/src/reflection/record-owner.ts +7 -3
  571. package/src/render-contract.ts +54 -5
  572. package/src/render-graph-primitives.ts +7 -1
  573. package/src/render-pipeline.ts +130 -0
  574. package/src/render-system-extract-tail.ts +90 -78
  575. package/src/render-system-extract.ts +76 -76
  576. package/src/render-system-presentation.ts +56 -0
  577. package/src/render-system-producer-roots.ts +96 -0
  578. package/src/render-system-projections.ts +3 -2
  579. package/src/render-system-residency.ts +159 -0
  580. package/src/render-system.ts +421 -408
  581. package/src/scene/probe-blend.ts +96 -4
  582. package/src/scene/render-scene.ts +416 -337
  583. package/src/scene/render-source.ts +90 -0
  584. package/src/scene/source-systems.ts +33 -0
  585. package/src/ssao-buffers.ts +95 -2
  586. package/src/surface/__tests__/dynamic-input.unit.test.ts +374 -0
  587. package/src/surface/__tests__/gpu-driven.unit.test.ts +87 -0
  588. package/src/surface/dynamic-input.ts +658 -0
  589. package/src/surface/gpu-driven.ts +147 -0
  590. package/src/surface/submission-observation.ts +517 -0
  591. package/src/systems/skin-palette-allocator.ts +5 -1
  592. package/src/systems/skin-palette-types.ts +11 -0
  593. package/src/systems/transparent-dispatch.ts +2 -2
  594. package/src/systems/transparent-sort-config.ts +3 -1
  595. package/src/temporal/gpu.ts +101 -17
  596. package/src/temporal/inspection.ts +7 -1
  597. package/src/temporal/standard-scene-data.ts +5 -0
  598. package/src/temporal/target-coverage-attachment.ts +4 -0
  599. package/src/transmission/__tests__/single-layer-medium.unit.test.ts +149 -0
  600. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +12 -11
  601. package/src/transmission/single-layer-medium.ts +322 -0
  602. package/src/typed-render-graph-primitives.ts +70 -2
  603. package/src/volume/component.ts +10 -9
  604. package/src/volume/extract.ts +11 -5
  605. package/src/volume/inspection.ts +3 -0
  606. package/src/volume/passes.ts +87 -4
  607. package/src/volume/resources.ts +2 -1
  608. package/dist/chunk-3RTCJTH4.mjs +0 -1610
  609. package/dist/chunk-3RTCJTH4.mjs.map +0 -1
  610. package/dist/chunk-BLXGONR2.mjs.map +0 -1
  611. package/dist/chunk-ILQ5MEZF.mjs +0 -32
  612. package/dist/chunk-ILQ5MEZF.mjs.map +0 -1
  613. package/dist/chunk-LEAXOMYJ.mjs.map +0 -1
  614. package/dist/chunk-NB2S7ZRX.mjs +0 -1783
  615. package/dist/chunk-NB2S7ZRX.mjs.map +0 -1
  616. package/dist/chunk-NNQL7CB3.mjs +0 -1713
  617. package/dist/chunk-NNQL7CB3.mjs.map +0 -1
  618. package/dist/chunk-OO3DRYK7.mjs.map +0 -1
  619. package/dist/chunk-PA5DRJNI.mjs.map +0 -1
  620. package/dist/chunk-PLAXOOX5.mjs +0 -1907
  621. package/dist/chunk-PLAXOOX5.mjs.map +0 -1
  622. package/dist/chunk-RXZ3RGOU.mjs +0 -53
  623. package/dist/chunk-RXZ3RGOU.mjs.map +0 -1
  624. package/dist/chunk-VOZWDM3L.mjs.map +0 -1
@@ -0,0 +1,65 @@
1
+ import type { Buffer, MappedBuffer, RhiDevice, Texture } from '@forgeax/engine-rhi';
2
+ import { ok } from '@forgeax/engine-types';
3
+ import { expect, it, vi } from 'vitest';
4
+ import { createRendererObservationCaptureOwner } from '../assembly/webgpu-renderer-observation-owner';
5
+
6
+ function deferred<T>(): { readonly promise: Promise<T>; readonly resolve: (value: T) => void } {
7
+ let resolve!: (value: T) => void;
8
+ const promise = new Promise<T>((next) => {
9
+ resolve = next;
10
+ });
11
+ return { promise, resolve };
12
+ }
13
+
14
+ it('retires a receipt observation readback only after its pending map completes', async () => {
15
+ const mapStarted = deferred<void>();
16
+ const mapCompleted = deferred<ReturnType<typeof ok<MappedBuffer>>>();
17
+ const mappedBytes = new ArrayBuffer(256);
18
+ const unmap = vi.fn();
19
+ const buffer = {
20
+ mapAsync: async () => {
21
+ mapStarted.resolve();
22
+ return mapCompleted.promise;
23
+ },
24
+ } as unknown as Buffer;
25
+ const texture = {} as Texture;
26
+ const destroyBuffer = vi.fn(() => ok(undefined));
27
+ const device = { destroyBuffer } as unknown as RhiDevice;
28
+ const errors: unknown[] = [];
29
+ const owner = createRendererObservationCaptureOwner((error) => errors.push(error));
30
+ const capture = {
31
+ domain: 'linear-hdr' as const,
32
+ format: 'rgba8unorm' as const,
33
+ device,
34
+ texture,
35
+ buffer,
36
+ frameNumber: 17,
37
+ deviceGeneration: 3,
38
+ graphGeneration: 9,
39
+ backendId: 'null' as const,
40
+ width: 1,
41
+ height: 1,
42
+ bytesPerRow: 256,
43
+ };
44
+ owner.observationCaptureOwner.register(capture);
45
+ expect(owner.observationCaptureOwner.consume(17)).toEqual([capture]);
46
+
47
+ const pending = owner.readObservationCapture(capture);
48
+ await mapStarted.promise;
49
+ expect(destroyBuffer).not.toHaveBeenCalled();
50
+ expect(owner.stats).toMatchObject({ liveCount: 1, mapCount: 1, readbackCount: 0 });
51
+
52
+ mapCompleted.resolve(
53
+ ok({
54
+ getMappedRange: () => ok(mappedBytes),
55
+ unmap,
56
+ } as unknown as MappedBuffer),
57
+ );
58
+ await expect(pending).resolves.toMatchObject({ ok: true, value: new Uint8Array(256) });
59
+ expect(unmap).toHaveBeenCalledTimes(1);
60
+ expect(destroyBuffer).toHaveBeenCalledTimes(1);
61
+ expect(owner.stats).toMatchObject({ liveCount: 0, mapCount: 1, readbackCount: 1 });
62
+ expect(errors).toEqual([]);
63
+ expect(owner.disposeReceiptObservationCaptures()).toBeUndefined();
64
+ expect(destroyBuffer).toHaveBeenCalledTimes(1);
65
+ });
@@ -22,6 +22,10 @@ const rendererRecoverySource = readFileSync(
22
22
  resolve(import.meta.dirname, '../assembly/recovery/renderer-recover.ts'),
23
23
  'utf8',
24
24
  );
25
+ const recoveryPipelineSource = readFileSync(
26
+ resolve(import.meta.dirname, '../record/recovery-pipeline.ts'),
27
+ 'utf8',
28
+ );
25
29
 
26
30
  describe('recovery candidate preparation contract', () => {
27
31
  it('does not restore the lost device SSR history when publishing a detached candidate', () => {
@@ -145,6 +149,31 @@ describe('recovery candidate preparation contract', () => {
145
149
  expect(resolver).not.toContain('internals.device.limits');
146
150
  });
147
151
 
152
+ it('rehydrates capability variants before copying late-installed material declarations', () => {
153
+ const preparation = rendererRecoverySource.indexOf('await prepareMaterialShaders(');
154
+ const copy = rendererRecoverySource.indexOf(
155
+ 'const activeShaderCatalog = getActiveShaderState().shaderInstance',
156
+ );
157
+ expect(preparation).toBeGreaterThanOrEqual(0);
158
+ expect(copy).toBeGreaterThan(preparation);
159
+ const copyBlock = rendererRecoverySource.slice(copy);
160
+ expect(copyBlock).toContain('candidateShaderCatalog.findMaterialArtifact(identifier).ok');
161
+ expect(copyBlock).toContain('candidateShaderCatalog.installMaterialArtifact(identifier');
162
+ });
163
+
164
+ it('rehydrates Surface dynamic page bytes before candidate GPU preparation', () => {
165
+ const start = recoveryCandidateSource.indexOf('let recoverySurfaceDynamicInput =');
166
+ const end = recoveryCandidateSource.indexOf(
167
+ 'if (candidateGpuDrivenScene.state !== undefined)',
168
+ start,
169
+ );
170
+ const surface = recoveryCandidateSource.slice(start, end);
171
+ expect(surface).toContain('candidateSurfacePage.bytes.set(source.page.bytes)');
172
+ expect(surface.indexOf('candidateSurfacePage.bytes.set(source.page.bytes)')).toBeLessThan(
173
+ surface.indexOf('recoverySurfaceDynamicInput = {'),
174
+ );
175
+ });
176
+
148
177
  it('keeps render-system residency bindings live across publication', () => {
149
178
  const start = rendererSource.indexOf('const renderSystem: RenderSystem = createRenderSystem({');
150
179
  const end = rendererSource.indexOf('errorRegistry: internals.errorRegistry', start);
@@ -155,4 +184,44 @@ describe('recovery candidate preparation contract', () => {
155
184
  expect(assembly).not.toMatch(/\n\s*gpuStore,\n/);
156
185
  expect(assembly).not.toMatch(/\n\s*dynamicTextureStore,\n/);
157
186
  });
187
+
188
+ it('rebuilds candidate shaders from accepted receipts with candidate-bound adapters', () => {
189
+ expect(rendererRecoverySource).toContain('entry.value.receipt === undefined');
190
+ expect(rendererRecoverySource).toContain('{ receipt: entry.value.receipt }');
191
+ expect(rendererRecoverySource).toContain(
192
+ 'getShaderModuleAdapter().createShaderModule(descriptor)',
193
+ );
194
+ expect(rendererRecoverySource).toContain(
195
+ 'getImmediateShaderModuleAdapter().createShaderModule(descriptor)',
196
+ );
197
+ expect(renderSystemSource).toContain('materialArtifacts: frameState.recoveryMaterialArtifacts');
198
+ expect(recoveryCandidateSource).toContain('materialArtifacts.size === 0');
199
+ expect(recoveryCandidateSource).toContain('{ materialArtifacts }');
200
+ expect(recoveryCandidateSource).toContain('domain: range.domain');
201
+ expect(recoveryCandidateSource).not.toContain('recovery-water-domain');
202
+ });
203
+
204
+ it('prepares the submitted forward specialization with the direct Surface ABI', () => {
205
+ expect(recoveryPipelineSource).toContain('readonly dispatch: readonly DispatchEntry[]');
206
+ expect(recoveryPipelineSource).toContain(
207
+ "candidate.tags.LightMode !== 'Forward' || candidate.materialShaderId === undefined",
208
+ );
209
+ expect(recoveryPipelineSource).toContain('forwardProgram?.materialShaderId');
210
+ expect(recoveryPipelineSource).toContain("'surface-direct-cluster-pbr' as const");
211
+ expect(recoveryPipelineSource).toContain("'surface-direct-pbr' as const");
212
+ expect(recoveryPipelineSource).toContain("'immediate',");
213
+ });
214
+
215
+ it('retires the candidate display view before publishing its prepared owners', () => {
216
+ const start = recoveryCandidateSource.indexOf('const publishRecoveryGraphCandidate');
217
+ const publish = recoveryCandidateSource.slice(start);
218
+ const retire = publish.indexOf('candidateDisplayGraph?.retire()');
219
+ const publication = publish.indexOf('Object.assign(frameState, candidate.frameState)');
220
+
221
+ expect(retire).toBeGreaterThanOrEqual(0);
222
+ expect(publication).toBeGreaterThan(retire);
223
+ expect(publish).toContain('candidate.frameState.compiledFrameGraph = null');
224
+ expect(publish).toContain('candidate.frameState.compiledFrameGraphTopologyKey = null');
225
+ expect(publish).toContain('candidate.frameState.perFrameGraph = null');
226
+ });
158
227
  });
@@ -15,6 +15,7 @@ import type {
15
15
  } from '../index';
16
16
 
17
17
  const expectedCodes = [
18
+ 'render-publication-invalid',
18
19
  'auto-exposure-invalid-parameter',
19
20
  'auto-exposure-capability-unavailable',
20
21
  'auto-exposure-stale-generation',
@@ -39,7 +40,9 @@ const expectedCodes = [
39
40
  'dynamic-resolution-invalid-parameter',
40
41
  'dynamic-resolution-requires-taa',
41
42
  'dynamic-resolution-timing-unavailable',
43
+ 'barrel-distortion-invalid-parameter',
42
44
  'motion-blur-invalid-params',
45
+
43
46
  'shadow-invalid-config',
44
47
  'equirect-projection-failed',
45
48
  'standard-light-budget-exceeded',
@@ -87,6 +90,11 @@ const expectedCodes = [
87
90
  'volume-invalid-bounds',
88
91
  'volume-invalid-parameters',
89
92
  'projector-binding-failed',
93
+ 'cloud-layer-invalid-parameter',
94
+ 'cloud-layer-owner-conflict',
95
+ 'cloud-layer-cache-invalid',
96
+ 'cloud-layer-capability-missing',
97
+ 'cloud-layer-resource-failed',
90
98
  ] as const satisfies readonly RenderErrorCode[];
91
99
  type ExpectedCodeUnion = (typeof expectedCodes)[number];
92
100
 
@@ -12,7 +12,17 @@ function gatedFeature(): RenderFeature<{ readonly frame: number }> {
12
12
  identity: 'synthetic.gated',
13
13
  requiredCapabilities: ['compute'],
14
14
  extract: ({ frameNumber }) => ok({ frame: frameNumber }),
15
- plan: () => ok({ resources: [], passes: [] }),
15
+ plan: () =>
16
+ ok({
17
+ resources: [
18
+ {
19
+ kind: 'fullscreen-program' as const,
20
+ name: 'synthetic.gated.program',
21
+ source: 'synthetic',
22
+ },
23
+ ],
24
+ passes: [],
25
+ }),
16
26
  };
17
27
  }
18
28
 
@@ -82,7 +92,10 @@ describe('render feature diagnostics and capability gate', () => {
82
92
  caps: caps(false),
83
93
  });
84
94
 
85
- expect(first.stageEvents).toEqual([]);
95
+ expect(first.stageEvents).toEqual([
96
+ { featureIdentity: 'synthetic.gated', order: 0, stage: 'extract' },
97
+ { featureIdentity: 'synthetic.gated', order: 0, stage: 'plan' },
98
+ ]);
86
99
  expect(first.errors).toHaveLength(1);
87
100
  expect(first.errors[0]?.code).toBe('render-feature-capability-missing');
88
101
  expect(host.diagnostics()[0]?.status).toBe('disabled');
@@ -1,12 +1,15 @@
1
1
  import { describe, expect, it } from 'vitest';
2
+ import { type RenderError, RenderFeatureStageFailedError } from '../errors/render.js';
2
3
  import {
3
4
  PENDING_RENDER_FEATURE_ERROR_REPORT_INTERVAL_FRAMES,
5
+ type PendingRenderFeatureErrorReport,
6
+ reportPendingRenderFeatureErrors,
4
7
  shouldReportPendingRenderFeatureError,
5
8
  } from '../features/error-reporting.js';
6
9
 
7
10
  describe('pending render-feature error reporting', () => {
8
11
  it('reports the first failure, suppresses the hot-loop, and reopens the cooldown', () => {
9
- const lastReportedFrame = new Map<string, number>();
12
+ const lastReportedFrame = new Map<string, PendingRenderFeatureErrorReport>();
10
13
  expect(shouldReportPendingRenderFeatureError(lastReportedFrame, 'feature\0plan', 10)).toBe(
11
14
  true,
12
15
  );
@@ -22,3 +25,20 @@ describe('pending render-feature error reporting', () => {
22
25
  ).toBe(true);
23
26
  });
24
27
  });
28
+
29
+ describe('changed render-feature failures', () => {
30
+ it('reports a new cause immediately without flooding repeats', () => {
31
+ const lastReportedFrame = new Map<string, PendingRenderFeatureErrorReport>();
32
+ const reported: RenderError[] = [];
33
+ const registry = { fire: (error: RenderError) => reported.push(error) };
34
+ const failure = (message: string) =>
35
+ new RenderFeatureStageFailedError('recovery', 0, 'plan', 'next-frame', new Error(message));
36
+ reportPendingRenderFeatureErrors([failure('invalid-format')], 1, lastReportedFrame, registry);
37
+ reportPendingRenderFeatureErrors([failure('invalid-format')], 2, lastReportedFrame, registry);
38
+ reportPendingRenderFeatureErrors([failure('cycle')], 3, lastReportedFrame, registry);
39
+ expect(reported).toHaveLength(2);
40
+ reportPendingRenderFeatureErrors([failure('cycle')], 4, lastReportedFrame, registry);
41
+ expect(reported).toHaveLength(2);
42
+ expect(lastReportedFrame.size).toBe(1);
43
+ });
44
+ });
@@ -23,7 +23,16 @@ function feature(mode: 'healthy' | 'failed'): RenderFeature<{ readonly ready: tr
23
23
  'next-frame',
24
24
  ),
25
25
  )
26
- : ok({ resources: [], passes: [] }),
26
+ : ok({
27
+ resources: [
28
+ {
29
+ kind: 'fullscreen-program' as const,
30
+ name: 'synthetic.diagnostics.program',
31
+ source: 'synthetic',
32
+ },
33
+ ],
34
+ passes: [],
35
+ }),
27
36
  };
28
37
  }
29
38
 
@@ -55,7 +55,17 @@ describe('render feature retry rules', () => {
55
55
  identity: 'synthetic.capability-retry',
56
56
  requiredCapabilities: ['compute'],
57
57
  extract: ({ frameNumber }) => ok({ frame: frameNumber }),
58
- plan: () => ok({ resources: [], passes: [] }),
58
+ plan: () =>
59
+ ok({
60
+ resources: [
61
+ {
62
+ kind: 'fullscreen-program' as const,
63
+ name: 'synthetic.capability-retry.program',
64
+ source: 'synthetic',
65
+ },
66
+ ],
67
+ passes: [],
68
+ }),
59
69
  };
60
70
  const host = createRenderFeatureHost([feature], caps(false)).unwrap();
61
71
 
@@ -65,6 +75,7 @@ describe('render feature retry rules', () => {
65
75
  frameNumber: 1,
66
76
  caps: caps(false),
67
77
  });
78
+ expect(host.diagnostics()[0]?.status).toBe('disabled');
68
79
  expect(
69
80
  runRenderFeatureFrame(host, {
70
81
  worlds: [],
@@ -1,4 +1,4 @@
1
- import { AssetRegistry, HANDLE_CUBE } from '@forgeax/engine-assets-runtime';
1
+ import { AssetRegistry, HANDLE_CUBE, RuntimeMaterialValue } from '@forgeax/engine-assets-runtime';
2
2
  import type { EntityHandle } from '@forgeax/engine-ecs';
3
3
  import { World } from '@forgeax/engine-ecs';
4
4
  import { createRenderReadLease } from '@forgeax/engine-ecs/projection';
@@ -15,7 +15,7 @@ import {
15
15
  Visibility,
16
16
  VisibilityStateValue,
17
17
  } from '../components';
18
- import { InstanceCollectionStore } from '../instances';
18
+ import { InstanceProjectionStore } from '../instances';
19
19
  import { type ExtractedFrame, extractFrames } from '../render-system-extract';
20
20
  import {
21
21
  type PersistentRenderCandidateRequest,
@@ -47,7 +47,7 @@ function material(values: Record<string, unknown>): MaterialAsset {
47
47
  };
48
48
  }
49
49
 
50
- function createWorld(instanceCollections: InstanceCollectionStore, withCamera: boolean) {
50
+ function createWorld(withCamera: boolean) {
51
51
  const world = new World();
52
52
  registerPropagateTransforms(world);
53
53
  const values: Record<string, unknown> = {
@@ -80,7 +80,6 @@ function createWorld(instanceCollections: InstanceCollectionStore, withCamera: b
80
80
  { component: Visibility, data: { state: VisibilityStateValue.visible } },
81
81
  )
82
82
  .unwrap();
83
- const mixedCollection = instanceCollections.create({ transforms: matrices(2, 0) }).unwrap();
84
83
  const mixed = world
85
84
  .spawn(
86
85
  { component: Transform, data: { pos: [0, 0, 0] } },
@@ -88,7 +87,7 @@ function createWorld(instanceCollections: InstanceCollectionStore, withCamera: b
88
87
  { component: Visibility, data: { state: VisibilityStateValue.inherited } },
89
88
  { component: MeshFilter, data: { assetHandle: HANDLE_CUBE } },
90
89
  { component: MeshRenderer, data: { materials: [materialHandle] } },
91
- { component: Instances, data: { collectionId: mixedCollection.collectionId } },
90
+ { component: Instances, data: { transforms: matrices(2, 0) } },
92
91
  )
93
92
  .unwrap();
94
93
  const sharedConsumer = world
@@ -119,7 +118,6 @@ function createWorld(instanceCollections: InstanceCollectionStore, withCamera: b
119
118
  world,
120
119
  values,
121
120
  materialHandle,
122
- mixedCollectionId: mixedCollection.collectionId,
123
121
  mixed,
124
122
  sharedConsumer,
125
123
  hiddenTarget,
@@ -167,10 +165,59 @@ function rowFor(frame: ExtractedFrame, worldId: number, entityKey: EntityHandle)
167
165
  }
168
166
 
169
167
  describe('PersistentRenderScene mixed World updates', () => {
168
+ it('publishes instance inspection only with the accepted Scene and retires removed components', () => {
169
+ const { world, mixed } = createWorld(true);
170
+ const instanceCollections = new InstanceProjectionStore();
171
+ const scene = new PersistentRenderScene({ instanceCollections });
172
+ const lease = createRenderReadLease(world);
173
+ const owner = { cameraOwner: 0, resourceOwner: 0 };
174
+ let rejectCandidate = false;
175
+ const build = (request: PersistentRenderCandidateRequest) => {
176
+ const frame = extractFrames([world], owner, undefined, undefined, undefined, {
177
+ cull: 'none',
178
+ retainHidden: true,
179
+ renderables: request,
180
+ instanceCollections,
181
+ });
182
+ if (rejectCandidate && request !== 'none') throw new Error('reject prepared candidate');
183
+ return frame;
184
+ };
185
+ const draw = () => scene.extractComposition([world], owner, 0, build, [lease]);
186
+ try {
187
+ draw();
188
+ const before = instanceCollections._inspections(0);
189
+ expect(before).toHaveLength(1);
190
+ world.set(mixed, Instances, { transforms: matrices(3, 9) }).unwrap();
191
+ rejectCandidate = true;
192
+ expect(draw).toThrow('reject prepared candidate');
193
+ expect(instanceCollections._inspections(0)).toEqual(before);
194
+ rejectCandidate = false;
195
+ expect(rowFor(draw(), 0, mixed)?.instances?.instanceCount).toBe(3);
196
+ expect(instanceCollections._inspections(0)[0]?.revision).toBe((before[0]?.revision ?? 0) + 1);
197
+ world.removeComponent(mixed, Instances).unwrap();
198
+ draw();
199
+ expect(instanceCollections._inspections(0)).toEqual([]);
200
+ world
201
+ .addComponent(mixed, { component: Instances, data: { transforms: matrices(1, 2) } })
202
+ .unwrap();
203
+ draw();
204
+ expect(instanceCollections._inspections(0)[0]?.collectionId).not.toBe(
205
+ before[0]?.collectionId,
206
+ );
207
+ world.despawn(mixed).unwrap();
208
+ draw();
209
+ expect(instanceCollections._inspections(0)).toEqual([]);
210
+ } finally {
211
+ lease.dispose();
212
+ scene.dispose();
213
+ instanceCollections.dispose();
214
+ }
215
+ });
216
+
170
217
  it('reconciles mixed fields against a fresh extract while retaining other slot history', () => {
171
- const instanceCollections = new InstanceCollectionStore();
172
- const first = createWorld(instanceCollections, true);
173
- const second = createWorld(instanceCollections, false);
218
+ const instanceCollections = new InstanceProjectionStore();
219
+ const first = createWorld(true);
220
+ const second = createWorld(false);
174
221
  const worlds = [first.world, second.world] as const;
175
222
  const leases = [
176
223
  createRenderReadLease(first.world),
@@ -226,15 +273,24 @@ describe('PersistentRenderScene mixed World updates', () => {
226
273
  expect(initialOtherWorld).toBeDefined();
227
274
 
228
275
  requests.length = 0;
229
- instanceCollections.replace(first.mixedCollectionId, matrices(3, 2)).unwrap();
276
+ first.world.set(first.mixed, Instances, { transforms: matrices(3, 2) }).unwrap();
230
277
  first.world.set(first.mixed, Transform, { pos: [0.25, 0, 0] }).unwrap();
231
278
  first.world.set(first.mixed, ChildOf, { parent: first.parentB }).unwrap();
232
279
  first.world.set(first.parentA, Visibility, { state: VisibilityStateValue.hidden }).unwrap();
233
280
  first.world
234
281
  .set(first.hiddenTarget, Visibility, { state: VisibilityStateValue.hidden })
235
282
  .unwrap();
236
- first.values.baseColor = [0, 1, 0, 1];
237
- first.world.sharedRefs.markChanged(first.materialHandle).unwrap();
283
+ first.world
284
+ .spawn({
285
+ component: RuntimeMaterialValue,
286
+ data: {
287
+ asset: first.materialHandle,
288
+ parameter: 'baseColor',
289
+ kind: 2,
290
+ value: [0, 1, 0, 1],
291
+ },
292
+ })
293
+ .unwrap();
238
294
  first.world.update(0).unwrap();
239
295
 
240
296
  const mixed = draw();
@@ -264,7 +320,7 @@ describe('PersistentRenderScene mixed World updates', () => {
264
320
  submit(mixed);
265
321
 
266
322
  requests.length = 0;
267
- instanceCollections.replace(first.mixedCollectionId, matrices(0, 0)).unwrap();
323
+ first.world.set(first.mixed, Instances, { transforms: matrices(0, 0) }).unwrap();
268
324
  first.world.update(0).unwrap();
269
325
  const zero = draw();
270
326
  expect(frameFacts(zero)).toEqual(frameFacts(drawableOracle()));
@@ -274,7 +330,7 @@ describe('PersistentRenderScene mixed World updates', () => {
274
330
  submit(zero);
275
331
 
276
332
  requests.length = 0;
277
- instanceCollections.replace(first.mixedCollectionId, matrices(1, -2)).unwrap();
333
+ first.world.set(first.mixed, Instances, { transforms: matrices(1, -2) }).unwrap();
278
334
  first.world.update(0).unwrap();
279
335
  const one = draw();
280
336
  expect(frameFacts(one)).toEqual(frameFacts(drawableOracle()));
@@ -290,9 +346,9 @@ describe('PersistentRenderScene mixed World updates', () => {
290
346
  });
291
347
 
292
348
  it('retains slot and temporal history across World reorder and catalog refresh', () => {
293
- const instanceCollections = new InstanceCollectionStore();
294
- const first = createWorld(instanceCollections, true);
295
- const second = createWorld(instanceCollections, false);
349
+ const instanceCollections = new InstanceProjectionStore();
350
+ const first = createWorld(true);
351
+ const second = createWorld(false);
296
352
  let worlds: readonly World[] = [first.world, second.world];
297
353
  let leases = [createRenderReadLease(first.world), createRenderReadLease(second.world)];
298
354
  let owner = { cameraOwner: 0, resourceOwner: 0 };
@@ -373,8 +429,8 @@ describe('PersistentRenderScene mixed World updates', () => {
373
429
  });
374
430
 
375
431
  it('scopes parent and leaf visibility changes while preserving unrelated snapshots', () => {
376
- const instanceCollections = new InstanceCollectionStore();
377
- const first = createWorld(instanceCollections, true);
432
+ const instanceCollections = new InstanceProjectionStore();
433
+ const first = createWorld(true);
378
434
  const lease = createRenderReadLease(first.world);
379
435
  const scene = new PersistentRenderScene({ instanceCollections });
380
436
  const assets = new AssetRegistry({
@@ -424,3 +480,73 @@ describe('PersistentRenderScene mixed World updates', () => {
424
480
  }
425
481
  });
426
482
  });
483
+
484
+ it('two Renderers independently observe shared content replacement, rebinding, deletion and retry', () => {
485
+ const fixture = createWorld(true);
486
+ const { world, materialHandle, sharedConsumer } = fixture;
487
+ const owner = { cameraOwner: 0, resourceOwner: 0 };
488
+ const assets = new AssetRegistry({
489
+ findMaterialArtifact: () => ({ ok: false, error: new Error('not registered') }),
490
+ } as unknown as ShaderRegistry);
491
+ const consumers = [0, 1].map(() => {
492
+ const instanceCollections = new InstanceProjectionStore();
493
+ const scene = new PersistentRenderScene({ instanceCollections });
494
+ const lease = createRenderReadLease(world);
495
+ let fail = false;
496
+ const build = (request: PersistentRenderCandidateRequest) => {
497
+ if (fail) {
498
+ fail = false;
499
+ throw new Error('injected extraction failure');
500
+ }
501
+ return extractFrames([world], owner, assets, undefined, scene.materialSnapshotCacheStore(), {
502
+ cull: 'none',
503
+ retainHidden: true,
504
+ renderables: request,
505
+ instanceCollections,
506
+ });
507
+ };
508
+ return {
509
+ scene,
510
+ lease,
511
+ draw: () => scene.extractComposition([world], owner, 0, build, [lease]),
512
+ fail: () => {
513
+ fail = true;
514
+ },
515
+ };
516
+ });
517
+ const color = (frame: ExtractedFrame) =>
518
+ Array.from(rowFor(frame, 0, sharedConsumer)?.material.baseColor ?? []);
519
+ try {
520
+ for (const consumer of consumers) expect(color(consumer.draw())).toEqual([1, 0, 0]);
521
+ const content = world
522
+ .spawn({
523
+ component: RuntimeMaterialValue,
524
+ data: {
525
+ asset: materialHandle,
526
+ parameter: 'baseColor',
527
+ kind: 2,
528
+ value: [0, 1, 0, 1],
529
+ },
530
+ })
531
+ .unwrap();
532
+ for (const consumer of consumers) {
533
+ consumer.fail();
534
+ expect(() => consumer.draw()).toThrow('injected extraction failure');
535
+ expect(color(consumer.draw())).toEqual([0, 1, 0]);
536
+ }
537
+ const other = world.allocSharedRef('MaterialAsset', material({ baseColor: [0, 0, 1, 1] }));
538
+ world.set(content, RuntimeMaterialValue, { asset: other }).unwrap();
539
+ for (const consumer of consumers) expect(color(consumer.draw())).toEqual([1, 0, 0]);
540
+ world
541
+ .set(content, RuntimeMaterialValue, { asset: materialHandle, value: [0, 0, 1, 1] })
542
+ .unwrap();
543
+ for (const consumer of consumers) expect(color(consumer.draw())).toEqual([0, 0, 1]);
544
+ world.despawn(content).unwrap();
545
+ for (const consumer of consumers) expect(color(consumer.draw())).toEqual([1, 0, 0]);
546
+ } finally {
547
+ for (const consumer of consumers) {
548
+ consumer.lease.dispose();
549
+ consumer.scene.dispose();
550
+ }
551
+ }
552
+ });
@@ -1,3 +1,4 @@
1
+ import { RuntimeMeshVertices } from '@forgeax/engine-assets-runtime';
1
2
  import { World } from '@forgeax/engine-ecs';
2
3
  import { createRenderReadLease } from '@forgeax/engine-ecs/projection';
3
4
  import { vec3 } from '@forgeax/engine-math';
@@ -16,7 +17,7 @@ import {
16
17
  PointLightShadow,
17
18
  SpotLight,
18
19
  } from '../components';
19
- import { InstanceCollectionStore } from '../instances';
20
+ import { InstanceProjectionStore } from '../instances';
20
21
  import { makeZeroCameraFallbackSnapshot } from '../record/frame-snapshot';
21
22
  import type {
22
23
  DispatchEntry,
@@ -302,13 +303,12 @@ describe('RenderScene canonical owner', () => {
302
303
 
303
304
  it('classifies an Instances-only collection revision without widening the content update', () => {
304
305
  const world = new World();
305
- const instanceCollections = new InstanceCollectionStore();
306
+ const instanceCollections = new InstanceProjectionStore();
306
307
  const initialTransforms = new Float32Array(16);
307
308
  initialTransforms[0] = 1;
308
309
  initialTransforms[5] = 1;
309
310
  initialTransforms[10] = 1;
310
311
  initialTransforms[15] = 1;
311
- const collection = instanceCollections.create({ transforms: initialTransforms }).unwrap();
312
312
  const entity = world
313
313
  .spawn(
314
314
  {
@@ -317,14 +317,18 @@ describe('RenderScene canonical owner', () => {
317
317
  },
318
318
  { component: MeshFilter, data: { assetHandle: 1 as never } },
319
319
  { component: MeshRenderer, data: { materials: [] } },
320
- { component: Instances, data: { collectionId: collection.collectionId } },
320
+ { component: Instances, data: { transforms: initialTransforms } },
321
321
  )
322
322
  .unwrap();
323
323
  const lease = createRenderReadLease(world);
324
324
  const requests: PersistentRenderCandidateRequest[] = [];
325
325
  const build = (request: PersistentRenderCandidateRequest): ExtractedFrame => {
326
326
  requests.push(request);
327
- const detached = instanceCollections.snapshot(collection.collectionId).unwrap();
327
+ const detached = instanceCollections.project(
328
+ world,
329
+ entity,
330
+ world.get(entity, Instances).unwrap().transforms,
331
+ );
328
332
  return {
329
333
  cameras: [{ ...makeZeroCameraFallbackSnapshot(), worldId: 0, entityKey: 1 }],
330
334
  renderables: [
@@ -349,7 +353,7 @@ describe('RenderScene canonical owner', () => {
349
353
  ]);
350
354
  const nextTransforms = new Float32Array(initialTransforms);
351
355
  nextTransforms[12] = 4;
352
- instanceCollections.replace(collection.collectionId, nextTransforms).unwrap();
356
+ world.set(entity, Instances, { transforms: nextTransforms }).unwrap();
353
357
  persistent.extractComposition([world], { cameraOwner: 0, resourceOwner: 0 }, 0, build, [
354
358
  lease,
355
359
  ]);
@@ -734,8 +738,8 @@ describe('RenderScene canonical owner', () => {
734
738
  const world = new World();
735
739
  const lease = createRenderReadLease(world);
736
740
  const handle = world.allocSharedRef('MeshAsset', { kind: 'mesh' });
737
- const onSharedRefMutation = vi.fn();
738
- const persistent = new PersistentRenderScene({ onSharedRefMutation });
741
+ const onRuntimeAssetChange = vi.fn();
742
+ const persistent = new PersistentRenderScene({ onRuntimeAssetChange });
739
743
  const frame = {
740
744
  cameras: [],
741
745
  renderables: [],
@@ -746,12 +750,12 @@ describe('RenderScene canonical owner', () => {
746
750
  persistent.extractComposition([world], { cameraOwner: 0, resourceOwner: 0 }, 0, () => frame, [
747
751
  lease,
748
752
  ]);
749
- world.sharedRefs.markChanged(handle).unwrap();
753
+ world.spawn({ component: RuntimeMeshVertices, data: { asset: handle, vertices: [] } }).unwrap();
750
754
  persistent.extractComposition([world], { cameraOwner: 0, resourceOwner: 0 }, 0, () => frame, [
751
755
  lease,
752
756
  ]);
753
757
 
754
- expect(onSharedRefMutation).toHaveBeenCalledWith(0, handle);
758
+ expect(onRuntimeAssetChange).toHaveBeenCalledWith(0, handle);
755
759
  lease.dispose();
756
760
  persistent.dispose();
757
761
  });
@@ -974,6 +978,7 @@ describe('RenderScene canonical owner', () => {
974
978
  expect(projected.renderables).toEqual(cameraMoved.renderables);
975
979
  expect(projected.dispatch).toEqual(cameraMoved.dispatch);
976
980
  expect(initial.renderables).toHaveLength(1);
981
+ // Transform spans update poses without a second source-identity scan.
977
982
  expect(persistent.inspect()).toMatchObject({ fullRebuilds: 1, worldEntitiesScanned: 0 });
978
983
  } finally {
979
984
  lease.dispose();
@@ -1373,7 +1378,7 @@ describe('RenderScene', () => {
1373
1378
  } as unknown as ExtractedFrame;
1374
1379
 
1375
1380
  const projected = scene.extractComposition(
1376
- [{} as World],
1381
+ [new World()],
1377
1382
  { cameraOwner: 0, resourceOwner: 0 },
1378
1383
  0,
1379
1384
  () => frame,
@@ -1515,3 +1520,23 @@ describe('projected bounds query', () => {
1515
1520
  scene.dispose();
1516
1521
  });
1517
1522
  });
1523
+
1524
+ it('does not publish an earlier transform when later CPU candidate preparation fails', () => {
1525
+ const scene = new RenderScene();
1526
+ scene.apply([updateSnapshot(snapshot(1, 0)), updateSnapshot(snapshot(2, 0))]);
1527
+ const changed = snapshot(1, 7).transform.world;
1528
+ const malformed = snapshot(2, 9);
1529
+ Object.defineProperty(malformed, 'transform', {
1530
+ get() {
1531
+ throw new Error('candidate copy failure');
1532
+ },
1533
+ });
1534
+ expect(() =>
1535
+ scene.apply([
1536
+ { kind: 'update', worldId: 0, entityKey: 1, world: changed },
1537
+ updateSnapshot(malformed),
1538
+ ]),
1539
+ ).toThrow('candidate copy failure');
1540
+ expect(scene.slot(0, 1)?.snapshot.transform.world[12]).toBe(0);
1541
+ expect(scene.slot(0, 2)?.snapshot.transform.world[12]).toBe(0);
1542
+ });