lecodes-sdk 0.19.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 (367) hide show
  1. package/README.md +114 -0
  2. package/dist/global.d.ts +503 -0
  3. package/dist/host.d.ts +61 -0
  4. package/dist/inject.js +4730 -0
  5. package/dist/types/animate/animate.d.ts +20 -0
  6. package/dist/types/animate/bezier.d.ts +2 -0
  7. package/dist/types/animate/easings.d.ts +21 -0
  8. package/dist/types/canvas/Canvas.d.ts +168 -0
  9. package/dist/types/core/Aspect.d.ts +171 -0
  10. package/dist/types/core/InspectorUI.d.ts +88 -0
  11. package/dist/types/core/StateMachine.d.ts +81 -0
  12. package/dist/types/core/color.d.ts +13 -0
  13. package/dist/types/core/compWrite.d.ts +11 -0
  14. package/dist/types/core/events.d.ts +11 -0
  15. package/dist/types/core/fields.d.ts +58 -0
  16. package/dist/types/core/registry.d.ts +7 -0
  17. package/dist/types/core/signals.d.ts +29 -0
  18. package/dist/types/core/time.d.ts +31 -0
  19. package/dist/types/g2/Camera2D.d.ts +19 -0
  20. package/dist/types/g2/CharacterController2D.d.ts +134 -0
  21. package/dist/types/g2/Node2D.d.ts +91 -0
  22. package/dist/types/g2/OneWay2D.d.ts +24 -0
  23. package/dist/types/g2/Physics2D.d.ts +147 -0
  24. package/dist/types/g2/Scene2D.d.ts +72 -0
  25. package/dist/types/g2/Shape2D.d.ts +58 -0
  26. package/dist/types/g2/Sprite.d.ts +51 -0
  27. package/dist/types/g2/SpriteAnimation.d.ts +50 -0
  28. package/dist/types/g2/SpriteSheet.d.ts +69 -0
  29. package/dist/types/g2/Texture2D.d.ts +16 -0
  30. package/dist/types/g2/Tilemap.d.ts +20 -0
  31. package/dist/types/g2/Tileset.d.ts +32 -0
  32. package/dist/types/g2/Trigger2D.d.ts +20 -0
  33. package/dist/types/g2/autotile.d.ts +133 -0
  34. package/dist/types/g2/cells.d.ts +14 -0
  35. package/dist/types/g2/defineScene2d.d.ts +141 -0
  36. package/dist/types/g2/groups2d.d.ts +16 -0
  37. package/dist/types/g2/loop.d.ts +4 -0
  38. package/dist/types/g2/scenarios2d.d.ts +30 -0
  39. package/dist/types/g2/touch.d.ts +2 -0
  40. package/dist/types/gl/Camera.d.ts +79 -0
  41. package/dist/types/gl/CameraPlace.d.ts +26 -0
  42. package/dist/types/gl/CharacterController.d.ts +98 -0
  43. package/dist/types/gl/Gearbox.d.ts +86 -0
  44. package/dist/types/gl/Geometry.d.ts +39 -0
  45. package/dist/types/gl/IK.d.ts +53 -0
  46. package/dist/types/gl/InstancedMesh.d.ts +46 -0
  47. package/dist/types/gl/Light.d.ts +73 -0
  48. package/dist/types/gl/Lightmap.d.ts +85 -0
  49. package/dist/types/gl/Locomotion.d.ts +279 -0
  50. package/dist/types/gl/Material.d.ts +100 -0
  51. package/dist/types/gl/Mesh.d.ts +33 -0
  52. package/dist/types/gl/Model.d.ts +53 -0
  53. package/dist/types/gl/NavAgent.d.ts +99 -0
  54. package/dist/types/gl/NavMesh.d.ts +169 -0
  55. package/dist/types/gl/Node.d.ts +99 -0
  56. package/dist/types/gl/Noise.d.ts +20 -0
  57. package/dist/types/gl/Particles.d.ts +277 -0
  58. package/dist/types/gl/Physics.d.ts +91 -0
  59. package/dist/types/gl/Plane.d.ts +19 -0
  60. package/dist/types/gl/Ragdoll.d.ts +86 -0
  61. package/dist/types/gl/Ray.d.ts +8 -0
  62. package/dist/types/gl/Scene.d.ts +221 -0
  63. package/dist/types/gl/Shape.d.ts +122 -0
  64. package/dist/types/gl/Terrain.d.ts +280 -0
  65. package/dist/types/gl/Texture.d.ts +27 -0
  66. package/dist/types/gl/Trigger.d.ts +10 -0
  67. package/dist/types/gl/Vehicle.d.ts +191 -0
  68. package/dist/types/gl/Wheel.d.ts +95 -0
  69. package/dist/types/gl/animation/AnimationClip.d.ts +60 -0
  70. package/dist/types/gl/animation/Animator.d.ts +219 -0
  71. package/dist/types/gl/animation/Layer.d.ts +31 -0
  72. package/dist/types/gl/animation/Loop.d.ts +17 -0
  73. package/dist/types/gl/animation/Playback.d.ts +36 -0
  74. package/dist/types/gl/animation/core.d.ts +367 -0
  75. package/dist/types/gl/controls.d.ts +19 -0
  76. package/dist/types/gl/physicsEvents.d.ts +1 -0
  77. package/dist/types/gl/scenarios.d.ts +98 -0
  78. package/dist/types/gl/state.d.ts +5 -0
  79. package/dist/types/gl/terrainMesh.d.ts +46 -0
  80. package/dist/types/gl/touch.d.ts +2 -0
  81. package/dist/types/inject.d.ts +123 -0
  82. package/dist/types/math/Mathf.d.ts +39 -0
  83. package/dist/types/math/mat4.d.ts +66 -0
  84. package/dist/types/math/quat.d.ts +53 -0
  85. package/dist/types/math/vec.d.ts +129 -0
  86. package/dist/types/net/codec.d.ts +25 -0
  87. package/dist/types/net/core.d.ts +106 -0
  88. package/dist/types/net/index.d.ts +80 -0
  89. package/dist/types/net/replication.d.ts +101 -0
  90. package/dist/types/plugins/camera.d.ts +25 -0
  91. package/dist/types/plugins/geolocation.d.ts +39 -0
  92. package/dist/types/plugins/oauth.d.ts +25 -0
  93. package/dist/types/plugins/permission.d.ts +1 -0
  94. package/dist/types/plugins/push.d.ts +46 -0
  95. package/dist/types/plugins/qr.d.ts +21 -0
  96. package/dist/types/plugins/service.d.ts +33 -0
  97. package/dist/types/runtime/app.d.ts +67 -0
  98. package/dist/types/runtime/appEvents.d.ts +7 -0
  99. package/dist/types/runtime/channel.d.ts +1 -0
  100. package/dist/types/runtime/clipboard.d.ts +7 -0
  101. package/dist/types/runtime/datetime.d.ts +80 -0
  102. package/dist/types/runtime/device.d.ts +126 -0
  103. package/dist/types/runtime/fetch.d.ts +31 -0
  104. package/dist/types/runtime/files.d.ts +33 -0
  105. package/dist/types/runtime/input.d.ts +121 -0
  106. package/dist/types/runtime/media.d.ts +46 -0
  107. package/dist/types/runtime/misc.d.ts +11 -0
  108. package/dist/types/runtime/net.d.ts +15 -0
  109. package/dist/types/runtime/rpc.d.ts +44 -0
  110. package/dist/types/runtime/service.d.ts +1 -0
  111. package/dist/types/runtime/share.d.ts +2 -0
  112. package/dist/types/runtime/storage.d.ts +5 -0
  113. package/dist/types/runtime/touch.d.ts +56 -0
  114. package/dist/types/scene/defineScene.d.ts +296 -0
  115. package/dist/types/scene/editorPlugins.d.ts +82 -0
  116. package/dist/types/scene/gizmos.d.ts +56 -0
  117. package/dist/types/scene/grammar.d.ts +56 -0
  118. package/dist/types/scene/level.d.ts +98 -0
  119. package/dist/types/scene/material.d.ts +76 -0
  120. package/dist/types/server/auth/appConfig.d.ts +16 -0
  121. package/dist/types/server/auth/global.d.ts +56 -0
  122. package/dist/types/server/auth/models.d.ts +164 -0
  123. package/dist/types/server/auth/types.d.ts +56 -0
  124. package/dist/types/server/channel.d.ts +28 -0
  125. package/dist/types/server/context.d.ts +27 -0
  126. package/dist/types/server/db/defineDb.d.ts +36 -0
  127. package/dist/types/server/db/fields.d.ts +146 -0
  128. package/dist/types/server/db/index.d.ts +6 -0
  129. package/dist/types/server/db/marci/query.d.ts +326 -0
  130. package/dist/types/server/db/types.d.ts +163 -0
  131. package/dist/types/server/errors.d.ts +8 -0
  132. package/dist/types/server/inject.d.ts +5 -0
  133. package/dist/types/ui/NativeView.d.ts +38 -0
  134. package/dist/types/ui/UI.d.ts +27 -0
  135. package/dist/types/ui/UIBottomSheet.d.ts +71 -0
  136. package/dist/types/ui/UIButton.d.ts +30 -0
  137. package/dist/types/ui/UIContainer.d.ts +33 -0
  138. package/dist/types/ui/UIImage.d.ts +35 -0
  139. package/dist/types/ui/UIInput.d.ts +110 -0
  140. package/dist/types/ui/UIModal.d.ts +70 -0
  141. package/dist/types/ui/UINode.d.ts +363 -0
  142. package/dist/types/ui/UIPager.d.ts +123 -0
  143. package/dist/types/ui/UIPopover.d.ts +39 -0
  144. package/dist/types/ui/UIScreen.d.ts +57 -0
  145. package/dist/types/ui/UIScrollable.d.ts +40 -0
  146. package/dist/types/ui/UISpacer.d.ts +9 -0
  147. package/dist/types/ui/UITabs.d.ts +96 -0
  148. package/dist/types/ui/UIText.d.ts +19 -0
  149. package/dist/types/ui/UIVideo.d.ts +28 -0
  150. package/dist/types/ui/UIVirtualizedList.d.ts +97 -0
  151. package/dist/types/ui/UIWidget.d.ts +65 -0
  152. package/dist/types/ui/fonts.d.ts +9 -0
  153. package/dist/types/ui/presentable.d.ts +68 -0
  154. package/dist/types/ui/router.d.ts +40 -0
  155. package/dist/types/ui/theme.d.ts +26 -0
  156. package/dist/types.json +1 -0
  157. package/package.json +46 -0
  158. package/prompts/2d.md +395 -0
  159. package/prompts/3d-scene-files.md +113 -0
  160. package/prompts/3d-scene.md +119 -0
  161. package/prompts/3d.md +303 -0
  162. package/prompts/README.md +142 -0
  163. package/prompts/ar.md +109 -0
  164. package/prompts/canvas.md +27 -0
  165. package/prompts/compose.ts +46 -0
  166. package/prompts/concept.md +49 -0
  167. package/prompts/core-design.md +75 -0
  168. package/prompts/core.md +264 -0
  169. package/prompts/design.md +218 -0
  170. package/prompts/dist/2d-game.md +1530 -0
  171. package/prompts/dist/3d-app.md +1672 -0
  172. package/prompts/dist/ar-app.md +1548 -0
  173. package/prompts/dist/concept.md +49 -0
  174. package/prompts/dist/design.md +498 -0
  175. package/prompts/dist/ui-app.md +1134 -0
  176. package/prompts/index.md +37 -0
  177. package/prompts/intake-prompt.md +33 -0
  178. package/prompts/namer-prompt.md +12 -0
  179. package/prompts/router-prompt.md +50 -0
  180. package/prompts/select.ts +111 -0
  181. package/prompts/ui-design.md +203 -0
  182. package/prompts/ui.md +803 -0
  183. package/src/animate/animate.ts +238 -0
  184. package/src/animate/bezier.ts +138 -0
  185. package/src/animate/easings.ts +126 -0
  186. package/src/bridges.d.ts +1345 -0
  187. package/src/canvas/Canvas.ts +305 -0
  188. package/src/compile/__tests__/assetIconMacro.test.ts +219 -0
  189. package/src/compile/__tests__/assetMacro.test.ts +74 -0
  190. package/src/compile/__tests__/assetName.test.ts +55 -0
  191. package/src/compile/__tests__/compile.test.ts +287 -0
  192. package/src/compile/__tests__/detectEntry.test.ts +132 -0
  193. package/src/compile/__tests__/fontMacro.test.ts +199 -0
  194. package/src/compile/aspectMacro.ts +86 -0
  195. package/src/compile/assetIconMacro.ts +384 -0
  196. package/src/compile/assetMacro.ts +146 -0
  197. package/src/compile/assetName.ts +50 -0
  198. package/src/compile/bundler.ts +287 -0
  199. package/src/compile/compileProject.ts +127 -0
  200. package/src/compile/detectEntry.ts +128 -0
  201. package/src/compile/fontMacro.ts +459 -0
  202. package/src/compile/fontRegistry.ts +78 -0
  203. package/src/compile/header.ts +67 -0
  204. package/src/compile/index.ts +100 -0
  205. package/src/compile/libraryImports.ts +52 -0
  206. package/src/compile/liteMaterial.ts +247 -0
  207. package/src/compile/sceneEditor.ts +88 -0
  208. package/src/compile/serverSplit.ts +233 -0
  209. package/src/compile/serverTypes.ts +227 -0
  210. package/src/compile/sfnt.ts +98 -0
  211. package/src/compile/shaderSchema.ts +202 -0
  212. package/src/compile/shaderTargets.ts +77 -0
  213. package/src/compile/sourcemap.ts +25 -0
  214. package/src/core/Aspect.ts +568 -0
  215. package/src/core/InspectorUI.ts +212 -0
  216. package/src/core/StateMachine.ts +308 -0
  217. package/src/core/__tests__/stateMachine.test.ts +132 -0
  218. package/src/core/color.ts +66 -0
  219. package/src/core/compWrite.ts +42 -0
  220. package/src/core/events.ts +38 -0
  221. package/src/core/fields.ts +120 -0
  222. package/src/core/registry.ts +23 -0
  223. package/src/core/signals.ts +277 -0
  224. package/src/core/time.ts +84 -0
  225. package/src/g2/Camera2D.ts +40 -0
  226. package/src/g2/CharacterController2D.ts +276 -0
  227. package/src/g2/Node2D.ts +267 -0
  228. package/src/g2/OneWay2D.ts +66 -0
  229. package/src/g2/Physics2D.ts +346 -0
  230. package/src/g2/Scene2D.ts +209 -0
  231. package/src/g2/Shape2D.ts +259 -0
  232. package/src/g2/Sprite.ts +89 -0
  233. package/src/g2/SpriteAnimation.ts +171 -0
  234. package/src/g2/SpriteSheet.ts +166 -0
  235. package/src/g2/Texture2D.ts +47 -0
  236. package/src/g2/Tilemap.ts +41 -0
  237. package/src/g2/Tileset.ts +71 -0
  238. package/src/g2/Trigger2D.ts +77 -0
  239. package/src/g2/autotile.ts +433 -0
  240. package/src/g2/cells.ts +91 -0
  241. package/src/g2/defineScene2d.ts +381 -0
  242. package/src/g2/groups2d.ts +106 -0
  243. package/src/g2/loop.ts +50 -0
  244. package/src/g2/scenarios2d.ts +69 -0
  245. package/src/g2/touch.ts +83 -0
  246. package/src/gl/Camera.ts +160 -0
  247. package/src/gl/CameraPlace.ts +52 -0
  248. package/src/gl/CharacterController.ts +238 -0
  249. package/src/gl/Gearbox.ts +212 -0
  250. package/src/gl/Geometry.ts +279 -0
  251. package/src/gl/IK.ts +193 -0
  252. package/src/gl/InstancedMesh.ts +132 -0
  253. package/src/gl/Light.ts +135 -0
  254. package/src/gl/Lightmap.ts +402 -0
  255. package/src/gl/Locomotion.ts +498 -0
  256. package/src/gl/Material.ts +279 -0
  257. package/src/gl/Mesh.ts +83 -0
  258. package/src/gl/Model.ts +124 -0
  259. package/src/gl/NavAgent.ts +337 -0
  260. package/src/gl/NavMesh.ts +397 -0
  261. package/src/gl/Node.ts +350 -0
  262. package/src/gl/Noise.ts +30 -0
  263. package/src/gl/Particles.ts +676 -0
  264. package/src/gl/Physics.ts +222 -0
  265. package/src/gl/Plane.ts +53 -0
  266. package/src/gl/Ragdoll.ts +270 -0
  267. package/src/gl/Ray.ts +16 -0
  268. package/src/gl/Scene.ts +505 -0
  269. package/src/gl/Shape.ts +409 -0
  270. package/src/gl/Terrain.ts +1092 -0
  271. package/src/gl/Texture.ts +63 -0
  272. package/src/gl/Trigger.ts +45 -0
  273. package/src/gl/Vehicle.ts +473 -0
  274. package/src/gl/Wheel.ts +240 -0
  275. package/src/gl/animation/AnimationClip.ts +204 -0
  276. package/src/gl/animation/Animator.ts +329 -0
  277. package/src/gl/animation/Layer.ts +39 -0
  278. package/src/gl/animation/Loop.ts +31 -0
  279. package/src/gl/animation/Playback.ts +52 -0
  280. package/src/gl/animation/core.ts +670 -0
  281. package/src/gl/controls.ts +95 -0
  282. package/src/gl/physicsEvents.ts +20 -0
  283. package/src/gl/scenarios.ts +291 -0
  284. package/src/gl/state.ts +6 -0
  285. package/src/gl/terrainMesh.ts +219 -0
  286. package/src/gl/touch.ts +68 -0
  287. package/src/host.d.ts +61 -0
  288. package/src/inject.ts +203 -0
  289. package/src/math/Mathf.ts +118 -0
  290. package/src/math/mat4.ts +278 -0
  291. package/src/math/quat.ts +232 -0
  292. package/src/math/vec.ts +255 -0
  293. package/src/net/codec.ts +119 -0
  294. package/src/net/core.ts +384 -0
  295. package/src/net/index.ts +181 -0
  296. package/src/net/replication.ts +622 -0
  297. package/src/plugins/camera.ts +81 -0
  298. package/src/plugins/geolocation.ts +123 -0
  299. package/src/plugins/oauth.ts +61 -0
  300. package/src/plugins/permission.ts +7 -0
  301. package/src/plugins/push.ts +132 -0
  302. package/src/plugins/qr.ts +73 -0
  303. package/src/plugins/service.ts +47 -0
  304. package/src/runtime/app.ts +101 -0
  305. package/src/runtime/appEvents.ts +54 -0
  306. package/src/runtime/channel.ts +61 -0
  307. package/src/runtime/clipboard.ts +20 -0
  308. package/src/runtime/datetime.ts +329 -0
  309. package/src/runtime/device.ts +293 -0
  310. package/src/runtime/fetch.ts +77 -0
  311. package/src/runtime/files.ts +108 -0
  312. package/src/runtime/input.ts +175 -0
  313. package/src/runtime/media.ts +111 -0
  314. package/src/runtime/misc.ts +16 -0
  315. package/src/runtime/net.ts +36 -0
  316. package/src/runtime/rpc.ts +218 -0
  317. package/src/runtime/service.ts +83 -0
  318. package/src/runtime/share.ts +9 -0
  319. package/src/runtime/storage.ts +13 -0
  320. package/src/runtime/touch.ts +76 -0
  321. package/src/scene/defineScene.ts +1396 -0
  322. package/src/scene/editorPlugins.ts +110 -0
  323. package/src/scene/gizmos.ts +148 -0
  324. package/src/scene/grammar.ts +120 -0
  325. package/src/scene/level.ts +296 -0
  326. package/src/scene/material.ts +188 -0
  327. package/src/server/auth/appConfig.ts +12 -0
  328. package/src/server/auth/global.ts +80 -0
  329. package/src/server/auth/host.ts +318 -0
  330. package/src/server/auth/models.ts +83 -0
  331. package/src/server/auth/types.ts +50 -0
  332. package/src/server/channel.ts +56 -0
  333. package/src/server/context.ts +36 -0
  334. package/src/server/db/defineDb.ts +237 -0
  335. package/src/server/db/fields.ts +132 -0
  336. package/src/server/db/httpTransport.ts +93 -0
  337. package/src/server/db/index.ts +7 -0
  338. package/src/server/db/marci/query.ts +412 -0
  339. package/src/server/db/types.ts +202 -0
  340. package/src/server/errors.ts +12 -0
  341. package/src/server/host.ts +74 -0
  342. package/src/server/inject.ts +13 -0
  343. package/src/server/runtime.ts +133 -0
  344. package/src/server/validate.ts +87 -0
  345. package/src/ui/NativeView.ts +142 -0
  346. package/src/ui/UI.ts +39 -0
  347. package/src/ui/UIBottomSheet.ts +139 -0
  348. package/src/ui/UIButton.ts +101 -0
  349. package/src/ui/UIContainer.ts +60 -0
  350. package/src/ui/UIImage.ts +83 -0
  351. package/src/ui/UIInput.ts +185 -0
  352. package/src/ui/UIModal.ts +139 -0
  353. package/src/ui/UINode.ts +830 -0
  354. package/src/ui/UIPager.ts +362 -0
  355. package/src/ui/UIPopover.ts +100 -0
  356. package/src/ui/UIScreen.ts +123 -0
  357. package/src/ui/UIScrollable.ts +87 -0
  358. package/src/ui/UISpacer.ts +14 -0
  359. package/src/ui/UITabs.ts +236 -0
  360. package/src/ui/UIText.ts +51 -0
  361. package/src/ui/UIVideo.ts +88 -0
  362. package/src/ui/UIVirtualizedList.ts +241 -0
  363. package/src/ui/UIWidget.ts +127 -0
  364. package/src/ui/fonts.ts +13 -0
  365. package/src/ui/presentable.ts +117 -0
  366. package/src/ui/router.ts +132 -0
  367. package/src/ui/theme.ts +84 -0
@@ -0,0 +1,505 @@
1
+ // A 3D scene: a draw set, a Camera, lights and environment. Opening a scene asks the host to bring up
2
+ // the 3D engine; only the active scene renders. Grown from the Phase 0 seed. Ported from
3
+ // worker/src/Scene.ts (Scene + ARScene), with the camera exposed as scene.camera.
4
+ //
5
+ // Lights are explicit: a scene gets image-based ambient (IBL) by default; add a Light.sun() for
6
+ // direct light + shadows.
7
+
8
+ import { Color, type ColorInput } from "../core/color"
9
+ import { _installAspectFrames, _installTimeScale, _attachSystem, _detachSystem, _detachAllSystems, type Aspect, type AspectCtor, type FieldOf, type TargetOf } from "../core/Aspect"
10
+ import { _bumpNavEpoch, _navEpoch, _navSupported, _setCurrent, Presentable, type PresentOptions } from "../ui/presentable"
11
+ import { device } from "../runtime/device"
12
+ import type { ClickEvent, TouchStartEvent } from "../runtime/touch"
13
+ import type { FetchResponse } from "../runtime/fetch"
14
+ import { _requestCameraPermission } from "../plugins/permission"
15
+ import { Camera } from "./Camera"
16
+ import { attachControls, type ControlsHandle, type ControlsOptions } from "./controls"
17
+ import { Material } from "./Material"
18
+ import { Node, nodeRegistry } from "./Node"
19
+ import { glState } from "./state"
20
+ import { registerTouchEndEvent, registerTouchStartEvent } from "./touch"
21
+
22
+ export type AmbientOcclusionOptions = {
23
+ /** Strength of the darkening (default 1). */
24
+ intensity?: number
25
+ /** How far the occlusion reaches, in metres (default 0.3). */
26
+ radius?: number
27
+ /** Falloff contrast; >1 tightens it into the crease (default 1). */
28
+ power?: number
29
+ /** Sample count + filtering (default 'medium'). Not the buffer resolution — that stays half. */
30
+ quality?: "low" | "medium" | "high" | "ultra"
31
+ }
32
+
33
+ export type FogOptions = {
34
+ /** A TINT on the in-scattered ambient, not an absolute colour: the engine multiplies it by the
35
+ * environment luminance, so white (the default) means "fog as bright as the ambient" and the fog
36
+ * brightens with `environmentIntensity`. Do NOT expect the same hex to look like it does in
37
+ * `skybox` — that one IS an absolute radiance and reads roughly an order of magnitude brighter.
38
+ * Tint towards the sky's hue; leave it white to sit at the ambient level. */
39
+ color?: ColorInput
40
+ /** Metres from the camera before the fog starts (default 0). */
41
+ start?: number
42
+ /** Extinction per metre at `height`; 0.01 ≈ clearly visible over ~100 m (default 0.01). */
43
+ density?: number
44
+ /** The fog's "sea level" in world Y (default 0). */
45
+ height?: number
46
+ /** How fast it thins with altitude, 1/m. 0 = uniform everywhere; higher = a ground-hugging layer
47
+ * you can see over (default 0). */
48
+ heightFalloff?: number
49
+ /** Cap on how opaque it can get, 0–1 — keeps far shapes readable (default 1). */
50
+ maxOpacity?: number
51
+ /** Metres after which fog stops applying; 0 = everywhere (default 0). */
52
+ cutoff?: number
53
+ /** Take the colour from the environment in the view direction, tinted by `color`, instead of a
54
+ * flat `color`. Convincing when the IBL is a real sky (default false). */
55
+ fromEnvironment?: boolean
56
+ }
57
+
58
+ export type SceneOptions = {
59
+ /** Image-based ambient lighting (default on). `false` turns it off; a STRING is the scene's own
60
+ * probe - `ibl: asset('../assets/ibl-interior.ktx')` - and is how two levels in one project get
61
+ * different environments. Without it the host looks for a project-wide `ibl.ktx`, which is one
62
+ * probe for every scene: swapping that file to suit an interior level silently relights the
63
+ * outdoor one. A named probe also travels through `asset()`, so a build only carries the
64
+ * environments its own entry point reaches. */
65
+ ibl?: boolean | string
66
+ /** IBL intensity (default 20000). */
67
+ environmentIntensity?: number
68
+ /** Camera exposure compensation in stops (default 0) - the same knob as
69
+ * `scene.camera.exposureCompensation`, next to the rest of the scene's lighting so a LEVEL can
70
+ * carry its own. The camera is a physical one at EV100 15 (f/16 - 1/125 - ISO 100), i.e. metered
71
+ * for bright sun; an interior lit at real levels needs +7..+9 to read at all. Expose the camera
72
+ * rather than inflating the lights: scaling lamps past what they physically emit gives bright
73
+ * fixtures in a black room, because it changes the RATIO, not the level. */
74
+ exposureCompensation?: number
75
+ /** Bloom post-processing. */
76
+ bloom?: boolean
77
+ bloomIntensity?: number
78
+ /** Tone mapping operator. `'aces'` (default, filament's ACES legacy) desaturates bright colours
79
+ * towards white — HDR fire reads pale; `'neutral'` (Khronos PBR Neutral) keeps hue and
80
+ * saturation until very bright; `'linear'` clips each channel (what an engine without a
81
+ * tonemapper shows — saturated, Unity-without-post-processing look); `'filmic'` (Uncharted). */
82
+ toneMapping?: "aces" | "neutral" | "linear" | "filmic"
83
+ /** The sky. Three forms:
84
+ * - a colour — a flat clear colour;
85
+ * - `{ texture }` — a KTX1 **cubemap**, the sharp `<name>_skybox.ktx` that filament's `cmgen`
86
+ * produces from an equirectangular .hdr/.exr. This is the one to use for a real sky. Pass an
87
+ * `asset('../assets/sky.ktx')` handle or a bare staged filename;
88
+ * - `'environment'` — reuse the scene's IBL cubemap. Cheapest (no second texture) but that file
89
+ * is prefiltered for roughness, so the sky comes out soft; fine as a fallback, not as the goal.
90
+ *
91
+ * Either texture form draws through filament's own skybox pass: a full-screen pass in device
92
+ * space after the opaque queue. No geometry, no meridian seam, no pole distortion — do NOT build
93
+ * a sky dome or a fullscreen equirect material by hand, both are strictly worse. */
94
+ skybox?: ColorInput | "environment" | { texture: string }
95
+ /** Screen-space ambient occlusion — the contact darkening in creases and where props meet the
96
+ * ground. Without it an IBL lights a crease exactly as brightly as an open face, so everything
97
+ * reads as pasted onto the floor rather than standing on it. `true` takes defaults tuned for
98
+ * human-scale props; `radius` is world-space metres and is the one knob that must follow the
99
+ * scene's scale (~0.3 for objects on a table, ~0.6-1 for a yard of crates and containers). */
100
+ ambientOcclusion?: boolean | AmbientOcclusionOptions
101
+ /** Distance fog / aerial perspective: distant geometry loses contrast so the eye reads depth, and
102
+ * the hard edge where a finite level ends against the skybox goes away. By default the fog applies
103
+ * at every distance — the skybox included — so the sky itself takes the fog colour and the horizon
104
+ * blends on its own; `cutoff` opts geometry beyond a distance back out. */
105
+ fog?: FogOptions
106
+ /** Multisample anti-aliasing: `true` = 4×, `2`/`4` = that many samples, `false` = off (FXAA
107
+ * takes over). Unset keeps the host default (desktop 4×, mobile/web off). The biggest single
108
+ * fill-rate cost after resolution — turn it down on big screens before anything else. */
109
+ antialias?: boolean | 2 | 4
110
+ /** Render the 3D at this fraction of the viewport (0.25–1) and upscale; the UI stays at native
111
+ * resolution. A fixed, predictable cut of per-pixel GPU work — `0.75` is ~45 % cheaper and
112
+ * barely visible in motion, `0.5` quarters it. Headless renders ignore it. */
113
+ renderScale?: number
114
+ /** Let the engine shrink the 3D buffers under `renderScale` only when frames run over budget
115
+ * (Filament dynamic resolution, sharpened upscale) down to `min` (default 0.5). Off by default:
116
+ * it makes the output frame-time dependent, so flow tests / headless renders never enable it. */
117
+ dynamicResolution?: boolean | { min?: number }
118
+ /** Anisotropic filtering, 1 (off) … 16. Default 2. What it buys is detail on surfaces seen at a
119
+ * GRAZING angle — a first-person weapon, a floor, a wall — where the sample footprint is far
120
+ * longer in one axis than the other and isotropic filtering has to pick a mip for the long one,
121
+ * several levels coarser than the short axis deserves. Measured on a weapon seen from the side
122
+ * (local contrast): 8.18 at 1× → 10.02 at 2× → 10.84 at 4× → 11.29 at 8×. The default is 2
123
+ * because it is where the curve is steepest per unit of memory bandwidth; raise it if you have
124
+ * the headroom.
125
+ *
126
+ * It is an ENGINE-WIDE default, not a property of this scene: a sampler is baked when its
127
+ * texture is bound, so this reaches the models a scene loads AFTER it (which is every model in a
128
+ * scene file — `env` is applied before the nodes build) and leaves already-loaded ones alone.
129
+ * On the desktop host `CREATOR_TEXTURE_ANISOTROPY` overrides it, for tuning without a rebuild. */
130
+ anisotropy?: number
131
+ /** The look on an HDR display (a screen with headroom above SDR white — Apple XDR panels, the
132
+ * macOS host today); ignored on SDR. `strength` 0..1 is how much of the picture reaches for the
133
+ * display's headroom (0 only what SDR clipped, 1 nearly everything; default 0.35). `paperWhite`
134
+ * 1..8 is where white lands as a multiple of SDR white — the "HDR brightness" of a console
135
+ * calibration screen, where 1.5–2 is the norm; default 1 keeps a white wall at the UI's white,
136
+ * faithful but dim next to what "HDR on" is expected to look like. Engine-wide like `anisotropy`;
137
+ * `device.hdr` is the runtime form and tells you whether the display has any headroom at all. */
138
+ hdr?: { strength?: number; paperWhite?: number }
139
+ }
140
+
141
+ // Route aspect update(dt) through the render-synced native phases (render() early = before physics,
142
+ // late = after animations / before draw), so followers/cameras are in lockstep with the draw. Done from
143
+ // the 3D layer (not core) so a 2D-only bundle never pulls in the 3D bridge. Installed once, lazily on
144
+ // the first Scene. On an older wasm without the hooks the installer returns false → setLoop fallback.
145
+ let framesInstalled = false
146
+ const installRenderSyncedFrames = (): void => {
147
+ if (framesInstalled) return
148
+ framesInstalled = true
149
+ _installAspectFrames((early, late) => {
150
+ if (typeof _creator.setEarlyUpdate !== "function") return false
151
+ // the frame stamp invalidates every Node's local-matrix cache once per frame (physics / character
152
+ // controllers / animation rewrite transforms natively between frames)
153
+ _creator.setEarlyUpdate((dt: number) => { glState.frame++; early(dt) })
154
+ _creator.setLateUpdate(late)
155
+ return true
156
+ }, (fixed) => {
157
+ // The fixed phase runs INSIDE the engine's substep loop (physics/world.cpp) when the host has the
158
+ // hook; each substep moves bodies natively, so the frame stamp bumps per step too. Feature-detected:
159
+ // an older host returns false and the dispatcher steps its own 1/60 accumulator.
160
+ if (typeof _creator.setFixedUpdate !== "function") return false
161
+ _creator.setFixedUpdate((dt: number) => { glState.frame++; fixed(dt) })
162
+ return true
163
+ })
164
+ // Time.scale / Time.paused reach the engine's own clocks (physics, animators, particles) through
165
+ // setTimeScale; a host without it keeps running its sims at wall-clock speed (feature-detected).
166
+ _installTimeScale((scale) => { _creator.setTimeScale?.(scale) })
167
+ }
168
+
169
+ export class Scene implements Presentable {
170
+ /** @internal native scene handle. */
171
+ readonly _id: number
172
+ /** @internal the IBL intensity this scene was built with (Lightmap.load's ambient floor). */
173
+ _environmentIntensity = 20000
174
+ readonly camera: Camera
175
+
176
+ /** @internal touch listeners, read by the dispatch system. */
177
+ readonly _clickListeners: Array<(ev: ClickEvent<Node | null>) => void> = []
178
+ readonly _touchStartListeners: Array<(ev: TouchStartEvent<Node | null>) => void> = []
179
+
180
+ private _material?: Material
181
+
182
+ private static _active: Scene | null = null
183
+ static get active(): Scene | null { return Scene._active }
184
+
185
+ constructor(options: SceneOptions = {}) {
186
+ installRenderSyncedFrames()
187
+ const parent = (options as any)._parent as Scene | undefined
188
+ if (parent) {
189
+ this.camera = parent.camera
190
+ this._id = _creator.createOverlayScene(parent._id)
191
+ // No camera re-attach: the native createOverlayScene wires the parent's camera into the
192
+ // overlay view. Re-attaching would (a) point camera._sceneId — the source for getRay /
193
+ // getViewDirection / fov — at the overlay's view, whose viewport/projection aren't
194
+ // maintained like the render view's, and (b) create a second native camera component on
195
+ // the shared entity. (Found via the scene editor: gizmo pick rays came out distorted.)
196
+ } else {
197
+ this.camera = new Camera()
198
+ this._id = _creator.createScene()
199
+ this.camera._attach(this._id)
200
+ }
201
+
202
+ if (options.ibl !== false) {
203
+ // `asset()` hands back "id:<n>" (already staged); a bare name is resolved on the spot; -1 is
204
+ // "no probe named", which leaves the host on the project-wide `ibl.ktx` (the old behaviour).
205
+ const src = typeof options.ibl === "string" ? options.ibl : undefined
206
+ const iblId = src === undefined ? -1 : src.startsWith("id:") ? Number(src.slice(3)) : _creatorUtils.fetchLocal(src)
207
+ _creator.setDefaultIbl(this._id, options.environmentIntensity ?? 20000, iblId)
208
+ }
209
+ this._environmentIntensity = options.ibl === false ? 0 : (options.environmentIntensity ?? 20000)
210
+ if (options.exposureCompensation !== undefined) this.camera.exposureCompensation = options.exposureCompensation
211
+ if (options.bloom) _creator.setBloomOptions(this._id, true, options.bloomIntensity ?? 0.2, 1)
212
+ if (options.toneMapping !== undefined && options.toneMapping !== "aces") {
213
+ _creator.setToneMapping?.(this._id, { neutral: 1, linear: 2, filmic: 3 }[options.toneMapping] ?? 0)
214
+ }
215
+ if (options.skybox !== undefined) this.skybox = options.skybox
216
+ if (options.ambientOcclusion) this.setAmbientOcclusion(options.ambientOcclusion)
217
+ if (options.fog) this.setFog(options.fog)
218
+ if (options.antialias !== undefined) {
219
+ const a = options.antialias
220
+ // `false` must reach the host: desktop defaults to 4× MSAA, so an explicit off is a real change.
221
+ _creator.setSceneMultiSampleAntiAliasing(this._id, a !== false, typeof a === "number" ? a : 4)
222
+ }
223
+ // Before anything else in this constructor that could load a texture: the value has to be in
224
+ // place by the time assets bind their samplers.
225
+ if (options.anisotropy !== undefined) _creator.setTextureAnisotropy?.(options.anisotropy)
226
+ if (options.hdr) {
227
+ if (options.hdr.strength !== undefined) device.hdr.strength = options.hdr.strength
228
+ if (options.hdr.paperWhite !== undefined) device.hdr.paperWhite = options.hdr.paperWhite
229
+ }
230
+ if (options.renderScale !== undefined || options.dynamicResolution) {
231
+ this.setRenderOptions(options.renderScale ?? 1, options.dynamicResolution ?? false)
232
+ }
233
+ }
234
+
235
+ /** Runtime form of `renderScale` / `dynamicResolution` (a graphics-settings menu). `renderScale`
236
+ * 0.25–1; older hosts without the method ignore it. */
237
+ setRenderOptions(renderScale: number, dynamicResolution: boolean | { min?: number } = false): void {
238
+ const min = typeof dynamicResolution === "object" ? (dynamicResolution.min ?? 0.5) : 0.5
239
+ _creator.setSceneRenderOptions?.(this._id, renderScale, !!dynamicResolution, min)
240
+ }
241
+
242
+ /** Scene occlusion material. */
243
+ get occlusionMaterial(): Material {
244
+ if (!this._material) this._material = new Material(_creator.getSceneMaterial(this._id))
245
+ return this._material
246
+ }
247
+
248
+ set skybox(sky: ColorInput | "environment" | { texture: string }) {
249
+ if (sky === "environment") { _creator.setSkyboxFromEnvironment?.(this._id); return }
250
+ if (typeof sky === "object" && sky !== null && "texture" in sky) {
251
+ const src = sky.texture
252
+ // `asset()` hands back "id:<n>" (already staged); a bare name is resolved on the spot.
253
+ const id = src.startsWith("id:") ? Number(src.slice(3)) : _creatorUtils.fetchLocal(src)
254
+ _creator.setSkyboxTexture?.(this._id, id)
255
+ return
256
+ }
257
+ _creator.setSkybox(this._id, Color.toPackedRgb(sky))
258
+ }
259
+
260
+ /** Runtime form of `ambientOcclusion` (a graphics-settings menu). `false` turns it off. */
261
+ setAmbientOcclusion(options: boolean | AmbientOcclusionOptions): void {
262
+ const o: AmbientOcclusionOptions = typeof options === "object" ? options : {}
263
+ const quality = { low: 0, medium: 1, high: 2, ultra: 3 }[o.quality ?? "medium"] ?? 1
264
+ _creator.setAmbientOcclusionOptions?.(this._id, options !== false, o.intensity ?? 1, o.radius ?? 0.3, o.power ?? 1, quality)
265
+ }
266
+
267
+ /** Runtime form of `fog` (weather, entering a building). `false` turns it off. */
268
+ setFog(options: FogOptions | false): void {
269
+ const o: FogOptions = options === false ? {} : options
270
+ _creator.setFogOptions?.(
271
+ this._id, options !== false, Color.toPackedRgb(o.color ?? "#ffffff"),
272
+ o.start ?? 0, o.density ?? 0.01, o.height ?? 0, o.heightFalloff ?? 0,
273
+ o.maxOpacity ?? 1, o.cutoff ?? 0, o.fromEnvironment ?? false,
274
+ )
275
+ }
276
+
277
+ setMaterialGlobalParameter(i: number, x: number, y: number, z: number, w: number): void {
278
+ _creator.setMaterialGlobalParameter(this._id, i, x, y, z, w)
279
+ }
280
+ setAntialias(enabled: boolean, scale = 4): void {
281
+ _creator.setSceneMultiSampleAntiAliasing(this._id, enabled, scale)
282
+ }
283
+
284
+ add(...nodes: Node[]): this {
285
+ for (const n of nodes) _creator.addEntityToScene(this._id, n.id)
286
+ return this
287
+ }
288
+ remove(...nodes: Node[]): this {
289
+ for (const n of nodes) _creator.removeEntityFromScene(this._id, n.id)
290
+ return this
291
+ }
292
+
293
+ // ---- systems: aspects of the scene (core/Aspect.ts `System`) ----
294
+ /** @internal class → instance, the authoritative store (named accessors mirror this). */
295
+ readonly _systems = new Map<Function, Aspect<any, any, any>>()
296
+
297
+ /** Attach (and configure) a system, or reconfigure it if already present. Returns the scene typed
298
+ * as now-having it (`scene.system(Hud).hud`). A `System<'x', Scene2D>` is rejected here. */
299
+ // `this: Self` (not `Self & Scene`): with the intersection TS infers Self = Scene and a chain
300
+ // `scene.system(A).system(B)` loses A's accessor from the result type.
301
+ system<Self extends TargetOf<A>, A extends Aspect<any, any, any>>(
302
+ this: Self,
303
+ ctor: AspectCtor<A>,
304
+ opts?: Partial<A>,
305
+ ): Self & FieldOf<A> {
306
+ return _attachSystem(this as unknown as Scene, ctor, opts) as unknown as Self & FieldOf<A>
307
+ }
308
+ /** Safe access — undefined if the system isn't attached. */
309
+ get<A extends Aspect<any, any, any>>(ctor: AspectCtor<A>): A | undefined {
310
+ return this._systems.get(ctor) as A | undefined
311
+ }
312
+ /** Existence check AND type guard: inside `if (scene.has(Hud))`, `scene.hud` is present. */
313
+ has<A extends Aspect<any, any, any>>(ctor: AspectCtor<A>): this is this & FieldOf<A> {
314
+ return this._systems.has(ctor)
315
+ }
316
+ /** Detach a system (runs its onDetach). Chainable. */
317
+ removeSystem<A extends Aspect<any, any, any>>(ctor: AspectCtor<A>): this {
318
+ _detachSystem(this, ctor)
319
+ return this
320
+ }
321
+ /** Tear the scene down: every system detaches (last-attached first) and the scene closes. Nodes
322
+ * are yours — destroy the ones you own; the native scene object itself is not released. */
323
+ destroy(): void {
324
+ _detachAllSystems(this)
325
+ this.close()
326
+ }
327
+
328
+ createOverlay(options: SceneOptions = {}): Scene {
329
+ return new Scene({ ...options, _parent: this } as any)
330
+ }
331
+
332
+ warmRender(): Promise<void> {
333
+ return new Promise<void>((res) => _creator.warmRender(this._id, res))
334
+ }
335
+
336
+ // ---- Presentable (docs/navigation-presentable-plan.md) ----
337
+ // A scene is a destination like a screen: opening it replaces whatever is visible (suspending
338
+ // an active Router until Router.restore()), and it can be pushed onto the Router stack. The
339
+ // scene graph survives while covered — only the view detaches and rendering pauses.
340
+
341
+ /** @internal presentation lifecycle — fired by the host on present/dismiss (new navigation),
342
+ * or synchronously by open()/close() on legacy hosts. */
343
+ readonly ol: (() => void)[] = []
344
+ readonly cl: (() => void)[] = []
345
+ _backButtonCallback?: () => void
346
+ private _vd?: object
347
+
348
+ /** Fires when the scene becomes the visible destination (incl. a pop revealing it). */
349
+ onOpen(callback: () => void): this {
350
+ this.ol.push(callback)
351
+ return this
352
+ }
353
+ /** Fires when the scene stops being visible (closed, replaced, or covered by a push). */
354
+ onClose(callback: () => void): this {
355
+ this.cl.push(callback)
356
+ return this
357
+ }
358
+ onBackPressed(callback: () => void): this {
359
+ this._backButtonCallback = callback
360
+ return this
361
+ }
362
+
363
+ /** @internal stable wire descriptor; hosts hand it back via router onChange (`_p` = this). */
364
+ _viewDesc(): object {
365
+ return this._vd ??= { type: "scene3d", sceneId: this._id, _p: this }
366
+ }
367
+
368
+ /** Make this the active scene — shows it as the current destination (replaces a screen /
369
+ * another scene; only the active scene renders). */
370
+ open(options?: PresentOptions): void | Promise<void> {
371
+ _bumpNavEpoch()
372
+ if (_navSupported()) {
373
+ // The host activates the engine (openScene) as part of presenting the destination.
374
+ _creatorUI.openView!(this._viewDesc(), options?.transition ?? "none")
375
+ } else {
376
+ _creator.openScene(this._id)
377
+ for (const cb of this.ol) cb()
378
+ }
379
+ Scene._active = this
380
+ glState.activeScene = this
381
+ _setCurrent(this)
382
+ }
383
+ close(): void {
384
+ _bumpNavEpoch()
385
+ if (_navSupported()) {
386
+ _creatorUI.closeView!()
387
+ } else {
388
+ _creator.closeScene()
389
+ for (const cb of this.cl) cb()
390
+ }
391
+ if (Scene._active === this) Scene._active = null
392
+ if (glState.activeScene === this) glState.activeScene = null
393
+ if (Presentable.current === this) _setCurrent(null)
394
+ }
395
+
396
+ addEventListener(channel: "click", callback: (ev: ClickEvent<Node | null>) => void): void
397
+ addEventListener(channel: "touchstart", callback: (ev: TouchStartEvent<Node | null>) => void): void
398
+ addEventListener(channel: "click" | "touchstart", callback: (ev: any) => void): void {
399
+ if (channel === "click") {
400
+ if (!this._clickListeners.includes(callback)) { this._clickListeners.push(callback); registerTouchEndEvent() }
401
+ } else if (channel === "touchstart") {
402
+ if (!this._touchStartListeners.includes(callback)) { this._touchStartListeners.push(callback); registerTouchStartEvent() }
403
+ }
404
+ }
405
+ removeEventListener(channel: "click" | "touchstart", callback: (ev: any) => void): void {
406
+ const list = channel === "click" ? this._clickListeners : this._touchStartListeners
407
+ const i = list.indexOf(callback)
408
+ if (i >= 0) list.splice(i, 1)
409
+ }
410
+ }
411
+
412
+ // --- AR ---
413
+
414
+ export type ARMode = "default" | "markers" | "arcore" | "detached"
415
+
416
+ const onTrackStateChange = (entityId: number, track: boolean): void => {
417
+ const node = nodeRegistry.get(entityId)
418
+ if (!node) return
419
+ node.isTracked = track
420
+ if (track) node._dispatchEvent("track")
421
+ else node._dispatchEvent("untrack")
422
+ }
423
+
424
+ export class ARScene<T extends ARMode = "default"> extends Scene {
425
+ readonly mode: T
426
+ useWarmRender = true
427
+ private _root?: Node
428
+
429
+ constructor(options: SceneOptions & { mode?: T } = {}) {
430
+ super(options)
431
+ this.mode = options.mode ?? ("default" as T)
432
+ _creator.createARController(this._id, this.camera.id, this.mode, onTrackStateChange)
433
+ }
434
+
435
+ /** Root anchor node (not available in marker mode). */
436
+ get root(): Node {
437
+ if (this._root) return this._root
438
+ this._root = new Node(_creator.createRootAnchor(this._id))
439
+ return this._root
440
+ }
441
+
442
+ /** Create an anchor from a tracking source (e.g. an image target). */
443
+ createAnchor(source: FetchResponse, physicalWidth = 0.2): Node {
444
+ return new Node(_creator.createAnchor(this._id, physicalWidth, (source as any)._id))
445
+ }
446
+
447
+ /**
448
+ * Let the user place & manipulate a node with touch gestures — the staple AR object-placement
449
+ * interaction: ONE finger drags it across the floor (relative to where the camera looks), TWO fingers
450
+ * pinch to scale and twist to rotate. Returns a handle; call `.remove()` to detach. Tunable via
451
+ * options (min/max scale, and toggles for `pan` / `pinch` / `twist`).
452
+ */
453
+ addControls(target: Node, options: ControlsOptions = {}): ControlsHandle {
454
+ return attachControls(this, target, options)
455
+ }
456
+
457
+ override async open(options?: PresentOptions): Promise<void> {
458
+ await this._prepare()
459
+ super.open(options)
460
+ }
461
+
462
+ /** @internal Presentable prepare hook: camera permission + warm render + AR session, all while
463
+ * the previous destination (typically a loading screen) stays visible — the swap to this scene
464
+ * happens only after it resolves. Warm render is headless on every platform (offscreen
465
+ * swapchain), so no GL view is created here — the host mounts the scene view at present time.
466
+ * Rejects on camera denial / AR launch failure, or if another navigation happened while
467
+ * preparing (superseded) — in every rejection case the current destination stays visible. */
468
+ async _prepare(): Promise<void> {
469
+ const epoch = _navEpoch()
470
+ await _requestCameraPermission()
471
+ if (this.useWarmRender) await this.warmRender()
472
+ await new Promise<void>((res, rej) => _creator.launchAR(this._id, res, rej))
473
+ if (_navEpoch() !== epoch) {
474
+ _creator.stopAR()
475
+ throw new Error("ARScene.open() superseded: another destination was opened while preparing")
476
+ }
477
+ }
478
+
479
+ override close(): void {
480
+ _creator.stopAR()
481
+ super.close()
482
+ }
483
+ }
484
+
485
+ // --- VR ---
486
+
487
+ /**
488
+ * A 3D scene rendered in a VR headset (OpenXR — e.g. Meta Quest). The headset drives the camera:
489
+ * every frame both eyes are rendered from the tracked head pose, so `scene.camera` position/lookAt
490
+ * have no effect while the scene is open. The world origin is at floor level (stage space) —
491
+ * author content with the ground at y = 0 and the user standing at the origin.
492
+ *
493
+ * `open()` rejects on hosts without a VR runtime ("VR is not supported on this device").
494
+ */
495
+ export class VRScene extends Scene {
496
+ override async open(options?: PresentOptions): Promise<void> {
497
+ await new Promise<void>((res, rej) => _creator.launchVR(this._id, res, rej))
498
+ super.open(options)
499
+ }
500
+
501
+ override close(): void {
502
+ _creator.stopVR()
503
+ super.close()
504
+ }
505
+ }