@aura3d/engine 3.0.0 → 3.0.1

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 (349) hide show
  1. package/README.md +24 -5
  2. package/dist/animation/AnimationMixer.d.ts +4 -1
  3. package/dist/animation/AnimationMixer.d.ts.map +1 -1
  4. package/dist/animation/AnimationMixer.js +36 -29
  5. package/dist/animation/AnimationMixer.js.map +1 -1
  6. package/dist/animation/FootIk.d.ts +9 -0
  7. package/dist/animation/FootIk.d.ts.map +1 -1
  8. package/dist/animation/FootIk.js +22 -8
  9. package/dist/animation/FootIk.js.map +1 -1
  10. package/dist/animation/HumanoidRetargeting.d.ts +6 -2
  11. package/dist/animation/HumanoidRetargeting.d.ts.map +1 -1
  12. package/dist/animation/HumanoidRetargeting.js +18 -8
  13. package/dist/animation/HumanoidRetargeting.js.map +1 -1
  14. package/dist/animation/LocomotionEvidence.d.ts +34 -0
  15. package/dist/animation/LocomotionEvidence.d.ts.map +1 -0
  16. package/dist/animation/LocomotionEvidence.js +48 -0
  17. package/dist/animation/LocomotionEvidence.js.map +1 -0
  18. package/dist/animation/RootMotion.d.ts +13 -4
  19. package/dist/animation/RootMotion.d.ts.map +1 -1
  20. package/dist/animation/RootMotion.js +21 -1
  21. package/dist/animation/RootMotion.js.map +1 -1
  22. package/dist/animation/index.d.ts +1 -0
  23. package/dist/animation/index.d.ts.map +1 -1
  24. package/dist/animation/index.js +1 -0
  25. package/dist/animation/index.js.map +1 -1
  26. package/dist/assets/GLTFAnimationRuntime.d.ts +84 -4
  27. package/dist/assets/GLTFAnimationRuntime.d.ts.map +1 -1
  28. package/dist/assets/GLTFAnimationRuntime.js +308 -23
  29. package/dist/assets/GLTFAnimationRuntime.js.map +1 -1
  30. package/dist/assets/GLTFCompressionDecoders.d.ts +1 -0
  31. package/dist/assets/GLTFCompressionDecoders.d.ts.map +1 -1
  32. package/dist/assets/GLTFCompressionDecoders.js +20 -5
  33. package/dist/assets/GLTFCompressionDecoders.js.map +1 -1
  34. package/dist/assets/GLTFLoader.d.ts.map +1 -1
  35. package/dist/assets/GLTFLoader.js +21 -7
  36. package/dist/assets/GLTFLoader.js.map +1 -1
  37. package/dist/assets/KTX2BasisTextureTranscoder.d.ts.map +1 -1
  38. package/dist/assets/KTX2BasisTextureTranscoder.js +8 -0
  39. package/dist/assets/KTX2BasisTextureTranscoder.js.map +1 -1
  40. package/dist/controls/MapControls.d.ts +1 -1
  41. package/dist/controls/MapControls.d.ts.map +1 -1
  42. package/dist/controls/MapControls.js +3 -3
  43. package/dist/controls/MapControls.js.map +1 -1
  44. package/dist/controls/OrbitControls.d.ts +17 -3
  45. package/dist/controls/OrbitControls.d.ts.map +1 -1
  46. package/dist/controls/OrbitControls.js +24 -7
  47. package/dist/controls/OrbitControls.js.map +1 -1
  48. package/dist/controls/TrackballControls.d.ts.map +1 -1
  49. package/dist/controls/TrackballControls.js +2 -0
  50. package/dist/controls/TrackballControls.js.map +1 -1
  51. package/dist/create-aura3d/index.js +1 -1
  52. package/dist/engine/agent-api/AnimationController.d.ts +5 -1
  53. package/dist/engine/agent-api/AnimationController.d.ts.map +1 -1
  54. package/dist/engine/agent-api/AnimationController.js +5 -0
  55. package/dist/engine/agent-api/AnimationController.js.map +1 -1
  56. package/dist/engine/agent-api/CameraProjection.d.ts +2 -0
  57. package/dist/engine/agent-api/CameraProjection.d.ts.map +1 -0
  58. package/dist/engine/agent-api/CameraProjection.js +2 -0
  59. package/dist/engine/agent-api/CameraProjection.js.map +1 -0
  60. package/dist/engine/agent-api/DeferredFrameResources.d.ts +2 -0
  61. package/dist/engine/agent-api/DeferredFrameResources.d.ts.map +1 -0
  62. package/dist/engine/agent-api/DeferredFrameResources.js +2 -0
  63. package/dist/engine/agent-api/DeferredFrameResources.js.map +1 -0
  64. package/dist/engine/agent-api/FootPlanting.d.ts +6 -0
  65. package/dist/engine/agent-api/FootPlanting.d.ts.map +1 -1
  66. package/dist/engine/agent-api/FootPlanting.js +2 -1
  67. package/dist/engine/agent-api/FootPlanting.js.map +1 -1
  68. package/dist/engine/agent-api/GameCameraRigs.d.ts.map +1 -1
  69. package/dist/engine/agent-api/GameCameraRigs.js +7 -2
  70. package/dist/engine/agent-api/GameCameraRigs.js.map +1 -1
  71. package/dist/engine/agent-api/LabelTelemetry.d.ts +8 -2
  72. package/dist/engine/agent-api/LabelTelemetry.d.ts.map +1 -1
  73. package/dist/engine/agent-api/LabelTelemetry.js +18 -4
  74. package/dist/engine/agent-api/LabelTelemetry.js.map +1 -1
  75. package/dist/engine/agent-api/ModelInstanceMatrices.d.ts +2 -0
  76. package/dist/engine/agent-api/ModelInstanceMatrices.d.ts.map +1 -0
  77. package/dist/engine/agent-api/ModelInstanceMatrices.js +2 -0
  78. package/dist/engine/agent-api/ModelInstanceMatrices.js.map +1 -0
  79. package/dist/engine/agent-api/NavigationCrowds.d.ts +28 -0
  80. package/dist/engine/agent-api/NavigationCrowds.d.ts.map +1 -1
  81. package/dist/engine/agent-api/NavigationCrowds.js +73 -1
  82. package/dist/engine/agent-api/NavigationCrowds.js.map +1 -1
  83. package/dist/engine/agent-api/PngTexturePixels.d.ts +9 -0
  84. package/dist/engine/agent-api/PngTexturePixels.d.ts.map +1 -0
  85. package/dist/engine/agent-api/PngTexturePixels.js +53 -0
  86. package/dist/engine/agent-api/PngTexturePixels.js.map +1 -0
  87. package/dist/engine/agent-api/RootDiagnosticSnapshot.d.ts +2 -0
  88. package/dist/engine/agent-api/RootDiagnosticSnapshot.d.ts.map +1 -0
  89. package/dist/engine/agent-api/RootDiagnosticSnapshot.js +2 -0
  90. package/dist/engine/agent-api/RootDiagnosticSnapshot.js.map +1 -0
  91. package/dist/engine/agent-api/RootFrameClock.d.ts +2 -0
  92. package/dist/engine/agent-api/RootFrameClock.d.ts.map +1 -0
  93. package/dist/engine/agent-api/RootFrameClock.js +2 -0
  94. package/dist/engine/agent-api/RootFrameClock.js.map +1 -0
  95. package/dist/engine/agent-api/RootPerformanceQuality.d.ts +2 -0
  96. package/dist/engine/agent-api/RootPerformanceQuality.d.ts.map +1 -0
  97. package/dist/engine/agent-api/RootPerformanceQuality.js +2 -0
  98. package/dist/engine/agent-api/RootPerformanceQuality.js.map +1 -0
  99. package/dist/engine/agent-api/RootRuntimeSupport.d.ts +61 -0
  100. package/dist/engine/agent-api/RootRuntimeSupport.d.ts.map +1 -0
  101. package/dist/engine/agent-api/RootRuntimeSupport.js +127 -0
  102. package/dist/engine/agent-api/RootRuntimeSupport.js.map +1 -0
  103. package/dist/engine/agent-api/RuntimeNodeHandle.d.ts +22 -0
  104. package/dist/engine/agent-api/RuntimeNodeHandle.d.ts.map +1 -1
  105. package/dist/engine/agent-api/RuntimeNodeHandle.js.map +1 -1
  106. package/dist/engine/agent-api/WorldLabelRenderer.d.ts +13 -2
  107. package/dist/engine/agent-api/WorldLabelRenderer.d.ts.map +1 -1
  108. package/dist/engine/agent-api/WorldLabelRenderer.js +58 -26
  109. package/dist/engine/agent-api/WorldLabelRenderer.js.map +1 -1
  110. package/dist/engine/agent-api/index.d.ts +103 -8
  111. package/dist/engine/agent-api/index.d.ts.map +1 -1
  112. package/dist/engine/agent-api/index.js +750 -242
  113. package/dist/engine/agent-api/index.js.map +1 -1
  114. package/dist/engine/agent-api/product-viewer-runtime.d.ts.map +1 -1
  115. package/dist/engine/production-runtime/RootGpuParticleWorkload.d.ts +34 -0
  116. package/dist/engine/production-runtime/RootGpuParticleWorkload.d.ts.map +1 -0
  117. package/dist/engine/production-runtime/RootGpuParticleWorkload.js +117 -0
  118. package/dist/engine/production-runtime/RootGpuParticleWorkload.js.map +1 -0
  119. package/dist/engine/production-runtime/RootRenderSourceBridge.d.ts +2 -0
  120. package/dist/engine/production-runtime/RootRenderSourceBridge.d.ts.map +1 -0
  121. package/dist/engine/production-runtime/RootRenderSourceBridge.js +2 -0
  122. package/dist/engine/production-runtime/RootRenderSourceBridge.js.map +1 -0
  123. package/dist/engine/production-runtime/TypedGLBActor.d.ts +6 -0
  124. package/dist/engine/production-runtime/TypedGLBActor.d.ts.map +1 -1
  125. package/dist/engine/production-runtime/TypedGLBActor.js +13 -0
  126. package/dist/engine/production-runtime/TypedGLBActor.js.map +1 -1
  127. package/dist/engine/production-runtime/index.d.ts +2 -0
  128. package/dist/engine/production-runtime/index.d.ts.map +1 -1
  129. package/dist/engine/production-runtime/index.js +2 -0
  130. package/dist/engine/production-runtime/index.js.map +1 -1
  131. package/dist/input/Haptics.d.ts +1 -1
  132. package/dist/input/Haptics.js +1 -1
  133. package/dist/input/InputSystem.d.ts.map +1 -1
  134. package/dist/input/InputSystem.js +1 -0
  135. package/dist/input/InputSystem.js.map +1 -1
  136. package/dist/input/PointerDevice.d.ts +1 -0
  137. package/dist/input/PointerDevice.d.ts.map +1 -1
  138. package/dist/input/PointerDevice.js +1 -1
  139. package/dist/input/PointerDevice.js.map +1 -1
  140. package/dist/input/controls/OrbitControls.d.ts +44 -1
  141. package/dist/input/controls/OrbitControls.d.ts.map +1 -1
  142. package/dist/input/controls/OrbitControls.js +151 -12
  143. package/dist/input/controls/OrbitControls.js.map +1 -1
  144. package/dist/lean/base.d.ts +1 -1
  145. package/dist/lean/base.d.ts.map +1 -1
  146. package/dist/lean/base.js +1 -1
  147. package/dist/lean/base.js.map +1 -1
  148. package/dist/lean/index.js +1 -1
  149. package/dist/lean/index.js.map +1 -1
  150. package/dist/navigation-recast/index.d.ts +116 -0
  151. package/dist/navigation-recast/index.d.ts.map +1 -0
  152. package/dist/navigation-recast/index.js +247 -0
  153. package/dist/navigation-recast/index.js.map +1 -0
  154. package/dist/physics-rapier/HeightfieldLayout.d.ts +12 -0
  155. package/dist/physics-rapier/HeightfieldLayout.d.ts.map +1 -0
  156. package/dist/physics-rapier/HeightfieldLayout.js +19 -0
  157. package/dist/physics-rapier/HeightfieldLayout.js.map +1 -0
  158. package/dist/physics-rapier/index.d.ts.map +1 -1
  159. package/dist/physics-rapier/index.js +2 -1
  160. package/dist/physics-rapier/index.js.map +1 -1
  161. package/dist/rendering/CascadedShadowMaps.d.ts.map +1 -1
  162. package/dist/rendering/CascadedShadowMaps.js +3 -1
  163. package/dist/rendering/CascadedShadowMaps.js.map +1 -1
  164. package/dist/rendering/ExtensionScalarAtlas.d.ts +18 -0
  165. package/dist/rendering/ExtensionScalarAtlas.d.ts.map +1 -0
  166. package/dist/rendering/ExtensionScalarAtlas.js +107 -0
  167. package/dist/rendering/ExtensionScalarAtlas.js.map +1 -0
  168. package/dist/rendering/LeanWebGL2Device.d.ts +265 -0
  169. package/dist/rendering/LeanWebGL2Device.d.ts.map +1 -0
  170. package/dist/rendering/LeanWebGL2Device.js +4001 -0
  171. package/dist/rendering/LeanWebGL2Device.js.map +1 -0
  172. package/dist/rendering/MaterialBinding.js +1 -1
  173. package/dist/rendering/MaterialBinding.js.map +1 -1
  174. package/dist/rendering/PlanarReflection.d.ts +2 -2
  175. package/dist/rendering/PlanarReflection.js +3 -3
  176. package/dist/rendering/PlanarReflection.js.map +1 -1
  177. package/dist/rendering/PostProcessPass.d.ts +19 -0
  178. package/dist/rendering/PostProcessPass.d.ts.map +1 -1
  179. package/dist/rendering/PostProcessPass.js +64 -32
  180. package/dist/rendering/PostProcessPass.js.map +1 -1
  181. package/dist/rendering/ProjectionMath.d.ts +3 -0
  182. package/dist/rendering/ProjectionMath.d.ts.map +1 -0
  183. package/dist/rendering/ProjectionMath.js +26 -0
  184. package/dist/rendering/ProjectionMath.js.map +1 -0
  185. package/dist/rendering/ReflectionSurfaces.d.ts +4 -1
  186. package/dist/rendering/ReflectionSurfaces.d.ts.map +1 -1
  187. package/dist/rendering/ReflectionSurfaces.js +10 -1
  188. package/dist/rendering/ReflectionSurfaces.js.map +1 -1
  189. package/dist/rendering/RenderDevice.d.ts +5 -0
  190. package/dist/rendering/RenderDevice.d.ts.map +1 -1
  191. package/dist/rendering/RenderDevice.js.map +1 -1
  192. package/dist/rendering/Renderer.d.ts +9 -0
  193. package/dist/rendering/Renderer.d.ts.map +1 -1
  194. package/dist/rendering/Renderer.js +94 -8
  195. package/dist/rendering/Renderer.js.map +1 -1
  196. package/dist/rendering/RendererPostprocessPlan.d.ts +16 -3
  197. package/dist/rendering/RendererPostprocessPlan.d.ts.map +1 -1
  198. package/dist/rendering/RendererPostprocessPlan.js +16 -2
  199. package/dist/rendering/RendererPostprocessPlan.js.map +1 -1
  200. package/dist/rendering/ScreenSpaceReflectionPass.d.ts +43 -0
  201. package/dist/rendering/ScreenSpaceReflectionPass.d.ts.map +1 -0
  202. package/dist/rendering/ScreenSpaceReflectionPass.js +58 -0
  203. package/dist/rendering/ScreenSpaceReflectionPass.js.map +1 -0
  204. package/dist/rendering/ShaderLibrary.d.ts.map +1 -1
  205. package/dist/rendering/ShaderLibrary.js +168 -197
  206. package/dist/rendering/ShaderLibrary.js.map +1 -1
  207. package/dist/rendering/ShaderLibraryCore.d.ts +3 -0
  208. package/dist/rendering/ShaderLibraryCore.d.ts.map +1 -1
  209. package/dist/rendering/ShaderLibraryCore.js +3 -0
  210. package/dist/rendering/ShaderLibraryCore.js.map +1 -1
  211. package/dist/rendering/TemporalHistory.d.ts +37 -0
  212. package/dist/rendering/TemporalHistory.d.ts.map +1 -0
  213. package/dist/rendering/TemporalHistory.js +135 -0
  214. package/dist/rendering/TemporalHistory.js.map +1 -0
  215. package/dist/rendering/TemporalMath.d.ts +3 -0
  216. package/dist/rendering/TemporalMath.d.ts.map +1 -0
  217. package/dist/rendering/TemporalMath.js +9 -0
  218. package/dist/rendering/TemporalMath.js.map +1 -0
  219. package/dist/rendering/TexturedPBRMaterial.d.ts +3 -0
  220. package/dist/rendering/TexturedPBRMaterial.d.ts.map +1 -1
  221. package/dist/rendering/TexturedPBRMaterial.js +65 -3
  222. package/dist/rendering/TexturedPBRMaterial.js.map +1 -1
  223. package/dist/rendering/WebGL2Device.d.ts +15 -0
  224. package/dist/rendering/WebGL2Device.d.ts.map +1 -1
  225. package/dist/rendering/WebGL2Device.js +328 -90
  226. package/dist/rendering/WebGL2Device.js.map +1 -1
  227. package/dist/rendering/WebGPUDevice.d.ts +16 -5
  228. package/dist/rendering/WebGPUDevice.d.ts.map +1 -1
  229. package/dist/rendering/WebGPUDevice.js +357 -58
  230. package/dist/rendering/WebGPUDevice.js.map +1 -1
  231. package/dist/rendering/WebGPUExtensionAtlas.d.ts +6 -0
  232. package/dist/rendering/WebGPUExtensionAtlas.d.ts.map +1 -0
  233. package/dist/rendering/WebGPUExtensionAtlas.js +225 -0
  234. package/dist/rendering/WebGPUExtensionAtlas.js.map +1 -0
  235. package/dist/rendering/effects/GPUParticleBackend.d.ts +4 -2
  236. package/dist/rendering/effects/GPUParticleBackend.d.ts.map +1 -1
  237. package/dist/rendering/effects/GPUParticleBackend.js +20 -2
  238. package/dist/rendering/effects/GPUParticleBackend.js.map +1 -1
  239. package/dist/rendering/effects/ResidentGPUParticleRenderer.d.ts +158 -0
  240. package/dist/rendering/effects/ResidentGPUParticleRenderer.d.ts.map +1 -0
  241. package/dist/rendering/effects/ResidentGPUParticleRenderer.js +576 -0
  242. package/dist/rendering/effects/ResidentGPUParticleRenderer.js.map +1 -0
  243. package/dist/rendering/extension-scalar-atlas.d.ts +3 -0
  244. package/dist/rendering/extension-scalar-atlas.d.ts.map +1 -0
  245. package/dist/rendering/extension-scalar-atlas.js +2 -0
  246. package/dist/rendering/extension-scalar-atlas.js.map +1 -0
  247. package/dist/rendering/index.d.ts +1 -0
  248. package/dist/rendering/index.d.ts.map +1 -1
  249. package/dist/rendering/index.js +1 -0
  250. package/dist/rendering/index.js.map +1 -1
  251. package/dist/rendering/lean/LeanProductionRenderer.d.ts.map +1 -1
  252. package/dist/rendering/lean/LeanProductionRenderer.js +2 -2
  253. package/dist/rendering/lean/LeanProductionRenderer.js.map +1 -1
  254. package/dist/rendering/lean-core-runtime.d.ts +13 -0
  255. package/dist/rendering/lean-core-runtime.d.ts.map +1 -0
  256. package/dist/rendering/lean-core-runtime.js +10 -0
  257. package/dist/rendering/lean-core-runtime.js.map +1 -0
  258. package/dist/rendering/postprocess/NativeBloomPyramid.d.ts +17 -1
  259. package/dist/rendering/postprocess/NativeBloomPyramid.d.ts.map +1 -1
  260. package/dist/rendering/postprocess/NativeBloomPyramid.js +31 -1
  261. package/dist/rendering/postprocess/NativeBloomPyramid.js.map +1 -1
  262. package/dist/rendering/production-runtime/ProductionRendererTypes.d.ts +1 -0
  263. package/dist/rendering/production-runtime/ProductionRendererTypes.d.ts.map +1 -1
  264. package/dist/rendering/production-runtime/ProductionRendererTypes.js.map +1 -1
  265. package/dist/rendering/production-runtime/ProductionRuntimeRenderer.d.ts +3 -0
  266. package/dist/rendering/production-runtime/ProductionRuntimeRenderer.d.ts.map +1 -1
  267. package/dist/rendering/production-runtime/ProductionRuntimeRenderer.js +8 -1
  268. package/dist/rendering/production-runtime/ProductionRuntimeRenderer.js.map +1 -1
  269. package/dist/rendering/production-runtime/ProductionWebGL2Renderer.d.ts +4 -0
  270. package/dist/rendering/production-runtime/ProductionWebGL2Renderer.d.ts.map +1 -1
  271. package/dist/rendering/production-runtime/ProductionWebGL2Renderer.js +15 -0
  272. package/dist/rendering/production-runtime/ProductionWebGL2Renderer.js.map +1 -1
  273. package/dist/rendering/production-runtime/ProductionWebGPURenderer.d.ts +7 -1
  274. package/dist/rendering/production-runtime/ProductionWebGPURenderer.d.ts.map +1 -1
  275. package/dist/rendering/production-runtime/ProductionWebGPURenderer.js +28 -2
  276. package/dist/rendering/production-runtime/ProductionWebGPURenderer.js.map +1 -1
  277. package/dist/rendering/production-runtime/framegraph/FrameGraph.d.ts +4 -2
  278. package/dist/rendering/production-runtime/framegraph/FrameGraph.d.ts.map +1 -1
  279. package/dist/rendering/production-runtime/framegraph/FrameGraph.js +62 -4
  280. package/dist/rendering/production-runtime/framegraph/FrameGraph.js.map +1 -1
  281. package/dist/rendering/production-runtime/framegraph/NativeFrameGraphBindings.d.ts +32 -0
  282. package/dist/rendering/production-runtime/framegraph/NativeFrameGraphBindings.d.ts.map +1 -0
  283. package/dist/rendering/production-runtime/framegraph/NativeFrameGraphBindings.js +111 -0
  284. package/dist/rendering/production-runtime/framegraph/NativeFrameGraphBindings.js.map +1 -0
  285. package/dist/rendering/production-runtime/framegraph/RenderPass.d.ts +23 -0
  286. package/dist/rendering/production-runtime/framegraph/RenderPass.d.ts.map +1 -1
  287. package/dist/rendering/production-runtime/framegraph/RenderPass.js +87 -1
  288. package/dist/rendering/production-runtime/framegraph/RenderPass.js.map +1 -1
  289. package/dist/rendering/production-runtime/index.d.ts +2 -0
  290. package/dist/rendering/production-runtime/index.d.ts.map +1 -1
  291. package/dist/rendering/production-runtime/index.js +1 -0
  292. package/dist/rendering/production-runtime/index.js.map +1 -1
  293. package/dist/rendering/production-runtime/passes/DepthPrepass.d.ts +4 -12
  294. package/dist/rendering/production-runtime/passes/DepthPrepass.d.ts.map +1 -1
  295. package/dist/rendering/production-runtime/passes/DepthPrepass.js +3 -11
  296. package/dist/rendering/production-runtime/passes/DepthPrepass.js.map +1 -1
  297. package/dist/rendering/production-runtime/passes/FramegraphTopology.js +1 -1
  298. package/dist/rendering/production-runtime/passes/FramegraphTopology.js.map +1 -1
  299. package/dist/rendering/production-runtime/passes/OpaquePass.d.ts +3 -8
  300. package/dist/rendering/production-runtime/passes/OpaquePass.d.ts.map +1 -1
  301. package/dist/rendering/production-runtime/passes/OpaquePass.js +3 -1
  302. package/dist/rendering/production-runtime/passes/OpaquePass.js.map +1 -1
  303. package/dist/rendering/production-runtime/passes/ShadowPass.d.ts +3 -8
  304. package/dist/rendering/production-runtime/passes/ShadowPass.d.ts.map +1 -1
  305. package/dist/rendering/production-runtime/passes/ShadowPass.js +2 -0
  306. package/dist/rendering/production-runtime/passes/ShadowPass.js.map +1 -1
  307. package/dist/rendering/production-runtime/passes/SkyboxPass.d.ts +3 -7
  308. package/dist/rendering/production-runtime/passes/SkyboxPass.d.ts.map +1 -1
  309. package/dist/rendering/production-runtime/passes/SkyboxPass.js +2 -0
  310. package/dist/rendering/production-runtime/passes/SkyboxPass.js.map +1 -1
  311. package/dist/rendering/production-runtime/passes/ToneMappingPass.d.ts +7 -10
  312. package/dist/rendering/production-runtime/passes/ToneMappingPass.d.ts.map +1 -1
  313. package/dist/rendering/production-runtime/passes/ToneMappingPass.js +9 -0
  314. package/dist/rendering/production-runtime/passes/ToneMappingPass.js.map +1 -1
  315. package/dist/rendering/production-runtime/passes/TransparentPass.d.ts +4 -9
  316. package/dist/rendering/production-runtime/passes/TransparentPass.d.ts.map +1 -1
  317. package/dist/rendering/production-runtime/passes/TransparentPass.js +6 -1
  318. package/dist/rendering/production-runtime/passes/TransparentPass.js.map +1 -1
  319. package/dist/rendering/reflection-surfaces.d.ts +5 -0
  320. package/dist/rendering/reflection-surfaces.d.ts.map +1 -0
  321. package/dist/rendering/reflection-surfaces.js +3 -0
  322. package/dist/rendering/reflection-surfaces.js.map +1 -0
  323. package/dist/rendering/webgpu/WebGPUPostShaders.d.ts +2 -3
  324. package/dist/rendering/webgpu/WebGPUPostShaders.d.ts.map +1 -1
  325. package/dist/rendering/webgpu/WebGPUPostShaders.js +2 -3
  326. package/dist/rendering/webgpu/WebGPUPostShaders.js.map +1 -1
  327. package/dist/rendering/webgpu/WebGPUTemporal.d.ts +17 -0
  328. package/dist/rendering/webgpu/WebGPUTemporal.d.ts.map +1 -0
  329. package/dist/rendering/webgpu/WebGPUTemporal.js +64 -0
  330. package/dist/rendering/webgpu/WebGPUTemporal.js.map +1 -0
  331. package/dist/rendering/webgpu.d.ts +6 -0
  332. package/dist/rendering/webgpu.d.ts.map +1 -1
  333. package/dist/rendering/webgpu.js +4 -0
  334. package/dist/rendering/webgpu.js.map +1 -1
  335. package/package.json +37 -24
  336. package/templates/cinematic-scene/package.json +2 -2
  337. package/templates/cinematic-scene/src/main.ts +14 -2
  338. package/templates/mini-game/package.json +2 -2
  339. package/templates/product-viewer/package.json +2 -2
  340. package/templates/production-asset-inspector/package.json +1 -1
  341. package/templates/production-material-studio/package.json +1 -1
  342. package/templates/production-product-configurator/package.json +1 -1
  343. package/templates/production-product-viewer/package.json +1 -1
  344. package/templates/cinematic-scene/node_modules/.vite/deps/_metadata.json +0 -8
  345. package/templates/cinematic-scene/node_modules/.vite/deps/package.json +0 -3
  346. package/templates/cinematic-scene/test-results/.last-run.json +0 -6
  347. package/templates/cinematic-scene/test-results/route-health-Aura3D-cinematic-scene-reaches-ready-state/error-context.md +0 -64
  348. package/templates/product-viewer/node_modules/.vite/deps/_metadata.json +0 -8
  349. package/templates/product-viewer/node_modules/.vite/deps/package.json +0 -3
@@ -1,3 +1,4 @@
1
+ import { applyRootParticleQuality, composeModelInstanceMatrices, createCameraProjection, DeferredFrameResources, getRootPerformanceBaseSize, getRootPerformanceQuality, getRootRenderSource, hasRootRenderableContent, includeRootSourceMetadata, initializeRootPerformanceQuality, readRootDiagnosticSnapshot, resolveCameraClipping, resolveRootRenderTime, setRootPerformanceQuality, supportsRootParticleQuality, validateRootPerformanceQuality } from "./RootRuntimeSupport.js";
1
2
  /*
2
3
  * WS-2.2 — module-scope physics values come from the SOLVERLESS entry.
3
4
  *
@@ -24,9 +25,9 @@ import { PhysicsDebugDraw, PhysicsStepper, ScenePhysicsBridge, Shape as PhysicsS
24
25
  import { PhysicsWorld } from "../../physics/world.js";
25
26
  import { createBeamDescriptor, createDayNightSky, createDualProbeEnvironmentLightingResources, createExternalParityEnvironmentLighting, createProductionEnvironmentLightingResources, createProductionPbrHdrPipelineFromRadiance, createSdfFontAtlas, createSdfTextQuadMesh, createSpotShadowProjection, createWaterSurface, createWeatherState, describeSdfTextPixelBacking, describeWetMaterial, layoutSdfText, rasterizeSdfTextLabelImage, resolveFlipbookUv, resolveSdfTextFrameOpacity, resolveVolumetricFog, selectSpotShadowAtlasTier, sampleOceanFixture, Geometry, IndexBuffer, InstancedPBRMaterial, PBRMaterial, ProductionRuntimeRenderer, resolveSamplerAnisotropy, Sampler, srgbToLinearChannel, Texture, TexturedPBRMaterial, VertexBuffer, VertexFormat } from "../../rendering/index.js";
26
27
  import { DirectionalLight, PointLight, SpotLight } from "../../scene/index.js";
27
- import { lookAtMat4, multiplyMat4, orthographicMat4, perspectiveMat4 } from "../../scene/MathTypes.js";
28
+ import { identityMat4, lookAtMat4, multiplyMat4 } from "../../scene/MathTypes.js";
28
29
  import { AURA_NORMALIZED_MODEL_MAX_DIMENSION, boundsFromAsset, boundsHeight, boundsMaxDimension, boundsSize } from "./SceneGroundingUtils.js";
29
- import { collectLabelTelemetry, summarizeTextBuckets } from "./LabelTelemetry.js";
30
+ import { collectLabelTelemetry, labelTelemetryRoleFor, summarizeTextBuckets } from "./LabelTelemetry.js";
30
31
  import { createWorldLabelLayer } from "./WorldLabelRenderer.js";
31
32
  import { createAuraText3DGeometry, defineAuraCustomGeometry, selectAuraRootLodLevel } from "./RootGeometry.js";
32
33
  export * from "./RootGeometry.js";
@@ -1193,7 +1194,8 @@ export const lights = {
1193
1194
  name: options.name,
1194
1195
  position: options.position ?? [3, 4, 3],
1195
1196
  intensity: options.intensity ?? 1.5,
1196
- color: options.color ?? "#ffffff"
1197
+ color: options.color ?? "#ffffff",
1198
+ shadow: options.shadow
1197
1199
  }),
1198
1200
  point: (options = {}) => new AuraNodeBuilder({
1199
1201
  kind: "light",
@@ -1269,6 +1271,7 @@ export const lights = {
1269
1271
  export const camera = {
1270
1272
  perspective: (options = {}) => ({
1271
1273
  mode: "perspective",
1274
+ ...resolveCameraClipping(options),
1272
1275
  position: options.position ?? [0, 1.4, 4],
1273
1276
  target: options.target ?? [0, 0.8, 0],
1274
1277
  fov: options.fov ?? 45
@@ -1278,6 +1281,7 @@ export const camera = {
1278
1281
  const target = options.target ?? [0, 0.8, 0];
1279
1282
  return {
1280
1283
  mode: "orbit",
1284
+ ...resolveCameraClipping(options),
1281
1285
  distance,
1282
1286
  target,
1283
1287
  position: options.position ?? [
@@ -1290,6 +1294,7 @@ export const camera = {
1290
1294
  },
1291
1295
  dolly: (options) => ({
1292
1296
  mode: "dolly",
1297
+ ...resolveCameraClipping(options),
1293
1298
  from: options.from,
1294
1299
  to: options.to,
1295
1300
  target: options.target ?? [0, 0.8, 0],
@@ -1299,6 +1304,7 @@ export const camera = {
1299
1304
  }),
1300
1305
  follow: (options) => ({
1301
1306
  mode: "follow",
1307
+ ...resolveCameraClipping(options),
1302
1308
  targetNode: options.targetNode,
1303
1309
  distance: options.distance ?? 5,
1304
1310
  position: options.position,
@@ -1314,6 +1320,7 @@ export const camera = {
1314
1320
  }),
1315
1321
  path: (options) => ({
1316
1322
  mode: "path",
1323
+ ...resolveCameraClipping(options),
1317
1324
  from: options.from,
1318
1325
  to: options.to,
1319
1326
  target: options.target ?? [0, 0.8, 0],
@@ -1324,6 +1331,7 @@ export const camera = {
1324
1331
  }),
1325
1332
  flythrough: (options = {}) => ({
1326
1333
  mode: "flythrough",
1334
+ ...resolveCameraClipping(options),
1327
1335
  from: options.from ?? [0, 0.36, 1.6],
1328
1336
  to: options.to ?? [0, 0.36, -4.4],
1329
1337
  target: options.target ?? [0, 0.28, -5.8],
@@ -1344,6 +1352,7 @@ export const camera = {
1344
1352
  */
1345
1353
  orthographic: (options = {}) => ({
1346
1354
  mode: "orthographic",
1355
+ ...resolveCameraClipping(options),
1347
1356
  position: options.position ?? [0, 1.4, 4],
1348
1357
  target: options.target ?? [0, 0.8, 0],
1349
1358
  orthographicSize: options.orthographicSize ?? 1.4
@@ -1366,6 +1375,7 @@ export const camera = {
1366
1375
  const horizontal = Math.cos(elevation) * distance;
1367
1376
  return {
1368
1377
  mode: "isometric",
1378
+ ...resolveCameraClipping(options),
1369
1379
  target,
1370
1380
  distance,
1371
1381
  orthographicSize: options.orthographicSize ?? 6,
@@ -1497,7 +1507,10 @@ export const effects = {
1497
1507
  radius: options.radius ?? 0.42,
1498
1508
  threshold: options.threshold ?? 0.72,
1499
1509
  antiBlowout: options.antiBlowout ?? true,
1500
- maxIntensity: options.maxIntensity ?? 0.92
1510
+ maxIntensity: options.maxIntensity ?? 0.92,
1511
+ ...(options.quality !== undefined ? { quality: options.quality } : {}),
1512
+ ...(options.softKnee !== undefined ? { softKnee: options.softKnee } : {}),
1513
+ ...(options.shoulder !== undefined ? { shoulder: options.shoulder } : {})
1501
1514
  }),
1502
1515
  neonBloom: (options = {}) => effects.bloom({
1503
1516
  intensity: options.intensity ?? 0.72,
@@ -1505,7 +1518,10 @@ export const effects = {
1505
1518
  radius: options.radius ?? 0.48,
1506
1519
  threshold: options.threshold ?? 0.68,
1507
1520
  antiBlowout: options.antiBlowout ?? true,
1508
- maxIntensity: options.maxIntensity ?? 0.92
1521
+ maxIntensity: options.maxIntensity ?? 0.92,
1522
+ ...(options.quality !== undefined ? { quality: options.quality } : {}),
1523
+ ...(options.softKnee !== undefined ? { softKnee: options.softKnee } : {}),
1524
+ ...(options.shoulder !== undefined ? { shoulder: options.shoulder } : {})
1509
1525
  }),
1510
1526
  /**
1511
1527
  * A5 volumetric fog (muse3jsparity-PRD): builds a DISTINCT "volumetric-fog"
@@ -1566,8 +1582,8 @@ export const effects = {
1566
1582
  }),
1567
1583
  /**
1568
1584
  * Root anti-alias node (muse3jsparity-PRD A3). `fxaa` executes natively;
1569
- * `off` submits nothing; `taa` is recorded but withheld (no history binding
1570
- * at root) with an explicit diagnostic warning.
1585
+ * `off` submits nothing; `taa` uses renderer-owned velocity/history for opaque
1586
+ * rigid triangles. Unsupported deforming/transparent geometry emits a named warning.
1571
1587
  */
1572
1588
  antiAlias: (options = {}) => new AuraNodeBuilder({
1573
1589
  kind: "effect",
@@ -1614,8 +1630,8 @@ export const effects = {
1614
1630
  intensity: options.intensity ?? 1
1615
1631
  }),
1616
1632
  /**
1617
- * Root motion-blur node (muse3jsparity-PRD A3): recorded but withheld (no
1618
- * velocity binding at root) with an explicit diagnostic warning.
1633
+ * Root motion-blur node: renderer-owned GPU velocity for opaque rigid geometry.
1634
+ * Unsupported deforming/transparent/instanced geometry emits a named warning.
1619
1635
  */
1620
1636
  motionBlur: (options = {}) => new AuraNodeBuilder({
1621
1637
  kind: "effect",
@@ -2313,9 +2329,8 @@ function createRendererDiagnosticReport(snapshot, runtime, rendererOptions) {
2313
2329
  const contactShadows = names.filter((name) => name.includes("contact shadow") || name.includes("footprint") || name.includes("glow pool")).length;
2314
2330
  const ambientOcclusion = flattened.some((node) => node.kind === "effect" && node.effect === "ambient-occlusion");
2315
2331
  const contactOcclusion = flattened.some((node) => node.kind === "effect" && node.effect === "contact-occlusion") || contactShadows > 0;
2316
- // muse3jsparity-PRD A3: newly requestable root passes. motion-blur and
2317
- // taa-mode anti-alias are recorded but withheld (no velocity/history binding
2318
- // at root) — requestedPasses names the intent, actualPasses the execution.
2332
+ // Authored temporal intent is separate from device-observed execution.
2333
+ // Missing submissions remain visible without claiming an unmounted scene rendered.
2319
2334
  const colorGradeNode = flattened.find((node) => node.kind === "effect" && node.effect === "color-grade");
2320
2335
  const antiAliasNode = flattened.find((node) => node.kind === "effect" && node.effect === "anti-alias");
2321
2336
  const outlineNode = flattened.find((node) => node.kind === "effect" && node.effect === "outline");
@@ -2326,13 +2341,14 @@ function createRendererDiagnosticReport(snapshot, runtime, rendererOptions) {
2326
2341
  const beamNode = flattened.find((node) => node.kind === "effect" && node.effect === "light-beam");
2327
2342
  const sdfTextRequested = flattened.some((node) => node.kind === "primitive" && node.text3D?.backend === "sdf");
2328
2343
  const fxaaRequested = (antiAliasNode?.mode ?? "fxaa") === "fxaa" && Boolean(antiAliasNode);
2329
- const taaWithheld = (antiAliasNode?.mode ?? "fxaa") === "taa";
2330
- const motionBlurWithheld = Boolean(motionBlurNode);
2344
+ const taaRequested = (antiAliasNode?.mode ?? "fxaa") === "taa";
2345
+ const taaWithheld = taaRequested && Boolean(runtime?.mounted) && !(runtime?.postprocess?.actualPasses ?? []).includes("taa");
2346
+ const motionBlurWithheld = Boolean(motionBlurNode) && Boolean(runtime?.mounted) && !(runtime?.postprocess?.actualPasses ?? []).includes("motion-blur");
2331
2347
  const runtimePostprocess = runtime?.postprocess;
2332
2348
  const runtimePasses = runtimePostprocess?.actualPasses ?? [];
2333
2349
  const postprocessRequested = Boolean(bloom) || ambientOcclusion || contactOcclusion
2334
2350
  || Boolean(colorGradeNode) || Boolean(antiAliasNode) || Boolean(outlineNode)
2335
- || Boolean(ssrNode) || Boolean(dofNode) || motionBlurWithheld || runtimePasses.length > 0;
2351
+ || Boolean(ssrNode) || Boolean(dofNode) || Boolean(motionBlurNode) || runtimePasses.length > 0;
2336
2352
  const requestedPasses = runtimePasses.length > 0
2337
2353
  ? runtimePasses
2338
2354
  : requestedRendererPostProcessPasses(Boolean(bloom), ambientOcclusion, contactOcclusion, {
@@ -2341,8 +2357,8 @@ function createRendererDiagnosticReport(snapshot, runtime, rendererOptions) {
2341
2357
  outline: Boolean(outlineNode),
2342
2358
  ssr: Boolean(ssrNode),
2343
2359
  depthOfField: Boolean(dofNode),
2344
- motionBlurWithheld,
2345
- taaWithheld
2360
+ motionBlurWithheld: Boolean(motionBlurNode),
2361
+ taaWithheld: taaRequested
2346
2362
  });
2347
2363
  const runtimeStatus = !postprocessRequested
2348
2364
  ? "disabled"
@@ -2359,7 +2375,7 @@ function createRendererDiagnosticReport(snapshot, runtime, rendererOptions) {
2359
2375
  if (bloom && (bloom.intensity ?? 0) > 0.95 && bloom.antiBlowout !== true)
2360
2376
  warnings.push("bloom is high without anti-blowout safeguards");
2361
2377
  if (motionBlurWithheld)
2362
- warnings.push("motion-blur is recorded but withheld: root has no velocity binding, so no motion-blur pass is submitted");
2378
+ warnings.push("TEMPORAL_INPUTS_UNAVAILABLE: motion-blur has no native submission");
2363
2379
  if (flipbookNode)
2364
2380
  warnings.push("flipbook-sprite is recorded but withheld: root has no native sprite-sheet sampler yet, so no flipbook pass is submitted");
2365
2381
  if (beamNode)
@@ -2373,7 +2389,7 @@ function createRendererDiagnosticReport(snapshot, runtime, rendererOptions) {
2373
2389
  : "text3D sdf backend is recorded but unmounted: the production bridge samples the SDF atlas at mount, so textPixelBacked stays false until render");
2374
2390
  }
2375
2391
  if (taaWithheld)
2376
- warnings.push("anti-alias mode \"taa\" is recorded but withheld: root has no history binding, so no taa pass is submitted");
2392
+ warnings.push("TEMPORAL_INPUTS_UNAVAILABLE: taa has no native submission");
2377
2393
  if (colorGradeNode && (colorGradeNode.exposure ?? 1) !== 1)
2378
2394
  warnings.push("color-grade exposure is recorded but has no native grade target yet; contrast/saturation execute");
2379
2395
  if (colorGradeNode && (colorGradeNode.shadows !== undefined || colorGradeNode.highlights !== undefined))
@@ -2444,6 +2460,7 @@ function createRendererDiagnosticReport(snapshot, runtime, rendererOptions) {
2444
2460
  label: runtime?.shadow?.label,
2445
2461
  nativeShadowMapBindings: runtime?.shadow?.nativeShadowMapBindings ?? 0,
2446
2462
  shadowRenderTargetsAllocated: runtime?.shadow?.shadowRenderTargetsAllocated ?? 0,
2463
+ observed: runtime?.mounted ? runtime.shadow?.observed ?? null : null,
2447
2464
  contactShadows,
2448
2465
  mapType: "pcf-soft",
2449
2466
  ...(runtime?.shadow?.spot === undefined ? {} : { spot: runtime.shadow.spot })
@@ -2489,6 +2506,8 @@ function createRendererDiagnosticReport(snapshot, runtime, rendererOptions) {
2489
2506
  passNames: runtimePostprocess?.actualPasses ?? [],
2490
2507
  warnings: runtime?.warnings ?? [],
2491
2508
  nativeInstancedSubmissions: runtime?.deviceDiagnostics?.nativeInstancedSubmissions ?? 0,
2509
+ nativeTemporalPasses: runtime?.deviceDiagnostics?.nativeTemporalPasses ?? 0,
2510
+ nativeTemporalBindings: runtime?.deviceDiagnostics?.nativeTemporalBindings ?? 0,
2492
2511
  submittedObjects: runtime?.deviceDiagnostics?.submittedObjects ?? 0,
2493
2512
  visibleObjects: runtime?.deviceDiagnostics?.visibleObjects ?? 0,
2494
2513
  culledObjects: runtime?.deviceDiagnostics?.culledObjects ?? 0,
@@ -2555,9 +2574,9 @@ function requestedRendererPostProcessPasses(hasBloom, ambientOcclusion, contactO
2555
2574
  // Withheld intents stay visible in the request list with an explicit suffix
2556
2575
  // so requested-vs-actual never silently agrees on an unexecuted pass.
2557
2576
  if (extra.motionBlurWithheld)
2558
- passes.push("motion-blur (withheld: no velocity binding)");
2577
+ passes.push("motion-blur");
2559
2578
  if (extra.taaWithheld)
2560
- passes.push("taa (withheld: no history binding)");
2579
+ passes.push("taa");
2561
2580
  passes.push("output");
2562
2581
  return passes;
2563
2582
  }
@@ -2618,6 +2637,7 @@ export class AuraSceneBuilder {
2618
2637
  return this;
2619
2638
  }
2620
2639
  camera(next) {
2640
+ resolveCameraClipping(next);
2621
2641
  this.cameraSpec = next;
2622
2642
  return this;
2623
2643
  }
@@ -6976,6 +6996,9 @@ export function createRuntimeNodeImportedAssetEvidence(input) {
6976
6996
  ...(input.lastFootPlantingTargetError !== undefined ? { lastFootPlantingTargetError: input.lastFootPlantingTargetError } : {}),
6977
6997
  ...(input.lastFootPlantingHipOffset !== undefined ? { lastFootPlantingHipOffset: input.lastFootPlantingHipOffset } : {}),
6978
6998
  ...(input.lastFootPlantingMissingLegs !== undefined ? { lastFootPlantingMissingLegs: [...input.lastFootPlantingMissingLegs] } : {}),
6999
+ ...(input.lastFootPlantingDeformation !== undefined ? { lastFootPlantingDeformation: input.lastFootPlantingDeformation.map(leg => ({ ...leg })) } : {}),
7000
+ ...(input.lastFootPlantingSurfaces !== undefined ? { lastFootPlantingSurfaces: input.lastFootPlantingSurfaces.map(point => ({ ...point, worldPosition: [...point.worldPosition] })) } : {}),
7001
+ ...(input.lastFootPlantingFeet !== undefined ? { lastFootPlantingFeet: input.lastFootPlantingFeet.map(foot => ({ ...foot, worldPosition: [...foot.worldPosition] })) } : {}),
6979
7002
  ...(input.footPlantingConfigured !== undefined ? { footPlantingConfigured: input.footPlantingConfigured } : {}),
6980
7003
  diagnostics
6981
7004
  };
@@ -7253,11 +7276,26 @@ export function createAuraApp(target, options) {
7253
7276
  const canvas = resolveCanvas(target);
7254
7277
  if (canvas) {
7255
7278
  configureCanvas(canvas, options.pixelRatio ?? rendererSelection.profile.pixelRatio ?? devicePixelRatioSafe(), options.resize ?? true);
7279
+ initializeRootPerformanceQuality(canvas);
7280
+ // Apply a caller's device/capture budget before the production controller
7281
+ // derives its first backing size, LODs, particle pool and shadow target.
7282
+ // Runtime mutations remain guarded by app.setPerformanceQuality().
7283
+ if (options.performanceQuality)
7284
+ setRootPerformanceQuality(canvas, options.performanceQuality);
7256
7285
  }
7257
7286
  const overlay = canvas && shouldRenderOverlay(options.diagnostics, snapshot) ? createDiagnosticsOverlay(canvas, diagnosticsState) : undefined;
7258
7287
  let disposed = false;
7259
7288
  let animationHandle = 0;
7260
7289
  let productionController;
7290
+ let pendingAsyncSteps = 0;
7291
+ let asyncSteps = Promise.resolve();
7292
+ const assertMutableFrame = () => {
7293
+ if (disposed)
7294
+ throw new Error("Aura3D app is disposed.");
7295
+ if (pendingAsyncSteps > 0 || productionController?.busy?.()) {
7296
+ throw new Error("Aura3D frame submission is pending; await stepAsync() before synchronous mutation.");
7297
+ }
7298
+ };
7261
7299
  /** WS-2.9: true from the moment a WebGL mount starts until the controller arrives or fails. */
7262
7300
  let productionMountPending = false;
7263
7301
  /**
@@ -7278,6 +7316,7 @@ export function createAuraApp(target, options) {
7278
7316
  * any time and on any scene.
7279
7317
  */
7280
7318
  let productionMountSettled = Promise.resolve();
7319
+ let productionMountTask = Promise.resolve();
7281
7320
  /** Lets `dispose()` settle an in-flight mount; see the note there. */
7282
7321
  let settleMountForDispose = () => undefined;
7283
7322
  /*
@@ -7399,6 +7438,8 @@ export function createAuraApp(target, options) {
7399
7438
  let runtimeAlpha = 0;
7400
7439
  const ownedInputControllers = new Set();
7401
7440
  const runRuntimeFrame = (dt, source) => {
7441
+ if (disposed)
7442
+ return;
7402
7443
  runtimeFrame += 1;
7403
7444
  runtimeTime += dt;
7404
7445
  runtimeAlpha = runtimeFixedDt > 0 ? Math.max(0, Math.min(1, (dt % runtimeFixedDt) / runtimeFixedDt)) : 0;
@@ -7416,7 +7457,7 @@ export function createAuraApp(target, options) {
7416
7457
  for (const callback of [...frameCallbacks])
7417
7458
  callback(frame);
7418
7459
  };
7419
- const shouldUseProductionRendererForCurrentScene = () => Boolean(canvas && renderSnapshot.nodes.some(isWebGLRenderableNode) && typeof window !== "undefined");
7460
+ const shouldUseProductionRendererForCurrentScene = () => Boolean(canvas && hasRootRenderableContent(canvas, renderSnapshot.nodes.some(isWebGLRenderableNode)) && typeof window !== "undefined");
7420
7461
  const resetDiagnosticsForCurrentScene = (backend) => {
7421
7462
  const fresh = createInitialDiagnostics(renderSnapshot, options.renderer);
7422
7463
  diagnosticsState.backend = backend;
@@ -7481,7 +7522,7 @@ export function createAuraApp(target, options) {
7481
7522
  * So the two cases are separated. A renderable scene with no usable canvas now raises a diagnosable
7482
7523
  * error naming the cause, instead of drawing something plausible.
7483
7524
  */
7484
- const declaresRenderableContent = renderSnapshot.nodes.some(isWebGLRenderableNode);
7525
+ const declaresRenderableContent = hasRootRenderableContent(canvas, renderSnapshot.nodes.some(isWebGLRenderableNode));
7485
7526
  /*
7486
7527
  * Scoped to the case that is actually a lie: a canvas WAS supplied, so the caller is looking at
7487
7528
  * pixels, and the scene has renderable content — but WebGL declined it, so those pixels would be a
@@ -7520,7 +7561,7 @@ export function createAuraApp(target, options) {
7520
7561
  : Promise.resolve();
7521
7562
  settleMountForDispose = settleMount;
7522
7563
  if (shouldUseProductionRenderer && canvas) {
7523
- void startProductionRender(canvas, renderSnapshot, diagnosticsState, options, overlay, runRuntimeFrame, () => runtimePaused, runtimeNodes,
7564
+ productionMountTask = startProductionRender(canvas, renderSnapshot, diagnosticsState, options, overlay, runRuntimeFrame, () => runtimePaused, runtimeNodes,
7524
7565
  // A route that registered a frame callback, or that has bodies to simulate, needs
7525
7566
  // frames regardless of what the scene declares.
7526
7567
  /*
@@ -7534,7 +7575,6 @@ export function createAuraApp(target, options) {
7534
7575
  () => runtimeTime * 1000)
7535
7576
  .then((controller) => {
7536
7577
  if (disposed || revision !== mountRevision) {
7537
- productionMountPending = false;
7538
7578
  settleMount();
7539
7579
  controller.dispose();
7540
7580
  return;
@@ -7546,6 +7586,7 @@ export function createAuraApp(target, options) {
7546
7586
  markRouteReady(snapshot, diagnosticsState);
7547
7587
  })
7548
7588
  .catch((error) => {
7589
+ settleMount();
7549
7590
  if (disposed || revision !== mountRevision)
7550
7591
  return;
7551
7592
  productionMountPending = false;
@@ -7570,7 +7611,18 @@ export function createAuraApp(target, options) {
7570
7611
  get backend() {
7571
7612
  return diagnosticsState.backend;
7572
7613
  },
7614
+ setPerformanceQuality(settings) {
7615
+ assertMutableFrame();
7616
+ const quality = validateRootPerformanceQuality(settings);
7617
+ if (!canvas || !productionController?.setPerformanceQuality)
7618
+ throw new Error("PERFORMANCE_QUALITY_UNSUPPORTED: await a production renderer mount before changing quality.");
7619
+ if (quality.particleScale !== 1 && !supportsRootParticleQuality(canvas))
7620
+ throw new Error("PERFORMANCE_PARTICLE_OWNER_UNAVAILABLE: this root workload has no adaptive native particle owner.");
7621
+ applyRootParticleQuality(canvas, quality.particleScale);
7622
+ productionController.setPerformanceQuality(quality);
7623
+ },
7573
7624
  setScene(nextScene) {
7625
+ assertMutableFrame();
7574
7626
  snapshot = normalizeSceneSnapshot(nextScene);
7575
7627
  renderSnapshot = flattenSceneSnapshot(snapshot);
7576
7628
  runtimeNodes.reset(renderSnapshot);
@@ -7617,6 +7669,10 @@ export function createAuraApp(target, options) {
7617
7669
  },
7618
7670
  pause() {
7619
7671
  runtimePaused = true;
7672
+ // Stop the production requestAnimationFrame owner as well as simulation.
7673
+ // Holding the clock alone still submitted the same expensive frame forever.
7674
+ productionController?.pause?.();
7675
+ productionController?.resetTemporalHistory?.("pause");
7620
7676
  },
7621
7677
  resetRuntimeClock() {
7622
7678
  /*
@@ -7639,6 +7695,8 @@ export function createAuraApp(target, options) {
7639
7695
  },
7640
7696
  resume() {
7641
7697
  runtimePaused = false;
7698
+ productionController?.resetTemporalHistory?.("resume");
7699
+ productionController?.resume?.();
7642
7700
  if (!animationHandle && !productionController && options.autoStart !== false && typeof requestAnimationFrame !== "undefined") {
7643
7701
  animationHandle = requestAnimationFrame(render);
7644
7702
  }
@@ -7667,11 +7725,17 @@ export function createAuraApp(target, options) {
7667
7725
  return productionController?.deviceLost?.() ?? false;
7668
7726
  },
7669
7727
  advance(dt = 1 / 60) {
7728
+ assertMutableFrame();
7670
7729
  const seconds = Math.max(0, dt);
7671
7730
  runRuntimeFrame(seconds, "manual");
7672
7731
  canvasRuntimePhysics?.step(seconds);
7732
+ // Imported GLB animation, consumed root motion and foot planting live in the
7733
+ // production actor bridge. They are simulation state, so `advance()` must
7734
+ // update them even when no GPU frame is presented.
7735
+ productionController?.update?.(runtimeTime * 1000);
7673
7736
  },
7674
7737
  step(dt = 1 / 60) {
7738
+ assertMutableFrame();
7675
7739
  const seconds = Math.max(0, dt);
7676
7740
  app.advance(seconds);
7677
7741
  const previousPaused = runtimePaused;
@@ -7731,7 +7795,38 @@ export function createAuraApp(target, options) {
7731
7795
  }
7732
7796
  runtimePaused = previousPaused;
7733
7797
  },
7798
+ stepAsync(dt = 1 / 60) {
7799
+ if (!Number.isFinite(dt) || dt < 0)
7800
+ return Promise.reject(new Error("Aura3D stepAsync requires a finite non-negative delta."));
7801
+ if (disposed)
7802
+ return Promise.reject(new Error("Aura3D app is disposed."));
7803
+ pendingAsyncSteps += 1;
7804
+ const next = asyncSteps.then(async () => {
7805
+ await productionMountSettled;
7806
+ if (disposed)
7807
+ throw new Error("Aura3D app is disposed.");
7808
+ const controller = productionController;
7809
+ if (productionMountFailed || !controller?.renderAsync) {
7810
+ throw new Error(`Aura3D asynchronous submission unavailable: ${diagnosticsState.errors.join("; ") || "a production renderer is required"}`);
7811
+ }
7812
+ await controller.renderAsync(() => {
7813
+ runRuntimeFrame(dt, "manual");
7814
+ canvasRuntimePhysics?.step(dt);
7815
+ return runtimeTime * 1000;
7816
+ });
7817
+ }).finally(() => { pendingAsyncSteps -= 1; });
7818
+ asyncSteps = next.catch(() => undefined);
7819
+ return next;
7820
+ },
7821
+ async disposeAsync() {
7822
+ const controller = productionController;
7823
+ app.dispose();
7824
+ await asyncSteps;
7825
+ await productionMountTask;
7826
+ await controller?.whenIdle?.();
7827
+ },
7734
7828
  diagnostics() {
7829
+ diagnosticsState.renderer = readRootDiagnosticSnapshot(diagnosticsState.renderer, productionController?.diagnostics, disposed, pendingAsyncSteps > 0 || Boolean(productionController?.busy?.()));
7735
7830
  return snapshotDiagnostics(diagnosticsState);
7736
7831
  },
7737
7832
  evidence(evidenceOptions = {}) {
@@ -8071,6 +8166,7 @@ function worldLabelsFromSnapshot(snapshot, runtimeNodes) {
8071
8166
  return {
8072
8167
  id: node.name ?? `${node.label}-${index + 1}`,
8073
8168
  text: node.text,
8169
+ role: labelTelemetryRoleFor(node.label),
8074
8170
  anchor: [0, 0, 0],
8075
8171
  offscreenPolicy: "hide"
8076
8172
  };
@@ -8084,6 +8180,7 @@ function worldLabelsFromSnapshot(snapshot, runtimeNodes) {
8084
8180
  return {
8085
8181
  id: node.name ?? `${node.label}-${index + 1}`,
8086
8182
  text: node.text,
8183
+ role: labelTelemetryRoleFor(node.label),
8087
8184
  anchor: [position[0], position[1], position[2]],
8088
8185
  leaderAnchor: [anchor[0], anchor[1], anchor[2]],
8089
8186
  screenOffset: separated ? [0, 0] : [0, -26],
@@ -8237,8 +8334,10 @@ requiresFrames = () => false,
8237
8334
  * wall-clock time. Without it, `pause()` froze the simulation but not time-driven rendering.
8238
8335
  */
8239
8336
  pausedRenderTime = () => 0) {
8240
- const renderableNode = snapshot.nodes.find(isWebGLRenderableNode);
8241
- if (!renderableNode) {
8337
+ // Use the same ownership rule as initial mount and setScene. A compatibility
8338
+ // source continues supplying arena/fighter geometry when public nodes are
8339
+ // temporarily absent (for example the crowd-hidden negative control).
8340
+ if (!hasRootRenderableContent(canvas, snapshot.nodes.some(isWebGLRenderableNode))) {
8242
8341
  throw new AuraRuntimeError("missing-asset", "Aura3D production rendering requires at least one typed model asset or primitive. Suggested fix: add model(assets.product), primitives.box(), primitives.sphere(), primitives.cylinder(), or primitives.plane().");
8243
8342
  }
8244
8343
  const renderer = await createProductionSceneRenderer(canvas, snapshot, options.renderer, runtimeNodes);
@@ -8247,14 +8346,34 @@ pausedRenderTime = () => 0) {
8247
8346
  const labelLayer = createSceneLabelLayer(canvas, snapshot);
8248
8347
  const rendererSelection = normalizeCreateAppRendererOptions(options.renderer);
8249
8348
  const pixelRatio = options.pixelRatio ?? rendererSelection.profile.pixelRatio ?? devicePixelRatioSafe();
8349
+ let disposed = false;
8350
+ let animationHandle = 0;
8351
+ let lastTime = 0;
8352
+ let pendingFrames = 0;
8353
+ let frameTail = Promise.resolve();
8354
+ const baseBackingSize = getRootPerformanceBaseSize(canvas);
8355
+ let resourcesReleased = false;
8356
+ const releaseResources = () => {
8357
+ if (resourcesReleased)
8358
+ return;
8359
+ resourcesReleased = true;
8360
+ labelLayer?.dispose();
8361
+ renderer.dispose();
8362
+ };
8250
8363
  const resizeRenderer = () => {
8364
+ if (disposed)
8365
+ return;
8366
+ if (pendingFrames > 0) {
8367
+ resourceOwner.requestResize();
8368
+ return;
8369
+ }
8251
8370
  if (options.resize === false)
8252
8371
  return;
8253
8372
  const parentRect = canvas.parentElement?.getBoundingClientRect();
8254
8373
  const cssWidth = parentRect?.width || canvas.clientWidth || (typeof window !== "undefined" ? window.innerWidth : 960) || 960;
8255
8374
  const cssHeight = parentRect?.height || canvas.clientHeight || (typeof window !== "undefined" ? window.innerHeight : 540) || 540;
8256
- const width = Math.max(320, Math.round(cssWidth * pixelRatio));
8257
- const height = Math.max(220, Math.round(cssHeight * pixelRatio));
8375
+ const width = Math.max(1, Math.round(Math.max(320, cssWidth * pixelRatio) * (getRootPerformanceQuality(canvas)?.resolutionScale ?? 1)));
8376
+ const height = Math.max(1, Math.round(Math.max(220, cssHeight * pixelRatio) * (getRootPerformanceQuality(canvas)?.resolutionScale ?? 1)));
8258
8377
  if (canvas.width === width && canvas.height === height)
8259
8378
  return;
8260
8379
  canvas.style.width = `${cssWidth}px`;
@@ -8263,21 +8382,19 @@ pausedRenderTime = () => 0) {
8263
8382
  canvas.height = height;
8264
8383
  renderer.resize?.(width, height);
8265
8384
  };
8385
+ const resourceOwner = new DeferredFrameResources(releaseResources, resizeRenderer);
8266
8386
  const resizeObserver = options.resize !== false && typeof ResizeObserver !== "undefined" && canvas.parentElement
8267
8387
  ? new ResizeObserver(resizeRenderer)
8268
8388
  : undefined;
8269
8389
  resizeObserver?.observe(canvas.parentElement);
8270
8390
  if (options.resize !== false && typeof window !== "undefined")
8271
8391
  window.addEventListener("resize", resizeRenderer);
8272
- let disposed = false;
8273
- let animationHandle = 0;
8274
- let lastTime = 0;
8275
- const renderFrame = (time = performanceNow()) => {
8392
+ const renderFrame = (time = performanceNow(), submittedDrawCalls, explicit = false) => {
8276
8393
  if (disposed)
8277
8394
  return;
8278
8395
  const delta = lastTime > 0 ? Math.max(1, time - lastTime) : 16.67;
8279
8396
  lastTime = time;
8280
- if (!isPaused())
8397
+ if (!explicit && !isPaused())
8281
8398
  beforeRender?.(delta / 1000, "raf");
8282
8399
  /*
8283
8400
  * A paused app must not keep advancing time-driven rendering.
@@ -8292,8 +8409,8 @@ pausedRenderTime = () => 0) {
8292
8409
  * `runtimeTime` and renders explicitly, so stepping still produces new frames — what stops is
8293
8410
  * time passing on its own.
8294
8411
  */
8295
- const renderTime = isPaused() ? pausedRenderTime() : time;
8296
- const drawCalls = renderer.render(renderTime);
8412
+ const renderTime = resolveRootRenderTime(time, explicit, isPaused, pausedRenderTime);
8413
+ const drawCalls = submittedDrawCalls ?? renderer.render(renderTime);
8297
8414
  diagnosticsState.backend = renderer.backend;
8298
8415
  diagnosticsState.fps = diagnosticsState.fps || 60;
8299
8416
  diagnosticsState.drawCalls = drawCalls;
@@ -8321,12 +8438,91 @@ pausedRenderTime = () => 0) {
8321
8438
  });
8322
8439
  }
8323
8440
  overlay?.update();
8324
- if ((sceneWantsFrames || requiresFrames()) && options.autoStart !== false && typeof requestAnimationFrame !== "undefined") {
8325
- animationHandle = requestAnimationFrame(renderFrame);
8441
+ if (!explicit && !isPaused() && (sceneWantsFrames || requiresFrames()) && options.autoStart !== false && typeof requestAnimationFrame !== "undefined") {
8442
+ animationHandle = requestAnimationFrame(scheduleFrame);
8326
8443
  }
8327
8444
  };
8328
- renderFrame();
8445
+ const submitAsync = (clock, automatic = false) => {
8446
+ if (disposed)
8447
+ return Promise.reject(new Error("Aura3D renderer is disposed."));
8448
+ if (!renderer.renderAsync)
8449
+ return Promise.reject(new Error("Aura3D asynchronous submission requires the production renderer."));
8450
+ pendingFrames += 1;
8451
+ const submitted = resourceOwner.run(() => frameTail.then(async () => {
8452
+ if (disposed)
8453
+ throw new Error("Aura3D renderer is disposed.");
8454
+ const time = clock();
8455
+ if (automatic) {
8456
+ const delta = lastTime > 0 ? Math.max(1, time - lastTime) : 16.67;
8457
+ lastTime = time;
8458
+ if (!isPaused())
8459
+ beforeRender?.(delta / 1000, "raf");
8460
+ }
8461
+ // User frame callbacks can dispose the app. Do not submit a new frame after
8462
+ // that callback; pending ownership still defers cleanup until this settles.
8463
+ if (disposed)
8464
+ throw new Error("Aura3D renderer is disposed.");
8465
+ const renderTime = resolveRootRenderTime(time, !automatic, isPaused, pausedRenderTime);
8466
+ const drawCalls = await renderer.renderAsync(renderTime);
8467
+ if (!disposed) {
8468
+ const previousLastTime = lastTime;
8469
+ renderFrame(renderTime, drawCalls, true);
8470
+ lastTime = previousLastTime;
8471
+ }
8472
+ }).finally(() => { pendingFrames -= 1; }));
8473
+ frameTail = submitted.catch(() => undefined);
8474
+ return submitted;
8475
+ };
8476
+ const scheduleFrame = (time) => {
8477
+ animationHandle = 0;
8478
+ if (disposed)
8479
+ return;
8480
+ if (options.frameMode === "async") {
8481
+ void submitAsync(() => time, true).then(() => {
8482
+ if (!disposed && !isPaused() && (sceneWantsFrames || requiresFrames()) && options.autoStart !== false && typeof requestAnimationFrame !== "undefined") {
8483
+ animationHandle = requestAnimationFrame(scheduleFrame);
8484
+ }
8485
+ }).catch((error) => {
8486
+ diagnosticsState.errors.push(productionRenderErrorMessage(error));
8487
+ overlay?.update();
8488
+ });
8489
+ }
8490
+ else if (pendingFrames > 0) {
8491
+ // A manual async frame owns the resources. Retry without advancing simulation.
8492
+ // A pause cancels automatic retries; the explicit owner completes independently.
8493
+ if (!isPaused())
8494
+ animationHandle = requestAnimationFrame(scheduleFrame);
8495
+ }
8496
+ else
8497
+ renderFrame(time);
8498
+ };
8499
+ try {
8500
+ if (options.frameMode === "async") {
8501
+ await submitAsync(() => performanceNow(), true);
8502
+ if (!isPaused() && (sceneWantsFrames || requiresFrames()) && options.autoStart !== false && typeof requestAnimationFrame !== "undefined") {
8503
+ animationHandle = requestAnimationFrame(scheduleFrame);
8504
+ }
8505
+ }
8506
+ else
8507
+ renderFrame();
8508
+ }
8509
+ catch (error) {
8510
+ // A failed first submission has no returned controller to dispose its resources.
8511
+ disposed = true;
8512
+ resizeObserver?.disconnect();
8513
+ if (typeof window !== "undefined")
8514
+ window.removeEventListener("resize", resizeRenderer);
8515
+ releaseResources();
8516
+ throw error;
8517
+ }
8329
8518
  return {
8519
+ update(time) {
8520
+ if (disposed)
8521
+ throw new Error("Aura3D renderer is disposed.");
8522
+ if (pendingFrames > 0)
8523
+ throw new Error("Aura3D async frame is pending; await it before advancing renderer state.");
8524
+ renderer.update?.(time);
8525
+ },
8330
8526
  render(time = performanceNow()) {
8331
8527
  /*
8332
8528
  * Preserve `lastTime` across an explicit render.
@@ -8337,10 +8533,50 @@ pausedRenderTime = () => 0) {
8337
8533
  * different clock — a visible jump. Same defect the canvas2d path had; fixed identically so
8338
8534
  * the two render paths cannot disagree about what `step` does.
8339
8535
  */
8536
+ if (pendingFrames > 0)
8537
+ throw new Error("Aura3D async frame is pending; await it before synchronous rendering.");
8340
8538
  const previousLastTime = lastTime;
8341
- renderFrame(time);
8539
+ renderFrame(time, undefined, true);
8342
8540
  lastTime = previousLastTime;
8343
8541
  },
8542
+ setPerformanceQuality(settings) {
8543
+ if (disposed || pendingFrames > 0)
8544
+ throw new Error("PERFORMANCE_QUALITY_BUSY: await the active frame before changing quality.");
8545
+ setRootPerformanceQuality(canvas, settings);
8546
+ if (options.resize !== false)
8547
+ resizeRenderer();
8548
+ else {
8549
+ const width = Math.max(1, Math.round(baseBackingSize.width * settings.resolutionScale));
8550
+ const height = Math.max(1, Math.round(baseBackingSize.height * settings.resolutionScale));
8551
+ canvas.width = width;
8552
+ canvas.height = height;
8553
+ renderer.resize?.(width, height);
8554
+ }
8555
+ renderer.resetTemporalHistory?.("performance-quality");
8556
+ },
8557
+ diagnostics: () => disposed || pendingFrames > 0 ? undefined : renderer.diagnostics,
8558
+ renderAsync: (clock) => submitAsync(clock),
8559
+ busy: () => pendingFrames > 0,
8560
+ whenIdle: () => frameTail,
8561
+ resetTemporalHistory(reason) {
8562
+ // Reset after the active submission, before the next one can consume history.
8563
+ if (pendingFrames > 0)
8564
+ frameTail = frameTail.then(() => { if (!disposed)
8565
+ renderer.resetTemporalHistory?.(reason); });
8566
+ else
8567
+ renderer.resetTemporalHistory?.(reason);
8568
+ },
8569
+ pause() {
8570
+ if (animationHandle && typeof cancelAnimationFrame !== "undefined")
8571
+ cancelAnimationFrame(animationHandle);
8572
+ animationHandle = 0;
8573
+ },
8574
+ resume() {
8575
+ if (disposed || animationHandle || options.autoStart === false || typeof requestAnimationFrame === "undefined")
8576
+ return;
8577
+ if (sceneWantsFrames || requiresFrames())
8578
+ animationHandle = requestAnimationFrame(scheduleFrame);
8579
+ },
8344
8580
  // WS-2.6 — forward device-loss subscription from whichever renderer backs this controller.
8345
8581
  onDeviceLost: renderer.onDeviceLost ? (listener) => renderer.onDeviceLost(listener) : undefined,
8346
8582
  onDeviceRestored: renderer.onDeviceRestored ? (listener) => renderer.onDeviceRestored(listener) : undefined,
@@ -8352,8 +8588,7 @@ pausedRenderTime = () => 0) {
8352
8588
  resizeObserver?.disconnect();
8353
8589
  if (typeof window !== "undefined")
8354
8590
  window.removeEventListener("resize", resizeRenderer);
8355
- labelLayer?.dispose();
8356
- renderer.dispose();
8591
+ resourceOwner.dispose();
8357
8592
  }
8358
8593
  };
8359
8594
  }
@@ -8543,7 +8778,14 @@ function createProductionRuntimeEnvironmentFog(snapshot, lights = [], renderWidt
8543
8778
  ...(resolved.forward.heightReference !== undefined ? { heightReference: resolved.forward.heightReference } : {})
8544
8779
  };
8545
8780
  }
8546
- function createProductionRuntimePostprocess(snapshot, lights = [], renderWidth = 1280, renderHeight = 720) {
8781
+ const productionTemporalSceneKeys = new WeakMap();
8782
+ let productionTemporalSceneSequence = 0;
8783
+ export function resolveNativeBloomRadius(authoredRadius) {
8784
+ const radius = authoredRadius ?? 0.38;
8785
+ const normalizedKernel = radius <= 1 ? Math.round(radius * 8) : Math.round(radius);
8786
+ return Math.max(1, Math.min(4, normalizedKernel));
8787
+ }
8788
+ function createProductionRuntimePostprocess(snapshot, lights = [], renderWidth = 1280, renderHeight = 720, temporalSupported = true) {
8547
8789
  const nodes = groups.flatten(snapshot.nodes);
8548
8790
  const authoredBloom = nodes.find((node) => node.kind === "effect" && node.effect === "bloom");
8549
8791
  // A5: the DISTINCT volumetric-fog node submits the depth-aware inscatter
@@ -8569,26 +8811,42 @@ function createProductionRuntimePostprocess(snapshot, lights = [], renderWidth =
8569
8811
  const authoredAmbientOcclusion = nodes.find((node) => node.kind === "effect" && node.effect === "ambient-occlusion");
8570
8812
  const authoredContactOcclusion = nodes.find((node) => node.kind === "effect" && node.effect === "contact-occlusion");
8571
8813
  const authoredOcclusion = authoredAmbientOcclusion ?? authoredContactOcclusion;
8572
- // muse3jsparity-PRD A3: color-grade / outline / fxaa / ssr / dof submit real
8573
- // native options. motion-blur and taa are withheld (velocity/history have no
8574
- // root binding): the status layer warns instead of submitting doomed options
8575
- // that would fail the route with zero draw calls.
8814
+ // The renderer owns temporal GPU inputs. Root checks actual drawable
8815
+ // geometry before requesting them; unsupported scenes retain ordinary output.
8576
8816
  const authoredColorGrade = nodes.find((node) => node.kind === "effect" && node.effect === "color-grade");
8577
8817
  const authoredAntiAlias = nodes.find((node) => node.kind === "effect" && node.effect === "anti-alias");
8578
8818
  const authoredOutline = nodes.find((node) => node.kind === "effect" && node.effect === "outline");
8579
8819
  const authoredSsr = nodes.find((node) => node.kind === "effect" && node.effect === "screen-space-reflections");
8580
8820
  const authoredDof = nodes.find((node) => node.kind === "effect" && node.effect === "depth-of-field");
8821
+ const authoredMotionBlur = nodes.find((node) => node.kind === "effect" && node.effect === "motion-blur");
8822
+ const temporalRequested = temporalSupported && (Boolean(authoredMotionBlur) || authoredAntiAlias?.mode === "taa");
8823
+ let sceneKey = productionTemporalSceneKeys.get(snapshot);
8824
+ if (!sceneKey) {
8825
+ sceneKey = `root-scene-${++productionTemporalSceneSequence}`;
8826
+ productionTemporalSceneKeys.set(snapshot, sceneKey);
8827
+ }
8581
8828
  const fxaaRequested = (authoredAntiAlias?.mode ?? "fxaa") === "fxaa";
8582
8829
  const outlineChannels = colorToRgba(authoredOutline?.color ?? "#ff9822");
8583
8830
  return {
8584
8831
  // Tone mapping requires unclamped linear input. RGBA8 quantized dark clear
8585
8832
  // colors and clipped highlights before ACES, which produced washed-out output.
8586
8833
  targetFormat: "rgba16f",
8834
+ ...(temporalRequested ? {
8835
+ temporal: { sceneKey },
8836
+ ...(authoredMotionBlur ? { motionBlur: { samples: 8, scale: clampNumber(authoredMotionBlur.intensity ?? .5, 0, 2) } } : {}),
8837
+ ...(authoredAntiAlias?.mode === "taa" ? { taa: { blend: .9 } } : {})
8838
+ } : {}),
8587
8839
  ...(bloomRequested ? {
8588
8840
  bloom: {
8589
8841
  threshold: clampNumber(authoredBloom?.threshold ?? 0.78, 0, 1),
8590
8842
  intensity: clampNumber(authoredBloom?.intensity ?? 0.3, 0, 2),
8591
- radius: Math.max(1, Math.min(4, Math.round(authoredBloom?.radius ?? 2))),
8843
+ // Public bloom radius follows the normalized UnrealBloomPass-style
8844
+ // control used by routes and prefabs (normally 0..1), while the native
8845
+ // separable blur consumes an integer pixel kernel. Preserve legacy
8846
+ // explicit pixel kernels above 1 and spread normalized values across
8847
+ // 1..4 so common authored values such as 0.22/0.38 do not all collapse
8848
+ // to a one-pixel blur after rounding.
8849
+ radius: resolveNativeBloomRadius(authoredBloom?.radius),
8592
8850
  ...(authoredBloom?.quality !== undefined ? { quality: authoredBloom.quality } : {}),
8593
8851
  ...(authoredBloom?.softKnee !== undefined ? { softKnee: authoredBloom.softKnee } : {}),
8594
8852
  ...(authoredBloom?.shoulder !== undefined ? { shoulder: authoredBloom.shoulder } : {})
@@ -8665,11 +8923,11 @@ function createProductionRuntimeShadowOptions(snapshot, collectedLights) {
8665
8923
  }, 1);
8666
8924
  const shadowCaster = collectedLights.find((light) => light.castsShadow);
8667
8925
  const size = sceneRadius > 30 ? 4096 : sceneRadius > 10 ? 2048 : 1024;
8668
- const texelWorldSize = (sceneRadius * 2) / size;
8926
+ const tuning = resolveProductionRuntimeShadowTuning(shadowCaster?.kind, size, sceneRadius);
8669
8927
  return {
8670
8928
  enabled: Boolean(shadowCaster),
8671
8929
  size,
8672
- bias: clampNumber(texelWorldSize * 0.55, 0.00035, 0.004),
8930
+ ...tuning,
8673
8931
  strength: category === "city-day" ? 0.38
8674
8932
  : category === "material" || category === "product" ? 0.24
8675
8933
  : 0.32,
@@ -8679,6 +8937,30 @@ function createProductionRuntimeShadowOptions(snapshot, collectedLights) {
8679
8937
  label: `aura3d-root-production-${category}-${size}px-shadow-map`
8680
8938
  };
8681
8939
  }
8940
+ /**
8941
+ * Shadow bias is compared in normalized light-depth space. Directional maps use
8942
+ * an orthographic projection, so their existing scene-radius/texel heuristic is
8943
+ * world-linear. A spot map is perspective: applying that world-space value at
8944
+ * the far end of its cone erased legitimate caster/receiver separation (Aura
8945
+ * Clash measured 0.00147 normalized depth versus the old 0.004 constant bias),
8946
+ * and the default slope term could erase the remainder on a vertical receiver.
8947
+ * Keep the established directional/point policy intact and use a bounded
8948
+ * fraction of one normalized spot-map texel for both perspective terms.
8949
+ */
8950
+ export function resolveProductionRuntimeShadowTuning(kind, size, sceneRadius) {
8951
+ if (!Number.isInteger(size) || size <= 0)
8952
+ throw new RangeError("Shadow-map size must be a positive integer.");
8953
+ if (!Number.isFinite(sceneRadius) || sceneRadius <= 0)
8954
+ throw new RangeError("Shadow scene radius must be finite and positive.");
8955
+ if (kind === "spot") {
8956
+ return {
8957
+ bias: clampNumber(0.15 / size, 0.00005, 0.00035),
8958
+ slopeBias: 0.12
8959
+ };
8960
+ }
8961
+ const texelWorldSize = (sceneRadius * 2) / size;
8962
+ return { bias: clampNumber(texelWorldSize * 0.55, 0.00035, 0.004) };
8963
+ }
8682
8964
  /**
8683
8965
  * Device-observed shadow state. `requested` comes from the submitted shadow
8684
8966
  * options, but `mapRendered`/`mapSampled` come only from what the device
@@ -8686,11 +8968,12 @@ function createProductionRuntimeShadowOptions(snapshot, collectedLights) {
8686
8968
  * sample it. This is what stops the root report from publishing an unconditional
8687
8969
  * `shadows.enabled: true`.
8688
8970
  */
8689
- function createProductionRuntimeShadowObservation(shadowOptions, diagnostics) {
8971
+ function createProductionRuntimeShadowObservation(shadowOptions, diagnostics, observed) {
8690
8972
  const nativeShadowMapBindings = diagnostics.nativeShadowMapBindings ?? 0;
8691
- const mapRendered = Boolean(shadowOptions.enabled) && (diagnostics.shadowRenderTargetsAllocated ?? 0) > 0;
8973
+ const mapRendered = Boolean(shadowOptions.enabled) && observed !== null && (diagnostics.shadowRenderTargetsAllocated ?? 0) > 0;
8692
8974
  return {
8693
8975
  requested: Boolean(shadowOptions.enabled),
8976
+ observed,
8694
8977
  mapRendered,
8695
8978
  mapSampled: mapRendered && nativeShadowMapBindings > 0,
8696
8979
  mapSize: shadowOptions.size,
@@ -8802,17 +9085,26 @@ function createProductionRuntimeCollectedLights(snapshot) {
8802
9085
  * all-unrequested scenes keep the legacy priority-then-intensity order.
8803
9086
  */
8804
9087
  export function resolveProductionShadowCasterIndex(descriptors) {
8805
- return descriptors.reduce((selected, descriptor, index) => {
8806
- if (selected < 0)
8807
- return index;
8808
- const current = descriptors[selected];
8809
- const priority = descriptor.shadowPriority + (descriptor.shadowRequested ? 10 : 0);
8810
- const currentPriority = current.shadowPriority + (current.shadowRequested ? 10 : 0);
8811
- if (priority !== currentPriority) {
8812
- return priority > currentPriority ? index : selected;
8813
- }
8814
- return descriptor.intensity > current.intensity ? index : selected;
8815
- }, -1);
9088
+ const explicitlyRequested = descriptors
9089
+ .map((descriptor, index) => ({ descriptor, index }))
9090
+ .filter(({ descriptor }) => descriptor.shadowRequested);
9091
+ // An authored false is scene-level opt-out when no light is explicitly
9092
+ // requested. Otherwise another unspecified light would silently take the
9093
+ // legacy caster slot and make `shadow: false` visually ineffective.
9094
+ if (explicitlyRequested.length === 0 && descriptors.some((descriptor) => descriptor.shadowDisabled === true)) {
9095
+ return -1;
9096
+ }
9097
+ const candidates = explicitlyRequested.length > 0
9098
+ ? explicitlyRequested
9099
+ : descriptors.map((descriptor, index) => ({ descriptor, index }));
9100
+ return candidates.reduce((selected, candidate) => {
9101
+ if (selected === undefined)
9102
+ return candidate;
9103
+ if (candidate.descriptor.shadowPriority !== selected.descriptor.shadowPriority) {
9104
+ return candidate.descriptor.shadowPriority > selected.descriptor.shadowPriority ? candidate : selected;
9105
+ }
9106
+ return candidate.descriptor.intensity > selected.descriptor.intensity ? candidate : selected;
9107
+ }, undefined)?.index ?? -1;
8816
9108
  }
8817
9109
  function createProductionRuntimeLightDescriptors(node, authoredIndex) {
8818
9110
  const name = node.name?.trim() || `aura-authored-${node.light}-${authoredIndex + 1}`;
@@ -8837,7 +9129,8 @@ function createProductionRuntimeLightDescriptors(node, authoredIndex) {
8837
9129
  spotAngle: 0,
8838
9130
  penumbra: 0,
8839
9131
  shadowPriority: 3,
8840
- shadowRequested: false,
9132
+ shadowRequested: node.shadow === true,
9133
+ shadowDisabled: node.shadow === false,
8841
9134
  authoredLight: node.light
8842
9135
  }];
8843
9136
  }
@@ -8876,7 +9169,8 @@ function createProductionRuntimeLightDescriptors(node, authoredIndex) {
8876
9169
  spotAngle: clampNumber(node.angle ?? Math.PI / 6, 0.08, Math.PI / 2 - 0.01),
8877
9170
  penumbra: clampNumber(node.penumbra ?? 0.4, 0, 1),
8878
9171
  shadowPriority: 2,
8879
- shadowRequested: node.shadow ?? false,
9172
+ shadowRequested: node.shadow === true,
9173
+ shadowDisabled: node.shadow === false,
8880
9174
  authoredLight: node.light
8881
9175
  }];
8882
9176
  }
@@ -9164,6 +9458,7 @@ async function createProductionRuntimeSceneRenderer(canvas, snapshot, rendererOp
9164
9458
  height: canvas.height,
9165
9459
  backend: "webgl2",
9166
9460
  antialias: true,
9461
+ ...(getRootRenderSource(canvas) ? { errorCheckMode: "frame" } : {}),
9167
9462
  preserveDrawingBuffer: true,
9168
9463
  // Background colors are display intent. Pre-invert the renderer's coupled
9169
9464
  // matrix-fitted ACES transform so presentation preserves that authored color.
@@ -9181,7 +9476,7 @@ async function createProductionRuntimeSceneRenderer(canvas, snapshot, rendererOp
9181
9476
  const textureUpgradeWarnings = new Set();
9182
9477
  void upgradeProductionPrimitiveTextures(primitiveEntries, (message) => {
9183
9478
  textureUpgradeWarnings.add(message);
9184
- }).catch((error) => {
9479
+ }, Number(canvas.getContext("webgl2")?.getParameter(WebGL2RenderingContext.MAX_TEXTURE_SIZE) ?? 4096)).catch((error) => {
9185
9480
  textureUpgradeWarnings.add(`textured upgrade pass failed (${error instanceof Error ? error.message : String(error)}); scalar materials retained`);
9186
9481
  });
9187
9482
  const productionEnvironment = createProductionRuntimeEnvironment(snapshot);
@@ -9224,7 +9519,7 @@ async function createProductionRuntimeSceneRenderer(canvas, snapshot, rendererOp
9224
9519
  // N1 + G1 + M2 observations refresh on every diagnostics read from the
9225
9520
  // live entries/device state — never cached intent.
9226
9521
  const buildDiagnostics = () => {
9227
- const shadowObservation = createProductionRuntimeShadowObservation(createProductionRuntimeShadowOptions(snapshot, productionRuntimeLights), latestDeviceDiagnostics);
9522
+ const shadowObservation = createProductionRuntimeShadowObservation({ ...createProductionRuntimeShadowOptions(snapshot, productionRuntimeLights), ...(getRootPerformanceQuality(canvas) ? { size: getRootPerformanceQuality(canvas).shadowSize } : {}) }, latestDeviceDiagnostics, productionRenderer.getShadowEvidence());
9228
9523
  const spotCaster = productionRuntimeLights.find((light) => light.castsShadow);
9229
9524
  const spotAngle = spotCaster?.kind === "spot" ? spotCaster.spotAngle : undefined;
9230
9525
  const texturesObservation = createProductionTexturesObservation(primitiveEntries, latestCameraEye, rendererOptions?.textureBudgetBytes ?? normalizeTextureBudgetBytes(undefined));
@@ -9307,6 +9602,17 @@ async function createProductionRuntimeSceneRenderer(canvas, snapshot, rendererOp
9307
9602
  })))
9308
9603
  }, rendererOptions);
9309
9604
  };
9605
+ let preparedFrame;
9606
+ const buildFrame = (time) => {
9607
+ runtimeWarnings.clear();
9608
+ latestCameraEye = resolveCameraFrame(snapshot, snapshot.camera, time, runtimeNodes).eye;
9609
+ return createProductionRuntimeRendererInput(snapshot, canvas, actorEntries, primitiveEntries, time, runtimeNodes, runtimeWarnings, currentEnvironmentLighting, productionRuntimeLights);
9610
+ };
9611
+ const takePreparedFrame = (time) => {
9612
+ const prepared = preparedFrame?.time === time ? preparedFrame.input : undefined;
9613
+ preparedFrame = undefined;
9614
+ return prepared ?? buildFrame(time);
9615
+ };
9310
9616
  return {
9311
9617
  get backend() {
9312
9618
  return productionRenderer.backend;
@@ -9314,18 +9620,34 @@ async function createProductionRuntimeSceneRenderer(canvas, snapshot, rendererOp
9314
9620
  get diagnostics() {
9315
9621
  return buildDiagnostics();
9316
9622
  },
9623
+ update(time) {
9624
+ // Building the frame applies controller-bound actor clips, root-motion
9625
+ // consumption, foot IK, morphs and imported-asset evidence. Discarding the
9626
+ // returned input deliberately avoids a GPU/device submission.
9627
+ preparedFrame = { time, input: buildFrame(time) };
9628
+ },
9317
9629
  render(time) {
9318
- runtimeWarnings.clear();
9319
- latestCameraEye = resolveCameraFrame(snapshot, snapshot.camera, time, runtimeNodes).eye;
9320
- const input = createProductionRuntimeRendererInput(snapshot, canvas, actorEntries, primitiveEntries, time, runtimeNodes, runtimeWarnings, currentEnvironmentLighting, productionRuntimeLights);
9630
+ const input = takePreparedFrame(time);
9321
9631
  const result = productionRenderer.renderInteractiveFrame(input);
9322
9632
  latestDeviceDiagnostics = result.diagnostics;
9633
+ getRootRenderSource(canvas)?.onFrame?.(latestDeviceDiagnostics, [...(input.source.collectRenderItems?.() ?? [])]);
9634
+ latestFeatures = result.features;
9635
+ return latestDeviceDiagnostics.drawCalls;
9636
+ },
9637
+ async renderAsync(time) {
9638
+ const input = takePreparedFrame(time);
9639
+ const result = await productionRenderer.renderInteractiveFrameAsync(input);
9640
+ latestDeviceDiagnostics = result.diagnostics;
9641
+ getRootRenderSource(canvas)?.onFrame?.(latestDeviceDiagnostics, [...(input.source.collectRenderItems?.() ?? [])]);
9323
9642
  latestFeatures = result.features;
9324
9643
  return latestDeviceDiagnostics.drawCalls;
9325
9644
  },
9326
9645
  viewProjection(time) {
9327
9646
  return createViewProjection(snapshot, canvas.width / Math.max(1, canvas.height), time, runtimeNodes);
9328
9647
  },
9648
+ resetTemporalHistory(reason) {
9649
+ productionRenderer.resetTemporalHistory(reason);
9650
+ },
9329
9651
  resize(width, height) {
9330
9652
  productionRenderer.resize(width, height);
9331
9653
  },
@@ -9344,7 +9666,8 @@ async function createProductionRuntimeSceneRenderer(canvas, snapshot, rendererOp
9344
9666
  for (const { actor } of actorEntries)
9345
9667
  actor.dispose();
9346
9668
  for (const { resources } of primitiveEntries) {
9347
- for (const { geometry, material, texturedMaterial } of resources) {
9669
+ for (const { geometry, material, texturedMaterial, textureDisposer } of resources) {
9670
+ textureDisposer?.();
9348
9671
  geometry.dispose();
9349
9672
  material.dispose();
9350
9673
  texturedMaterial?.dispose();
@@ -9354,19 +9677,36 @@ async function createProductionRuntimeSceneRenderer(canvas, snapshot, rendererOp
9354
9677
  };
9355
9678
  }
9356
9679
  function createProductionRuntimeRendererInput(snapshot, canvas, actorEntries, primitiveEntries, time, runtimeNodes, runtimeWarnings, environmentLighting, collectedLights) {
9357
- const items = [];
9680
+ const compatibility = getRootRenderSource(canvas);
9681
+ const attachedItems = compatibility ? [...(compatibility.source.collectRenderItems?.() ?? compatibility.source.renderItems ?? [])] : [];
9682
+ const items = [...attachedItems];
9358
9683
  const viewProjectionMatrix = createViewProjection(snapshot, canvas.width / Math.max(1, canvas.height), time, runtimeNodes);
9359
9684
  const cameraPosition = resolveCameraFrame(snapshot, snapshot.camera, time, runtimeNodes).eye;
9360
- for (const entry of actorEntries) {
9361
- const currentState = resolveProductionActorRuntimeState(entry, runtimeNodes);
9362
- const currentNode = currentState.node;
9685
+ for (const [actorIndex, entry] of actorEntries.entries()) {
9686
+ let currentState = resolveProductionActorRuntimeState(entry, runtimeNodes);
9687
+ let currentNode = currentState.node;
9363
9688
  if (currentNode.visible === false)
9364
9689
  continue;
9365
- const modelMatrix = [...createModelMatrix(currentNode, productionActorModelBounds(currentNode.asset, entry.actor), shouldNormalizeModelNode(currentNode), time)];
9690
+ let modelMatrix = [...createModelMatrix(currentNode, productionActorModelBounds(currentNode.asset, entry.actor), shouldNormalizeModelNode(currentNode), time)];
9366
9691
  // The foot-planting post-pass solves in the same world space this matrix draws into;
9367
9692
  // refresh its matrix before the clip plays so the solve uses this frame, not the last.
9368
9693
  applyProductionActorFootPlanting(entry, currentState.animationBinding, modelMatrix, runtimeWarnings);
9369
- applyProductionActorAnimation(entry, currentNode, currentState.animationBinding, time, runtimeWarnings);
9694
+ if (currentState.animationPose) {
9695
+ try {
9696
+ entry.actor.applyRetargetedPose(currentState.animationPose, currentState.animationPoseTime ?? time);
9697
+ entry.rootMotionCursors = undefined;
9698
+ }
9699
+ catch (error) {
9700
+ runtimeWarnings.add(`Typed GLB actor "${entry.actor.id}" failed to apply bound pose: ${productionRenderErrorMessage(error)}`);
9701
+ }
9702
+ }
9703
+ else
9704
+ applyProductionActorAnimation(entry, currentNode, currentState.animationBinding, time, runtimeWarnings, modelMatrix, () => {
9705
+ currentState = resolveProductionActorRuntimeState(entry, runtimeNodes);
9706
+ currentNode = currentState.node;
9707
+ modelMatrix = [...createModelMatrix(currentNode, productionActorModelBounds(currentNode.asset, entry.actor), shouldNormalizeModelNode(currentNode), time)];
9708
+ applyProductionActorFootPlanting(entry, currentState.animationBinding, modelMatrix, runtimeWarnings);
9709
+ });
9370
9710
  applyProductionActorMorphTargets(entry, currentState.morphTargets, runtimeWarnings);
9371
9711
  // Wrinkle detail (E1 face-rig demo): resolve morph weights through the model's hook.
9372
9712
  // Absent hook (or empty weights) resolves to 0 = today's rendering exactly.
@@ -9403,7 +9743,25 @@ function createProductionRuntimeRendererInput(snapshot, canvas, actorEntries, pr
9403
9743
  }
9404
9744
  if (currentNode.instancedModelWarning)
9405
9745
  runtimeWarnings.add(currentNode.instancedModelWarning);
9406
- items.push(...actorItems.map((item) => ({ ...item, castShadow: currentNode.castShadow, ...modelInstanceAttach })));
9746
+ // A GLB mesh has its own authored local/world transform. Native instance
9747
+ // transforms must apply the complete normalized node placement BEFORE that
9748
+ // mesh transform, exactly like an individually mounted actor. The previous
9749
+ // base(actorRoot * mesh) * instance(actorRoot) order normalized twice and
9750
+ // rotated/scaled the instance translations in the mesh's coordinate system.
9751
+ const localActorItems = modelInstanceAttach.instanceTransforms
9752
+ ? entry.actor.collectRenderItems({ modelMatrix: identityMat4(), ...(wrinkleStrength === undefined ? {} : { wrinkleStrength }) })
9753
+ : undefined;
9754
+ items.push(...actorItems.map((item, itemIndex) => {
9755
+ const nativeMatrices = modelInstanceAttach.instanceTransforms;
9756
+ const localItem = localActorItems?.[itemIndex];
9757
+ if (nativeMatrices && !localItem)
9758
+ throw new Error("Instanced GLB mesh collection changed within one frame");
9759
+ return { ...item, label: `actor-${actorIndex}:mesh-${itemIndex}:${item.label ?? "mesh"}`, castShadow: currentNode.castShadow, ...modelInstanceAttach,
9760
+ ...(nativeMatrices && localItem ? {
9761
+ modelMatrix: identityMat4(),
9762
+ instanceTransforms: composeModelInstanceMatrices(nativeMatrices, localItem.modelMatrix ?? identityMat4())
9763
+ } : {}) };
9764
+ }));
9407
9765
  attachProductionActorEvidence(currentNode, entry.actor, actorItems, runtimeNodes);
9408
9766
  }
9409
9767
  // G1 SDF occlusion test, built once per frame only when SDF quads exist
@@ -9412,11 +9770,11 @@ function createProductionRuntimeRendererInput(snapshot, canvas, actorEntries, pr
9412
9770
  const sdfOcclusionTest = sdfEntries.length > 0
9413
9771
  ? createSceneLabelOcclusionTest(snapshot, cameraPosition, runtimeNodes)
9414
9772
  : undefined;
9415
- for (const entry of primitiveEntries) {
9773
+ for (const [primitiveIndex, entry] of primitiveEntries.entries()) {
9416
9774
  const currentState = resolveProductionPrimitiveRuntimeState(entry, runtimeNodes);
9417
9775
  if (!currentState.visible)
9418
9776
  continue;
9419
- const resource = selectProductionPrimitiveResource(entry, currentState.node, cameraPosition);
9777
+ const resource = selectProductionPrimitiveResource(entry, currentState.node, cameraPosition, getRootPerformanceQuality(canvas)?.lodBias ?? 1);
9420
9778
  if (resource.sdfText && resource.texturedMaterial) {
9421
9779
  // G1 per-frame SDF opacity: LOD fade from the live camera distance
9422
9780
  // times the scene occlusion policy, written to the quad material.
@@ -9445,32 +9803,43 @@ function createProductionRuntimeRendererInput(snapshot, canvas, actorEntries, pr
9445
9803
  geometry: resource.geometry,
9446
9804
  material: resource.texturedMaterial ?? resource.material,
9447
9805
  modelMatrix: createModelMatrix(currentState.node, resource.bounds, false, time),
9448
- label: currentState.node.name ?? `aura-primitive-${currentState.node.primitive}`,
9806
+ label: `primitive-${primitiveIndex}:${resource.name}:${currentState.node.name ?? currentState.node.primitive}`,
9449
9807
  castShadow: currentState.node.castShadow,
9450
9808
  includeInAutoFrame: false,
9451
9809
  ...(currentState.node.instances ? { instanceTransforms: createProductionInstanceTransforms(currentState.node.instances, currentState.node), instanceColors: createProductionInstanceColors(currentState.node.instanceColors, currentState.node.instances.length) } : {})
9452
9810
  });
9453
9811
  }
9812
+ const unsupportedTemporal = items.find(item => {
9813
+ const base = item.material && "baseMaterial" in item.material ? item.material.baseMaterial : item.material;
9814
+ return Boolean(item.skinning || item.morphTargets?.length || item.instanceTransforms?.length || item.geometry.topology !== "triangles" || base?.renderState.blend);
9815
+ });
9816
+ const temporalRequested = groups.flatten(snapshot.nodes).some(node => node.kind === "effect" && (node.effect === "motion-blur" || (node.effect === "anti-alias" && node.mode === "taa")));
9817
+ for (const warning of runtimeWarnings)
9818
+ if (warning.startsWith("TEMPORAL_UNSUPPORTED_GEOMETRY:"))
9819
+ runtimeWarnings.delete(warning);
9820
+ if (temporalRequested && unsupportedTemporal)
9821
+ runtimeWarnings.add(`TEMPORAL_UNSUPPORTED_GEOMETRY: ${unsupportedTemporal.label}; requires opaque rigid noninstanced triangles`);
9454
9822
  const source = {
9823
+ ...(compatibility?.source ?? {}),
9455
9824
  collectRenderItems: () => items,
9456
- cameraPolicy: "require",
9457
- staticBatching: true,
9825
+ cameraPolicy: compatibility?.source.cameraPolicy ?? "require",
9826
+ staticBatching: !temporalRequested,
9458
9827
  frustumCulling: true,
9459
- collectedLights,
9460
- environmentLighting,
9828
+ collectedLights: [...collectedLights, ...(compatibility?.source.collectedLights ?? [])],
9829
+ environmentLighting: compatibility?.source.environmentLighting ?? environmentLighting,
9461
9830
  // The production runtime owns the pixel-backed HDR target and pass chain for
9462
9831
  // routes that request effects. The diagnostics are device-observed, so a
9463
9832
  // compositor failure is reported as fallback rather than claimed as a pass.
9464
- postprocess: createProductionRuntimePostprocess(snapshot, collectedLights, canvas.width, canvas.height),
9465
- shadow: createProductionRuntimeShadowOptions(snapshot, collectedLights),
9466
- environmentFog: createProductionRuntimeEnvironmentFog(snapshot, collectedLights, canvas.width, canvas.height),
9467
- cameraPosition
9833
+ postprocess: compatibility?.source.postprocess ?? createProductionRuntimePostprocess(snapshot, collectedLights, canvas.width, canvas.height, !unsupportedTemporal),
9834
+ shadow: { ...createProductionRuntimeShadowOptions(snapshot, collectedLights), ...(getRootPerformanceQuality(canvas) ? { size: getRootPerformanceQuality(canvas).shadowSize } : {}) },
9835
+ environmentFog: compatibility?.source.environmentFog ?? createProductionRuntimeEnvironmentFog(snapshot, collectedLights, canvas.width, canvas.height),
9836
+ ...(compatibility?.source.cameraPolicy === "auto-frame" ? {} : { cameraPosition })
9468
9837
  };
9469
9838
  const cameraLike = { viewProjectionMatrix };
9470
9839
  return {
9471
9840
  source,
9472
- camera: cameraLike,
9473
- metadata: createProductionRuntimeMetadata(actorEntries, primitiveEntries)
9841
+ ...(compatibility?.source.cameraPolicy === "auto-frame" ? {} : { camera: cameraLike }),
9842
+ metadata: includeRootSourceMetadata(createProductionRuntimeMetadata(actorEntries, primitiveEntries), attachedItems)
9474
9843
  };
9475
9844
  }
9476
9845
  function createProductionRuntimePrimitiveEntries(nodes) {
@@ -9486,6 +9855,7 @@ function blankProductionPrimitiveTextureState() {
9486
9855
  * fetchable urls; procedural inputs have no rasterizer and are reported so
9487
9856
  * the caller can warn instead of silently dropping them.
9488
9857
  */
9858
+ const ROOT_EXTENSION_TEXTURE_SLOTS = ["clearcoat", "clearcoatRoughness", "clearcoatNormal", "sheenColor", "sheenRoughness", "iridescence", "iridescenceThickness", "anisotropy"];
9489
9859
  export function createProductionPrimitiveTextureIntent(materialSpec) {
9490
9860
  const refUrl = (input) => input && typeof input === "object" && input.kind === "aura-asset-ref"
9491
9861
  ? input.url ?? undefined
@@ -9507,6 +9877,25 @@ export function createProductionPrimitiveTextureIntent(materialSpec) {
9507
9877
  const metalnessUrl = refUrl(materialSpec?.metalnessMap);
9508
9878
  const occlusionUrl = refUrl(materialSpec?.occlusionMap);
9509
9879
  const emissiveUrl = refUrl(materialSpec?.emissiveMap);
9880
+ const extensionMaps = Object.fromEntries(ROOT_EXTENSION_TEXTURE_SLOTS.flatMap((slot) => {
9881
+ const input = materialSpec?.[`${slot}Map`];
9882
+ if (input === undefined)
9883
+ return [];
9884
+ if (!input || typeof input !== "object" || input.kind !== "aura-asset-ref" || input.type !== "texture") {
9885
+ throw new AuraRuntimeError("unsupported-texture", `${slot}Map requires a typed texture asset reference`);
9886
+ }
9887
+ if (!["png", "jpg", "jpeg", "webp"].includes(input.format) || !input.url) {
9888
+ throw new AuraRuntimeError("unsupported-texture", `${slot}Map requires a fetchable png, jpg, jpeg or webp texture`);
9889
+ }
9890
+ const texCoord = materialSpec?.texCoords?.[slot] ?? 0;
9891
+ if (texCoord !== 0 && texCoord !== 1)
9892
+ throw new RangeError(`${slot} texCoord must be 0 or 1`);
9893
+ const transform = materialSpec?.texTransforms?.[slot];
9894
+ if (transform && [...(transform.offset ?? [0, 0]), ...(transform.scale ?? [1, 1]), transform.rotation ?? 0].some((value) => !Number.isFinite(value))) {
9895
+ throw new RangeError(`${slot} texture transform must contain finite values`);
9896
+ }
9897
+ return [[slot, { url: input.url, colorSpace: slot === "sheenColor" ? "srgb" : "linear", texCoord, ...(transform ? { transform } : {}) }]];
9898
+ }));
9510
9899
  return {
9511
9900
  ...(baseColorUrl ? { baseColorUrl } : {}),
9512
9901
  ...(normalUrl ? { normalUrl } : {}),
@@ -9514,6 +9903,7 @@ export function createProductionPrimitiveTextureIntent(materialSpec) {
9514
9903
  ...(metalnessUrl ? { metalnessUrl } : {}),
9515
9904
  ...(occlusionUrl ? { occlusionUrl } : {}),
9516
9905
  ...(emissiveUrl ? { emissiveUrl } : {}),
9906
+ ...(Object.keys(extensionMaps).length ? { extensionMaps } : {}),
9517
9907
  proceduralInputs: procedurals
9518
9908
  };
9519
9909
  }
@@ -9522,6 +9912,7 @@ export function createProductionPrimitiveTextureIntent(materialSpec) {
9522
9912
  * R = occlusion (unused here, forced to 255), G = roughness, B = metallic.
9523
9913
  * Missing channels fall back to the scalar spec values.
9524
9914
  */
9915
+ const productionPrimitiveBitmapPixels = new WeakMap();
9525
9916
  async function loadProductionPrimitiveBitmap(url) {
9526
9917
  if (typeof fetch !== "function" || typeof createImageBitmap !== "function") {
9527
9918
  throw new Error("texture fetch requires fetch + createImageBitmap (browser production mount)");
@@ -9529,9 +9920,24 @@ async function loadProductionPrimitiveBitmap(url) {
9529
9920
  const response = await fetch(url);
9530
9921
  if (!response.ok)
9531
9922
  throw new Error(`texture fetch failed with status ${response.status} for ${url}`);
9532
- return await createImageBitmap(await response.blob());
9923
+ const blob = await response.blob();
9924
+ const bitmap = await createImageBitmap(blob, { premultiplyAlpha: "none", colorSpaceConversion: "none" });
9925
+ try {
9926
+ const { decodePngTexturePixels } = await import("./PngTexturePixels.js");
9927
+ const pixels = await decodePngTexturePixels(new Uint8Array(await blob.arrayBuffer()));
9928
+ if (pixels)
9929
+ productionPrimitiveBitmapPixels.set(bitmap, pixels);
9930
+ return bitmap;
9931
+ }
9932
+ catch (error) {
9933
+ bitmap.close();
9934
+ throw error;
9935
+ }
9533
9936
  }
9534
9937
  function bitmapRgbaPixels(bitmap) {
9938
+ const decoded = productionPrimitiveBitmapPixels.get(bitmap);
9939
+ if (decoded)
9940
+ return decoded;
9535
9941
  if (typeof document === "undefined")
9536
9942
  throw new Error("texture compositing requires a DOM canvas");
9537
9943
  const canvas = document.createElement("canvas");
@@ -9550,7 +9956,7 @@ function bitmapRgbaPixels(bitmap) {
9550
9956
  * textured, fallback, or skipped — is recorded on the resource with warnings.
9551
9957
  * Procedural inputs have no rasterizer: recorded + warned, never faked.
9552
9958
  */
9553
- export async function upgradeProductionPrimitiveTextures(entries, warn) {
9959
+ export async function upgradeProductionPrimitiveTextures(entries, warn, maxTextureSize = 4096) {
9554
9960
  for (const entry of entries) {
9555
9961
  for (const resource of entry.resources) {
9556
9962
  const spec = resource.materialSpec;
@@ -9560,14 +9966,14 @@ export async function upgradeProductionPrimitiveTextures(entries, warn) {
9560
9966
  // per-resource without duplicating into runtime warnings.
9561
9967
  resource.textureWarnings.push(`procedural texture ${procedural} on "${resource.name}" has no rasterizer; recorded only, scalar material retained`);
9562
9968
  }
9563
- const urls = [intent.baseColorUrl, intent.normalUrl, intent.roughnessUrl, intent.metalnessUrl, intent.occlusionUrl, intent.emissiveUrl].filter((url) => typeof url === "string" && url.length > 0);
9969
+ const urls = [intent.baseColorUrl, intent.normalUrl, intent.roughnessUrl, intent.metalnessUrl, intent.occlusionUrl, intent.emissiveUrl, ...Object.values(intent.extensionMaps ?? {}).map((map) => map.url)].filter((url) => typeof url === "string" && url.length > 0);
9564
9970
  if (urls.length === 0)
9565
9971
  continue;
9566
9972
  if (resource.textureStatus !== "none")
9567
9973
  continue;
9568
9974
  resource.textureStatus = "pending";
9569
9975
  try {
9570
- await upgradeProductionPrimitiveResource(resource, resource.sourceNode, spec, intent);
9976
+ await upgradeProductionPrimitiveResource(resource, resource.sourceNode, spec, intent, maxTextureSize);
9571
9977
  }
9572
9978
  catch (error) {
9573
9979
  resource.textureStatus = "fallback";
@@ -9633,140 +10039,208 @@ export async function upgradeProductionEnvironmentHdri(url, intensity, reflectio
9633
10039
  dualProbe: true
9634
10040
  };
9635
10041
  }
9636
- async function upgradeProductionPrimitiveResource(resource, node, spec, intent) {
10042
+ async function upgradeProductionPrimitiveResource(resource, node, spec, intent, maxTextureSize) {
9637
10043
  const fail = (message) => new Error(message);
9638
- if (resource.material instanceof InstancedPBRMaterial) {
9639
- throw fail(`instanced primitives have no textured material variant for "${resource.name}"`);
9640
- }
9641
10044
  if (!resource.geometry.vertexBuffer.format.hasAttribute("uv")) {
9642
10045
  throw fail(`"${resource.name}" geometry carries no uv set; textured upgrade needs generated uvs`);
9643
10046
  }
9644
10047
  const scalars = resolveProductionPrimitiveScalars(node);
9645
- const [baseColorSource, normalSource, roughnessSource, metalnessSource, occlusionSource, emissiveSource] = await Promise.all([
9646
- intent.baseColorUrl ? loadProductionPrimitiveBitmap(intent.baseColorUrl) : Promise.resolve(undefined),
9647
- intent.normalUrl ? loadProductionPrimitiveBitmap(intent.normalUrl) : Promise.resolve(undefined),
9648
- intent.roughnessUrl ? loadProductionPrimitiveBitmap(intent.roughnessUrl) : Promise.resolve(undefined),
9649
- intent.metalnessUrl ? loadProductionPrimitiveBitmap(intent.metalnessUrl) : Promise.resolve(undefined),
9650
- intent.occlusionUrl ? loadProductionPrimitiveBitmap(intent.occlusionUrl) : Promise.resolve(undefined),
9651
- intent.emissiveUrl ? loadProductionPrimitiveBitmap(intent.emissiveUrl) : Promise.resolve(undefined)
9652
- ]);
9653
- const slots = [];
9654
- const baseColorTexture = baseColorSource
9655
- ? new Texture({ width: baseColorSource.width, height: baseColorSource.height, source: baseColorSource, colorSpace: "srgb", label: `${resource.name}-basecolor` })
9656
- : undefined;
9657
- if (baseColorTexture)
9658
- slots.push("baseColor");
9659
- const normalTexture = normalSource
9660
- ? new Texture({ width: normalSource.width, height: normalSource.height, source: normalSource, colorSpace: "linear", label: `${resource.name}-normal` })
9661
- : undefined;
9662
- if (normalTexture)
9663
- slots.push("normal");
9664
- let metallicRoughnessTexture;
9665
- if (roughnessSource ?? metalnessSource) {
9666
- const base = roughnessSource ?? metalnessSource;
9667
- const size = { width: base.width, height: base.height };
9668
- const readPixels = (bitmap) => {
9669
- if (!bitmap)
9670
- return undefined;
9671
- const decoded = bitmapRgbaPixels(bitmap);
9672
- if (decoded.width !== size.width || decoded.height !== size.height) {
9673
- const canvas = document.createElement("canvas");
9674
- canvas.width = size.width;
9675
- canvas.height = size.height;
9676
- const context = canvas.getContext("2d", { willReadFrequently: true });
9677
- if (!context)
9678
- throw fail("2d canvas unavailable for texture compositing");
9679
- context.drawImage(bitmap, 0, 0, size.width, size.height);
9680
- return new Uint8Array(context.getImageData(0, 0, size.width, size.height).data);
10048
+ const extensionEntries = Object.entries(intent.extensionMaps ?? {});
10049
+ const needsScalarAtlas = extensionEntries.some(([slot]) => slot.startsWith("clearcoat"))
10050
+ && extensionEntries.some(([slot]) => slot.startsWith("sheen") || slot === "anisotropy")
10051
+ && extensionEntries.some(([slot]) => slot.startsWith("iridescence"));
10052
+ if (extensionEntries.some(([, map]) => map.texCoord === 1) && !resource.geometry.vertexBuffer.format.hasAttribute("uv1")) {
10053
+ throw fail(`"${resource.name}" geometry carries no uv1 set requested by extension texture`);
10054
+ }
10055
+ const ownedBitmaps = [];
10056
+ const ownedTextures = [];
10057
+ const bitmapLoads = new Map();
10058
+ const load = (url) => {
10059
+ let pending = bitmapLoads.get(url);
10060
+ if (!pending) {
10061
+ pending = loadProductionPrimitiveBitmap(url).then((bitmap) => {
10062
+ ownedBitmaps.push(bitmap);
10063
+ return bitmap;
10064
+ });
10065
+ bitmapLoads.set(url, pending);
10066
+ }
10067
+ return pending;
10068
+ };
10069
+ const own = (texture) => { ownedTextures.push(texture); return texture; };
10070
+ const disposeTextures = () => {
10071
+ for (const texture of ownedTextures)
10072
+ texture.dispose();
10073
+ for (const bitmap of ownedBitmaps)
10074
+ bitmap.close();
10075
+ ownedTextures.length = 0;
10076
+ ownedBitmaps.length = 0;
10077
+ };
10078
+ try {
10079
+ const baseResults = await Promise.allSettled([
10080
+ intent.baseColorUrl ? load(intent.baseColorUrl) : Promise.resolve(undefined),
10081
+ intent.normalUrl ? load(intent.normalUrl) : Promise.resolve(undefined),
10082
+ intent.roughnessUrl ? load(intent.roughnessUrl) : Promise.resolve(undefined),
10083
+ intent.metalnessUrl ? load(intent.metalnessUrl) : Promise.resolve(undefined),
10084
+ intent.occlusionUrl ? load(intent.occlusionUrl) : Promise.resolve(undefined),
10085
+ intent.emissiveUrl ? load(intent.emissiveUrl) : Promise.resolve(undefined)
10086
+ ]);
10087
+ const failure = baseResults.find((result) => result.status === "rejected");
10088
+ if (failure?.status === "rejected")
10089
+ throw failure.reason;
10090
+ const [baseColorSource, normalSource, roughnessSource, metalnessSource, occlusionSource, emissiveSource] = baseResults.map((result) => result.status === "fulfilled" ? result.value : undefined);
10091
+ const slots = [];
10092
+ const extensionOptions = {};
10093
+ const extensionSources = [];
10094
+ const atlasPixels = {};
10095
+ for (const [slot, map] of extensionEntries) {
10096
+ const source = await load(map.url);
10097
+ extensionSources.push(source);
10098
+ if (needsScalarAtlas && ["clearcoat", "clearcoatRoughness", "sheenRoughness", "iridescence", "iridescenceThickness"].includes(slot)) {
10099
+ Object.assign(atlasPixels, { [slot]: bitmapRgbaPixels(source) });
9681
10100
  }
9682
- return decoded.data;
9683
- };
9684
- const composited = compositeMetallicRoughnessPixels(readPixels(roughnessSource), readPixels(metalnessSource), size.width * size.height, spec?.roughness ?? 0.58, spec?.metallic ?? spec?.metalness ?? 0);
9685
- metallicRoughnessTexture = new Texture({ width: size.width, height: size.height, data: composited, colorSpace: "linear", label: `${resource.name}-metallicroughness` });
9686
- slots.push("metallicRoughness");
9687
- }
9688
- const occlusionTexture = occlusionSource
9689
- ? new Texture({ width: occlusionSource.width, height: occlusionSource.height, source: occlusionSource, colorSpace: "linear", label: `${resource.name}-occlusion` })
9690
- : undefined;
9691
- if (occlusionTexture)
9692
- slots.push("occlusion");
9693
- const emissiveTexture = emissiveSource
9694
- ? new Texture({ width: emissiveSource.width, height: emissiveSource.height, source: emissiveSource, colorSpace: "srgb", label: `${resource.name}-emissive` })
9695
- : undefined;
9696
- if (emissiveTexture)
9697
- slots.push("emissive");
9698
- if (slots.length === 0)
9699
- throw fail(`no texture resolved for "${resource.name}"`);
9700
- // M2 streaming table: resident bytes from the decoded sources (base +
9701
- // full mip-chain estimate) with the chain keyed off the largest source.
9702
- const loadedDims = [baseColorSource, normalSource, roughnessSource, metalnessSource, occlusionSource, emissiveSource]
9703
- .filter((source) => source !== undefined)
9704
- .map((source) => ({ width: source.width, height: source.height }));
9705
- const loadedBaseBytes = loadedDims.reduce((total, dims) => total + dims.width * dims.height * 4, 0);
9706
- const largestDims = loadedDims.reduce((largest, dims) => (dims.width * dims.height > largest.width * largest.height ? dims : largest), { width: 1, height: 1 });
9707
- resource.textureBytes = Math.round(loadedBaseBytes * (4 / 3));
9708
- resource.textureMipBytes = mipChainBytesCoarseToFine(largestDims.width, largestDims.height);
9709
- const texCoords = spec?.texCoords;
9710
- // C3: every root textured slot shares one capability-gated sampler request
9711
- // (default 8x where supported; the renderer clamps to the device maximum).
9712
- const textureSampler = new Sampler({
9713
- maxAnisotropy: resolveSamplerAnisotropy({ desired: spec?.textureAnisotropy }).applied
9714
- });
9715
- resource.texturedMaterial = new TexturedPBRMaterial({
9716
- name: `a3d-production-textured-primitive-${resource.name}`,
9717
- baseColor: [scalars.baseColor[0], scalars.baseColor[1], scalars.baseColor[2], scalars.opacity],
9718
- metallic: clamp01(spec?.metallic ?? spec?.metalness ?? 0),
9719
- roughness: clamp01(spec?.roughness ?? 0.58),
9720
- emissiveColor: spec?.emissive
9721
- ? [scalars.emissiveColor[0], scalars.emissiveColor[1], scalars.emissiveColor[2]]
9722
- : emissiveTexture ? [1, 1, 1] : [scalars.emissiveColor[0], scalars.emissiveColor[1], scalars.emissiveColor[2]],
9723
- emissiveStrength: Math.max(0, spec?.emissiveIntensity ?? (spec?.emissive || emissiveTexture ? 1.35 : 0)),
9724
- occlusionStrength: clamp01(spec?.occlusionStrength ?? 1),
9725
- clearcoatFactor: scalars.clearcoat,
9726
- clearcoatRoughnessFactor: clamp01(spec?.clearcoatRoughness ?? 0.34),
9727
- sheenColorFactor: [scalars.sheenColor[0], scalars.sheenColor[1], scalars.sheenColor[2]],
9728
- sheenRoughnessFactor: clamp01(spec?.sheenRoughness ?? 0.3),
9729
- anisotropyStrength: scalars.anisotropy,
9730
- anisotropyRotation: spec?.anisotropyRotation ?? 0,
9731
- iridescenceFactor: scalars.iridescence,
9732
- iridescenceIor: Math.max(1, spec?.iridescenceIOR ?? 1.3),
9733
- ...(scalars.thicknessRange ? { iridescenceThicknessMinimum: scalars.thicknessRange[0], iridescenceThicknessMaximum: scalars.thicknessRange[1] } : {}),
9734
- transmissionFactor: scalars.transmission,
9735
- ...(spec?.thickness === undefined ? {} : { volumeThicknessFactor: Math.max(0, spec.thickness) }),
9736
- ...(spec?.ior === undefined ? {} : { ior: Math.max(1, spec.ior) }),
9737
- ...(spec?.attenuationColor ? { volumeAttenuationColor: colorToLinearRgb(spec.attenuationColor) } : {}),
9738
- ...(spec?.attenuationDistance === undefined ? {} : { volumeAttenuationDistance: Math.max(0, spec.attenuationDistance) }),
9739
- environmentIntensity: scalars.environmentIntensity,
9740
- envMapIntensity: scalars.envMapIntensity,
9741
- renderState: {
9742
- blend: scalars.opacity < 0.999,
9743
- depthWrite: scalars.opacity >= 0.999,
9744
- cullMode: node.primitive === "plane" || scalars.opacity < 0.999 ? "none" : "back"
9745
- },
9746
- ...(baseColorTexture ? { baseColorTexture, baseColorSampler: textureSampler } : {}),
9747
- ...(normalTexture ? { normalTexture, normalSampler: textureSampler, normalScale: spec?.normalScale ?? 1 } : {}),
9748
- ...(metallicRoughnessTexture ? { metallicRoughnessTexture, metallicRoughnessSampler: textureSampler } : {}),
9749
- ...(occlusionTexture ? { occlusionTexture, occlusionSampler: textureSampler } : {}),
9750
- ...(emissiveTexture ? { emissiveTexture, emissiveSampler: textureSampler } : {}),
9751
- ...(spec?.texTransforms?.baseColor ? { baseColorTextureTransform: { ...spec.texTransforms.baseColor } } : {}),
9752
- ...(spec?.texTransforms?.normal ? { normalTextureTransform: { ...spec.texTransforms.normal } } : {}),
9753
- ...(spec?.texTransforms?.metallicRoughness ? { metallicRoughnessTextureTransform: { ...spec.texTransforms.metallicRoughness } } : {}),
9754
- ...(spec?.texTransforms?.occlusion ? { occlusionTextureTransform: { ...spec.texTransforms.occlusion } } : {}),
9755
- ...(spec?.texTransforms?.emissive ? { emissiveTextureTransform: { ...spec.texTransforms.emissive } } : {}),
9756
- ...((texCoords?.baseColor ?? 0) > 0 || (texCoords?.normal ?? 0) > 0 || (texCoords?.metallicRoughness ?? 0) > 0 || (texCoords?.occlusion ?? 0) > 0 || (texCoords?.emissive ?? 0) > 0
9757
- ? {
9758
- textureTexCoords: {
9759
- ...(baseColorTexture && (texCoords?.baseColor ?? 0) > 0 ? { baseColor: 1 } : {}),
9760
- ...(normalTexture && (texCoords?.normal ?? 0) > 0 ? { normal: 1 } : {}),
9761
- ...(metallicRoughnessTexture && (texCoords?.metallicRoughness ?? 0) > 0 ? { metallicRoughness: 1 } : {}),
9762
- ...(occlusionTexture && (texCoords?.occlusion ?? 0) > 0 ? { occlusion: 1 } : {}),
9763
- ...(emissiveTexture && (texCoords?.emissive ?? 0) > 0 ? { emissive: 1 } : {})
10101
+ extensionOptions[`${slot}Texture`] = own(new Texture({ width: source.width, height: source.height, source, colorSpace: map.colorSpace, label: `${resource.name}-${slot}` }));
10102
+ if (map.transform)
10103
+ extensionOptions[`${slot}TextureTransform`] = { ...map.transform };
10104
+ slots.push(slot);
10105
+ }
10106
+ if (needsScalarAtlas) {
10107
+ const { createExtensionScalarAtlas } = await import("../../rendering/extension-scalar-atlas.js");
10108
+ const atlas = createExtensionScalarAtlas(atlasPixels, maxTextureSize);
10109
+ own(atlas.texture);
10110
+ extensionOptions.extensionScalarAtlas = atlas;
10111
+ }
10112
+ const baseColorTexture = baseColorSource
10113
+ ? own(new Texture({ width: baseColorSource.width, height: baseColorSource.height, source: baseColorSource, colorSpace: "srgb", label: `${resource.name}-basecolor` }))
10114
+ : undefined;
10115
+ if (baseColorTexture)
10116
+ slots.push("baseColor");
10117
+ const normalTexture = normalSource
10118
+ ? own(new Texture({ width: normalSource.width, height: normalSource.height, source: normalSource, colorSpace: "linear", label: `${resource.name}-normal` }))
10119
+ : undefined;
10120
+ if (normalTexture)
10121
+ slots.push("normal");
10122
+ let metallicRoughnessTexture;
10123
+ if (roughnessSource ?? metalnessSource) {
10124
+ const base = roughnessSource ?? metalnessSource;
10125
+ const size = { width: base.width, height: base.height };
10126
+ const readPixels = (bitmap) => {
10127
+ if (!bitmap)
10128
+ return undefined;
10129
+ const decoded = bitmapRgbaPixels(bitmap);
10130
+ if (decoded.width !== size.width || decoded.height !== size.height) {
10131
+ const canvas = document.createElement("canvas");
10132
+ canvas.width = size.width;
10133
+ canvas.height = size.height;
10134
+ const context = canvas.getContext("2d", { willReadFrequently: true });
10135
+ if (!context)
10136
+ throw fail("2d canvas unavailable for texture compositing");
10137
+ context.drawImage(bitmap, 0, 0, size.width, size.height);
10138
+ return new Uint8Array(context.getImageData(0, 0, size.width, size.height).data);
9764
10139
  }
9765
- }
9766
- : {})
9767
- });
9768
- resource.textureStatus = "textured";
9769
- resource.textureSlots = slots;
10140
+ return decoded.data;
10141
+ };
10142
+ const composited = compositeMetallicRoughnessPixels(readPixels(roughnessSource), readPixels(metalnessSource), size.width * size.height, spec?.roughness ?? 0.58, spec?.metallic ?? spec?.metalness ?? 0);
10143
+ metallicRoughnessTexture = own(new Texture({ width: size.width, height: size.height, data: composited, colorSpace: "linear", label: `${resource.name}-metallicroughness` }));
10144
+ slots.push("metallicRoughness");
10145
+ }
10146
+ const occlusionTexture = occlusionSource
10147
+ ? own(new Texture({ width: occlusionSource.width, height: occlusionSource.height, source: occlusionSource, colorSpace: "linear", label: `${resource.name}-occlusion` }))
10148
+ : undefined;
10149
+ if (occlusionTexture)
10150
+ slots.push("occlusion");
10151
+ const emissiveTexture = emissiveSource
10152
+ ? own(new Texture({ width: emissiveSource.width, height: emissiveSource.height, source: emissiveSource, colorSpace: "srgb", label: `${resource.name}-emissive` }))
10153
+ : undefined;
10154
+ if (emissiveTexture)
10155
+ slots.push("emissive");
10156
+ if (slots.length === 0)
10157
+ throw fail(`no texture resolved for "${resource.name}"`);
10158
+ // M2 streaming table: resident bytes from the decoded sources (base +
10159
+ // full mip-chain estimate) with the chain keyed off the largest source.
10160
+ const loadedDims = [baseColorSource, normalSource, roughnessSource, metalnessSource, occlusionSource, emissiveSource, ...extensionSources]
10161
+ .filter((source) => source !== undefined)
10162
+ .map((source) => ({ width: source.width, height: source.height }));
10163
+ const loadedBaseBytes = loadedDims.reduce((total, dims) => total + dims.width * dims.height * 4, 0);
10164
+ const largestDims = loadedDims.reduce((largest, dims) => (dims.width * dims.height > largest.width * largest.height ? dims : largest), { width: 1, height: 1 });
10165
+ resource.textureBytes = Math.round(loadedBaseBytes * (4 / 3));
10166
+ resource.textureMipBytes = mipChainBytesCoarseToFine(largestDims.width, largestDims.height);
10167
+ const texCoords = spec?.texCoords;
10168
+ // C3: every root textured slot shares one capability-gated sampler request
10169
+ // (default 8x where supported; the renderer clamps to the device maximum).
10170
+ const textureSampler = new Sampler({
10171
+ maxAnisotropy: resolveSamplerAnisotropy({ desired: spec?.textureAnisotropy }).applied
10172
+ });
10173
+ for (const [slot] of extensionEntries)
10174
+ extensionOptions[`${slot}Sampler`] = textureSampler;
10175
+ resource.texturedMaterial = new TexturedPBRMaterial({
10176
+ name: `a3d-production-textured-primitive-${resource.name}`,
10177
+ baseColor: [scalars.baseColor[0], scalars.baseColor[1], scalars.baseColor[2], scalars.opacity],
10178
+ metallic: clamp01(spec?.metallic ?? spec?.metalness ?? 0),
10179
+ roughness: clamp01(spec?.roughness ?? 0.58),
10180
+ emissiveColor: spec?.emissive
10181
+ ? [scalars.emissiveColor[0], scalars.emissiveColor[1], scalars.emissiveColor[2]]
10182
+ : emissiveTexture ? [1, 1, 1] : [scalars.emissiveColor[0], scalars.emissiveColor[1], scalars.emissiveColor[2]],
10183
+ emissiveStrength: Math.max(0, spec?.emissiveIntensity ?? (spec?.emissive || emissiveTexture ? 1.35 : 0)),
10184
+ occlusionStrength: clamp01(spec?.occlusionStrength ?? 1),
10185
+ clearcoatFactor: scalars.clearcoat,
10186
+ clearcoatRoughnessFactor: clamp01(spec?.clearcoatRoughness ?? 0.34),
10187
+ clearcoatNormalScale: Math.max(0, spec?.clearcoatNormalScale ?? 1),
10188
+ ...extensionOptions,
10189
+ sheenColorFactor: [scalars.sheenColor[0], scalars.sheenColor[1], scalars.sheenColor[2]],
10190
+ sheenRoughnessFactor: clamp01(spec?.sheenRoughness ?? 0.3),
10191
+ anisotropyStrength: scalars.anisotropy,
10192
+ anisotropyRotation: spec?.anisotropyRotation ?? 0,
10193
+ iridescenceFactor: scalars.iridescence,
10194
+ iridescenceIor: Math.max(1, spec?.iridescenceIOR ?? 1.3),
10195
+ ...(scalars.thicknessRange ? { iridescenceThicknessMinimum: scalars.thicknessRange[0], iridescenceThicknessMaximum: scalars.thicknessRange[1] } : {}),
10196
+ transmissionFactor: scalars.transmission,
10197
+ ...(spec?.thickness === undefined ? {} : { volumeThicknessFactor: Math.max(0, spec.thickness) }),
10198
+ ...(spec?.ior === undefined ? {} : { ior: Math.max(1, spec.ior) }),
10199
+ ...(spec?.attenuationColor ? { volumeAttenuationColor: colorToLinearRgb(spec.attenuationColor) } : {}),
10200
+ ...(spec?.attenuationDistance === undefined ? {} : { volumeAttenuationDistance: Math.max(0, spec.attenuationDistance) }),
10201
+ environmentIntensity: scalars.environmentIntensity,
10202
+ envMapIntensity: scalars.envMapIntensity,
10203
+ renderState: {
10204
+ blend: scalars.opacity < 0.999,
10205
+ depthWrite: scalars.opacity >= 0.999,
10206
+ cullMode: node.primitive === "plane" || scalars.opacity < 0.999 ? "none" : "back"
10207
+ },
10208
+ ...(baseColorTexture ? { baseColorTexture, baseColorSampler: textureSampler } : {}),
10209
+ ...(normalTexture ? { normalTexture, normalSampler: textureSampler, normalScale: spec?.normalScale ?? 1 } : {}),
10210
+ ...(metallicRoughnessTexture ? { metallicRoughnessTexture, metallicRoughnessSampler: textureSampler } : {}),
10211
+ ...(occlusionTexture ? { occlusionTexture, occlusionSampler: textureSampler } : {}),
10212
+ ...(emissiveTexture ? { emissiveTexture, emissiveSampler: textureSampler } : {}),
10213
+ ...(spec?.texTransforms?.baseColor ? { baseColorTextureTransform: { ...spec.texTransforms.baseColor } } : {}),
10214
+ ...(spec?.texTransforms?.normal ? { normalTextureTransform: { ...spec.texTransforms.normal } } : {}),
10215
+ ...(spec?.texTransforms?.metallicRoughness ? { metallicRoughnessTextureTransform: { ...spec.texTransforms.metallicRoughness } } : {}),
10216
+ ...(spec?.texTransforms?.occlusion ? { occlusionTextureTransform: { ...spec.texTransforms.occlusion } } : {}),
10217
+ ...(spec?.texTransforms?.emissive ? { emissiveTextureTransform: { ...spec.texTransforms.emissive } } : {}),
10218
+ ...((texCoords?.baseColor ?? 0) > 0 || (texCoords?.normal ?? 0) > 0 || (texCoords?.metallicRoughness ?? 0) > 0 || (texCoords?.occlusion ?? 0) > 0 || (texCoords?.emissive ?? 0) > 0 || extensionEntries.length > 0
10219
+ ? {
10220
+ textureTexCoords: {
10221
+ ...Object.fromEntries(extensionEntries.map(([slot, map]) => [slot, map.texCoord])),
10222
+ ...(baseColorTexture && (texCoords?.baseColor ?? 0) > 0 ? { baseColor: 1 } : {}),
10223
+ ...(normalTexture && (texCoords?.normal ?? 0) > 0 ? { normal: 1 } : {}),
10224
+ ...(metallicRoughnessTexture && (texCoords?.metallicRoughness ?? 0) > 0 ? { metallicRoughness: 1 } : {}),
10225
+ ...(occlusionTexture && (texCoords?.occlusion ?? 0) > 0 ? { occlusion: 1 } : {}),
10226
+ ...(emissiveTexture && (texCoords?.emissive ?? 0) > 0 ? { emissive: 1 } : {})
10227
+ }
10228
+ }
10229
+ : {})
10230
+ });
10231
+ if (resource.material.disposed) {
10232
+ resource.texturedMaterial.dispose();
10233
+ resource.texturedMaterial = null;
10234
+ throw fail("primitive disposed while texture upgrade was pending");
10235
+ }
10236
+ resource.textureDisposer = disposeTextures;
10237
+ resource.textureStatus = "textured";
10238
+ resource.textureSlots = slots;
10239
+ }
10240
+ catch (error) {
10241
+ disposeTextures();
10242
+ throw error;
10243
+ }
9770
10244
  }
9771
10245
  export function compositeMetallicRoughnessPixels(rough, metal, pixelCount, roughnessScalar, metalnessScalar) {
9772
10246
  const out = new Uint8Array(pixelCount * 4);
@@ -10005,12 +10479,12 @@ function createProductionPrimitiveResources(node) {
10005
10479
  return levelFallback;
10006
10480
  });
10007
10481
  }
10008
- function selectProductionPrimitiveResource(entry, node, cameraPosition) {
10482
+ function selectProductionPrimitiveResource(entry, node, cameraPosition, lodBias = 1) {
10009
10483
  if (!node.lod?.levels.length)
10010
10484
  return entry.resources[0];
10011
10485
  const position = node.position ?? [0, 0, 0];
10012
10486
  const distance = Math.hypot(cameraPosition[0] - position[0], cameraPosition[1] - position[1], cameraPosition[2] - position[2]);
10013
- const selection = selectAuraRootLodLevel(distance, node.lod.levels, entry.currentLodIndex, node.lod.hysteresis ?? 0);
10487
+ const selection = selectAuraRootLodLevel(distance * lodBias, node.lod.levels, entry.currentLodIndex, node.lod.hysteresis ?? 0);
10014
10488
  entry.currentLodIndex = selection.levelIndex;
10015
10489
  return entry.resources[selection.levelIndex] ?? entry.resources[entry.resources.length - 1];
10016
10490
  }
@@ -10266,10 +10740,12 @@ function resolveProductionActorRuntimeState(entry, runtimeNodes) {
10266
10740
  animation: runtimeSnapshot.animation ?? entry.node.animation
10267
10741
  },
10268
10742
  animationBinding: runtimeSnapshot.animationBinding,
10743
+ animationPose: runtimeSnapshot.animationPose,
10744
+ animationPoseTime: runtimeSnapshot.animationPoseBinding?.localTime ?? runtimeSnapshot.animationPoseBinding?.captureTime,
10269
10745
  ...(Object.keys(morphTargets).length > 0 ? { morphTargets } : {})
10270
10746
  };
10271
10747
  }
10272
- function applyProductionActorAnimation(entry, node, animationBinding, time, runtimeWarnings) {
10748
+ function applyProductionActorAnimation(entry, node, animationBinding, time, runtimeWarnings, modelMatrix, refreshAfterMovement) {
10273
10749
  const animation = node.animation;
10274
10750
  if (!animation?.clip || isModelTransformAnimationClip(animation.clip))
10275
10751
  return;
@@ -10279,7 +10755,46 @@ function applyProductionActorAnimation(entry, node, animationBinding, time, runt
10279
10755
  return;
10280
10756
  }
10281
10757
  try {
10282
- entry.actor.playClip(clipName, resolveProductionActorAnimationSeconds(animation, animationBinding, time));
10758
+ const binding = animationBinding?.rootMotion;
10759
+ if (binding && animationBinding) {
10760
+ const observed = animationBinding.rootMotionSamples ?? [{
10761
+ playbackId: animationBinding.playbackId ?? clipName, clipName,
10762
+ time: animationBinding.rootMotionTime ?? 0, weight: 1,
10763
+ loop: animationBinding.loop ?? animation.loop ?? true, additive: false
10764
+ }];
10765
+ if (observed.length === 0) {
10766
+ entry.rootMotionCursors = undefined;
10767
+ return;
10768
+ }
10769
+ const cursors = entry.rootMotionCursors ?? new Map();
10770
+ const samples = observed.map(sample => {
10771
+ if (!Number.isFinite(sample.time) || sample.time < 0)
10772
+ throw new Error("Root motion requires a finite nonnegative unwrapped controller time.");
10773
+ const previous = cursors.get(sample.playbackId);
10774
+ return { clipName: sample.clipName, target: binding.target,
10775
+ fromTime: previous === undefined ? 0 : Math.min(previous, sample.time),
10776
+ toTime: sample.time, weight: sample.weight, loop: sample.loop, additive: sample.additive };
10777
+ });
10778
+ const commitCursors = () => { entry.rootMotionCursors = new Map(observed.map(sample => [sample.playbackId, sample.time])); };
10779
+ const result = entry.actor.playRootMotionClips(samples, {
10780
+ worldFromLocal: [...modelMatrix],
10781
+ move: requested => {
10782
+ if (samples.every(sample => sample.fromTime === sample.toTime))
10783
+ return [0, 0, 0];
10784
+ const accepted = binding.move(requested);
10785
+ // Advance cursors before pose/IK work: retrying a failed pose cannot move twice.
10786
+ commitCursors();
10787
+ refreshAfterMovement();
10788
+ return accepted;
10789
+ }
10790
+ });
10791
+ commitCursors();
10792
+ binding.onSample?.(result.motion);
10793
+ }
10794
+ else {
10795
+ entry.rootMotionCursors = undefined;
10796
+ entry.actor.playClip(clipName, resolveProductionActorAnimationSeconds(animation, animationBinding, time));
10797
+ }
10283
10798
  }
10284
10799
  catch (error) {
10285
10800
  runtimeWarnings.add(`Typed GLB actor "${entry.actor.id}" failed to apply clip "${clipName}": ${productionRenderErrorMessage(error)}`);
@@ -10298,7 +10813,7 @@ function applyProductionActorFootPlanting(entry, animationBinding, modelMatrix,
10298
10813
  try {
10299
10814
  entry.actor.animation.setFootPlanting((footPlanting === undefined
10300
10815
  ? undefined
10301
- : { ...footPlanting, ...spreadFootPlantingWorldMatrix(modelMatrix) }));
10816
+ : { ...footPlanting, legs: footPlanting.legs.map(leg => ({ ...leg, ...(leg.contactPhase ? { contact: leg.contactPhase(animationBinding?.localTime ?? 0) } : {}) })), ...spreadFootPlantingWorldMatrix(modelMatrix) }));
10302
10817
  }
10303
10818
  catch (error) {
10304
10819
  runtimeWarnings.add(`Typed GLB actor "${entry.actor.id}" failed to apply foot planting: ${productionRenderErrorMessage(error)}`);
@@ -10372,6 +10887,9 @@ function createRuntimeEvidenceFromTypedGLBActor(node, actor, renderItems) {
10372
10887
  lastFootPlantingTargetError: actorEvidence.lastFootPlantingTargetError,
10373
10888
  lastFootPlantingHipOffset: actorEvidence.lastFootPlantingHipOffset,
10374
10889
  lastFootPlantingMissingLegs: [...actorEvidence.lastFootPlantingMissingLegs],
10890
+ lastFootPlantingFeet: actorEvidence.lastFootPlantingFeet,
10891
+ lastFootPlantingSurfaces: actorEvidence.lastFootPlantingSurfaces,
10892
+ lastFootPlantingDeformation: actorEvidence.lastFootPlantingDeformation,
10375
10893
  footPlantingConfigured: actorEvidence.footPlantingConfigured
10376
10894
  });
10377
10895
  }
@@ -12540,11 +13058,7 @@ function createViewProjection(snapshot, aspect, time, runtimeNodes) {
12540
13058
  const cameraSpec = snapshot.camera;
12541
13059
  const { target, eye } = resolveCameraFrame(snapshot, cameraSpec, time, runtimeNodes);
12542
13060
  const view = lookAtMat4([...eye], [...target], [0, 1, 0]);
12543
- const halfHeight = Math.max(1e-4, cameraSpec.orthographicSize ?? 1.4);
12544
- const halfWidth = Math.max(1e-4, halfHeight * aspect);
12545
- const projection = isOrthographicCameraMode(cameraSpec.mode)
12546
- ? orthographicMat4(-halfWidth, halfWidth, -halfHeight, halfHeight, 0.05, 100)
12547
- : perspectiveMat4(((cameraSpec.fov ?? 45) * Math.PI) / 180, aspect, 0.05, 100);
13061
+ const projection = createCameraProjection(cameraSpec, aspect);
12548
13062
  return new Float32Array(multiplyMat4(projection, view));
12549
13063
  }
12550
13064
  function createModelMatrix(node, bounds, normalizeToUnit, time = 0) {
@@ -12617,6 +13131,10 @@ function isModelTransformAnimationClip(clip) {
12617
13131
  return clip === "turntable" || clip === "float" || clip === "orbit";
12618
13132
  }
12619
13133
  function primitiveSize(node) {
13134
+ // SDF quad positions are already authored in world units by layoutSdfText.
13135
+ // Applying the public text size again here shrinks/grows the GPU quad twice.
13136
+ if (node.text3D?.backend === "sdf")
13137
+ return [1, 1, 1];
12620
13138
  if (typeof node.size === "number")
12621
13139
  return [node.size, node.size, node.size];
12622
13140
  return node.size ?? [1, 1, 1];
@@ -12950,17 +13468,7 @@ function collectGeneratedCodeWarnings(snapshot) {
12950
13468
  if (!snapshot.nodes.some((node) => node.kind === "interaction")) {
12951
13469
  warnings.push("Scene has no interactions. Suggested fix: add interactions.orbit() for product/viewer scenes.");
12952
13470
  }
12953
- // muse3jsparity-PRD A3: withheld postprocess intents must warn on the
12954
- // mounted channel too (plan-level warnings never reach app.diagnostics()).
12955
13471
  const flatNodes = groups.flatten(snapshot.nodes);
12956
- const hasEffect = (effect) => flatNodes.some((node) => node.kind === "effect" && node.effect === effect);
12957
- if (hasEffect("motion-blur")) {
12958
- warnings.push("motion-blur is recorded but withheld: root has no velocity binding, so no motion-blur pass is submitted");
12959
- }
12960
- const antiAlias = flatNodes.find((node) => node.kind === "effect" && node.effect === "anti-alias");
12961
- if (antiAlias && (antiAlias.mode ?? "fxaa") === "taa") {
12962
- warnings.push("anti-alias mode \"taa\" is recorded but withheld: root has no history binding, so no taa pass is submitted");
12963
- }
12964
13472
  // muse3jsparity-PRD C1: procedural texture inputs have no rasterizer. The
12965
13473
  // intent is compile-time known, so warn here; fetch outcomes stay dynamic.
12966
13474
  for (const node of flatNodes) {