@aura3d/engine 2.0.3 → 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 (704) hide show
  1. package/README.md +37 -46
  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/asset-core-types.d.ts +19 -0
  94. package/dist/aura3d-cli/asset-core-types.d.ts.map +1 -1
  95. package/dist/aura3d-cli/asset-manifest.d.ts.map +1 -1
  96. package/dist/aura3d-cli/asset-manifest.js +2 -1
  97. package/dist/aura3d-cli/asset-manifest.js.map +1 -1
  98. package/dist/aura3d-cli/cli-help.d.ts.map +1 -1
  99. package/dist/aura3d-cli/cli-help.js +2 -0
  100. package/dist/aura3d-cli/cli-help.js.map +1 -1
  101. package/dist/aura3d-cli/cli.js +24 -1
  102. package/dist/aura3d-cli/cli.js.map +1 -1
  103. package/dist/aura3d-cli/index.d.ts +5 -1
  104. package/dist/aura3d-cli/index.d.ts.map +1 -1
  105. package/dist/aura3d-cli/index.js +5 -1
  106. package/dist/aura3d-cli/index.js.map +1 -1
  107. package/dist/aura3d-cli/meshy/admission.d.ts +40 -0
  108. package/dist/aura3d-cli/meshy/admission.d.ts.map +1 -0
  109. package/dist/aura3d-cli/meshy/admission.js +126 -0
  110. package/dist/aura3d-cli/meshy/admission.js.map +1 -0
  111. package/dist/aura3d-cli/meshy/evidence.d.ts +12 -0
  112. package/dist/aura3d-cli/meshy/evidence.d.ts.map +1 -0
  113. package/dist/aura3d-cli/meshy/evidence.js +37 -0
  114. package/dist/aura3d-cli/meshy/evidence.js.map +1 -0
  115. package/dist/aura3d-cli/meshy/import.d.ts +25 -0
  116. package/dist/aura3d-cli/meshy/import.d.ts.map +1 -0
  117. package/dist/aura3d-cli/meshy/import.js +104 -0
  118. package/dist/aura3d-cli/meshy/import.js.map +1 -0
  119. package/dist/aura3d-cli/meshy/index.d.ts +10 -0
  120. package/dist/aura3d-cli/meshy/index.d.ts.map +1 -0
  121. package/dist/aura3d-cli/meshy/index.js +6 -0
  122. package/dist/aura3d-cli/meshy/index.js.map +1 -0
  123. package/dist/aura3d-cli/meshy/metadata.d.ts +22 -0
  124. package/dist/aura3d-cli/meshy/metadata.d.ts.map +1 -0
  125. package/dist/aura3d-cli/meshy/metadata.js +176 -0
  126. package/dist/aura3d-cli/meshy/metadata.js.map +1 -0
  127. package/dist/aura3d-cli/meshy/provenance.d.ts +4 -0
  128. package/dist/aura3d-cli/meshy/provenance.d.ts.map +1 -0
  129. package/dist/aura3d-cli/meshy/provenance.js +21 -0
  130. package/dist/aura3d-cli/meshy/provenance.js.map +1 -0
  131. package/dist/aura3d-cli/meshy/thumbnail.d.ts +19 -0
  132. package/dist/aura3d-cli/meshy/thumbnail.d.ts.map +1 -0
  133. package/dist/aura3d-cli/meshy/thumbnail.js +59 -0
  134. package/dist/aura3d-cli/meshy/thumbnail.js.map +1 -0
  135. package/dist/aura3d-cli/meshy/validation.d.ts +5 -0
  136. package/dist/aura3d-cli/meshy/validation.d.ts.map +1 -0
  137. package/dist/aura3d-cli/meshy/validation.js +64 -0
  138. package/dist/aura3d-cli/meshy/validation.js.map +1 -0
  139. package/dist/aura3d-cli/pull-bridge/profiles.d.ts +12 -2
  140. package/dist/aura3d-cli/pull-bridge/profiles.d.ts.map +1 -1
  141. package/dist/aura3d-cli/pull-bridge/profiles.js +35 -4
  142. package/dist/aura3d-cli/pull-bridge/profiles.js.map +1 -1
  143. package/dist/aura3d-cli/pull-bridge/provenance.d.ts +15 -1
  144. package/dist/aura3d-cli/pull-bridge/provenance.d.ts.map +1 -1
  145. package/dist/aura3d-cli/pull-bridge/provenance.js +60 -1
  146. package/dist/aura3d-cli/pull-bridge/provenance.js.map +1 -1
  147. package/dist/aura3d-cli/pull-bridge/scoring.d.ts +10 -1
  148. package/dist/aura3d-cli/pull-bridge/scoring.d.ts.map +1 -1
  149. package/dist/aura3d-cli/pull-bridge/scoring.js +9 -1
  150. package/dist/aura3d-cli/pull-bridge/scoring.js.map +1 -1
  151. package/dist/aura3d-cli/pull-bridge/search.js +2 -2
  152. package/dist/aura3d-cli/pull-bridge/search.js.map +1 -1
  153. package/dist/aura3d-cli/pull-bridge.d.ts +15 -3
  154. package/dist/aura3d-cli/pull-bridge.d.ts.map +1 -1
  155. package/dist/aura3d-cli/pull-bridge.js +68 -5
  156. package/dist/aura3d-cli/pull-bridge.js.map +1 -1
  157. package/dist/controls/ArcballControls.d.ts +92 -0
  158. package/dist/controls/ArcballControls.d.ts.map +1 -0
  159. package/dist/controls/ArcballControls.js +286 -0
  160. package/dist/controls/ArcballControls.js.map +1 -0
  161. package/dist/controls/DragControls.d.ts +13 -0
  162. package/dist/controls/DragControls.d.ts.map +1 -1
  163. package/dist/controls/DragControls.js +24 -1
  164. package/dist/controls/DragControls.js.map +1 -1
  165. package/dist/controls/FlyControls.d.ts +9 -0
  166. package/dist/controls/FlyControls.d.ts.map +1 -1
  167. package/dist/controls/FlyControls.js +20 -2
  168. package/dist/controls/FlyControls.js.map +1 -1
  169. package/dist/controls/FocusFrame.d.ts +34 -0
  170. package/dist/controls/FocusFrame.d.ts.map +1 -0
  171. package/dist/controls/FocusFrame.js +82 -0
  172. package/dist/controls/FocusFrame.js.map +1 -0
  173. package/dist/controls/HoverOutline.d.ts +46 -0
  174. package/dist/controls/HoverOutline.d.ts.map +1 -0
  175. package/dist/controls/HoverOutline.js +87 -0
  176. package/dist/controls/HoverOutline.js.map +1 -0
  177. package/dist/controls/InteractionControls.d.ts +7 -0
  178. package/dist/controls/InteractionControls.d.ts.map +1 -1
  179. package/dist/controls/InteractionControls.js +12 -1
  180. package/dist/controls/InteractionControls.js.map +1 -1
  181. package/dist/controls/NativeControlTypes.d.ts +17 -0
  182. package/dist/controls/NativeControlTypes.d.ts.map +1 -1
  183. package/dist/controls/NativeControlTypes.js.map +1 -1
  184. package/dist/controls/OrbitControls.d.ts +9 -0
  185. package/dist/controls/OrbitControls.d.ts.map +1 -1
  186. package/dist/controls/OrbitControls.js +26 -6
  187. package/dist/controls/OrbitControls.js.map +1 -1
  188. package/dist/controls/Picking.d.ts +15 -0
  189. package/dist/controls/Picking.d.ts.map +1 -1
  190. package/dist/controls/Picking.js +62 -23
  191. package/dist/controls/Picking.js.map +1 -1
  192. package/dist/controls/PointerLockControls.d.ts.map +1 -1
  193. package/dist/controls/PointerLockControls.js +4 -0
  194. package/dist/controls/PointerLockControls.js.map +1 -1
  195. package/dist/controls/SelectionManager.d.ts +8 -0
  196. package/dist/controls/SelectionManager.d.ts.map +1 -1
  197. package/dist/controls/SelectionManager.js +11 -0
  198. package/dist/controls/SelectionManager.js.map +1 -1
  199. package/dist/controls/TrackballControls.d.ts +7 -0
  200. package/dist/controls/TrackballControls.d.ts.map +1 -1
  201. package/dist/controls/TrackballControls.js +19 -5
  202. package/dist/controls/TrackballControls.js.map +1 -1
  203. package/dist/controls/TransformControls.d.ts +8 -0
  204. package/dist/controls/TransformControls.d.ts.map +1 -1
  205. package/dist/controls/TransformControls.js +15 -2
  206. package/dist/controls/TransformControls.js.map +1 -1
  207. package/dist/controls/index.d.ts +6 -0
  208. package/dist/controls/index.d.ts.map +1 -1
  209. package/dist/controls/index.js +3 -0
  210. package/dist/controls/index.js.map +1 -1
  211. package/dist/create-aura3d/index.js +1 -1
  212. package/dist/create-aura3d/showcase-spec-game-geometry-extractor.d.ts.map +1 -1
  213. package/dist/create-aura3d/showcase-spec-game-geometry-extractor.js +86 -4
  214. package/dist/create-aura3d/showcase-spec-game-geometry-extractor.js.map +1 -1
  215. package/dist/editor-runtime/RootEditorSurface.d.ts +65 -0
  216. package/dist/editor-runtime/RootEditorSurface.d.ts.map +1 -0
  217. package/dist/editor-runtime/RootEditorSurface.js +62 -0
  218. package/dist/editor-runtime/RootEditorSurface.js.map +1 -0
  219. package/dist/editor-runtime/index.d.ts +2 -0
  220. package/dist/editor-runtime/index.d.ts.map +1 -1
  221. package/dist/editor-runtime/index.js +1 -0
  222. package/dist/editor-runtime/index.js.map +1 -1
  223. package/dist/engine/agent-api/AnimationController.d.ts +9 -1
  224. package/dist/engine/agent-api/AnimationController.d.ts.map +1 -1
  225. package/dist/engine/agent-api/AnimationController.js +5 -1
  226. package/dist/engine/agent-api/AnimationController.js.map +1 -1
  227. package/dist/engine/agent-api/AnimationDebugOverlay.d.ts +56 -0
  228. package/dist/engine/agent-api/AnimationDebugOverlay.d.ts.map +1 -0
  229. package/dist/engine/agent-api/AnimationDebugOverlay.js +148 -0
  230. package/dist/engine/agent-api/AnimationDebugOverlay.js.map +1 -0
  231. package/dist/engine/agent-api/AnimationMixerBuilders.d.ts +41 -0
  232. package/dist/engine/agent-api/AnimationMixerBuilders.d.ts.map +1 -0
  233. package/dist/engine/agent-api/AnimationMixerBuilders.js +146 -0
  234. package/dist/engine/agent-api/AnimationMixerBuilders.js.map +1 -0
  235. package/dist/engine/agent-api/AssetDecoders.d.ts +17 -0
  236. package/dist/engine/agent-api/AssetDecoders.d.ts.map +1 -0
  237. package/dist/engine/agent-api/AssetDecoders.js +18 -0
  238. package/dist/engine/agent-api/AssetDecoders.js.map +1 -0
  239. package/dist/engine/agent-api/Decals.d.ts +174 -0
  240. package/dist/engine/agent-api/Decals.d.ts.map +1 -0
  241. package/dist/engine/agent-api/Decals.js +350 -0
  242. package/dist/engine/agent-api/Decals.js.map +1 -0
  243. package/dist/engine/agent-api/FootPlanting.d.ts +75 -0
  244. package/dist/engine/agent-api/FootPlanting.d.ts.map +1 -0
  245. package/dist/engine/agent-api/FootPlanting.js +60 -0
  246. package/dist/engine/agent-api/FootPlanting.js.map +1 -0
  247. package/dist/engine/agent-api/GameAppRuntime.d.ts +19 -0
  248. package/dist/engine/agent-api/GameAppRuntime.d.ts.map +1 -1
  249. package/dist/engine/agent-api/GameAppRuntime.js +37 -1
  250. package/dist/engine/agent-api/GameAppRuntime.js.map +1 -1
  251. package/dist/engine/agent-api/GameCameraRigs.d.ts +185 -0
  252. package/dist/engine/agent-api/GameCameraRigs.d.ts.map +1 -0
  253. package/dist/engine/agent-api/GameCameraRigs.js +414 -0
  254. package/dist/engine/agent-api/GameCameraRigs.js.map +1 -0
  255. package/dist/engine/agent-api/GameFeel.d.ts +100 -0
  256. package/dist/engine/agent-api/GameFeel.d.ts.map +1 -0
  257. package/dist/engine/agent-api/GameFeel.js +226 -0
  258. package/dist/engine/agent-api/GameFeel.js.map +1 -0
  259. package/dist/engine/agent-api/GameGenreKits.d.ts.map +1 -1
  260. package/dist/engine/agent-api/GameGenreKits.js +26 -3
  261. package/dist/engine/agent-api/GameGenreKits.js.map +1 -1
  262. package/dist/engine/agent-api/LabelTelemetry.d.ts +108 -0
  263. package/dist/engine/agent-api/LabelTelemetry.d.ts.map +1 -0
  264. package/dist/engine/agent-api/LabelTelemetry.js +116 -0
  265. package/dist/engine/agent-api/LabelTelemetry.js.map +1 -0
  266. package/dist/engine/agent-api/NavigationCrowds.d.ts +146 -0
  267. package/dist/engine/agent-api/NavigationCrowds.d.ts.map +1 -0
  268. package/dist/engine/agent-api/NavigationCrowds.js +104 -0
  269. package/dist/engine/agent-api/NavigationCrowds.js.map +1 -0
  270. package/dist/engine/agent-api/PhysicsRuntime.d.ts +61 -5
  271. package/dist/engine/agent-api/PhysicsRuntime.d.ts.map +1 -1
  272. package/dist/engine/agent-api/PhysicsRuntime.js +53 -21
  273. package/dist/engine/agent-api/PhysicsRuntime.js.map +1 -1
  274. package/dist/engine/agent-api/RootGeometry.d.ts +16 -1
  275. package/dist/engine/agent-api/RootGeometry.d.ts.map +1 -1
  276. package/dist/engine/agent-api/RootGeometry.js.map +1 -1
  277. package/dist/engine/agent-api/RuntimeNodeHandle.d.ts +6 -0
  278. package/dist/engine/agent-api/RuntimeNodeHandle.d.ts.map +1 -1
  279. package/dist/engine/agent-api/RuntimeNodeHandle.js.map +1 -1
  280. package/dist/engine/agent-api/Scatter.d.ts +18 -0
  281. package/dist/engine/agent-api/Scatter.d.ts.map +1 -0
  282. package/dist/engine/agent-api/Scatter.js +18 -0
  283. package/dist/engine/agent-api/Scatter.js.map +1 -0
  284. package/dist/engine/agent-api/TouchControlBinding.d.ts +32 -0
  285. package/dist/engine/agent-api/TouchControlBinding.d.ts.map +1 -1
  286. package/dist/engine/agent-api/TouchControlBinding.js +36 -0
  287. package/dist/engine/agent-api/TouchControlBinding.js.map +1 -1
  288. package/dist/engine/agent-api/VehicleChassis.d.ts.map +1 -1
  289. package/dist/engine/agent-api/VehicleChassis.js +76 -13
  290. package/dist/engine/agent-api/VehicleChassis.js.map +1 -1
  291. package/dist/engine/agent-api/index.d.ts +723 -6
  292. package/dist/engine/agent-api/index.d.ts.map +1 -1
  293. package/dist/engine/agent-api/index.js +1805 -137
  294. package/dist/engine/agent-api/index.js.map +1 -1
  295. package/dist/engine/agent-api/product-viewer-runtime.d.ts.map +1 -1
  296. package/dist/engine/game/GameAudio.d.ts +66 -0
  297. package/dist/engine/game/GameAudio.d.ts.map +1 -1
  298. package/dist/engine/game/GameAudio.js +179 -44
  299. package/dist/engine/game/GameAudio.js.map +1 -1
  300. package/dist/engine/instances-model/InstancedModel.d.ts +111 -0
  301. package/dist/engine/instances-model/InstancedModel.d.ts.map +1 -0
  302. package/dist/engine/instances-model/InstancedModel.js +108 -0
  303. package/dist/engine/instances-model/InstancedModel.js.map +1 -0
  304. package/dist/engine/material-physical/PhysicalMaterialSpec.d.ts +72 -0
  305. package/dist/engine/material-physical/PhysicalMaterialSpec.d.ts.map +1 -0
  306. package/dist/engine/material-physical/PhysicalMaterialSpec.js +95 -0
  307. package/dist/engine/material-physical/PhysicalMaterialSpec.js.map +1 -0
  308. package/dist/engine/production-runtime/GameAppRuntime.d.ts +1 -1
  309. package/dist/engine/production-runtime/GameAppRuntime.d.ts.map +1 -1
  310. package/dist/engine/production-runtime/GameRenderPreset.d.ts +76 -0
  311. package/dist/engine/production-runtime/GameRenderPreset.d.ts.map +1 -1
  312. package/dist/engine/production-runtime/GameRenderPreset.js +167 -0
  313. package/dist/engine/production-runtime/GameRenderPreset.js.map +1 -1
  314. package/dist/engine/production-runtime/TypedGLBActor.d.ts +36 -2
  315. package/dist/engine/production-runtime/TypedGLBActor.d.ts.map +1 -1
  316. package/dist/engine/production-runtime/TypedGLBActor.js +92 -5
  317. package/dist/engine/production-runtime/TypedGLBActor.js.map +1 -1
  318. package/dist/engine/production-runtime/index.d.ts +4 -4
  319. package/dist/engine/production-runtime/index.d.ts.map +1 -1
  320. package/dist/engine/production-runtime/index.js +5 -3
  321. package/dist/engine/production-runtime/index.js.map +1 -1
  322. package/dist/input/ActionMap.d.ts +40 -0
  323. package/dist/input/ActionMap.d.ts.map +1 -1
  324. package/dist/input/ActionMap.js +179 -0
  325. package/dist/input/ActionMap.js.map +1 -1
  326. package/dist/input/ComboDetector.d.ts +49 -0
  327. package/dist/input/ComboDetector.d.ts.map +1 -0
  328. package/dist/input/ComboDetector.js +102 -0
  329. package/dist/input/ComboDetector.js.map +1 -0
  330. package/dist/input/Haptics.d.ts +46 -0
  331. package/dist/input/Haptics.d.ts.map +1 -0
  332. package/dist/input/Haptics.js +67 -0
  333. package/dist/input/Haptics.js.map +1 -0
  334. package/dist/input/TouchLayouts.d.ts +35 -0
  335. package/dist/input/TouchLayouts.d.ts.map +1 -0
  336. package/dist/input/TouchLayouts.js +88 -0
  337. package/dist/input/TouchLayouts.js.map +1 -0
  338. package/dist/input/WebXRSessionController.d.ts +53 -0
  339. package/dist/input/WebXRSessionController.d.ts.map +1 -1
  340. package/dist/input/WebXRSessionController.js +88 -2
  341. package/dist/input/WebXRSessionController.js.map +1 -1
  342. package/dist/input/index.d.ts +8 -2
  343. package/dist/input/index.d.ts.map +1 -1
  344. package/dist/input/index.js +3 -0
  345. package/dist/input/index.js.map +1 -1
  346. package/dist/lean/game.d.ts +98 -0
  347. package/dist/lean/game.d.ts.map +1 -1
  348. package/dist/lean/game.js +192 -0
  349. package/dist/lean/game.js.map +1 -1
  350. package/dist/materials/GameReadyMaterialLibrary.d.ts +39 -0
  351. package/dist/materials/GameReadyMaterialLibrary.d.ts.map +1 -0
  352. package/dist/materials/GameReadyMaterialLibrary.js +253 -0
  353. package/dist/materials/GameReadyMaterialLibrary.js.map +1 -0
  354. package/dist/materials/MaterialValidation.d.ts +21 -0
  355. package/dist/materials/MaterialValidation.d.ts.map +1 -1
  356. package/dist/materials/MaterialValidation.js +105 -0
  357. package/dist/materials/MaterialValidation.js.map +1 -1
  358. package/dist/materials/browser-index.d.ts +2 -0
  359. package/dist/materials/browser-index.d.ts.map +1 -1
  360. package/dist/materials/browser-index.js +1 -0
  361. package/dist/materials/browser-index.js.map +1 -1
  362. package/dist/materials/node.d.ts +2 -2
  363. package/dist/materials/node.d.ts.map +1 -1
  364. package/dist/materials/node.js +1 -1
  365. package/dist/materials/node.js.map +1 -1
  366. package/dist/physics/CollisionVolumes.d.ts +59 -0
  367. package/dist/physics/CollisionVolumes.d.ts.map +1 -1
  368. package/dist/physics/CollisionVolumes.js +87 -0
  369. package/dist/physics/CollisionVolumes.js.map +1 -1
  370. package/dist/physics/PhysicsDebugDraw.d.ts +37 -0
  371. package/dist/physics/PhysicsDebugDraw.d.ts.map +1 -1
  372. package/dist/physics/PhysicsDebugDraw.js +38 -1
  373. package/dist/physics/PhysicsDebugDraw.js.map +1 -1
  374. package/dist/physics/PhysicsWorld.d.ts +15 -0
  375. package/dist/physics/PhysicsWorld.d.ts.map +1 -1
  376. package/dist/physics/PhysicsWorld.js +8 -1
  377. package/dist/physics/PhysicsWorld.js.map +1 -1
  378. package/dist/physics/solverless.d.ts +1 -1
  379. package/dist/physics/solverless.d.ts.map +1 -1
  380. package/dist/physics/solverless.js.map +1 -1
  381. package/dist/physics-rapier/index.d.ts +7 -1
  382. package/dist/physics-rapier/index.d.ts.map +1 -1
  383. package/dist/physics-rapier/index.js +1 -1
  384. package/dist/physics-rapier/index.js.map +1 -1
  385. package/dist/react/index.d.ts +136 -2
  386. package/dist/react/index.d.ts.map +1 -1
  387. package/dist/react/index.js +251 -10
  388. package/dist/react/index.js.map +1 -1
  389. package/dist/rendering/AtmosphereWetness.d.ts +61 -0
  390. package/dist/rendering/AtmosphereWetness.d.ts.map +1 -0
  391. package/dist/rendering/AtmosphereWetness.js +99 -0
  392. package/dist/rendering/AtmosphereWetness.js.map +1 -0
  393. package/dist/rendering/ClusteredForwardLighting.d.ts +25 -1
  394. package/dist/rendering/ClusteredForwardLighting.d.ts.map +1 -1
  395. package/dist/rendering/ClusteredForwardLighting.js +80 -3
  396. package/dist/rendering/ClusteredForwardLighting.js.map +1 -1
  397. package/dist/rendering/DayNightSky.d.ts +63 -0
  398. package/dist/rendering/DayNightSky.d.ts.map +1 -0
  399. package/dist/rendering/DayNightSky.js +156 -0
  400. package/dist/rendering/DayNightSky.js.map +1 -0
  401. package/dist/rendering/EnvironmentPresetPack.d.ts +30 -0
  402. package/dist/rendering/EnvironmentPresetPack.d.ts.map +1 -0
  403. package/dist/rendering/EnvironmentPresetPack.js +49 -0
  404. package/dist/rendering/EnvironmentPresetPack.js.map +1 -0
  405. package/dist/rendering/ForwardPass.d.ts +67 -0
  406. package/dist/rendering/ForwardPass.d.ts.map +1 -1
  407. package/dist/rendering/ForwardPass.js +196 -2
  408. package/dist/rendering/ForwardPass.js.map +1 -1
  409. package/dist/rendering/InstancedPBRMaterial.d.ts +1 -0
  410. package/dist/rendering/InstancedPBRMaterial.d.ts.map +1 -1
  411. package/dist/rendering/InstancedPBRMaterial.js +3 -0
  412. package/dist/rendering/InstancedPBRMaterial.js.map +1 -1
  413. package/dist/rendering/InstancingDiagnostics.d.ts +69 -0
  414. package/dist/rendering/InstancingDiagnostics.d.ts.map +1 -0
  415. package/dist/rendering/InstancingDiagnostics.js +98 -0
  416. package/dist/rendering/InstancingDiagnostics.js.map +1 -0
  417. package/dist/rendering/LightingRig.d.ts +19 -1
  418. package/dist/rendering/LightingRig.d.ts.map +1 -1
  419. package/dist/rendering/LightingRig.js +61 -1
  420. package/dist/rendering/LightingRig.js.map +1 -1
  421. package/dist/rendering/MeshConsolidation.d.ts +12 -0
  422. package/dist/rendering/MeshConsolidation.d.ts.map +1 -1
  423. package/dist/rendering/MeshConsolidation.js +28 -0
  424. package/dist/rendering/MeshConsolidation.js.map +1 -1
  425. package/dist/rendering/MorphTargetPlan.d.ts +29 -2
  426. package/dist/rendering/MorphTargetPlan.d.ts.map +1 -1
  427. package/dist/rendering/MorphTargetPlan.js +23 -4
  428. package/dist/rendering/MorphTargetPlan.js.map +1 -1
  429. package/dist/rendering/NormalMappedPBRMaterial.d.ts.map +1 -1
  430. package/dist/rendering/NormalMappedPBRMaterial.js +22 -2
  431. package/dist/rendering/NormalMappedPBRMaterial.js.map +1 -1
  432. package/dist/rendering/OceanSurface.d.ts +75 -0
  433. package/dist/rendering/OceanSurface.d.ts.map +1 -1
  434. package/dist/rendering/OceanSurface.js +211 -0
  435. package/dist/rendering/OceanSurface.js.map +1 -1
  436. package/dist/rendering/PBRMaterial.d.ts +1 -0
  437. package/dist/rendering/PBRMaterial.d.ts.map +1 -1
  438. package/dist/rendering/PBRMaterial.js +23 -2
  439. package/dist/rendering/PBRMaterial.js.map +1 -1
  440. package/dist/rendering/PbrReference.d.ts +48 -0
  441. package/dist/rendering/PbrReference.d.ts.map +1 -1
  442. package/dist/rendering/PbrReference.js +126 -0
  443. package/dist/rendering/PbrReference.js.map +1 -1
  444. package/dist/rendering/PlanarReflection.d.ts +221 -0
  445. package/dist/rendering/PlanarReflection.d.ts.map +1 -0
  446. package/dist/rendering/PlanarReflection.js +541 -0
  447. package/dist/rendering/PlanarReflection.js.map +1 -0
  448. package/dist/rendering/PostProcessPass.d.ts +13 -0
  449. package/dist/rendering/PostProcessPass.d.ts.map +1 -1
  450. package/dist/rendering/PostProcessPass.js +15 -4
  451. package/dist/rendering/PostProcessPass.js.map +1 -1
  452. package/dist/rendering/ReflectionSurfaces.d.ts +14 -0
  453. package/dist/rendering/ReflectionSurfaces.d.ts.map +1 -1
  454. package/dist/rendering/ReflectionSurfaces.js +93 -0
  455. package/dist/rendering/ReflectionSurfaces.js.map +1 -1
  456. package/dist/rendering/RenderDevice.d.ts +87 -0
  457. package/dist/rendering/RenderDevice.d.ts.map +1 -1
  458. package/dist/rendering/RenderDevice.js +85 -0
  459. package/dist/rendering/RenderDevice.js.map +1 -1
  460. package/dist/rendering/Renderer.js +8 -5
  461. package/dist/rendering/Renderer.js.map +1 -1
  462. package/dist/rendering/RendererPostprocessPlan.d.ts +48 -0
  463. package/dist/rendering/RendererPostprocessPlan.d.ts.map +1 -1
  464. package/dist/rendering/RendererPostprocessPlan.js +101 -11
  465. package/dist/rendering/RendererPostprocessPlan.js.map +1 -1
  466. package/dist/rendering/RendererVisualPipelineReport.d.ts.map +1 -1
  467. package/dist/rendering/RendererVisualPipelineReport.js +3 -1
  468. package/dist/rendering/RendererVisualPipelineReport.js.map +1 -1
  469. package/dist/rendering/Sampler.d.ts +29 -0
  470. package/dist/rendering/Sampler.d.ts.map +1 -1
  471. package/dist/rendering/Sampler.js +47 -0
  472. package/dist/rendering/Sampler.js.map +1 -1
  473. package/dist/rendering/SceneOptimization.d.ts +1 -0
  474. package/dist/rendering/SceneOptimization.d.ts.map +1 -1
  475. package/dist/rendering/SceneOptimization.js +4 -2
  476. package/dist/rendering/SceneOptimization.js.map +1 -1
  477. package/dist/rendering/SdfText.d.ts +250 -0
  478. package/dist/rendering/SdfText.d.ts.map +1 -0
  479. package/dist/rendering/SdfText.js +551 -0
  480. package/dist/rendering/SdfText.js.map +1 -0
  481. package/dist/rendering/ShaderChunks.d.ts.map +1 -1
  482. package/dist/rendering/ShaderChunks.js +30 -12
  483. package/dist/rendering/ShaderChunks.js.map +1 -1
  484. package/dist/rendering/ShaderLibrary.d.ts.map +1 -1
  485. package/dist/rendering/ShaderLibrary.js +478 -34
  486. package/dist/rendering/ShaderLibrary.js.map +1 -1
  487. package/dist/rendering/ShaderLibraryCore.d.ts.map +1 -1
  488. package/dist/rendering/ShaderLibraryCore.js +70 -12
  489. package/dist/rendering/ShaderLibraryCore.js.map +1 -1
  490. package/dist/rendering/ShadowMap.d.ts +24 -0
  491. package/dist/rendering/ShadowMap.d.ts.map +1 -1
  492. package/dist/rendering/ShadowMap.js +43 -0
  493. package/dist/rendering/ShadowMap.js.map +1 -1
  494. package/dist/rendering/SkinnedLitMaterial.d.ts.map +1 -1
  495. package/dist/rendering/SkinnedLitMaterial.js +2 -0
  496. package/dist/rendering/SkinnedLitMaterial.js.map +1 -1
  497. package/dist/rendering/SkinningBounds.d.ts +10 -0
  498. package/dist/rendering/SkinningBounds.d.ts.map +1 -1
  499. package/dist/rendering/SkinningBounds.js +27 -0
  500. package/dist/rendering/SkinningBounds.js.map +1 -1
  501. package/dist/rendering/SpriteFlipbook.d.ts +60 -0
  502. package/dist/rendering/SpriteFlipbook.d.ts.map +1 -0
  503. package/dist/rendering/SpriteFlipbook.js +128 -0
  504. package/dist/rendering/SpriteFlipbook.js.map +1 -0
  505. package/dist/rendering/TerrainTiles.d.ts +120 -0
  506. package/dist/rendering/TerrainTiles.d.ts.map +1 -0
  507. package/dist/rendering/TerrainTiles.js +221 -0
  508. package/dist/rendering/TerrainTiles.js.map +1 -0
  509. package/dist/rendering/TexturedPBRMaterial.d.ts +1 -0
  510. package/dist/rendering/TexturedPBRMaterial.d.ts.map +1 -1
  511. package/dist/rendering/TexturedPBRMaterial.js +23 -2
  512. package/dist/rendering/TexturedPBRMaterial.js.map +1 -1
  513. package/dist/rendering/TexturedUnlitMaterial.d.ts.map +1 -1
  514. package/dist/rendering/TexturedUnlitMaterial.js +20 -2
  515. package/dist/rendering/TexturedUnlitMaterial.js.map +1 -1
  516. package/dist/rendering/UnlitMaterial.d.ts.map +1 -1
  517. package/dist/rendering/UnlitMaterial.js +20 -2
  518. package/dist/rendering/UnlitMaterial.js.map +1 -1
  519. package/dist/rendering/VertexFormat.d.ts +6 -0
  520. package/dist/rendering/VertexFormat.d.ts.map +1 -1
  521. package/dist/rendering/VertexFormat.js +12 -0
  522. package/dist/rendering/VertexFormat.js.map +1 -1
  523. package/dist/rendering/VolumetricFog.d.ts +55 -0
  524. package/dist/rendering/VolumetricFog.d.ts.map +1 -0
  525. package/dist/rendering/VolumetricFog.js +91 -0
  526. package/dist/rendering/VolumetricFog.js.map +1 -0
  527. package/dist/rendering/WaterSurface.d.ts +77 -0
  528. package/dist/rendering/WaterSurface.d.ts.map +1 -0
  529. package/dist/rendering/WaterSurface.js +133 -0
  530. package/dist/rendering/WaterSurface.js.map +1 -0
  531. package/dist/rendering/WebGL2Device.d.ts +58 -0
  532. package/dist/rendering/WebGL2Device.d.ts.map +1 -1
  533. package/dist/rendering/WebGL2Device.js +536 -17
  534. package/dist/rendering/WebGL2Device.js.map +1 -1
  535. package/dist/rendering/WebGPUDevice.d.ts +50 -0
  536. package/dist/rendering/WebGPUDevice.d.ts.map +1 -1
  537. package/dist/rendering/WebGPUDevice.js +504 -10
  538. package/dist/rendering/WebGPUDevice.js.map +1 -1
  539. package/dist/rendering/WebGPUSkinningLimits.d.ts +40 -0
  540. package/dist/rendering/WebGPUSkinningLimits.d.ts.map +1 -1
  541. package/dist/rendering/WebGPUSkinningLimits.js +32 -0
  542. package/dist/rendering/WebGPUSkinningLimits.js.map +1 -1
  543. package/dist/rendering/effects/CollisionModule.d.ts +9 -0
  544. package/dist/rendering/effects/CollisionModule.d.ts.map +1 -1
  545. package/dist/rendering/effects/CollisionModule.js +10 -0
  546. package/dist/rendering/effects/CollisionModule.js.map +1 -1
  547. package/dist/rendering/effects/ColorModule.d.ts +7 -0
  548. package/dist/rendering/effects/ColorModule.d.ts.map +1 -1
  549. package/dist/rendering/effects/ColorModule.js +23 -0
  550. package/dist/rendering/effects/ColorModule.js.map +1 -1
  551. package/dist/rendering/effects/ForceModule.d.ts +35 -0
  552. package/dist/rendering/effects/ForceModule.d.ts.map +1 -1
  553. package/dist/rendering/effects/ForceModule.js +49 -0
  554. package/dist/rendering/effects/ForceModule.js.map +1 -1
  555. package/dist/rendering/effects/GPUParticleBackend.d.ts +186 -0
  556. package/dist/rendering/effects/GPUParticleBackend.d.ts.map +1 -1
  557. package/dist/rendering/effects/GPUParticleBackend.js +692 -1
  558. package/dist/rendering/effects/GPUParticleBackend.js.map +1 -1
  559. package/dist/rendering/effects/HeightfieldModule.d.ts +58 -0
  560. package/dist/rendering/effects/HeightfieldModule.d.ts.map +1 -0
  561. package/dist/rendering/effects/HeightfieldModule.js +129 -0
  562. package/dist/rendering/effects/HeightfieldModule.js.map +1 -0
  563. package/dist/rendering/effects/LightingModule.d.ts +34 -0
  564. package/dist/rendering/effects/LightingModule.d.ts.map +1 -0
  565. package/dist/rendering/effects/LightingModule.js +54 -0
  566. package/dist/rendering/effects/LightingModule.js.map +1 -0
  567. package/dist/rendering/effects/ParticleDiagnostics.d.ts +8 -0
  568. package/dist/rendering/effects/ParticleDiagnostics.d.ts.map +1 -1
  569. package/dist/rendering/effects/ParticleDiagnostics.js +3 -0
  570. package/dist/rendering/effects/ParticleDiagnostics.js.map +1 -1
  571. package/dist/rendering/effects/ParticleModule.d.ts +8 -0
  572. package/dist/rendering/effects/ParticleModule.d.ts.map +1 -1
  573. package/dist/rendering/effects/ParticleModule.js.map +1 -1
  574. package/dist/rendering/effects/ParticleRenderer.d.ts +20 -0
  575. package/dist/rendering/effects/ParticleRenderer.d.ts.map +1 -1
  576. package/dist/rendering/effects/ParticleRenderer.js +57 -7
  577. package/dist/rendering/effects/ParticleRenderer.js.map +1 -1
  578. package/dist/rendering/effects/ParticleSystem.d.ts +45 -1
  579. package/dist/rendering/effects/ParticleSystem.d.ts.map +1 -1
  580. package/dist/rendering/effects/ParticleSystem.js +373 -4
  581. package/dist/rendering/effects/ParticleSystem.js.map +1 -1
  582. package/dist/rendering/effects/SizeModule.d.ts +4 -0
  583. package/dist/rendering/effects/SizeModule.d.ts.map +1 -1
  584. package/dist/rendering/effects/SizeModule.js +16 -0
  585. package/dist/rendering/effects/SizeModule.js.map +1 -1
  586. package/dist/rendering/effects/SubEmitterModule.d.ts +61 -0
  587. package/dist/rendering/effects/SubEmitterModule.d.ts.map +1 -0
  588. package/dist/rendering/effects/SubEmitterModule.js +119 -0
  589. package/dist/rendering/effects/SubEmitterModule.js.map +1 -0
  590. package/dist/rendering/effects/TrailModule.d.ts +38 -0
  591. package/dist/rendering/effects/TrailModule.d.ts.map +1 -1
  592. package/dist/rendering/effects/TrailModule.js +95 -2
  593. package/dist/rendering/effects/TrailModule.js.map +1 -1
  594. package/dist/rendering/effects/TurbulenceModule.d.ts +43 -0
  595. package/dist/rendering/effects/TurbulenceModule.d.ts.map +1 -0
  596. package/dist/rendering/effects/TurbulenceModule.js +130 -0
  597. package/dist/rendering/effects/TurbulenceModule.js.map +1 -0
  598. package/dist/rendering/effects/index.d.ts +4 -0
  599. package/dist/rendering/effects/index.d.ts.map +1 -1
  600. package/dist/rendering/effects/index.js +4 -0
  601. package/dist/rendering/effects/index.js.map +1 -1
  602. package/dist/rendering/index.d.ts +57 -19
  603. package/dist/rendering/index.d.ts.map +1 -1
  604. package/dist/rendering/index.js +31 -10
  605. package/dist/rendering/index.js.map +1 -1
  606. package/dist/rendering/materials/MaterialExtensions.js +3 -3
  607. package/dist/rendering/materials/MaterialExtensions.js.map +1 -1
  608. package/dist/rendering/postprocess/NativeBloomPyramid.d.ts +28 -0
  609. package/dist/rendering/postprocess/NativeBloomPyramid.d.ts.map +1 -0
  610. package/dist/rendering/postprocess/NativeBloomPyramid.js +54 -0
  611. package/dist/rendering/postprocess/NativeBloomPyramid.js.map +1 -0
  612. package/dist/rendering/postprocess/NativeLdrEffectLuts.d.ts +10 -1
  613. package/dist/rendering/postprocess/NativeLdrEffectLuts.d.ts.map +1 -1
  614. package/dist/rendering/postprocess/NativeLdrEffectLuts.js +19 -2
  615. package/dist/rendering/postprocess/NativeLdrEffectLuts.js.map +1 -1
  616. package/dist/rendering/postprocess/index.d.ts +2 -0
  617. package/dist/rendering/postprocess/index.d.ts.map +1 -1
  618. package/dist/rendering/postprocess/index.js +1 -0
  619. package/dist/rendering/postprocess/index.js.map +1 -1
  620. package/dist/rendering/production-runtime/PBRHDRPipeline.d.ts +22 -0
  621. package/dist/rendering/production-runtime/PBRHDRPipeline.d.ts.map +1 -1
  622. package/dist/rendering/production-runtime/PBRHDRPipeline.js +125 -0
  623. package/dist/rendering/production-runtime/PBRHDRPipeline.js.map +1 -1
  624. package/dist/rendering/production-runtime/ProductionWebGPURenderer.d.ts +52 -0
  625. package/dist/rendering/production-runtime/ProductionWebGPURenderer.d.ts.map +1 -1
  626. package/dist/rendering/production-runtime/ProductionWebGPURenderer.js +101 -0
  627. package/dist/rendering/production-runtime/ProductionWebGPURenderer.js.map +1 -1
  628. package/dist/rendering/production-runtime/index.d.ts +5 -4
  629. package/dist/rendering/production-runtime/index.d.ts.map +1 -1
  630. package/dist/rendering/production-runtime/index.js +3 -2
  631. package/dist/rendering/production-runtime/index.js.map +1 -1
  632. package/dist/rendering/production-runtime/passes/DepthPrepass.d.ts +26 -1
  633. package/dist/rendering/production-runtime/passes/DepthPrepass.d.ts.map +1 -1
  634. package/dist/rendering/production-runtime/passes/DepthPrepass.js +49 -2
  635. package/dist/rendering/production-runtime/passes/DepthPrepass.js.map +1 -1
  636. package/dist/rendering/production-runtime/passes/FramegraphTopology.d.ts +13 -0
  637. package/dist/rendering/production-runtime/passes/FramegraphTopology.d.ts.map +1 -0
  638. package/dist/rendering/production-runtime/passes/FramegraphTopology.js +59 -0
  639. package/dist/rendering/production-runtime/passes/FramegraphTopology.js.map +1 -0
  640. package/dist/rendering/production-runtime/passes/OpaquePass.d.ts +24 -1
  641. package/dist/rendering/production-runtime/passes/OpaquePass.d.ts.map +1 -1
  642. package/dist/rendering/production-runtime/passes/OpaquePass.js +41 -2
  643. package/dist/rendering/production-runtime/passes/OpaquePass.js.map +1 -1
  644. package/dist/rendering/production-runtime/passes/ShadowPass.d.ts +24 -1
  645. package/dist/rendering/production-runtime/passes/ShadowPass.d.ts.map +1 -1
  646. package/dist/rendering/production-runtime/passes/ShadowPass.js +43 -2
  647. package/dist/rendering/production-runtime/passes/ShadowPass.js.map +1 -1
  648. package/dist/rendering/production-runtime/passes/SkyboxPass.d.ts +21 -1
  649. package/dist/rendering/production-runtime/passes/SkyboxPass.d.ts.map +1 -1
  650. package/dist/rendering/production-runtime/passes/SkyboxPass.js +39 -2
  651. package/dist/rendering/production-runtime/passes/SkyboxPass.js.map +1 -1
  652. package/dist/rendering/production-runtime/passes/ToneMappingPass.d.ts +29 -1
  653. package/dist/rendering/production-runtime/passes/ToneMappingPass.d.ts.map +1 -1
  654. package/dist/rendering/production-runtime/passes/ToneMappingPass.js +53 -2
  655. package/dist/rendering/production-runtime/passes/ToneMappingPass.js.map +1 -1
  656. package/dist/rendering/production-runtime/passes/TransparentPass.d.ts +24 -1
  657. package/dist/rendering/production-runtime/passes/TransparentPass.d.ts.map +1 -1
  658. package/dist/rendering/production-runtime/passes/TransparentPass.js +42 -2
  659. package/dist/rendering/production-runtime/passes/TransparentPass.js.map +1 -1
  660. package/dist/rendering/shadows/CascadeHysteresis.d.ts +67 -0
  661. package/dist/rendering/shadows/CascadeHysteresis.d.ts.map +1 -0
  662. package/dist/rendering/shadows/CascadeHysteresis.js +104 -0
  663. package/dist/rendering/shadows/CascadeHysteresis.js.map +1 -0
  664. package/dist/rendering/shadows/ContactShadows.d.ts +52 -0
  665. package/dist/rendering/shadows/ContactShadows.d.ts.map +1 -1
  666. package/dist/rendering/shadows/ContactShadows.js +122 -0
  667. package/dist/rendering/shadows/ContactShadows.js.map +1 -1
  668. package/dist/rendering/shadows/SpotShadowMaps.d.ts +77 -0
  669. package/dist/rendering/shadows/SpotShadowMaps.d.ts.map +1 -0
  670. package/dist/rendering/shadows/SpotShadowMaps.js +106 -0
  671. package/dist/rendering/shadows/SpotShadowMaps.js.map +1 -0
  672. package/dist/rendering/webgpu/WebGPUPostProcess.d.ts +2 -0
  673. package/dist/rendering/webgpu/WebGPUPostProcess.d.ts.map +1 -1
  674. package/dist/rendering/webgpu/WebGPUPostProcess.js +1 -0
  675. package/dist/rendering/webgpu/WebGPUPostProcess.js.map +1 -1
  676. package/dist/rendering/webgpu/WebGPUPostShaders.d.ts +78 -0
  677. package/dist/rendering/webgpu/WebGPUPostShaders.d.ts.map +1 -0
  678. package/dist/rendering/webgpu/WebGPUPostShaders.js +262 -0
  679. package/dist/rendering/webgpu/WebGPUPostShaders.js.map +1 -0
  680. package/dist/scene/Scene.d.ts +8 -1
  681. package/dist/scene/Scene.d.ts.map +1 -1
  682. package/dist/scene/Scene.js +12 -2
  683. package/dist/scene/Scene.js.map +1 -1
  684. package/dist/scripting/VisualGraphContext.d.ts +31 -0
  685. package/dist/scripting/VisualGraphContext.d.ts.map +1 -1
  686. package/dist/scripting/VisualGraphExecutor.d.ts.map +1 -1
  687. package/dist/scripting/VisualGraphExecutor.js +110 -0
  688. package/dist/scripting/VisualGraphExecutor.js.map +1 -1
  689. package/dist/scripting/VisualNodeCatalog.d.ts +1 -1
  690. package/dist/scripting/VisualNodeCatalog.d.ts.map +1 -1
  691. package/dist/scripting/VisualNodeCatalog.js +20 -0
  692. package/dist/scripting/VisualNodeCatalog.js.map +1 -1
  693. package/dist/scripting/VisualScriptingRoot.d.ts +96 -0
  694. package/dist/scripting/VisualScriptingRoot.d.ts.map +1 -0
  695. package/dist/scripting/VisualScriptingRoot.js +150 -0
  696. package/dist/scripting/VisualScriptingRoot.js.map +1 -0
  697. package/dist/scripting/index.d.ts +3 -1
  698. package/dist/scripting/index.d.ts.map +1 -1
  699. package/dist/scripting/index.js +1 -0
  700. package/dist/scripting/index.js.map +1 -1
  701. package/package.json +30 -23
  702. package/templates/cinematic-scene/package.json +1 -1
  703. package/templates/mini-game/package.json +1 -1
  704. package/templates/product-viewer/package.json +1 -1
@@ -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,10 +50,27 @@ 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";
67
+ /**
68
+ * Deliberate public surface for the shared arcade vehicle core. The racing kit
69
+ * owns the certified circuit path; exporting the bare motion helper lets routes
70
+ * such as showcase-courier-rush prove a different driving personality on their
71
+ * own topology without touching kit internals. Pinned by unit test.
72
+ */
73
+ export { createGameArcadeVehicle } from "./GameRuntime.js";
56
74
  export * from "./PlatformerMotion.js";
57
75
  export * from "./CombatFrameData.js";
58
76
  export * from "./SceneQueries.js";
@@ -76,7 +94,16 @@ export * from "./WebCodecsFrameEncoder.js";
76
94
  */
77
95
  export { GameInspector, createGameInspector } from "./GameInspector.js";
78
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";
79
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";
80
107
  export * from "./RenderProgressTracker.js";
81
108
  export * from "./AudioVisemeAnalyzer.js";
82
109
  export * from "./ExternalPhonemeAnalyzer.js";
@@ -107,6 +134,7 @@ export * from "./EpisodeTemplates.js";
107
134
  export * from "./AnimationMotionQuality.js";
108
135
  export * from "./AnimationRouteProof.js";
109
136
  export * from "./AnimationEpisodePackage.js";
137
+ import { evaluateDistancePrioritizedMipResidency } from "../../assets/browser-index.js";
110
138
  import { createFrameLoop } from "./FrameLoop.js";
111
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";
112
140
  import { collectGameRuntimeEvidence as collectGameRuntimeEvidenceV105 } from "./GameEvidence.js";
@@ -146,6 +174,8 @@ export * from "./GameAssetValidation.js";
146
174
  export * from "./CharacterAssembly.js";
147
175
  export * from "./AssetEvidence.js";
148
176
  export * from "./AnimationController.js";
177
+ export * from "./AnimationMixerBuilders.js";
178
+ export * from "./AnimationDebugOverlay.js";
149
179
  export { gameAssetValidation, quaterniusGameReadyFighterValidationContract, validateQuaterniusGameReadyFighterAsset } from "./GameAssetValidation.js";
150
180
  export { createAnimationController } from "./AnimationController.js";
151
181
  export * from "./PromptAnimationContract.js";
@@ -237,7 +267,8 @@ export function model(asset, options = {}) {
237
267
  targetMaxDimension: options.targetMaxDimension,
238
268
  targetLength: options.targetLength,
239
269
  physics: options.physics,
240
- hiddenNodeNames: options.hiddenNodeNames
270
+ hiddenNodeNames: options.hiddenNodeNames,
271
+ wrinkle: options.wrinkle
241
272
  });
242
273
  }
243
274
  export function unsafeModelUrl(url, options = {}) {
@@ -329,15 +360,103 @@ export const instances = {
329
360
  plane: (options) => instancedPrimitive("plane", options),
330
361
  cylinder: (options) => instancedPrimitive("cylinder", options),
331
362
  capsule: (options) => instancedPrimitive("capsule", options),
332
- torus: (options) => instancedPrimitive("torus", options)
363
+ torus: (options) => instancedPrimitive("torus", options),
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"
333
421
  };
334
422
  export const geometry = {
335
423
  define: defineAuraCustomGeometry,
336
424
  custom: (spec, options = {}) => primitive("custom", { ...options, geometry: defineAuraCustomGeometry(spec) })
337
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
+ }
338
432
  export function text3D(text, options = {}) {
339
433
  const built = createAuraText3DGeometry(text, options);
340
- 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
+ });
341
460
  }
342
461
  export function distanceLod(options) {
343
462
  if (options.levels.length === 0)
@@ -389,6 +508,13 @@ function proceduralTexture(texture, options = {}) {
389
508
  colorB: options.colorB
390
509
  };
391
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
+ ];
392
518
  export const material = {
393
519
  pbr: (options = {}) => ({
394
520
  color: "#d7dee8",
@@ -397,7 +523,29 @@ export const material = {
397
523
  metalness: options.metalness ?? options.metallic ?? 0,
398
524
  ...options
399
525
  }),
400
- 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
+ },
401
549
  emissive: (options = {}) => ({
402
550
  color: options.color ?? "#111827",
403
551
  emissive: options.emissive ?? options.color ?? "#38d6ff",
@@ -691,33 +839,41 @@ const materialCapabilityCatalog = [
691
839
  {
692
840
  id: "base-color-texture",
693
841
  label: "Base-color texture",
694
- rootSafeApi: "partial",
842
+ rootSafeApi: "supported",
695
843
  productionRuntime: "supported",
696
- 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).",
697
845
  claimRule: "Can describe typed texture metadata and rendered textured assets; do not claim full texture-material parity without controlled root pixels."
698
846
  },
699
847
  {
700
848
  id: "metallic-roughness",
701
849
  label: "Metallic/roughness",
702
- rootSafeApi: "partial",
850
+ rootSafeApi: "supported",
703
851
  productionRuntime: "supported",
704
- 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).",
705
853
  claimRule: "Do not claim full PBR parity without browser evidence."
706
854
  },
707
855
  {
708
856
  id: "normal-map",
709
857
  label: "Normal maps",
710
- rootSafeApi: "partial",
858
+ rootSafeApi: "supported",
711
859
  productionRuntime: "supported",
712
- evidence: "Root specs carry normal/procedural-normal intent; production shader parity is not guaranteed.",
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",
867
+ productionRuntime: "supported",
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).",
713
869
  claimRule: "Claim only with material-specific screenshot evidence."
714
870
  },
715
871
  {
716
872
  id: "emissive",
717
873
  label: "Emissive",
718
- rootSafeApi: "partial",
874
+ rootSafeApi: "supported",
719
875
  productionRuntime: "supported",
720
- 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.",
721
877
  claimRule: "Distinguish emissive material from postprocess bloom."
722
878
  },
723
879
  {
@@ -771,9 +927,9 @@ const materialCapabilityCatalog = [
771
927
  {
772
928
  id: "hdr-ibl",
773
929
  label: "HDR/IBL",
774
- rootSafeApi: "partial",
930
+ rootSafeApi: "supported",
775
931
  productionRuntime: "supported",
776
- 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).",
777
933
  claimRule: "Root HDR/IBL claims require diagnostics and browser pixels."
778
934
  },
779
935
  {
@@ -797,7 +953,9 @@ function createMaterialCapabilityDiagnostics(input) {
797
953
  requested.add("metallic-roughness");
798
954
  if (spec.normal)
799
955
  requested.add("normal-map");
800
- 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)
801
959
  requested.add("emissive");
802
960
  if (spec.opacity !== undefined && spec.opacity < 1)
803
961
  requested.add("alpha");
@@ -821,6 +979,11 @@ function createMaterialCapabilityDiagnostics(input) {
821
979
  if (partialRequestedFeatures.length > 0) {
822
980
  warnings.push(`Partial root material features requested: ${partialRequestedFeatures.join(", ")}. Capture route pixels before claiming production material quality.`);
823
981
  }
982
+ for (const spec of specs) {
983
+ for (const physicalWarning of spec.physicalWarnings ?? []) {
984
+ warnings.push(`Physical material extension bounded: ${physicalWarning}`);
985
+ }
986
+ }
824
987
  if (unsupportedRequestedFeatures.length > 0) {
825
988
  warnings.push(`Metadata/internal material features requested: ${unsupportedRequestedFeatures.join(", ")}. Do not claim rendered support from root createAuraApp alone.`);
826
989
  }
@@ -1040,6 +1203,20 @@ export const lights = {
1040
1203
  intensity: options.intensity ?? 2,
1041
1204
  color: options.color ?? "#ffffff"
1042
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
+ }),
1043
1220
  studio: (options = {}) => new AuraNodeBuilder({
1044
1221
  kind: "light",
1045
1222
  light: "studio",
@@ -1127,6 +1304,8 @@ export const camera = {
1127
1304
  position: options.position,
1128
1305
  target: options.target ?? [0, 1, 0],
1129
1306
  offset: options.offset,
1307
+ targetOffset: options.targetOffset,
1308
+ offsetMode: options.offsetMode,
1130
1309
  fov: options.fov ?? 50,
1131
1310
  easing: options.easing,
1132
1311
  captureTime: options.captureTime,
@@ -1247,8 +1426,10 @@ export const camera = {
1247
1426
  solar: () => camera.orbit({ target: [0, 0, 0], distance: 7.2, fov: 46 }),
1248
1427
  humanoid: () => camera.perspective({ position: [1.2, 1.12, 3.45], target: [0, 0.78, -0.55], fov: 36 }),
1249
1428
  miniGolf: () => camera.follow({ targetNode: "white physics golf ball", distance: 4.2, fov: 48 }),
1250
- 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
1251
1431
  };
1432
+ export const gameFeel = { create: gameFeelBuilders.create, hitStopDefaults: gameFeelBuilders.hitStopDefaults };
1252
1433
  function resolveFrameAssetRenderScale(bounds, options) {
1253
1434
  const size = boundsSize(bounds);
1254
1435
  const height = Math.max(0.001, boundsHeight(bounds));
@@ -1304,7 +1485,10 @@ export const effects = {
1304
1485
  radius: options.radius ?? 0.38,
1305
1486
  threshold: options.threshold ?? 0.7,
1306
1487
  antiBlowout,
1307
- maxIntensity
1488
+ maxIntensity,
1489
+ ...(options.quality !== undefined ? { quality: options.quality } : {}),
1490
+ ...(options.softKnee !== undefined ? { softKnee: options.softKnee } : {}),
1491
+ ...(options.shoulder !== undefined ? { shoulder: options.shoulder } : {})
1308
1492
  });
1309
1493
  },
1310
1494
  cinematicBloom: (options = {}) => effects.bloom({
@@ -1323,10 +1507,22 @@ export const effects = {
1323
1507
  antiBlowout: options.antiBlowout ?? true,
1324
1508
  maxIntensity: options.maxIntensity ?? 0.92
1325
1509
  }),
1326
- 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",
1327
1519
  density: options.density ?? 0.18,
1328
1520
  color: options.color ?? "#6f84b9",
1329
- 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 } : {})
1330
1526
  }),
1331
1527
  depthFog: (options = {}) => effects.fog({
1332
1528
  density: options.density ?? 0.14,
@@ -1351,6 +1547,82 @@ export const effects = {
1351
1547
  density: options.density ?? 0.7,
1352
1548
  color: options.color ?? "#020617"
1353
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
+ }),
1354
1626
  rain: (options = {}) => new AuraNodeBuilder({
1355
1627
  kind: "effect",
1356
1628
  effect: "rain",
@@ -1363,6 +1635,62 @@ export const effects = {
1363
1635
  splashes: options.splashes ?? true,
1364
1636
  mist: options.mist ?? true
1365
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
+ },
1366
1694
  particles: (options = {}) => new AuraNodeBuilder({
1367
1695
  kind: "effect",
1368
1696
  effect: "particles",
@@ -1388,6 +1716,161 @@ export const effects = {
1388
1716
  noise: options.noise ?? 0.22
1389
1717
  })
1390
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
+ };
1391
1874
  export const interactions = {
1392
1875
  orbit: (options = {}) => new AuraNodeBuilder({
1393
1876
  kind: "interaction",
@@ -1638,7 +2121,24 @@ export const environments = {
1638
2121
  if (materialClass === "product")
1639
2122
  return environments.productHero();
1640
2123
  return environments.studio();
1641
- }
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
+ })
1642
2142
  };
1643
2143
  const rendererColorManagementPreset = {
1644
2144
  kind: "aura-renderer-color-management",
@@ -1764,6 +2264,7 @@ function normalizeCreateAppRendererOptions(options) {
1764
2264
  mode,
1765
2265
  fallback: options?.fallback ?? "safe-basic",
1766
2266
  qualityProfile: profile.id,
2267
+ textureBudgetBytes: normalizeTextureBudgetBytes(options?.textureBudgetBytes),
1767
2268
  profile
1768
2269
  };
1769
2270
  }
@@ -1807,17 +2308,42 @@ function createRendererDiagnosticReport(snapshot, runtime, rendererOptions) {
1807
2308
  const sceneCategory = resolveRendererSceneCategory(snapshot, names);
1808
2309
  const exposure = sceneExposurePresets[sceneCategory];
1809
2310
  const bloom = flattened.find((node) => node.kind === "effect" && node.effect === "bloom");
1810
- 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"));
1811
2312
  const environment = flattened.find((node) => node.kind === "environment");
1812
2313
  const contactShadows = names.filter((name) => name.includes("contact shadow") || name.includes("footprint") || name.includes("glow pool")).length;
1813
2314
  const ambientOcclusion = flattened.some((node) => node.kind === "effect" && node.effect === "ambient-occlusion");
1814
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);
1815
2331
  const runtimePostprocess = runtime?.postprocess;
1816
2332
  const runtimePasses = runtimePostprocess?.actualPasses ?? [];
1817
- 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;
1818
2336
  const requestedPasses = runtimePasses.length > 0
1819
2337
  ? runtimePasses
1820
- : 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
+ });
1821
2347
  const runtimeStatus = !postprocessRequested
1822
2348
  ? "disabled"
1823
2349
  : runtime?.mounted
@@ -1832,6 +2358,28 @@ function createRendererDiagnosticReport(snapshot, runtime, rendererOptions) {
1832
2358
  warnings.push("scene has no contact shadow or contact-occlusion grounding cue");
1833
2359
  if (bloom && (bloom.intensity ?? 0) > 0.95 && bloom.antiBlowout !== true)
1834
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");
1835
2383
  if (postprocessRequested && !runtime?.mounted)
1836
2384
  warnings.push("renderer diagnostics are a scene plan only; call createAuraApp(...).diagnostics() after the first render for pixel-backed pass status");
1837
2385
  if (postprocessRequested && runtime?.mounted && !runtimePostprocess?.pixelBacked)
@@ -1881,7 +2429,8 @@ function createRendererDiagnosticReport(snapshot, runtime, rendererOptions) {
1881
2429
  intensity: bloom?.intensity ?? 0,
1882
2430
  threshold: bloom?.threshold ?? 0,
1883
2431
  radius: bloom?.radius ?? 0,
1884
- antiBlowout: bloom?.antiBlowout ?? true
2432
+ antiBlowout: bloom?.antiBlowout ?? true,
2433
+ ...(bloom?.quality !== undefined ? { quality: bloom.quality } : {})
1885
2434
  },
1886
2435
  shadows: {
1887
2436
  // Never a source-authored `true`. When the runtime is mounted this is the
@@ -1896,7 +2445,8 @@ function createRendererDiagnosticReport(snapshot, runtime, rendererOptions) {
1896
2445
  nativeShadowMapBindings: runtime?.shadow?.nativeShadowMapBindings ?? 0,
1897
2446
  shadowRenderTargetsAllocated: runtime?.shadow?.shadowRenderTargetsAllocated ?? 0,
1898
2447
  contactShadows,
1899
- mapType: "pcf-soft"
2448
+ mapType: "pcf-soft",
2449
+ ...(runtime?.shadow?.spot === undefined ? {} : { spot: runtime.shadow.spot })
1900
2450
  },
1901
2451
  occlusion: {
1902
2452
  enabled: ambientOcclusion || contactOcclusion,
@@ -1911,7 +2461,9 @@ function createRendererDiagnosticReport(snapshot, runtime, rendererOptions) {
1911
2461
  fog: {
1912
2462
  enabled: Boolean(fog),
1913
2463
  density: fog?.density ?? 0,
1914
- 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"
1915
2467
  },
1916
2468
  postprocess: {
1917
2469
  enabled: runtimePostprocess?.pixelBacked ?? false,
@@ -1927,6 +2479,7 @@ function createRendererDiagnosticReport(snapshot, runtime, rendererOptions) {
1927
2479
  actualPasses: runtimePostprocess?.actualPasses ?? [],
1928
2480
  fallbackPasses: runtimePostprocess?.fallbackPasses ?? [],
1929
2481
  targetFormat: runtimePostprocess?.targetFormat,
2482
+ executionMode: runtimePostprocess?.executionMode ?? "unknown",
1930
2483
  evidence: postprocessEvidence
1931
2484
  },
1932
2485
  runtime: {
@@ -1940,13 +2493,39 @@ function createRendererDiagnosticReport(snapshot, runtime, rendererOptions) {
1940
2493
  visibleObjects: runtime?.deviceDiagnostics?.visibleObjects ?? 0,
1941
2494
  culledObjects: runtime?.deviceDiagnostics?.culledObjects ?? 0,
1942
2495
  frustumTestedObjects: runtime?.deviceDiagnostics?.frustumTestedObjects ?? 0,
1943
- 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 ?? []
1944
2501
  },
1945
2502
  environment: {
1946
2503
  enabled: environmentStatus.enabled,
1947
2504
  preset: environmentStatus.preset,
1948
2505
  intensity: environmentStatus.intensity,
1949
- 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: []
1950
2529
  },
1951
2530
  antialiasing: rendererQualityPresets.screenshot.antialiasing,
1952
2531
  screenshotQuality: rendererQualityPresets.screenshot,
@@ -1954,14 +2533,31 @@ function createRendererDiagnosticReport(snapshot, runtime, rendererOptions) {
1954
2533
  warnings
1955
2534
  };
1956
2535
  }
1957
- function requestedRendererPostProcessPasses(hasBloom, ambientOcclusion, contactOcclusion) {
1958
- 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)
1959
2539
  return [];
1960
2540
  const passes = ["render"];
1961
2541
  if (ambientOcclusion || contactOcclusion)
1962
2542
  passes.push("ssao");
1963
2543
  if (hasBloom)
1964
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)");
1965
2561
  passes.push("output");
1966
2562
  return passes;
1967
2563
  }
@@ -6374,6 +6970,13 @@ export function createRuntimeNodeImportedAssetEvidence(input) {
6374
6970
  renderItemCount: input.renderItemCount ?? 0,
6375
6971
  skinnedRenderItemCount: input.skinnedRenderItemCount ?? 0,
6376
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 } : {}),
6377
6980
  diagnostics
6378
6981
  };
6379
6982
  }
@@ -6739,7 +7342,9 @@ export function createAuraApp(target, options) {
6739
7342
  appPhysicsWorldInstance ??= new PhysicsWorld({
6740
7343
  gravity: options.physics?.gravity ? [...options.physics.gravity] : [0, -9.81, 0],
6741
7344
  fixedDelta: 1 / 60,
6742
- enableSleeping: true
7345
+ enableSleeping: true,
7346
+ ...(options.physics?.seed === undefined ? {} : { seed: options.physics.seed }),
7347
+ ...(options.physics?.continuousCollision ? { continuousCollision: options.physics.continuousCollision } : {})
6743
7348
  });
6744
7349
  return appPhysicsWorldInstance;
6745
7350
  };
@@ -7707,6 +8312,13 @@ pausedRenderTime = () => 0) {
7707
8312
  labelLayer.setOcclusionTest(createSceneLabelOcclusionTest(snapshot, resolveCameraFrame(snapshot, snapshot.camera, renderTime, runtimeNodes).eye, runtimeNodes));
7708
8313
  labelLayer.update(renderer.viewProjection(renderTime));
7709
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
+ });
7710
8322
  }
7711
8323
  overlay?.update();
7712
8324
  if ((sceneWantsFrames || requiresFrames()) && options.autoStart !== false && typeof requestAnimationFrame !== "undefined") {
@@ -7804,16 +8416,37 @@ function createProductionRuntimeEnvironment(snapshot) {
7804
8416
  "product-hero": "softbox",
7805
8417
  "night-cinematic": "evening",
7806
8418
  "metal-studio": "exhibit",
7807
- "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"
7808
8423
  };
7809
8424
  const preset = presetByEnvironment[authoredEnvironment.environment];
7810
8425
  const bundle = createExternalParityEnvironmentLighting(preset);
7811
8426
  const intensity = nonNegativeFinite(authoredEnvironment.intensity);
7812
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;
7813
8441
  return {
7814
8442
  preset: authoredEnvironment.environment,
7815
8443
  intensity,
7816
- 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 } : {}),
7817
8450
  lighting: {
7818
8451
  ...bundle.lighting,
7819
8452
  color: authoredEnvironment.color ? colorToLinearRgb(authoredEnvironment.color) : bundle.lighting.color,
@@ -7868,14 +8501,16 @@ function createProductionRuntimeEnvironment(snapshot) {
7868
8501
  * changed exactly zero pixels through `createAuraApp`. Returning `false` when no
7869
8502
  * fog node is authored keeps the unfogged path unchanged.
7870
8503
  */
7871
- function createProductionRuntimeEnvironmentFog(snapshot) {
7872
- 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");
7873
8508
  if (!fog)
7874
8509
  return false;
7875
8510
  const density = clampNumber(fog.density ?? 0.12, 0, 1);
7876
8511
  const intensity = clampNumber(fog.intensity ?? 0.5, 0, 1);
7877
8512
  const color = colorToLinearRgb(fog.color ?? "#9fb7d9");
7878
- return {
8513
+ const base = {
7879
8514
  // Exponential-squared reads as depth haze rather than a hard linear band, which
7880
8515
  // matches what the public helper documents.
7881
8516
  mode: "exponential-squared",
@@ -7885,10 +8520,46 @@ function createProductionRuntimeEnvironmentFog(snapshot) {
7885
8520
  density,
7886
8521
  maxOpacity: clampNumber(0.25 + intensity * 0.55, 0, 0.92)
7887
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
+ };
7888
8545
  }
7889
- function createProductionRuntimePostprocess(snapshot) {
8546
+ function createProductionRuntimePostprocess(snapshot, lights = [], renderWidth = 1280, renderHeight = 720) {
7890
8547
  const nodes = groups.flatten(snapshot.nodes);
7891
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;
7892
8563
  const bloomRequested = Boolean(authoredBloom);
7893
8564
  // Root previously advertised `ssao` in `requestedPasses` whenever a scene added
7894
8565
  // effects.ambientOcclusion() or effects.contactOcclusion(), but never submitted
@@ -7898,6 +8569,17 @@ function createProductionRuntimePostprocess(snapshot) {
7898
8569
  const authoredAmbientOcclusion = nodes.find((node) => node.kind === "effect" && node.effect === "ambient-occlusion");
7899
8570
  const authoredContactOcclusion = nodes.find((node) => node.kind === "effect" && node.effect === "contact-occlusion");
7900
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");
7901
8583
  return {
7902
8584
  // Tone mapping requires unclamped linear input. RGBA8 quantized dark clear
7903
8585
  // colors and clipped highlights before ACES, which produced washed-out output.
@@ -7906,7 +8588,10 @@ function createProductionRuntimePostprocess(snapshot) {
7906
8588
  bloom: {
7907
8589
  threshold: clampNumber(authoredBloom?.threshold ?? 0.78, 0, 1),
7908
8590
  intensity: clampNumber(authoredBloom?.intensity ?? 0.3, 0, 2),
7909
- 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 } : {})
7910
8595
  }
7911
8596
  } : {}),
7912
8597
  ...(authoredOcclusion ? {
@@ -7927,7 +8612,41 @@ function createProductionRuntimePostprocess(snapshot) {
7927
8612
  operator: "aces",
7928
8613
  inputColorSpace: "linear",
7929
8614
  outputColorSpace: "srgb"
7930
- }
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 } : {})
7931
8650
  };
7932
8651
  }
7933
8652
  function createProductionRuntimeShadowOptions(snapshot, collectedLights) {
@@ -7980,6 +8699,79 @@ function createProductionRuntimeShadowObservation(shadowOptions, diagnostics) {
7980
8699
  shadowRenderTargetsAllocated: diagnostics.shadowRenderTargetsAllocated ?? 0
7981
8700
  };
7982
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
+ }
7983
8775
  function createProductionRuntimePostprocessObservation(diagnostics) {
7984
8776
  const actualPasses = diagnostics.postprocessPassNames ?? [];
7985
8777
  const pixelBacked = actualPasses.length > 0 && diagnostics.lastError === null;
@@ -7992,7 +8784,8 @@ function createProductionRuntimePostprocessObservation(diagnostics) {
7992
8784
  pixelBacked,
7993
8785
  actualPasses,
7994
8786
  fallbackPasses: pixelBacked ? [] : ["direct-render"],
7995
- targetFormat: diagnostics.postprocessTargetFormat
8787
+ targetFormat: diagnostics.postprocessTargetFormat,
8788
+ executionMode: diagnostics.postprocessPlan?.executionMode ?? "unknown"
7996
8789
  };
7997
8790
  }
7998
8791
  function createProductionRuntimeCollectedLights(snapshot) {
@@ -8000,16 +8793,26 @@ function createProductionRuntimeCollectedLights(snapshot) {
8000
8793
  const descriptors = authoredLights.flatMap((node, index) => createProductionRuntimeLightDescriptors(node, index));
8001
8794
  if (descriptors.length === 0)
8002
8795
  return createProductionRuntimeFallbackLights();
8003
- 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) => {
8004
8806
  if (selected < 0)
8005
8807
  return index;
8006
8808
  const current = descriptors[selected];
8007
- if (descriptor.shadowPriority !== current.shadowPriority) {
8008
- 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;
8009
8813
  }
8010
8814
  return descriptor.intensity > current.intensity ? index : selected;
8011
8815
  }, -1);
8012
- return descriptors.map((descriptor, index) => createProductionRuntimeCollectedLight(descriptor, index === shadowCasterIndex));
8013
8816
  }
8014
8817
  function createProductionRuntimeLightDescriptors(node, authoredIndex) {
8015
8818
  const name = node.name?.trim() || `aura-authored-${node.light}-${authoredIndex + 1}`;
@@ -8034,6 +8837,7 @@ function createProductionRuntimeLightDescriptors(node, authoredIndex) {
8034
8837
  spotAngle: 0,
8035
8838
  penumbra: 0,
8036
8839
  shadowPriority: 3,
8840
+ shadowRequested: false,
8037
8841
  authoredLight: node.light
8038
8842
  }];
8039
8843
  }
@@ -8049,12 +8853,33 @@ function createProductionRuntimeLightDescriptors(node, authoredIndex) {
8049
8853
  spotAngle: 0,
8050
8854
  penumbra: 0,
8051
8855
  shadowPriority: 1,
8856
+ shadowRequested: false,
8052
8857
  authoredLight: node.light
8053
8858
  }];
8054
8859
  }
8055
8860
  if (node.light === "studio") {
8056
8861
  return createProductionRuntimeStudioLightDescriptors(node, name, color, intensity, position);
8057
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
+ }
8058
8883
  const width = positiveFinite(node.width, node.light === "softbox" ? 2.4 : 2.2)
8059
8884
  * productionRuntimeLightAxisScale(node.scale, 0);
8060
8885
  const height = positiveFinite(node.height, node.light === "softbox" ? 1.6 : 1.2)
@@ -8073,6 +8898,7 @@ function createProductionRuntimeLightDescriptors(node, authoredIndex) {
8073
8898
  spotAngle: clampNumber(Math.atan2(halfDiagonal, targetDistance), 0.08, Math.PI / 2 - 0.01),
8074
8899
  penumbra: node.light === "softbox" ? 0.78 : 0.42,
8075
8900
  shadowPriority: 2,
8901
+ shadowRequested: false,
8076
8902
  authoredLight: node.light,
8077
8903
  authoredWidth: width,
8078
8904
  authoredHeight: height
@@ -8102,6 +8928,7 @@ function createProductionRuntimeStudioLightDescriptors(node, name, color, intens
8102
8928
  spotAngle: 0,
8103
8929
  penumbra: 0,
8104
8930
  shadowPriority: 3,
8931
+ shadowRequested: false,
8105
8932
  authoredLight: node.light
8106
8933
  },
8107
8934
  {
@@ -8115,6 +8942,7 @@ function createProductionRuntimeStudioLightDescriptors(node, name, color, intens
8115
8942
  spotAngle: 0,
8116
8943
  penumbra: 0,
8117
8944
  shadowPriority: 0,
8945
+ shadowRequested: false,
8118
8946
  authoredLight: node.light
8119
8947
  },
8120
8948
  {
@@ -8128,6 +8956,7 @@ function createProductionRuntimeStudioLightDescriptors(node, name, color, intens
8128
8956
  spotAngle: 0,
8129
8957
  penumbra: 0,
8130
8958
  shadowPriority: 0,
8959
+ shadowRequested: false,
8131
8960
  authoredLight: node.light
8132
8961
  }
8133
8962
  ];
@@ -8147,6 +8976,7 @@ function createProductionRuntimeFallbackLights() {
8147
8976
  spotAngle: 0,
8148
8977
  penumbra: 0,
8149
8978
  shadowPriority: 3,
8979
+ shadowRequested: false,
8150
8980
  authoredLight: "fallback"
8151
8981
  },
8152
8982
  {
@@ -8160,6 +8990,7 @@ function createProductionRuntimeFallbackLights() {
8160
8990
  spotAngle: 0,
8161
8991
  penumbra: 0,
8162
8992
  shadowPriority: 0,
8993
+ shadowRequested: false,
8163
8994
  authoredLight: "fallback"
8164
8995
  },
8165
8996
  {
@@ -8173,6 +9004,7 @@ function createProductionRuntimeFallbackLights() {
8173
9004
  spotAngle: 0,
8174
9005
  penumbra: 0,
8175
9006
  shadowPriority: 0,
9007
+ shadowRequested: false,
8176
9008
  authoredLight: "fallback"
8177
9009
  }
8178
9010
  ];
@@ -8306,6 +9138,7 @@ async function createProductionRuntimeSceneRenderer(canvas, snapshot, rendererOp
8306
9138
  width: canvas.width,
8307
9139
  height: canvas.height,
8308
9140
  ...(node.hiddenNodeNames ? { hiddenNodeNames: node.hiddenNodeNames } : {}),
9141
+ ...(node.role === "primaryWorld" ? { consolidateStaticMeshes: true } : {}),
8309
9142
  ...(node.material?.color ? {
8310
9143
  tint: {
8311
9144
  baseColor: colorToLinearRgba(node.material.color),
@@ -8338,53 +9171,142 @@ async function createProductionRuntimeSceneRenderer(canvas, snapshot, rendererOp
8338
9171
  });
8339
9172
  let latestDeviceDiagnostics = productionRenderer.getDiagnostics();
8340
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;
8341
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
+ });
8342
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
+ }
8343
9219
  const productionRuntimeLights = createProductionRuntimeCollectedLights(snapshot);
8344
9220
  const authoredLightNodes = flattened.filter((node) => node.kind === "light");
8345
9221
  const authoredDirectLightNodes = authoredLightNodes.filter((node) => node.light !== "ambient");
8346
9222
  const authoredAmbientLightCount = authoredLightNodes.length - authoredDirectLightNodes.length;
8347
9223
  const authoredAreaProxyCount = authoredDirectLightNodes.filter((node) => node.light === "rect" || node.light === "softbox").length;
8348
- const buildDiagnostics = () => createRendererDiagnosticReport(snapshot, {
8349
- mounted: true,
8350
- backend: "production-runtime",
8351
- postprocess: createProductionRuntimePostprocessObservation(latestDeviceDiagnostics),
8352
- shadow: createProductionRuntimeShadowObservation(createProductionRuntimeShadowOptions(snapshot, productionRuntimeLights), latestDeviceDiagnostics),
8353
- environment: {
8354
- enabled: true,
8355
- preset: productionEnvironment.preset,
8356
- intensity: productionEnvironment.intensity,
8357
- evidence: productionEnvironment.evidence
8358
- },
8359
- warnings: [
8360
- `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}.`,
8361
- ...(authoredDirectLightNodes.length === 0
8362
- ? ["Production runtime direct-light fallback active: the scene has no authored directional, point, studio, rect, or softbox light."]
8363
- : [`Production runtime derived ${productionRuntimeLights.length} collected direct light${productionRuntimeLights.length === 1 ? "" : "s"} from ${authoredDirectLightNodes.length} authored scene light${authoredDirectLightNodes.length === 1 ? "" : "s"}.`]),
8364
- ...(authoredAmbientLightCount > 0
8365
- ? [`${authoredAmbientLightCount} authored ambient light${authoredAmbientLightCount === 1 ? "" : "s"} remain environment-lighting intent and are not mislabeled as direct CollectedLight entries.`]
8366
- : []),
8367
- ...(authoredAreaProxyCount > 0
8368
- ? [`${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.`]
8369
- : []),
8370
- ...(flattened.some((node) => node.kind === "effect")
8371
- ? ["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."]
8372
- : []),
8373
- ...(productionRenderer.backendSelection.fallback ? [`Production runtime backend fallback: ${productionRenderer.backendSelection.reason}`] : []),
8374
- ...latestFeatures
8375
- .filter((feature) => feature.state !== "supported")
8376
- .map((feature) => `Production runtime feature ${feature.id} is ${feature.state}: ${feature.detail}`),
8377
- ...runtimeWarnings
8378
- ],
8379
- deviceDiagnostics: latestDeviceDiagnostics,
8380
- lodSelections: primitiveEntries
8381
- .filter((entry) => Boolean(entry.node.lod?.levels.length))
8382
- .map((entry) => ({
8383
- nodeName: entry.node.name ?? "unnamed distance LOD",
8384
- levelIndex: entry.currentLodIndex,
8385
- levelName: entry.resources[entry.currentLodIndex]?.name ?? `level-${entry.currentLodIndex}`
8386
- }))
8387
- }, 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
+ };
8388
9310
  return {
8389
9311
  get backend() {
8390
9312
  return productionRenderer.backend;
@@ -8394,7 +9316,8 @@ async function createProductionRuntimeSceneRenderer(canvas, snapshot, rendererOp
8394
9316
  },
8395
9317
  render(time) {
8396
9318
  runtimeWarnings.clear();
8397
- 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);
8398
9321
  const result = productionRenderer.renderInteractiveFrame(input);
8399
9322
  latestDeviceDiagnostics = result.diagnostics;
8400
9323
  latestFeatures = result.features;
@@ -8416,13 +9339,15 @@ async function createProductionRuntimeSceneRenderer(canvas, snapshot, rendererOp
8416
9339
  return productionRenderer.deviceLost();
8417
9340
  },
8418
9341
  dispose() {
9342
+ disposeHdriEnvironment?.();
8419
9343
  productionRenderer.dispose();
8420
9344
  for (const { actor } of actorEntries)
8421
- actor.pipeline.dispose();
9345
+ actor.dispose();
8422
9346
  for (const { resources } of primitiveEntries) {
8423
- for (const { geometry, material } of resources) {
9347
+ for (const { geometry, material, texturedMaterial } of resources) {
8424
9348
  geometry.dispose();
8425
9349
  material.dispose();
9350
+ texturedMaterial?.dispose();
8426
9351
  }
8427
9352
  }
8428
9353
  }
@@ -8437,23 +9362,91 @@ function createProductionRuntimeRendererInput(snapshot, canvas, actorEntries, pr
8437
9362
  const currentNode = currentState.node;
8438
9363
  if (currentNode.visible === false)
8439
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);
8440
9369
  applyProductionActorAnimation(entry, currentNode, currentState.animationBinding, time, runtimeWarnings);
8441
9370
  applyProductionActorMorphTargets(entry, currentState.morphTargets, runtimeWarnings);
8442
- const modelMatrix = [...createModelMatrix(currentNode, productionActorModelBounds(currentNode.asset, entry.actor), shouldNormalizeModelNode(currentNode), time)];
8443
- const actorItems = entry.actor.collectRenderItems({ modelMatrix });
8444
- items.push(...actorItems);
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 })));
8445
9407
  attachProductionActorEvidence(currentNode, entry.actor, actorItems, runtimeNodes);
8446
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;
8447
9415
  for (const entry of primitiveEntries) {
8448
9416
  const currentState = resolveProductionPrimitiveRuntimeState(entry, runtimeNodes);
8449
9417
  if (!currentState.visible)
8450
9418
  continue;
8451
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
+ }
8452
9444
  items.push({
8453
9445
  geometry: resource.geometry,
8454
- material: resource.material,
9446
+ material: resource.texturedMaterial ?? resource.material,
8455
9447
  modelMatrix: createModelMatrix(currentState.node, resource.bounds, false, time),
8456
9448
  label: currentState.node.name ?? `aura-primitive-${currentState.node.primitive}`,
9449
+ castShadow: currentState.node.castShadow,
8457
9450
  includeInAutoFrame: false,
8458
9451
  ...(currentState.node.instances ? { instanceTransforms: createProductionInstanceTransforms(currentState.node.instances, currentState.node), instanceColors: createProductionInstanceColors(currentState.node.instanceColors, currentState.node.instances.length) } : {})
8459
9452
  });
@@ -8465,9 +9458,12 @@ function createProductionRuntimeRendererInput(snapshot, canvas, actorEntries, pr
8465
9458
  frustumCulling: true,
8466
9459
  collectedLights,
8467
9460
  environmentLighting,
8468
- postprocess: createProductionRuntimePostprocess(snapshot),
9461
+ // The production runtime owns the pixel-backed HDR target and pass chain for
9462
+ // routes that request effects. The diagnostics are device-observed, so a
9463
+ // compositor failure is reported as fallback rather than claimed as a pass.
9464
+ postprocess: createProductionRuntimePostprocess(snapshot, collectedLights, canvas.width, canvas.height),
8469
9465
  shadow: createProductionRuntimeShadowOptions(snapshot, collectedLights),
8470
- environmentFog: createProductionRuntimeEnvironmentFog(snapshot),
9466
+ environmentFog: createProductionRuntimeEnvironmentFog(snapshot, collectedLights, canvas.width, canvas.height),
8471
9467
  cameraPosition
8472
9468
  };
8473
9469
  const cameraLike = { viewProjectionMatrix };
@@ -8482,10 +9478,505 @@ function createProductionRuntimePrimitiveEntries(nodes) {
8482
9478
  .filter((node) => node.kind === "primitive")
8483
9479
  .map((node) => ({ node, resources: createProductionPrimitiveResources(node), currentLodIndex: 0 }));
8484
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
+ }
8485
9960
  function createProductionPrimitiveResources(node) {
9961
+ const sdfWarnings = [];
9962
+ const sdfResource = createSdfTextPrimitiveResource(node, (message) => { sdfWarnings.push(message); });
8486
9963
  const levels = node.lod?.levels;
8487
9964
  if (!levels?.length) {
8488
- 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];
8489
9980
  }
8490
9981
  return levels.map((level, index) => {
8491
9982
  if (!level.primitive && !level.geometry)
@@ -8497,7 +9988,21 @@ function createProductionPrimitiveResources(node) {
8497
9988
  material: level.material ?? node.material,
8498
9989
  lod: undefined
8499
9990
  };
8500
- 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;
8501
10006
  });
8502
10007
  }
8503
10008
  function selectProductionPrimitiveResource(entry, node, cameraPosition) {
@@ -8509,6 +10014,14 @@ function selectProductionPrimitiveResource(entry, node, cameraPosition) {
8509
10014
  entry.currentLodIndex = selection.levelIndex;
8510
10015
  return entry.resources[selection.levelIndex] ?? entry.resources[entry.resources.length - 1];
8511
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
+ }
8512
10025
  function createProductionInstanceTransforms(transforms, node) {
8513
10026
  const matrices = new Float32Array(transforms.length * 16);
8514
10027
  transforms.forEach((transform, index) => {
@@ -8544,37 +10057,7 @@ function resolveProductionPrimitiveRuntimeState(entry, runtimeNodes) {
8544
10057
  visible: runtimeSnapshot.visible
8545
10058
  };
8546
10059
  }
8547
- /**
8548
- * Build the production `PBRMaterial` for a primitive node.
8549
- *
8550
- * ## WS-2.1a — the parameter drop this function used to be
8551
- *
8552
- * Until 1.6 this forwarded `clearcoat` and **nothing else** from the extended material surface.
8553
- * `AuraMaterialSpec` accepts `sheen`, `sheenRoughness`, `sheenColor`, `iridescence`,
8554
- * `iridescenceIOR`, `iridescenceThicknessRange`, `anisotropy`, `anisotropyRotation`, `transmission`,
8555
- * `thickness`, `ior`, `attenuationColor` and `attenuationDistance`; `PBRMaterial` accepts a uniform
8556
- * for every one of them and binds them all. The bridge between the two dropped them on the floor.
8557
- *
8558
- * How that presented, measured by `tools/material-structural-parity` before the fix:
8559
- *
8560
- * ```
8561
- * material.pbr({ ..., sheen: 1 }) -> byte-identical frame to sheen: 0
8562
- * material.pbr({ ..., iridescence: 1 }) -> byte-identical frame to iridescence: 0
8563
- * material.pbr({ ..., clearcoat: 1 }) -> byte-identical frame to clearcoat: 0
8564
- * ```
8565
- *
8566
- * `clearcoat` was *forwarded* and still produced an identical frame, which pins the second, separate
8567
- * defect: `a3dPbrExtensionEnvironmentLight` in `ShaderChunks.ts` adds its lobes to
8568
- * `specularRadiance`, a term that is zero without an environment map. Forwarding alone is necessary
8569
- * and not sufficient, so `environmentIntensity` now also has a floor when an extension factor is
8570
- * present — otherwise a developer setting `clearcoat: 1` on a scene with no environment sees nothing
8571
- * and has no way to find out why.
8572
- *
8573
- * The reason this survived so long is worth stating: three of the four lobes are scalar
8574
- * approximations rather than real BRDFs, and **nobody could tell**, because the parameters never
8575
- * arrived. The plumbing defect concealed the shading defect.
8576
- */
8577
- function createProductionPrimitiveMaterial(node) {
10060
+ function resolveProductionPrimitiveScalars(node) {
8578
10061
  const materialSpec = node.material;
8579
10062
  const baseColor = colorToLinearRgba(materialSpec?.color ?? materialSpec?.emissive ?? "#d7dee8");
8580
10063
  const opacity = clamp01(materialSpec?.opacity ?? baseColor[3] ?? 1);
@@ -8605,6 +10088,17 @@ function createProductionPrimitiveMaterial(node) {
8605
10088
  const environmentIntensity = declaresExtension
8606
10089
  ? Math.max(0.35, requestedEnvironmentIntensity)
8607
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);
8608
10102
  /*
8609
10103
  * Native instance submission requires the renderer's instanced shader contract. The ordinary
8610
10104
  * PBR shader intentionally has no instance matrix inputs and ForwardPass therefore expands it.
@@ -8622,6 +10116,7 @@ function createProductionPrimitiveMaterial(node) {
8622
10116
  emissiveColor,
8623
10117
  emissiveStrength: Math.max(0, materialSpec?.emissiveIntensity ?? (materialSpec?.emissive ? 1.35 : 0)),
8624
10118
  environmentIntensity,
10119
+ envMapIntensity,
8625
10120
  renderState: {
8626
10121
  blend: opacity < 0.999,
8627
10122
  depthWrite: opacity >= 0.999,
@@ -8652,6 +10147,7 @@ function createProductionPrimitiveMaterial(node) {
8652
10147
  ...(materialSpec?.attenuationColor ? { volumeAttenuationColor: colorToLinearRgb(materialSpec.attenuationColor) } : {}),
8653
10148
  ...(materialSpec?.attenuationDistance === undefined ? {} : { volumeAttenuationDistance: Math.max(0, materialSpec.attenuationDistance) }),
8654
10149
  environmentIntensity,
10150
+ envMapIntensity,
8655
10151
  renderState: {
8656
10152
  blend: opacity < 0.999,
8657
10153
  depthWrite: opacity >= 0.999,
@@ -8674,6 +10170,12 @@ function primitiveGeometryBounds(nodeOrPrimitive) {
8674
10170
  throw new Error("Aura3D custom primitive requires geometry.");
8675
10171
  return createProductionPrimitiveMesh(primitive).bounds;
8676
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
+ }
8677
10179
  function createProductionPrimitiveMesh(primitive) {
8678
10180
  if (primitive === "sphere")
8679
10181
  return createSphereGeometry();
@@ -8725,11 +10227,24 @@ function calculateCustomGeometryNormals(spec) {
8725
10227
  }
8726
10228
  function createProductionGeometryFromPrimitiveMesh(mesh) {
8727
10229
  const vertexCount = mesh.positions.length / 3;
8728
- 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);
8729
10233
  for (let index = 0; index < vertexCount; index += 1) {
8730
10234
  const base = index * 3;
8731
10235
  vertices.setAttribute(index, "position", [mesh.positions[base] ?? 0, mesh.positions[base + 1] ?? 0, mesh.positions[base + 2] ?? 0]);
8732
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
+ }
8733
10248
  }
8734
10249
  return new Geometry(vertices, new IndexBuffer(Array.from(mesh.indices), vertexCount), "triangles", mesh.bounds);
8735
10250
  }
@@ -8770,6 +10285,39 @@ function applyProductionActorAnimation(entry, node, animationBinding, time, runt
8770
10285
  runtimeWarnings.add(`Typed GLB actor "${entry.actor.id}" failed to apply clip "${clipName}": ${productionRenderErrorMessage(error)}`);
8771
10286
  }
8772
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
+ }
8773
10321
  function applyProductionActorMorphTargets(entry, morphTargets, runtimeWarnings) {
8774
10322
  if (!morphTargets || Object.keys(morphTargets).length === 0)
8775
10323
  return;
@@ -8817,7 +10365,14 @@ function createRuntimeEvidenceFromTypedGLBActor(node, actor, renderItems) {
8817
10365
  },
8818
10366
  renderItemCount: renderItems.length,
8819
10367
  skinnedRenderItemCount: renderItems.filter((item) => item.skinning).length,
8820
- 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
8821
10376
  });
8822
10377
  }
8823
10378
  function createProductionRuntimeMetadata(actorEntries, primitiveEntries) {
@@ -9130,7 +10685,12 @@ function findRuntimeCameraTarget(cameraSpec, runtimeNodes) {
9130
10685
  return undefined;
9131
10686
  const targetNode = cameraSpec.targetNode;
9132
10687
  const directHandle = runtimeNodes.get(targetNode);
9133
- if (directHandle && directHandle.visible !== false)
10688
+ // An explicitly named target remains a valid camera anchor while hidden.
10689
+ // Composition probes temporarily suppress the hero node to isolate its
10690
+ // pixels; dropping that target here would also drop target-yaw rotation and
10691
+ // move the camera between the visible and suppressed captures. Visibility
10692
+ // controls drawing, not whether the camera can follow the requested node.
10693
+ if (directHandle)
9134
10694
  return { position: directHandle.position, rotation: directHandle.rotation };
9135
10695
  const namedHandle = runtimeNodes.all().find((handle) => handle.visible !== false && (handle.name === targetNode || handle.tags.includes(targetNode)));
9136
10696
  return namedHandle ? { position: namedHandle.position, rotation: namedHandle.rotation } : undefined;
@@ -9257,7 +10817,9 @@ function collectRuntimeEffectNodes(snapshot) {
9257
10817
  return groups.flatten(snapshot.nodes).filter((node) => node.kind === "effect");
9258
10818
  }
9259
10819
  function hasRuntimePostProcessEffects(effectNodes) {
9260
- 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");
9261
10823
  }
9262
10824
  async function createWebGLSceneRenderer(canvas, snapshot, rendererOptions, runtimeWarnings = [], runtimeNodes) {
9263
10825
  const gl = canvas.getContext("webgl2", { antialias: true, preserveDrawingBuffer: true });
@@ -9293,7 +10855,8 @@ async function createWebGLSceneRenderer(canvas, snapshot, rendererOptions, runti
9293
10855
  contactOcclusionReceiver: false,
9294
10856
  pixelBacked: false,
9295
10857
  actualPasses: [],
9296
- fallbackPasses: hasRuntimePostProcessEffects(requestedEffectNodes) ? ["webgl2-direct-render"] : []
10858
+ fallbackPasses: hasRuntimePostProcessEffects(requestedEffectNodes) ? ["webgl2-direct-render"] : [],
10859
+ executionMode: "none"
9297
10860
  },
9298
10861
  warnings: [
9299
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.",
@@ -10529,6 +12092,8 @@ function textureRef(texture, uvs, key) {
10529
12092
  return { [key]: texture.index };
10530
12093
  }
10531
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]);
10532
12097
  return {
10533
12098
  positions: new Float32Array([
10534
12099
  -0.5, 0, -0.5,
@@ -10549,10 +12114,21 @@ function createPlaneGeometry() {
10549
12114
  // up-normal downward. The plane then faced away from every overhead light and
10550
12115
  // received no direct lighting or visible cast shadows.
10551
12116
  indices: new Uint16Array([0, 2, 1, 0, 3, 2]),
10552
- 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
10553
12121
  };
10554
12122
  }
10555
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]));
10556
12132
  const positions = new Float32Array([
10557
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,
10558
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,
@@ -10580,7 +12156,10 @@ function createBoxGeometry() {
10580
12156
  16, 17, 18, 16, 18, 19,
10581
12157
  20, 21, 22, 20, 22, 23
10582
12158
  ]),
10583
- 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
10584
12163
  };
10585
12164
  }
10586
12165
  function createSphereGeometry() {
@@ -10588,6 +12167,8 @@ function createSphereGeometry() {
10588
12167
  const columns = 16;
10589
12168
  const positions = [];
10590
12169
  const normals = [];
12170
+ const uvs = [];
12171
+ const tangents = [];
10591
12172
  const indices = [];
10592
12173
  for (let row = 0; row <= rows; row += 1) {
10593
12174
  const v = row / rows;
@@ -10600,6 +12181,8 @@ function createSphereGeometry() {
10600
12181
  const z = Math.sin(theta) * Math.sin(phi);
10601
12182
  positions.push(x * 0.5, y * 0.5, z * 0.5);
10602
12183
  normals.push(x, y, z);
12184
+ uvs.push(u, 1 - v);
12185
+ tangents.push(-Math.sin(phi), 0, Math.cos(phi), 1);
10603
12186
  }
10604
12187
  }
10605
12188
  for (let row = 0; row < rows; row += 1) {
@@ -10612,11 +12195,15 @@ function createSphereGeometry() {
10612
12195
  indices.push(a, a + 1, b, b, a + 1, b + 1);
10613
12196
  }
10614
12197
  }
12198
+ const sphereUvs = new Float32Array(uvs);
10615
12199
  return {
10616
12200
  positions: new Float32Array(positions),
10617
12201
  normals: new Float32Array(normals),
10618
12202
  indices: new Uint16Array(indices),
10619
- 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)
10620
12207
  };
10621
12208
  }
10622
12209
  function createCylinderGeometry() {
@@ -11285,8 +12872,33 @@ function validateSceneAssets(snapshot, assets) {
11285
12872
  }
11286
12873
  function createAssetProvenance(asset) {
11287
12874
  const remote = /^https?:\/\//i.test(asset.url);
12875
+ /*
12876
+ * A deployment may rewrite the URL of a generated manifest asset to an
12877
+ * immutable external asset host (for example, GitHub media or an object
12878
+ * store) so the site bundle does not need to carry every GLB. URL location
12879
+ * is therefore not a sufficient trust signal: the manifest identity is the
12880
+ * typed ref's kind/id/hash plus durable source-path provenance. Keep
12881
+ * `unsafeModelUrl(...)` blocked by its sentinel id, and do not bless an
12882
+ * arbitrary remote object that merely happens to carry a hash.
12883
+ */
12884
+ const manifestPaths = [
12885
+ asset.metadata?.provenance?.sourcePath,
12886
+ asset.metadata?.sourcePath,
12887
+ asset.metadata?.outputPath,
12888
+ asset.metadata?.provenance?.outputPath
12889
+ ];
12890
+ const hasDurableManifestProvenance = asset.kind === "aura-asset-ref"
12891
+ && asset.id !== "unsafe"
12892
+ && typeof asset.hash === "string"
12893
+ && asset.hash.length > 0
12894
+ && manifestPaths.some((value) => typeof value === "string" && value.replaceAll("\\", "/").startsWith("public/aura-assets/"));
12895
+ const trustedManifestAsset = !remote || hasDurableManifestProvenance;
11288
12896
  return {
11289
- source: asset.id === "unsafe" || remote ? "unsafe-url" : asset.hash ? "typed-aura-assets-manifest" : "inline-definition",
12897
+ source: asset.id === "unsafe" || !trustedManifestAsset
12898
+ ? "unsafe-url"
12899
+ : asset.hash
12900
+ ? "typed-aura-assets-manifest"
12901
+ : "inline-definition",
11290
12902
  id: asset.id,
11291
12903
  url: asset.url,
11292
12904
  ...(asset.hash ? { hash: asset.hash } : {}),
@@ -11325,7 +12937,9 @@ function snapshotDiagnostics(value) {
11325
12937
  renderer: value.renderer,
11326
12938
  warnings: [...value.warnings],
11327
12939
  errors: [...value.errors],
11328
- ...(value.labels ? { labels: value.labels } : {})
12940
+ ...(value.labels ? { labels: value.labels } : {}),
12941
+ ...(value.labelTelemetry ? { labelTelemetry: value.labelTelemetry } : {}),
12942
+ ...(value.textBuckets ? { textBuckets: value.textBuckets } : {})
11329
12943
  };
11330
12944
  }
11331
12945
  function collectGeneratedCodeWarnings(snapshot) {
@@ -11336,6 +12950,27 @@ function collectGeneratedCodeWarnings(snapshot) {
11336
12950
  if (!snapshot.nodes.some((node) => node.kind === "interaction")) {
11337
12951
  warnings.push("Scene has no interactions. Suggested fix: add interactions.orbit() for product/viewer scenes.");
11338
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
+ }
11339
12974
  for (const node of snapshot.nodes) {
11340
12975
  if (node.kind === "model" && createAssetProvenance(node.asset).source === "unsafe-url") {
11341
12976
  warnings.push(`Model uses unsafeModelUrl("${node.asset.url}"). Suggested fix: run assets add and use typed assets.`);
@@ -11499,6 +13134,16 @@ function drawEffect(context, width, height, node, time) {
11499
13134
  context.fillStyle = toAlphaColor(node.color ?? "#9fb7d9", node.density ?? 0.12);
11500
13135
  context.fillRect(0, height * 0.2, width, height * 0.8);
11501
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
+ }
11502
13147
  if (node.effect === "bloom") {
11503
13148
  const gradient = context.createRadialGradient(width * 0.5, height * 0.45, 20, width * 0.5, height * 0.45, width * 0.46);
11504
13149
  gradient.addColorStop(0, toAlphaColor(node.color ?? "#ffffff", (node.intensity ?? 0.35) * 0.3));
@@ -11551,6 +13196,29 @@ function drawEffect(context, width, height, node, time) {
11551
13196
  }
11552
13197
  }
11553
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
+ }
11554
13222
  if (node.effect === "particles") {
11555
13223
  const isFountain = node.emitter === "fountain";
11556
13224
  // Fountain cap matches prefabs.particleFountain's 2400 maximum so rendered counts never undercut the scene JSON.