@aura3d/engine 2.0.4 → 3.0.0

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 (651) hide show
  1. package/README.md +25 -10
  2. package/dist/animation/AnimationAction.d.ts.map +1 -1
  3. package/dist/animation/AnimationAction.js +20 -2
  4. package/dist/animation/AnimationAction.js.map +1 -1
  5. package/dist/animation/AnimationLayer.d.ts +1 -0
  6. package/dist/animation/AnimationLayer.d.ts.map +1 -1
  7. package/dist/animation/AnimationLayer.js +24 -3
  8. package/dist/animation/AnimationLayer.js.map +1 -1
  9. package/dist/animation/AnimationMixer.d.ts +3 -0
  10. package/dist/animation/AnimationMixer.d.ts.map +1 -1
  11. package/dist/animation/AnimationMixer.js +69 -5
  12. package/dist/animation/AnimationMixer.js.map +1 -1
  13. package/dist/animation/AnimationTrack.d.ts.map +1 -1
  14. package/dist/animation/AnimationTrack.js +13 -0
  15. package/dist/animation/AnimationTrack.js.map +1 -1
  16. package/dist/animation/FootIk.d.ts +10 -0
  17. package/dist/animation/FootIk.d.ts.map +1 -1
  18. package/dist/animation/FootIk.js +95 -3
  19. package/dist/animation/FootIk.js.map +1 -1
  20. package/dist/animation/HumanoidRetargeting.d.ts +29 -0
  21. package/dist/animation/HumanoidRetargeting.d.ts.map +1 -1
  22. package/dist/animation/HumanoidRetargeting.js +41 -1
  23. package/dist/animation/HumanoidRetargeting.js.map +1 -1
  24. package/dist/animation/LocomotionKit.d.ts +6 -0
  25. package/dist/animation/LocomotionKit.d.ts.map +1 -1
  26. package/dist/animation/LocomotionKit.js +6 -0
  27. package/dist/animation/LocomotionKit.js.map +1 -1
  28. package/dist/animation/RootMotion.d.ts +27 -0
  29. package/dist/animation/RootMotion.d.ts.map +1 -1
  30. package/dist/animation/RootMotion.js +38 -0
  31. package/dist/animation/RootMotion.js.map +1 -1
  32. package/dist/animation/SpringBones.d.ts +28 -0
  33. package/dist/animation/SpringBones.d.ts.map +1 -1
  34. package/dist/animation/SpringBones.js +24 -0
  35. package/dist/animation/SpringBones.js.map +1 -1
  36. package/dist/assets/GLTFAnimationRuntime.d.ts +95 -2
  37. package/dist/assets/GLTFAnimationRuntime.d.ts.map +1 -1
  38. package/dist/assets/GLTFAnimationRuntime.js +328 -3
  39. package/dist/assets/GLTFAnimationRuntime.js.map +1 -1
  40. package/dist/assets/GLTFExtensionSupport.js +2 -2
  41. package/dist/assets/GLTFExtensionSupport.js.map +1 -1
  42. package/dist/assets/GLTFLoader.d.ts +360 -1
  43. package/dist/assets/GLTFLoader.d.ts.map +1 -1
  44. package/dist/assets/GLTFLoader.js +197 -1
  45. package/dist/assets/GLTFLoader.js.map +1 -1
  46. package/dist/assets/KTX2BasisTextureTranscoder.d.ts +39 -0
  47. package/dist/assets/KTX2BasisTextureTranscoder.d.ts.map +1 -1
  48. package/dist/assets/KTX2BasisTextureTranscoder.js +70 -0
  49. package/dist/assets/KTX2BasisTextureTranscoder.js.map +1 -1
  50. package/dist/assets/TexturePipeline.d.ts +20 -0
  51. package/dist/assets/TexturePipeline.d.ts.map +1 -1
  52. package/dist/assets/TexturePipeline.js +20 -0
  53. package/dist/assets/TexturePipeline.js.map +1 -1
  54. package/dist/assets/TextureStreaming.d.ts +30 -0
  55. package/dist/assets/TextureStreaming.d.ts.map +1 -1
  56. package/dist/assets/TextureStreaming.js +53 -0
  57. package/dist/assets/TextureStreaming.js.map +1 -1
  58. package/dist/assets/browser-index.d.ts +7 -3
  59. package/dist/assets/browser-index.d.ts.map +1 -1
  60. package/dist/assets/browser-index.js +3 -1
  61. package/dist/assets/browser-index.js.map +1 -1
  62. package/dist/assets/gltf-runtime.d.ts +1 -1
  63. package/dist/assets/gltf-runtime.d.ts.map +1 -1
  64. package/dist/assets/gltf-runtime.js.map +1 -1
  65. package/dist/assets/index.d.ts +7 -7
  66. package/dist/assets/index.d.ts.map +1 -1
  67. package/dist/assets/index.js +3 -3
  68. package/dist/assets/index.js.map +1 -1
  69. package/dist/assets/loaders/HDRLoader.d.ts +40 -0
  70. package/dist/assets/loaders/HDRLoader.d.ts.map +1 -1
  71. package/dist/assets/loaders/HDRLoader.js +158 -0
  72. package/dist/assets/loaders/HDRLoader.js.map +1 -1
  73. package/dist/audio/AudioSource.d.ts +2 -0
  74. package/dist/audio/AudioSource.d.ts.map +1 -1
  75. package/dist/audio/AudioSource.js +7 -0
  76. package/dist/audio/AudioSource.js.map +1 -1
  77. package/dist/audio/Footsteps.d.ts +47 -0
  78. package/dist/audio/Footsteps.d.ts.map +1 -0
  79. package/dist/audio/Footsteps.js +73 -0
  80. package/dist/audio/Footsteps.js.map +1 -0
  81. package/dist/audio/GameMixer.d.ts +69 -0
  82. package/dist/audio/GameMixer.d.ts.map +1 -0
  83. package/dist/audio/GameMixer.js +141 -0
  84. package/dist/audio/GameMixer.js.map +1 -0
  85. package/dist/audio/PositionalEmitter.d.ts +95 -0
  86. package/dist/audio/PositionalEmitter.d.ts.map +1 -0
  87. package/dist/audio/PositionalEmitter.js +199 -0
  88. package/dist/audio/PositionalEmitter.js.map +1 -0
  89. package/dist/audio/index.d.ts +6 -0
  90. package/dist/audio/index.d.ts.map +1 -1
  91. package/dist/audio/index.js +3 -0
  92. package/dist/audio/index.js.map +1 -1
  93. package/dist/aura3d-cli/pull-bridge/profiles.d.ts +12 -2
  94. package/dist/aura3d-cli/pull-bridge/profiles.d.ts.map +1 -1
  95. package/dist/aura3d-cli/pull-bridge/profiles.js +35 -4
  96. package/dist/aura3d-cli/pull-bridge/profiles.js.map +1 -1
  97. package/dist/aura3d-cli/pull-bridge/provenance.d.ts +15 -1
  98. package/dist/aura3d-cli/pull-bridge/provenance.d.ts.map +1 -1
  99. package/dist/aura3d-cli/pull-bridge/provenance.js +60 -1
  100. package/dist/aura3d-cli/pull-bridge/provenance.js.map +1 -1
  101. package/dist/aura3d-cli/pull-bridge/scoring.d.ts +10 -1
  102. package/dist/aura3d-cli/pull-bridge/scoring.d.ts.map +1 -1
  103. package/dist/aura3d-cli/pull-bridge/scoring.js +9 -1
  104. package/dist/aura3d-cli/pull-bridge/scoring.js.map +1 -1
  105. package/dist/aura3d-cli/pull-bridge/search.js +2 -2
  106. package/dist/aura3d-cli/pull-bridge/search.js.map +1 -1
  107. package/dist/aura3d-cli/pull-bridge.d.ts +15 -3
  108. package/dist/aura3d-cli/pull-bridge.d.ts.map +1 -1
  109. package/dist/aura3d-cli/pull-bridge.js +68 -5
  110. package/dist/aura3d-cli/pull-bridge.js.map +1 -1
  111. package/dist/controls/ArcballControls.d.ts +92 -0
  112. package/dist/controls/ArcballControls.d.ts.map +1 -0
  113. package/dist/controls/ArcballControls.js +286 -0
  114. package/dist/controls/ArcballControls.js.map +1 -0
  115. package/dist/controls/DragControls.d.ts +13 -0
  116. package/dist/controls/DragControls.d.ts.map +1 -1
  117. package/dist/controls/DragControls.js +24 -1
  118. package/dist/controls/DragControls.js.map +1 -1
  119. package/dist/controls/FlyControls.d.ts +9 -0
  120. package/dist/controls/FlyControls.d.ts.map +1 -1
  121. package/dist/controls/FlyControls.js +20 -2
  122. package/dist/controls/FlyControls.js.map +1 -1
  123. package/dist/controls/FocusFrame.d.ts +34 -0
  124. package/dist/controls/FocusFrame.d.ts.map +1 -0
  125. package/dist/controls/FocusFrame.js +82 -0
  126. package/dist/controls/FocusFrame.js.map +1 -0
  127. package/dist/controls/HoverOutline.d.ts +46 -0
  128. package/dist/controls/HoverOutline.d.ts.map +1 -0
  129. package/dist/controls/HoverOutline.js +87 -0
  130. package/dist/controls/HoverOutline.js.map +1 -0
  131. package/dist/controls/InteractionControls.d.ts +7 -0
  132. package/dist/controls/InteractionControls.d.ts.map +1 -1
  133. package/dist/controls/InteractionControls.js +12 -1
  134. package/dist/controls/InteractionControls.js.map +1 -1
  135. package/dist/controls/NativeControlTypes.d.ts +17 -0
  136. package/dist/controls/NativeControlTypes.d.ts.map +1 -1
  137. package/dist/controls/NativeControlTypes.js.map +1 -1
  138. package/dist/controls/OrbitControls.d.ts +9 -0
  139. package/dist/controls/OrbitControls.d.ts.map +1 -1
  140. package/dist/controls/OrbitControls.js +26 -6
  141. package/dist/controls/OrbitControls.js.map +1 -1
  142. package/dist/controls/Picking.d.ts +15 -0
  143. package/dist/controls/Picking.d.ts.map +1 -1
  144. package/dist/controls/Picking.js +62 -23
  145. package/dist/controls/Picking.js.map +1 -1
  146. package/dist/controls/PointerLockControls.d.ts.map +1 -1
  147. package/dist/controls/PointerLockControls.js +4 -0
  148. package/dist/controls/PointerLockControls.js.map +1 -1
  149. package/dist/controls/SelectionManager.d.ts +8 -0
  150. package/dist/controls/SelectionManager.d.ts.map +1 -1
  151. package/dist/controls/SelectionManager.js +11 -0
  152. package/dist/controls/SelectionManager.js.map +1 -1
  153. package/dist/controls/TrackballControls.d.ts +7 -0
  154. package/dist/controls/TrackballControls.d.ts.map +1 -1
  155. package/dist/controls/TrackballControls.js +19 -5
  156. package/dist/controls/TrackballControls.js.map +1 -1
  157. package/dist/controls/TransformControls.d.ts +8 -0
  158. package/dist/controls/TransformControls.d.ts.map +1 -1
  159. package/dist/controls/TransformControls.js +15 -2
  160. package/dist/controls/TransformControls.js.map +1 -1
  161. package/dist/controls/index.d.ts +6 -0
  162. package/dist/controls/index.d.ts.map +1 -1
  163. package/dist/controls/index.js +3 -0
  164. package/dist/controls/index.js.map +1 -1
  165. package/dist/create-aura3d/index.js +1 -1
  166. package/dist/editor-runtime/RootEditorSurface.d.ts +65 -0
  167. package/dist/editor-runtime/RootEditorSurface.d.ts.map +1 -0
  168. package/dist/editor-runtime/RootEditorSurface.js +62 -0
  169. package/dist/editor-runtime/RootEditorSurface.js.map +1 -0
  170. package/dist/editor-runtime/index.d.ts +2 -0
  171. package/dist/editor-runtime/index.d.ts.map +1 -1
  172. package/dist/editor-runtime/index.js +1 -0
  173. package/dist/editor-runtime/index.js.map +1 -1
  174. package/dist/engine/agent-api/AnimationController.d.ts +9 -1
  175. package/dist/engine/agent-api/AnimationController.d.ts.map +1 -1
  176. package/dist/engine/agent-api/AnimationController.js +5 -1
  177. package/dist/engine/agent-api/AnimationController.js.map +1 -1
  178. package/dist/engine/agent-api/AnimationDebugOverlay.d.ts +56 -0
  179. package/dist/engine/agent-api/AnimationDebugOverlay.d.ts.map +1 -0
  180. package/dist/engine/agent-api/AnimationDebugOverlay.js +148 -0
  181. package/dist/engine/agent-api/AnimationDebugOverlay.js.map +1 -0
  182. package/dist/engine/agent-api/AnimationMixerBuilders.d.ts +41 -0
  183. package/dist/engine/agent-api/AnimationMixerBuilders.d.ts.map +1 -0
  184. package/dist/engine/agent-api/AnimationMixerBuilders.js +146 -0
  185. package/dist/engine/agent-api/AnimationMixerBuilders.js.map +1 -0
  186. package/dist/engine/agent-api/AssetDecoders.d.ts +17 -0
  187. package/dist/engine/agent-api/AssetDecoders.d.ts.map +1 -0
  188. package/dist/engine/agent-api/AssetDecoders.js +18 -0
  189. package/dist/engine/agent-api/AssetDecoders.js.map +1 -0
  190. package/dist/engine/agent-api/Decals.d.ts +174 -0
  191. package/dist/engine/agent-api/Decals.d.ts.map +1 -0
  192. package/dist/engine/agent-api/Decals.js +350 -0
  193. package/dist/engine/agent-api/Decals.js.map +1 -0
  194. package/dist/engine/agent-api/FootPlanting.d.ts +75 -0
  195. package/dist/engine/agent-api/FootPlanting.d.ts.map +1 -0
  196. package/dist/engine/agent-api/FootPlanting.js +60 -0
  197. package/dist/engine/agent-api/FootPlanting.js.map +1 -0
  198. package/dist/engine/agent-api/GameAppRuntime.d.ts +19 -0
  199. package/dist/engine/agent-api/GameAppRuntime.d.ts.map +1 -1
  200. package/dist/engine/agent-api/GameAppRuntime.js +37 -1
  201. package/dist/engine/agent-api/GameAppRuntime.js.map +1 -1
  202. package/dist/engine/agent-api/GameCameraRigs.d.ts +185 -0
  203. package/dist/engine/agent-api/GameCameraRigs.d.ts.map +1 -0
  204. package/dist/engine/agent-api/GameCameraRigs.js +414 -0
  205. package/dist/engine/agent-api/GameCameraRigs.js.map +1 -0
  206. package/dist/engine/agent-api/GameFeel.d.ts +100 -0
  207. package/dist/engine/agent-api/GameFeel.d.ts.map +1 -0
  208. package/dist/engine/agent-api/GameFeel.js +226 -0
  209. package/dist/engine/agent-api/GameFeel.js.map +1 -0
  210. package/dist/engine/agent-api/LabelTelemetry.d.ts +108 -0
  211. package/dist/engine/agent-api/LabelTelemetry.d.ts.map +1 -0
  212. package/dist/engine/agent-api/LabelTelemetry.js +116 -0
  213. package/dist/engine/agent-api/LabelTelemetry.js.map +1 -0
  214. package/dist/engine/agent-api/NavigationCrowds.d.ts +146 -0
  215. package/dist/engine/agent-api/NavigationCrowds.d.ts.map +1 -0
  216. package/dist/engine/agent-api/NavigationCrowds.js +104 -0
  217. package/dist/engine/agent-api/NavigationCrowds.js.map +1 -0
  218. package/dist/engine/agent-api/PhysicsRuntime.d.ts +61 -5
  219. package/dist/engine/agent-api/PhysicsRuntime.d.ts.map +1 -1
  220. package/dist/engine/agent-api/PhysicsRuntime.js +53 -21
  221. package/dist/engine/agent-api/PhysicsRuntime.js.map +1 -1
  222. package/dist/engine/agent-api/RootGeometry.d.ts +16 -1
  223. package/dist/engine/agent-api/RootGeometry.d.ts.map +1 -1
  224. package/dist/engine/agent-api/RootGeometry.js.map +1 -1
  225. package/dist/engine/agent-api/RuntimeNodeHandle.d.ts +6 -0
  226. package/dist/engine/agent-api/RuntimeNodeHandle.d.ts.map +1 -1
  227. package/dist/engine/agent-api/RuntimeNodeHandle.js.map +1 -1
  228. package/dist/engine/agent-api/Scatter.d.ts +18 -0
  229. package/dist/engine/agent-api/Scatter.d.ts.map +1 -0
  230. package/dist/engine/agent-api/Scatter.js +18 -0
  231. package/dist/engine/agent-api/Scatter.js.map +1 -0
  232. package/dist/engine/agent-api/TouchControlBinding.d.ts +32 -0
  233. package/dist/engine/agent-api/TouchControlBinding.d.ts.map +1 -1
  234. package/dist/engine/agent-api/TouchControlBinding.js +36 -0
  235. package/dist/engine/agent-api/TouchControlBinding.js.map +1 -1
  236. package/dist/engine/agent-api/VehicleChassis.d.ts.map +1 -1
  237. package/dist/engine/agent-api/VehicleChassis.js +76 -13
  238. package/dist/engine/agent-api/VehicleChassis.js.map +1 -1
  239. package/dist/engine/agent-api/index.d.ts +708 -6
  240. package/dist/engine/agent-api/index.d.ts.map +1 -1
  241. package/dist/engine/agent-api/index.js +1757 -134
  242. package/dist/engine/agent-api/index.js.map +1 -1
  243. package/dist/engine/agent-api/product-viewer-runtime.d.ts.map +1 -1
  244. package/dist/engine/game/GameAudio.d.ts +66 -0
  245. package/dist/engine/game/GameAudio.d.ts.map +1 -1
  246. package/dist/engine/game/GameAudio.js +179 -44
  247. package/dist/engine/game/GameAudio.js.map +1 -1
  248. package/dist/engine/instances-model/InstancedModel.d.ts +111 -0
  249. package/dist/engine/instances-model/InstancedModel.d.ts.map +1 -0
  250. package/dist/engine/instances-model/InstancedModel.js +108 -0
  251. package/dist/engine/instances-model/InstancedModel.js.map +1 -0
  252. package/dist/engine/material-physical/PhysicalMaterialSpec.d.ts +72 -0
  253. package/dist/engine/material-physical/PhysicalMaterialSpec.d.ts.map +1 -0
  254. package/dist/engine/material-physical/PhysicalMaterialSpec.js +95 -0
  255. package/dist/engine/material-physical/PhysicalMaterialSpec.js.map +1 -0
  256. package/dist/engine/production-runtime/GameAppRuntime.d.ts +1 -1
  257. package/dist/engine/production-runtime/GameAppRuntime.d.ts.map +1 -1
  258. package/dist/engine/production-runtime/GameRenderPreset.d.ts +76 -0
  259. package/dist/engine/production-runtime/GameRenderPreset.d.ts.map +1 -1
  260. package/dist/engine/production-runtime/GameRenderPreset.js +167 -0
  261. package/dist/engine/production-runtime/GameRenderPreset.js.map +1 -1
  262. package/dist/engine/production-runtime/TypedGLBActor.d.ts +22 -2
  263. package/dist/engine/production-runtime/TypedGLBActor.d.ts.map +1 -1
  264. package/dist/engine/production-runtime/TypedGLBActor.js +33 -1
  265. package/dist/engine/production-runtime/TypedGLBActor.js.map +1 -1
  266. package/dist/engine/production-runtime/index.d.ts +4 -4
  267. package/dist/engine/production-runtime/index.d.ts.map +1 -1
  268. package/dist/engine/production-runtime/index.js +5 -3
  269. package/dist/engine/production-runtime/index.js.map +1 -1
  270. package/dist/input/ActionMap.d.ts +40 -0
  271. package/dist/input/ActionMap.d.ts.map +1 -1
  272. package/dist/input/ActionMap.js +179 -0
  273. package/dist/input/ActionMap.js.map +1 -1
  274. package/dist/input/ComboDetector.d.ts +49 -0
  275. package/dist/input/ComboDetector.d.ts.map +1 -0
  276. package/dist/input/ComboDetector.js +102 -0
  277. package/dist/input/ComboDetector.js.map +1 -0
  278. package/dist/input/Haptics.d.ts +46 -0
  279. package/dist/input/Haptics.d.ts.map +1 -0
  280. package/dist/input/Haptics.js +67 -0
  281. package/dist/input/Haptics.js.map +1 -0
  282. package/dist/input/TouchLayouts.d.ts +35 -0
  283. package/dist/input/TouchLayouts.d.ts.map +1 -0
  284. package/dist/input/TouchLayouts.js +88 -0
  285. package/dist/input/TouchLayouts.js.map +1 -0
  286. package/dist/input/WebXRSessionController.d.ts +53 -0
  287. package/dist/input/WebXRSessionController.d.ts.map +1 -1
  288. package/dist/input/WebXRSessionController.js +88 -2
  289. package/dist/input/WebXRSessionController.js.map +1 -1
  290. package/dist/input/index.d.ts +8 -2
  291. package/dist/input/index.d.ts.map +1 -1
  292. package/dist/input/index.js +3 -0
  293. package/dist/input/index.js.map +1 -1
  294. package/dist/lean/game.d.ts +98 -0
  295. package/dist/lean/game.d.ts.map +1 -1
  296. package/dist/lean/game.js +192 -0
  297. package/dist/lean/game.js.map +1 -1
  298. package/dist/materials/GameReadyMaterialLibrary.d.ts +39 -0
  299. package/dist/materials/GameReadyMaterialLibrary.d.ts.map +1 -0
  300. package/dist/materials/GameReadyMaterialLibrary.js +253 -0
  301. package/dist/materials/GameReadyMaterialLibrary.js.map +1 -0
  302. package/dist/materials/MaterialValidation.d.ts +21 -0
  303. package/dist/materials/MaterialValidation.d.ts.map +1 -1
  304. package/dist/materials/MaterialValidation.js +105 -0
  305. package/dist/materials/MaterialValidation.js.map +1 -1
  306. package/dist/materials/browser-index.d.ts +2 -0
  307. package/dist/materials/browser-index.d.ts.map +1 -1
  308. package/dist/materials/browser-index.js +1 -0
  309. package/dist/materials/browser-index.js.map +1 -1
  310. package/dist/materials/node.d.ts +2 -2
  311. package/dist/materials/node.d.ts.map +1 -1
  312. package/dist/materials/node.js +1 -1
  313. package/dist/materials/node.js.map +1 -1
  314. package/dist/physics/CollisionVolumes.d.ts +59 -0
  315. package/dist/physics/CollisionVolumes.d.ts.map +1 -1
  316. package/dist/physics/CollisionVolumes.js +87 -0
  317. package/dist/physics/CollisionVolumes.js.map +1 -1
  318. package/dist/physics/PhysicsDebugDraw.d.ts +37 -0
  319. package/dist/physics/PhysicsDebugDraw.d.ts.map +1 -1
  320. package/dist/physics/PhysicsDebugDraw.js +38 -1
  321. package/dist/physics/PhysicsDebugDraw.js.map +1 -1
  322. package/dist/physics/PhysicsWorld.d.ts +15 -0
  323. package/dist/physics/PhysicsWorld.d.ts.map +1 -1
  324. package/dist/physics/PhysicsWorld.js +8 -1
  325. package/dist/physics/PhysicsWorld.js.map +1 -1
  326. package/dist/physics/solverless.d.ts +1 -1
  327. package/dist/physics/solverless.d.ts.map +1 -1
  328. package/dist/physics/solverless.js.map +1 -1
  329. package/dist/physics-rapier/index.d.ts +7 -1
  330. package/dist/physics-rapier/index.d.ts.map +1 -1
  331. package/dist/physics-rapier/index.js +1 -1
  332. package/dist/physics-rapier/index.js.map +1 -1
  333. package/dist/react/index.d.ts +136 -2
  334. package/dist/react/index.d.ts.map +1 -1
  335. package/dist/react/index.js +251 -10
  336. package/dist/react/index.js.map +1 -1
  337. package/dist/rendering/AtmosphereWetness.d.ts +61 -0
  338. package/dist/rendering/AtmosphereWetness.d.ts.map +1 -0
  339. package/dist/rendering/AtmosphereWetness.js +99 -0
  340. package/dist/rendering/AtmosphereWetness.js.map +1 -0
  341. package/dist/rendering/ClusteredForwardLighting.d.ts +25 -1
  342. package/dist/rendering/ClusteredForwardLighting.d.ts.map +1 -1
  343. package/dist/rendering/ClusteredForwardLighting.js +80 -3
  344. package/dist/rendering/ClusteredForwardLighting.js.map +1 -1
  345. package/dist/rendering/DayNightSky.d.ts +63 -0
  346. package/dist/rendering/DayNightSky.d.ts.map +1 -0
  347. package/dist/rendering/DayNightSky.js +156 -0
  348. package/dist/rendering/DayNightSky.js.map +1 -0
  349. package/dist/rendering/EnvironmentPresetPack.d.ts +30 -0
  350. package/dist/rendering/EnvironmentPresetPack.d.ts.map +1 -0
  351. package/dist/rendering/EnvironmentPresetPack.js +49 -0
  352. package/dist/rendering/EnvironmentPresetPack.js.map +1 -0
  353. package/dist/rendering/ForwardPass.d.ts +65 -0
  354. package/dist/rendering/ForwardPass.d.ts.map +1 -1
  355. package/dist/rendering/ForwardPass.js +196 -2
  356. package/dist/rendering/ForwardPass.js.map +1 -1
  357. package/dist/rendering/InstancedPBRMaterial.d.ts +1 -0
  358. package/dist/rendering/InstancedPBRMaterial.d.ts.map +1 -1
  359. package/dist/rendering/InstancedPBRMaterial.js +3 -0
  360. package/dist/rendering/InstancedPBRMaterial.js.map +1 -1
  361. package/dist/rendering/InstancingDiagnostics.d.ts +69 -0
  362. package/dist/rendering/InstancingDiagnostics.d.ts.map +1 -0
  363. package/dist/rendering/InstancingDiagnostics.js +98 -0
  364. package/dist/rendering/InstancingDiagnostics.js.map +1 -0
  365. package/dist/rendering/LightingRig.d.ts +19 -1
  366. package/dist/rendering/LightingRig.d.ts.map +1 -1
  367. package/dist/rendering/LightingRig.js +61 -1
  368. package/dist/rendering/LightingRig.js.map +1 -1
  369. package/dist/rendering/MeshConsolidation.d.ts +12 -0
  370. package/dist/rendering/MeshConsolidation.d.ts.map +1 -1
  371. package/dist/rendering/MeshConsolidation.js +28 -0
  372. package/dist/rendering/MeshConsolidation.js.map +1 -1
  373. package/dist/rendering/MorphTargetPlan.d.ts +29 -2
  374. package/dist/rendering/MorphTargetPlan.d.ts.map +1 -1
  375. package/dist/rendering/MorphTargetPlan.js +23 -4
  376. package/dist/rendering/MorphTargetPlan.js.map +1 -1
  377. package/dist/rendering/NormalMappedPBRMaterial.d.ts.map +1 -1
  378. package/dist/rendering/NormalMappedPBRMaterial.js +22 -2
  379. package/dist/rendering/NormalMappedPBRMaterial.js.map +1 -1
  380. package/dist/rendering/OceanSurface.d.ts +75 -0
  381. package/dist/rendering/OceanSurface.d.ts.map +1 -1
  382. package/dist/rendering/OceanSurface.js +211 -0
  383. package/dist/rendering/OceanSurface.js.map +1 -1
  384. package/dist/rendering/PBRMaterial.d.ts +1 -0
  385. package/dist/rendering/PBRMaterial.d.ts.map +1 -1
  386. package/dist/rendering/PBRMaterial.js +23 -2
  387. package/dist/rendering/PBRMaterial.js.map +1 -1
  388. package/dist/rendering/PbrReference.d.ts +48 -0
  389. package/dist/rendering/PbrReference.d.ts.map +1 -1
  390. package/dist/rendering/PbrReference.js +126 -0
  391. package/dist/rendering/PbrReference.js.map +1 -1
  392. package/dist/rendering/PlanarReflection.d.ts +221 -0
  393. package/dist/rendering/PlanarReflection.d.ts.map +1 -0
  394. package/dist/rendering/PlanarReflection.js +541 -0
  395. package/dist/rendering/PlanarReflection.js.map +1 -0
  396. package/dist/rendering/PostProcessPass.d.ts +13 -0
  397. package/dist/rendering/PostProcessPass.d.ts.map +1 -1
  398. package/dist/rendering/PostProcessPass.js +15 -4
  399. package/dist/rendering/PostProcessPass.js.map +1 -1
  400. package/dist/rendering/ReflectionSurfaces.d.ts +14 -0
  401. package/dist/rendering/ReflectionSurfaces.d.ts.map +1 -1
  402. package/dist/rendering/ReflectionSurfaces.js +93 -0
  403. package/dist/rendering/ReflectionSurfaces.js.map +1 -1
  404. package/dist/rendering/RenderDevice.d.ts +87 -0
  405. package/dist/rendering/RenderDevice.d.ts.map +1 -1
  406. package/dist/rendering/RenderDevice.js +85 -0
  407. package/dist/rendering/RenderDevice.js.map +1 -1
  408. package/dist/rendering/Renderer.js +3 -1
  409. package/dist/rendering/Renderer.js.map +1 -1
  410. package/dist/rendering/RendererPostprocessPlan.d.ts +48 -0
  411. package/dist/rendering/RendererPostprocessPlan.d.ts.map +1 -1
  412. package/dist/rendering/RendererPostprocessPlan.js +101 -11
  413. package/dist/rendering/RendererPostprocessPlan.js.map +1 -1
  414. package/dist/rendering/RendererVisualPipelineReport.d.ts.map +1 -1
  415. package/dist/rendering/RendererVisualPipelineReport.js +3 -1
  416. package/dist/rendering/RendererVisualPipelineReport.js.map +1 -1
  417. package/dist/rendering/Sampler.d.ts +29 -0
  418. package/dist/rendering/Sampler.d.ts.map +1 -1
  419. package/dist/rendering/Sampler.js +47 -0
  420. package/dist/rendering/Sampler.js.map +1 -1
  421. package/dist/rendering/SdfText.d.ts +250 -0
  422. package/dist/rendering/SdfText.d.ts.map +1 -0
  423. package/dist/rendering/SdfText.js +551 -0
  424. package/dist/rendering/SdfText.js.map +1 -0
  425. package/dist/rendering/ShaderChunks.d.ts.map +1 -1
  426. package/dist/rendering/ShaderChunks.js +30 -12
  427. package/dist/rendering/ShaderChunks.js.map +1 -1
  428. package/dist/rendering/ShaderLibrary.d.ts.map +1 -1
  429. package/dist/rendering/ShaderLibrary.js +478 -34
  430. package/dist/rendering/ShaderLibrary.js.map +1 -1
  431. package/dist/rendering/ShaderLibraryCore.d.ts.map +1 -1
  432. package/dist/rendering/ShaderLibraryCore.js +70 -12
  433. package/dist/rendering/ShaderLibraryCore.js.map +1 -1
  434. package/dist/rendering/ShadowMap.d.ts +24 -0
  435. package/dist/rendering/ShadowMap.d.ts.map +1 -1
  436. package/dist/rendering/ShadowMap.js +43 -0
  437. package/dist/rendering/ShadowMap.js.map +1 -1
  438. package/dist/rendering/SkinnedLitMaterial.d.ts.map +1 -1
  439. package/dist/rendering/SkinnedLitMaterial.js +2 -0
  440. package/dist/rendering/SkinnedLitMaterial.js.map +1 -1
  441. package/dist/rendering/SkinningBounds.d.ts +10 -0
  442. package/dist/rendering/SkinningBounds.d.ts.map +1 -1
  443. package/dist/rendering/SkinningBounds.js +27 -0
  444. package/dist/rendering/SkinningBounds.js.map +1 -1
  445. package/dist/rendering/SpriteFlipbook.d.ts +60 -0
  446. package/dist/rendering/SpriteFlipbook.d.ts.map +1 -0
  447. package/dist/rendering/SpriteFlipbook.js +128 -0
  448. package/dist/rendering/SpriteFlipbook.js.map +1 -0
  449. package/dist/rendering/TerrainTiles.d.ts +120 -0
  450. package/dist/rendering/TerrainTiles.d.ts.map +1 -0
  451. package/dist/rendering/TerrainTiles.js +221 -0
  452. package/dist/rendering/TerrainTiles.js.map +1 -0
  453. package/dist/rendering/TexturedPBRMaterial.d.ts +1 -0
  454. package/dist/rendering/TexturedPBRMaterial.d.ts.map +1 -1
  455. package/dist/rendering/TexturedPBRMaterial.js +23 -2
  456. package/dist/rendering/TexturedPBRMaterial.js.map +1 -1
  457. package/dist/rendering/TexturedUnlitMaterial.d.ts.map +1 -1
  458. package/dist/rendering/TexturedUnlitMaterial.js +20 -2
  459. package/dist/rendering/TexturedUnlitMaterial.js.map +1 -1
  460. package/dist/rendering/UnlitMaterial.d.ts.map +1 -1
  461. package/dist/rendering/UnlitMaterial.js +20 -2
  462. package/dist/rendering/UnlitMaterial.js.map +1 -1
  463. package/dist/rendering/VertexFormat.d.ts +6 -0
  464. package/dist/rendering/VertexFormat.d.ts.map +1 -1
  465. package/dist/rendering/VertexFormat.js +12 -0
  466. package/dist/rendering/VertexFormat.js.map +1 -1
  467. package/dist/rendering/VolumetricFog.d.ts +55 -0
  468. package/dist/rendering/VolumetricFog.d.ts.map +1 -0
  469. package/dist/rendering/VolumetricFog.js +91 -0
  470. package/dist/rendering/VolumetricFog.js.map +1 -0
  471. package/dist/rendering/WaterSurface.d.ts +77 -0
  472. package/dist/rendering/WaterSurface.d.ts.map +1 -0
  473. package/dist/rendering/WaterSurface.js +133 -0
  474. package/dist/rendering/WaterSurface.js.map +1 -0
  475. package/dist/rendering/WebGL2Device.d.ts +58 -0
  476. package/dist/rendering/WebGL2Device.d.ts.map +1 -1
  477. package/dist/rendering/WebGL2Device.js +536 -17
  478. package/dist/rendering/WebGL2Device.js.map +1 -1
  479. package/dist/rendering/WebGPUDevice.d.ts +50 -0
  480. package/dist/rendering/WebGPUDevice.d.ts.map +1 -1
  481. package/dist/rendering/WebGPUDevice.js +504 -10
  482. package/dist/rendering/WebGPUDevice.js.map +1 -1
  483. package/dist/rendering/WebGPUSkinningLimits.d.ts +40 -0
  484. package/dist/rendering/WebGPUSkinningLimits.d.ts.map +1 -1
  485. package/dist/rendering/WebGPUSkinningLimits.js +32 -0
  486. package/dist/rendering/WebGPUSkinningLimits.js.map +1 -1
  487. package/dist/rendering/effects/CollisionModule.d.ts +9 -0
  488. package/dist/rendering/effects/CollisionModule.d.ts.map +1 -1
  489. package/dist/rendering/effects/CollisionModule.js +10 -0
  490. package/dist/rendering/effects/CollisionModule.js.map +1 -1
  491. package/dist/rendering/effects/ColorModule.d.ts +7 -0
  492. package/dist/rendering/effects/ColorModule.d.ts.map +1 -1
  493. package/dist/rendering/effects/ColorModule.js +23 -0
  494. package/dist/rendering/effects/ColorModule.js.map +1 -1
  495. package/dist/rendering/effects/ForceModule.d.ts +35 -0
  496. package/dist/rendering/effects/ForceModule.d.ts.map +1 -1
  497. package/dist/rendering/effects/ForceModule.js +49 -0
  498. package/dist/rendering/effects/ForceModule.js.map +1 -1
  499. package/dist/rendering/effects/GPUParticleBackend.d.ts +186 -0
  500. package/dist/rendering/effects/GPUParticleBackend.d.ts.map +1 -1
  501. package/dist/rendering/effects/GPUParticleBackend.js +692 -1
  502. package/dist/rendering/effects/GPUParticleBackend.js.map +1 -1
  503. package/dist/rendering/effects/HeightfieldModule.d.ts +58 -0
  504. package/dist/rendering/effects/HeightfieldModule.d.ts.map +1 -0
  505. package/dist/rendering/effects/HeightfieldModule.js +129 -0
  506. package/dist/rendering/effects/HeightfieldModule.js.map +1 -0
  507. package/dist/rendering/effects/LightingModule.d.ts +34 -0
  508. package/dist/rendering/effects/LightingModule.d.ts.map +1 -0
  509. package/dist/rendering/effects/LightingModule.js +54 -0
  510. package/dist/rendering/effects/LightingModule.js.map +1 -0
  511. package/dist/rendering/effects/ParticleDiagnostics.d.ts +8 -0
  512. package/dist/rendering/effects/ParticleDiagnostics.d.ts.map +1 -1
  513. package/dist/rendering/effects/ParticleDiagnostics.js +3 -0
  514. package/dist/rendering/effects/ParticleDiagnostics.js.map +1 -1
  515. package/dist/rendering/effects/ParticleModule.d.ts +8 -0
  516. package/dist/rendering/effects/ParticleModule.d.ts.map +1 -1
  517. package/dist/rendering/effects/ParticleModule.js.map +1 -1
  518. package/dist/rendering/effects/ParticleRenderer.d.ts +20 -0
  519. package/dist/rendering/effects/ParticleRenderer.d.ts.map +1 -1
  520. package/dist/rendering/effects/ParticleRenderer.js +57 -7
  521. package/dist/rendering/effects/ParticleRenderer.js.map +1 -1
  522. package/dist/rendering/effects/ParticleSystem.d.ts +45 -1
  523. package/dist/rendering/effects/ParticleSystem.d.ts.map +1 -1
  524. package/dist/rendering/effects/ParticleSystem.js +373 -4
  525. package/dist/rendering/effects/ParticleSystem.js.map +1 -1
  526. package/dist/rendering/effects/SizeModule.d.ts +4 -0
  527. package/dist/rendering/effects/SizeModule.d.ts.map +1 -1
  528. package/dist/rendering/effects/SizeModule.js +16 -0
  529. package/dist/rendering/effects/SizeModule.js.map +1 -1
  530. package/dist/rendering/effects/SubEmitterModule.d.ts +61 -0
  531. package/dist/rendering/effects/SubEmitterModule.d.ts.map +1 -0
  532. package/dist/rendering/effects/SubEmitterModule.js +119 -0
  533. package/dist/rendering/effects/SubEmitterModule.js.map +1 -0
  534. package/dist/rendering/effects/TrailModule.d.ts +38 -0
  535. package/dist/rendering/effects/TrailModule.d.ts.map +1 -1
  536. package/dist/rendering/effects/TrailModule.js +95 -2
  537. package/dist/rendering/effects/TrailModule.js.map +1 -1
  538. package/dist/rendering/effects/TurbulenceModule.d.ts +43 -0
  539. package/dist/rendering/effects/TurbulenceModule.d.ts.map +1 -0
  540. package/dist/rendering/effects/TurbulenceModule.js +130 -0
  541. package/dist/rendering/effects/TurbulenceModule.js.map +1 -0
  542. package/dist/rendering/effects/index.d.ts +4 -0
  543. package/dist/rendering/effects/index.d.ts.map +1 -1
  544. package/dist/rendering/effects/index.js +4 -0
  545. package/dist/rendering/effects/index.js.map +1 -1
  546. package/dist/rendering/index.d.ts +57 -19
  547. package/dist/rendering/index.d.ts.map +1 -1
  548. package/dist/rendering/index.js +31 -10
  549. package/dist/rendering/index.js.map +1 -1
  550. package/dist/rendering/materials/MaterialExtensions.js +3 -3
  551. package/dist/rendering/materials/MaterialExtensions.js.map +1 -1
  552. package/dist/rendering/postprocess/NativeBloomPyramid.d.ts +28 -0
  553. package/dist/rendering/postprocess/NativeBloomPyramid.d.ts.map +1 -0
  554. package/dist/rendering/postprocess/NativeBloomPyramid.js +54 -0
  555. package/dist/rendering/postprocess/NativeBloomPyramid.js.map +1 -0
  556. package/dist/rendering/postprocess/NativeLdrEffectLuts.d.ts +10 -1
  557. package/dist/rendering/postprocess/NativeLdrEffectLuts.d.ts.map +1 -1
  558. package/dist/rendering/postprocess/NativeLdrEffectLuts.js +19 -2
  559. package/dist/rendering/postprocess/NativeLdrEffectLuts.js.map +1 -1
  560. package/dist/rendering/postprocess/index.d.ts +2 -0
  561. package/dist/rendering/postprocess/index.d.ts.map +1 -1
  562. package/dist/rendering/postprocess/index.js +1 -0
  563. package/dist/rendering/postprocess/index.js.map +1 -1
  564. package/dist/rendering/production-runtime/PBRHDRPipeline.d.ts +22 -0
  565. package/dist/rendering/production-runtime/PBRHDRPipeline.d.ts.map +1 -1
  566. package/dist/rendering/production-runtime/PBRHDRPipeline.js +125 -0
  567. package/dist/rendering/production-runtime/PBRHDRPipeline.js.map +1 -1
  568. package/dist/rendering/production-runtime/ProductionWebGPURenderer.d.ts +52 -0
  569. package/dist/rendering/production-runtime/ProductionWebGPURenderer.d.ts.map +1 -1
  570. package/dist/rendering/production-runtime/ProductionWebGPURenderer.js +101 -0
  571. package/dist/rendering/production-runtime/ProductionWebGPURenderer.js.map +1 -1
  572. package/dist/rendering/production-runtime/index.d.ts +5 -4
  573. package/dist/rendering/production-runtime/index.d.ts.map +1 -1
  574. package/dist/rendering/production-runtime/index.js +3 -2
  575. package/dist/rendering/production-runtime/index.js.map +1 -1
  576. package/dist/rendering/production-runtime/passes/DepthPrepass.d.ts +26 -1
  577. package/dist/rendering/production-runtime/passes/DepthPrepass.d.ts.map +1 -1
  578. package/dist/rendering/production-runtime/passes/DepthPrepass.js +49 -2
  579. package/dist/rendering/production-runtime/passes/DepthPrepass.js.map +1 -1
  580. package/dist/rendering/production-runtime/passes/FramegraphTopology.d.ts +13 -0
  581. package/dist/rendering/production-runtime/passes/FramegraphTopology.d.ts.map +1 -0
  582. package/dist/rendering/production-runtime/passes/FramegraphTopology.js +59 -0
  583. package/dist/rendering/production-runtime/passes/FramegraphTopology.js.map +1 -0
  584. package/dist/rendering/production-runtime/passes/OpaquePass.d.ts +24 -1
  585. package/dist/rendering/production-runtime/passes/OpaquePass.d.ts.map +1 -1
  586. package/dist/rendering/production-runtime/passes/OpaquePass.js +41 -2
  587. package/dist/rendering/production-runtime/passes/OpaquePass.js.map +1 -1
  588. package/dist/rendering/production-runtime/passes/ShadowPass.d.ts +24 -1
  589. package/dist/rendering/production-runtime/passes/ShadowPass.d.ts.map +1 -1
  590. package/dist/rendering/production-runtime/passes/ShadowPass.js +43 -2
  591. package/dist/rendering/production-runtime/passes/ShadowPass.js.map +1 -1
  592. package/dist/rendering/production-runtime/passes/SkyboxPass.d.ts +21 -1
  593. package/dist/rendering/production-runtime/passes/SkyboxPass.d.ts.map +1 -1
  594. package/dist/rendering/production-runtime/passes/SkyboxPass.js +39 -2
  595. package/dist/rendering/production-runtime/passes/SkyboxPass.js.map +1 -1
  596. package/dist/rendering/production-runtime/passes/ToneMappingPass.d.ts +29 -1
  597. package/dist/rendering/production-runtime/passes/ToneMappingPass.d.ts.map +1 -1
  598. package/dist/rendering/production-runtime/passes/ToneMappingPass.js +53 -2
  599. package/dist/rendering/production-runtime/passes/ToneMappingPass.js.map +1 -1
  600. package/dist/rendering/production-runtime/passes/TransparentPass.d.ts +24 -1
  601. package/dist/rendering/production-runtime/passes/TransparentPass.d.ts.map +1 -1
  602. package/dist/rendering/production-runtime/passes/TransparentPass.js +42 -2
  603. package/dist/rendering/production-runtime/passes/TransparentPass.js.map +1 -1
  604. package/dist/rendering/shadows/CascadeHysteresis.d.ts +67 -0
  605. package/dist/rendering/shadows/CascadeHysteresis.d.ts.map +1 -0
  606. package/dist/rendering/shadows/CascadeHysteresis.js +104 -0
  607. package/dist/rendering/shadows/CascadeHysteresis.js.map +1 -0
  608. package/dist/rendering/shadows/ContactShadows.d.ts +52 -0
  609. package/dist/rendering/shadows/ContactShadows.d.ts.map +1 -1
  610. package/dist/rendering/shadows/ContactShadows.js +122 -0
  611. package/dist/rendering/shadows/ContactShadows.js.map +1 -1
  612. package/dist/rendering/shadows/SpotShadowMaps.d.ts +77 -0
  613. package/dist/rendering/shadows/SpotShadowMaps.d.ts.map +1 -0
  614. package/dist/rendering/shadows/SpotShadowMaps.js +106 -0
  615. package/dist/rendering/shadows/SpotShadowMaps.js.map +1 -0
  616. package/dist/rendering/webgpu/WebGPUPostProcess.d.ts +2 -0
  617. package/dist/rendering/webgpu/WebGPUPostProcess.d.ts.map +1 -1
  618. package/dist/rendering/webgpu/WebGPUPostProcess.js +1 -0
  619. package/dist/rendering/webgpu/WebGPUPostProcess.js.map +1 -1
  620. package/dist/rendering/webgpu/WebGPUPostShaders.d.ts +78 -0
  621. package/dist/rendering/webgpu/WebGPUPostShaders.d.ts.map +1 -0
  622. package/dist/rendering/webgpu/WebGPUPostShaders.js +262 -0
  623. package/dist/rendering/webgpu/WebGPUPostShaders.js.map +1 -0
  624. package/dist/scene/Scene.d.ts +8 -1
  625. package/dist/scene/Scene.d.ts.map +1 -1
  626. package/dist/scene/Scene.js +12 -2
  627. package/dist/scene/Scene.js.map +1 -1
  628. package/dist/scripting/VisualGraphContext.d.ts +31 -0
  629. package/dist/scripting/VisualGraphContext.d.ts.map +1 -1
  630. package/dist/scripting/VisualGraphExecutor.d.ts.map +1 -1
  631. package/dist/scripting/VisualGraphExecutor.js +110 -0
  632. package/dist/scripting/VisualGraphExecutor.js.map +1 -1
  633. package/dist/scripting/VisualNodeCatalog.d.ts +1 -1
  634. package/dist/scripting/VisualNodeCatalog.d.ts.map +1 -1
  635. package/dist/scripting/VisualNodeCatalog.js +20 -0
  636. package/dist/scripting/VisualNodeCatalog.js.map +1 -1
  637. package/dist/scripting/VisualScriptingRoot.d.ts +96 -0
  638. package/dist/scripting/VisualScriptingRoot.d.ts.map +1 -0
  639. package/dist/scripting/VisualScriptingRoot.js +150 -0
  640. package/dist/scripting/VisualScriptingRoot.js.map +1 -0
  641. package/dist/scripting/index.d.ts +3 -1
  642. package/dist/scripting/index.d.ts.map +1 -1
  643. package/dist/scripting/index.js +1 -0
  644. package/dist/scripting/index.js.map +1 -1
  645. package/package.json +24 -22
  646. package/templates/cinematic-scene/node_modules/.vite/deps/_metadata.json +8 -0
  647. package/templates/cinematic-scene/node_modules/.vite/deps/package.json +3 -0
  648. package/templates/cinematic-scene/test-results/.last-run.json +6 -0
  649. package/templates/cinematic-scene/test-results/route-health-Aura3D-cinematic-scene-reaches-ready-state/error-context.md +64 -0
  650. package/templates/product-viewer/node_modules/.vite/deps/_metadata.json +8 -0
  651. package/templates/product-viewer/node_modules/.vite/deps/package.json +3 -0
@@ -22,10 +22,11 @@ import { PhysicsDebugDraw, PhysicsStepper, ScenePhysicsBridge, Shape as PhysicsS
22
22
  * of which arrived just to reach one class.
23
23
  */
24
24
  import { PhysicsWorld } from "../../physics/world.js";
25
- import { createExternalParityEnvironmentLighting, Geometry, IndexBuffer, InstancedPBRMaterial, PBRMaterial, ProductionRuntimeRenderer, srgbToLinearChannel, VertexBuffer, VertexFormat } from "../../rendering/index.js";
25
+ 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
26
  import { DirectionalLight, PointLight, SpotLight } from "../../scene/index.js";
27
27
  import { lookAtMat4, multiplyMat4, orthographicMat4, perspectiveMat4 } from "../../scene/MathTypes.js";
28
28
  import { AURA_NORMALIZED_MODEL_MAX_DIMENSION, boundsFromAsset, boundsHeight, boundsMaxDimension, boundsSize } from "./SceneGroundingUtils.js";
29
+ import { collectLabelTelemetry, summarizeTextBuckets } from "./LabelTelemetry.js";
29
30
  import { createWorldLabelLayer } from "./WorldLabelRenderer.js";
30
31
  import { createAuraText3DGeometry, defineAuraCustomGeometry, selectAuraRootLodLevel } from "./RootGeometry.js";
31
32
  export * from "./RootGeometry.js";
@@ -49,8 +50,18 @@ export * from "./PhysicsRuntime.js";
49
50
  */
50
51
  export { buildMeshBVH, createMeshSurfaceQuery, raycastMesh } from "../../physics/solverless.js";
51
52
  import { createPhysicsRuntime } from "./PhysicsRuntime.js";
53
+ import { gameCameraRigs } from "./GameCameraRigs.js";
54
+ import { gameFeelBuilders } from "./GameFeel.js";
52
55
  export * from "./FocusSelection.js";
53
56
  export * from "./WorldLabelRenderer.js";
57
+ export * from "./GameCameraRigs.js";
58
+ export * from "./GameFeel.js";
59
+ export * from "./LabelTelemetry.js";
60
+ export * from "./NavigationCrowds.js";
61
+ export * from "./FootPlanting.js";
62
+ export * from "./Decals.js";
63
+ export * from "./Scatter.js";
64
+ export * from "./AssetDecoders.js";
54
65
  export * from "./VehicleChassis.js";
55
66
  export * from "./VehicleDriverAi.js";
56
67
  /**
@@ -83,7 +94,16 @@ export * from "./WebCodecsFrameEncoder.js";
83
94
  */
84
95
  export { GameInspector, createGameInspector } from "./GameInspector.js";
85
96
  import { createGameInspector } from "./GameInspector.js";
97
+ import { createPhysicalMaterialSpec } from "../material-physical/PhysicalMaterialSpec.js";
98
+ import { createInstancedModelNode } from "../instances-model/InstancedModel.js";
99
+ import { resolveWrinkleMapStrength, warnOnInstancingFallback } from "../../rendering/index.js";
86
100
  export { createGameAudio } from "../game/GameAudio.js";
101
+ export { PositionalEmitter, FootstepPlayer, createGameMixer, attachFocusPolicy, computeDistanceAttenuation, computeDopplerShift, resolveOcclusion } from "../../audio/index.js";
102
+ export { ComboDetector, createTouchLayoutPreset, probeHaptics, playHaptic } from "../../input/index.js";
103
+ export { attachVisualScriptingGraph, createVisualScriptingGraph, listVisualScriptingNodeCatalog } from "../../scripting/index.js";
104
+ export { createRootEditorSurface } from "../../editor-runtime/index.js";
105
+ import { attachVisualScriptingGraph as attachVisualScriptingGraphFn, createVisualScriptingGraph as createVisualScriptingGraphFn, listVisualScriptingNodeCatalog as listVisualScriptingNodeCatalogFn } from "../../scripting/index.js";
106
+ import { createRootEditorSurface as createRootEditorSurfaceFn } from "../../editor-runtime/index.js";
87
107
  export * from "./RenderProgressTracker.js";
88
108
  export * from "./AudioVisemeAnalyzer.js";
89
109
  export * from "./ExternalPhonemeAnalyzer.js";
@@ -114,6 +134,7 @@ export * from "./EpisodeTemplates.js";
114
134
  export * from "./AnimationMotionQuality.js";
115
135
  export * from "./AnimationRouteProof.js";
116
136
  export * from "./AnimationEpisodePackage.js";
137
+ import { evaluateDistancePrioritizedMipResidency } from "../../assets/browser-index.js";
117
138
  import { createFrameLoop } from "./FrameLoop.js";
118
139
  import { createCombatWorld, createGameCameraDirector, createGameEffects, applyGameCombatEventsToRuntime, createGameAccessibilityFocus, createGameAccessibilityLabel, createGameAccessibilityRuntimeSettings, createGameEventLog, createGameHighContrastSource, createGameHudBindings, createGameHudComboBinding, createGameHudCheckpointBinding, createGameHudDebugToggleBinding, createGameHudEventLogBinding, createGameHudHealthBinding, createGameHudLivesBinding, createGameHudMeterBinding, createGameHudObjectiveBinding, createGameHudRoundBinding, createGameHudScoreBinding, createGameHudSnapshot, createGameHudTimerBinding, createGameHudValueBinding, createGameBoxCollider, createGameCapsuleCollider, createGameColliderDebugGeometry, createGameCombatDebugGeometry, createGameCollisionWorld, createGamePlanarCollisionWorld, createGameDebugOverlayData, createGameDebugSceneNodes, createGameHitboxDebugGeometry, createGameSimulation, exportGameInputReplay, createGameFighting2DRules, createGameInput, createGameInputReplay, createGameInputReplayDriver, importGameInputReplay, createGameJumpAssist, createGameKinematicBody, createGamePauseControlsSource, createGameRectCollider, createGameReducedFlashSource, createGameReducedMotionSource, createGameSphereCollider, createGameTouchControlLayout, gameColliderAabb, gameColliders, gameEffectPresets, gameGuardboxes, gameHitboxes, gameHurtboxes, gameInputReplayEventsAt, gamePushboxes, runGameSimulation, gameTriggerVolumes } from "./GameRuntime.js";
119
140
  import { collectGameRuntimeEvidence as collectGameRuntimeEvidenceV105 } from "./GameEvidence.js";
@@ -153,6 +174,8 @@ export * from "./GameAssetValidation.js";
153
174
  export * from "./CharacterAssembly.js";
154
175
  export * from "./AssetEvidence.js";
155
176
  export * from "./AnimationController.js";
177
+ export * from "./AnimationMixerBuilders.js";
178
+ export * from "./AnimationDebugOverlay.js";
156
179
  export { gameAssetValidation, quaterniusGameReadyFighterValidationContract, validateQuaterniusGameReadyFighterAsset } from "./GameAssetValidation.js";
157
180
  export { createAnimationController } from "./AnimationController.js";
158
181
  export * from "./PromptAnimationContract.js";
@@ -244,7 +267,8 @@ export function model(asset, options = {}) {
244
267
  targetMaxDimension: options.targetMaxDimension,
245
268
  targetLength: options.targetLength,
246
269
  physics: options.physics,
247
- hiddenNodeNames: options.hiddenNodeNames
270
+ hiddenNodeNames: options.hiddenNodeNames,
271
+ wrinkle: options.wrinkle
248
272
  });
249
273
  }
250
274
  export function unsafeModelUrl(url, options = {}) {
@@ -337,15 +361,102 @@ export const instances = {
337
361
  cylinder: (options) => instancedPrimitive("cylinder", options),
338
362
  capsule: (options) => instancedPrimitive("capsule", options),
339
363
  torus: (options) => instancedPrimitive("torus", options),
340
- custom: (spec, options) => instancedPrimitive("custom", { ...options, geometry: defineAuraCustomGeometry(spec) })
364
+ custom: (spec, options) => instancedPrimitive("custom", { ...options, geometry: defineAuraCustomGeometry(spec) }),
365
+ /**
366
+ * P2 instanced GLB models (muse3jsparity-PRD): one draw class for repeated
367
+ * static models. Skinned actors fall back with a D1 warning at mount (E1
368
+ * owns rigs); explicitly unaware materials warn at build.
369
+ */
370
+ model: (asset, options) => {
371
+ const built = createInstancedModelNode({
372
+ asset,
373
+ ...(options.name !== undefined ? { name: options.name } : {}),
374
+ transforms: options.transforms.map((transform) => ({
375
+ ...(transform.position !== undefined ? { position: [...transform.position] } : {}),
376
+ ...(transform.rotation !== undefined ? { rotation: [...transform.rotation] } : {}),
377
+ ...(transform.scale !== undefined
378
+ ? { scale: typeof transform.scale === "number" ? transform.scale : [...transform.scale] }
379
+ : {})
380
+ })),
381
+ ...(options.colors ? { colors: [...options.colors] } : {}),
382
+ ...(options.lod ? { lod: options.lod } : {}),
383
+ materialInstancingAware: options.instancingAware ?? true,
384
+ ...(options.maxInstancesPerDraw !== undefined ? { maxInstancesPerDraw: options.maxInstancesPerDraw } : {}),
385
+ ...(options.material?.name !== undefined ? { materialName: options.material.name } : {})
386
+ });
387
+ const base = model(asset, options).toJSON();
388
+ return new AuraNodeBuilder({
389
+ ...base,
390
+ instances: built.node.instances.map((transform) => ({
391
+ ...(transform.position ? { position: [...transform.position] } : {}),
392
+ ...(transform.rotation ? { rotation: [...transform.rotation] } : {}),
393
+ ...(transform.scale !== undefined ? { scale: transform.scale } : {})
394
+ })),
395
+ // Safe: the builder only accepts AuraColor strings above, so no RGB tuples reach the node.
396
+ ...(built.node.instanceColors ? { instanceColors: [...built.node.instanceColors] } : {}),
397
+ ...(built.node.instanceLod ? { instanceLod: built.node.instanceLod } : {}),
398
+ instanceCulling: {
399
+ instanceCount: built.node.instanceCulling.instanceCount,
400
+ centroid: [...built.node.instanceCulling.centroid],
401
+ boundingRadius: built.node.instanceCulling.boundingRadius,
402
+ cullable: true
403
+ },
404
+ ...(built.diagnostics.fallbackWarning ? { instancedModelWarning: built.diagnostics.fallbackWarning.diagnostic } : {})
405
+ });
406
+ }
407
+ };
408
+ export const visualScripting = {
409
+ graph: createVisualScriptingGraphFn,
410
+ attach: attachVisualScriptingGraphFn,
411
+ catalog: listVisualScriptingNodeCatalogFn
412
+ };
413
+ const rootEditorSurface = createRootEditorSurfaceFn();
414
+ export const editor = {
415
+ undo: rootEditorSurface.undo,
416
+ redo: rootEditorSurface.redo,
417
+ gizmo: rootEditorSurface.attachGizmo,
418
+ playMode: { enter: rootEditorSurface.enterPlayMode, exit: rootEditorSurface.exitPlayMode },
419
+ outliner: rootEditorSurface.describeOutliner,
420
+ capabilityLabel: "editor"
341
421
  };
342
422
  export const geometry = {
343
423
  define: defineAuraCustomGeometry,
344
424
  custom: (spec, options = {}) => primitive("custom", { ...options, geometry: defineAuraCustomGeometry(spec) })
345
425
  };
426
+ let cachedSdfFontAtlas;
427
+ function rootSdfFontAtlas() {
428
+ // Baked once per session (~500ms default res); the caller never bakes per frame.
429
+ cachedSdfFontAtlas ??= createSdfFontAtlas();
430
+ return cachedSdfFontAtlas;
431
+ }
346
432
  export function text3D(text, options = {}) {
347
433
  const built = createAuraText3DGeometry(text, options);
348
- return primitive("custom", { ...options, geometry: built.geometry, text3D: { text: built.text, glyphCount: built.glyphCount, unsupportedCharacters: built.unsupportedCharacters, method: built.method } });
434
+ if ((options.backend ?? "extruded-mesh") === "extruded-mesh") {
435
+ return primitive("custom", { ...options, geometry: built.geometry, text3D: { text: built.text, glyphCount: built.glyphCount, unsupportedCharacters: built.unsupportedCharacters, method: built.method } });
436
+ }
437
+ // G1 SDF backend (muse3jsparity-PRD): the layout validates fail-loud and is
438
+ // recorded on the descriptor with the full author intent (size, spacing,
439
+ // style, occlusion policy) so the production bridge can replay the sampler
440
+ // at mount. The extruded mesh stays as the diagnosed fallback geometry.
441
+ const layout = layoutSdfText(text, rootSdfFontAtlas(), { size: options.size ?? 1, letterSpacing: options.letterSpacing, style: options.sdfStyle });
442
+ return primitive("custom", {
443
+ ...options,
444
+ geometry: built.geometry,
445
+ text3D: {
446
+ text: built.text,
447
+ glyphCount: built.glyphCount,
448
+ unsupportedCharacters: [...new Set([...built.unsupportedCharacters, ...layout.unsupportedCharacters])],
449
+ method: "sdf-atlas-quad",
450
+ backend: "sdf",
451
+ sdfQuadCount: layout.quads.length,
452
+ sdfWidthWorld: layout.widthWorld,
453
+ sdfHeightWorld: layout.heightWorld,
454
+ sdfSize: options.size ?? 1,
455
+ ...(options.letterSpacing === undefined ? {} : { sdfLetterSpacing: options.letterSpacing }),
456
+ ...(options.sdfStyle === undefined ? {} : { sdfStyle: options.sdfStyle }),
457
+ sdfOcclusion: options.sdfOcclusion ?? "dim"
458
+ }
459
+ });
349
460
  }
350
461
  export function distanceLod(options) {
351
462
  if (options.levels.length === 0)
@@ -397,6 +508,13 @@ function proceduralTexture(texture, options = {}) {
397
508
  colorB: options.colorB
398
509
  };
399
510
  }
511
+ const PHYSICAL_SPEC_KEYS = [
512
+ "color", "roughness", "metallic", "metalness", "clearcoat", "clearcoatRoughness",
513
+ "sheen", "sheenColor", "sheenRoughness", "iridescence", "iridescenceIOR",
514
+ "iridescenceThicknessRange", "anisotropy", "anisotropyRotation", "transmission",
515
+ "thickness", "attenuationColor", "attenuationDistance", "ior", "specularIntensity",
516
+ "specularColor"
517
+ ];
400
518
  export const material = {
401
519
  pbr: (options = {}) => ({
402
520
  color: "#d7dee8",
@@ -405,7 +523,29 @@ export const material = {
405
523
  metalness: options.metalness ?? options.metallic ?? 0,
406
524
  ...options
407
525
  }),
408
- physical: (options = {}) => material.pbr(options),
526
+ physical: (options = {}) => {
527
+ // P3 (muse3jsparity-PRD): the sync factory stays scalar (C1 decision —
528
+ // no renderer change); extension params validate through the physical
529
+ // matrix and every non-supported request rides the spec as a warning.
530
+ const defined = PHYSICAL_SPEC_KEYS.reduce((picked, key) => {
531
+ const value = options[key];
532
+ if (value !== undefined)
533
+ picked[key] = value;
534
+ return picked;
535
+ }, {});
536
+ const result = createPhysicalMaterialSpec(defined);
537
+ const normalized = PHYSICAL_SPEC_KEYS.reduce((picked, key) => {
538
+ const value = result.spec[key];
539
+ if (value !== undefined && options[key] !== undefined)
540
+ picked[key] = value;
541
+ return picked;
542
+ }, {});
543
+ return {
544
+ ...material.pbr(options),
545
+ ...normalized,
546
+ ...(result.boundedWarnings.length > 0 ? { physicalWarnings: [...result.boundedWarnings] } : {})
547
+ };
548
+ },
409
549
  emissive: (options = {}) => ({
410
550
  color: options.color ?? "#111827",
411
551
  emissive: options.emissive ?? options.color ?? "#38d6ff",
@@ -699,33 +839,41 @@ const materialCapabilityCatalog = [
699
839
  {
700
840
  id: "base-color-texture",
701
841
  label: "Base-color texture",
702
- rootSafeApi: "partial",
842
+ rootSafeApi: "supported",
703
843
  productionRuntime: "supported",
704
- evidence: "Imported GLB baseColorTexture is sampled by the root WebGL path, but the root material contract currently keeps texture support partial until controlled texture on/off pixels are retained.",
844
+ evidence: "Root textured-PBR upgrade resolves AuraAssetRef texture urls to sRGB baseColor textures with controlled texture on/off pixel deltas (tests/browser/root-textured-c1.spec.ts, tests/reports/root-textured-c1/c1-probe.json).",
705
845
  claimRule: "Can describe typed texture metadata and rendered textured assets; do not claim full texture-material parity without controlled root pixels."
706
846
  },
707
847
  {
708
848
  id: "metallic-roughness",
709
849
  label: "Metallic/roughness",
710
- rootSafeApi: "partial",
850
+ rootSafeApi: "supported",
711
851
  productionRuntime: "supported",
712
- evidence: "Root material specs expose values, but production-style material response needs route-specific pixel proof.",
852
+ evidence: "Root textured-PBR upgrade composites roughnessMap/metalnessMap to the glTF metallic-roughness convention with scalar fallbacks and controlled browser pixel proof (tests/browser/root-textured-c1.spec.ts).",
713
853
  claimRule: "Do not claim full PBR parity without browser evidence."
714
854
  },
715
855
  {
716
856
  id: "normal-map",
717
857
  label: "Normal maps",
718
- rootSafeApi: "partial",
858
+ rootSafeApi: "supported",
859
+ productionRuntime: "supported",
860
+ evidence: "Root textured-PBR upgrade binds linear normal maps with tangent-space sampling over authored TBN and controlled browser pixel proof (tests/browser/root-textured-c1.spec.ts).",
861
+ claimRule: "Claim only with material-specific screenshot evidence."
862
+ },
863
+ {
864
+ id: "occlusion-map",
865
+ label: "Occlusion maps",
866
+ rootSafeApi: "supported",
719
867
  productionRuntime: "supported",
720
- evidence: "Root specs carry normal/procedural-normal intent; production shader parity is not guaranteed.",
868
+ evidence: "Root textured-PBR upgrade binds linear occlusionMap textures (R channel) with occlusionStrength and controlled browser pixel proof (tests/browser/root-textured-c1.spec.ts).",
721
869
  claimRule: "Claim only with material-specific screenshot evidence."
722
870
  },
723
871
  {
724
872
  id: "emissive",
725
873
  label: "Emissive",
726
- rootSafeApi: "partial",
874
+ rootSafeApi: "supported",
727
875
  productionRuntime: "supported",
728
- evidence: "Root specs and effects expose emissive color; pixel-backed bloom is reported separately.",
876
+ evidence: "Root textured-PBR upgrade binds sRGB emissiveMap textures multiplied by the emissive factor with controlled browser pixel proof (tests/browser/root-textured-c1.spec.ts); pixel-backed bloom is reported separately.",
729
877
  claimRule: "Distinguish emissive material from postprocess bloom."
730
878
  },
731
879
  {
@@ -779,9 +927,9 @@ const materialCapabilityCatalog = [
779
927
  {
780
928
  id: "hdr-ibl",
781
929
  label: "HDR/IBL",
782
- rootSafeApi: "partial",
930
+ rootSafeApi: "supported",
783
931
  productionRuntime: "supported",
784
- evidence: "Root environment nodes request lighting intent; environment prefiltering is not proven in the root path.",
932
+ evidence: "Root environments.hdri resolves authored Radiance HDR through the HDR-to-cubemap-to-GGX-to-BRDF-LUT chain with iblPixelBacked diagnostics and controlled browser pixel deltas (tests/browser/root-ibl-b3.spec.ts, tests/reports/root-ibl-b3/b3-probe.json).",
785
933
  claimRule: "Root HDR/IBL claims require diagnostics and browser pixels."
786
934
  },
787
935
  {
@@ -805,7 +953,9 @@ function createMaterialCapabilityDiagnostics(input) {
805
953
  requested.add("metallic-roughness");
806
954
  if (spec.normal)
807
955
  requested.add("normal-map");
808
- if (spec.emissive || spec.emissiveIntensity !== undefined)
956
+ if (spec.occlusionMap || spec.occlusionStrength !== undefined)
957
+ requested.add("occlusion-map");
958
+ if (spec.emissive || spec.emissiveIntensity !== undefined || spec.emissiveMap)
809
959
  requested.add("emissive");
810
960
  if (spec.opacity !== undefined && spec.opacity < 1)
811
961
  requested.add("alpha");
@@ -829,6 +979,11 @@ function createMaterialCapabilityDiagnostics(input) {
829
979
  if (partialRequestedFeatures.length > 0) {
830
980
  warnings.push(`Partial root material features requested: ${partialRequestedFeatures.join(", ")}. Capture route pixels before claiming production material quality.`);
831
981
  }
982
+ for (const spec of specs) {
983
+ for (const physicalWarning of spec.physicalWarnings ?? []) {
984
+ warnings.push(`Physical material extension bounded: ${physicalWarning}`);
985
+ }
986
+ }
832
987
  if (unsupportedRequestedFeatures.length > 0) {
833
988
  warnings.push(`Metadata/internal material features requested: ${unsupportedRequestedFeatures.join(", ")}. Do not claim rendered support from root createAuraApp alone.`);
834
989
  }
@@ -1048,6 +1203,20 @@ export const lights = {
1048
1203
  intensity: options.intensity ?? 2,
1049
1204
  color: options.color ?? "#ffffff"
1050
1205
  }),
1206
+ spot: (options = {}) => new AuraNodeBuilder({
1207
+ kind: "light",
1208
+ light: "spot",
1209
+ name: options.name ?? "spot light",
1210
+ position: options.position ?? [0, 4, 0],
1211
+ target: options.target,
1212
+ angle: options.angle ?? Math.PI / 6,
1213
+ penumbra: options.penumbra ?? 0.4,
1214
+ distance: options.distance ?? 12,
1215
+ decay: options.decay,
1216
+ intensity: options.intensity ?? 8,
1217
+ color: options.color ?? "#ffffff",
1218
+ shadow: options.shadow
1219
+ }),
1051
1220
  studio: (options = {}) => new AuraNodeBuilder({
1052
1221
  kind: "light",
1053
1222
  light: "studio",
@@ -1257,8 +1426,10 @@ export const camera = {
1257
1426
  solar: () => camera.orbit({ target: [0, 0, 0], distance: 7.2, fov: 46 }),
1258
1427
  humanoid: () => camera.perspective({ position: [1.2, 1.12, 3.45], target: [0, 0.78, -0.55], fov: 36 }),
1259
1428
  miniGolf: () => camera.follow({ targetNode: "white physics golf ball", distance: 4.2, fov: 48 }),
1260
- neon: () => camera.flythrough({ from: [0, 0.36, 1.6], to: [0, 0.36, -5.8], target: [0, 0.26, -6.8], fov: 54, captureTime: 0.16 })
1429
+ neon: () => camera.flythrough({ from: [0, 0.36, 1.6], to: [0, 0.36, -5.8], target: [0, 0.26, -6.8], fov: 54, captureTime: 0.16 }),
1430
+ ...gameCameraRigs
1261
1431
  };
1432
+ export const gameFeel = { create: gameFeelBuilders.create, hitStopDefaults: gameFeelBuilders.hitStopDefaults };
1262
1433
  function resolveFrameAssetRenderScale(bounds, options) {
1263
1434
  const size = boundsSize(bounds);
1264
1435
  const height = Math.max(0.001, boundsHeight(bounds));
@@ -1314,7 +1485,10 @@ export const effects = {
1314
1485
  radius: options.radius ?? 0.38,
1315
1486
  threshold: options.threshold ?? 0.7,
1316
1487
  antiBlowout,
1317
- maxIntensity
1488
+ maxIntensity,
1489
+ ...(options.quality !== undefined ? { quality: options.quality } : {}),
1490
+ ...(options.softKnee !== undefined ? { softKnee: options.softKnee } : {}),
1491
+ ...(options.shoulder !== undefined ? { shoulder: options.shoulder } : {})
1318
1492
  });
1319
1493
  },
1320
1494
  cinematicBloom: (options = {}) => effects.bloom({
@@ -1333,10 +1507,22 @@ export const effects = {
1333
1507
  antiBlowout: options.antiBlowout ?? true,
1334
1508
  maxIntensity: options.maxIntensity ?? 0.92
1335
1509
  }),
1336
- volumetricFog: (options = {}) => effects.fog({
1510
+ /**
1511
+ * A5 volumetric fog (muse3jsparity-PRD): builds a DISTINCT "volumetric-fog"
1512
+ * node (never plain fog) that submits the depth-aware inscatter pass plus
1513
+ * forward GPU height-fog terms. quality "off" keeps forward exp2 fog only.
1514
+ */
1515
+ volumetricFog: (options = {}) => new AuraNodeBuilder({
1516
+ kind: "effect",
1517
+ effect: "volumetric-fog",
1518
+ name: options.name ?? "volumetric fog inscatter",
1337
1519
  density: options.density ?? 0.18,
1338
1520
  color: options.color ?? "#6f84b9",
1339
- intensity: options.intensity ?? 0.7
1521
+ intensity: options.intensity ?? 0.7,
1522
+ ...(options.volumetricQuality ? { volumetricQuality: options.volumetricQuality } : {}),
1523
+ ...(options.lightPosition ? { lightPosition: options.lightPosition } : {}),
1524
+ ...(options.heightFalloff !== undefined ? { heightFalloff: options.heightFalloff } : {}),
1525
+ ...(options.heightReference !== undefined ? { heightReference: options.heightReference } : {})
1340
1526
  }),
1341
1527
  depthFog: (options = {}) => effects.fog({
1342
1528
  density: options.density ?? 0.14,
@@ -1361,6 +1547,82 @@ export const effects = {
1361
1547
  density: options.density ?? 0.7,
1362
1548
  color: options.color ?? "#020617"
1363
1549
  }),
1550
+ /**
1551
+ * Root color-grade node (muse3jsparity-PRD A3). contrast/saturation execute
1552
+ * natively; exposure/shadows/highlights/lut are recorded on the node and
1553
+ * warned (no native grade target yet — never silently accepted).
1554
+ */
1555
+ colorGrade: (options = {}) => new AuraNodeBuilder({
1556
+ kind: "effect",
1557
+ effect: "color-grade",
1558
+ name: options.name ?? "color grade",
1559
+ intensity: options.intensity ?? 1,
1560
+ exposure: options.exposure ?? 1,
1561
+ contrast: options.contrast ?? 1,
1562
+ saturation: options.saturation ?? 1,
1563
+ ...(options.shadows !== undefined ? { shadows: options.shadows } : {}),
1564
+ ...(options.highlights !== undefined ? { highlights: options.highlights } : {}),
1565
+ ...(options.lut !== undefined ? { lut: options.lut } : {})
1566
+ }),
1567
+ /**
1568
+ * 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.
1571
+ */
1572
+ antiAlias: (options = {}) => new AuraNodeBuilder({
1573
+ kind: "effect",
1574
+ effect: "anti-alias",
1575
+ name: options.name ?? "anti alias",
1576
+ mode: options.mode ?? "fxaa",
1577
+ intensity: options.intensity ?? 1
1578
+ }),
1579
+ /**
1580
+ * Root outline node (muse3jsparity-PRD A3): native outline pass, width in
1581
+ * pixels clamped to the device range 1-6 by the bridge.
1582
+ */
1583
+ outline: (options = {}) => new AuraNodeBuilder({
1584
+ kind: "effect",
1585
+ effect: "outline",
1586
+ name: options.name ?? "outline",
1587
+ color: options.color ?? "#ff9822",
1588
+ width: options.width ?? 3,
1589
+ threshold: options.threshold ?? 0.12,
1590
+ intensity: options.intensity ?? 0.9
1591
+ }),
1592
+ /**
1593
+ * Root screen-space reflections node (muse3jsparity-PRD A3): submits the
1594
+ * native SSR pass against renderer-owned depth where available.
1595
+ */
1596
+ screenSpaceReflections: (options = {}) => new AuraNodeBuilder({
1597
+ kind: "effect",
1598
+ effect: "screen-space-reflections",
1599
+ name: options.name ?? "screen space reflections",
1600
+ intensity: options.intensity ?? 0.9
1601
+ }),
1602
+ /**
1603
+ * Root depth-of-field node (muse3jsparity-PRD A3). focus is a
1604
+ * linear-distance fraction (0 = near plane, 1 = far plane); aperture widens
1605
+ * the focus transition band (game-tuned control, not a physical f-stop).
1606
+ */
1607
+ depthOfField: (options = {}) => new AuraNodeBuilder({
1608
+ kind: "effect",
1609
+ effect: "depth-of-field",
1610
+ name: options.name ?? "depth of field",
1611
+ focus: options.focus ?? 0.02,
1612
+ aperture: options.aperture ?? 0.35,
1613
+ maxBlur: options.maxBlur ?? 4,
1614
+ intensity: options.intensity ?? 1
1615
+ }),
1616
+ /**
1617
+ * Root motion-blur node (muse3jsparity-PRD A3): recorded but withheld (no
1618
+ * velocity binding at root) with an explicit diagnostic warning.
1619
+ */
1620
+ motionBlur: (options = {}) => new AuraNodeBuilder({
1621
+ kind: "effect",
1622
+ effect: "motion-blur",
1623
+ name: options.name ?? "motion blur",
1624
+ intensity: options.intensity ?? 0.5
1625
+ }),
1364
1626
  rain: (options = {}) => new AuraNodeBuilder({
1365
1627
  kind: "effect",
1366
1628
  effect: "rain",
@@ -1373,6 +1635,62 @@ export const effects = {
1373
1635
  splashes: options.splashes ?? true,
1374
1636
  mist: options.mist ?? true
1375
1637
  }),
1638
+ snow: (options = {}) => new AuraNodeBuilder({
1639
+ kind: "effect",
1640
+ effect: "snow",
1641
+ intensity: options.intensity ?? 0.4,
1642
+ density: options.density ?? 0.68,
1643
+ color: options.color ?? "#e8f1ff",
1644
+ speed: options.speed ?? 1,
1645
+ wind: options.wind ?? [-0.85, -1.1, -0.22],
1646
+ particleCount: options.particleCount,
1647
+ splashes: options.splashes ?? false,
1648
+ mist: options.mist ?? true
1649
+ }),
1650
+ /**
1651
+ * Flipbook explosion/muzzle-flash sprite sheet (muse3jsparity-PRD D4):
1652
+ * recorded with validated sheet geometry, but withheld — root has no
1653
+ * native sprite-sheet sampler yet, so no pass is submitted.
1654
+ */
1655
+ flipbook: (options = {}) => {
1656
+ const columns = options.spriteColumns ?? 4;
1657
+ const rows = options.spriteRows ?? 4;
1658
+ resolveFlipbookUv(0, columns, rows);
1659
+ return new AuraNodeBuilder({
1660
+ kind: "effect",
1661
+ effect: "flipbook-sprite",
1662
+ name: options.name ?? "flipbook explosion sprite",
1663
+ intensity: options.intensity ?? 1,
1664
+ color: options.color ?? "#ffb347",
1665
+ spriteColumns: columns,
1666
+ spriteRows: rows,
1667
+ frameRate: options.frameRate ?? 24
1668
+ });
1669
+ },
1670
+ /**
1671
+ * Additive thick light beam / fence strip (muse3jsparity-PRD D4): recorded
1672
+ * with a validated quad-strip descriptor, but withheld — root has no native
1673
+ * beam target yet, so no pass is submitted.
1674
+ */
1675
+ beam: (options = {}) => {
1676
+ const descriptor = createBeamDescriptor({
1677
+ from: options.from ?? [0, 1, 0],
1678
+ to: options.to ?? [0, 1, -4],
1679
+ ...(options.widthWorld !== undefined ? { widthWorld: options.widthWorld } : {}),
1680
+ ...(options.segmentCount !== undefined ? { segmentCount: options.segmentCount } : {})
1681
+ });
1682
+ return new AuraNodeBuilder({
1683
+ kind: "effect",
1684
+ effect: "light-beam",
1685
+ name: options.name ?? "additive light beam",
1686
+ intensity: options.intensity ?? 0.9,
1687
+ color: options.color ?? "#9fd8ff",
1688
+ from: [...descriptor.from],
1689
+ to: [...descriptor.to],
1690
+ widthWorld: descriptor.widthWorld,
1691
+ segmentCount: descriptor.segmentCount
1692
+ });
1693
+ },
1376
1694
  particles: (options = {}) => new AuraNodeBuilder({
1377
1695
  kind: "effect",
1378
1696
  effect: "particles",
@@ -1398,6 +1716,161 @@ export const effects = {
1398
1716
  noise: options.noise ?? 0.22
1399
1717
  })
1400
1718
  };
1719
+ /**
1720
+ * D3 atmosphere builders (PRD D3 boxes 1-2). ADDITIVE root surface over the
1721
+ * pure descriptors in `@aura3d/rendering` (`DayNightSky.ts`,
1722
+ * `AtmosphereWetness.ts`, `Weather.ts`).
1723
+ *
1724
+ * Every builder below composes pre-existing node kinds only (primitive,
1725
+ * light, effect); no existing builder is modified. Precipitation pixels in
1726
+ * the default production path come from weather-state-driven primitive
1727
+ * streaks/flakes, because the production bridge does not pixel-back `rain`,
1728
+ * `snow`, or `particles` effect passes (they render only in the safe-basic
1729
+ * fallback and the Canvas2D diagnostic path). The matching effect node is
1730
+ * still declared so diagnostics report the request.
1731
+ */
1732
+ export const sky = {
1733
+ /** Time-of-day sky: background key, sun/moon discs, stars, 2D-noise clouds, key light. */
1734
+ dayNight: (options = {}) => {
1735
+ const state = createDayNightSky(options);
1736
+ const nodes = [];
1737
+ const sunUp = state.sun.elevationRadians > -0.05;
1738
+ const moonUp = state.moon.elevationRadians > -0.05;
1739
+ if (sunUp) {
1740
+ nodes.push(primitives.sphere({ name: "d3 sun disc", material: material.emissive({ color: state.sun.color, emissive: state.sun.color, emissiveIntensity: 2.2, roughness: 0.8 }) })
1741
+ .position(-6 * Math.cos(state.sun.azimuthRadians), 1 + 5 * Math.sin(state.sun.azimuthRadians), -7).scale(0.85).toJSON());
1742
+ }
1743
+ if (moonUp) {
1744
+ nodes.push(primitives.sphere({ name: "d3 moon disc", material: material.emissive({ color: state.moon.color, emissive: state.moon.color, emissiveIntensity: 1.4, roughness: 0.8 }) })
1745
+ .position(-6 * Math.cos(state.moon.azimuthRadians), 1 + 5 * Math.sin(state.moon.azimuthRadians), -7).scale(0.6).toJSON());
1746
+ }
1747
+ const starLimit = Math.max(0, Math.min(120, options.starLimit ?? 48));
1748
+ const visibleStars = state.stars.filter((star) => star.brightness > 0.05 && star.y > 0).slice(0, starLimit);
1749
+ for (const [index, star] of visibleStars.entries()) {
1750
+ nodes.push(primitives.sphere({ name: `d3 night star ${index}`, material: material.emissive({ color: "#dbeafe", emissive: "#bfdbfe", emissiveIntensity: 1.8, roughness: 0.9 }) })
1751
+ .position(star.x * 7, star.y * 4 + 0.6, -6.5).scale(0.028 + star.size * 2).toJSON());
1752
+ }
1753
+ const cloudLimit = Math.max(0, Math.min(48, options.cloudLimit ?? 12));
1754
+ const cloudTint = state.dayFactor > 0.5 ? "#f1f5f9" : state.dayFactor > 0.2 ? "#b6a6a6" : "#1e293b";
1755
+ for (const [index, cell] of state.clouds.slice(0, cloudLimit).entries()) {
1756
+ nodes.push(primitives.sphere({ name: `d3 noise cloud ${index}`, material: material.pbr({ color: cloudTint, roughness: 1, metallic: 0 }) })
1757
+ .position(cell.x * 6, cell.y * 3 + 1.4, -5.5).scale([cell.radius * 1.7, cell.radius * 0.5, cell.radius * 0.8]).toJSON());
1758
+ }
1759
+ nodes.push(lights.directional({
1760
+ name: sunUp ? "d3 sun key light" : "d3 moon key light",
1761
+ position: sunUp ? [-4, 5, 2] : [3, 4, -1],
1762
+ color: sunUp ? state.sun.color : state.moon.color,
1763
+ intensity: sunUp ? 0.6 + state.dayFactor * 1.2 : 0.35
1764
+ }).toJSON());
1765
+ return { nodes, background: state.dayFactor > 0.5 ? state.horizonColor : state.nightFactor > 0.6 ? "#020617" : state.horizonColor, dayFactor: state.dayFactor, visibleStarCount: visibleStars.length };
1766
+ }
1767
+ };
1768
+ export const weather = {
1769
+ /** Rain/snow declaration + weather-state-driven primitive streaks/flakes for production-path pixels. */
1770
+ precipitation: (options) => {
1771
+ const state = createWeatherState({
1772
+ type: options.type,
1773
+ seed: options.seed ?? 0xd3e7,
1774
+ elapsedSeconds: options.elapsedSeconds ?? 1.2,
1775
+ maxVisualDrops: 400
1776
+ });
1777
+ const snowing = options.type === "snow";
1778
+ const intensity = snowing ? Math.max(0.2, state.snowIntensity) : Math.max(0.2, state.rainIntensity);
1779
+ const nodes = [
1780
+ (snowing ? effects.snow({ intensity, color: "#e8f1ff" }) : effects.rain({ intensity, color: "#bcd7ff" })).toJSON()
1781
+ ];
1782
+ const limit = Math.max(0, Math.min(160, options.streakLimit ?? (snowing ? 70 : 90)));
1783
+ const drops = state.visualDrops.slice(0, limit);
1784
+ drops.forEach((drop, index) => {
1785
+ const x = Math.max(-3.4, Math.min(3.4, drop.x * 2.4));
1786
+ const y = Math.max(0.15, Math.min(3.4, drop.y + 1.6));
1787
+ const z = Math.max(-4.5, Math.min(2.5, drop.z * 2.4));
1788
+ if (snowing) {
1789
+ nodes.push(primitives.sphere({ name: `d3 snow flake ${index}`, material: material.pbr({ color: "#eef4ff", roughness: 0.85, metallic: 0 }) })
1790
+ .position(x, y, z).scale(0.035).toJSON());
1791
+ }
1792
+ else {
1793
+ nodes.push(primitives.box({ name: `d3 rain streak ${index}`, material: material.pbr({ color: "#bcd7ff", roughness: 0.35, metallic: 0 }) })
1794
+ .position(x, y, z).scale([0.014, Math.max(0.08, drop.length * 2.4), 0.014]).toJSON());
1795
+ }
1796
+ });
1797
+ return { nodes, dropCount: drops.length, wetness: state.wetness };
1798
+ },
1799
+ /** Ground slab with wetness darkening + puddle discs mapped from WeatherPuddlePatch. */
1800
+ wetGround: (options = {}) => {
1801
+ const probe = describeWetMaterial({
1802
+ type: options.type ?? "rain",
1803
+ dryColor: options.dryColor ?? "#5b6b4f",
1804
+ dryRoughness: options.dryRoughness ?? 0.9,
1805
+ seed: options.seed ?? 0xd3e7
1806
+ });
1807
+ const size = Math.max(2, Math.min(14, options.size ?? 8));
1808
+ const nodes = [
1809
+ primitives.box({ name: "d3 weather ground slab", material: material.pbr({ color: probe.response.albedoColor, roughness: probe.response.roughness, metallic: 0 }) })
1810
+ .position(0, -0.02, 0).scale([size, 0.04, size]).toJSON()
1811
+ ];
1812
+ probe.weather.puddlePatches.forEach((patch, index) => {
1813
+ nodes.push(primitives.cylinder({ name: `d3 puddle disc ${index}`, material: material.pbr({ color: "#16283a", roughness: 0.05, metallic: 0.1 }) })
1814
+ .position(patch.x * 2.4, 0.005, patch.z * 2.4).scale([Math.max(0.06, patch.radius * 3), 0.006, Math.max(0.06, patch.radius * 3)]).toJSON());
1815
+ });
1816
+ return {
1817
+ nodes,
1818
+ wetness: probe.response.wetness,
1819
+ albedoColor: probe.response.albedoColor,
1820
+ roughness: probe.response.roughness,
1821
+ puddleCount: probe.weather.puddlePatches.length
1822
+ };
1823
+ },
1824
+ /** Lightning-flash light hook: intensity is 0 unless type is thunderstorm. */
1825
+ lightning: (options = { type: "thunderstorm" }) => {
1826
+ const probe = describeWetMaterial({ type: options.type, elapsedSeconds: options.elapsedSeconds ?? 0.4, seed: options.seed ?? 0xd3e7 });
1827
+ return {
1828
+ nodes: probe.flash.intensity > 0
1829
+ ? [lights.directional({ name: "d3 lightning flash", color: "#dbeafe", intensity: 1 + probe.flash.intensity * 4, position: [2, 6, -3] }).toJSON()]
1830
+ : [],
1831
+ intensity: probe.flash.intensity
1832
+ };
1833
+ }
1834
+ };
1835
+ /**
1836
+ * D3 water surface builder (PRD D3 box 3). ADDITIVE root surface over
1837
+ * `createWaterSurface` in `@aura3d/rendering`.
1838
+ *
1839
+ * Rendered material = layered opaque depth-tinted bands (bounded refraction
1840
+ * look) + fresnel-baked sky tint + white shore-foam discs mapped from
1841
+ * `OceanFoamPatch` + boat + fading wake trail. Buoyancy queries stay on the
1842
+ * fixture (`sampleOceanFixture().buoyancy`); this builder creates no planar
1843
+ * reflection/refraction targets (B4 dependency, see
1844
+ * WATER_SURFACE_PLANAR_DEPENDENCY).
1845
+ */
1846
+ export const water = {
1847
+ surface: (options = {}) => {
1848
+ const state = createWaterSurface({ preset: options.preset, seed: options.seed, boat: options.boat });
1849
+ const nodes = [];
1850
+ const bandSpan = 4 / state.bands.length;
1851
+ state.bands.forEach((band, index) => {
1852
+ nodes.push(primitives.box({ name: `d3 water depth band ${index}`, material: material.pbr({ color: band.color, roughness: 0.12, metallic: 0.05 }) })
1853
+ .position(0, 0.02 + index * 0.0012, -4.5 + bandSpan * (index + 0.5)).scale([9, 0.024, bandSpan + 0.01]).toJSON());
1854
+ });
1855
+ nodes.push(primitives.box({ name: "d3 shoreline sand", material: material.pbr({ color: "#cbb37f", roughness: 0.95, metallic: 0 }) })
1856
+ .position(0, 0, 0.9).scale([9, 0.03, 2.6]).toJSON());
1857
+ for (const [index, patch] of state.foam.entries()) {
1858
+ nodes.push(primitives.sphere({ name: `d3 shore foam ${index}`, material: material.pbr({ color: "#f4fafd", roughness: 0.55, metallic: 0 }) })
1859
+ .position(Math.max(-4.2, Math.min(4.2, patch.x * 3)), 0.045, -0.42 + (index % 3) * 0.12).scale([Math.max(0.08, patch.radius * 6), 0.02, Math.max(0.05, patch.radius * 3)]).toJSON());
1860
+ }
1861
+ if (options.withBoatHull !== false && options.boat) {
1862
+ nodes.push(primitives.box({ name: "d3 wake boat hull", material: material.pbr({ color: "#7c3f21", roughness: 0.7, metallic: 0 }) })
1863
+ .position(options.boat.x ?? 0, 0.12, options.boat.z ?? -2).scale([0.34, 0.16, 0.9]).toJSON());
1864
+ }
1865
+ state.wake.forEach((segment, index) => {
1866
+ nodes.push(primitives.box({ name: `d3 boat wake ${index}`, material: material.pbr({ color: "#cfeaf7", roughness: 0.3, metallic: 0 }) })
1867
+ .position(segment.x * 2, 0.038, segment.z - 1.2).scale([segment.width, 0.012, 0.3]).toJSON());
1868
+ });
1869
+ return { nodes, bandCount: state.bands.length, foamCount: state.foam.length, wakeActive: state.wakeActive, wakeSegmentCount: state.wake.length };
1870
+ },
1871
+ /** Fixture-side buoyancy telemetry passthrough (no physics implemented here). */
1872
+ buoyancy: (options = {}) => sampleOceanFixture({ preset: options.preset ?? "moderate", seed: options.seed ?? 0xaa7e5 }).buoyancy
1873
+ };
1401
1874
  export const interactions = {
1402
1875
  orbit: (options = {}) => new AuraNodeBuilder({
1403
1876
  kind: "interaction",
@@ -1648,7 +2121,24 @@ export const environments = {
1648
2121
  if (materialClass === "product")
1649
2122
  return environments.productHero();
1650
2123
  return environments.studio();
1651
- }
2124
+ },
2125
+ /**
2126
+ * B3 root HDRI environment (muse3jsparity-PRD). The `.hdr` asset resolves
2127
+ * post-mount: first frames render the honest studio procedural fallback,
2128
+ * then the HDR→cubemap→GGX-prefilter→BRDF-LUT chain swaps in and
2129
+ * `iblPixelBacked` flips true in diagnostics. Fetch/parse failures keep the
2130
+ * fallback and warn — never a black scene, never a silent swap.
2131
+ */
2132
+ hdri: (options) => new AuraNodeBuilder({
2133
+ kind: "environment",
2134
+ environment: "hdri",
2135
+ name: options.name ?? "hdri ibl environment",
2136
+ intensity: options.intensity ?? 1,
2137
+ texture: options.texture,
2138
+ ...(options.reflectionTexture ? { reflectionTexture: options.reflectionTexture } : {}),
2139
+ ...(options.color ? { color: options.color } : {}),
2140
+ ...(options.rotation !== undefined ? { rotation: options.rotation } : {})
2141
+ })
1652
2142
  };
1653
2143
  const rendererColorManagementPreset = {
1654
2144
  kind: "aura-renderer-color-management",
@@ -1774,6 +2264,7 @@ function normalizeCreateAppRendererOptions(options) {
1774
2264
  mode,
1775
2265
  fallback: options?.fallback ?? "safe-basic",
1776
2266
  qualityProfile: profile.id,
2267
+ textureBudgetBytes: normalizeTextureBudgetBytes(options?.textureBudgetBytes),
1777
2268
  profile
1778
2269
  };
1779
2270
  }
@@ -1817,17 +2308,42 @@ function createRendererDiagnosticReport(snapshot, runtime, rendererOptions) {
1817
2308
  const sceneCategory = resolveRendererSceneCategory(snapshot, names);
1818
2309
  const exposure = sceneExposurePresets[sceneCategory];
1819
2310
  const bloom = flattened.find((node) => node.kind === "effect" && node.effect === "bloom");
1820
- const fog = flattened.find((node) => node.kind === "effect" && node.effect === "fog");
2311
+ const fog = flattened.find((node) => node.kind === "effect" && (node.effect === "fog" || node.effect === "volumetric-fog"));
1821
2312
  const environment = flattened.find((node) => node.kind === "environment");
1822
2313
  const contactShadows = names.filter((name) => name.includes("contact shadow") || name.includes("footprint") || name.includes("glow pool")).length;
1823
2314
  const ambientOcclusion = flattened.some((node) => node.kind === "effect" && node.effect === "ambient-occlusion");
1824
2315
  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.
2319
+ const colorGradeNode = flattened.find((node) => node.kind === "effect" && node.effect === "color-grade");
2320
+ const antiAliasNode = flattened.find((node) => node.kind === "effect" && node.effect === "anti-alias");
2321
+ const outlineNode = flattened.find((node) => node.kind === "effect" && node.effect === "outline");
2322
+ const ssrNode = flattened.find((node) => node.kind === "effect" && node.effect === "screen-space-reflections");
2323
+ const dofNode = flattened.find((node) => node.kind === "effect" && node.effect === "depth-of-field");
2324
+ const motionBlurNode = flattened.find((node) => node.kind === "effect" && node.effect === "motion-blur");
2325
+ const flipbookNode = flattened.find((node) => node.kind === "effect" && node.effect === "flipbook-sprite");
2326
+ const beamNode = flattened.find((node) => node.kind === "effect" && node.effect === "light-beam");
2327
+ const sdfTextRequested = flattened.some((node) => node.kind === "primitive" && node.text3D?.backend === "sdf");
2328
+ const fxaaRequested = (antiAliasNode?.mode ?? "fxaa") === "fxaa" && Boolean(antiAliasNode);
2329
+ const taaWithheld = (antiAliasNode?.mode ?? "fxaa") === "taa";
2330
+ const motionBlurWithheld = Boolean(motionBlurNode);
1825
2331
  const runtimePostprocess = runtime?.postprocess;
1826
2332
  const runtimePasses = runtimePostprocess?.actualPasses ?? [];
1827
- const postprocessRequested = Boolean(bloom) || ambientOcclusion || contactOcclusion || runtimePasses.length > 0;
2333
+ const postprocessRequested = Boolean(bloom) || ambientOcclusion || contactOcclusion
2334
+ || Boolean(colorGradeNode) || Boolean(antiAliasNode) || Boolean(outlineNode)
2335
+ || Boolean(ssrNode) || Boolean(dofNode) || motionBlurWithheld || runtimePasses.length > 0;
1828
2336
  const requestedPasses = runtimePasses.length > 0
1829
2337
  ? runtimePasses
1830
- : requestedRendererPostProcessPasses(Boolean(bloom), ambientOcclusion, contactOcclusion);
2338
+ : requestedRendererPostProcessPasses(Boolean(bloom), ambientOcclusion, contactOcclusion, {
2339
+ colorGrade: Boolean(colorGradeNode),
2340
+ fxaa: fxaaRequested,
2341
+ outline: Boolean(outlineNode),
2342
+ ssr: Boolean(ssrNode),
2343
+ depthOfField: Boolean(dofNode),
2344
+ motionBlurWithheld,
2345
+ taaWithheld
2346
+ });
1831
2347
  const runtimeStatus = !postprocessRequested
1832
2348
  ? "disabled"
1833
2349
  : runtime?.mounted
@@ -1842,6 +2358,28 @@ function createRendererDiagnosticReport(snapshot, runtime, rendererOptions) {
1842
2358
  warnings.push("scene has no contact shadow or contact-occlusion grounding cue");
1843
2359
  if (bloom && (bloom.intensity ?? 0) > 0.95 && bloom.antiBlowout !== true)
1844
2360
  warnings.push("bloom is high without anti-blowout safeguards");
2361
+ if (motionBlurWithheld)
2362
+ warnings.push("motion-blur is recorded but withheld: root has no velocity binding, so no motion-blur pass is submitted");
2363
+ if (flipbookNode)
2364
+ warnings.push("flipbook-sprite is recorded but withheld: root has no native sprite-sheet sampler yet, so no flipbook pass is submitted");
2365
+ if (beamNode)
2366
+ warnings.push("light-beam is recorded but withheld: root has no native beam target yet, so no beam pass is submitted");
2367
+ // G1: the baked-SDF sampler mounts synchronously on the production bridge.
2368
+ // Warn only when the SDF request cannot be pixel-backed (non-production
2369
+ // mount, or the sampler fell back to the extruded mesh at runtime).
2370
+ if (sdfTextRequested && (!runtime?.mounted || !(runtime.text?.textPixelBacked ?? false))) {
2371
+ warnings.push(runtime?.mounted
2372
+ ? `text3D sdf backend fell back to the extruded mesh (${runtime.text?.reason ?? "sampler failed"}); textPixelBacked stays false`
2373
+ : "text3D sdf backend is recorded but unmounted: the production bridge samples the SDF atlas at mount, so textPixelBacked stays false until render");
2374
+ }
2375
+ if (taaWithheld)
2376
+ warnings.push("anti-alias mode \"taa\" is recorded but withheld: root has no history binding, so no taa pass is submitted");
2377
+ if (colorGradeNode && (colorGradeNode.exposure ?? 1) !== 1)
2378
+ warnings.push("color-grade exposure is recorded but has no native grade target yet; contrast/saturation execute");
2379
+ if (colorGradeNode && (colorGradeNode.shadows !== undefined || colorGradeNode.highlights !== undefined))
2380
+ warnings.push("color-grade shadows/highlights are recorded but have no native grade target yet; contrast/saturation execute");
2381
+ if (colorGradeNode?.lut !== undefined)
2382
+ warnings.push("color-grade lut is recorded but LUT samplers are not bound yet; the lut is ignored");
1845
2383
  if (postprocessRequested && !runtime?.mounted)
1846
2384
  warnings.push("renderer diagnostics are a scene plan only; call createAuraApp(...).diagnostics() after the first render for pixel-backed pass status");
1847
2385
  if (postprocessRequested && runtime?.mounted && !runtimePostprocess?.pixelBacked)
@@ -1891,7 +2429,8 @@ function createRendererDiagnosticReport(snapshot, runtime, rendererOptions) {
1891
2429
  intensity: bloom?.intensity ?? 0,
1892
2430
  threshold: bloom?.threshold ?? 0,
1893
2431
  radius: bloom?.radius ?? 0,
1894
- antiBlowout: bloom?.antiBlowout ?? true
2432
+ antiBlowout: bloom?.antiBlowout ?? true,
2433
+ ...(bloom?.quality !== undefined ? { quality: bloom.quality } : {})
1895
2434
  },
1896
2435
  shadows: {
1897
2436
  // Never a source-authored `true`. When the runtime is mounted this is the
@@ -1906,7 +2445,8 @@ function createRendererDiagnosticReport(snapshot, runtime, rendererOptions) {
1906
2445
  nativeShadowMapBindings: runtime?.shadow?.nativeShadowMapBindings ?? 0,
1907
2446
  shadowRenderTargetsAllocated: runtime?.shadow?.shadowRenderTargetsAllocated ?? 0,
1908
2447
  contactShadows,
1909
- mapType: "pcf-soft"
2448
+ mapType: "pcf-soft",
2449
+ ...(runtime?.shadow?.spot === undefined ? {} : { spot: runtime.shadow.spot })
1910
2450
  },
1911
2451
  occlusion: {
1912
2452
  enabled: ambientOcclusion || contactOcclusion,
@@ -1921,7 +2461,9 @@ function createRendererDiagnosticReport(snapshot, runtime, rendererOptions) {
1921
2461
  fog: {
1922
2462
  enabled: Boolean(fog),
1923
2463
  density: fog?.density ?? 0,
1924
- preset: fog ? ((fog.intensity ?? 0) > 0.65 ? "volumetric" : "depth") : "none"
2464
+ // A5: a volumetric-fog node labels "volumetric" because it submits the
2465
+ // distinct inscatter pass; plain fog keeps the legacy intensity label.
2466
+ preset: fog ? (fog.effect === "volumetric-fog" || (fog.intensity ?? 0) > 0.65 ? "volumetric" : "depth") : "none"
1925
2467
  },
1926
2468
  postprocess: {
1927
2469
  enabled: runtimePostprocess?.pixelBacked ?? false,
@@ -1937,6 +2479,7 @@ function createRendererDiagnosticReport(snapshot, runtime, rendererOptions) {
1937
2479
  actualPasses: runtimePostprocess?.actualPasses ?? [],
1938
2480
  fallbackPasses: runtimePostprocess?.fallbackPasses ?? [],
1939
2481
  targetFormat: runtimePostprocess?.targetFormat,
2482
+ executionMode: runtimePostprocess?.executionMode ?? "unknown",
1940
2483
  evidence: postprocessEvidence
1941
2484
  },
1942
2485
  runtime: {
@@ -1950,13 +2493,39 @@ function createRendererDiagnosticReport(snapshot, runtime, rendererOptions) {
1950
2493
  visibleObjects: runtime?.deviceDiagnostics?.visibleObjects ?? 0,
1951
2494
  culledObjects: runtime?.deviceDiagnostics?.culledObjects ?? 0,
1952
2495
  frustumTestedObjects: runtime?.deviceDiagnostics?.frustumTestedObjects ?? 0,
1953
- lodSelections: runtime?.lodSelections ?? []
2496
+ bloom: runtime?.deviceDiagnostics?.bloom ?? null,
2497
+ samplerAnisotropyUploads: runtime?.deviceDiagnostics?.samplerAnisotropyUploads ?? 0,
2498
+ maxTextureAnisotropy: runtime?.deviceDiagnostics?.maxTextureAnisotropy ?? 1,
2499
+ lodSelections: runtime?.lodSelections ?? [],
2500
+ texturedMaterials: runtime?.texturedMaterials ?? []
1954
2501
  },
1955
2502
  environment: {
1956
2503
  enabled: environmentStatus.enabled,
1957
2504
  preset: environmentStatus.preset,
1958
2505
  intensity: environmentStatus.intensity,
1959
- evidence: environmentStatus.evidence
2506
+ evidence: environmentStatus.evidence,
2507
+ // B3: pass through the HDRI upgrade observation; the unmounted default
2508
+ // reports not-pixel-backed so static diagnostics never claim HDRI proof.
2509
+ iblPixelBacked: environmentStatus.iblPixelBacked ?? false,
2510
+ hdriStatus: environmentStatus.hdriStatus ?? "none",
2511
+ ...(environmentStatus.dualProbe === undefined ? {} : { dualProbe: environmentStatus.dualProbe }),
2512
+ ...(environmentStatus.hdriRotation === undefined ? {} : { hdriRotation: environmentStatus.hdriRotation })
2513
+ },
2514
+ text: runtime?.text ?? {
2515
+ sdfTexts: flattened.filter((node) => node.kind === "primitive" && node.text3D?.backend === "sdf").length,
2516
+ textPixelBacked: false,
2517
+ quadCount: 0,
2518
+ lastOpacity: 1,
2519
+ reason: "SDF text requested in the scene graph but no runtime has mounted yet"
2520
+ },
2521
+ textures: runtime?.textures ?? {
2522
+ budgetBytes: normalizeTextureBudgetBytes(rendererOptions?.textureBudgetBytes),
2523
+ usedBytes: 0,
2524
+ requestedBytes: 0,
2525
+ overBudget: false,
2526
+ overBudgetBytes: 0,
2527
+ residentEntries: 0,
2528
+ evictedEntries: []
1960
2529
  },
1961
2530
  antialiasing: rendererQualityPresets.screenshot.antialiasing,
1962
2531
  screenshotQuality: rendererQualityPresets.screenshot,
@@ -1964,14 +2533,31 @@ function createRendererDiagnosticReport(snapshot, runtime, rendererOptions) {
1964
2533
  warnings
1965
2534
  };
1966
2535
  }
1967
- function requestedRendererPostProcessPasses(hasBloom, ambientOcclusion, contactOcclusion) {
1968
- if (!hasBloom && !ambientOcclusion && !contactOcclusion)
2536
+ function requestedRendererPostProcessPasses(hasBloom, ambientOcclusion, contactOcclusion, extra = {}) {
2537
+ if (!hasBloom && !ambientOcclusion && !contactOcclusion && !extra.colorGrade && !extra.fxaa
2538
+ && !extra.outline && !extra.ssr && !extra.depthOfField && !extra.motionBlurWithheld && !extra.taaWithheld)
1969
2539
  return [];
1970
2540
  const passes = ["render"];
1971
2541
  if (ambientOcclusion || contactOcclusion)
1972
2542
  passes.push("ssao");
1973
2543
  if (hasBloom)
1974
2544
  passes.push("bloom");
2545
+ if (extra.colorGrade)
2546
+ passes.push("color-grade");
2547
+ if (extra.ssr)
2548
+ passes.push("ssr");
2549
+ if (extra.depthOfField)
2550
+ passes.push("depth-of-field");
2551
+ if (extra.outline)
2552
+ passes.push("outline");
2553
+ if (extra.fxaa)
2554
+ passes.push("fxaa");
2555
+ // Withheld intents stay visible in the request list with an explicit suffix
2556
+ // so requested-vs-actual never silently agrees on an unexecuted pass.
2557
+ if (extra.motionBlurWithheld)
2558
+ passes.push("motion-blur (withheld: no velocity binding)");
2559
+ if (extra.taaWithheld)
2560
+ passes.push("taa (withheld: no history binding)");
1975
2561
  passes.push("output");
1976
2562
  return passes;
1977
2563
  }
@@ -6384,6 +6970,13 @@ export function createRuntimeNodeImportedAssetEvidence(input) {
6384
6970
  renderItemCount: input.renderItemCount ?? 0,
6385
6971
  skinnedRenderItemCount: input.skinnedRenderItemCount ?? 0,
6386
6972
  morphRenderItemCount: input.morphRenderItemCount ?? 0,
6973
+ ...(input.lastMaterialTracksApplied !== undefined ? { lastMaterialTracksApplied: input.lastMaterialTracksApplied } : {}),
6974
+ ...(input.lastLightTracksApplied !== undefined ? { lastLightTracksApplied: input.lastLightTracksApplied } : {}),
6975
+ ...(input.lastFootPlantingGroundedFeet !== undefined ? { lastFootPlantingGroundedFeet: input.lastFootPlantingGroundedFeet } : {}),
6976
+ ...(input.lastFootPlantingTargetError !== undefined ? { lastFootPlantingTargetError: input.lastFootPlantingTargetError } : {}),
6977
+ ...(input.lastFootPlantingHipOffset !== undefined ? { lastFootPlantingHipOffset: input.lastFootPlantingHipOffset } : {}),
6978
+ ...(input.lastFootPlantingMissingLegs !== undefined ? { lastFootPlantingMissingLegs: [...input.lastFootPlantingMissingLegs] } : {}),
6979
+ ...(input.footPlantingConfigured !== undefined ? { footPlantingConfigured: input.footPlantingConfigured } : {}),
6387
6980
  diagnostics
6388
6981
  };
6389
6982
  }
@@ -6749,7 +7342,9 @@ export function createAuraApp(target, options) {
6749
7342
  appPhysicsWorldInstance ??= new PhysicsWorld({
6750
7343
  gravity: options.physics?.gravity ? [...options.physics.gravity] : [0, -9.81, 0],
6751
7344
  fixedDelta: 1 / 60,
6752
- enableSleeping: true
7345
+ enableSleeping: true,
7346
+ ...(options.physics?.seed === undefined ? {} : { seed: options.physics.seed }),
7347
+ ...(options.physics?.continuousCollision ? { continuousCollision: options.physics.continuousCollision } : {})
6753
7348
  });
6754
7349
  return appPhysicsWorldInstance;
6755
7350
  };
@@ -7717,6 +8312,13 @@ pausedRenderTime = () => 0) {
7717
8312
  labelLayer.setOcclusionTest(createSceneLabelOcclusionTest(snapshot, resolveCameraFrame(snapshot, snapshot.camera, renderTime, runtimeNodes).eye, runtimeNodes));
7718
8313
  labelLayer.update(renderer.viewProjection(renderTime));
7719
8314
  diagnosticsState.labels = labelLayer.snapshot();
8315
+ const labelNodes = groups.flatten(snapshot.nodes).filter((node) => node.kind === "label");
8316
+ diagnosticsState.labelTelemetry = collectLabelTelemetry(labelNodes, diagnosticsState.labels);
8317
+ diagnosticsState.textBuckets = summarizeTextBuckets({
8318
+ accessibleDom: labelNodes.filter((node) => node.label === "hud").length,
8319
+ worldAnchoredPlaced: diagnosticsState.labels.filter((label) => label.visible).length,
8320
+ sdfTexts: groups.flatten(snapshot.nodes).filter((node) => node.kind === "primitive" && node.text3D?.backend === "sdf").length
8321
+ });
7720
8322
  }
7721
8323
  overlay?.update();
7722
8324
  if ((sceneWantsFrames || requiresFrames()) && options.autoStart !== false && typeof requestAnimationFrame !== "undefined") {
@@ -7814,16 +8416,37 @@ function createProductionRuntimeEnvironment(snapshot) {
7814
8416
  "product-hero": "softbox",
7815
8417
  "night-cinematic": "evening",
7816
8418
  "metal-studio": "exhibit",
7817
- "glass-studio": "softbox"
8419
+ "glass-studio": "softbox",
8420
+ // B3: HDRI first frames render the honest studio procedural fallback;
8421
+ // the post-mount upgrade swaps in the HDR chain (hdriUrl below).
8422
+ "hdri": "studio"
7818
8423
  };
7819
8424
  const preset = presetByEnvironment[authoredEnvironment.environment];
7820
8425
  const bundle = createExternalParityEnvironmentLighting(preset);
7821
8426
  const intensity = nonNegativeFinite(authoredEnvironment.intensity);
7822
8427
  const proceduralMap = bundle.lighting.proceduralMap;
8428
+ const hdriUrl = authoredEnvironment.environment === "hdri"
8429
+ && typeof authoredEnvironment.texture === "object"
8430
+ && authoredEnvironment.texture.kind === "aura-asset-ref"
8431
+ ? authoredEnvironment.texture.url ?? undefined
8432
+ : undefined;
8433
+ const hdriReflectionUrl = authoredEnvironment.environment === "hdri"
8434
+ && typeof authoredEnvironment.reflectionTexture === "object"
8435
+ && authoredEnvironment.reflectionTexture.kind === "aura-asset-ref"
8436
+ ? authoredEnvironment.reflectionTexture.url ?? undefined
8437
+ : undefined;
8438
+ const hdriRotation = authoredEnvironment.environment === "hdri" && authoredEnvironment.rotation !== undefined
8439
+ ? authoredEnvironment.rotation
8440
+ : undefined;
7823
8441
  return {
7824
8442
  preset: authoredEnvironment.environment,
7825
8443
  intensity,
7826
- evidence: `production bridge submitted the authored ${authoredEnvironment.environment} environment through generated HDR ${preset} lighting`,
8444
+ evidence: hdriUrl
8445
+ ? `production bridge renders the studio procedural fallback until the authored HDRI ${hdriUrl}${hdriReflectionUrl ? ` (reflection ${hdriReflectionUrl})` : ""} resolves through the HDR chain`
8446
+ : `production bridge submitted the authored ${authoredEnvironment.environment} environment through generated HDR ${preset} lighting`,
8447
+ ...(hdriUrl ? { hdriUrl } : {}),
8448
+ ...(hdriReflectionUrl ? { hdriReflectionUrl } : {}),
8449
+ ...(hdriRotation !== undefined ? { hdriRotation } : {}),
7827
8450
  lighting: {
7828
8451
  ...bundle.lighting,
7829
8452
  color: authoredEnvironment.color ? colorToLinearRgb(authoredEnvironment.color) : bundle.lighting.color,
@@ -7878,14 +8501,16 @@ function createProductionRuntimeEnvironment(snapshot) {
7878
8501
  * changed exactly zero pixels through `createAuraApp`. Returning `false` when no
7879
8502
  * fog node is authored keeps the unfogged path unchanged.
7880
8503
  */
7881
- function createProductionRuntimeEnvironmentFog(snapshot) {
7882
- const fog = groups.flatten(snapshot.nodes).find((node) => node.kind === "effect" && node.effect === "fog");
8504
+ function createProductionRuntimeEnvironmentFog(snapshot, lights = [], renderWidth = 1280, renderHeight = 720) {
8505
+ const nodes = groups.flatten(snapshot.nodes);
8506
+ const volumetric = nodes.find((node) => node.kind === "effect" && node.effect === "volumetric-fog");
8507
+ const fog = volumetric ?? nodes.find((node) => node.kind === "effect" && node.effect === "fog");
7883
8508
  if (!fog)
7884
8509
  return false;
7885
8510
  const density = clampNumber(fog.density ?? 0.12, 0, 1);
7886
8511
  const intensity = clampNumber(fog.intensity ?? 0.5, 0, 1);
7887
8512
  const color = colorToLinearRgb(fog.color ?? "#9fb7d9");
7888
- return {
8513
+ const base = {
7889
8514
  // Exponential-squared reads as depth haze rather than a hard linear band, which
7890
8515
  // matches what the public helper documents.
7891
8516
  mode: "exponential-squared",
@@ -7895,10 +8520,46 @@ function createProductionRuntimeEnvironmentFog(snapshot) {
7895
8520
  density,
7896
8521
  maxOpacity: clampNumber(0.25 + intensity * 0.55, 0, 0.92)
7897
8522
  };
8523
+ if (!volumetric)
8524
+ return base;
8525
+ // A5: the volumetric-fog node drives the GPU forward inscatter terms from
8526
+ // the dominant collected light; quality "off" zeroes the forward term and
8527
+ // keeps this exp2 base exactly.
8528
+ const resolved = resolveVolumetricFog({
8529
+ density: volumetric.density,
8530
+ intensity: volumetric.intensity,
8531
+ ...(volumetric.lightPosition ? { lightPosition: volumetric.lightPosition } : {}),
8532
+ ...(volumetric.heightFalloff !== undefined ? { heightFalloff: volumetric.heightFalloff } : {}),
8533
+ ...(volumetric.heightReference !== undefined ? { heightReference: volumetric.heightReference } : {}),
8534
+ ...(volumetric.volumetricQuality ? { quality: volumetric.volumetricQuality } : {})
8535
+ }, lights, renderWidth, renderHeight);
8536
+ return {
8537
+ ...base,
8538
+ density: clampNumber(volumetric.density ?? 0.18, 0, 1),
8539
+ volumetricIntensity: resolved.forward.volumetricIntensity,
8540
+ ...(resolved.forward.volumetricLightDirection ? { volumetricLightDirection: resolved.forward.volumetricLightDirection } : {}),
8541
+ volumetricLightColor: resolved.forward.volumetricLightColor,
8542
+ ...(resolved.forward.heightFalloff !== undefined ? { heightFalloff: resolved.forward.heightFalloff } : {}),
8543
+ ...(resolved.forward.heightReference !== undefined ? { heightReference: resolved.forward.heightReference } : {})
8544
+ };
7898
8545
  }
7899
- function createProductionRuntimePostprocess(snapshot) {
8546
+ function createProductionRuntimePostprocess(snapshot, lights = [], renderWidth = 1280, renderHeight = 720) {
7900
8547
  const nodes = groups.flatten(snapshot.nodes);
7901
8548
  const authoredBloom = nodes.find((node) => node.kind === "effect" && node.effect === "bloom");
8549
+ // A5: the DISTINCT volumetric-fog node submits the depth-aware inscatter
8550
+ // pass (renderer-owned depth attaches automatically); quality "off" (or no
8551
+ // node) submits nothing and the scene keeps forward exp2 fog only.
8552
+ const authoredVolumetricFog = nodes.find((node) => node.kind === "effect" && node.effect === "volumetric-fog");
8553
+ const volumetricPass = authoredVolumetricFog
8554
+ ? resolveVolumetricFog({
8555
+ density: authoredVolumetricFog.density,
8556
+ intensity: authoredVolumetricFog.intensity,
8557
+ ...(authoredVolumetricFog.lightPosition ? { lightPosition: authoredVolumetricFog.lightPosition } : {}),
8558
+ ...(authoredVolumetricFog.heightFalloff !== undefined ? { heightFalloff: authoredVolumetricFog.heightFalloff } : {}),
8559
+ ...(authoredVolumetricFog.heightReference !== undefined ? { heightReference: authoredVolumetricFog.heightReference } : {}),
8560
+ ...(authoredVolumetricFog.volumetricQuality ? { quality: authoredVolumetricFog.volumetricQuality } : {})
8561
+ }, lights, renderWidth, renderHeight).pass
8562
+ : null;
7902
8563
  const bloomRequested = Boolean(authoredBloom);
7903
8564
  // Root previously advertised `ssao` in `requestedPasses` whenever a scene added
7904
8565
  // effects.ambientOcclusion() or effects.contactOcclusion(), but never submitted
@@ -7908,6 +8569,17 @@ function createProductionRuntimePostprocess(snapshot) {
7908
8569
  const authoredAmbientOcclusion = nodes.find((node) => node.kind === "effect" && node.effect === "ambient-occlusion");
7909
8570
  const authoredContactOcclusion = nodes.find((node) => node.kind === "effect" && node.effect === "contact-occlusion");
7910
8571
  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.
8576
+ const authoredColorGrade = nodes.find((node) => node.kind === "effect" && node.effect === "color-grade");
8577
+ const authoredAntiAlias = nodes.find((node) => node.kind === "effect" && node.effect === "anti-alias");
8578
+ const authoredOutline = nodes.find((node) => node.kind === "effect" && node.effect === "outline");
8579
+ const authoredSsr = nodes.find((node) => node.kind === "effect" && node.effect === "screen-space-reflections");
8580
+ const authoredDof = nodes.find((node) => node.kind === "effect" && node.effect === "depth-of-field");
8581
+ const fxaaRequested = (authoredAntiAlias?.mode ?? "fxaa") === "fxaa";
8582
+ const outlineChannels = colorToRgba(authoredOutline?.color ?? "#ff9822");
7911
8583
  return {
7912
8584
  // Tone mapping requires unclamped linear input. RGBA8 quantized dark clear
7913
8585
  // colors and clipped highlights before ACES, which produced washed-out output.
@@ -7916,7 +8588,10 @@ function createProductionRuntimePostprocess(snapshot) {
7916
8588
  bloom: {
7917
8589
  threshold: clampNumber(authoredBloom?.threshold ?? 0.78, 0, 1),
7918
8590
  intensity: clampNumber(authoredBloom?.intensity ?? 0.3, 0, 2),
7919
- radius: Math.max(1, Math.min(4, Math.round(authoredBloom?.radius ?? 2)))
8591
+ radius: Math.max(1, Math.min(4, Math.round(authoredBloom?.radius ?? 2))),
8592
+ ...(authoredBloom?.quality !== undefined ? { quality: authoredBloom.quality } : {}),
8593
+ ...(authoredBloom?.softKnee !== undefined ? { softKnee: authoredBloom.softKnee } : {}),
8594
+ ...(authoredBloom?.shoulder !== undefined ? { shoulder: authoredBloom.shoulder } : {})
7920
8595
  }
7921
8596
  } : {}),
7922
8597
  ...(authoredOcclusion ? {
@@ -7937,7 +8612,41 @@ function createProductionRuntimePostprocess(snapshot) {
7937
8612
  operator: "aces",
7938
8613
  inputColorSpace: "linear",
7939
8614
  outputColorSpace: "srgb"
7940
- }
8615
+ },
8616
+ ...(authoredColorGrade ? {
8617
+ colorGrade: {
8618
+ contrast: clampNumber(authoredColorGrade.contrast ?? 1, 0, 3),
8619
+ saturation: clampNumber(authoredColorGrade.saturation ?? 1, 0, 3)
8620
+ }
8621
+ } : {}),
8622
+ ...(authoredOutline ? {
8623
+ outline: {
8624
+ color: [
8625
+ Math.round(outlineChannels[0] * 255),
8626
+ Math.round(outlineChannels[1] * 255),
8627
+ Math.round(outlineChannels[2] * 255),
8628
+ 255
8629
+ ],
8630
+ width: Math.max(1, Math.min(6, Math.round(authoredOutline.width ?? 3))),
8631
+ threshold: clampNumber(authoredOutline.threshold ?? 0.12, 0, 4),
8632
+ opacity: clampNumber(authoredOutline.intensity ?? 0.9, 0, 1)
8633
+ }
8634
+ } : {}),
8635
+ ...(fxaaRequested && authoredAntiAlias ? { fxaa: {} } : {}),
8636
+ ...(authoredSsr ? {
8637
+ ssr: {
8638
+ intensity: clampNumber(authoredSsr.intensity ?? 0.9, 0, 2),
8639
+ maxDistance: 18
8640
+ }
8641
+ } : {}),
8642
+ ...(authoredDof ? {
8643
+ depthOfField: {
8644
+ focusDepth: clampNumber(authoredDof.focus ?? 0.02, 0, 1),
8645
+ focusRange: clampNumber(0.02 + clampNumber(authoredDof.aperture ?? 0.35, 0, 1) * 0.3, 0.001, 1),
8646
+ maxRadius: Math.max(0, Math.min(8, Math.round(authoredDof.maxBlur ?? 4)))
8647
+ }
8648
+ } : {}),
8649
+ ...(volumetricPass ? { volumetricLight: volumetricPass } : {})
7941
8650
  };
7942
8651
  }
7943
8652
  function createProductionRuntimeShadowOptions(snapshot, collectedLights) {
@@ -7990,6 +8699,79 @@ function createProductionRuntimeShadowObservation(shadowOptions, diagnostics) {
7990
8699
  shadowRenderTargetsAllocated: diagnostics.shadowRenderTargetsAllocated ?? 0
7991
8700
  };
7992
8701
  }
8702
+ /**
8703
+ * G1 SDF text observation (muse3jsparity-PRD): per-resource fail-closed gate
8704
+ * (`describeSdfTextPixelBacking`) ANDed across mounted SDF resources. Layout
8705
+ * alone never backs pixels; lastOpacity is the live LOD x occlusion proof.
8706
+ */
8707
+ function createProductionTextObservation(entries) {
8708
+ const sdf = entries.flatMap((entry) => entry.resources.filter((resource) => resource.sdfText !== null));
8709
+ if (sdf.length === 0) {
8710
+ return {
8711
+ sdfTexts: 0,
8712
+ textPixelBacked: false,
8713
+ quadCount: 0,
8714
+ lastOpacity: 1,
8715
+ reason: "no SDF text nodes mounted"
8716
+ };
8717
+ }
8718
+ const gates = sdf.map((resource) => describeSdfTextPixelBacking({
8719
+ atlasUploaded: resource.textureStatus === "textured" && resource.texturedMaterial !== null,
8720
+ quadsSubmitted: resource.sdfText.lastSubmitted,
8721
+ quadCount: resource.sdfText.quadCount
8722
+ }));
8723
+ const backed = gates.filter((gate) => gate.textPixelBacked).length;
8724
+ const quadCount = sdf.reduce((total, resource) => total + (resource.sdfText?.quadCount ?? 0), 0);
8725
+ const lastOpacity = Math.min(...sdf.map((resource) => resource.sdfText?.lastOpacity ?? 1));
8726
+ return {
8727
+ sdfTexts: sdf.length,
8728
+ textPixelBacked: backed === sdf.length,
8729
+ quadCount,
8730
+ lastOpacity,
8731
+ reason: backed === sdf.length
8732
+ ? `${backed}/${sdf.length} SDF quad sets sampled from uploaded label textures (${quadCount} quads)`
8733
+ : `${backed}/${sdf.length} SDF quad sets backed: ${gates.filter((gate) => !gate.textPixelBacked).map((gate) => gate.reason).join("; ")}`
8734
+ };
8735
+ }
8736
+ /**
8737
+ * M2 texture streaming observation (muse3jsparity-PRD): funds the
8738
+ * post-upgrade texture table (SDF label images + C1 upgrades) against the
8739
+ * budget with distance-prioritized residency. Empty table = nothing to fund.
8740
+ */
8741
+ function createProductionTexturesObservation(entries, cameraEye, budgetBytes) {
8742
+ const budget = normalizeTextureBudgetBytes(budgetBytes);
8743
+ const table = entries.flatMap((entry, entryIndex) => entry.resources
8744
+ .filter((resource) => resource.textureMipBytes.length > 0)
8745
+ .map((resource, resourceIndex) => {
8746
+ const position = entry.node.position ?? [0, 0, 0];
8747
+ return {
8748
+ id: `${entry.node.name ?? `primitive-${entryIndex}`}#${resourceIndex}`,
8749
+ mipBytesCoarseToFine: resource.textureMipBytes,
8750
+ distanceMeters: Math.hypot(cameraEye[0] - position[0], cameraEye[1] - position[1], cameraEye[2] - position[2])
8751
+ };
8752
+ }));
8753
+ if (table.length === 0) {
8754
+ return {
8755
+ budgetBytes: budget,
8756
+ usedBytes: 0,
8757
+ requestedBytes: 0,
8758
+ overBudget: false,
8759
+ overBudgetBytes: 0,
8760
+ residentEntries: 0,
8761
+ evictedEntries: []
8762
+ };
8763
+ }
8764
+ const residency = describeTextureStreamingResidency(table, budget);
8765
+ return {
8766
+ budgetBytes: budget,
8767
+ usedBytes: residency.usedBytes,
8768
+ requestedBytes: residency.requestedBytes,
8769
+ overBudget: residency.overBudget,
8770
+ overBudgetBytes: residency.overBudgetBytes,
8771
+ residentEntries: residency.residents.filter((resident) => resident.residentLevels > 0).length,
8772
+ evictedEntries: [...residency.evicted]
8773
+ };
8774
+ }
7993
8775
  function createProductionRuntimePostprocessObservation(diagnostics) {
7994
8776
  const actualPasses = diagnostics.postprocessPassNames ?? [];
7995
8777
  const pixelBacked = actualPasses.length > 0 && diagnostics.lastError === null;
@@ -8002,7 +8784,8 @@ function createProductionRuntimePostprocessObservation(diagnostics) {
8002
8784
  pixelBacked,
8003
8785
  actualPasses,
8004
8786
  fallbackPasses: pixelBacked ? [] : ["direct-render"],
8005
- targetFormat: diagnostics.postprocessTargetFormat
8787
+ targetFormat: diagnostics.postprocessTargetFormat,
8788
+ executionMode: diagnostics.postprocessPlan?.executionMode ?? "unknown"
8006
8789
  };
8007
8790
  }
8008
8791
  function createProductionRuntimeCollectedLights(snapshot) {
@@ -8010,16 +8793,26 @@ function createProductionRuntimeCollectedLights(snapshot) {
8010
8793
  const descriptors = authoredLights.flatMap((node, index) => createProductionRuntimeLightDescriptors(node, index));
8011
8794
  if (descriptors.length === 0)
8012
8795
  return createProductionRuntimeFallbackLights();
8013
- const shadowCasterIndex = descriptors.reduce((selected, descriptor, index) => {
8796
+ const shadowCasterIndex = resolveProductionShadowCasterIndex(descriptors);
8797
+ return descriptors.map((descriptor, index) => createProductionRuntimeCollectedLight(descriptor, index === shadowCasterIndex));
8798
+ }
8799
+ /**
8800
+ * N1 caster selection (pure, unit-tested): an explicit `shadow: true`
8801
+ * request wins the single caster slot over unrequested lights; ties and
8802
+ * all-unrequested scenes keep the legacy priority-then-intensity order.
8803
+ */
8804
+ export function resolveProductionShadowCasterIndex(descriptors) {
8805
+ return descriptors.reduce((selected, descriptor, index) => {
8014
8806
  if (selected < 0)
8015
8807
  return index;
8016
8808
  const current = descriptors[selected];
8017
- if (descriptor.shadowPriority !== current.shadowPriority) {
8018
- return descriptor.shadowPriority > current.shadowPriority ? index : 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;
8019
8813
  }
8020
8814
  return descriptor.intensity > current.intensity ? index : selected;
8021
8815
  }, -1);
8022
- return descriptors.map((descriptor, index) => createProductionRuntimeCollectedLight(descriptor, index === shadowCasterIndex));
8023
8816
  }
8024
8817
  function createProductionRuntimeLightDescriptors(node, authoredIndex) {
8025
8818
  const name = node.name?.trim() || `aura-authored-${node.light}-${authoredIndex + 1}`;
@@ -8044,6 +8837,7 @@ function createProductionRuntimeLightDescriptors(node, authoredIndex) {
8044
8837
  spotAngle: 0,
8045
8838
  penumbra: 0,
8046
8839
  shadowPriority: 3,
8840
+ shadowRequested: false,
8047
8841
  authoredLight: node.light
8048
8842
  }];
8049
8843
  }
@@ -8059,12 +8853,33 @@ function createProductionRuntimeLightDescriptors(node, authoredIndex) {
8059
8853
  spotAngle: 0,
8060
8854
  penumbra: 0,
8061
8855
  shadowPriority: 1,
8856
+ shadowRequested: false,
8062
8857
  authoredLight: node.light
8063
8858
  }];
8064
8859
  }
8065
8860
  if (node.light === "studio") {
8066
8861
  return createProductionRuntimeStudioLightDescriptors(node, name, color, intensity, position);
8067
8862
  }
8863
+ if (node.light === "spot") {
8864
+ // N1 (muse3jsparity-PRD): authored spot with an explicit cone. The aim
8865
+ // target wins over lookAt; the direction feeds the B1 spot shadow path
8866
+ // and the A5 volumetric dominant-light selection.
8867
+ const spotTarget = node.target ?? node.lookAt ?? [0, 0.75, 0];
8868
+ return [{
8869
+ kind: "spot",
8870
+ name,
8871
+ color,
8872
+ intensity,
8873
+ position,
8874
+ direction: normalize3([spotTarget[0] - position[0], spotTarget[1] - position[1], spotTarget[2] - position[2]]),
8875
+ range: Math.max(1, node.distance ?? 12),
8876
+ spotAngle: clampNumber(node.angle ?? Math.PI / 6, 0.08, Math.PI / 2 - 0.01),
8877
+ penumbra: clampNumber(node.penumbra ?? 0.4, 0, 1),
8878
+ shadowPriority: 2,
8879
+ shadowRequested: node.shadow ?? false,
8880
+ authoredLight: node.light
8881
+ }];
8882
+ }
8068
8883
  const width = positiveFinite(node.width, node.light === "softbox" ? 2.4 : 2.2)
8069
8884
  * productionRuntimeLightAxisScale(node.scale, 0);
8070
8885
  const height = positiveFinite(node.height, node.light === "softbox" ? 1.6 : 1.2)
@@ -8083,6 +8898,7 @@ function createProductionRuntimeLightDescriptors(node, authoredIndex) {
8083
8898
  spotAngle: clampNumber(Math.atan2(halfDiagonal, targetDistance), 0.08, Math.PI / 2 - 0.01),
8084
8899
  penumbra: node.light === "softbox" ? 0.78 : 0.42,
8085
8900
  shadowPriority: 2,
8901
+ shadowRequested: false,
8086
8902
  authoredLight: node.light,
8087
8903
  authoredWidth: width,
8088
8904
  authoredHeight: height
@@ -8112,6 +8928,7 @@ function createProductionRuntimeStudioLightDescriptors(node, name, color, intens
8112
8928
  spotAngle: 0,
8113
8929
  penumbra: 0,
8114
8930
  shadowPriority: 3,
8931
+ shadowRequested: false,
8115
8932
  authoredLight: node.light
8116
8933
  },
8117
8934
  {
@@ -8125,6 +8942,7 @@ function createProductionRuntimeStudioLightDescriptors(node, name, color, intens
8125
8942
  spotAngle: 0,
8126
8943
  penumbra: 0,
8127
8944
  shadowPriority: 0,
8945
+ shadowRequested: false,
8128
8946
  authoredLight: node.light
8129
8947
  },
8130
8948
  {
@@ -8138,6 +8956,7 @@ function createProductionRuntimeStudioLightDescriptors(node, name, color, intens
8138
8956
  spotAngle: 0,
8139
8957
  penumbra: 0,
8140
8958
  shadowPriority: 0,
8959
+ shadowRequested: false,
8141
8960
  authoredLight: node.light
8142
8961
  }
8143
8962
  ];
@@ -8157,6 +8976,7 @@ function createProductionRuntimeFallbackLights() {
8157
8976
  spotAngle: 0,
8158
8977
  penumbra: 0,
8159
8978
  shadowPriority: 3,
8979
+ shadowRequested: false,
8160
8980
  authoredLight: "fallback"
8161
8981
  },
8162
8982
  {
@@ -8170,6 +8990,7 @@ function createProductionRuntimeFallbackLights() {
8170
8990
  spotAngle: 0,
8171
8991
  penumbra: 0,
8172
8992
  shadowPriority: 0,
8993
+ shadowRequested: false,
8173
8994
  authoredLight: "fallback"
8174
8995
  },
8175
8996
  {
@@ -8183,6 +9004,7 @@ function createProductionRuntimeFallbackLights() {
8183
9004
  spotAngle: 0,
8184
9005
  penumbra: 0,
8185
9006
  shadowPriority: 0,
9007
+ shadowRequested: false,
8186
9008
  authoredLight: "fallback"
8187
9009
  }
8188
9010
  ];
@@ -8349,53 +9171,142 @@ async function createProductionRuntimeSceneRenderer(canvas, snapshot, rendererOp
8349
9171
  });
8350
9172
  let latestDeviceDiagnostics = productionRenderer.getDiagnostics();
8351
9173
  let latestFeatures = productionRenderer.getFeatures();
9174
+ // M2 streaming distances measure against the live camera eye; refreshed
9175
+ // every render so residency follows the camera instead of mount intent.
9176
+ let latestCameraEye = resolveCameraFrame(snapshot, snapshot.camera, 0, runtimeNodes).eye;
8352
9177
  const runtimeWarnings = new Set();
9178
+ // C1 textured upgrade: fire-and-forget after mount. Scalar first frames stay
9179
+ // fast; outcomes land in textureUpgradeWarnings, which render() never clears
9180
+ // (per-frame runtimeWarnings are rebuilt every frame).
9181
+ const textureUpgradeWarnings = new Set();
9182
+ void upgradeProductionPrimitiveTextures(primitiveEntries, (message) => {
9183
+ textureUpgradeWarnings.add(message);
9184
+ }).catch((error) => {
9185
+ textureUpgradeWarnings.add(`textured upgrade pass failed (${error instanceof Error ? error.message : String(error)}); scalar materials retained`);
9186
+ });
8353
9187
  const productionEnvironment = createProductionRuntimeEnvironment(snapshot);
9188
+ // B3 HDRI upgrade: fire-and-forget after mount. First frames render the
9189
+ // studio procedural fallback; the HDR chain swaps the lighting object the
9190
+ // render closure reads every frame. Outcomes land in hdriWarnings, which
9191
+ // render() never clears (per-frame runtimeWarnings are rebuilt every frame).
9192
+ let currentEnvironmentLighting = productionEnvironment.lighting;
9193
+ const hdriWarnings = new Set();
9194
+ const hdriState = { status: productionEnvironment.hdriUrl ? "pending" : "none" };
9195
+ let disposeHdriEnvironment = null;
9196
+ if (flattened.some((node) => node.kind === "environment" && node.environment === "hdri" && node.texture?.kind !== "aura-asset-ref")) {
9197
+ hdriWarnings.add("hdri environment has no texture asset ref; studio procedural fallback retained");
9198
+ }
9199
+ if (productionEnvironment.hdriUrl) {
9200
+ const hdriUrl = productionEnvironment.hdriUrl;
9201
+ const hdriReflectionUrl = productionEnvironment.hdriReflectionUrl;
9202
+ const hdriIntensity = productionEnvironment.intensity;
9203
+ const hdriRotation = productionEnvironment.hdriRotation;
9204
+ void upgradeProductionEnvironmentHdri(hdriUrl, hdriIntensity, hdriReflectionUrl, hdriRotation)
9205
+ .then((result) => {
9206
+ disposeHdriEnvironment?.();
9207
+ disposeHdriEnvironment = result.dispose;
9208
+ currentEnvironmentLighting = result.lighting;
9209
+ hdriState.status = "ready";
9210
+ hdriState.maxLinearValue = result.maxLinearValue;
9211
+ hdriState.specularMipCount = result.specularMipCount;
9212
+ hdriState.dualProbe = result.dualProbe;
9213
+ })
9214
+ .catch((error) => {
9215
+ hdriState.status = "fallback";
9216
+ hdriWarnings.add(`HDRI upgrade failed for ${hdriUrl} (${error instanceof Error ? error.message : String(error)}); studio procedural fallback retained`);
9217
+ });
9218
+ }
8354
9219
  const productionRuntimeLights = createProductionRuntimeCollectedLights(snapshot);
8355
9220
  const authoredLightNodes = flattened.filter((node) => node.kind === "light");
8356
9221
  const authoredDirectLightNodes = authoredLightNodes.filter((node) => node.light !== "ambient");
8357
9222
  const authoredAmbientLightCount = authoredLightNodes.length - authoredDirectLightNodes.length;
8358
9223
  const authoredAreaProxyCount = authoredDirectLightNodes.filter((node) => node.light === "rect" || node.light === "softbox").length;
8359
- const buildDiagnostics = () => createRendererDiagnosticReport(snapshot, {
8360
- mounted: true,
8361
- backend: "production-runtime",
8362
- postprocess: createProductionRuntimePostprocessObservation(latestDeviceDiagnostics),
8363
- shadow: createProductionRuntimeShadowObservation(createProductionRuntimeShadowOptions(snapshot, productionRuntimeLights), latestDeviceDiagnostics),
8364
- environment: {
8365
- enabled: true,
8366
- preset: productionEnvironment.preset,
8367
- intensity: productionEnvironment.intensity,
8368
- evidence: productionEnvironment.evidence
8369
- },
8370
- warnings: [
8371
- `Production runtime bridge active with ${actorEntries.length} typed GLB actor${actorEntries.length === 1 ? "" : "s"} and ${primitiveEntries.length} Aura primitive${primitiveEntries.length === 1 ? "" : "s"} on ${productionRenderer.backend}.`,
8372
- ...(authoredDirectLightNodes.length === 0
8373
- ? ["Production runtime direct-light fallback active: the scene has no authored directional, point, studio, rect, or softbox light."]
8374
- : [`Production runtime derived ${productionRuntimeLights.length} collected direct light${productionRuntimeLights.length === 1 ? "" : "s"} from ${authoredDirectLightNodes.length} authored scene light${authoredDirectLightNodes.length === 1 ? "" : "s"}.`]),
8375
- ...(authoredAmbientLightCount > 0
8376
- ? [`${authoredAmbientLightCount} authored ambient light${authoredAmbientLightCount === 1 ? "" : "s"} remain environment-lighting intent and are not mislabeled as direct CollectedLight entries.`]
8377
- : []),
8378
- ...(authoredAreaProxyCount > 0
8379
- ? [`${authoredAreaProxyCount} authored rect/softbox light${authoredAreaProxyCount === 1 ? "" : "s"} use bounded spot-light proxies whose cone and range derive from authored width and height; this does not claim physical area-light shading.`]
8380
- : []),
8381
- ...(flattened.some((node) => node.kind === "effect")
8382
- ? ["Effect nodes are requested in the scene graph; production bridge diagnostics report them, but unsupported postprocess/effect passes remain non-pixel-backed until the runtime feature reports support."]
8383
- : []),
8384
- ...(productionRenderer.backendSelection.fallback ? [`Production runtime backend fallback: ${productionRenderer.backendSelection.reason}`] : []),
8385
- ...latestFeatures
8386
- .filter((feature) => feature.state !== "supported")
8387
- .map((feature) => `Production runtime feature ${feature.id} is ${feature.state}: ${feature.detail}`),
8388
- ...runtimeWarnings
8389
- ],
8390
- deviceDiagnostics: latestDeviceDiagnostics,
8391
- lodSelections: primitiveEntries
8392
- .filter((entry) => Boolean(entry.node.lod?.levels.length))
8393
- .map((entry) => ({
8394
- nodeName: entry.node.name ?? "unnamed distance LOD",
8395
- levelIndex: entry.currentLodIndex,
8396
- levelName: entry.resources[entry.currentLodIndex]?.name ?? `level-${entry.currentLodIndex}`
8397
- }))
8398
- }, rendererOptions);
9224
+ // N1 + G1 + M2 observations refresh on every diagnostics read from the
9225
+ // live entries/device state — never cached intent.
9226
+ const buildDiagnostics = () => {
9227
+ const shadowObservation = createProductionRuntimeShadowObservation(createProductionRuntimeShadowOptions(snapshot, productionRuntimeLights), latestDeviceDiagnostics);
9228
+ const spotCaster = productionRuntimeLights.find((light) => light.castsShadow);
9229
+ const spotAngle = spotCaster?.kind === "spot" ? spotCaster.spotAngle : undefined;
9230
+ const texturesObservation = createProductionTexturesObservation(primitiveEntries, latestCameraEye, rendererOptions?.textureBudgetBytes ?? normalizeTextureBudgetBytes(undefined));
9231
+ const streamingWarnings = texturesObservation.overBudget
9232
+ ? [`texture streaming over budget by ${texturesObservation.overBudgetBytes} bytes `
9233
+ + `(${texturesObservation.residentEntries} resident, `
9234
+ + `${texturesObservation.evictedEntries.length} evicted: ${texturesObservation.evictedEntries.join(", ") || "none"}); `
9235
+ + `raise renderer.textureBudgetBytes above ${texturesObservation.requestedBytes} bytes or move textures closer`]
9236
+ : [];
9237
+ return createRendererDiagnosticReport(snapshot, {
9238
+ mounted: true,
9239
+ backend: "production-runtime",
9240
+ postprocess: createProductionRuntimePostprocessObservation(latestDeviceDiagnostics),
9241
+ shadow: {
9242
+ ...shadowObservation,
9243
+ spot: describeProductionSpotShadow({
9244
+ requested: flattened.some((node) => node.kind === "light" && node.light === "spot" && node.shadow === true),
9245
+ casterIsSpot: spotCaster?.kind === "spot",
9246
+ ...(spotCaster === undefined ? {} : { casterName: spotCaster.source.name }),
9247
+ ...(spotAngle === undefined ? {} : { angle: spotAngle }),
9248
+ ...(spotCaster?.kind === "spot" ? { penumbra: spotCaster.penumbra, range: spotCaster.range } : {}),
9249
+ mapRendered: shadowObservation.mapRendered,
9250
+ mapSampled: shadowObservation.mapSampled
9251
+ })
9252
+ },
9253
+ text: createProductionTextObservation(primitiveEntries),
9254
+ textures: texturesObservation,
9255
+ environment: {
9256
+ enabled: true,
9257
+ preset: productionEnvironment.preset,
9258
+ intensity: productionEnvironment.intensity,
9259
+ evidence: hdriState.status === "ready"
9260
+ ? `${productionEnvironment.evidence} — HDRI chain ready${hdriState.dualProbe ? " (dual-probe: illumination diffuse + reflection specular)" : " (single probe)"} (maxLinear ${hdriState.maxLinearValue}, ${hdriState.specularMipCount} specular mips)`
9261
+ : productionEnvironment.evidence,
9262
+ // B3 iblPixelBacked: true only after the HDR chain swaps the live
9263
+ // lighting object. Procedural first frames and fallbacks never claim it.
9264
+ iblPixelBacked: hdriState.status === "ready",
9265
+ hdriStatus: hdriState.status,
9266
+ ...(hdriState.dualProbe === undefined ? {} : { dualProbe: hdriState.dualProbe }),
9267
+ ...(productionEnvironment.hdriRotation === undefined ? {} : { hdriRotation: productionEnvironment.hdriRotation })
9268
+ },
9269
+ warnings: [
9270
+ `Production runtime bridge active with ${actorEntries.length} typed GLB actor${actorEntries.length === 1 ? "" : "s"} and ${primitiveEntries.length} Aura primitive${primitiveEntries.length === 1 ? "" : "s"} on ${productionRenderer.backend}.`,
9271
+ ...(authoredDirectLightNodes.length === 0
9272
+ ? ["Production runtime direct-light fallback active: the scene has no authored directional, point, studio, rect, or softbox light."]
9273
+ : [`Production runtime derived ${productionRuntimeLights.length} collected direct light${productionRuntimeLights.length === 1 ? "" : "s"} from ${authoredDirectLightNodes.length} authored scene light${authoredDirectLightNodes.length === 1 ? "" : "s"}.`]),
9274
+ ...(authoredAmbientLightCount > 0
9275
+ ? [`${authoredAmbientLightCount} authored ambient light${authoredAmbientLightCount === 1 ? "" : "s"} remain environment-lighting intent and are not mislabeled as direct CollectedLight entries.`]
9276
+ : []),
9277
+ ...(authoredAreaProxyCount > 0
9278
+ ? [`${authoredAreaProxyCount} authored rect/softbox light${authoredAreaProxyCount === 1 ? "" : "s"} use bounded spot-light proxies whose cone and range derive from authored width and height; this does not claim physical area-light shading.`]
9279
+ : []),
9280
+ ...(flattened.some((node) => node.kind === "effect")
9281
+ ? ["Effect nodes are requested in the scene graph; production bridge diagnostics report them, but unsupported postprocess/effect passes remain non-pixel-backed until the runtime feature reports support."]
9282
+ : []),
9283
+ ...(productionRenderer.backendSelection.fallback ? [`Production runtime backend fallback: ${productionRenderer.backendSelection.reason}`] : []),
9284
+ ...latestFeatures
9285
+ .filter((feature) => feature.state !== "supported")
9286
+ .map((feature) => `Production runtime feature ${feature.id} is ${feature.state}: ${feature.detail}`),
9287
+ ...runtimeWarnings,
9288
+ ...textureUpgradeWarnings,
9289
+ ...streamingWarnings,
9290
+ ...hdriWarnings
9291
+ ],
9292
+ deviceDiagnostics: latestDeviceDiagnostics,
9293
+ lodSelections: primitiveEntries
9294
+ .filter((entry) => Boolean(entry.node.lod?.levels.length))
9295
+ .map((entry) => ({
9296
+ nodeName: entry.node.name ?? "unnamed distance LOD",
9297
+ levelIndex: entry.currentLodIndex,
9298
+ levelName: entry.resources[entry.currentLodIndex]?.name ?? `level-${entry.currentLodIndex}`
9299
+ })),
9300
+ texturedMaterials: primitiveEntries.flatMap((entry) => entry.resources.map((resource) => ({
9301
+ nodeName: entry.node.name ?? `aura-primitive-${entry.node.primitive}`,
9302
+ levelName: resource.name,
9303
+ status: resource.textureStatus,
9304
+ slots: resource.textureSlots,
9305
+ pixelBacked: resource.textureStatus === "textured" && resource.texturedMaterial !== null,
9306
+ warnings: resource.textureWarnings
9307
+ })))
9308
+ }, rendererOptions);
9309
+ };
8399
9310
  return {
8400
9311
  get backend() {
8401
9312
  return productionRenderer.backend;
@@ -8405,7 +9316,8 @@ async function createProductionRuntimeSceneRenderer(canvas, snapshot, rendererOp
8405
9316
  },
8406
9317
  render(time) {
8407
9318
  runtimeWarnings.clear();
8408
- const input = createProductionRuntimeRendererInput(snapshot, canvas, actorEntries, primitiveEntries, time, runtimeNodes, runtimeWarnings, productionEnvironment.lighting, productionRuntimeLights);
9319
+ latestCameraEye = resolveCameraFrame(snapshot, snapshot.camera, time, runtimeNodes).eye;
9320
+ const input = createProductionRuntimeRendererInput(snapshot, canvas, actorEntries, primitiveEntries, time, runtimeNodes, runtimeWarnings, currentEnvironmentLighting, productionRuntimeLights);
8409
9321
  const result = productionRenderer.renderInteractiveFrame(input);
8410
9322
  latestDeviceDiagnostics = result.diagnostics;
8411
9323
  latestFeatures = result.features;
@@ -8427,13 +9339,15 @@ async function createProductionRuntimeSceneRenderer(canvas, snapshot, rendererOp
8427
9339
  return productionRenderer.deviceLost();
8428
9340
  },
8429
9341
  dispose() {
9342
+ disposeHdriEnvironment?.();
8430
9343
  productionRenderer.dispose();
8431
9344
  for (const { actor } of actorEntries)
8432
9345
  actor.dispose();
8433
9346
  for (const { resources } of primitiveEntries) {
8434
- for (const { geometry, material } of resources) {
9347
+ for (const { geometry, material, texturedMaterial } of resources) {
8435
9348
  geometry.dispose();
8436
9349
  material.dispose();
9350
+ texturedMaterial?.dispose();
8437
9351
  }
8438
9352
  }
8439
9353
  }
@@ -8448,21 +9362,88 @@ function createProductionRuntimeRendererInput(snapshot, canvas, actorEntries, pr
8448
9362
  const currentNode = currentState.node;
8449
9363
  if (currentNode.visible === false)
8450
9364
  continue;
9365
+ const modelMatrix = [...createModelMatrix(currentNode, productionActorModelBounds(currentNode.asset, entry.actor), shouldNormalizeModelNode(currentNode), time)];
9366
+ // The foot-planting post-pass solves in the same world space this matrix draws into;
9367
+ // refresh its matrix before the clip plays so the solve uses this frame, not the last.
9368
+ applyProductionActorFootPlanting(entry, currentState.animationBinding, modelMatrix, runtimeWarnings);
8451
9369
  applyProductionActorAnimation(entry, currentNode, currentState.animationBinding, time, runtimeWarnings);
8452
9370
  applyProductionActorMorphTargets(entry, currentState.morphTargets, runtimeWarnings);
8453
- const modelMatrix = [...createModelMatrix(currentNode, productionActorModelBounds(currentNode.asset, entry.actor), shouldNormalizeModelNode(currentNode), time)];
8454
- const actorItems = entry.actor.collectRenderItems({ modelMatrix });
8455
- items.push(...actorItems.map((item) => ({ ...item, castShadow: currentNode.castShadow })));
9371
+ // Wrinkle detail (E1 face-rig demo): resolve morph weights through the model's hook.
9372
+ // Absent hook (or empty weights) resolves to 0 = today's rendering exactly.
9373
+ const wrinkleStrength = currentNode.wrinkle
9374
+ ? resolveWrinkleMapStrength(currentState.morphTargets ?? {}, currentNode.wrinkle)
9375
+ : undefined;
9376
+ const actorItems = entry.actor.collectRenderItems({
9377
+ modelMatrix,
9378
+ ...(wrinkleStrength === undefined ? {} : { wrinkleStrength })
9379
+ });
9380
+ // P2 (muse3jsparity-PRD): model instances attach exactly like primitive
9381
+ // nodes. Skinned actors cannot instance (D1 matrix) — warn once and draw
9382
+ // single instead of silently dropping copies.
9383
+ const modelInstances = currentNode.instances;
9384
+ let modelInstanceAttach = {};
9385
+ if (modelInstances && modelInstances.length > 0) {
9386
+ if (actorItems.some((item) => item.skinning)) {
9387
+ warnOnInstancingFallback({
9388
+ material: currentNode.name ?? currentNode.asset.id,
9389
+ requestedInstances: modelInstances.length,
9390
+ drawnBatches: Math.max(1, actorItems.length),
9391
+ reason: "skinned-palette-overflow-cpu-fallback",
9392
+ onWarning: (message) => runtimeWarnings.add(message)
9393
+ });
9394
+ }
9395
+ else {
9396
+ modelInstanceAttach = {
9397
+ instanceTransforms: createProductionModelInstanceTransforms(modelInstances, currentNode, productionActorModelBounds(currentNode.asset, entry.actor), time),
9398
+ ...(currentNode.instanceColors
9399
+ ? { instanceColors: createProductionInstanceColors(currentNode.instanceColors, modelInstances.length) }
9400
+ : {})
9401
+ };
9402
+ }
9403
+ }
9404
+ if (currentNode.instancedModelWarning)
9405
+ runtimeWarnings.add(currentNode.instancedModelWarning);
9406
+ items.push(...actorItems.map((item) => ({ ...item, castShadow: currentNode.castShadow, ...modelInstanceAttach })));
8456
9407
  attachProductionActorEvidence(currentNode, entry.actor, actorItems, runtimeNodes);
8457
9408
  }
9409
+ // G1 SDF occlusion test, built once per frame only when SDF quads exist
9410
+ // (same cost argument as WS-2.7: one bbox per node, negligible beside render).
9411
+ const sdfEntries = primitiveEntries.filter((entry) => entry.resources.some((resource) => resource.sdfText !== null));
9412
+ const sdfOcclusionTest = sdfEntries.length > 0
9413
+ ? createSceneLabelOcclusionTest(snapshot, cameraPosition, runtimeNodes)
9414
+ : undefined;
8458
9415
  for (const entry of primitiveEntries) {
8459
9416
  const currentState = resolveProductionPrimitiveRuntimeState(entry, runtimeNodes);
8460
9417
  if (!currentState.visible)
8461
9418
  continue;
8462
9419
  const resource = selectProductionPrimitiveResource(entry, currentState.node, cameraPosition);
9420
+ if (resource.sdfText && resource.texturedMaterial) {
9421
+ // G1 per-frame SDF opacity: LOD fade from the live camera distance
9422
+ // times the scene occlusion policy, written to the quad material.
9423
+ // Occlusion "hide" skips submission (no pixels, no backing claim).
9424
+ const position = currentState.node.position ?? [0, 0, 0];
9425
+ const distance = Math.hypot(cameraPosition[0] - position[0], cameraPosition[1] - position[1], cameraPosition[2] - position[2]);
9426
+ const frame = resolveSdfTextFrameOpacity({
9427
+ distance,
9428
+ ...(resource.sdfText.lodFadeNear === undefined ? {} : { lodFadeNear: resource.sdfText.lodFadeNear }),
9429
+ ...(resource.sdfText.lodFadeFar === undefined ? {} : { lodFadeFar: resource.sdfText.lodFadeFar }),
9430
+ occluded: sdfOcclusionTest?.(position) ?? false,
9431
+ occlusionPolicy: resource.sdfText.occlusionPolicy
9432
+ });
9433
+ resource.sdfText.lastOpacity = frame.opacity;
9434
+ resource.sdfText.lastVisible = frame.visible;
9435
+ resource.sdfText.lastSubmitted = frame.visible;
9436
+ if (!frame.visible)
9437
+ continue;
9438
+ const base = resource.texturedMaterial.getParameter("baseColor");
9439
+ const rgb = Array.isArray(base) && base.length >= 3
9440
+ ? [base[0] ?? 1, base[1] ?? 1, base[2] ?? 1]
9441
+ : [1, 1, 1];
9442
+ resource.texturedMaterial.setParameter("baseColor", [rgb[0], rgb[1], rgb[2], frame.opacity]);
9443
+ }
8463
9444
  items.push({
8464
9445
  geometry: resource.geometry,
8465
- material: resource.material,
9446
+ material: resource.texturedMaterial ?? resource.material,
8466
9447
  modelMatrix: createModelMatrix(currentState.node, resource.bounds, false, time),
8467
9448
  label: currentState.node.name ?? `aura-primitive-${currentState.node.primitive}`,
8468
9449
  castShadow: currentState.node.castShadow,
@@ -8480,9 +9461,9 @@ function createProductionRuntimeRendererInput(snapshot, canvas, actorEntries, pr
8480
9461
  // The production runtime owns the pixel-backed HDR target and pass chain for
8481
9462
  // routes that request effects. The diagnostics are device-observed, so a
8482
9463
  // compositor failure is reported as fallback rather than claimed as a pass.
8483
- postprocess: createProductionRuntimePostprocess(snapshot),
9464
+ postprocess: createProductionRuntimePostprocess(snapshot, collectedLights, canvas.width, canvas.height),
8484
9465
  shadow: createProductionRuntimeShadowOptions(snapshot, collectedLights),
8485
- environmentFog: createProductionRuntimeEnvironmentFog(snapshot),
9466
+ environmentFog: createProductionRuntimeEnvironmentFog(snapshot, collectedLights, canvas.width, canvas.height),
8486
9467
  cameraPosition
8487
9468
  };
8488
9469
  const cameraLike = { viewProjectionMatrix };
@@ -8497,10 +9478,505 @@ function createProductionRuntimePrimitiveEntries(nodes) {
8497
9478
  .filter((node) => node.kind === "primitive")
8498
9479
  .map((node) => ({ node, resources: createProductionPrimitiveResources(node), currentLodIndex: 0 }));
8499
9480
  }
9481
+ function blankProductionPrimitiveTextureState() {
9482
+ return { texturedMaterial: null, textureStatus: "none", textureSlots: [], textureWarnings: [], sdfText: null, textureBytes: 0, textureMipBytes: [] };
9483
+ }
9484
+ /**
9485
+ * C1 texture intent classification (pure, unit-tested). Asset refs resolve to
9486
+ * fetchable urls; procedural inputs have no rasterizer and are reported so
9487
+ * the caller can warn instead of silently dropping them.
9488
+ */
9489
+ export function createProductionPrimitiveTextureIntent(materialSpec) {
9490
+ const refUrl = (input) => input && typeof input === "object" && input.kind === "aura-asset-ref"
9491
+ ? input.url ?? undefined
9492
+ : undefined;
9493
+ const procedurals = [];
9494
+ const noteProcedural = (slot, input) => {
9495
+ if (input && typeof input === "object" && input.kind === "aura-procedural-texture") {
9496
+ procedurals.push(`${slot}:${input.texture}`);
9497
+ }
9498
+ };
9499
+ noteProcedural("normal", materialSpec?.normal);
9500
+ noteProcedural("roughnessMap", materialSpec?.roughnessMap);
9501
+ noteProcedural("metalnessMap", materialSpec?.metalnessMap);
9502
+ noteProcedural("occlusionMap", materialSpec?.occlusionMap);
9503
+ noteProcedural("emissiveMap", materialSpec?.emissiveMap);
9504
+ const baseColorUrl = refUrl(materialSpec?.texture);
9505
+ const normalUrl = refUrl(materialSpec?.normal);
9506
+ const roughnessUrl = refUrl(materialSpec?.roughnessMap);
9507
+ const metalnessUrl = refUrl(materialSpec?.metalnessMap);
9508
+ const occlusionUrl = refUrl(materialSpec?.occlusionMap);
9509
+ const emissiveUrl = refUrl(materialSpec?.emissiveMap);
9510
+ return {
9511
+ ...(baseColorUrl ? { baseColorUrl } : {}),
9512
+ ...(normalUrl ? { normalUrl } : {}),
9513
+ ...(roughnessUrl ? { roughnessUrl } : {}),
9514
+ ...(metalnessUrl ? { metalnessUrl } : {}),
9515
+ ...(occlusionUrl ? { occlusionUrl } : {}),
9516
+ ...(emissiveUrl ? { emissiveUrl } : {}),
9517
+ proceduralInputs: procedurals
9518
+ };
9519
+ }
9520
+ /**
9521
+ * C1 metallic-roughness compositing (pure, unit-tested). glTF convention:
9522
+ * R = occlusion (unused here, forced to 255), G = roughness, B = metallic.
9523
+ * Missing channels fall back to the scalar spec values.
9524
+ */
9525
+ async function loadProductionPrimitiveBitmap(url) {
9526
+ if (typeof fetch !== "function" || typeof createImageBitmap !== "function") {
9527
+ throw new Error("texture fetch requires fetch + createImageBitmap (browser production mount)");
9528
+ }
9529
+ const response = await fetch(url);
9530
+ if (!response.ok)
9531
+ throw new Error(`texture fetch failed with status ${response.status} for ${url}`);
9532
+ return await createImageBitmap(await response.blob());
9533
+ }
9534
+ function bitmapRgbaPixels(bitmap) {
9535
+ if (typeof document === "undefined")
9536
+ throw new Error("texture compositing requires a DOM canvas");
9537
+ const canvas = document.createElement("canvas");
9538
+ canvas.width = bitmap.width;
9539
+ canvas.height = bitmap.height;
9540
+ const context = canvas.getContext("2d", { willReadFrequently: true });
9541
+ if (!context)
9542
+ throw new Error("2d canvas unavailable for texture compositing");
9543
+ context.drawImage(bitmap, 0, 0);
9544
+ const image = context.getImageData(0, 0, bitmap.width, bitmap.height);
9545
+ return { width: bitmap.width, height: bitmap.height, data: new Uint8Array(image.data) };
9546
+ }
9547
+ /**
9548
+ * C1 post-mount textured upgrade (muse3jsparity-PRD). Runs fire-and-forget
9549
+ * after mount: scalar first frames stay fast and honest, and every outcome —
9550
+ * textured, fallback, or skipped — is recorded on the resource with warnings.
9551
+ * Procedural inputs have no rasterizer: recorded + warned, never faked.
9552
+ */
9553
+ export async function upgradeProductionPrimitiveTextures(entries, warn) {
9554
+ for (const entry of entries) {
9555
+ for (const resource of entry.resources) {
9556
+ const spec = resource.materialSpec;
9557
+ const intent = createProductionPrimitiveTextureIntent(spec);
9558
+ for (const procedural of intent.proceduralInputs) {
9559
+ // Static channel (collectGeneratedCodeWarnings) already warns; record
9560
+ // per-resource without duplicating into runtime warnings.
9561
+ resource.textureWarnings.push(`procedural texture ${procedural} on "${resource.name}" has no rasterizer; recorded only, scalar material retained`);
9562
+ }
9563
+ const urls = [intent.baseColorUrl, intent.normalUrl, intent.roughnessUrl, intent.metalnessUrl, intent.occlusionUrl, intent.emissiveUrl].filter((url) => typeof url === "string" && url.length > 0);
9564
+ if (urls.length === 0)
9565
+ continue;
9566
+ if (resource.textureStatus !== "none")
9567
+ continue;
9568
+ resource.textureStatus = "pending";
9569
+ try {
9570
+ await upgradeProductionPrimitiveResource(resource, resource.sourceNode, spec, intent);
9571
+ }
9572
+ catch (error) {
9573
+ resource.textureStatus = "fallback";
9574
+ const message = `textured upgrade failed for "${resource.name}" (${error instanceof Error ? error.message : String(error)}); scalar material retained`;
9575
+ resource.textureWarnings.push(message);
9576
+ warn(message);
9577
+ }
9578
+ }
9579
+ }
9580
+ }
9581
+ /**
9582
+ * B3 post-mount HDRI upgrade (muse3jsparity-PRD). Fetches a Radiance `.hdr`
9583
+ * asset, runs the HDR→cubemap→GGX-prefilter→BRDF-LUT chain, and returns the
9584
+ * live lighting object plus its disposal. Throws on fetch/parse failure so
9585
+ * the caller keeps the honest procedural fallback and warns.
9586
+ */
9587
+ export async function upgradeProductionEnvironmentHdri(url, intensity, reflectionUrl, rotation) {
9588
+ if (typeof fetch !== "function") {
9589
+ throw new Error("HDRI upgrade requires fetch (browser production mount)");
9590
+ }
9591
+ const fetchRadiance = async (assetUrl) => {
9592
+ const response = await fetch(assetUrl);
9593
+ if (!response.ok)
9594
+ throw new Error(`HDRI fetch failed with status ${response.status} for ${assetUrl}`);
9595
+ const bytes = new Uint8Array(await response.arrayBuffer());
9596
+ if (bytes.length === 0)
9597
+ throw new Error(`HDRI asset is empty for ${assetUrl}`);
9598
+ return bytes;
9599
+ };
9600
+ const clampIntensity = Math.max(0, intensity);
9601
+ const illumination = createProductionPbrHdrPipelineFromRadiance(await fetchRadiance(url), {
9602
+ id: "root-hdri-environment",
9603
+ label: "Root HDRI environment",
9604
+ intensity: clampIntensity,
9605
+ ...(rotation !== undefined ? { rotation } : {})
9606
+ });
9607
+ if (!illumination.diagnostics.realRadianceHdr)
9608
+ throw new Error(`HDRI asset did not parse as Radiance HDR for ${url}`);
9609
+ if (!reflectionUrl) {
9610
+ const resources = createProductionEnvironmentLightingResources(illumination);
9611
+ return {
9612
+ lighting: resources.lighting,
9613
+ dispose: resources.dispose,
9614
+ maxLinearValue: illumination.diagnostics.maxLinearValue,
9615
+ specularMipCount: illumination.diagnostics.specularMipCount,
9616
+ dualProbe: false
9617
+ };
9618
+ }
9619
+ const reflection = createProductionPbrHdrPipelineFromRadiance(await fetchRadiance(reflectionUrl), {
9620
+ id: "root-hdri-reflection-environment",
9621
+ label: "Root HDRI reflection environment",
9622
+ intensity: clampIntensity,
9623
+ ...(rotation !== undefined ? { rotation } : {})
9624
+ });
9625
+ if (!reflection.diagnostics.realRadianceHdr)
9626
+ throw new Error(`HDRI reflection asset did not parse as Radiance HDR for ${reflectionUrl}`);
9627
+ const dual = createDualProbeEnvironmentLightingResources({ illumination, reflection });
9628
+ return {
9629
+ lighting: dual.lighting,
9630
+ dispose: dual.dispose,
9631
+ maxLinearValue: Math.max(illumination.diagnostics.maxLinearValue, reflection.diagnostics.maxLinearValue),
9632
+ specularMipCount: reflection.diagnostics.specularMipCount,
9633
+ dualProbe: true
9634
+ };
9635
+ }
9636
+ async function upgradeProductionPrimitiveResource(resource, node, spec, intent) {
9637
+ 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
+ if (!resource.geometry.vertexBuffer.format.hasAttribute("uv")) {
9642
+ throw fail(`"${resource.name}" geometry carries no uv set; textured upgrade needs generated uvs`);
9643
+ }
9644
+ 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);
9681
+ }
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 } : {})
9764
+ }
9765
+ }
9766
+ : {})
9767
+ });
9768
+ resource.textureStatus = "textured";
9769
+ resource.textureSlots = slots;
9770
+ }
9771
+ export function compositeMetallicRoughnessPixels(rough, metal, pixelCount, roughnessScalar, metalnessScalar) {
9772
+ const out = new Uint8Array(pixelCount * 4);
9773
+ const fallbackG = Math.round(clamp01(roughnessScalar) * 255);
9774
+ const fallbackB = Math.round(clamp01(metalnessScalar) * 255);
9775
+ for (let pixel = 0; pixel < pixelCount; pixel += 1) {
9776
+ const base = pixel * 4;
9777
+ out[base] = 255;
9778
+ out[base + 1] = rough ? rough[base + 1] ?? fallbackG : fallbackG;
9779
+ out[base + 2] = metal ? metal[base + 2] ?? fallbackB : fallbackB;
9780
+ out[base + 3] = 255;
9781
+ }
9782
+ return out;
9783
+ }
9784
+ /**
9785
+ * G1 SDF text resource (muse3jsparity-PRD): replays the recorded descriptor
9786
+ * through the atlas sampler at mount, uploads the label image as a native
9787
+ * texture, and submits atlas-derived quads. Returns null (extruded fallback)
9788
+ * with a warning when the sampler cannot run — never a silent mesh swap.
9789
+ */
9790
+ function createSdfTextPrimitiveResource(node, warn) {
9791
+ const text = node.text3D;
9792
+ if (!text || text.backend !== "sdf")
9793
+ return null;
9794
+ try {
9795
+ const layout = layoutSdfText(text.text, rootSdfFontAtlas(), {
9796
+ size: text.sdfSize ?? 1,
9797
+ ...(text.sdfLetterSpacing === undefined ? {} : { letterSpacing: text.sdfLetterSpacing }),
9798
+ ...(text.sdfStyle === undefined ? {} : { style: text.sdfStyle })
9799
+ });
9800
+ const style = layout.style;
9801
+ const styled = text.sdfStyle !== undefined && (style.outlineWidthEm > 0 || style.glowRadiusEm > 0 || style.shadowOffsetEm !== undefined);
9802
+ const image = rasterizeSdfTextLabelImage(layout, rootSdfFontAtlas(), {
9803
+ texelsPerWorldUnit: 64,
9804
+ ...(styled
9805
+ ? {
9806
+ outline: [1, 0.85, 0.6, 1],
9807
+ glow: [0.5, 0.8, 1, 1],
9808
+ shadow: [0, 0, 0, 1]
9809
+ }
9810
+ : {})
9811
+ });
9812
+ const mesh = createSdfTextQuadMesh(layout, image);
9813
+ const vertexCount = mesh.vertexCount;
9814
+ const vertices = new VertexBuffer(VertexFormat.P3N3T4T2T2, vertexCount);
9815
+ for (let index = 0; index < vertexCount; index += 1) {
9816
+ vertices.setAttribute(index, "position", [mesh.positions[index * 3] ?? 0, mesh.positions[index * 3 + 1] ?? 0, 0]);
9817
+ vertices.setAttribute(index, "normal", [0, 0, 1]);
9818
+ vertices.setAttribute(index, "tangent", [1, 0, 0, 1]);
9819
+ vertices.setAttribute(index, "uv", [mesh.uvs[index * 2] ?? 0, mesh.uvs[index * 2 + 1] ?? 0]);
9820
+ vertices.setAttribute(index, "uv1", [mesh.uvs[index * 2] ?? 0, mesh.uvs[index * 2 + 1] ?? 0]);
9821
+ }
9822
+ const geometry = new Geometry(vertices, new IndexBuffer(mesh.indices, vertexCount), "triangles", { min: [mesh.min[0], mesh.min[1], mesh.min[2]], max: [mesh.max[0], mesh.max[1], mesh.max[2]] });
9823
+ const opacity = clamp01(node.material?.opacity ?? 1);
9824
+ const labelTexture = new Texture({
9825
+ width: image.width,
9826
+ height: image.height,
9827
+ data: image.data,
9828
+ colorSpace: "srgb",
9829
+ label: `${node.name ?? "sdf-text"}-sdf-label`
9830
+ });
9831
+ const sampler = new Sampler({ maxAnisotropy: resolveSamplerAnisotropy({ desired: node.material?.textureAnisotropy }).applied });
9832
+ const texturedMaterial = new TexturedPBRMaterial({
9833
+ name: `a3d-production-sdf-text-${node.name ?? "label"}`,
9834
+ baseColor: [1, 1, 1, opacity],
9835
+ metallic: 0,
9836
+ roughness: 0.9,
9837
+ emissiveColor: [0.92, 0.92, 0.92],
9838
+ emissiveStrength: 0.9,
9839
+ occlusionStrength: 0,
9840
+ environmentIntensity: 0,
9841
+ renderState: { blend: true, depthWrite: false, cullMode: "none" },
9842
+ baseColorTexture: labelTexture,
9843
+ baseColorSampler: sampler,
9844
+ emissiveTexture: labelTexture,
9845
+ emissiveSampler: sampler
9846
+ });
9847
+ const mipBytes = mipChainBytesCoarseToFine(image.width, image.height);
9848
+ return {
9849
+ geometry,
9850
+ material: createProductionPrimitiveMaterial(node),
9851
+ bounds: { min: [mesh.min[0], mesh.min[1], mesh.min[2]], max: [mesh.max[0], mesh.max[1], mesh.max[2]] },
9852
+ name: node.name ?? "sdf-text",
9853
+ materialSpec: node.material,
9854
+ sourceNode: node,
9855
+ ...blankProductionPrimitiveTextureState(),
9856
+ texturedMaterial,
9857
+ textureStatus: "textured",
9858
+ textureSlots: ["baseColor", "emissive"],
9859
+ textureWarnings: [],
9860
+ sdfText: {
9861
+ quadCount: mesh.quadCount,
9862
+ imageBytes: image.width * image.height * 4,
9863
+ ...(style.lodFadeNear === undefined ? {} : { lodFadeNear: style.lodFadeNear }),
9864
+ ...(style.lodFadeFar === undefined ? {} : { lodFadeFar: style.lodFadeFar }),
9865
+ occlusionPolicy: (text.sdfOcclusion ?? "dim"),
9866
+ lastOpacity: opacity,
9867
+ lastVisible: true,
9868
+ lastSubmitted: false
9869
+ },
9870
+ textureBytes: Math.round(image.width * image.height * 4 * (4 / 3)),
9871
+ textureMipBytes: mipBytes
9872
+ };
9873
+ }
9874
+ catch (error) {
9875
+ warn(`SDF text sampler failed for "${node.name ?? "sdf-text"}" (${error instanceof Error ? error.message : String(error)}); extruded mesh fallback retained`);
9876
+ return null;
9877
+ }
9878
+ }
9879
+ /**
9880
+ * M2 mip-chain byte estimate (pure, unit-tested): full chain from the base
9881
+ * level, coarse-to-fine, RGBA8. Matches the GPU residency the bridge funds.
9882
+ */
9883
+ export function mipChainBytesCoarseToFine(width, height) {
9884
+ if (!Number.isInteger(width) || width <= 0 || !Number.isInteger(height) || height <= 0) {
9885
+ throw new Error("Mip-chain bytes require positive integer dimensions.");
9886
+ }
9887
+ const levels = [];
9888
+ let w = width;
9889
+ let h = height;
9890
+ while (true) {
9891
+ levels.unshift(w * h * 4);
9892
+ if (w === 1 && h === 1)
9893
+ break;
9894
+ w = Math.max(1, Math.floor(w / 2));
9895
+ h = Math.max(1, Math.floor(h / 2));
9896
+ }
9897
+ return levels;
9898
+ }
9899
+ /** M2 streaming budget normalization (pure, unit-tested): default 256 MiB, fail-closed. */
9900
+ export function normalizeTextureBudgetBytes(value) {
9901
+ if (value === undefined)
9902
+ return 256 * 1024 * 1024;
9903
+ if (!Number.isFinite(value) || value <= 0) {
9904
+ throw new Error("Aura3D textureBudgetBytes must be a positive byte count.");
9905
+ }
9906
+ return Math.floor(value);
9907
+ }
9908
+ /**
9909
+ * M2 streaming residency from the post-upgrade texture table (pure,
9910
+ * unit-tested): distance-prioritized mip funding against the budget with
9911
+ * over-budget telemetry for the unfunded tail.
9912
+ */
9913
+ export function describeTextureStreamingResidency(table, budgetBytes) {
9914
+ const candidates = table.map((entry) => ({
9915
+ id: entry.id,
9916
+ mipBytesCoarseToFine: entry.mipBytesCoarseToFine,
9917
+ distanceMeters: entry.distanceMeters
9918
+ }));
9919
+ return evaluateDistancePrioritizedMipResidency(candidates, normalizeTextureBudgetBytes(budgetBytes));
9920
+ }
9921
+ /**
9922
+ * N1 spot shadow observation (pure, unit-tested): cone + atlas tier from the
9923
+ * authored spot, pixel-backing gated on the device-observed map signals with
9924
+ * the spot as caster. No signal, no claim.
9925
+ */
9926
+ export function describeProductionSpotShadow(input) {
9927
+ if (!input.requested || !input.casterIsSpot || input.angle === undefined) {
9928
+ return {
9929
+ requested: input.requested,
9930
+ casterIsSpot: input.casterIsSpot,
9931
+ ...(input.casterName === undefined ? {} : { casterName: input.casterName }),
9932
+ spotPixelBacked: false,
9933
+ reason: !input.requested
9934
+ ? "no authored spot requested a shadow map"
9935
+ : !input.casterIsSpot
9936
+ ? "shadow requested but the caster slot went to a non-spot light"
9937
+ : "spot caster has no cone description"
9938
+ };
9939
+ }
9940
+ // Tier selection validates the cone fail-closed (RangeError on bad angle);
9941
+ // the full projection matrix composes in-device from this cone + range.
9942
+ const tier = selectSpotShadowAtlasTier(input.angle);
9943
+ createSpotShadowProjection(input.angle, Math.max(1, input.range ?? 12));
9944
+ const backed = input.mapRendered && input.mapSampled;
9945
+ return {
9946
+ requested: true,
9947
+ casterIsSpot: true,
9948
+ ...(input.casterName === undefined ? {} : { casterName: input.casterName }),
9949
+ angle: input.angle,
9950
+ ...(input.penumbra === undefined ? {} : { penumbra: input.penumbra }),
9951
+ range: Math.max(1, input.range ?? 12),
9952
+ atlasResolution: tier.resolution,
9953
+ atlasReason: tier.reason,
9954
+ spotPixelBacked: backed,
9955
+ reason: backed
9956
+ ? `spot shadow map rendered and sampled (${tier.resolution}px ${tier.reason})`
9957
+ : "spot caster selected but the device shows no rendered+sampled shadow map yet"
9958
+ };
9959
+ }
8500
9960
  function createProductionPrimitiveResources(node) {
9961
+ const sdfWarnings = [];
9962
+ const sdfResource = createSdfTextPrimitiveResource(node, (message) => { sdfWarnings.push(message); });
8501
9963
  const levels = node.lod?.levels;
8502
9964
  if (!levels?.length) {
8503
- return [{ geometry: createProductionPrimitiveGeometry(node), material: createProductionPrimitiveMaterial(node), bounds: primitiveGeometryBounds(node), name: node.name ?? node.primitive }];
9965
+ if (sdfResource)
9966
+ return [sdfResource];
9967
+ const fallback = {
9968
+ geometry: createProductionPrimitiveGeometry(node),
9969
+ material: createProductionPrimitiveMaterial(node),
9970
+ bounds: primitiveGeometryBounds(node),
9971
+ name: node.name ?? node.primitive,
9972
+ materialSpec: node.material,
9973
+ sourceNode: node,
9974
+ ...blankProductionPrimitiveTextureState()
9975
+ };
9976
+ // Sampler failures stay visible on the fallback resource (surfaced via
9977
+ // texturedMaterials diagnostics), never swallowed by the mesh swap.
9978
+ fallback.textureWarnings.push(...sdfWarnings);
9979
+ return [fallback];
8504
9980
  }
8505
9981
  return levels.map((level, index) => {
8506
9982
  if (!level.primitive && !level.geometry)
@@ -8512,7 +9988,21 @@ function createProductionPrimitiveResources(node) {
8512
9988
  material: level.material ?? node.material,
8513
9989
  lod: undefined
8514
9990
  };
8515
- return { geometry: createProductionPrimitiveGeometry(levelNode), material: createProductionPrimitiveMaterial(levelNode), bounds: primitiveGeometryBounds(levelNode), name: level.name };
9991
+ const levelWarnings = [];
9992
+ const levelSdf = createSdfTextPrimitiveResource(levelNode, (message) => { levelWarnings.push(message); });
9993
+ if (levelSdf)
9994
+ return { ...levelSdf, name: level.name };
9995
+ const levelFallback = {
9996
+ geometry: createProductionPrimitiveGeometry(levelNode),
9997
+ material: createProductionPrimitiveMaterial(levelNode),
9998
+ bounds: primitiveGeometryBounds(levelNode),
9999
+ name: level.name,
10000
+ materialSpec: levelNode.material,
10001
+ sourceNode: levelNode,
10002
+ ...blankProductionPrimitiveTextureState()
10003
+ };
10004
+ levelFallback.textureWarnings.push(...levelWarnings);
10005
+ return levelFallback;
8516
10006
  });
8517
10007
  }
8518
10008
  function selectProductionPrimitiveResource(entry, node, cameraPosition) {
@@ -8524,6 +10014,14 @@ function selectProductionPrimitiveResource(entry, node, cameraPosition) {
8524
10014
  entry.currentLodIndex = selection.levelIndex;
8525
10015
  return entry.resources[selection.levelIndex] ?? entry.resources[entry.resources.length - 1];
8526
10016
  }
10017
+ function createProductionModelInstanceTransforms(transforms, node, bounds, time) {
10018
+ const matrices = new Float32Array(transforms.length * 16);
10019
+ transforms.forEach((transform, index) => {
10020
+ const localNode = { ...node, ...transform };
10021
+ matrices.set(createModelMatrix(localNode, bounds, shouldNormalizeModelNode(node), time), index * 16);
10022
+ });
10023
+ return matrices;
10024
+ }
8527
10025
  function createProductionInstanceTransforms(transforms, node) {
8528
10026
  const matrices = new Float32Array(transforms.length * 16);
8529
10027
  transforms.forEach((transform, index) => {
@@ -8559,37 +10057,7 @@ function resolveProductionPrimitiveRuntimeState(entry, runtimeNodes) {
8559
10057
  visible: runtimeSnapshot.visible
8560
10058
  };
8561
10059
  }
8562
- /**
8563
- * Build the production `PBRMaterial` for a primitive node.
8564
- *
8565
- * ## WS-2.1a — the parameter drop this function used to be
8566
- *
8567
- * Until 1.6 this forwarded `clearcoat` and **nothing else** from the extended material surface.
8568
- * `AuraMaterialSpec` accepts `sheen`, `sheenRoughness`, `sheenColor`, `iridescence`,
8569
- * `iridescenceIOR`, `iridescenceThicknessRange`, `anisotropy`, `anisotropyRotation`, `transmission`,
8570
- * `thickness`, `ior`, `attenuationColor` and `attenuationDistance`; `PBRMaterial` accepts a uniform
8571
- * for every one of them and binds them all. The bridge between the two dropped them on the floor.
8572
- *
8573
- * How that presented, measured by `tools/material-structural-parity` before the fix:
8574
- *
8575
- * ```
8576
- * material.pbr({ ..., sheen: 1 }) -> byte-identical frame to sheen: 0
8577
- * material.pbr({ ..., iridescence: 1 }) -> byte-identical frame to iridescence: 0
8578
- * material.pbr({ ..., clearcoat: 1 }) -> byte-identical frame to clearcoat: 0
8579
- * ```
8580
- *
8581
- * `clearcoat` was *forwarded* and still produced an identical frame, which pins the second, separate
8582
- * defect: `a3dPbrExtensionEnvironmentLight` in `ShaderChunks.ts` adds its lobes to
8583
- * `specularRadiance`, a term that is zero without an environment map. Forwarding alone is necessary
8584
- * and not sufficient, so `environmentIntensity` now also has a floor when an extension factor is
8585
- * present — otherwise a developer setting `clearcoat: 1` on a scene with no environment sees nothing
8586
- * and has no way to find out why.
8587
- *
8588
- * The reason this survived so long is worth stating: three of the four lobes are scalar
8589
- * approximations rather than real BRDFs, and **nobody could tell**, because the parameters never
8590
- * arrived. The plumbing defect concealed the shading defect.
8591
- */
8592
- function createProductionPrimitiveMaterial(node) {
10060
+ function resolveProductionPrimitiveScalars(node) {
8593
10061
  const materialSpec = node.material;
8594
10062
  const baseColor = colorToLinearRgba(materialSpec?.color ?? materialSpec?.emissive ?? "#d7dee8");
8595
10063
  const opacity = clamp01(materialSpec?.opacity ?? baseColor[3] ?? 1);
@@ -8620,6 +10088,17 @@ function createProductionPrimitiveMaterial(node) {
8620
10088
  const environmentIntensity = declaresExtension
8621
10089
  ? Math.max(0.35, requestedEnvironmentIntensity)
8622
10090
  : requestedEnvironmentIntensity;
10091
+ // B3: the sampled env-map scale defaults to 1 (no look change) and honors
10092
+ // an explicitly authored envMapIntensity, including 0 (killed response).
10093
+ const envMapIntensity = materialSpec?.envMapIntensity === undefined ? 1 : Math.max(0, materialSpec.envMapIntensity);
10094
+ return {
10095
+ materialSpec, baseColor, opacity, emissiveColor, clearcoat, sheen, iridescence,
10096
+ anisotropy, transmission, thicknessRange, sheenColor, declaresExtension, environmentIntensity, envMapIntensity
10097
+ };
10098
+ }
10099
+ function createProductionPrimitiveMaterial(node) {
10100
+ const materialSpec = node.material;
10101
+ const { baseColor, opacity, emissiveColor, clearcoat, sheenColor, declaresExtension, transmission, thicknessRange, sheen, iridescence, anisotropy, environmentIntensity, envMapIntensity } = resolveProductionPrimitiveScalars(node);
8623
10102
  /*
8624
10103
  * Native instance submission requires the renderer's instanced shader contract. The ordinary
8625
10104
  * PBR shader intentionally has no instance matrix inputs and ForwardPass therefore expands it.
@@ -8637,6 +10116,7 @@ function createProductionPrimitiveMaterial(node) {
8637
10116
  emissiveColor,
8638
10117
  emissiveStrength: Math.max(0, materialSpec?.emissiveIntensity ?? (materialSpec?.emissive ? 1.35 : 0)),
8639
10118
  environmentIntensity,
10119
+ envMapIntensity,
8640
10120
  renderState: {
8641
10121
  blend: opacity < 0.999,
8642
10122
  depthWrite: opacity >= 0.999,
@@ -8667,6 +10147,7 @@ function createProductionPrimitiveMaterial(node) {
8667
10147
  ...(materialSpec?.attenuationColor ? { volumeAttenuationColor: colorToLinearRgb(materialSpec.attenuationColor) } : {}),
8668
10148
  ...(materialSpec?.attenuationDistance === undefined ? {} : { volumeAttenuationDistance: Math.max(0, materialSpec.attenuationDistance) }),
8669
10149
  environmentIntensity,
10150
+ envMapIntensity,
8670
10151
  renderState: {
8671
10152
  blend: opacity < 0.999,
8672
10153
  depthWrite: opacity >= 0.999,
@@ -8689,6 +10170,12 @@ function primitiveGeometryBounds(nodeOrPrimitive) {
8689
10170
  throw new Error("Aura3D custom primitive requires geometry.");
8690
10171
  return createProductionPrimitiveMesh(primitive).bounds;
8691
10172
  }
10173
+ function tilingSecondUnwrap(uvs) {
10174
+ const uv1s = new Float32Array(uvs.length);
10175
+ for (let i = 0; i < uvs.length; i += 1)
10176
+ uv1s[i] = (uvs[i] ?? 0) * 2;
10177
+ return uv1s;
10178
+ }
8692
10179
  function createProductionPrimitiveMesh(primitive) {
8693
10180
  if (primitive === "sphere")
8694
10181
  return createSphereGeometry();
@@ -8740,11 +10227,24 @@ function calculateCustomGeometryNormals(spec) {
8740
10227
  }
8741
10228
  function createProductionGeometryFromPrimitiveMesh(mesh) {
8742
10229
  const vertexCount = mesh.positions.length / 3;
8743
- const vertices = new VertexBuffer(VertexFormat.P3N3, vertexCount);
10230
+ const textured = Boolean(mesh.uvs && mesh.uvs.length === vertexCount * 2
10231
+ && mesh.tangents && mesh.tangents.length === vertexCount * 4);
10232
+ const vertices = new VertexBuffer(textured ? VertexFormat.P3N3T4T2T2 : VertexFormat.P3N3, vertexCount);
8744
10233
  for (let index = 0; index < vertexCount; index += 1) {
8745
10234
  const base = index * 3;
8746
10235
  vertices.setAttribute(index, "position", [mesh.positions[base] ?? 0, mesh.positions[base + 1] ?? 0, mesh.positions[base + 2] ?? 0]);
8747
10236
  vertices.setAttribute(index, "normal", [mesh.normals[base] ?? 0, mesh.normals[base + 1] ?? 1, mesh.normals[base + 2] ?? 0]);
10237
+ if (textured) {
10238
+ const uvBase = index * 2;
10239
+ const tangentBase = index * 4;
10240
+ vertices.setAttribute(index, "tangent", [
10241
+ mesh.tangents[tangentBase] ?? 1, mesh.tangents[tangentBase + 1] ?? 0,
10242
+ mesh.tangents[tangentBase + 2] ?? 0, mesh.tangents[tangentBase + 3] ?? 1
10243
+ ]);
10244
+ vertices.setAttribute(index, "uv", [mesh.uvs[uvBase] ?? 0, mesh.uvs[uvBase + 1] ?? 0]);
10245
+ const uv1 = mesh.uv1s && mesh.uv1s.length === vertexCount * 2 ? mesh.uv1s : mesh.uvs;
10246
+ vertices.setAttribute(index, "uv1", [uv1[uvBase] ?? 0, uv1[uvBase + 1] ?? 0]);
10247
+ }
8748
10248
  }
8749
10249
  return new Geometry(vertices, new IndexBuffer(Array.from(mesh.indices), vertexCount), "triangles", mesh.bounds);
8750
10250
  }
@@ -8785,6 +10285,39 @@ function applyProductionActorAnimation(entry, node, animationBinding, time, runt
8785
10285
  runtimeWarnings.add(`Typed GLB actor "${entry.actor.id}" failed to apply clip "${clipName}": ${productionRenderErrorMessage(error)}`);
8786
10286
  }
8787
10287
  }
10288
+ /**
10289
+ * Applies controller-bound foot planting (E2) to the typed GLB actor. The resolved config
10290
+ * is refreshed every frame with the actor's live model matrix: time-animated model nodes
10291
+ * (float/orbit/turntable) move, and the post-pass solves in world space, so a stale matrix
10292
+ * would plant feet in yesterday's frame. Foot-lock state survives the refresh — the runtime
10293
+ * keeps its rig across matrix-only updates and resets it only when the leg set, ground, or
10294
+ * solve parameters change.
10295
+ */
10296
+ function applyProductionActorFootPlanting(entry, animationBinding, modelMatrix, runtimeWarnings) {
10297
+ const footPlanting = animationBinding?.footPlanting;
10298
+ try {
10299
+ entry.actor.animation.setFootPlanting((footPlanting === undefined
10300
+ ? undefined
10301
+ : { ...footPlanting, ...spreadFootPlantingWorldMatrix(modelMatrix) }));
10302
+ }
10303
+ catch (error) {
10304
+ runtimeWarnings.add(`Typed GLB actor "${entry.actor.id}" failed to apply foot planting: ${productionRenderErrorMessage(error)}`);
10305
+ }
10306
+ }
10307
+ /**
10308
+ * Packages the actor model matrix as the post-pass `worldFromLocal`. The matrix the
10309
+ * renderer draws with (column-major 16, model-local → world) is exactly the space the
10310
+ * foot-planting ground speaks, so the same array threads both paths. Returns {} when the
10311
+ * matrix is missing or malformed so the pass falls back to actor-local solving.
10312
+ */
10313
+ function spreadFootPlantingWorldMatrix(modelMatrix) {
10314
+ if (!modelMatrix || modelMatrix.length !== 16)
10315
+ return {};
10316
+ const worldFromLocal = Array.from(modelMatrix, (component) => Number(component));
10317
+ if (worldFromLocal.some((component) => !Number.isFinite(component)))
10318
+ return {};
10319
+ return { worldFromLocal };
10320
+ }
8788
10321
  function applyProductionActorMorphTargets(entry, morphTargets, runtimeWarnings) {
8789
10322
  if (!morphTargets || Object.keys(morphTargets).length === 0)
8790
10323
  return;
@@ -8832,7 +10365,14 @@ function createRuntimeEvidenceFromTypedGLBActor(node, actor, renderItems) {
8832
10365
  },
8833
10366
  renderItemCount: renderItems.length,
8834
10367
  skinnedRenderItemCount: renderItems.filter((item) => item.skinning).length,
8835
- morphRenderItemCount: renderItems.filter((item) => item.morphTargets && item.morphTargets.length > 0).length
10368
+ morphRenderItemCount: renderItems.filter((item) => item.morphTargets && item.morphTargets.length > 0).length,
10369
+ lastMaterialTracksApplied: actorEvidence.lastMaterialTracksApplied,
10370
+ lastLightTracksApplied: actorEvidence.lastLightTracksApplied,
10371
+ lastFootPlantingGroundedFeet: actorEvidence.lastFootPlantingGroundedFeet,
10372
+ lastFootPlantingTargetError: actorEvidence.lastFootPlantingTargetError,
10373
+ lastFootPlantingHipOffset: actorEvidence.lastFootPlantingHipOffset,
10374
+ lastFootPlantingMissingLegs: [...actorEvidence.lastFootPlantingMissingLegs],
10375
+ footPlantingConfigured: actorEvidence.footPlantingConfigured
8836
10376
  });
8837
10377
  }
8838
10378
  function createProductionRuntimeMetadata(actorEntries, primitiveEntries) {
@@ -9277,7 +10817,9 @@ function collectRuntimeEffectNodes(snapshot) {
9277
10817
  return groups.flatten(snapshot.nodes).filter((node) => node.kind === "effect");
9278
10818
  }
9279
10819
  function hasRuntimePostProcessEffects(effectNodes) {
9280
- return effectNodes.some((node) => node.effect === "bloom" || node.effect === "ambient-occlusion" || node.effect === "contact-occlusion");
10820
+ return effectNodes.some((node) => node.effect === "bloom" || node.effect === "ambient-occlusion" || node.effect === "contact-occlusion"
10821
+ || node.effect === "color-grade" || node.effect === "anti-alias" || node.effect === "outline"
10822
+ || node.effect === "screen-space-reflections" || node.effect === "depth-of-field" || node.effect === "motion-blur");
9281
10823
  }
9282
10824
  async function createWebGLSceneRenderer(canvas, snapshot, rendererOptions, runtimeWarnings = [], runtimeNodes) {
9283
10825
  const gl = canvas.getContext("webgl2", { antialias: true, preserveDrawingBuffer: true });
@@ -9313,7 +10855,8 @@ async function createWebGLSceneRenderer(canvas, snapshot, rendererOptions, runti
9313
10855
  contactOcclusionReceiver: false,
9314
10856
  pixelBacked: false,
9315
10857
  actualPasses: [],
9316
- fallbackPasses: hasRuntimePostProcessEffects(requestedEffectNodes) ? ["webgl2-direct-render"] : []
10858
+ fallbackPasses: hasRuntimePostProcessEffects(requestedEffectNodes) ? ["webgl2-direct-render"] : [],
10859
+ executionMode: "none"
9317
10860
  },
9318
10861
  warnings: [
9319
10862
  "Aura3D WebGL2 agent runtime is an explicit safe-basic fallback; advanced postprocess, environment prefiltering, shadow maps, and GLB animation mixers are reported as unsupported unless the production runtime proves them.",
@@ -10549,6 +12092,8 @@ function textureRef(texture, uvs, key) {
10549
12092
  return { [key]: texture.index };
10550
12093
  }
10551
12094
  function createPlaneGeometry() {
12095
+ const uvs = new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]);
12096
+ const tangents = new Float32Array([1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1]);
10552
12097
  return {
10553
12098
  positions: new Float32Array([
10554
12099
  -0.5, 0, -0.5,
@@ -10569,10 +12114,21 @@ function createPlaneGeometry() {
10569
12114
  // up-normal downward. The plane then faced away from every overhead light and
10570
12115
  // received no direct lighting or visible cast shadows.
10571
12116
  indices: new Uint16Array([0, 2, 1, 0, 3, 2]),
10572
- bounds: { min: [-0.5, 0, -0.5], max: [0.5, 0, 0.5] }
12117
+ bounds: { min: [-0.5, 0, -0.5], max: [0.5, 0, 0.5] },
12118
+ uvs,
12119
+ uv1s: tilingSecondUnwrap(uvs),
12120
+ tangents
10573
12121
  };
10574
12122
  }
10575
12123
  function createBoxGeometry() {
12124
+ // One full 0-1 unwrap per face, matching the 4-vertex face order below.
12125
+ const faceUvs = [0, 0, 1, 0, 1, 1, 0, 1];
12126
+ const uvs = new Float32Array([...faceUvs, ...faceUvs, ...faceUvs, ...faceUvs, ...faceUvs, ...faceUvs]);
12127
+ // Analytic tangents along each face's +u direction (face order +z,-z,+y,-y,+x,-x).
12128
+ const faceTangents = [
12129
+ [1, 0, 0, 1], [-1, 0, 0, 1], [1, 0, 0, 1], [1, 0, 0, 1], [0, 0, -1, 1], [0, 0, 1, 1]
12130
+ ];
12131
+ const tangents = new Float32Array(faceTangents.flatMap((tangent) => [...tangent, ...tangent, ...tangent, ...tangent]));
10576
12132
  const positions = new Float32Array([
10577
12133
  -0.5, -0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5,
10578
12134
  0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5,
@@ -10600,7 +12156,10 @@ function createBoxGeometry() {
10600
12156
  16, 17, 18, 16, 18, 19,
10601
12157
  20, 21, 22, 20, 22, 23
10602
12158
  ]),
10603
- bounds: { min: [-0.5, -0.5, -0.5], max: [0.5, 0.5, 0.5] }
12159
+ bounds: { min: [-0.5, -0.5, -0.5], max: [0.5, 0.5, 0.5] },
12160
+ uvs,
12161
+ uv1s: tilingSecondUnwrap(uvs),
12162
+ tangents
10604
12163
  };
10605
12164
  }
10606
12165
  function createSphereGeometry() {
@@ -10608,6 +12167,8 @@ function createSphereGeometry() {
10608
12167
  const columns = 16;
10609
12168
  const positions = [];
10610
12169
  const normals = [];
12170
+ const uvs = [];
12171
+ const tangents = [];
10611
12172
  const indices = [];
10612
12173
  for (let row = 0; row <= rows; row += 1) {
10613
12174
  const v = row / rows;
@@ -10620,6 +12181,8 @@ function createSphereGeometry() {
10620
12181
  const z = Math.sin(theta) * Math.sin(phi);
10621
12182
  positions.push(x * 0.5, y * 0.5, z * 0.5);
10622
12183
  normals.push(x, y, z);
12184
+ uvs.push(u, 1 - v);
12185
+ tangents.push(-Math.sin(phi), 0, Math.cos(phi), 1);
10623
12186
  }
10624
12187
  }
10625
12188
  for (let row = 0; row < rows; row += 1) {
@@ -10632,11 +12195,15 @@ function createSphereGeometry() {
10632
12195
  indices.push(a, a + 1, b, b, a + 1, b + 1);
10633
12196
  }
10634
12197
  }
12198
+ const sphereUvs = new Float32Array(uvs);
10635
12199
  return {
10636
12200
  positions: new Float32Array(positions),
10637
12201
  normals: new Float32Array(normals),
10638
12202
  indices: new Uint16Array(indices),
10639
- bounds: { min: [-0.5, -0.5, -0.5], max: [0.5, 0.5, 0.5] }
12203
+ bounds: { min: [-0.5, -0.5, -0.5], max: [0.5, 0.5, 0.5] },
12204
+ uvs: sphereUvs,
12205
+ uv1s: tilingSecondUnwrap(sphereUvs),
12206
+ tangents: new Float32Array(tangents)
10640
12207
  };
10641
12208
  }
10642
12209
  function createCylinderGeometry() {
@@ -11370,7 +12937,9 @@ function snapshotDiagnostics(value) {
11370
12937
  renderer: value.renderer,
11371
12938
  warnings: [...value.warnings],
11372
12939
  errors: [...value.errors],
11373
- ...(value.labels ? { labels: value.labels } : {})
12940
+ ...(value.labels ? { labels: value.labels } : {}),
12941
+ ...(value.labelTelemetry ? { labelTelemetry: value.labelTelemetry } : {}),
12942
+ ...(value.textBuckets ? { textBuckets: value.textBuckets } : {})
11374
12943
  };
11375
12944
  }
11376
12945
  function collectGeneratedCodeWarnings(snapshot) {
@@ -11381,6 +12950,27 @@ function collectGeneratedCodeWarnings(snapshot) {
11381
12950
  if (!snapshot.nodes.some((node) => node.kind === "interaction")) {
11382
12951
  warnings.push("Scene has no interactions. Suggested fix: add interactions.orbit() for product/viewer scenes.");
11383
12952
  }
12953
+ // muse3jsparity-PRD A3: withheld postprocess intents must warn on the
12954
+ // mounted channel too (plan-level warnings never reach app.diagnostics()).
12955
+ 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
+ // muse3jsparity-PRD C1: procedural texture inputs have no rasterizer. The
12965
+ // intent is compile-time known, so warn here; fetch outcomes stay dynamic.
12966
+ for (const node of flatNodes) {
12967
+ if (node.kind !== "primitive" || !node.material)
12968
+ continue;
12969
+ const label = node.name ?? `aura-primitive-${node.primitive}`;
12970
+ for (const procedural of createProductionPrimitiveTextureIntent(node.material).proceduralInputs) {
12971
+ warnings.push(`procedural texture ${procedural} on "${label}" has no rasterizer; recorded only, scalar material retained`);
12972
+ }
12973
+ }
11384
12974
  for (const node of snapshot.nodes) {
11385
12975
  if (node.kind === "model" && createAssetProvenance(node.asset).source === "unsafe-url") {
11386
12976
  warnings.push(`Model uses unsafeModelUrl("${node.asset.url}"). Suggested fix: run assets add and use typed assets.`);
@@ -11544,6 +13134,16 @@ function drawEffect(context, width, height, node, time) {
11544
13134
  context.fillStyle = toAlphaColor(node.color ?? "#9fb7d9", node.density ?? 0.12);
11545
13135
  context.fillRect(0, height * 0.2, width, height * 0.8);
11546
13136
  }
13137
+ if (node.effect === "volumetric-fog") {
13138
+ context.fillStyle = toAlphaColor(node.color ?? "#6f84b9", node.density ?? 0.18);
13139
+ context.fillRect(0, height * 0.2, width, height * 0.8);
13140
+ const anchor = node.lightPosition ?? [0.5, 0.18];
13141
+ const glow = context.createRadialGradient(width * anchor[0], height * anchor[1], 8, width * anchor[0], height * anchor[1], width * 0.3);
13142
+ glow.addColorStop(0, toAlphaColor(node.color ?? "#fff3d6", (node.intensity ?? 0.7) * 0.35));
13143
+ glow.addColorStop(1, "rgba(255,255,255,0)");
13144
+ context.fillStyle = glow;
13145
+ context.fillRect(0, 0, width, height);
13146
+ }
11547
13147
  if (node.effect === "bloom") {
11548
13148
  const gradient = context.createRadialGradient(width * 0.5, height * 0.45, 20, width * 0.5, height * 0.45, width * 0.46);
11549
13149
  gradient.addColorStop(0, toAlphaColor(node.color ?? "#ffffff", (node.intensity ?? 0.35) * 0.3));
@@ -11596,6 +13196,29 @@ function drawEffect(context, width, height, node, time) {
11596
13196
  }
11597
13197
  }
11598
13198
  }
13199
+ if (node.effect === "snow") {
13200
+ const density = Math.max(0.2, Math.min(1.6, node.density ?? node.intensity ?? 0.68));
13201
+ const intensity = Math.max(0.1, Math.min(1.4, node.intensity ?? 0.4));
13202
+ const color = node.color ?? "#e8f1ff";
13203
+ if (node.mist !== false) {
13204
+ context.fillStyle = toAlphaColor(color, Math.min(0.1, 0.03 + intensity * 0.04));
13205
+ context.fillRect(0, height * 0.2, width, height * 0.72);
13206
+ }
13207
+ const drift = (node.wind?.[0] ?? -0.85) * 0.02;
13208
+ for (let layer = 0; layer < 3; layer += 1) {
13209
+ const count = Math.round((layer === 2 ? 30 : 52) * density);
13210
+ const size = layer === 2 ? 3.1 : layer === 1 ? 2.2 : 1.4;
13211
+ context.fillStyle = toAlphaColor(color, Math.min(0.85, 0.34 + intensity * (0.3 - layer * 0.06)));
13212
+ for (let i = 0; i < count; i += 1) {
13213
+ const sway = Math.sin(time * 0.0011 * (node.speed ?? 1) + i * 1.7 + layer) * (6 + layer * 5);
13214
+ const x = (((i * 61 + layer * 197 + time * 0.008 * (node.speed ?? 1) * drift * 60) % width) + width) % width + sway * 0.14;
13215
+ const y = ((i * 97 + layer * 131 + time * 0.028 * (node.speed ?? 1) * (1 + layer * 0.3)) % (height * 0.86)) + height * 0.06;
13216
+ context.beginPath();
13217
+ context.arc(x, y, size, 0, Math.PI * 2);
13218
+ context.fill();
13219
+ }
13220
+ }
13221
+ }
11599
13222
  if (node.effect === "particles") {
11600
13223
  const isFountain = node.emitter === "fountain";
11601
13224
  // Fountain cap matches prefabs.particleFountain's 2400 maximum so rendered counts never undercut the scene JSON.