@aura3d/engine 1.3.3 → 1.4.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 (229) hide show
  1. package/README.md +122 -15
  2. package/dist/assets/GLTFLoader.js +1 -0
  3. package/dist/assets/GLTFLoader.js.map +1 -1
  4. package/dist/assets/GLTFRenderResources.d.ts.map +1 -1
  5. package/dist/assets/GLTFRenderResources.js +1 -0
  6. package/dist/assets/GLTFRenderResources.js.map +1 -1
  7. package/dist/aura3d-cli/asset-constants.d.ts +5 -0
  8. package/dist/aura3d-cli/asset-constants.d.ts.map +1 -0
  9. package/dist/aura3d-cli/asset-constants.js +5 -0
  10. package/dist/aura3d-cli/asset-constants.js.map +1 -0
  11. package/dist/aura3d-cli/asset-core-types.d.ts +206 -0
  12. package/dist/aura3d-cli/asset-core-types.d.ts.map +1 -0
  13. package/dist/aura3d-cli/asset-core-types.js +2 -0
  14. package/dist/aura3d-cli/asset-core-types.js.map +1 -0
  15. package/dist/aura3d-cli/asset-inspection-types.d.ts +131 -0
  16. package/dist/aura3d-cli/asset-inspection-types.d.ts.map +1 -0
  17. package/dist/aura3d-cli/asset-inspection-types.js +2 -0
  18. package/dist/aura3d-cli/asset-inspection-types.js.map +1 -0
  19. package/dist/aura3d-cli/asset-manifest.d.ts +8 -0
  20. package/dist/aura3d-cli/asset-manifest.d.ts.map +1 -0
  21. package/dist/aura3d-cli/asset-manifest.js +116 -0
  22. package/dist/aura3d-cli/asset-manifest.js.map +1 -0
  23. package/dist/aura3d-cli/asset-readiness-types.d.ts +164 -0
  24. package/dist/aura3d-cli/asset-readiness-types.d.ts.map +1 -0
  25. package/dist/aura3d-cli/asset-readiness-types.js +2 -0
  26. package/dist/aura3d-cli/asset-readiness-types.js.map +1 -0
  27. package/dist/aura3d-cli/asset-source-ast.d.ts +8 -0
  28. package/dist/aura3d-cli/asset-source-ast.d.ts.map +1 -0
  29. package/dist/aura3d-cli/asset-source-ast.js +241 -0
  30. package/dist/aura3d-cli/asset-source-ast.js.map +1 -0
  31. package/dist/aura3d-cli/asset-source-roles.d.ts +4 -0
  32. package/dist/aura3d-cli/asset-source-roles.d.ts.map +1 -0
  33. package/dist/aura3d-cli/asset-source-roles.js +79 -0
  34. package/dist/aura3d-cli/asset-source-roles.js.map +1 -0
  35. package/dist/aura3d-cli/asset-source-validation.d.ts +4 -0
  36. package/dist/aura3d-cli/asset-source-validation.d.ts.map +1 -0
  37. package/dist/aura3d-cli/asset-source-validation.js +200 -0
  38. package/dist/aura3d-cli/asset-source-validation.js.map +1 -0
  39. package/dist/aura3d-cli/character-assembly-types.d.ts +39 -0
  40. package/dist/aura3d-cli/character-assembly-types.d.ts.map +1 -0
  41. package/dist/aura3d-cli/character-assembly-types.js +2 -0
  42. package/dist/aura3d-cli/character-assembly-types.js.map +1 -0
  43. package/dist/aura3d-cli/cli-help.d.ts +4 -0
  44. package/dist/aura3d-cli/cli-help.d.ts.map +1 -0
  45. package/dist/aura3d-cli/cli-help.js +48 -0
  46. package/dist/aura3d-cli/cli-help.js.map +1 -0
  47. package/dist/aura3d-cli/cli-options.d.ts +66 -0
  48. package/dist/aura3d-cli/cli-options.d.ts.map +1 -0
  49. package/dist/aura3d-cli/cli-options.js +234 -0
  50. package/dist/aura3d-cli/cli-options.js.map +1 -0
  51. package/dist/aura3d-cli/cli.js +20 -177
  52. package/dist/aura3d-cli/cli.js.map +1 -1
  53. package/dist/aura3d-cli/index.d.ts +12 -402
  54. package/dist/aura3d-cli/index.d.ts.map +1 -1
  55. package/dist/aura3d-cli/index.js +948 -90
  56. package/dist/aura3d-cli/index.js.map +1 -1
  57. package/dist/aura3d-cli/pull-bridge/adapters.d.ts +4 -0
  58. package/dist/aura3d-cli/pull-bridge/adapters.d.ts.map +1 -0
  59. package/dist/aura3d-cli/pull-bridge/adapters.js +38 -0
  60. package/dist/aura3d-cli/pull-bridge/adapters.js.map +1 -0
  61. package/dist/aura3d-cli/pull-bridge/download.d.ts +6 -0
  62. package/dist/aura3d-cli/pull-bridge/download.d.ts.map +1 -0
  63. package/dist/aura3d-cli/pull-bridge/download.js +208 -0
  64. package/dist/aura3d-cli/pull-bridge/download.js.map +1 -0
  65. package/dist/aura3d-cli/pull-bridge/profiles.d.ts +14 -0
  66. package/dist/aura3d-cli/pull-bridge/profiles.d.ts.map +1 -0
  67. package/dist/aura3d-cli/pull-bridge/profiles.js +63 -0
  68. package/dist/aura3d-cli/pull-bridge/profiles.js.map +1 -0
  69. package/dist/aura3d-cli/pull-bridge/provenance.d.ts +9 -0
  70. package/dist/aura3d-cli/pull-bridge/provenance.d.ts.map +1 -0
  71. package/dist/aura3d-cli/pull-bridge/provenance.js +99 -0
  72. package/dist/aura3d-cli/pull-bridge/provenance.js.map +1 -0
  73. package/dist/aura3d-cli/pull-bridge/pullable.d.ts +14 -0
  74. package/dist/aura3d-cli/pull-bridge/pullable.d.ts.map +1 -0
  75. package/dist/aura3d-cli/pull-bridge/pullable.js +58 -0
  76. package/dist/aura3d-cli/pull-bridge/pullable.js.map +1 -0
  77. package/dist/aura3d-cli/pull-bridge/scoring.d.ts +21 -0
  78. package/dist/aura3d-cli/pull-bridge/scoring.d.ts.map +1 -0
  79. package/dist/aura3d-cli/pull-bridge/scoring.js +177 -0
  80. package/dist/aura3d-cli/pull-bridge/scoring.js.map +1 -0
  81. package/dist/aura3d-cli/pull-bridge/search.d.ts +39 -0
  82. package/dist/aura3d-cli/pull-bridge/search.d.ts.map +1 -0
  83. package/dist/aura3d-cli/pull-bridge/search.js +114 -0
  84. package/dist/aura3d-cli/pull-bridge/search.js.map +1 -0
  85. package/dist/aura3d-cli/pull-bridge/types.d.ts +9 -0
  86. package/dist/aura3d-cli/pull-bridge/types.d.ts.map +1 -0
  87. package/dist/aura3d-cli/pull-bridge/types.js +2 -0
  88. package/dist/aura3d-cli/pull-bridge/types.js.map +1 -0
  89. package/dist/aura3d-cli/pull-bridge/vector3.d.ts +2 -0
  90. package/dist/aura3d-cli/pull-bridge/vector3.d.ts.map +1 -0
  91. package/dist/aura3d-cli/pull-bridge/vector3.js +4 -0
  92. package/dist/aura3d-cli/pull-bridge/vector3.js.map +1 -0
  93. package/dist/aura3d-cli/pull-bridge.d.ts +18 -100
  94. package/dist/aura3d-cli/pull-bridge.d.ts.map +1 -1
  95. package/dist/aura3d-cli/pull-bridge.js +59 -526
  96. package/dist/aura3d-cli/pull-bridge.js.map +1 -1
  97. package/dist/create-aura3d/cli.js +10 -0
  98. package/dist/create-aura3d/cli.js.map +1 -1
  99. package/dist/create-aura3d/index.d.ts +1 -1
  100. package/dist/create-aura3d/index.d.ts.map +1 -1
  101. package/dist/create-aura3d/index.js +2 -0
  102. package/dist/create-aura3d/index.js.map +1 -1
  103. package/dist/create-aura3d/showcase-spec-artifacts.d.ts +104 -0
  104. package/dist/create-aura3d/showcase-spec-artifacts.d.ts.map +1 -0
  105. package/dist/create-aura3d/showcase-spec-artifacts.js +142 -0
  106. package/dist/create-aura3d/showcase-spec-artifacts.js.map +1 -0
  107. package/dist/create-aura3d/showcase-spec-category-plan.d.ts +15 -0
  108. package/dist/create-aura3d/showcase-spec-category-plan.d.ts.map +1 -0
  109. package/dist/create-aura3d/showcase-spec-category-plan.js +45 -0
  110. package/dist/create-aura3d/showcase-spec-category-plan.js.map +1 -0
  111. package/dist/create-aura3d/showcase-spec-compiler.d.ts +5 -0
  112. package/dist/create-aura3d/showcase-spec-compiler.d.ts.map +1 -0
  113. package/dist/create-aura3d/showcase-spec-compiler.js +240 -0
  114. package/dist/create-aura3d/showcase-spec-compiler.js.map +1 -0
  115. package/dist/create-aura3d/showcase-spec-evidence.d.ts +7 -0
  116. package/dist/create-aura3d/showcase-spec-evidence.d.ts.map +1 -0
  117. package/dist/create-aura3d/showcase-spec-evidence.js +815 -0
  118. package/dist/create-aura3d/showcase-spec-evidence.js.map +1 -0
  119. package/dist/create-aura3d/showcase-spec-game-geometry-extractor.d.ts +21 -0
  120. package/dist/create-aura3d/showcase-spec-game-geometry-extractor.d.ts.map +1 -0
  121. package/dist/create-aura3d/showcase-spec-game-geometry-extractor.js +725 -0
  122. package/dist/create-aura3d/showcase-spec-game-geometry-extractor.js.map +1 -0
  123. package/dist/create-aura3d/showcase-spec-game-replacement-evidence.d.ts +14 -0
  124. package/dist/create-aura3d/showcase-spec-game-replacement-evidence.d.ts.map +1 -0
  125. package/dist/create-aura3d/showcase-spec-game-replacement-evidence.js +219 -0
  126. package/dist/create-aura3d/showcase-spec-game-replacement-evidence.js.map +1 -0
  127. package/dist/create-aura3d/showcase-spec-game-template-evidence.d.ts +72 -0
  128. package/dist/create-aura3d/showcase-spec-game-template-evidence.d.ts.map +1 -0
  129. package/dist/create-aura3d/showcase-spec-game-template-evidence.js +435 -0
  130. package/dist/create-aura3d/showcase-spec-game-template-evidence.js.map +1 -0
  131. package/dist/create-aura3d/showcase-spec-gameplay-evidence.d.ts +3 -0
  132. package/dist/create-aura3d/showcase-spec-gameplay-evidence.d.ts.map +1 -0
  133. package/dist/create-aura3d/showcase-spec-gameplay-evidence.js +108 -0
  134. package/dist/create-aura3d/showcase-spec-gameplay-evidence.js.map +1 -0
  135. package/dist/create-aura3d/showcase-spec-parser.d.ts +3 -0
  136. package/dist/create-aura3d/showcase-spec-parser.d.ts.map +1 -0
  137. package/dist/create-aura3d/showcase-spec-parser.js +662 -0
  138. package/dist/create-aura3d/showcase-spec-parser.js.map +1 -0
  139. package/dist/create-aura3d/showcase-spec-platformer-artifacts.d.ts +3 -0
  140. package/dist/create-aura3d/showcase-spec-platformer-artifacts.d.ts.map +1 -0
  141. package/dist/create-aura3d/showcase-spec-platformer-artifacts.js +276 -0
  142. package/dist/create-aura3d/showcase-spec-platformer-artifacts.js.map +1 -0
  143. package/dist/create-aura3d/showcase-spec-racing-artifacts.d.ts +3 -0
  144. package/dist/create-aura3d/showcase-spec-racing-artifacts.d.ts.map +1 -0
  145. package/dist/create-aura3d/showcase-spec-racing-artifacts.js +273 -0
  146. package/dist/create-aura3d/showcase-spec-racing-artifacts.js.map +1 -0
  147. package/dist/create-aura3d/showcase-spec-replacement-apply.d.ts +10 -0
  148. package/dist/create-aura3d/showcase-spec-replacement-apply.d.ts.map +1 -0
  149. package/dist/create-aura3d/showcase-spec-replacement-apply.js +166 -0
  150. package/dist/create-aura3d/showcase-spec-replacement-apply.js.map +1 -0
  151. package/dist/create-aura3d/showcase-spec-replacement-candidates.d.ts +16 -0
  152. package/dist/create-aura3d/showcase-spec-replacement-candidates.d.ts.map +1 -0
  153. package/dist/create-aura3d/showcase-spec-replacement-candidates.js +244 -0
  154. package/dist/create-aura3d/showcase-spec-replacement-candidates.js.map +1 -0
  155. package/dist/create-aura3d/showcase-spec-replacement-game-geometry.d.ts +4 -0
  156. package/dist/create-aura3d/showcase-spec-replacement-game-geometry.d.ts.map +1 -0
  157. package/dist/create-aura3d/showcase-spec-replacement-game-geometry.js +175 -0
  158. package/dist/create-aura3d/showcase-spec-replacement-game-geometry.js.map +1 -0
  159. package/dist/create-aura3d/showcase-spec-replacement-manifest.d.ts +41 -0
  160. package/dist/create-aura3d/showcase-spec-replacement-manifest.d.ts.map +1 -0
  161. package/dist/create-aura3d/showcase-spec-replacement-manifest.js +62 -0
  162. package/dist/create-aura3d/showcase-spec-replacement-manifest.js.map +1 -0
  163. package/dist/create-aura3d/showcase-spec-replacement-values.d.ts +9 -0
  164. package/dist/create-aura3d/showcase-spec-replacement-values.d.ts.map +1 -0
  165. package/dist/create-aura3d/showcase-spec-replacement-values.js +41 -0
  166. package/dist/create-aura3d/showcase-spec-replacement-values.js.map +1 -0
  167. package/dist/create-aura3d/showcase-spec-replacement.d.ts +13 -0
  168. package/dist/create-aura3d/showcase-spec-replacement.d.ts.map +1 -0
  169. package/dist/create-aura3d/showcase-spec-replacement.js +144 -0
  170. package/dist/create-aura3d/showcase-spec-replacement.js.map +1 -0
  171. package/dist/create-aura3d/showcase-spec-types.d.ts +304 -0
  172. package/dist/create-aura3d/showcase-spec-types.d.ts.map +1 -0
  173. package/dist/create-aura3d/showcase-spec-types.js +2 -0
  174. package/dist/create-aura3d/showcase-spec-types.js.map +1 -0
  175. package/dist/engine/agent-api/GameAssetValidation.d.ts +5 -0
  176. package/dist/engine/agent-api/GameAssetValidation.d.ts.map +1 -1
  177. package/dist/engine/agent-api/GameAssetValidation.js +103 -0
  178. package/dist/engine/agent-api/GameAssetValidation.js.map +1 -1
  179. package/dist/engine/agent-api/GameEvidence.d.ts +14 -1
  180. package/dist/engine/agent-api/GameEvidence.d.ts.map +1 -1
  181. package/dist/engine/agent-api/GameEvidence.js +77 -6
  182. package/dist/engine/agent-api/GameEvidence.js.map +1 -1
  183. package/dist/engine/agent-api/GameGenreKits.d.ts +473 -0
  184. package/dist/engine/agent-api/GameGenreKits.d.ts.map +1 -0
  185. package/dist/engine/agent-api/GameGenreKits.js +1269 -0
  186. package/dist/engine/agent-api/GameGenreKits.js.map +1 -0
  187. package/dist/engine/agent-api/GameRuntime.d.ts +190 -3
  188. package/dist/engine/agent-api/GameRuntime.d.ts.map +1 -1
  189. package/dist/engine/agent-api/GameRuntime.js +431 -2
  190. package/dist/engine/agent-api/GameRuntime.js.map +1 -1
  191. package/dist/engine/agent-api/GameSceneGeometryBindings.d.ts +181 -0
  192. package/dist/engine/agent-api/GameSceneGeometryBindings.d.ts.map +1 -0
  193. package/dist/engine/agent-api/GameSceneGeometryBindings.js +488 -0
  194. package/dist/engine/agent-api/GameSceneGeometryBindings.js.map +1 -0
  195. package/dist/engine/agent-api/GameSceneGeometryMath.d.ts +39 -0
  196. package/dist/engine/agent-api/GameSceneGeometryMath.d.ts.map +1 -0
  197. package/dist/engine/agent-api/GameSceneGeometryMath.js +89 -0
  198. package/dist/engine/agent-api/GameSceneGeometryMath.js.map +1 -0
  199. package/dist/engine/agent-api/RuntimeNodeHandle.d.ts +4 -0
  200. package/dist/engine/agent-api/RuntimeNodeHandle.d.ts.map +1 -1
  201. package/dist/engine/agent-api/RuntimeNodeHandle.js +2 -0
  202. package/dist/engine/agent-api/RuntimeNodeHandle.js.map +1 -1
  203. package/dist/engine/agent-api/SceneGroundingUtils.d.ts +86 -0
  204. package/dist/engine/agent-api/SceneGroundingUtils.d.ts.map +1 -1
  205. package/dist/engine/agent-api/SceneGroundingUtils.js +116 -0
  206. package/dist/engine/agent-api/SceneGroundingUtils.js.map +1 -1
  207. package/dist/engine/agent-api/index.d.ts +149 -5
  208. package/dist/engine/agent-api/index.d.ts.map +1 -1
  209. package/dist/engine/agent-api/index.js +1893 -113
  210. package/dist/engine/agent-api/index.js.map +1 -1
  211. package/dist/engine/agent-api/product-viewer-runtime.d.ts.map +1 -1
  212. package/dist/engine/production-runtime/TypedGLBActor.d.ts +14 -2
  213. package/dist/engine/production-runtime/TypedGLBActor.d.ts.map +1 -1
  214. package/dist/engine/production-runtime/TypedGLBActor.js +95 -4
  215. package/dist/engine/production-runtime/TypedGLBActor.js.map +1 -1
  216. package/dist/rendering/ForwardPass.js +7 -1
  217. package/dist/rendering/ForwardPass.js.map +1 -1
  218. package/dist/rendering/MorphTarget.d.ts +1 -0
  219. package/dist/rendering/MorphTarget.d.ts.map +1 -1
  220. package/dist/rendering/MorphTarget.js.map +1 -1
  221. package/dist/rendering/Renderer.js +7 -1
  222. package/dist/rendering/Renderer.js.map +1 -1
  223. package/package.json +22 -22
  224. package/templates/mini-game/README.md +13 -0
  225. package/templates/mini-game/index.html +1 -1
  226. package/templates/mini-game/package.json +1 -1
  227. package/templates/mini-game/src/main.ts +251 -21
  228. package/templates/mini-game/tests/playable.spec.ts +49 -0
  229. package/templates/mini-game/tests/screenshot.spec.ts +37 -32
@@ -1,4 +1,8 @@
1
1
  import { PhysicsDebugDraw, PhysicsStepper, PhysicsWorld, ScenePhysicsBridge, Shape as PhysicsShapeFactory } from "../../physics/index.js";
2
+ import { createExternalParityEnvironmentLighting, Geometry, IndexBuffer, PBRMaterial, ProductionRuntimeRenderer, VertexBuffer, VertexFormat } from "../../rendering/index.js";
3
+ import { DirectionalLight } from "../../scene/index.js";
4
+ import { createTypedGLBActor } from "../production-runtime/TypedGLBActor.js";
5
+ import { AURA_NORMALIZED_MODEL_MAX_DIMENSION, boundsFromAsset, boundsHeight, boundsMaxDimension, boundsSize } from "./SceneGroundingUtils.js";
2
6
  export * from "./FrameEncoder.js";
3
7
  export * from "./BrowserFrameCaptureAdapter.js";
4
8
  export * from "./MediaRecorderFrameEncoder.js";
@@ -43,11 +47,15 @@ export * from "./AnimationMotionQuality.js";
43
47
  export * from "./AnimationRouteProof.js";
44
48
  export * from "./AnimationEpisodePackage.js";
45
49
  import { createFrameLoop } from "./FrameLoop.js";
46
- import { createCombatWorld, createGameCameraDirector, createGameEffects, applyGameCombatEventsToRuntime, createGameAccessibilityFocus, createGameAccessibilityLabel, createGameAccessibilityRuntimeSettings, createGameHighContrastSource, createGameHudBindings, createGameHudComboBinding, createGameHudDebugToggleBinding, createGameHudHealthBinding, createGameHudMeterBinding, createGameHudRoundBinding, createGameHudSnapshot, createGameHudTimerBinding, createGameBoxCollider, createGameCapsuleCollider, createGameColliderDebugGeometry, createGameCombatDebugGeometry, 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";
50
+ 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, 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";
47
51
  import { collectGameRuntimeEvidence as collectGameRuntimeEvidenceV105 } from "./GameEvidence.js";
48
52
  import { calculateRuntimeNodeBounds } from "./RuntimeNodeHandle.js";
49
53
  import { createRuntimeNodeSpec } from "./GameSceneBridge.js";
50
54
  import { createFightingGameKit, fighting as fightingGameKit } from "./game-kits/fighting.js";
55
+ import { GAME_FALLING_BLOCK_PIECES, createGameAssetBoundPlatformerLevel, createGameAssetBoundRacingRoute, createGameFallingBlocksKit, createGameLocomotionKit, createGamePlatformerKit, createGameRacingKit } from "./GameGenreKits.js";
56
+ import { createGamePlatformerSceneBinding, createGamePlatformerPresentationCamera, createGameRacingPresentationCamera, createGameRacingSceneBinding } from "./GameSceneGeometryBindings.js";
57
+ export { GAME_FALLING_BLOCK_PIECES, createGameAssetBoundPlatformerLevel, createGameAssetBoundRacingRoute, createGameFallingBlocksKit, createGameLocomotionKit, createGamePlatformerKit, createGameRacingKit } from "./GameGenreKits.js";
58
+ export { createGamePlatformerSceneBinding, createGamePlatformerPresentationCamera, createGameRacingPresentationCamera, createGameRacingSceneBinding } from "./GameSceneGeometryBindings.js";
51
59
  import { createPromptAnimationEpisodePlan, createPromptAnimationStoryBible, definePromptAnimationStoryboard } from "./PromptAnimationContract.js";
52
60
  import { applyShotPlaybackFrame, createShotPlaybackPlan, createShotTimeline, installShotPlayback, sampleShotPlaybackPlan } from "./ShotTimeline.js";
53
61
  import { captionCueAtTime, createCaptionTimingProof, deriveCaptionTrackFromDialogue } from "./DialoguePerformance.js";
@@ -157,6 +165,11 @@ export function model(asset, options = {}) {
157
165
  castShadow: options.castShadow ?? true,
158
166
  receiveShadow: options.receiveShadow ?? true,
159
167
  visible: options.visible ?? true,
168
+ role: options.role,
169
+ scaleMode: options.scaleMode,
170
+ targetHeight: options.targetHeight,
171
+ targetMaxDimension: options.targetMaxDimension,
172
+ targetLength: options.targetLength,
160
173
  physics: options.physics
161
174
  });
162
175
  }
@@ -554,8 +567,205 @@ export const material = {
554
567
  fabric: material.fabric()
555
568
  }),
556
569
  inspector: (name, spec) => createMaterialInspector(name, spec),
557
- visualQA: (nodes) => validateMaterialVisualQA(nodes)
570
+ visualQA: (nodes) => validateMaterialVisualQA(nodes),
571
+ capabilityDiagnostics: (input) => createMaterialCapabilityDiagnostics(input)
558
572
  };
573
+ const materialCapabilityCatalog = [
574
+ {
575
+ id: "base-color",
576
+ label: "Base color",
577
+ rootSafeApi: "supported",
578
+ productionRuntime: "supported",
579
+ evidence: "AuraMaterialSpec.color and imported GLB baseColorFactor are consumed by the root renderer.",
580
+ claimRule: "Can be claimed with a route screenshot."
581
+ },
582
+ {
583
+ id: "base-color-texture",
584
+ label: "Base-color texture",
585
+ rootSafeApi: "partial",
586
+ productionRuntime: "supported",
587
+ 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.",
588
+ claimRule: "Can describe typed texture metadata and rendered textured assets; do not claim full texture-material parity without controlled root pixels."
589
+ },
590
+ {
591
+ id: "metallic-roughness",
592
+ label: "Metallic/roughness",
593
+ rootSafeApi: "partial",
594
+ productionRuntime: "supported",
595
+ evidence: "Root material specs expose values, but production-style material response needs route-specific pixel proof.",
596
+ claimRule: "Do not claim full PBR parity without browser evidence."
597
+ },
598
+ {
599
+ id: "normal-map",
600
+ label: "Normal maps",
601
+ rootSafeApi: "partial",
602
+ productionRuntime: "supported",
603
+ evidence: "Root specs carry normal/procedural-normal intent; production shader parity is not guaranteed.",
604
+ claimRule: "Claim only with material-specific screenshot evidence."
605
+ },
606
+ {
607
+ id: "emissive",
608
+ label: "Emissive",
609
+ rootSafeApi: "partial",
610
+ productionRuntime: "supported",
611
+ evidence: "Root specs and effects expose emissive color; pixel-backed bloom is reported separately.",
612
+ claimRule: "Distinguish emissive material from postprocess bloom."
613
+ },
614
+ {
615
+ id: "alpha",
616
+ label: "Alpha/opacity",
617
+ rootSafeApi: "partial",
618
+ productionRuntime: "supported",
619
+ evidence: "Opacity is accepted by the public spec, but sorting and blending must be visually verified.",
620
+ claimRule: "Verify alpha behavior in screenshots before claiming."
621
+ },
622
+ {
623
+ id: "double-sided",
624
+ label: "Double-sided materials",
625
+ rootSafeApi: "metadata-only",
626
+ productionRuntime: "supported",
627
+ evidence: "Root diagnostics may preserve metadata, but public material specs do not expose a full double-sided render contract.",
628
+ claimRule: "Claim only when tested route shows backface behavior."
629
+ },
630
+ {
631
+ id: "clearcoat",
632
+ label: "Clearcoat",
633
+ rootSafeApi: "partial",
634
+ productionRuntime: "supported",
635
+ evidence: "Root specs and inspectors expose clearcoat values; physically accurate layered highlights need pixel proof.",
636
+ claimRule: "Do not call clearcoat physically accurate without renderer evidence."
637
+ },
638
+ {
639
+ id: "sheen",
640
+ label: "Sheen",
641
+ rootSafeApi: "partial",
642
+ productionRuntime: "supported",
643
+ evidence: "Root specs expose sheen values for inspection and future renderer binding.",
644
+ claimRule: "Treat as partial unless screenshot-proven."
645
+ },
646
+ {
647
+ id: "transmission",
648
+ label: "Transmission/glass",
649
+ rootSafeApi: "partial",
650
+ productionRuntime: "supported",
651
+ evidence: "Root specs expose glass intent, opacity, IOR, and thickness; real refraction/volume is not proven in the root path.",
652
+ claimRule: "Do not claim real transmission/refraction through root createAuraApp without pixels."
653
+ },
654
+ {
655
+ id: "variants",
656
+ label: "Material variants",
657
+ rootSafeApi: "metadata-only",
658
+ productionRuntime: "supported",
659
+ evidence: "Variant metadata and route logic can be inspected; renderer-level variant switching needs route tests.",
660
+ claimRule: "Claim selected variant behavior only when tested."
661
+ },
662
+ {
663
+ id: "hdr-ibl",
664
+ label: "HDR/IBL",
665
+ rootSafeApi: "partial",
666
+ productionRuntime: "supported",
667
+ evidence: "Root environment nodes request lighting intent; environment prefiltering is not proven in the root path.",
668
+ claimRule: "Root HDR/IBL claims require diagnostics and browser pixels."
669
+ },
670
+ {
671
+ id: "shadow-maps",
672
+ label: "Shadow maps",
673
+ rootSafeApi: "partial",
674
+ productionRuntime: "supported",
675
+ evidence: "Root path reports contact-shadow cues; production shadow-map sampling is not proven by spec alone.",
676
+ claimRule: "Production-shadow claims require pixel proof."
677
+ }
678
+ ];
679
+ function createMaterialCapabilityDiagnostics(input) {
680
+ const specs = extractMaterialCapabilitySpecs(input);
681
+ const requested = new Set();
682
+ if (specs.length > 0)
683
+ requested.add("base-color");
684
+ for (const spec of specs) {
685
+ if (spec.texture)
686
+ requested.add("base-color-texture");
687
+ if (spec.metallic !== undefined || spec.metalness !== undefined || spec.roughness !== undefined || spec.roughnessMap || spec.metalnessMap)
688
+ requested.add("metallic-roughness");
689
+ if (spec.normal)
690
+ requested.add("normal-map");
691
+ if (spec.emissive || spec.emissiveIntensity !== undefined)
692
+ requested.add("emissive");
693
+ if (spec.opacity !== undefined && spec.opacity < 1)
694
+ requested.add("alpha");
695
+ if (spec.clearcoat !== undefined || spec.clearcoatRoughness !== undefined)
696
+ requested.add("clearcoat");
697
+ if (spec.sheen !== undefined || spec.sheenColor !== undefined || spec.sheenRoughness !== undefined)
698
+ requested.add("sheen");
699
+ if (spec.transmission !== undefined || spec.thickness !== undefined || spec.ior !== undefined || spec.attenuationColor !== undefined || spec.attenuationDistance !== undefined)
700
+ requested.add("transmission");
701
+ if (spec.envMapIntensity !== undefined)
702
+ requested.add("hdr-ibl");
703
+ }
704
+ const features = materialCapabilityCatalog.map((feature) => ({ ...feature, requested: requested.has(feature.id) }));
705
+ const unsupportedRequestedFeatures = features
706
+ .filter((feature) => feature.requested && (feature.rootSafeApi === "unsupported" || feature.rootSafeApi === "internal" || feature.rootSafeApi === "metadata-only"))
707
+ .map((feature) => feature.id);
708
+ const partialRequestedFeatures = features
709
+ .filter((feature) => feature.requested && feature.rootSafeApi === "partial")
710
+ .map((feature) => feature.id);
711
+ const warnings = [];
712
+ if (partialRequestedFeatures.length > 0) {
713
+ warnings.push(`Partial root material features requested: ${partialRequestedFeatures.join(", ")}. Capture route pixels before claiming production material quality.`);
714
+ }
715
+ if (unsupportedRequestedFeatures.length > 0) {
716
+ warnings.push(`Metadata/internal material features requested: ${unsupportedRequestedFeatures.join(", ")}. Do not claim rendered support from root createAuraApp alone.`);
717
+ }
718
+ return {
719
+ kind: "aura-material-capability-diagnostics",
720
+ rendererPath: "root-createAuraApp",
721
+ requestedFeatures: [...requested].sort(),
722
+ unsupportedRequestedFeatures,
723
+ partialRequestedFeatures,
724
+ features,
725
+ warnings,
726
+ claimBoundary: "Material diagnostics describe public root createAuraApp support. They are not a substitute for browser screenshot evidence."
727
+ };
728
+ }
729
+ function extractMaterialCapabilitySpecs(input) {
730
+ if (!input)
731
+ return [];
732
+ if (Array.isArray(input)) {
733
+ if (input.every(isAuraMaterialSpecLike))
734
+ return input;
735
+ return groups.flatten(input)
736
+ .filter((node) => (node.kind === "primitive" || node.kind === "model") && Boolean(node.material))
737
+ .map((node) => node.material);
738
+ }
739
+ if (isAuraMaterialSpecLike(input))
740
+ return [input];
741
+ const snapshot = normalizeSceneSnapshot(input);
742
+ return groups.flatten(snapshot.nodes)
743
+ .filter((node) => (node.kind === "primitive" || node.kind === "model") && Boolean(node.material))
744
+ .map((node) => node.material);
745
+ }
746
+ function isAuraMaterialSpecLike(value) {
747
+ if (!value || typeof value !== "object")
748
+ return false;
749
+ const candidate = value;
750
+ if (candidate.kind !== undefined || candidate.nodes !== undefined)
751
+ return false;
752
+ return Boolean(candidate.color !== undefined ||
753
+ candidate.roughness !== undefined ||
754
+ candidate.metallic !== undefined ||
755
+ candidate.metalness !== undefined ||
756
+ candidate.emissive !== undefined ||
757
+ candidate.opacity !== undefined ||
758
+ candidate.transmission !== undefined ||
759
+ candidate.clearcoat !== undefined ||
760
+ candidate.sheen !== undefined ||
761
+ candidate.texture !== undefined ||
762
+ candidate.normal !== undefined ||
763
+ candidate.roughnessMap !== undefined ||
764
+ candidate.metalnessMap !== undefined ||
765
+ candidate.envMapIntensity !== undefined ||
766
+ candidate.shader !== undefined ||
767
+ candidate.name !== undefined);
768
+ }
559
769
  function createMaterialInspector(name, spec) {
560
770
  const metalness = spec.metalness ?? spec.metallic ?? 0;
561
771
  const parameters = [
@@ -807,6 +1017,7 @@ export const camera = {
807
1017
  distance: options.distance ?? 5,
808
1018
  position: options.position,
809
1019
  target: options.target ?? [0, 1, 0],
1020
+ offset: options.offset,
810
1021
  fov: options.fov ?? 50,
811
1022
  easing: options.easing,
812
1023
  captureTime: options.captureTime,
@@ -844,6 +1055,37 @@ export const camera = {
844
1055
  const distance = extent * (options.padding ?? 2.15);
845
1056
  return camera.orbit({ target: center, distance, fov: options.fov ?? 42 });
846
1057
  },
1058
+ frameAsset: (asset, options = {}) => {
1059
+ const bounds = boundsFromAsset(asset);
1060
+ const size = boundsSize(bounds);
1061
+ const renderedScale = resolveFrameAssetRenderScale(bounds, options);
1062
+ const renderedSize = [
1063
+ Math.max(0.001, size[0] * renderedScale),
1064
+ Math.max(0.001, size[1] * renderedScale),
1065
+ Math.max(0.001, size[2] * renderedScale)
1066
+ ];
1067
+ const position = options.position ?? [0, options.floorY ?? 0, 0];
1068
+ const target = options.target ?? [
1069
+ position[0],
1070
+ position[1] + renderedSize[1] * 0.28,
1071
+ position[2]
1072
+ ];
1073
+ const fov = options.fov ?? 36;
1074
+ const fovRadians = Math.max(1, Math.min(120, fov)) * Math.PI / 180;
1075
+ const radius = Math.hypot(renderedSize[0], renderedSize[1], renderedSize[2]) / 2;
1076
+ const verticalFitDistance = renderedSize[1] / (2 * Math.tan(fovRadians / 2));
1077
+ const horizontalFitDistance = Math.max(renderedSize[0], renderedSize[2]) / (2 * Math.tan(fovRadians / 2));
1078
+ const distance = Math.max(options.minDistance ?? 0.85, verticalFitDistance * (options.padding ?? 1.42), horizontalFitDistance * (options.padding ?? 1.42), radius / Math.tan(fovRadians / 2) * (options.padding ?? 1.42));
1079
+ const azimuth = options.azimuth ?? 0.62;
1080
+ const elevation = options.elevation ?? 0.28;
1081
+ const horizontal = Math.max(0.001, Math.cos(elevation));
1082
+ const eye = [
1083
+ target[0] + Math.sin(azimuth) * horizontal * distance,
1084
+ target[1] + Math.sin(elevation) * distance,
1085
+ target[2] + Math.cos(azimuth) * horizontal * distance
1086
+ ];
1087
+ return camera.orbit({ target, position: eye, distance, fov });
1088
+ },
847
1089
  physics: () => camera.orbit({ target: [0, 0.58, -0.35], distance: 5.8, fov: 43 }),
848
1090
  charts: () => camera.orbit({ target: [0, 0.78, 0], distance: 6.4, fov: 40 }),
849
1091
  materials: () => camera.perspective({ position: [0, 2.08, 7.35], target: [0, 0.9, -0.72], fov: 40 }),
@@ -854,6 +1096,19 @@ export const camera = {
854
1096
  miniGolf: () => camera.follow({ targetNode: "white physics golf ball", distance: 4.2, fov: 48 }),
855
1097
  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 })
856
1098
  };
1099
+ function resolveFrameAssetRenderScale(bounds, options) {
1100
+ const size = boundsSize(bounds);
1101
+ const height = Math.max(0.001, boundsHeight(bounds));
1102
+ const horizontalLength = Math.max(0.001, size[0], size[2]);
1103
+ const maxDimension = Math.max(0.001, boundsMaxDimension(bounds));
1104
+ if (isPositiveFinite(options.targetHeight))
1105
+ return options.targetHeight / height;
1106
+ if (isPositiveFinite(options.targetLength))
1107
+ return options.targetLength / horizontalLength;
1108
+ if (isPositiveFinite(options.targetMaxDimension))
1109
+ return options.targetMaxDimension / maxDimension;
1110
+ return AURA_NORMALIZED_MODEL_MAX_DIMENSION / maxDimension;
1111
+ }
857
1112
  export const timeline = {
858
1113
  loop: (options = {}) => ({
859
1114
  mode: "loop",
@@ -1284,17 +1539,120 @@ const rendererQualityPresets = {
1284
1539
  evidence: "prioritizes benchmark screenshots with stronger edge quality for labels, axes, and neon rings"
1285
1540
  }
1286
1541
  };
1542
+ const rendererQualityProfiles = {
1543
+ "safe-basic": {
1544
+ kind: "aura-renderer-quality-profile",
1545
+ id: "safe-basic",
1546
+ label: "Safe Basic",
1547
+ rendererMode: "safe-basic",
1548
+ status: "supported",
1549
+ antialiasing: "msaa",
1550
+ pixelRatio: 1,
1551
+ preserveDrawingBuffer: true,
1552
+ maxRecommendedDrawCalls: 180,
1553
+ requestedFeatures: ["typed models", "primitives", "basic particles", "base-color textures", "runtime nodes"],
1554
+ supportedInRoot: ["typed models", "primitives", "basic particles", "base-color textures", "runtime nodes"],
1555
+ blockedInRoot: ["production PBR parity", "skinned animation mixer", "native WebGPU compute", "postprocess pass chain"],
1556
+ claimBoundary: "Root createAuraApp safe path. Use this for public examples unless a browser test proves a stronger profile."
1557
+ },
1558
+ production: {
1559
+ kind: "aura-renderer-quality-profile",
1560
+ id: "production",
1561
+ label: "Production Typed GLB",
1562
+ rendererMode: "production",
1563
+ status: "supported",
1564
+ antialiasing: "msaa-plus-high-dpi",
1565
+ pixelRatio: 1.5,
1566
+ preserveDrawingBuffer: true,
1567
+ maxRecommendedDrawCalls: 260,
1568
+ requestedFeatures: ["typed GLB actors", "PBR material pipeline", "environment lighting", "shadow maps", "postprocess"],
1569
+ supportedInRoot: ["typed GLB actor bridge", "imported GLB render source", "PBR material pipeline metadata", "runtime diagnostics"],
1570
+ blockedInRoot: ["environment prefiltering without pixel proof", "shadow-map sampling without pixel proof", "postprocess pass chain without pixel proof", "native WebGPU claim without adapter/render evidence"],
1571
+ claimBoundary: "Root createAuraApp can route typed GLB manifest assets through the production runtime. Claim only the specific features proven by route diagnostics and screenshots."
1572
+ },
1573
+ cinematic: {
1574
+ kind: "aura-renderer-quality-profile",
1575
+ id: "cinematic",
1576
+ label: "Cinematic Request",
1577
+ rendererMode: "production",
1578
+ status: "fallback-only",
1579
+ antialiasing: "msaa-plus-high-dpi",
1580
+ pixelRatio: 1.5,
1581
+ preserveDrawingBuffer: true,
1582
+ maxRecommendedDrawCalls: 320,
1583
+ requestedFeatures: ["cinematic lighting", "environment lighting", "postprocess", "motion proof", "high-DPI screenshots"],
1584
+ supportedInRoot: ["high-DPI canvas sizing", "scene diagnostics", "basic particles", "camera/timeline animation"],
1585
+ blockedInRoot: ["renderer-owned bloom pass", "depth-aware postprocess", "production shadow maps", "skinned animation mixer"],
1586
+ claimBoundary: "Cinematic is an explicit request profile, not proof of production cinematic rendering."
1587
+ },
1588
+ "experimental-webgpu": {
1589
+ kind: "aura-renderer-quality-profile",
1590
+ id: "experimental-webgpu",
1591
+ label: "Experimental WebGPU Request",
1592
+ rendererMode: "production",
1593
+ status: "experimental",
1594
+ antialiasing: "msaa",
1595
+ pixelRatio: 1,
1596
+ preserveDrawingBuffer: true,
1597
+ maxRecommendedDrawCalls: 220,
1598
+ requestedFeatures: ["WebGPU adapter", "compute dispatch", "native WebGPU rendering"],
1599
+ supportedInRoot: ["capability diagnostics", "fallback route evidence"],
1600
+ blockedInRoot: ["native WebGPU claim without adapter/backend/dispatch/render evidence"],
1601
+ claimBoundary: "May only claim native WebGPU when route diagnostics and browser screenshots prove adapter, backend, dispatch, and pixels."
1602
+ }
1603
+ };
1604
+ function resolveRendererQualityProfile(id) {
1605
+ return rendererQualityProfiles[id ?? "safe-basic"] ?? rendererQualityProfiles["safe-basic"];
1606
+ }
1607
+ function normalizeCreateAppRendererOptions(options) {
1608
+ const profile = resolveRendererQualityProfile(options?.qualityProfile);
1609
+ const mode = options?.mode ?? profile.rendererMode;
1610
+ return {
1611
+ mode,
1612
+ fallback: options?.fallback ?? "safe-basic",
1613
+ qualityProfile: profile.id,
1614
+ profile
1615
+ };
1616
+ }
1617
+ function analyzeProductionBridgeEligibility(nodes) {
1618
+ const modelNodes = nodes.filter(isRenderableModelNode);
1619
+ const typedModelCount = modelNodes.filter((node) => createAssetProvenance(node.asset).source === "typed-aura-assets-manifest").length;
1620
+ const unsafeModelCount = modelNodes.filter((node) => createAssetProvenance(node.asset).source === "unsafe-url").length;
1621
+ const inlineModelCount = modelNodes.filter((node) => createAssetProvenance(node.asset).source === "inline-definition").length;
1622
+ const primitiveCount = nodes.filter((node) => node.kind === "primitive").length;
1623
+ const effectCount = nodes.filter((node) => node.kind === "effect").length;
1624
+ const reasons = [];
1625
+ if (typedModelCount < 1)
1626
+ reasons.push("production bridge requires at least one typed GLB from generated aura-assets");
1627
+ if (unsafeModelCount > 0)
1628
+ reasons.push("unsafeModelUrl and remote/raw model URLs are blocked from the production bridge");
1629
+ if (inlineModelCount > 0)
1630
+ reasons.push("inline model definitions without manifest hashes are blocked from the production bridge");
1631
+ return {
1632
+ eligible: reasons.length === 0,
1633
+ typedModelCount,
1634
+ unsafeModelCount,
1635
+ inlineModelCount,
1636
+ primitiveCount,
1637
+ effectCount,
1638
+ reasons
1639
+ };
1640
+ }
1287
1641
  export const renderer = {
1288
1642
  colorManagementPreset: () => rendererColorManagementPreset,
1289
1643
  exposurePresets: () => sceneExposurePresets,
1290
1644
  exposureFor: (category) => sceneExposurePresets[category],
1291
1645
  qualityPresets: () => rendererQualityPresets,
1646
+ qualityProfiles: () => rendererQualityProfiles,
1647
+ qualityProfile: (id) => resolveRendererQualityProfile(id),
1292
1648
  screenshotQuality: () => rendererQualityPresets.screenshot,
1293
- diagnostics: (sceneValue) => createRendererDiagnosticReport(flattenSceneSnapshot(normalizeSceneSnapshot(sceneValue)))
1649
+ diagnostics: (sceneValue, options) => createRendererDiagnosticReport(flattenSceneSnapshot(normalizeSceneSnapshot(sceneValue)), undefined, options)
1294
1650
  };
1295
- function createRendererDiagnosticReport(snapshot, runtime) {
1651
+ function createRendererDiagnosticReport(snapshot, runtime, rendererOptions) {
1296
1652
  const flattened = groups.flatten(snapshot.nodes);
1297
1653
  const names = flattened.map((node) => "name" in node ? node.name?.toLowerCase() ?? "" : "");
1654
+ const rendererSelection = normalizeCreateAppRendererOptions(rendererOptions);
1655
+ const productionEligibility = analyzeProductionBridgeEligibility(flattened);
1298
1656
  const sceneCategory = resolveRendererSceneCategory(snapshot, names);
1299
1657
  const exposure = sceneExposurePresets[sceneCategory];
1300
1658
  const bloom = flattened.find((node) => node.kind === "effect" && node.effect === "bloom");
@@ -1303,9 +1661,12 @@ function createRendererDiagnosticReport(snapshot, runtime) {
1303
1661
  const contactShadows = names.filter((name) => name.includes("contact shadow") || name.includes("footprint") || name.includes("glow pool")).length;
1304
1662
  const ambientOcclusion = flattened.some((node) => node.kind === "effect" && node.effect === "ambient-occlusion");
1305
1663
  const contactOcclusion = flattened.some((node) => node.kind === "effect" && node.effect === "contact-occlusion") || contactShadows > 0;
1306
- const postprocessRequested = Boolean(bloom) || ambientOcclusion || contactOcclusion;
1307
- const requestedPasses = requestedRendererPostProcessPasses(Boolean(bloom), ambientOcclusion, contactOcclusion);
1308
1664
  const runtimePostprocess = runtime?.postprocess;
1665
+ const runtimePasses = runtimePostprocess?.actualPasses ?? [];
1666
+ const postprocessRequested = Boolean(bloom) || ambientOcclusion || contactOcclusion || runtimePasses.length > 0;
1667
+ const requestedPasses = runtimePasses.length > 0
1668
+ ? runtimePasses
1669
+ : requestedRendererPostProcessPasses(Boolean(bloom), ambientOcclusion, contactOcclusion);
1309
1670
  const runtimeStatus = !postprocessRequested
1310
1671
  ? "disabled"
1311
1672
  : runtime?.mounted
@@ -1324,6 +1685,18 @@ function createRendererDiagnosticReport(snapshot, runtime) {
1324
1685
  warnings.push("renderer diagnostics are a scene plan only; call createAuraApp(...).diagnostics() after the first render for pixel-backed pass status");
1325
1686
  if (postprocessRequested && runtime?.mounted && !runtimePostprocess?.pixelBacked)
1326
1687
  warnings.push("postprocess was requested but the runtime composer did not initialize a pixel-backed pass");
1688
+ if (rendererSelection.mode === "production" && !runtime?.mounted && productionEligibility.eligible) {
1689
+ warnings.push(`Renderer profile "${rendererSelection.profile.id}" can use the typed-GLB production bridge after createAuraApp mounts; inspect app.diagnostics().renderer.runtime for pixel-backed status.`);
1690
+ }
1691
+ if (rendererSelection.mode === "production" && !productionEligibility.eligible) {
1692
+ warnings.push(`Renderer profile "${rendererSelection.profile.id}" will use safe-basic fallback for this scene: ${productionEligibility.reasons.join("; ")}.`);
1693
+ }
1694
+ if (rendererSelection.profile.status === "fallback-only") {
1695
+ warnings.push(`Renderer profile "${rendererSelection.profile.id}" is a request profile; public claims require route-specific runtime diagnostics and screenshot proof.`);
1696
+ }
1697
+ if (rendererSelection.profile.id === "experimental-webgpu") {
1698
+ warnings.push("Experimental WebGPU profile cannot claim native WebGPU without adapter, backend, dispatch, render, and pixel evidence.");
1699
+ }
1327
1700
  warnings.push(...(runtime?.warnings ?? []));
1328
1701
  const environmentStatus = runtime?.environment ?? {
1329
1702
  enabled: Boolean(environment),
@@ -1343,6 +1716,9 @@ function createRendererDiagnosticReport(snapshot, runtime) {
1343
1716
  return {
1344
1717
  kind: "aura-renderer-diagnostics",
1345
1718
  colorManagement: rendererColorManagementPreset,
1719
+ rendererMode: rendererSelection.mode,
1720
+ fallbackMode: rendererSelection.fallback,
1721
+ qualityProfile: rendererSelection.profile,
1346
1722
  sceneCategory,
1347
1723
  exposure,
1348
1724
  toneMapping: "aces-filmic",
@@ -1406,6 +1782,7 @@ function createRendererDiagnosticReport(snapshot, runtime) {
1406
1782
  },
1407
1783
  antialiasing: rendererQualityPresets.screenshot.antialiasing,
1408
1784
  screenshotQuality: rendererQualityPresets.screenshot,
1785
+ materialCapabilities: createMaterialCapabilityDiagnostics(snapshot),
1409
1786
  warnings
1410
1787
  };
1411
1788
  }
@@ -1439,7 +1816,18 @@ function resolveRendererSceneCategory(snapshot, names) {
1439
1816
  return "physics";
1440
1817
  if (hasName("chart") || hasName("data bar") || hasName("axis"))
1441
1818
  return "chart";
1442
- if (hasName("golf") || hasName("score") || hasName("cup"))
1819
+ if (hasName("game") ||
1820
+ hasName("golf") ||
1821
+ hasName("score") ||
1822
+ hasName("cup") ||
1823
+ hasName("race") ||
1824
+ hasName("car") ||
1825
+ hasName("circuit") ||
1826
+ hasName("runner") ||
1827
+ hasName("platform") ||
1828
+ hasName("checkpoint") ||
1829
+ hasName("arcade") ||
1830
+ hasName("blockfall"))
1443
1831
  return "game";
1444
1832
  const background = colorToClearColor(snapshot.background);
1445
1833
  const luminance = background[0] * 0.2126 + background[1] * 0.7152 + background[2] * 0.0722;
@@ -3238,6 +3626,287 @@ export function createAuraGameRuntime(options = {}) {
3238
3626
  bodies: []
3239
3627
  };
3240
3628
  }
3629
+ export function createGameRacingPresentationTrackNodes(options) {
3630
+ const mode = options.mode ?? "standalone";
3631
+ const routePoints = options.route.points.map((point) => options.sceneBinding.toScenePoint(point, options.roadY ?? 0.012));
3632
+ if (routePoints.length < 2)
3633
+ return [];
3634
+ const guideVisibility = options.guideVisibility ?? (mode === "asset-overlay" ? "evidence" : "full");
3635
+ if (mode === "asset-overlay" && guideVisibility === "public")
3636
+ return [];
3637
+ const isCircuitStage = mode === "game-circuit";
3638
+ const roadWidth = Math.max((options.route.width ?? 0.18) * options.sceneBinding.transform.scale * (isCircuitStage ? 3.05 : 1.5), isCircuitStage ? 0.42 : 0.18);
3639
+ const nodes = [];
3640
+ if (mode === "standalone" || mode === "game-circuit") {
3641
+ const bounds = boundsForPresentationPoints(routePoints, roadWidth * (isCircuitStage ? 4.5 : 3.2));
3642
+ nodes.push(primitives.box({
3643
+ name: "scene-bound racing terrain pad",
3644
+ material: material.pbr({ color: options.terrainColor ?? (isCircuitStage ? "#1f342e" : "#263b35"), roughness: 0.92, metallic: 0 })
3645
+ })
3646
+ .position(bounds.centerX, -0.032, bounds.centerZ)
3647
+ .scale([bounds.width, 0.035, bounds.depth])
3648
+ .toJSON());
3649
+ if (isCircuitStage) {
3650
+ nodes.push(primitives.box({
3651
+ name: "scene-bound racing pit lane apron",
3652
+ material: material.pbr({ color: "#27323a", roughness: 0.86, metallic: 0 })
3653
+ })
3654
+ .position(roundGamePresentation(bounds.centerX - bounds.width * 0.12), 0.002, roundGamePresentation(bounds.centerZ + bounds.depth * 0.23))
3655
+ .rotate(0, -0.16, 0)
3656
+ .scale([Math.max(0.8, bounds.width * 0.42), 0.026, roadWidth * 0.58])
3657
+ .toJSON());
3658
+ }
3659
+ }
3660
+ for (let index = 0; index < routePoints.length - 1; index += 1) {
3661
+ const start = routePoints[index];
3662
+ const end = routePoints[index + 1];
3663
+ if (!start || !end)
3664
+ continue;
3665
+ const segment = segmentPresentation(start, end);
3666
+ if (segment.length <= 0.02)
3667
+ continue;
3668
+ if (mode === "asset-overlay") {
3669
+ nodes.push(primitives.box({
3670
+ name: `asset-bound racing topology line ${index + 1}`,
3671
+ material: material.emissive({
3672
+ color: options.laneColor ?? "#d8f6ff",
3673
+ emissive: options.laneColor ?? "#8df4ff",
3674
+ emissiveIntensity: 0.48,
3675
+ roughness: 0.42
3676
+ })
3677
+ })
3678
+ .position(segment.midX, segment.midY + 0.038, segment.midZ)
3679
+ .rotate(0, -segment.angle, 0)
3680
+ .scale([Math.max(0.06, segment.length * 0.68), 0.008, 0.014])
3681
+ .toJSON());
3682
+ continue;
3683
+ }
3684
+ const sideX = -Math.sin(segment.angle);
3685
+ const sideZ = Math.cos(segment.angle);
3686
+ const y = segment.midY;
3687
+ nodes.push(primitives.box({
3688
+ name: `scene-bound racing road segment ${index + 1}`,
3689
+ material: material.pbr({ color: options.roadColor ?? "#343b3f", roughness: 0.76, metallic: 0 })
3690
+ })
3691
+ .position(segment.midX, y, segment.midZ)
3692
+ .rotate(0, -segment.angle, 0)
3693
+ .scale([segment.length + roadWidth * 0.6, 0.03, roadWidth])
3694
+ .toJSON(), primitives.box({
3695
+ name: `scene-bound racing center stripe ${index + 1}`,
3696
+ material: material.emissive({
3697
+ color: options.laneColor ?? "#d8f6ff",
3698
+ emissive: options.laneColor ?? "#8df4ff",
3699
+ emissiveIntensity: 0.7,
3700
+ roughness: 0.4
3701
+ })
3702
+ })
3703
+ .position(segment.midX, y + 0.022, segment.midZ)
3704
+ .rotate(0, -segment.angle, 0)
3705
+ .scale([Math.max(0.08, segment.length * (isCircuitStage ? 0.44 : 0.72)), 0.012, 0.014])
3706
+ .toJSON(), primitives.box({
3707
+ name: `scene-bound racing left curb ${index + 1}`,
3708
+ material: material.pbr({ color: index % 2 === 0 ? "#f4f7fb" : options.curbColor ?? "#df3550", roughness: 0.62 })
3709
+ })
3710
+ .position(roundGamePresentation(segment.midX + sideX * roadWidth * 0.54), y + 0.024, roundGamePresentation(segment.midZ + sideZ * roadWidth * 0.54))
3711
+ .rotate(0, -segment.angle, 0)
3712
+ .scale([Math.max(0.08, segment.length * 0.82), 0.015, 0.02])
3713
+ .toJSON(), primitives.box({
3714
+ name: `scene-bound racing right curb ${index + 1}`,
3715
+ material: material.pbr({ color: index % 2 === 0 ? options.curbColor ?? "#df3550" : "#f4f7fb", roughness: 0.62 })
3716
+ })
3717
+ .position(roundGamePresentation(segment.midX - sideX * roadWidth * 0.54), y + 0.024, roundGamePresentation(segment.midZ - sideZ * roadWidth * 0.54))
3718
+ .rotate(0, -segment.angle, 0)
3719
+ .scale([Math.max(0.08, segment.length * 0.82), 0.015, 0.02])
3720
+ .toJSON());
3721
+ }
3722
+ options.sceneBinding.checkpointScenePoints.forEach((point, index) => {
3723
+ nodes.push(primitives.box({
3724
+ name: `${mode === "asset-overlay" ? "asset-bound" : "scene-bound"} racing checkpoint gate ${index + 1}`,
3725
+ material: material.emissive({
3726
+ color: "#f8f1bf",
3727
+ emissive: "#f6e27a",
3728
+ emissiveIntensity: mode === "asset-overlay" ? 0.38 : 0.56,
3729
+ roughness: 0.46
3730
+ })
3731
+ })
3732
+ .position(point[0], point[1] + (mode === "asset-overlay" ? 0.045 : 0.026), point[2])
3733
+ .scale([roadWidth * (mode === "asset-overlay" ? 0.9 : 1.25), mode === "asset-overlay" ? 0.01 : 0.014, 0.026])
3734
+ .toJSON());
3735
+ });
3736
+ const start = routePoints[0];
3737
+ if (start) {
3738
+ nodes.push(primitives.box({
3739
+ name: `${mode === "asset-overlay" ? "asset-bound" : "scene-bound"} racing start finish band`,
3740
+ material: material.emissive({
3741
+ color: "#ffffff",
3742
+ emissive: "#ffffff",
3743
+ emissiveIntensity: mode === "asset-overlay" ? 0.36 : 0.5,
3744
+ roughness: 0.3
3745
+ })
3746
+ })
3747
+ .position(start[0], start[1] + (mode === "asset-overlay" ? 0.052 : 0.03), start[2])
3748
+ .scale([roadWidth * (mode === "asset-overlay" ? 1.05 : 1.45), mode === "asset-overlay" ? 0.01 : 0.014, 0.035])
3749
+ .toJSON());
3750
+ }
3751
+ return nodes;
3752
+ }
3753
+ export function createGamePlatformerPresentationSurfaceNodes(options) {
3754
+ const mode = options.mode ?? "standalone";
3755
+ const platforms = options.level.platforms ?? [];
3756
+ const guideVisibility = options.guideVisibility ?? (mode === "asset-overlay" ? "evidence" : "full");
3757
+ if (mode === "asset-overlay" && guideVisibility === "public")
3758
+ return [];
3759
+ const isGameLevel = mode === "game-level";
3760
+ const nodes = [];
3761
+ if ((mode === "standalone" || mode === "game-level") && platforms.length > 0) {
3762
+ const rects = platforms.map((surface) => options.sceneBinding.surfaceToSceneRect(surface));
3763
+ const bounds = boundsForPresentationRects(rects, isGameLevel ? 0.78 : 0.42);
3764
+ nodes.push(primitives.box({
3765
+ name: "scene-bound platformer backdrop",
3766
+ material: material.pbr({ color: isGameLevel ? "#263d48" : "#314d59", roughness: 0.94, metallic: 0, opacity: isGameLevel ? 0.2 : 0.28 })
3767
+ })
3768
+ .position(bounds.centerX, bounds.centerY + (isGameLevel ? 0.18 : 0.08), bounds.backZ)
3769
+ .scale([bounds.width, Math.max(0.2, bounds.height * (isGameLevel ? 2.25 : 1.8)), 0.035])
3770
+ .toJSON());
3771
+ }
3772
+ for (const surface of platforms) {
3773
+ const rect = options.sceneBinding.surfaceToSceneRect(surface);
3774
+ const width = Math.max(rect.size[0], 0.08);
3775
+ const height = Math.max(rect.size[1], 0.055);
3776
+ if (mode === "asset-overlay") {
3777
+ nodes.push(primitives.box({
3778
+ name: `asset-bound platformer contact strip ${surface.id}`,
3779
+ material: material.emissive({
3780
+ color: options.platformTrimColor ?? "#c9f7ff",
3781
+ emissive: options.platformTrimColor ?? "#a8f4ff",
3782
+ emissiveIntensity: 0.28,
3783
+ roughness: 0.44
3784
+ })
3785
+ })
3786
+ .position(rect.center[0], roundGamePresentation(rect.center[1] + height / 2 + 0.026), rect.center[2] + 0.24)
3787
+ .scale([width * 0.78, 0.012, 0.024])
3788
+ .toJSON());
3789
+ continue;
3790
+ }
3791
+ nodes.push(primitives.box({
3792
+ name: `scene-bound platformer surface ${surface.id}`,
3793
+ material: material.pbr({ color: options.platformColor ?? (isGameLevel ? "#526972" : "#718994"), roughness: 0.72, metallic: 0 })
3794
+ })
3795
+ .position(rect.center[0], rect.center[1], rect.center[2])
3796
+ .scale([width, height, isGameLevel ? 0.64 : 0.5])
3797
+ .toJSON(), primitives.box({
3798
+ name: `scene-bound platformer surface trim ${surface.id}`,
3799
+ material: material.emissive({
3800
+ color: options.platformTrimColor ?? "#c9f7ff",
3801
+ emissive: options.platformTrimColor ?? "#a8f4ff",
3802
+ emissiveIntensity: 0.36,
3803
+ roughness: 0.38
3804
+ })
3805
+ })
3806
+ .position(rect.center[0], roundGamePresentation(rect.center[1] + height / 2 + 0.012), rect.center[2] + (isGameLevel ? 0.334 : 0.262))
3807
+ .scale([width * 0.92, 0.016, 0.035])
3808
+ .toJSON());
3809
+ }
3810
+ for (const hazard of options.level.hazards ?? []) {
3811
+ const rect = options.sceneBinding.surfaceToSceneRect(hazard);
3812
+ nodes.push(primitives.box({
3813
+ name: `scene-bound platformer hazard ${hazard.id}`,
3814
+ material: material.emissive({
3815
+ color: options.hazardColor ?? "#ff6b6b",
3816
+ emissive: options.hazardColor ?? "#ff6b6b",
3817
+ emissiveIntensity: 0.7,
3818
+ roughness: 0.36
3819
+ })
3820
+ })
3821
+ .position(rect.center[0], rect.center[1], rect.center[2] + (isGameLevel ? 0.39 : 0.31))
3822
+ .scale([Math.max(rect.size[0], 0.045), Math.max(rect.size[1], 0.045), 0.055])
3823
+ .toJSON());
3824
+ }
3825
+ for (const collectible of options.level.collectibles ?? []) {
3826
+ const point = options.sceneBinding.toScenePoint(collectible, 0.04);
3827
+ nodes.push(primitives.torus({
3828
+ name: `scene-bound platformer collectible ${collectible.id}`,
3829
+ material: material.emissive({
3830
+ color: options.collectibleColor ?? "#fff3a3",
3831
+ emissive: options.collectibleColor ?? "#f6dc67",
3832
+ emissiveIntensity: 0.62,
3833
+ roughness: 0.4
3834
+ })
3835
+ })
3836
+ .position(point[0], point[1], point[2] + (isGameLevel ? 0.26 : 0.16))
3837
+ .rotate(Math.PI / 2, 0, 0)
3838
+ .scale([0.045, 0.045, 0.012])
3839
+ .toJSON());
3840
+ }
3841
+ for (const checkpoint of options.level.checkpoints ?? []) {
3842
+ const point = options.sceneBinding.toScenePoint(checkpoint, 0.02);
3843
+ nodes.push(primitives.box({
3844
+ name: `scene-bound platformer checkpoint ${checkpoint.id}`,
3845
+ material: material.emissive({ color: "#d7f9ff", emissive: "#a6f4ff", emissiveIntensity: 0.48, roughness: 0.4 })
3846
+ })
3847
+ .position(point[0], point[1], point[2] + (isGameLevel ? 0.29 : 0.19))
3848
+ .scale([0.026, 0.22, 0.034])
3849
+ .toJSON());
3850
+ }
3851
+ if (options.level.finish) {
3852
+ const finish = options.sceneBinding.toScenePoint(options.level.finish, 0.04);
3853
+ nodes.push(primitives.box({
3854
+ name: "scene-bound platformer finish marker",
3855
+ material: material.emissive({
3856
+ color: options.finishColor ?? "#b6ffbd",
3857
+ emissive: options.finishColor ?? "#83f58f",
3858
+ emissiveIntensity: 0.6,
3859
+ roughness: 0.42
3860
+ })
3861
+ })
3862
+ .position(finish[0], finish[1], finish[2] + (isGameLevel ? 0.34 : 0.22))
3863
+ .scale([0.06, 0.34, 0.05])
3864
+ .toJSON());
3865
+ }
3866
+ return nodes;
3867
+ }
3868
+ function boundsForPresentationPoints(points, padding) {
3869
+ const xs = points.map((point) => point[0]);
3870
+ const zs = points.map((point) => point[2]);
3871
+ const minX = Math.min(...xs);
3872
+ const maxX = Math.max(...xs);
3873
+ const minZ = Math.min(...zs);
3874
+ const maxZ = Math.max(...zs);
3875
+ return {
3876
+ centerX: roundGamePresentation((minX + maxX) / 2),
3877
+ centerZ: roundGamePresentation((minZ + maxZ) / 2),
3878
+ width: roundGamePresentation(Math.max(1.2, maxX - minX + padding)),
3879
+ depth: roundGamePresentation(Math.max(1.2, maxZ - minZ + padding))
3880
+ };
3881
+ }
3882
+ function boundsForPresentationRects(rects, padding) {
3883
+ const minX = Math.min(...rects.map((rect) => rect.center[0] - rect.size[0] / 2));
3884
+ const maxX = Math.max(...rects.map((rect) => rect.center[0] + rect.size[0] / 2));
3885
+ const minY = Math.min(...rects.map((rect) => rect.center[1] - rect.size[1] / 2));
3886
+ const maxY = Math.max(...rects.map((rect) => rect.center[1] + rect.size[1] / 2));
3887
+ const z = Math.min(...rects.map((rect) => rect.center[2]));
3888
+ return {
3889
+ centerX: roundGamePresentation((minX + maxX) / 2),
3890
+ centerY: roundGamePresentation((minY + maxY) / 2),
3891
+ backZ: roundGamePresentation(z - 0.22),
3892
+ width: roundGamePresentation(Math.max(1, maxX - minX + padding)),
3893
+ height: roundGamePresentation(Math.max(0.6, maxY - minY + padding))
3894
+ };
3895
+ }
3896
+ function segmentPresentation(start, end) {
3897
+ const dx = end[0] - start[0];
3898
+ const dz = end[2] - start[2];
3899
+ return {
3900
+ midX: roundGamePresentation((start[0] + end[0]) / 2),
3901
+ midY: roundGamePresentation((start[1] + end[1]) / 2),
3902
+ midZ: roundGamePresentation((start[2] + end[2]) / 2),
3903
+ length: roundGamePresentation(Math.hypot(dx, dz)),
3904
+ angle: Math.atan2(dz, dx)
3905
+ };
3906
+ }
3907
+ function roundGamePresentation(value) {
3908
+ return Math.round(value * 1000) / 1000;
3909
+ }
3241
3910
  export const game = {
3242
3911
  createRuntime: createAuraGameRuntime,
3243
3912
  rules: gameRules,
@@ -3258,8 +3927,23 @@ export const game = {
3258
3927
  simulation: createGameSimulation,
3259
3928
  runSimulation: runGameSimulation,
3260
3929
  inspector: createGameInspector,
3930
+ locomotion: createGameLocomotionKit,
3931
+ assetBoundPlatformerLevel: createGameAssetBoundPlatformerLevel,
3932
+ platformerSceneBinding: createGamePlatformerSceneBinding,
3933
+ platformerPresentationCamera: createGamePlatformerPresentationCamera,
3934
+ platformerPresentationSurfaces: createGamePlatformerPresentationSurfaceNodes,
3935
+ platformer: createGamePlatformerKit,
3936
+ assetBoundRacingRoute: createGameAssetBoundRacingRoute,
3937
+ racingSceneBinding: createGameRacingSceneBinding,
3938
+ racingPresentationCamera: createGameRacingPresentationCamera,
3939
+ racingPresentationTrack: createGameRacingPresentationTrackNodes,
3940
+ racing: createGameRacingKit,
3941
+ fallingBlocks: createGameFallingBlocksKit,
3942
+ fallingBlockPieces: GAME_FALLING_BLOCK_PIECES,
3943
+ eventLog: createGameEventLog,
3261
3944
  touchControls: createGameTouchControlLayout,
3262
3945
  kinematicBody: createGameKinematicBody,
3946
+ collisionWorld: createGameCollisionWorld,
3263
3947
  jumpAssist: createGameJumpAssist,
3264
3948
  collider: {
3265
3949
  box: createGameBoxCollider,
@@ -3292,6 +3976,12 @@ export const game = {
3292
3976
  timer: createGameHudTimerBinding,
3293
3977
  combo: createGameHudComboBinding,
3294
3978
  round: createGameHudRoundBinding,
3979
+ score: createGameHudScoreBinding,
3980
+ lives: createGameHudLivesBinding,
3981
+ objective: createGameHudObjectiveBinding,
3982
+ checkpoint: createGameHudCheckpointBinding,
3983
+ value: createGameHudValueBinding,
3984
+ eventLog: createGameHudEventLogBinding,
3295
3985
  debugToggle: createGameHudDebugToggleBinding,
3296
3986
  bindings: createGameHudBindings,
3297
3987
  snapshot: createGameHudSnapshot
@@ -5047,6 +5737,8 @@ export function createRuntimeNodeImportedAssetEvidence(input) {
5047
5737
  }
5048
5738
  } : {}),
5049
5739
  morphTargets,
5740
+ activeMorphTargets: { ...(input.activeMorphTargets ?? {}) },
5741
+ missingMorphTargets: [...(input.missingMorphTargets ?? [])],
5050
5742
  ...(input.bounds ? { bounds: isRuntimeNodeEvidenceBounds(input.bounds) ? input.bounds : calculateRuntimeNodeBounds(input.bounds) } : {}),
5051
5743
  renderItemCount: input.renderItemCount ?? 0,
5052
5744
  skinnedRenderItemCount: input.skinnedRenderItemCount ?? 0,
@@ -5297,6 +5989,8 @@ function cloneRuntimeImportedAssetEvidence(evidence) {
5297
5989
  clips: [...evidence.clips],
5298
5990
  skinningPalette: evidence.skinningPalette ? { ...evidence.skinningPalette } : undefined,
5299
5991
  morphTargets: [...evidence.morphTargets],
5992
+ activeMorphTargets: { ...evidence.activeMorphTargets },
5993
+ missingMorphTargets: [...evidence.missingMorphTargets],
5300
5994
  bounds: evidence.bounds
5301
5995
  ? {
5302
5996
  ...evidence.bounds,
@@ -5320,10 +6014,11 @@ function sanitizeRuntimeMorphWeight(weight) {
5320
6014
  export function createAuraApp(target, options) {
5321
6015
  let snapshot = normalizeSceneSnapshot(options.scene);
5322
6016
  let renderSnapshot = flattenSceneSnapshot(snapshot);
5323
- const diagnosticsState = createInitialDiagnostics(renderSnapshot);
6017
+ const rendererSelection = normalizeCreateAppRendererOptions(options.renderer);
6018
+ const diagnosticsState = createInitialDiagnostics(renderSnapshot, options.renderer);
5324
6019
  const canvas = resolveCanvas(target);
5325
6020
  if (canvas) {
5326
- configureCanvas(canvas, options.pixelRatio ?? devicePixelRatioSafe(), options.resize ?? true);
6021
+ configureCanvas(canvas, options.pixelRatio ?? rendererSelection.profile.pixelRatio ?? devicePixelRatioSafe(), options.resize ?? true);
5327
6022
  }
5328
6023
  const overlay = canvas && shouldRenderOverlay(options.diagnostics, snapshot) ? createDiagnosticsOverlay(canvas, diagnosticsState) : undefined;
5329
6024
  let disposed = false;
@@ -5360,7 +6055,7 @@ export function createAuraApp(target, options) {
5360
6055
  };
5361
6056
  const shouldUseProductionRendererForCurrentScene = () => Boolean(canvas && renderSnapshot.nodes.some(isWebGLRenderableNode) && typeof window !== "undefined");
5362
6057
  const resetDiagnosticsForCurrentScene = (backend) => {
5363
- const fresh = createInitialDiagnostics(renderSnapshot);
6058
+ const fresh = createInitialDiagnostics(renderSnapshot, options.renderer);
5364
6059
  diagnosticsState.backend = backend;
5365
6060
  diagnosticsState.fps = fresh.fps;
5366
6061
  diagnosticsState.drawCalls = fresh.drawCalls;
@@ -5409,7 +6104,7 @@ export function createAuraApp(target, options) {
5409
6104
  overlay?.update();
5410
6105
  const revision = ++mountRevision;
5411
6106
  if (shouldUseProductionRenderer && canvas) {
5412
- void startProductionRender(canvas, renderSnapshot, diagnosticsState, options, overlay, runRuntimeFrame, () => runtimePaused)
6107
+ void startProductionRender(canvas, renderSnapshot, diagnosticsState, options, overlay, runRuntimeFrame, () => runtimePaused, runtimeNodes)
5413
6108
  .then((controller) => {
5414
6109
  if (disposed || revision !== mountRevision) {
5415
6110
  controller.dispose();
@@ -5737,12 +6432,12 @@ function isRenderableModelNode(node) {
5737
6432
  function isWebGLRenderableNode(node) {
5738
6433
  return isRenderableModelNode(node) || node.kind === "primitive";
5739
6434
  }
5740
- async function startProductionRender(canvas, snapshot, diagnosticsState, options, overlay, beforeRender, isPaused = () => false) {
6435
+ async function startProductionRender(canvas, snapshot, diagnosticsState, options, overlay, beforeRender, isPaused = () => false, runtimeNodes) {
5741
6436
  const renderableNode = snapshot.nodes.find(isWebGLRenderableNode);
5742
6437
  if (!renderableNode) {
5743
6438
  throw new AuraRuntimeError("missing-asset", "Aura3D production rendering requires at least one typed model asset or primitive. Suggested fix: add model(assets.product), primitives.box(), primitives.sphere(), primitives.cylinder(), or primitives.plane().");
5744
6439
  }
5745
- const renderer = await createProductionSceneRenderer(canvas, snapshot);
6440
+ const renderer = await createProductionSceneRenderer(canvas, snapshot, options.renderer, runtimeNodes);
5746
6441
  diagnosticsState.renderer = renderer.diagnostics;
5747
6442
  const continuousRender = shouldContinuouslyRender(snapshot);
5748
6443
  let disposed = false;
@@ -5756,7 +6451,7 @@ async function startProductionRender(canvas, snapshot, diagnosticsState, options
5756
6451
  if (!isPaused())
5757
6452
  beforeRender?.(delta / 1000, "raf");
5758
6453
  const drawCalls = renderer.render(time);
5759
- diagnosticsState.backend = "webgl2";
6454
+ diagnosticsState.backend = renderer.backend;
5760
6455
  diagnosticsState.fps = diagnosticsState.fps || 60;
5761
6456
  diagnosticsState.drawCalls = drawCalls;
5762
6457
  diagnosticsState.renderSize = [canvas.width, canvas.height];
@@ -5795,8 +6490,494 @@ function shouldContinuouslyRender(snapshot) {
5795
6490
  return node.effect === "particles" || node.effect === "rain";
5796
6491
  });
5797
6492
  }
5798
- async function createProductionSceneRenderer(canvas, snapshot) {
5799
- return await createWebGLSceneRenderer(canvas, snapshot);
6493
+ async function createProductionSceneRenderer(canvas, snapshot, rendererOptions, runtimeNodes) {
6494
+ const rendererSelection = normalizeCreateAppRendererOptions(rendererOptions);
6495
+ if (rendererSelection.mode !== "production") {
6496
+ return await createWebGLSceneRenderer(canvas, snapshot, rendererOptions, [], runtimeNodes);
6497
+ }
6498
+ const flattened = groups.flatten(snapshot.nodes);
6499
+ const eligibility = analyzeProductionBridgeEligibility(flattened);
6500
+ if (!eligibility.eligible) {
6501
+ return await createWebGLSceneRenderer(canvas, snapshot, rendererOptions, [
6502
+ `Production bridge skipped: ${eligibility.reasons.join("; ")}.`
6503
+ ], runtimeNodes);
6504
+ }
6505
+ try {
6506
+ return await createProductionRuntimeSceneRenderer(canvas, snapshot, rendererOptions, runtimeNodes);
6507
+ }
6508
+ catch (error) {
6509
+ return await createWebGLSceneRenderer(canvas, snapshot, rendererOptions, [
6510
+ `Production bridge failed and safe-basic fallback rendered instead: ${productionRenderErrorMessage(error)}`
6511
+ ], runtimeNodes);
6512
+ }
6513
+ }
6514
+ const PRODUCTION_RUNTIME_POSTPROCESS = {
6515
+ targetFormat: "rgba8",
6516
+ toneMapping: {
6517
+ exposure: 1.14,
6518
+ whitePoint: 1.18,
6519
+ operator: "filmic",
6520
+ inputColorSpace: "linear",
6521
+ outputColorSpace: "srgb"
6522
+ },
6523
+ colorGrade: {
6524
+ contrast: 1.1,
6525
+ saturation: 1.06,
6526
+ vibrance: 0.12,
6527
+ vignette: 0.08,
6528
+ sharpening: 0.24
6529
+ },
6530
+ fxaa: {
6531
+ edgeThreshold: 0.08,
6532
+ subpixelBlend: 0.55
6533
+ }
6534
+ };
6535
+ const PRODUCTION_RUNTIME_SHADOWS = {
6536
+ enabled: true,
6537
+ size: 1024,
6538
+ bias: 0.0014,
6539
+ strength: 0.28,
6540
+ pcfRadius: 1.2,
6541
+ pcfSamples: 9,
6542
+ filter: "pcf",
6543
+ label: "aura3d-root-production-shadow-map"
6544
+ };
6545
+ let cachedProductionRuntimeLights;
6546
+ function createProductionRuntimeEnvironment(snapshot) {
6547
+ const names = groups.flatten(snapshot.nodes).map((node) => "name" in node ? node.name?.toLowerCase() ?? "" : "");
6548
+ const category = resolveRendererSceneCategory(snapshot, names);
6549
+ const preset = category === "city-day" ? "daylight"
6550
+ : category === "city-night" ? "evening"
6551
+ : category === "game" ? "gameplay"
6552
+ : category === "material" ? "inspection"
6553
+ : "studio";
6554
+ const bundle = createExternalParityEnvironmentLighting(preset);
6555
+ return {
6556
+ preset,
6557
+ intensity: bundle.lighting.environmentMapIntensity ?? bundle.lighting.intensity,
6558
+ evidence: `production bridge submitted generated HDR ${preset} environment lighting through RenderSource.environmentLighting`,
6559
+ lighting: bundle.lighting
6560
+ };
6561
+ }
6562
+ function createProductionRuntimePostprocess() {
6563
+ return PRODUCTION_RUNTIME_POSTPROCESS;
6564
+ }
6565
+ function createProductionRuntimeShadowOptions() {
6566
+ return PRODUCTION_RUNTIME_SHADOWS;
6567
+ }
6568
+ function createProductionRuntimePostprocessObservation(diagnostics) {
6569
+ const actualPasses = diagnostics.postprocessPassNames ?? [];
6570
+ const pixelBacked = actualPasses.length > 0 && diagnostics.lastError === null;
6571
+ return {
6572
+ renderPass: actualPasses.length > 0,
6573
+ outputPass: pixelBacked,
6574
+ bloomPass: actualPasses.includes("bloom"),
6575
+ ambientOcclusionPass: actualPasses.includes("ssao"),
6576
+ contactOcclusionReceiver: actualPasses.includes("contact-shadow"),
6577
+ pixelBacked,
6578
+ actualPasses,
6579
+ fallbackPasses: pixelBacked ? [] : ["direct-render"]
6580
+ };
6581
+ }
6582
+ function createProductionRuntimeCollectedLights() {
6583
+ if (cachedProductionRuntimeLights)
6584
+ return cachedProductionRuntimeLights;
6585
+ const key = new DirectionalLight("aura3d-root-production-key-shadow");
6586
+ const fill = new DirectionalLight("aura3d-root-production-fill");
6587
+ const rim = new DirectionalLight("aura3d-root-production-rim");
6588
+ key.castsShadow = true;
6589
+ key.color = [1, 0.94, 0.82];
6590
+ key.intensity = 2.6;
6591
+ fill.color = [0.52, 0.64, 0.9];
6592
+ fill.intensity = 0.64;
6593
+ rim.color = [0.88, 0.94, 1];
6594
+ rim.intensity = 0.88;
6595
+ cachedProductionRuntimeLights = [
6596
+ {
6597
+ kind: "directional",
6598
+ color: [1, 0.94, 0.82],
6599
+ intensity: 2.6,
6600
+ position: [0, 0, 0],
6601
+ direction: [0.44, -0.64, -0.63],
6602
+ range: 0,
6603
+ spotAngle: 0,
6604
+ penumbra: 0,
6605
+ castsShadow: true,
6606
+ layerMask: 0xffffffff,
6607
+ source: key
6608
+ },
6609
+ {
6610
+ kind: "directional",
6611
+ color: [0.52, 0.64, 0.9],
6612
+ intensity: 0.64,
6613
+ position: [0, 0, 0],
6614
+ direction: [-0.5, -0.28, -0.82],
6615
+ range: 0,
6616
+ spotAngle: 0,
6617
+ penumbra: 0,
6618
+ castsShadow: false,
6619
+ layerMask: 0xffffffff,
6620
+ source: fill
6621
+ },
6622
+ {
6623
+ kind: "directional",
6624
+ color: [0.88, 0.94, 1],
6625
+ intensity: 0.88,
6626
+ position: [0, 0, 0],
6627
+ direction: [-0.24, -0.36, 0.9],
6628
+ range: 0,
6629
+ spotAngle: 0,
6630
+ penumbra: 0,
6631
+ castsShadow: false,
6632
+ layerMask: 0xffffffff,
6633
+ source: rim
6634
+ }
6635
+ ];
6636
+ return cachedProductionRuntimeLights;
6637
+ }
6638
+ async function createProductionRuntimeSceneRenderer(canvas, snapshot, rendererOptions, runtimeNodes) {
6639
+ const flattened = groups.flatten(snapshot.nodes);
6640
+ const modelNodes = flattened.filter((node) => isRenderableModelNode(node) && createAssetProvenance(node.asset).source === "typed-aura-assets-manifest");
6641
+ const actorEntries = await Promise.all(modelNodes.map(async (node, index) => ({
6642
+ node,
6643
+ actor: await createTypedGLBActor({
6644
+ asset: node.asset,
6645
+ id: node.runtime?.id ?? node.asset.id ?? `model-${index + 1}`,
6646
+ name: node.name ?? node.asset.id ?? `model-${index + 1}`,
6647
+ width: canvas.width,
6648
+ height: canvas.height,
6649
+ ...(node.material?.color ? { tint: { baseColor: colorToRgba(node.material.color) } } : {})
6650
+ })
6651
+ })));
6652
+ const primitiveEntries = createProductionRuntimePrimitiveEntries(flattened);
6653
+ const productionRenderer = await ProductionRuntimeRenderer.create({
6654
+ canvas,
6655
+ width: canvas.width,
6656
+ height: canvas.height,
6657
+ backend: "webgl2",
6658
+ antialias: true,
6659
+ preserveDrawingBuffer: true,
6660
+ clearColor: colorToClearColor(snapshot.background)
6661
+ });
6662
+ let latestDeviceDiagnostics = productionRenderer.getDiagnostics();
6663
+ let latestFeatures = productionRenderer.getFeatures();
6664
+ const runtimeWarnings = new Set();
6665
+ const productionEnvironment = createProductionRuntimeEnvironment(snapshot);
6666
+ const buildDiagnostics = () => createRendererDiagnosticReport(snapshot, {
6667
+ mounted: true,
6668
+ backend: "production-runtime",
6669
+ postprocess: createProductionRuntimePostprocessObservation(latestDeviceDiagnostics),
6670
+ environment: {
6671
+ enabled: true,
6672
+ preset: productionEnvironment.preset,
6673
+ intensity: productionEnvironment.intensity,
6674
+ evidence: productionEnvironment.evidence
6675
+ },
6676
+ warnings: [
6677
+ `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}.`,
6678
+ ...(flattened.some((node) => node.kind === "effect")
6679
+ ? ["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."]
6680
+ : []),
6681
+ ...(productionRenderer.backendSelection.fallback ? [`Production runtime backend fallback: ${productionRenderer.backendSelection.reason}`] : []),
6682
+ ...latestFeatures
6683
+ .filter((feature) => feature.state !== "supported")
6684
+ .map((feature) => `Production runtime feature ${feature.id} is ${feature.state}: ${feature.detail}`),
6685
+ ...runtimeWarnings
6686
+ ]
6687
+ }, rendererOptions);
6688
+ return {
6689
+ get backend() {
6690
+ return productionRenderer.backend;
6691
+ },
6692
+ get diagnostics() {
6693
+ return buildDiagnostics();
6694
+ },
6695
+ render(time) {
6696
+ runtimeWarnings.clear();
6697
+ const input = createProductionRuntimeRendererInput(snapshot, canvas, actorEntries, primitiveEntries, time, runtimeNodes, runtimeWarnings, productionEnvironment.lighting);
6698
+ const result = productionRenderer.renderInteractiveFrame(input);
6699
+ latestDeviceDiagnostics = result.diagnostics;
6700
+ latestFeatures = result.features;
6701
+ return latestDeviceDiagnostics.drawCalls;
6702
+ },
6703
+ dispose() {
6704
+ productionRenderer.dispose();
6705
+ for (const { actor } of actorEntries)
6706
+ actor.pipeline.dispose();
6707
+ for (const { geometry, material } of primitiveEntries) {
6708
+ geometry.dispose();
6709
+ material.dispose();
6710
+ }
6711
+ }
6712
+ };
6713
+ }
6714
+ function createProductionRuntimeRendererInput(snapshot, canvas, actorEntries, primitiveEntries, time, runtimeNodes, runtimeWarnings, environmentLighting) {
6715
+ const items = [];
6716
+ for (const entry of actorEntries) {
6717
+ const currentState = resolveProductionActorRuntimeState(entry, runtimeNodes);
6718
+ const currentNode = currentState.node;
6719
+ if (currentNode.visible === false)
6720
+ continue;
6721
+ applyProductionActorAnimation(entry, currentNode, currentState.animationBinding, time, runtimeWarnings);
6722
+ applyProductionActorMorphTargets(entry, currentState.morphTargets, runtimeWarnings);
6723
+ const modelMatrix = [...createModelMatrix(currentNode, boundsFromAuraAsset(currentNode.asset), shouldNormalizeModelNode(currentNode), time)];
6724
+ const actorItems = entry.actor.collectRenderItems({ modelMatrix });
6725
+ items.push(...actorItems);
6726
+ attachProductionActorEvidence(currentNode, entry.actor, actorItems, runtimeNodes);
6727
+ }
6728
+ for (const entry of primitiveEntries) {
6729
+ const currentState = resolveProductionPrimitiveRuntimeState(entry, runtimeNodes);
6730
+ if (!currentState.visible)
6731
+ continue;
6732
+ items.push({
6733
+ geometry: entry.geometry,
6734
+ material: entry.material,
6735
+ modelMatrix: createModelMatrix(currentState.node, entry.bounds, false, time),
6736
+ label: currentState.node.name ?? `aura-primitive-${currentState.node.primitive}`,
6737
+ includeInAutoFrame: false
6738
+ });
6739
+ }
6740
+ const viewProjectionMatrix = createViewProjection(snapshot, canvas.width / Math.max(1, canvas.height), time, runtimeNodes);
6741
+ const cameraPosition = resolveCameraEye(snapshot, snapshot.camera, time, runtimeNodes);
6742
+ const source = {
6743
+ collectRenderItems: () => items,
6744
+ cameraPolicy: "require",
6745
+ staticBatching: false,
6746
+ frustumCulling: false,
6747
+ collectedLights: createProductionRuntimeCollectedLights(),
6748
+ environmentLighting,
6749
+ postprocess: createProductionRuntimePostprocess(),
6750
+ shadow: createProductionRuntimeShadowOptions(),
6751
+ cameraPosition
6752
+ };
6753
+ const cameraLike = { viewProjectionMatrix };
6754
+ return {
6755
+ source,
6756
+ camera: cameraLike,
6757
+ metadata: createProductionRuntimeMetadata(actorEntries)
6758
+ };
6759
+ }
6760
+ function createProductionRuntimePrimitiveEntries(nodes) {
6761
+ return nodes
6762
+ .filter((node) => node.kind === "primitive")
6763
+ .map((node) => ({
6764
+ node,
6765
+ geometry: createProductionPrimitiveGeometry(node),
6766
+ material: createProductionPrimitiveMaterial(node),
6767
+ bounds: primitiveGeometryBounds(node.primitive)
6768
+ }));
6769
+ }
6770
+ function resolveProductionPrimitiveRuntimeState(entry, runtimeNodes) {
6771
+ const runtimeId = entry.node.runtime?.id;
6772
+ if (!runtimeId)
6773
+ return { node: entry.node, visible: true };
6774
+ const runtimeSnapshot = runtimeNodes?.get(runtimeId)?.snapshot();
6775
+ if (!runtimeSnapshot)
6776
+ return { node: entry.node, visible: true };
6777
+ return {
6778
+ node: {
6779
+ ...entry.node,
6780
+ position: runtimeSnapshot.position,
6781
+ rotation: runtimeSnapshot.rotation,
6782
+ scale: runtimeSnapshot.scale,
6783
+ animation: runtimeSnapshot.animation ?? entry.node.animation
6784
+ },
6785
+ visible: runtimeSnapshot.visible
6786
+ };
6787
+ }
6788
+ function createProductionPrimitiveMaterial(node) {
6789
+ const materialSpec = node.material;
6790
+ const baseColor = colorToRgba(materialSpec?.color ?? materialSpec?.emissive ?? "#d7dee8");
6791
+ const opacity = clamp01(materialSpec?.opacity ?? baseColor[3] ?? 1);
6792
+ const emissiveColor = materialSpec?.emissive ? colorToRgb(materialSpec.emissive) : [0, 0, 0];
6793
+ return new PBRMaterial({
6794
+ name: `a3d-production-primitive-${node.primitive}${node.name ? `-${node.name}` : ""}`,
6795
+ baseColor: [baseColor[0], baseColor[1], baseColor[2], opacity],
6796
+ metallic: clamp01(materialSpec?.metallic ?? materialSpec?.metalness ?? 0),
6797
+ roughness: clamp01(materialSpec?.roughness ?? 0.58),
6798
+ emissiveColor,
6799
+ emissiveStrength: Math.max(0, materialSpec?.emissiveIntensity ?? (materialSpec?.emissive ? 1.35 : 0)),
6800
+ clearcoatFactor: clamp01(materialSpec?.clearcoat ?? 0),
6801
+ clearcoatRoughnessFactor: clamp01(materialSpec?.clearcoatRoughness ?? 0.34),
6802
+ environmentIntensity: Math.max(0, materialSpec?.envMapIntensity ?? 0.75),
6803
+ renderState: {
6804
+ blend: opacity < 0.999,
6805
+ depthWrite: opacity >= 0.999,
6806
+ cullMode: node.primitive === "plane" || opacity < 0.999 ? "none" : "back"
6807
+ }
6808
+ });
6809
+ }
6810
+ function createProductionPrimitiveGeometry(node) {
6811
+ return createProductionGeometryFromPrimitiveMesh(createProductionPrimitiveMesh(node.primitive));
6812
+ }
6813
+ function primitiveGeometryBounds(primitive) {
6814
+ return createProductionPrimitiveMesh(primitive).bounds;
6815
+ }
6816
+ function createProductionPrimitiveMesh(primitive) {
6817
+ if (primitive === "sphere")
6818
+ return createSphereGeometry();
6819
+ if (primitive === "capsule")
6820
+ return createCapsuleApproxGeometry();
6821
+ if (primitive === "torus")
6822
+ return createTorusGeometry();
6823
+ if (primitive === "box")
6824
+ return createBoxGeometry();
6825
+ if (primitive === "cylinder")
6826
+ return createCylinderGeometry();
6827
+ return createPlaneGeometry();
6828
+ }
6829
+ function createProductionGeometryFromPrimitiveMesh(mesh) {
6830
+ const vertexCount = mesh.positions.length / 3;
6831
+ const vertices = new VertexBuffer(VertexFormat.P3N3, vertexCount);
6832
+ for (let index = 0; index < vertexCount; index += 1) {
6833
+ const base = index * 3;
6834
+ vertices.setAttribute(index, "position", [mesh.positions[base] ?? 0, mesh.positions[base + 1] ?? 0, mesh.positions[base + 2] ?? 0]);
6835
+ vertices.setAttribute(index, "normal", [mesh.normals[base] ?? 0, mesh.normals[base + 1] ?? 1, mesh.normals[base + 2] ?? 0]);
6836
+ }
6837
+ return new Geometry(vertices, new IndexBuffer(Array.from(mesh.indices), vertexCount), "triangles", mesh.bounds);
6838
+ }
6839
+ function resolveProductionActorRuntimeState(entry, runtimeNodes) {
6840
+ const runtimeId = entry.node.runtime?.id;
6841
+ if (!runtimeId)
6842
+ return { node: entry.node };
6843
+ const runtimeSnapshot = runtimeNodes?.get(runtimeId)?.snapshot();
6844
+ if (!runtimeSnapshot)
6845
+ return { node: entry.node };
6846
+ const morphTargets = runtimeSnapshot.morphTargets ?? {};
6847
+ return {
6848
+ node: {
6849
+ ...entry.node,
6850
+ position: runtimeSnapshot.position,
6851
+ rotation: runtimeSnapshot.rotation,
6852
+ scale: runtimeSnapshot.scale,
6853
+ visible: runtimeSnapshot.visible,
6854
+ animation: runtimeSnapshot.animation ?? entry.node.animation
6855
+ },
6856
+ animationBinding: runtimeSnapshot.animationBinding,
6857
+ ...(Object.keys(morphTargets).length > 0 ? { morphTargets } : {})
6858
+ };
6859
+ }
6860
+ function applyProductionActorAnimation(entry, node, animationBinding, time, runtimeWarnings) {
6861
+ const animation = node.animation;
6862
+ if (!animation?.clip || isModelTransformAnimationClip(animation.clip))
6863
+ return;
6864
+ const clipName = entry.actor.animation.resolveClipName(animation.clip);
6865
+ if (!clipName) {
6866
+ runtimeWarnings.add(`Typed GLB actor "${entry.actor.id}" has no clips for requested animation "${animation.clip}".`);
6867
+ return;
6868
+ }
6869
+ try {
6870
+ entry.actor.playClip(clipName, resolveProductionActorAnimationSeconds(animation, animationBinding, time));
6871
+ }
6872
+ catch (error) {
6873
+ runtimeWarnings.add(`Typed GLB actor "${entry.actor.id}" failed to apply clip "${clipName}": ${productionRenderErrorMessage(error)}`);
6874
+ }
6875
+ }
6876
+ function applyProductionActorMorphTargets(entry, morphTargets, runtimeWarnings) {
6877
+ if (!morphTargets || Object.keys(morphTargets).length === 0)
6878
+ return;
6879
+ try {
6880
+ const result = entry.actor.applyMorphTargets(morphTargets);
6881
+ for (const missing of result.missingTargets) {
6882
+ runtimeWarnings.add(`Typed GLB actor "${entry.actor.id}" has no morph target "${missing}".`);
6883
+ }
6884
+ }
6885
+ catch (error) {
6886
+ runtimeWarnings.add(`Typed GLB actor "${entry.actor.id}" failed to apply morph targets: ${productionRenderErrorMessage(error)}`);
6887
+ }
6888
+ }
6889
+ function attachProductionActorEvidence(node, actor, renderItems, runtimeNodes) {
6890
+ if (!node.runtime?.id || !runtimeNodes?.has(node.runtime.id))
6891
+ return;
6892
+ const evidence = createRuntimeEvidenceFromTypedGLBActor(node, actor, renderItems);
6893
+ runtimeNodes.get(node.runtime.id)?.setImportedAssetEvidence(evidence);
6894
+ }
6895
+ function createRuntimeEvidenceFromTypedGLBActor(node, actor, renderItems) {
6896
+ const actorEvidence = actor.evidence;
6897
+ const skeletonBones = actor.pipeline.asset.skins.flatMap((skin) => skin.jointNames);
6898
+ const morphTargets = actor.pipeline.asset.meshes.flatMap((mesh) => mesh.morphTargets.map((target, index) => target.name ?? `${mesh.name}-morph-${index + 1}`));
6899
+ const morphApply = actorEvidence.lastMorphApply;
6900
+ return createRuntimeNodeImportedAssetEvidence({
6901
+ assetId: node.asset.id,
6902
+ ...(node.runtime?.id ? { nodeId: node.runtime.id } : {}),
6903
+ skeletonBones,
6904
+ clips: actorEvidence.clips,
6905
+ ...(actorEvidence.lastClip ? { activeClip: actorEvidence.lastClip } : {}),
6906
+ ...(actorEvidence.skinningBindingCount > 0 ? {
6907
+ skinningPalette: {
6908
+ jointCount: actorEvidence.skinningBindingCount,
6909
+ matrixCount: actorEvidence.skinningBindingCount,
6910
+ updated: actorEvidence.lastSkinningPalettesUpdated > 0
6911
+ }
6912
+ } : {}),
6913
+ morphTargets,
6914
+ activeMorphTargets: morphApply?.activeWeights ?? {},
6915
+ missingMorphTargets: morphApply?.missingTargets ?? [],
6916
+ bounds: {
6917
+ position: node.position,
6918
+ scale: node.scale,
6919
+ size: node.asset.bounds
6920
+ },
6921
+ renderItemCount: renderItems.length,
6922
+ skinnedRenderItemCount: renderItems.filter((item) => item.skinning).length,
6923
+ morphRenderItemCount: renderItems.filter((item) => item.morphTargets && item.morphTargets.length > 0).length
6924
+ });
6925
+ }
6926
+ function createProductionRuntimeMetadata(actorEntries) {
6927
+ const metadata = actorEntries.map((entry) => entry.actor.pipeline.metadata);
6928
+ const primary = metadata[0];
6929
+ return {
6930
+ assetId: metadata.length === 1 ? primary?.assetId ?? "typed-glb" : `typed-glb-batch-${metadata.length}`,
6931
+ assetUri: metadata.length === 1 ? primary?.assetUri ?? "aura3d://typed-glb" : "aura3d://scene/typed-glb-batch",
6932
+ ...(metadata.length === 1 && primary?.assetName ? { assetName: primary.assetName } : metadata.length > 1 ? { assetName: "Aura3D typed GLB scene batch" } : {}),
6933
+ meshCount: sumProductionMetadata(metadata, "meshCount"),
6934
+ primitiveCount: sumProductionMetadata(metadata, "primitiveCount"),
6935
+ materialCount: sumProductionMetadata(metadata, "materialCount"),
6936
+ textureCount: sumProductionMetadata(metadata, "textureCount"),
6937
+ imageCount: sumProductionMetadata(metadata, "imageCount"),
6938
+ animationCount: sumProductionMetadata(metadata, "animationCount"),
6939
+ skinCount: sumProductionMetadata(metadata, "skinCount"),
6940
+ morphTargetCount: sumProductionMetadata(metadata, "morphTargetCount"),
6941
+ extensionsUsed: [...new Set(metadata.flatMap((item) => item.extensionsUsed))]
6942
+ };
6943
+ }
6944
+ function sumProductionMetadata(metadata, key) {
6945
+ return metadata.reduce((total, item) => total + item[key], 0);
6946
+ }
6947
+ function boundsFromAuraAsset(asset) {
6948
+ const metadataBounds = asset.metadata?.boundsMetadata;
6949
+ const metadataMin = vec3FromReadonly(metadataBounds?.min);
6950
+ const metadataMax = vec3FromReadonly(metadataBounds?.max);
6951
+ if (metadataMin && metadataMax)
6952
+ return { min: metadataMin, max: metadataMax };
6953
+ const metadataSize = vec3FromReadonly(metadataBounds?.size);
6954
+ const metadataCenter = vec3FromReadonly(metadataBounds?.center);
6955
+ if (metadataSize) {
6956
+ const center = metadataCenter ?? [0, metadataSize[1] / 2, 0];
6957
+ return {
6958
+ min: [center[0] - metadataSize[0] / 2, center[1] - metadataSize[1] / 2, center[2] - metadataSize[2] / 2],
6959
+ max: [center[0] + metadataSize[0] / 2, center[1] + metadataSize[1] / 2, center[2] + metadataSize[2] / 2]
6960
+ };
6961
+ }
6962
+ const bounds = asset.bounds;
6963
+ if (!bounds || bounds.length < 3)
6964
+ return { min: [-0.5, 0, -0.5], max: [0.5, 1, 0.5] };
6965
+ const halfX = Math.max(0.001, bounds[0] / 2);
6966
+ const halfZ = Math.max(0.001, bounds[2] / 2);
6967
+ return {
6968
+ min: [-halfX, 0, -halfZ],
6969
+ max: [halfX, Math.max(0.001, bounds[1]), halfZ]
6970
+ };
6971
+ }
6972
+ function vec3FromReadonly(value) {
6973
+ if (!value || value.length < 3)
6974
+ return undefined;
6975
+ const vec = [Number(value[0]), Number(value[1]), Number(value[2])];
6976
+ return vec.every((component) => Number.isFinite(component)) ? vec : undefined;
6977
+ }
6978
+ function colorToRgba(color) {
6979
+ const [r, g, b, a] = colorToClearColor(color);
6980
+ return [r, g, b, a];
5800
6981
  }
5801
6982
  function colorToClearColor(color) {
5802
6983
  if (typeof color === "string" && /^#[0-9a-f]{6}$/i.test(color)) {
@@ -5838,6 +7019,16 @@ function resolveAnimationSeconds(animation, time) {
5838
7019
  return rawSeconds;
5839
7020
  return animation.loop === false ? Math.min(rawSeconds, duration) : rawSeconds % duration;
5840
7021
  }
7022
+ function resolveProductionActorAnimationSeconds(animation, animationBinding, time) {
7023
+ if (!animation || animation.captureTime === undefined || !animationBinding?.controllerId) {
7024
+ return resolveAnimationSeconds(animation, time);
7025
+ }
7026
+ const duration = Math.max(0, animation.duration ?? 0);
7027
+ const phase = Math.max(0, animationBinding.captureTime ?? animationBinding.localTime ?? animation.captureTime);
7028
+ if (duration <= 0)
7029
+ return phase;
7030
+ return animation.loop === false ? Math.min(phase, duration) : phase % duration;
7031
+ }
5841
7032
  function orbitAnimatedAngle(seconds, speed) {
5842
7033
  return seconds * speed * 0.58;
5843
7034
  }
@@ -5917,17 +7108,59 @@ function seededRange(index, salt, min, max) {
5917
7108
  const normalized = value - Math.floor(value);
5918
7109
  return min + (max - min) * normalized;
5919
7110
  }
5920
- function resolveCameraTarget(snapshot, cameraSpec) {
7111
+ function findRuntimeCameraTarget(cameraSpec, runtimeNodes) {
7112
+ if (cameraSpec.mode !== "follow" || !cameraSpec.targetNode || !runtimeNodes)
7113
+ return undefined;
7114
+ const targetNode = cameraSpec.targetNode;
7115
+ const directHandle = runtimeNodes.get(targetNode);
7116
+ if (directHandle && directHandle.visible !== false)
7117
+ return { position: directHandle.position, rotation: directHandle.rotation };
7118
+ const namedHandle = runtimeNodes.all().find((handle) => handle.visible !== false && (handle.name === targetNode || handle.tags.includes(targetNode)));
7119
+ return namedHandle ? { position: namedHandle.position, rotation: namedHandle.rotation } : undefined;
7120
+ }
7121
+ function rotateVec3BySceneYaw(vector, yaw) {
7122
+ const cos = Math.cos(yaw);
7123
+ const sin = Math.sin(yaw);
7124
+ return [
7125
+ vector[0] * cos + vector[2] * sin,
7126
+ vector[1],
7127
+ -vector[0] * sin + vector[2] * cos
7128
+ ];
7129
+ }
7130
+ function applyCameraOffset(cameraSpec, offset, target) {
7131
+ if (cameraSpec.offsetMode !== "target-yaw" || !target)
7132
+ return offset;
7133
+ return rotateVec3BySceneYaw(offset, target.rotation[1]);
7134
+ }
7135
+ function add3(left, right) {
7136
+ return [left[0] + right[0], left[1] + right[1], left[2] + right[2]];
7137
+ }
7138
+ function resolveRuntimeCameraTarget(cameraSpec, runtimeNodes) {
7139
+ return findRuntimeCameraTarget(cameraSpec, runtimeNodes);
7140
+ }
7141
+ function resolveCameraTarget(snapshot, cameraSpec, runtimeNodes) {
7142
+ const runtimeTarget = resolveRuntimeCameraTarget(cameraSpec, runtimeNodes);
7143
+ if (runtimeTarget) {
7144
+ const targetOffset = cameraSpec.targetOffset
7145
+ ? applyCameraOffset(cameraSpec, cameraSpec.targetOffset, runtimeTarget)
7146
+ : [0, 0, 0];
7147
+ return add3(runtimeTarget.position, targetOffset);
7148
+ }
5921
7149
  if (cameraSpec.mode === "follow" && cameraSpec.targetNode) {
5922
- const targetNode = snapshot.nodes.find((node) => (node.kind === "model" || node.kind === "primitive") &&
5923
- (node.name === cameraSpec.targetNode || (node.kind === "model" && node.asset.id === cameraSpec.targetNode)));
5924
- if (targetNode?.position)
5925
- return targetNode.position;
7150
+ const targetNode = groups.flatten(snapshot.nodes).find((node) => (node.kind === "model" || node.kind === "primitive") &&
7151
+ (node.name === cameraSpec.targetNode ||
7152
+ (node.kind === "model" && node.asset.id === cameraSpec.targetNode) ||
7153
+ node.runtime?.id === cameraSpec.targetNode));
7154
+ if (targetNode?.position) {
7155
+ const targetOffset = cameraSpec.targetOffset ?? [0, 0, 0];
7156
+ return add3(targetNode.position, targetOffset);
7157
+ }
5926
7158
  }
5927
7159
  return cameraSpec.target ?? [0, 0.7, 0];
5928
7160
  }
5929
- function resolveCameraEye(snapshot, cameraSpec, time) {
5930
- const target = resolveCameraTarget(snapshot, cameraSpec);
7161
+ function resolveCameraEye(snapshot, cameraSpec, time, runtimeNodes) {
7162
+ const target = resolveCameraTarget(snapshot, cameraSpec, runtimeNodes);
7163
+ const runtimeTarget = resolveRuntimeCameraTarget(cameraSpec, runtimeNodes);
5931
7164
  let eye = cameraSpec.position ?? [0, 1.4, cameraSpec.distance ?? 4];
5932
7165
  if (cameraSpec.mode === "orbit") {
5933
7166
  const distance = cameraSpec.distance ?? 4;
@@ -5935,7 +7168,11 @@ function resolveCameraEye(snapshot, cameraSpec, time) {
5935
7168
  }
5936
7169
  if (cameraSpec.mode === "follow") {
5937
7170
  const distance = cameraSpec.distance ?? 4;
5938
- eye = cameraSpec.position ?? [target[0] - distance * 0.38, target[1] + distance * 0.52, target[2] + distance * 0.82];
7171
+ const offset = cameraSpec.offset ? applyCameraOffset(cameraSpec, cameraSpec.offset, runtimeTarget) : undefined;
7172
+ eye = cameraSpec.position
7173
+ ?? (offset
7174
+ ? [target[0] + offset[0], target[1] + offset[1], target[2] + offset[2]]
7175
+ : [target[0] - distance * 0.38, target[1] + distance * 0.52, target[2] + distance * 0.82]);
5939
7176
  }
5940
7177
  if (cameraSpec.mode === "dolly") {
5941
7178
  const seconds = cameraSpec.seconds ?? 6;
@@ -5962,7 +7199,7 @@ function collectRuntimeEffectNodes(snapshot) {
5962
7199
  function hasRuntimePostProcessEffects(effectNodes) {
5963
7200
  return effectNodes.some((node) => node.effect === "bloom" || node.effect === "ambient-occlusion" || node.effect === "contact-occlusion");
5964
7201
  }
5965
- async function createWebGLSceneRenderer(canvas, snapshot) {
7202
+ async function createWebGLSceneRenderer(canvas, snapshot, rendererOptions, runtimeWarnings = [], runtimeNodes) {
5966
7203
  const gl = canvas.getContext("webgl2", { antialias: true, preserveDrawingBuffer: true });
5967
7204
  if (!gl) {
5968
7205
  throw new AuraRuntimeError("backend-fallback", "Aura3D could not create a WebGL2 renderer. Suggested fix: use a WebGL2-capable browser.");
@@ -5998,9 +7235,13 @@ async function createWebGLSceneRenderer(canvas, snapshot) {
5998
7235
  actualPasses: [],
5999
7236
  fallbackPasses: hasRuntimePostProcessEffects(requestedEffectNodes) ? ["webgl2-direct-render"] : []
6000
7237
  },
6001
- warnings: ["Aura3D WebGL2 agent runtime renders typed models, primitives, and basic effects; advanced postprocess, environment prefiltering, shadow maps, and GLB animation mixers are reported as unsupported until the production renderer adapter covers them."]
6002
- });
7238
+ warnings: [
7239
+ "Aura3D WebGL2 agent runtime renders typed models, primitives, and basic effects; advanced postprocess, environment prefiltering, shadow maps, and GLB animation mixers are reported as unsupported unless a route uses the typed-GLB production bridge.",
7240
+ ...runtimeWarnings
7241
+ ]
7242
+ }, rendererOptions);
6003
7243
  return {
7244
+ backend: "webgl2",
6004
7245
  diagnostics: runtimeRendererDiagnostics,
6005
7246
  render(time) {
6006
7247
  gl.viewport(0, 0, canvas.width, canvas.height);
@@ -6009,14 +7250,23 @@ async function createWebGLSceneRenderer(canvas, snapshot) {
6009
7250
  let drawCalls = backdrop.render();
6010
7251
  gl.useProgram(program.program);
6011
7252
  gl.enable(gl.DEPTH_TEST);
6012
- const viewProjection = createViewProjection(snapshot, canvas.width / Math.max(1, canvas.height), time);
7253
+ const viewProjection = createViewProjection(snapshot, canvas.width / Math.max(1, canvas.height), time, runtimeNodes);
6013
7254
  gl.uniformMatrix4fv(program.uniforms.viewProjection, false, viewProjection);
6014
7255
  gl.uniform3fv(program.uniforms.lightDirection, new Float32Array(normalize3([0.45, 0.82, 0.36])));
7256
+ gl.uniform1i(program.uniforms.baseColorTexture, 0);
7257
+ gl.uniform1i(program.uniforms.metallicRoughnessTexture, 1);
7258
+ gl.uniform1i(program.uniforms.occlusionTexture, 2);
7259
+ gl.uniform1i(program.uniforms.emissiveTexture, 3);
6015
7260
  for (const modelEntry of models) {
7261
+ modelEntry.update?.(time);
6016
7262
  const modelMatrix = modelEntry.modelMatrix ?? createModelMatrix(modelEntry.node, modelEntry.bounds, modelEntry.normalizeToUnit, time);
6017
- gl.uniformMatrix4fv(program.uniforms.model, false, modelMatrix);
6018
7263
  for (const primitiveEntry of modelEntry.primitives) {
7264
+ const primitiveMatrix = primitiveEntry.modelMatrix ? multiply4(modelMatrix, primitiveEntry.modelMatrix(time)) : modelMatrix;
7265
+ gl.uniformMatrix4fv(program.uniforms.model, false, primitiveMatrix);
6019
7266
  gl.uniform3fv(program.uniforms.color, new Float32Array(primitiveEntry.color ?? modelEntry.color));
7267
+ gl.uniform1f(program.uniforms.metallic, primitiveEntry.metallic ?? 0);
7268
+ gl.uniform1f(program.uniforms.roughness, primitiveEntry.roughness ?? 0.72);
7269
+ gl.uniform3fv(program.uniforms.emissive, new Float32Array(primitiveEntry.emissive ?? [0, 0, 0]));
6020
7270
  gl.bindBuffer(gl.ARRAY_BUFFER, primitiveEntry.position);
6021
7271
  gl.enableVertexAttribArray(program.attributes.position);
6022
7272
  gl.vertexAttribPointer(program.attributes.position, 3, gl.FLOAT, false, 0, 0);
@@ -6032,6 +7282,55 @@ async function createWebGLSceneRenderer(canvas, snapshot) {
6032
7282
  gl.disableVertexAttribArray(program.attributes.color);
6033
7283
  gl.vertexAttrib3f(program.attributes.color, 1, 1, 1);
6034
7284
  }
7285
+ if (primitiveEntry.uv) {
7286
+ gl.bindBuffer(gl.ARRAY_BUFFER, primitiveEntry.uv);
7287
+ gl.enableVertexAttribArray(program.attributes.uv);
7288
+ gl.vertexAttribPointer(program.attributes.uv, 2, gl.FLOAT, false, 0, 0);
7289
+ }
7290
+ else {
7291
+ gl.disableVertexAttribArray(program.attributes.uv);
7292
+ gl.vertexAttrib2f(program.attributes.uv, 0, 0);
7293
+ }
7294
+ if (primitiveEntry.texture) {
7295
+ gl.activeTexture(gl.TEXTURE0);
7296
+ gl.bindTexture(gl.TEXTURE_2D, primitiveEntry.texture);
7297
+ gl.uniform1i(program.uniforms.useTexture, 1);
7298
+ }
7299
+ else {
7300
+ gl.activeTexture(gl.TEXTURE0);
7301
+ gl.bindTexture(gl.TEXTURE_2D, null);
7302
+ gl.uniform1i(program.uniforms.useTexture, 0);
7303
+ }
7304
+ if (primitiveEntry.metallicRoughnessTexture) {
7305
+ gl.activeTexture(gl.TEXTURE1);
7306
+ gl.bindTexture(gl.TEXTURE_2D, primitiveEntry.metallicRoughnessTexture);
7307
+ gl.uniform1i(program.uniforms.useMetallicRoughnessTexture, 1);
7308
+ }
7309
+ else {
7310
+ gl.activeTexture(gl.TEXTURE1);
7311
+ gl.bindTexture(gl.TEXTURE_2D, null);
7312
+ gl.uniform1i(program.uniforms.useMetallicRoughnessTexture, 0);
7313
+ }
7314
+ if (primitiveEntry.occlusionTexture) {
7315
+ gl.activeTexture(gl.TEXTURE2);
7316
+ gl.bindTexture(gl.TEXTURE_2D, primitiveEntry.occlusionTexture);
7317
+ gl.uniform1i(program.uniforms.useOcclusionTexture, 1);
7318
+ }
7319
+ else {
7320
+ gl.activeTexture(gl.TEXTURE2);
7321
+ gl.bindTexture(gl.TEXTURE_2D, null);
7322
+ gl.uniform1i(program.uniforms.useOcclusionTexture, 0);
7323
+ }
7324
+ if (primitiveEntry.emissiveTexture) {
7325
+ gl.activeTexture(gl.TEXTURE3);
7326
+ gl.bindTexture(gl.TEXTURE_2D, primitiveEntry.emissiveTexture);
7327
+ gl.uniform1i(program.uniforms.useEmissiveTexture, 1);
7328
+ }
7329
+ else {
7330
+ gl.activeTexture(gl.TEXTURE3);
7331
+ gl.bindTexture(gl.TEXTURE_2D, null);
7332
+ gl.uniform1i(program.uniforms.useEmissiveTexture, 0);
7333
+ }
6035
7334
  if (primitiveEntry.index && primitiveEntry.indexType) {
6036
7335
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, primitiveEntry.index);
6037
7336
  gl.drawElements(primitiveEntry.mode, primitiveEntry.count, primitiveEntry.indexType, 0);
@@ -6046,12 +7345,25 @@ async function createWebGLSceneRenderer(canvas, snapshot) {
6046
7345
  },
6047
7346
  dispose() {
6048
7347
  for (const modelEntry of models) {
7348
+ const textures = new Set();
6049
7349
  for (const primitiveEntry of modelEntry.primitives) {
6050
7350
  gl.deleteBuffer(primitiveEntry.position);
6051
7351
  gl.deleteBuffer(primitiveEntry.normal);
7352
+ if (primitiveEntry.uv)
7353
+ gl.deleteBuffer(primitiveEntry.uv);
6052
7354
  if (primitiveEntry.index)
6053
7355
  gl.deleteBuffer(primitiveEntry.index);
7356
+ if (primitiveEntry.texture)
7357
+ textures.add(primitiveEntry.texture);
7358
+ if (primitiveEntry.metallicRoughnessTexture)
7359
+ textures.add(primitiveEntry.metallicRoughnessTexture);
7360
+ if (primitiveEntry.occlusionTexture)
7361
+ textures.add(primitiveEntry.occlusionTexture);
7362
+ if (primitiveEntry.emissiveTexture)
7363
+ textures.add(primitiveEntry.emissiveTexture);
6054
7364
  }
7365
+ for (const texture of textures)
7366
+ gl.deleteTexture(texture);
6055
7367
  }
6056
7368
  gl.deleteProgram(program.program);
6057
7369
  backdrop.dispose();
@@ -6145,27 +7457,156 @@ function createBackdropPalette(snapshot) {
6145
7457
  };
6146
7458
  }
6147
7459
  function createWebGLModel(gl, node, modelData) {
7460
+ const textures = new Map();
7461
+ const textureFor = (index) => {
7462
+ if (index === undefined)
7463
+ return undefined;
7464
+ const existing = textures.get(index);
7465
+ if (existing)
7466
+ return existing;
7467
+ const source = modelData.textures[index];
7468
+ if (!source)
7469
+ return undefined;
7470
+ const texture = createTexture2D(gl, source.image);
7471
+ textures.set(index, texture);
7472
+ return texture;
7473
+ };
6148
7474
  return {
6149
7475
  node,
6150
7476
  bounds: modelData.bounds,
6151
7477
  color: colorToRgb(node.material?.color ?? "#8fb4ff"),
6152
- normalizeToUnit: true,
7478
+ normalizeToUnit: shouldNormalizeModelNode(node),
6153
7479
  primitives: modelData.primitives.map((primitiveEntry) => {
6154
7480
  const position = createBuffer(gl, gl.ARRAY_BUFFER, primitiveEntry.positions);
6155
7481
  const normal = createBuffer(gl, gl.ARRAY_BUFFER, primitiveEntry.normals);
7482
+ const uv = primitiveEntry.uvs ? createBuffer(gl, gl.ARRAY_BUFFER, primitiveEntry.uvs) : undefined;
6156
7483
  const index = primitiveEntry.indices ? createBuffer(gl, gl.ELEMENT_ARRAY_BUFFER, primitiveEntry.indices) : undefined;
7484
+ const texture = textureFor(primitiveEntry.textureIndex);
7485
+ const metallicRoughnessTexture = textureFor(primitiveEntry.metallicRoughnessTextureIndex);
7486
+ const occlusionTexture = textureFor(primitiveEntry.occlusionTextureIndex);
7487
+ const emissiveTexture = textureFor(primitiveEntry.emissiveTextureIndex);
7488
+ const primitiveModelMatrix = createGltfPrimitiveModelMatrixResolver(modelData, node.animation, primitiveEntry);
6157
7489
  return {
6158
7490
  position,
6159
7491
  normal,
7492
+ ...(uv ? { uv } : {}),
6160
7493
  ...(index ? { index } : {}),
6161
7494
  count: primitiveEntry.indices?.length ?? primitiveEntry.positions.length / 3,
6162
7495
  mode: webglDrawMode(gl, primitiveEntry.mode),
6163
7496
  color: node.material?.color ? colorToRgb(node.material.color) : primitiveEntry.color,
7497
+ ...(texture ? { texture } : {}),
7498
+ ...(metallicRoughnessTexture ? { metallicRoughnessTexture } : {}),
7499
+ ...(occlusionTexture ? { occlusionTexture } : {}),
7500
+ ...(emissiveTexture ? { emissiveTexture } : {}),
7501
+ ...(primitiveEntry.metallic !== undefined ? { metallic: primitiveEntry.metallic } : {}),
7502
+ ...(primitiveEntry.roughness !== undefined ? { roughness: primitiveEntry.roughness } : {}),
7503
+ ...(primitiveEntry.emissive ? { emissive: primitiveEntry.emissive } : {}),
7504
+ ...(primitiveModelMatrix ? { modelMatrix: primitiveModelMatrix } : {}),
6164
7505
  ...(primitiveEntry.indices ? { indexType: primitiveEntry.indices instanceof Uint32Array ? gl.UNSIGNED_INT : gl.UNSIGNED_SHORT } : {})
6165
7506
  };
6166
7507
  })
6167
7508
  };
6168
7509
  }
7510
+ function createGltfPrimitiveModelMatrixResolver(modelData, animation, primitiveEntry) {
7511
+ const staticMatrix = primitiveEntry.staticWorldMatrix;
7512
+ const nodeIndex = primitiveEntry.nodeIndex;
7513
+ if (nodeIndex === undefined)
7514
+ return staticMatrix ? () => staticMatrix : undefined;
7515
+ const clip = animation?.clip ? findGltfAnimationClip(modelData, animation.clip) : undefined;
7516
+ if (!clip)
7517
+ return staticMatrix ? () => staticMatrix : undefined;
7518
+ return (time) => resolveGltfAnimatedNodeMatrix(modelData, clip, animation, nodeIndex, time) ?? staticMatrix ?? identity4();
7519
+ }
7520
+ function findGltfAnimationClip(modelData, requestedClip) {
7521
+ const requested = requestedClip.trim().toLowerCase();
7522
+ if (!requested)
7523
+ return undefined;
7524
+ return modelData.animations.find((clip) => clip.name === requestedClip)
7525
+ ?? modelData.animations.find((clip) => clip.name.toLowerCase() === requested);
7526
+ }
7527
+ function resolveGltfAnimatedNodeMatrix(modelData, clip, animation, nodeIndex, time) {
7528
+ if (nodeIndex < 0 || nodeIndex >= modelData.nodes.length)
7529
+ return undefined;
7530
+ const seconds = resolveGltfAnimationSeconds(animation, clip, time);
7531
+ const poses = modelData.nodes.map((node) => ({
7532
+ translation: [...node.baseTranslation],
7533
+ rotation: [...node.baseRotation],
7534
+ scale: [...node.baseScale],
7535
+ changed: false
7536
+ }));
7537
+ for (const channel of clip.channels) {
7538
+ const pose = poses[channel.nodeIndex];
7539
+ if (!pose)
7540
+ continue;
7541
+ pose.changed = true;
7542
+ if (channel.path === "rotation")
7543
+ pose.rotation = sampleGltfQuaternionChannel(channel, seconds);
7544
+ else if (channel.path === "scale")
7545
+ pose.scale = sampleGltfVec3Channel(channel, seconds);
7546
+ else
7547
+ pose.translation = sampleGltfVec3Channel(channel, seconds);
7548
+ }
7549
+ const localMatrices = modelData.nodes.map((node, index) => {
7550
+ const pose = poses[index];
7551
+ return pose?.changed ? gltfTrsMatrix(pose.translation, pose.rotation, pose.scale) : node.baseMatrix;
7552
+ });
7553
+ const worldMatrices = new Array(modelData.nodes.length);
7554
+ const visited = new Set();
7555
+ const visit = (currentIndex, parentMatrix) => {
7556
+ if (visited.has(currentIndex))
7557
+ return;
7558
+ const currentNode = modelData.nodes[currentIndex];
7559
+ const localMatrix = localMatrices[currentIndex];
7560
+ if (!currentNode || !localMatrix)
7561
+ return;
7562
+ visited.add(currentIndex);
7563
+ const worldMatrix = multiply4(parentMatrix, localMatrix);
7564
+ worldMatrices[currentIndex] = worldMatrix;
7565
+ for (const childIndex of currentNode.children)
7566
+ visit(childIndex, worldMatrix);
7567
+ };
7568
+ const roots = modelData.sceneRoots.length > 0 ? modelData.sceneRoots : modelData.nodes.map((_, index) => index);
7569
+ for (const rootIndex of roots)
7570
+ visit(rootIndex, identity4());
7571
+ for (let index = 0; index < modelData.nodes.length; index += 1) {
7572
+ if (!visited.has(index))
7573
+ visit(index, identity4());
7574
+ }
7575
+ return worldMatrices[nodeIndex];
7576
+ }
7577
+ function resolveGltfAnimationSeconds(animation, clip, time) {
7578
+ const speed = Math.max(0.05, animation?.speed ?? 1);
7579
+ const startTime = animation?.startTime ?? 0;
7580
+ const phase = Math.max(0, animation?.captureTime ?? 0);
7581
+ const rawSeconds = Math.max(0, time / 1000 - startTime) * speed;
7582
+ const seconds = phase + rawSeconds;
7583
+ const duration = Math.max(0, animation?.duration ?? clip.duration);
7584
+ if (duration <= 0)
7585
+ return seconds;
7586
+ return animation?.loop === false ? Math.min(seconds, duration) : seconds % duration;
7587
+ }
7588
+ function createTexture2D(gl, image) {
7589
+ const texture = gl.createTexture();
7590
+ if (!texture)
7591
+ throw new AuraRuntimeError("backend-fallback", "Aura3D WebGL2 texture allocation failed. Suggested fix: reduce texture count or reload the page.");
7592
+ gl.bindTexture(gl.TEXTURE_2D, texture);
7593
+ gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
7594
+ gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
7595
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
7596
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
7597
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
7598
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
7599
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
7600
+ const anisotropic = gl.getExtension("EXT_texture_filter_anisotropic")
7601
+ ?? gl.getExtension("WEBKIT_EXT_texture_filter_anisotropic")
7602
+ ?? gl.getExtension("MOZ_EXT_texture_filter_anisotropic");
7603
+ if (anisotropic) {
7604
+ const maxAnisotropy = Number(gl.getParameter(anisotropic.MAX_TEXTURE_MAX_ANISOTROPY_EXT)) || 1;
7605
+ gl.texParameterf(gl.TEXTURE_2D, anisotropic.TEXTURE_MAX_ANISOTROPY_EXT, Math.min(8, maxAnisotropy));
7606
+ }
7607
+ gl.generateMipmap(gl.TEXTURE_2D);
7608
+ return texture;
7609
+ }
6169
7610
  function createWebGLPrimitiveModel(gl, node) {
6170
7611
  const primitive = node.primitive === "sphere"
6171
7612
  ? createSphereGeometry()
@@ -6189,7 +7630,10 @@ function createWebGLPrimitiveModel(gl, node) {
6189
7630
  index: createBuffer(gl, gl.ELEMENT_ARRAY_BUFFER, primitive.indices),
6190
7631
  count: primitive.indices.length,
6191
7632
  mode: gl.TRIANGLES,
6192
- indexType: gl.UNSIGNED_SHORT
7633
+ indexType: gl.UNSIGNED_SHORT,
7634
+ metallic: clamp01(node.material?.metallic ?? node.material?.metalness ?? 0),
7635
+ roughness: clamp01(node.material?.roughness ?? 0.72),
7636
+ emissive: node.material?.emissive ? colorToRgb(node.material.emissive) : [0, 0, 0]
6193
7637
  }]
6194
7638
  };
6195
7639
  }
@@ -6278,53 +7722,66 @@ function createWebGLParticleModel(gl, effect) {
6278
7722
  ? ["#fff7ad", "#fef08a", "#fb923c", "#60a5fa", "#38bdf8", "#fb7185"]
6279
7723
  : [effect.color ?? "#7dfcff", "#ffd166", "#60a5fa"]);
6280
7724
  const sizeCurve = effect.sizeOverLife ?? [0.35, 1, 0.58];
6281
- for (let index = 0; index < count; index += 1) {
6282
- writeParticlePosition(center, 0, 0, effect.emitter ?? "swirl", radius, height, index, turbulence, gravity, groundCollision, fountainLayer);
6283
- const life = getParticleLife(index, 0, effect.emitter ?? "swirl");
6284
- const sizeLife = life < 0.5
6285
- ? (sizeCurve[0] ?? 0.35) + ((sizeCurve[1] ?? 1) - (sizeCurve[0] ?? 0.35)) * (life / 0.5)
6286
- : (sizeCurve[1] ?? 1) + ((sizeCurve[2] ?? 0.58) - (sizeCurve[1] ?? 1)) * ((life - 0.5) / 0.5);
6287
- const baseSize = isFountain
6288
- ? fountainLayer === "mist"
6289
- ? 0.026
6290
- : fountainLayer === "splash"
6291
- ? 0.058
6292
- : 0.074
6293
- : materialMode === "dust" || materialMode === "smoke"
6294
- ? 0.032
6295
- : 0.044;
6296
- const size = baseSize * Math.max(0.45, sizeLife) * seededRange(index, 353, 0.78, 1.22);
6297
- const particleColor = colorToRgb(lifetimeRamp[index % lifetimeRamp.length] ?? effect.color ?? "#7dfcff");
6298
- const vertexBase = index * 6;
6299
- const positionBase = vertexBase * 3;
6300
- for (let vertex = 0; vertex < localVertices.length; vertex += 1) {
6301
- const local = localVertices[vertex];
6302
- const offset = positionBase + vertex * 3;
6303
- positions[offset] = center[0] + local[0] * size;
6304
- positions[offset + 1] = center[1] + local[1] * size;
6305
- positions[offset + 2] = center[2] + local[2] * size;
6306
- normals[offset] = 0.45;
6307
- normals[offset + 1] = 0.82;
6308
- normals[offset + 2] = 0.36;
6309
- colors[offset] = particleColor[0];
6310
- colors[offset + 1] = particleColor[1];
6311
- colors[offset + 2] = particleColor[2];
6312
- }
6313
- const indexBase = index * 24;
6314
- for (let tri = 0; tri < localTriangles.length; tri += 1) {
6315
- const local = localTriangles[tri];
6316
- indices[indexBase + tri * 3] = vertexBase + local[0];
6317
- indices[indexBase + tri * 3 + 1] = vertexBase + local[1];
6318
- indices[indexBase + tri * 3 + 2] = vertexBase + local[2];
7725
+ const writeParticleVertices = (seconds) => {
7726
+ const emitter = effect.emitter ?? "swirl";
7727
+ for (let index = 0; index < count; index += 1) {
7728
+ writeParticlePosition(center, 0, seconds, emitter, radius, height, index, turbulence, gravity, groundCollision, fountainLayer);
7729
+ const life = getParticleLife(index, seconds, emitter);
7730
+ const sizeLife = life < 0.5
7731
+ ? (sizeCurve[0] ?? 0.35) + ((sizeCurve[1] ?? 1) - (sizeCurve[0] ?? 0.35)) * (life / 0.5)
7732
+ : (sizeCurve[1] ?? 1) + ((sizeCurve[2] ?? 0.58) - (sizeCurve[1] ?? 1)) * ((life - 0.5) / 0.5);
7733
+ const baseSize = isFountain
7734
+ ? fountainLayer === "mist"
7735
+ ? 0.012
7736
+ : fountainLayer === "splash"
7737
+ ? 0.024
7738
+ : 0.032
7739
+ : materialMode === "dust" || materialMode === "smoke"
7740
+ ? 0.011
7741
+ : materialMode === "star" || materialMode === "spark"
7742
+ ? 0.016
7743
+ : 0.022;
7744
+ const size = baseSize * Math.max(0.38, sizeLife) * seededRange(index, 353, 0.72, 1.14);
7745
+ const particleColor = colorToRgb(lifetimeRamp[index % lifetimeRamp.length] ?? effect.color ?? "#7dfcff");
7746
+ const vertexBase = index * 6;
7747
+ const positionBase = vertexBase * 3;
7748
+ for (let vertex = 0; vertex < localVertices.length; vertex += 1) {
7749
+ const local = localVertices[vertex];
7750
+ const offset = positionBase + vertex * 3;
7751
+ positions[offset] = center[0] + local[0] * size;
7752
+ positions[offset + 1] = center[1] + local[1] * size;
7753
+ positions[offset + 2] = center[2] + local[2] * size;
7754
+ normals[offset] = 0.45;
7755
+ normals[offset + 1] = 0.82;
7756
+ normals[offset + 2] = 0.36;
7757
+ colors[offset] = particleColor[0];
7758
+ colors[offset + 1] = particleColor[1];
7759
+ colors[offset + 2] = particleColor[2];
7760
+ }
7761
+ const indexBase = index * 24;
7762
+ for (let tri = 0; tri < localTriangles.length; tri += 1) {
7763
+ const local = localTriangles[tri];
7764
+ indices[indexBase + tri * 3] = vertexBase + local[0];
7765
+ indices[indexBase + tri * 3 + 1] = vertexBase + local[1];
7766
+ indices[indexBase + tri * 3 + 2] = vertexBase + local[2];
7767
+ }
6319
7768
  }
6320
- }
7769
+ };
7770
+ writeParticleVertices(0);
7771
+ const position = createBuffer(gl, gl.ARRAY_BUFFER, positions);
6321
7772
  return {
7773
+ node: effect,
6322
7774
  bounds: { min: [-radius * 2, 0, -radius * 2], max: [radius * 2, height, radius * 2] },
6323
7775
  color: [1, 1, 1],
6324
7776
  normalizeToUnit: false,
6325
- modelMatrix: identity4(),
7777
+ update(time) {
7778
+ const seconds = time * Math.max(0.05, effect.speed ?? 1);
7779
+ writeParticleVertices(seconds);
7780
+ gl.bindBuffer(gl.ARRAY_BUFFER, position);
7781
+ gl.bufferSubData(gl.ARRAY_BUFFER, 0, positions);
7782
+ },
6326
7783
  primitives: [{
6327
- position: createBuffer(gl, gl.ARRAY_BUFFER, positions),
7784
+ position,
6328
7785
  normal: createBuffer(gl, gl.ARRAY_BUFFER, normals),
6329
7786
  vertexColor: createBuffer(gl, gl.ARRAY_BUFFER, colors),
6330
7787
  index: createBuffer(gl, gl.ELEMENT_ARRAY_BUFFER, indices),
@@ -6348,16 +7805,19 @@ precision highp float;
6348
7805
  in vec3 a_position;
6349
7806
  in vec3 a_normal;
6350
7807
  in vec3 a_color;
7808
+ in vec2 a_uv;
6351
7809
  uniform mat4 u_model;
6352
7810
  uniform mat4 u_viewProjection;
6353
7811
  out vec3 v_normal;
6354
7812
  out vec3 v_world;
6355
7813
  out vec3 v_color;
7814
+ out vec2 v_uv;
6356
7815
  void main() {
6357
7816
  vec4 world = u_model * vec4(a_position, 1.0);
6358
7817
  v_world = world.xyz;
6359
7818
  v_normal = normalize(mat3(u_model) * a_normal);
6360
7819
  v_color = a_color;
7820
+ v_uv = a_uv;
6361
7821
  gl_Position = u_viewProjection * world;
6362
7822
  }`);
6363
7823
  const fragment = compileShader(gl, gl.FRAGMENT_SHADER, `#version 300 es
@@ -6365,16 +7825,48 @@ precision highp float;
6365
7825
  in vec3 v_normal;
6366
7826
  in vec3 v_world;
6367
7827
  in vec3 v_color;
7828
+ in vec2 v_uv;
6368
7829
  uniform vec3 u_color;
6369
7830
  uniform vec3 u_lightDirection;
7831
+ uniform sampler2D u_baseColorTexture;
7832
+ uniform int u_useTexture;
7833
+ uniform sampler2D u_metallicRoughnessTexture;
7834
+ uniform int u_useMetallicRoughnessTexture;
7835
+ uniform sampler2D u_occlusionTexture;
7836
+ uniform int u_useOcclusionTexture;
7837
+ uniform sampler2D u_emissiveTexture;
7838
+ uniform int u_useEmissiveTexture;
7839
+ uniform float u_metallic;
7840
+ uniform float u_roughness;
7841
+ uniform vec3 u_emissive;
6370
7842
  out vec4 outColor;
6371
7843
  void main() {
6372
7844
  vec3 normal = normalize(v_normal);
6373
- float key = max(dot(normal, normalize(u_lightDirection)), 0.0);
6374
- float rim = pow(1.0 - max(dot(normal, normalize(vec3(0.0, 0.35, 1.0))), 0.0), 2.0);
6375
- vec3 base = u_color * v_color;
6376
- vec3 color = base * (0.34 + key * 0.66) + vec3(0.35, 0.65, 1.0) * rim * 0.12;
6377
- outColor = vec4(pow(color, vec3(1.0 / 2.2)), 1.0);
7845
+ vec3 lightDirection = normalize(u_lightDirection);
7846
+ vec3 viewDirection = normalize(vec3(0.0, 0.34, 1.0));
7847
+ vec3 halfVector = normalize(lightDirection + viewDirection);
7848
+ float key = max(dot(normal, lightDirection), 0.0);
7849
+ float wrap = max(dot(normal, normalize(vec3(-0.36, 0.52, -0.28))), 0.0);
7850
+ float fresnel = pow(1.0 - max(dot(normal, viewDirection), 0.0), 4.0);
7851
+ float rim = pow(1.0 - max(dot(normal, viewDirection), 0.0), 2.0);
7852
+ vec4 texel = u_useTexture == 1 ? texture(u_baseColorTexture, v_uv) : vec4(1.0);
7853
+ vec4 mrTexel = u_useMetallicRoughnessTexture == 1 ? texture(u_metallicRoughnessTexture, v_uv) : vec4(1.0);
7854
+ vec3 emissiveTexel = u_useEmissiveTexture == 1 ? texture(u_emissiveTexture, v_uv).rgb : vec3(1.0);
7855
+ float ao = u_useOcclusionTexture == 1 ? mix(0.45, 1.0, texture(u_occlusionTexture, v_uv).r) : 1.0;
7856
+ float roughness = clamp(u_roughness * mrTexel.g, 0.045, 1.0);
7857
+ float metallic = clamp(u_metallic * mrTexel.b, 0.0, 1.0);
7858
+ vec3 base = u_color * v_color * texel.rgb;
7859
+ vec3 dielectricSpecular = vec3(0.045);
7860
+ vec3 specularColor = mix(dielectricSpecular, base, metallic);
7861
+ float specularPower = mix(96.0, 10.0, roughness);
7862
+ float specular = pow(max(dot(normal, halfVector), 0.0), specularPower) * mix(0.95, 0.18, roughness);
7863
+ vec3 diffuse = base * (1.0 - metallic) * (0.22 + key * 0.78 + wrap * 0.12);
7864
+ vec3 environment = mix(vec3(0.10, 0.15, 0.18), vec3(0.74, 0.86, 1.0), normal.y * 0.5 + 0.5);
7865
+ vec3 reflection = environment * specularColor * (fresnel * 0.62 + (1.0 - roughness) * 0.22);
7866
+ vec3 emissive = u_emissive * emissiveTexel;
7867
+ vec3 color = (diffuse + specularColor * specular + reflection) * ao + emissive + vec3(0.35, 0.65, 1.0) * rim * 0.075;
7868
+ color = color / (color + vec3(1.0));
7869
+ outColor = vec4(pow(color, vec3(1.0 / 2.2)), texel.a);
6378
7870
  }`);
6379
7871
  const program = gl.createProgram();
6380
7872
  if (!program)
@@ -6391,14 +7883,26 @@ void main() {
6391
7883
  model: requiredUniform(gl, program, "u_model"),
6392
7884
  viewProjection: requiredUniform(gl, program, "u_viewProjection"),
6393
7885
  color: requiredUniform(gl, program, "u_color"),
6394
- lightDirection: requiredUniform(gl, program, "u_lightDirection")
7886
+ lightDirection: requiredUniform(gl, program, "u_lightDirection"),
7887
+ baseColorTexture: requiredUniform(gl, program, "u_baseColorTexture"),
7888
+ useTexture: requiredUniform(gl, program, "u_useTexture"),
7889
+ metallicRoughnessTexture: requiredUniform(gl, program, "u_metallicRoughnessTexture"),
7890
+ useMetallicRoughnessTexture: requiredUniform(gl, program, "u_useMetallicRoughnessTexture"),
7891
+ occlusionTexture: requiredUniform(gl, program, "u_occlusionTexture"),
7892
+ useOcclusionTexture: requiredUniform(gl, program, "u_useOcclusionTexture"),
7893
+ emissiveTexture: requiredUniform(gl, program, "u_emissiveTexture"),
7894
+ useEmissiveTexture: requiredUniform(gl, program, "u_useEmissiveTexture"),
7895
+ metallic: requiredUniform(gl, program, "u_metallic"),
7896
+ roughness: requiredUniform(gl, program, "u_roughness"),
7897
+ emissive: requiredUniform(gl, program, "u_emissive")
6395
7898
  };
6396
7899
  return {
6397
7900
  program,
6398
7901
  attributes: {
6399
7902
  position: gl.getAttribLocation(program, "a_position"),
6400
7903
  normal: gl.getAttribLocation(program, "a_normal"),
6401
- color: gl.getAttribLocation(program, "a_color")
7904
+ color: gl.getAttribLocation(program, "a_color"),
7905
+ uv: gl.getAttribLocation(program, "a_uv")
6402
7906
  },
6403
7907
  uniforms
6404
7908
  };
@@ -6440,7 +7944,7 @@ async function loadGltfForWebGL(url) {
6440
7944
  const bytes = await response.arrayBuffer();
6441
7945
  const loaded = isGlb(bytes) ? parseGlb(bytes) : { json: JSON.parse(new TextDecoder().decode(bytes)), buffers: [] };
6442
7946
  const buffers = loaded.buffers.length > 0 ? loaded.buffers : await loadExternalGltfBuffers(loaded.json, absoluteUrl);
6443
- return createGltfModel(loaded.json, buffers);
7947
+ return await createGltfModel(loaded.json, buffers, absoluteUrl);
6444
7948
  }
6445
7949
  function isGlb(bytes) {
6446
7950
  return new DataView(bytes).getUint32(0, true) === 0x46546c67;
@@ -6487,10 +7991,14 @@ function dataUriToArrayBuffer(uri) {
6487
7991
  bytes[index] = binary.charCodeAt(index);
6488
7992
  return bytes.buffer;
6489
7993
  }
6490
- function createGltfModel(json, buffers) {
7994
+ async function createGltfModel(json, buffers, modelUrl) {
6491
7995
  const primitives = [];
7996
+ const textures = await createGltfTextureSources(json, buffers, modelUrl);
7997
+ const nodes = createGltfRuntimeNodes(json);
7998
+ const animations = createGltfAnimationClips(json, buffers);
7999
+ const sceneRoots = resolveGltfSceneRoots(json);
6492
8000
  let bounds;
6493
- const pushMesh = (meshIndex, matrix) => {
8001
+ const pushMesh = (meshIndex, matrix, nodeIndex) => {
6494
8002
  const mesh = json.meshes?.[meshIndex];
6495
8003
  if (!mesh)
6496
8004
  return;
@@ -6502,17 +8010,29 @@ function createGltfModel(json, buffers) {
6502
8010
  const sourceNormals = primitive.attributes?.NORMAL === undefined
6503
8011
  ? createDefaultNormals(sourcePositions.length / 3)
6504
8012
  : readAccessor(json, buffers, primitive.attributes.NORMAL, 3);
6505
- const positions = transformPositions(sourcePositions, matrix);
6506
- const normals = transformNormals(sourceNormals, matrix);
8013
+ const sourceUvs = primitive.attributes?.TEXCOORD_0 === undefined
8014
+ ? undefined
8015
+ : readAccessor(json, buffers, primitive.attributes.TEXCOORD_0, 2);
6507
8016
  const indices = primitive.indices === undefined ? undefined : readIndices(json, buffers, primitive.indices);
6508
- const primitiveBounds = boundsFromPositions(positions);
8017
+ const materialInfo = materialRenderInfo(json, primitive.material, sourceUvs);
8018
+ const primitiveBounds = boundsFromPositions(transformPositions(sourcePositions, matrix));
6509
8019
  bounds = bounds ? mergeBounds(bounds, primitiveBounds) : primitiveBounds;
6510
8020
  primitives.push({
6511
- positions,
6512
- normals,
8021
+ positions: sourcePositions,
8022
+ normals: sourceNormals,
8023
+ ...(sourceUvs ? { uvs: sourceUvs } : {}),
6513
8024
  ...(indices ? { indices } : {}),
6514
8025
  mode: primitive.mode ?? 4,
6515
- color: materialColor(json, primitive.material)
8026
+ color: materialInfo.color,
8027
+ ...(materialInfo.textureIndex !== undefined ? { textureIndex: materialInfo.textureIndex } : {}),
8028
+ ...(materialInfo.metallicRoughnessTextureIndex !== undefined ? { metallicRoughnessTextureIndex: materialInfo.metallicRoughnessTextureIndex } : {}),
8029
+ ...(materialInfo.occlusionTextureIndex !== undefined ? { occlusionTextureIndex: materialInfo.occlusionTextureIndex } : {}),
8030
+ ...(materialInfo.emissiveTextureIndex !== undefined ? { emissiveTextureIndex: materialInfo.emissiveTextureIndex } : {}),
8031
+ metallic: materialInfo.metallic,
8032
+ roughness: materialInfo.roughness,
8033
+ emissive: materialInfo.emissive,
8034
+ ...(nodeIndex !== undefined ? { nodeIndex } : {}),
8035
+ staticWorldMatrix: matrix
6516
8036
  });
6517
8037
  }
6518
8038
  };
@@ -6527,22 +8047,16 @@ function createGltfModel(json, buffers) {
6527
8047
  const nextStack = new Set(stack);
6528
8048
  nextStack.add(nodeIndex);
6529
8049
  if (node.mesh !== undefined)
6530
- pushMesh(node.mesh, worldMatrix);
8050
+ pushMesh(node.mesh, worldMatrix, nodeIndex);
6531
8051
  for (const childIndex of node.children ?? [])
6532
8052
  visitNode(childIndex, worldMatrix, nextStack);
6533
8053
  };
6534
- const sceneRoots = json.scenes?.[json.scene ?? 0]?.nodes;
6535
- if (json.nodes?.length && sceneRoots?.length) {
8054
+ if (json.nodes?.length && sceneRoots.length) {
6536
8055
  for (const nodeIndex of sceneRoots)
6537
8056
  visitNode(nodeIndex, identity4(), new Set());
6538
8057
  }
6539
8058
  else if (json.nodes?.length) {
6540
- const childNodes = new Set();
6541
- for (const node of json.nodes)
6542
- for (const childIndex of node.children ?? [])
6543
- childNodes.add(childIndex);
6544
- const roots = json.nodes.map((_, nodeIndex) => nodeIndex).filter((nodeIndex) => !childNodes.has(nodeIndex));
6545
- for (const nodeIndex of roots.length > 0 ? roots : json.nodes.map((_, nodeIndex) => nodeIndex))
8059
+ for (const nodeIndex of json.nodes.map((_, nodeIndex) => nodeIndex))
6546
8060
  visitNode(nodeIndex, identity4(), new Set());
6547
8061
  }
6548
8062
  else {
@@ -6552,19 +8066,163 @@ function createGltfModel(json, buffers) {
6552
8066
  if (primitives.length === 0) {
6553
8067
  throw new AuraRuntimeError("failed-glb-load", "Aura3D found no mesh primitives with POSITION data in the model. Suggested fix: export a visible mesh to GLB/glTF.");
6554
8068
  }
6555
- return { primitives, bounds: bounds ?? { min: [-1, -1, -1], max: [1, 1, 1] } };
8069
+ return { primitives, textures, nodes, animations, sceneRoots, bounds: bounds ?? { min: [-1, -1, -1], max: [1, 1, 1] } };
8070
+ }
8071
+ function resolveGltfSceneRoots(json) {
8072
+ const nodeCount = json.nodes?.length ?? 0;
8073
+ const sceneRoots = json.scenes?.[json.scene ?? 0]?.nodes?.filter((nodeIndex) => nodeIndex >= 0 && nodeIndex < nodeCount) ?? [];
8074
+ if (sceneRoots.length > 0)
8075
+ return sceneRoots;
8076
+ if (nodeCount === 0)
8077
+ return [];
8078
+ const childNodes = new Set();
8079
+ for (const node of json.nodes ?? []) {
8080
+ for (const childIndex of node.children ?? [])
8081
+ childNodes.add(childIndex);
8082
+ }
8083
+ const roots = (json.nodes ?? []).map((_, nodeIndex) => nodeIndex).filter((nodeIndex) => !childNodes.has(nodeIndex));
8084
+ return roots.length > 0 ? roots : (json.nodes ?? []).map((_, nodeIndex) => nodeIndex);
8085
+ }
8086
+ function createGltfRuntimeNodes(json) {
8087
+ return (json.nodes ?? []).map((node) => {
8088
+ const translate = node.translation ?? [0, 0, 0];
8089
+ const rotate = node.rotation ?? [0, 0, 0, 1];
8090
+ const scale = node.scale ?? [1, 1, 1];
8091
+ return {
8092
+ children: node.children ?? [],
8093
+ baseTranslation: [translate[0] ?? 0, translate[1] ?? 0, translate[2] ?? 0],
8094
+ baseRotation: normalizeQuaternion([rotate[0] ?? 0, rotate[1] ?? 0, rotate[2] ?? 0, rotate[3] ?? 1]),
8095
+ baseScale: [scale[0] ?? 1, scale[1] ?? 1, scale[2] ?? 1],
8096
+ baseMatrix: gltfNodeMatrix(node)
8097
+ };
8098
+ });
8099
+ }
8100
+ function createGltfAnimationClips(json, buffers) {
8101
+ return (json.animations ?? []).map((animation, animationIndex) => {
8102
+ const channels = [];
8103
+ let duration = 0;
8104
+ for (const channel of animation.channels ?? []) {
8105
+ const sampler = animation.samplers?.[channel.sampler];
8106
+ const nodeIndex = channel.target.node;
8107
+ const path = parseGltfAnimationPath(channel.target.path);
8108
+ if (!sampler || nodeIndex === undefined || !path)
8109
+ continue;
8110
+ const times = readAccessor(json, buffers, sampler.input, 1);
8111
+ const values = readAccessor(json, buffers, sampler.output, path === "rotation" ? 4 : 3);
8112
+ if (times.length === 0 || values.length === 0)
8113
+ continue;
8114
+ duration = Math.max(duration, times[times.length - 1] ?? 0);
8115
+ channels.push({
8116
+ nodeIndex,
8117
+ path,
8118
+ times,
8119
+ values,
8120
+ interpolation: sampler.interpolation ?? "LINEAR"
8121
+ });
8122
+ }
8123
+ return {
8124
+ name: animation.name ?? `animation-${animationIndex + 1}`,
8125
+ duration,
8126
+ channels
8127
+ };
8128
+ }).filter((clip) => clip.channels.length > 0);
8129
+ }
8130
+ function parseGltfAnimationPath(path) {
8131
+ return path === "translation" || path === "rotation" || path === "scale" ? path : undefined;
8132
+ }
8133
+ async function createGltfTextureSources(json, buffers, modelUrl) {
8134
+ const images = new Map();
8135
+ const loadImage = (imageIndex) => {
8136
+ const existing = images.get(imageIndex);
8137
+ if (existing)
8138
+ return existing;
8139
+ const promise = loadGltfImage(json, buffers, imageIndex, modelUrl).catch(() => undefined);
8140
+ images.set(imageIndex, promise);
8141
+ return promise;
8142
+ };
8143
+ return await Promise.all((json.textures ?? []).map(async (texture) => {
8144
+ if (texture.source === undefined)
8145
+ return undefined;
8146
+ const image = await loadImage(texture.source);
8147
+ return image ? { image } : undefined;
8148
+ }));
6556
8149
  }
6557
- function materialColor(json, materialIndex) {
6558
- if (materialIndex === undefined)
8150
+ async function loadGltfImage(json, buffers, imageIndex, modelUrl) {
8151
+ const image = json.images?.[imageIndex];
8152
+ if (!image)
8153
+ return undefined;
8154
+ let blob;
8155
+ if (image.bufferView !== undefined) {
8156
+ const view = json.bufferViews?.[image.bufferView];
8157
+ if (!view)
8158
+ return undefined;
8159
+ const buffer = buffers[view.buffer];
8160
+ if (!buffer)
8161
+ return undefined;
8162
+ const bytes = new Uint8Array(buffer, view.byteOffset ?? 0, view.byteLength);
8163
+ blob = new Blob([bytes], { type: image.mimeType ?? "application/octet-stream" });
8164
+ }
8165
+ else if (image.uri?.startsWith("data:")) {
8166
+ const response = await fetch(image.uri);
8167
+ blob = await response.blob();
8168
+ }
8169
+ else if (image.uri) {
8170
+ const response = await fetch(new URL(image.uri, modelUrl).href);
8171
+ if (!response.ok)
8172
+ return undefined;
8173
+ blob = await response.blob();
8174
+ }
8175
+ else {
6559
8176
  return undefined;
8177
+ }
8178
+ if (typeof createImageBitmap === "function")
8179
+ return await createImageBitmap(blob);
8180
+ return await blobToHtmlImage(blob);
8181
+ }
8182
+ async function blobToHtmlImage(blob) {
8183
+ const url = URL.createObjectURL(blob);
8184
+ try {
8185
+ const image = new Image();
8186
+ image.decoding = "async";
8187
+ image.src = url;
8188
+ if (typeof image.decode === "function")
8189
+ await image.decode();
8190
+ else
8191
+ await new Promise((resolve, reject) => {
8192
+ image.onload = () => resolve();
8193
+ image.onerror = () => reject(new Error("Aura3D failed to decode glTF image."));
8194
+ });
8195
+ return image;
8196
+ }
8197
+ finally {
8198
+ URL.revokeObjectURL(url);
8199
+ }
8200
+ }
8201
+ function materialRenderInfo(json, materialIndex, uvs) {
8202
+ if (materialIndex === undefined) {
8203
+ return { metallic: 0, roughness: 0.72, emissive: [0, 0, 0] };
8204
+ }
6560
8205
  const materialEntry = json.materials?.[materialIndex];
6561
- const base = materialEntry?.pbrMetallicRoughness?.baseColorFactor;
6562
- if (base && base.length >= 3)
6563
- return [clamp01(base[0]), clamp01(base[1]), clamp01(base[2])];
8206
+ const pbr = materialEntry?.pbrMetallicRoughness;
8207
+ const base = pbr?.baseColorFactor;
6564
8208
  const emissive = materialEntry?.emissiveFactor;
6565
- if (emissive && emissive.length >= 3)
6566
- return [clamp01(emissive[0]), clamp01(emissive[1]), clamp01(emissive[2])];
6567
- return undefined;
8209
+ return {
8210
+ ...(base && base.length >= 3 ? { color: [clamp01(base[0]), clamp01(base[1]), clamp01(base[2])] } : {}),
8211
+ ...textureRef(pbr?.baseColorTexture, uvs, "textureIndex"),
8212
+ ...textureRef(pbr?.metallicRoughnessTexture, uvs, "metallicRoughnessTextureIndex"),
8213
+ ...textureRef(materialEntry?.occlusionTexture, uvs, "occlusionTextureIndex"),
8214
+ ...textureRef(materialEntry?.emissiveTexture, uvs, "emissiveTextureIndex"),
8215
+ metallic: clamp01(pbr?.metallicFactor ?? 0),
8216
+ roughness: clamp01(pbr?.roughnessFactor ?? 0.72),
8217
+ emissive: emissive && emissive.length >= 3
8218
+ ? [clamp01(emissive[0]), clamp01(emissive[1]), clamp01(emissive[2])]
8219
+ : [0, 0, 0]
8220
+ };
8221
+ }
8222
+ function textureRef(texture, uvs, key) {
8223
+ if (!texture || !uvs || texture.texCoord !== undefined && texture.texCoord !== 0)
8224
+ return {};
8225
+ return { [key]: texture.index };
6568
8226
  }
6569
8227
  function createPlaneGeometry() {
6570
8228
  return {
@@ -6809,6 +8467,106 @@ function gltfNodeMatrix(node) {
6809
8467
  const scale = node.scale ?? [1, 1, 1];
6810
8468
  return multiply4(translation(translate[0] ?? 0, translate[1] ?? 0, translate[2] ?? 0), multiply4(rotationQuaternion(rotate), scaling(scale[0] ?? 1, scale[1] ?? 1, scale[2] ?? 1)));
6811
8469
  }
8470
+ function gltfTrsMatrix(translate, rotate, scale) {
8471
+ return multiply4(translation(translate[0], translate[1], translate[2]), multiply4(rotationQuaternion(rotate), scaling(scale[0], scale[1], scale[2])));
8472
+ }
8473
+ function sampleGltfVec3Channel(channel, seconds) {
8474
+ const [left, right, t] = gltfKeyframePair(channel.times, seconds);
8475
+ const a = readGltfChannelValue(channel, left, 3);
8476
+ if (left === right || channel.interpolation === "STEP")
8477
+ return [a[0], a[1], a[2]];
8478
+ const b = readGltfChannelValue(channel, right, 3);
8479
+ return [
8480
+ a[0] + (b[0] - a[0]) * t,
8481
+ a[1] + (b[1] - a[1]) * t,
8482
+ a[2] + (b[2] - a[2]) * t
8483
+ ];
8484
+ }
8485
+ function sampleGltfQuaternionChannel(channel, seconds) {
8486
+ const [left, right, t] = gltfKeyframePair(channel.times, seconds);
8487
+ const a = normalizeQuaternion(readGltfChannelValue(channel, left, 4));
8488
+ if (left === right || channel.interpolation === "STEP")
8489
+ return a;
8490
+ const b = normalizeQuaternion(readGltfChannelValue(channel, right, 4));
8491
+ return slerpQuaternion(a, b, t);
8492
+ }
8493
+ function gltfKeyframePair(times, seconds) {
8494
+ if (times.length <= 1)
8495
+ return [0, 0, 0];
8496
+ if (seconds <= (times[0] ?? 0))
8497
+ return [0, 0, 0];
8498
+ const lastIndex = times.length - 1;
8499
+ if (seconds >= (times[lastIndex] ?? seconds))
8500
+ return [lastIndex, lastIndex, 0];
8501
+ for (let index = 0; index < lastIndex; index += 1) {
8502
+ const start = times[index] ?? 0;
8503
+ const end = times[index + 1] ?? start;
8504
+ if (seconds >= start && seconds <= end) {
8505
+ const duration = Math.max(0.000001, end - start);
8506
+ return [index, index + 1, Math.min(1, Math.max(0, (seconds - start) / duration))];
8507
+ }
8508
+ }
8509
+ return [lastIndex, lastIndex, 0];
8510
+ }
8511
+ function readGltfChannelValue(channel, keyframeIndex, components) {
8512
+ const cubic = channel.interpolation === "CUBICSPLINE";
8513
+ const stride = components * (cubic ? 3 : 1);
8514
+ const offset = keyframeIndex * stride + (cubic ? components : 0);
8515
+ if (components === 4) {
8516
+ return [
8517
+ channel.values[offset] ?? 0,
8518
+ channel.values[offset + 1] ?? 0,
8519
+ channel.values[offset + 2] ?? 0,
8520
+ channel.values[offset + 3] ?? 1
8521
+ ];
8522
+ }
8523
+ return [
8524
+ channel.values[offset] ?? 0,
8525
+ channel.values[offset + 1] ?? 0,
8526
+ channel.values[offset + 2] ?? 0
8527
+ ];
8528
+ }
8529
+ function normalizeQuaternion(rotation) {
8530
+ const length = Math.hypot(rotation[0] ?? 0, rotation[1] ?? 0, rotation[2] ?? 0, rotation[3] ?? 1) || 1;
8531
+ return [
8532
+ (rotation[0] ?? 0) / length,
8533
+ (rotation[1] ?? 0) / length,
8534
+ (rotation[2] ?? 0) / length,
8535
+ (rotation[3] ?? 1) / length
8536
+ ];
8537
+ }
8538
+ function slerpQuaternion(a, b, t) {
8539
+ let bx = b[0];
8540
+ let by = b[1];
8541
+ let bz = b[2];
8542
+ let bw = b[3];
8543
+ let dot = a[0] * bx + a[1] * by + a[2] * bz + a[3] * bw;
8544
+ if (dot < 0) {
8545
+ dot = -dot;
8546
+ bx = -bx;
8547
+ by = -by;
8548
+ bz = -bz;
8549
+ bw = -bw;
8550
+ }
8551
+ if (dot > 0.9995) {
8552
+ return normalizeQuaternion([
8553
+ a[0] + (bx - a[0]) * t,
8554
+ a[1] + (by - a[1]) * t,
8555
+ a[2] + (bz - a[2]) * t,
8556
+ a[3] + (bw - a[3]) * t
8557
+ ]);
8558
+ }
8559
+ const theta = Math.acos(Math.min(1, Math.max(-1, dot)));
8560
+ const sinTheta = Math.sin(theta) || 1;
8561
+ const wa = Math.sin((1 - t) * theta) / sinTheta;
8562
+ const wb = Math.sin(t * theta) / sinTheta;
8563
+ return normalizeQuaternion([
8564
+ a[0] * wa + bx * wb,
8565
+ a[1] * wa + by * wb,
8566
+ a[2] * wa + bz * wb,
8567
+ a[3] * wa + bw * wb
8568
+ ]);
8569
+ }
6812
8570
  function rotationQuaternion(rotation) {
6813
8571
  const length = Math.hypot(rotation[0] ?? 0, rotation[1] ?? 0, rotation[2] ?? 0, rotation[3] ?? 1) || 1;
6814
8572
  const x = (rotation[0] ?? 0) / length;
@@ -6878,10 +8636,10 @@ function mergeBounds(a, b) {
6878
8636
  max: [Math.max(a.max[0], b.max[0]), Math.max(a.max[1], b.max[1]), Math.max(a.max[2], b.max[2])]
6879
8637
  };
6880
8638
  }
6881
- function createViewProjection(snapshot, aspect, time) {
8639
+ function createViewProjection(snapshot, aspect, time, runtimeNodes) {
6882
8640
  const cameraSpec = snapshot.camera;
6883
- const target = resolveCameraTarget(snapshot, cameraSpec);
6884
- const eye = resolveCameraEye(snapshot, cameraSpec, time);
8641
+ const target = resolveCameraTarget(snapshot, cameraSpec, runtimeNodes);
8642
+ const eye = resolveCameraEye(snapshot, cameraSpec, time, runtimeNodes);
6885
8643
  const view = lookAt(eye, target, [0, 1, 0]);
6886
8644
  const projection = perspective(((cameraSpec.fov ?? 45) * Math.PI) / 180, aspect, 0.05, 100);
6887
8645
  return multiply4(projection, view);
@@ -6892,7 +8650,7 @@ function createModelMatrix(node, bounds, normalizeToUnit, time = 0) {
6892
8650
  Math.max(0.001, bounds.max[1] - bounds.min[1]),
6893
8651
  Math.max(0.001, bounds.max[2] - bounds.min[2])
6894
8652
  ];
6895
- const fitScale = normalizeToUnit ? 1.55 / Math.max(extent[0], extent[1], extent[2]) : 1;
8653
+ const fitScale = resolveModelFitScale(node, extent, normalizeToUnit);
6896
8654
  const centerX = (bounds.min[0] + bounds.max[0]) / 2;
6897
8655
  const centerZ = (bounds.min[2] + bounds.max[2]) / 2;
6898
8656
  const baseSize = node?.kind === "primitive" ? primitiveSize(node) : [1, 1, 1];
@@ -6901,6 +8659,23 @@ function createModelMatrix(node, bounds, normalizeToUnit, time = 0) {
6901
8659
  const rotation = animatedRotation(node, time);
6902
8660
  return multiply4(translation(position[0], position[1], position[2]), multiply4(rotationXYZ(rotation), multiply4(scaling(nodeScale[0] * baseSize[0] * fitScale, nodeScale[1] * baseSize[1] * fitScale, nodeScale[2] * baseSize[2] * fitScale), normalizeToUnit ? translation(-centerX, -bounds.min[1], -centerZ) : identity4())));
6903
8661
  }
8662
+ function resolveModelFitScale(node, extent, normalizeToUnit) {
8663
+ if (node?.kind === "model") {
8664
+ if (isPositiveFinite(node.targetHeight))
8665
+ return node.targetHeight / extent[1];
8666
+ if (isPositiveFinite(node.targetLength))
8667
+ return node.targetLength / Math.max(extent[0], extent[2]);
8668
+ if (isPositiveFinite(node.targetMaxDimension))
8669
+ return node.targetMaxDimension / Math.max(extent[0], extent[1], extent[2]);
8670
+ }
8671
+ return normalizeToUnit ? AURA_NORMALIZED_MODEL_MAX_DIMENSION / Math.max(extent[0], extent[1], extent[2]) : 1;
8672
+ }
8673
+ function isPositiveFinite(value) {
8674
+ return typeof value === "number" && Number.isFinite(value) && value > 0;
8675
+ }
8676
+ function shouldNormalizeModelNode(node) {
8677
+ return node?.scaleMode !== "world";
8678
+ }
6904
8679
  function animatedPosition(node, time) {
6905
8680
  const basePosition = node?.position ?? [0, 0, 0];
6906
8681
  if (!node?.animation)
@@ -6918,6 +8693,8 @@ function animatedRotation(node, time) {
6918
8693
  const baseRotation = node?.rotation ?? [0, 0, 0];
6919
8694
  if (!node?.animation)
6920
8695
  return baseRotation;
8696
+ if (node.kind === "model" && !isModelTransformAnimationClip(node.animation.clip))
8697
+ return baseRotation;
6921
8698
  const speed = Math.max(0.05, node.animation.speed ?? 1);
6922
8699
  const seconds = resolveAnimationSeconds(node.animation, time);
6923
8700
  if (node.animation.clip === "turntable") {
@@ -6933,6 +8710,9 @@ function animatedRotation(node, time) {
6933
8710
  return baseRotation;
6934
8711
  return [baseRotation[0], baseRotation[1] + seconds * speed, baseRotation[2]];
6935
8712
  }
8713
+ function isModelTransformAnimationClip(clip) {
8714
+ return clip === "turntable" || clip === "float" || clip === "orbit";
8715
+ }
6936
8716
  function primitiveSize(node) {
6937
8717
  if (typeof node.size === "number")
6938
8718
  return [node.size, node.size, node.size];
@@ -7215,7 +8995,7 @@ function validateMaterialTexture(materialSpec) {
7215
8995
  throw new AuraRuntimeError("unsupported-texture", `Aura3D texture asset "${texture.id}" uses unsupported format "${texture.format}". Suggested fix: use png, jpg, jpeg, webp, or ktx2 textures.`);
7216
8996
  }
7217
8997
  }
7218
- function createInitialDiagnostics(snapshot) {
8998
+ function createInitialDiagnostics(snapshot, rendererOptions) {
7219
8999
  return {
7220
9000
  backend: "headless",
7221
9001
  fps: 0,
@@ -7223,7 +9003,7 @@ function createInitialDiagnostics(snapshot) {
7223
9003
  renderSize: [0, 0],
7224
9004
  assets: [],
7225
9005
  evidence: collectAuraSceneEvidence(snapshot),
7226
- renderer: createRendererDiagnosticReport(snapshot),
9006
+ renderer: createRendererDiagnosticReport(snapshot, undefined, rendererOptions),
7227
9007
  warnings: snapshot.nodes.length === 0 ? ["Scene contains no renderable nodes. Add model(assets.assetId) or primitives.box()."] : [],
7228
9008
  errors: []
7229
9009
  };
@@ -7250,7 +9030,7 @@ function collectGeneratedCodeWarnings(snapshot) {
7250
9030
  warnings.push("Scene has no interactions. Suggested fix: add interactions.orbit() for product/viewer scenes.");
7251
9031
  }
7252
9032
  for (const node of snapshot.nodes) {
7253
- if (node.kind === "model" && node.asset.id === "unsafe-url") {
9033
+ if (node.kind === "model" && createAssetProvenance(node.asset).source === "unsafe-url") {
7254
9034
  warnings.push(`Model uses unsafeModelUrl("${node.asset.url}"). Suggested fix: run assets add and use typed assets.`);
7255
9035
  }
7256
9036
  }