@vgai/engine 0.5.21 → 0.5.23

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 (339) hide show
  1. package/README.md +6 -3
  2. package/dist/adapter/adapter-module.d.ts.map +1 -1
  3. package/dist/adapter/adapter-module.js +2 -4
  4. package/dist/adapter/adapter-surface.d.ts +1 -1
  5. package/dist/adapter/adapter-surface.js +1 -1
  6. package/dist/adapter/authoring.d.ts +19 -21
  7. package/dist/adapter/authoring.d.ts.map +1 -1
  8. package/dist/adapter/authoring.js +2 -2
  9. package/dist/adapter/binding.d.ts +341 -0
  10. package/dist/adapter/binding.d.ts.map +1 -0
  11. package/dist/adapter/binding.js +148 -0
  12. package/dist/adapter/colyseus-networking-adapter.d.ts.map +1 -1
  13. package/dist/adapter/colyseus-networking-adapter.js +23 -4
  14. package/dist/adapter/entrypoint-selection-readers.d.ts +24 -0
  15. package/dist/adapter/entrypoint-selection-readers.d.ts.map +1 -0
  16. package/dist/adapter/entrypoint-selection-readers.js +63 -0
  17. package/dist/adapter/entrypoint-selection-source.d.ts.map +1 -1
  18. package/dist/adapter/entrypoint-selection-source.js +1 -44
  19. package/dist/adapter/finders/scenes-from-entrypoint-selection.d.ts.map +1 -1
  20. package/dist/adapter/finders/scenes-from-entrypoint-selection.js +1 -52
  21. package/dist/adapter/host-context.d.ts +46 -15
  22. package/dist/adapter/host-context.d.ts.map +1 -1
  23. package/dist/adapter/host-context.js +2 -9
  24. package/dist/adapter/index.d.ts +11 -12
  25. package/dist/adapter/index.d.ts.map +1 -1
  26. package/dist/adapter/index.js +9 -10
  27. package/dist/adapter/ingest/game-contract.d.ts +0 -3
  28. package/dist/adapter/ingest/game-contract.d.ts.map +1 -1
  29. package/dist/adapter/ingest/game-contract.js +0 -3
  30. package/dist/adapter/ingest/scene-capture.d.ts +19 -23
  31. package/dist/adapter/ingest/scene-capture.d.ts.map +1 -1
  32. package/dist/adapter/ingest/scene-capture.js +143 -17
  33. package/dist/adapter/ingest/visible-capture-window.d.ts +34 -0
  34. package/dist/adapter/ingest/visible-capture-window.d.ts.map +1 -1
  35. package/dist/adapter/manifest-interpreter.d.ts +59 -0
  36. package/dist/adapter/manifest-interpreter.d.ts.map +1 -0
  37. package/dist/adapter/manifest-interpreter.js +50 -0
  38. package/dist/adapter/native-debug-module.d.ts +71 -3
  39. package/dist/adapter/native-debug-module.d.ts.map +1 -1
  40. package/dist/adapter/native-debug-module.js +146 -10
  41. package/dist/adapter/rapier-physics-adapter.d.ts +31 -1
  42. package/dist/adapter/rapier-physics-adapter.d.ts.map +1 -1
  43. package/dist/adapter/rapier-physics-adapter.js +50 -36
  44. package/dist/adapter/renderer-config.d.ts +3 -3
  45. package/dist/adapter/renderer-config.js +3 -3
  46. package/dist/adapter/root-adapter.d.ts +7 -7
  47. package/dist/adapter/root-adapter.js +4 -4
  48. package/dist/adapter/system-adapter.d.ts +45 -7
  49. package/dist/adapter/system-adapter.d.ts.map +1 -1
  50. package/dist/adapter/system-seam-contract.d.ts +3 -3
  51. package/dist/adapter/system-seam-contract.js +3 -3
  52. package/dist/adapter/system-slot.d.ts +168 -0
  53. package/dist/adapter/system-slot.d.ts.map +1 -0
  54. package/dist/adapter/system-slot.js +229 -0
  55. package/dist/adapter/transform.d.ts +11 -1
  56. package/dist/adapter/transform.d.ts.map +1 -1
  57. package/dist/ai/navigation.d.ts +17 -1
  58. package/dist/ai/navigation.d.ts.map +1 -1
  59. package/dist/ai/navigation.js +26 -5
  60. package/dist/animation/anim-graph-types.d.ts +11 -3
  61. package/dist/animation/anim-graph-types.d.ts.map +1 -1
  62. package/dist/animation/theatre-clock-binding.d.ts +4 -4
  63. package/dist/animation/theatre-clock-binding.js +4 -4
  64. package/dist/animation/xstate-animation-binding.d.ts.map +1 -1
  65. package/dist/animation/xstate-animation-binding.js +2 -2
  66. package/dist/animation/xstate-animation-meta.d.ts +13 -0
  67. package/dist/animation/xstate-animation-meta.d.ts.map +1 -1
  68. package/dist/animation/xstate-animation-meta.js +14 -0
  69. package/dist/asset-formats/index.d.ts +4 -7
  70. package/dist/asset-formats/index.d.ts.map +1 -1
  71. package/dist/asset-formats/index.js +4 -7
  72. package/dist/asset-formats/render-env.d.ts +5 -8
  73. package/dist/asset-formats/render-env.d.ts.map +1 -1
  74. package/dist/asset-formats/render-env.js +3 -6
  75. package/dist/asset-parse-error.d.ts +2 -5
  76. package/dist/asset-parse-error.d.ts.map +1 -1
  77. package/dist/asset-parse-error.js +2 -5
  78. package/dist/audio/bus-mixer.d.ts +101 -0
  79. package/dist/audio/bus-mixer.d.ts.map +1 -0
  80. package/dist/audio/bus-mixer.js +115 -0
  81. package/dist/canvas-react/index.d.ts +5 -12
  82. package/dist/canvas-react/index.d.ts.map +1 -1
  83. package/dist/canvas-react/index.js +5 -12
  84. package/dist/canvas-react/pixi-react-root-factory.d.ts +34 -21
  85. package/dist/canvas-react/pixi-react-root-factory.d.ts.map +1 -1
  86. package/dist/canvas-react/pixi-react-root-factory.js +274 -36
  87. package/dist/core/countdown-timer.d.ts +118 -0
  88. package/dist/core/countdown-timer.d.ts.map +1 -0
  89. package/dist/core/countdown-timer.js +137 -0
  90. package/dist/core/deferred-commands.d.ts +127 -0
  91. package/dist/core/deferred-commands.d.ts.map +1 -0
  92. package/dist/core/deferred-commands.js +132 -0
  93. package/dist/core/frame-pacing.d.ts +1 -1
  94. package/dist/core/frame-pacing.js +2 -2
  95. package/dist/core/game-loop.d.ts +1 -3
  96. package/dist/core/game-loop.d.ts.map +1 -1
  97. package/dist/core/game-loop.js +1 -3
  98. package/dist/core/seeded-random.d.ts +1 -1
  99. package/dist/core/sim-clock.d.ts +19 -8
  100. package/dist/core/sim-clock.d.ts.map +1 -1
  101. package/dist/core/sim-clock.js +18 -7
  102. package/dist/core/types.d.ts +1 -3
  103. package/dist/core/types.d.ts.map +1 -1
  104. package/dist/defaults.d.ts +6 -14
  105. package/dist/defaults.d.ts.map +1 -1
  106. package/dist/defaults.js +6 -14
  107. package/dist/dev/instruments.d.ts +38 -11
  108. package/dist/dev/instruments.d.ts.map +1 -1
  109. package/dist/dev/instruments.js +81 -11
  110. package/dist/dev/register-render-vitals.d.ts +1 -4
  111. package/dist/dev/register-render-vitals.d.ts.map +1 -1
  112. package/dist/dev/register-render-vitals.js +1 -4
  113. package/dist/dev/render-debug-adapter.d.ts +2 -3
  114. package/dist/dev/render-debug-adapter.d.ts.map +1 -1
  115. package/dist/dev/render-debug-adapter.js +2 -3
  116. package/dist/dev/render-vitals.d.ts +3 -3
  117. package/dist/dev/render-vitals.js +3 -3
  118. package/dist/dev/webgl-frame-capture.d.ts +1 -1
  119. package/dist/dev/webgl-frame-capture.js +1 -1
  120. package/dist/index.d.ts +2 -5
  121. package/dist/index.d.ts.map +1 -1
  122. package/dist/index.js +2 -4
  123. package/dist/input/host-pointer.d.ts +122 -0
  124. package/dist/input/host-pointer.d.ts.map +1 -0
  125. package/dist/input/host-pointer.js +160 -0
  126. package/dist/input/input-manager.d.ts +33 -5
  127. package/dist/input/input-manager.d.ts.map +1 -1
  128. package/dist/input/input-manager.js +48 -13
  129. package/dist/input/rebind-controller.d.ts.map +1 -1
  130. package/dist/input/rebind-controller.js +6 -8
  131. package/dist/input/schema.d.ts.map +1 -1
  132. package/dist/input/schema.js +56 -28
  133. package/dist/manifest/filename.d.ts.map +1 -1
  134. package/dist/manifest/filename.js +8 -12
  135. package/dist/manifest/load.d.ts +10 -10
  136. package/dist/manifest/load.d.ts.map +1 -1
  137. package/dist/manifest/load.js +9 -6
  138. package/dist/manifest/schema.d.ts +17 -21
  139. package/dist/manifest/schema.d.ts.map +1 -1
  140. package/dist/manifest/schema.js +45 -35
  141. package/dist/physics/collider-dimensions.d.ts +8 -12
  142. package/dist/physics/collider-dimensions.d.ts.map +1 -1
  143. package/dist/physics/collider-dimensions.js +8 -12
  144. package/dist/pixi/scene-capture.d.ts +11 -13
  145. package/dist/pixi/scene-capture.d.ts.map +1 -1
  146. package/dist/react/world-state.d.ts +1 -1
  147. package/dist/react/world-state.d.ts.map +1 -1
  148. package/dist/render/directional-shadow-fit.d.ts +88 -0
  149. package/dist/render/directional-shadow-fit.d.ts.map +1 -0
  150. package/dist/render/directional-shadow-fit.js +133 -0
  151. package/dist/render/environment-capture.d.ts +68 -0
  152. package/dist/render/environment-capture.d.ts.map +1 -0
  153. package/dist/render/environment-capture.js +90 -0
  154. package/dist/render/ibl-override-material.d.ts +97 -0
  155. package/dist/render/ibl-override-material.d.ts.map +1 -0
  156. package/dist/render/ibl-override-material.js +114 -0
  157. package/dist/render/light-camera-factory.d.ts +5 -7
  158. package/dist/render/light-camera-factory.d.ts.map +1 -1
  159. package/dist/render/light-camera-factory.js +8 -11
  160. package/dist/render/material-factory.js +1 -1
  161. package/dist/render/render-batch-system.d.ts.map +1 -1
  162. package/dist/render/render-features.d.ts +17 -6
  163. package/dist/render/render-features.d.ts.map +1 -1
  164. package/dist/render/render-features.js +0 -28
  165. package/dist/runtime/create-runtime.d.ts +34 -25
  166. package/dist/runtime/create-runtime.d.ts.map +1 -1
  167. package/dist/runtime/create-runtime.js +218 -93
  168. package/dist/runtime/debug-registry.d.ts +77 -8
  169. package/dist/runtime/debug-registry.d.ts.map +1 -1
  170. package/dist/runtime/debug-registry.js +3 -3
  171. package/dist/runtime/dev-build.d.ts +2 -2
  172. package/dist/runtime/dev-build.js +2 -2
  173. package/dist/runtime/game-input-seams.d.ts +51 -0
  174. package/dist/runtime/game-input-seams.d.ts.map +1 -0
  175. package/dist/runtime/game-input-seams.js +86 -0
  176. package/dist/runtime/game.d.ts +104 -40
  177. package/dist/runtime/game.d.ts.map +1 -1
  178. package/dist/runtime/game.js +119 -61
  179. package/dist/runtime/mount-game.d.ts +0 -16
  180. package/dist/runtime/mount-game.d.ts.map +1 -1
  181. package/dist/runtime/mount-game.js +26 -41
  182. package/dist/runtime/mount-manifest.d.ts +22 -14
  183. package/dist/runtime/mount-manifest.d.ts.map +1 -1
  184. package/dist/runtime/mount-manifest.js +22 -20
  185. package/dist/runtime/playtest.d.ts +22 -0
  186. package/dist/runtime/playtest.d.ts.map +1 -0
  187. package/dist/runtime/playtest.js +12 -0
  188. package/dist/runtime/state-bridge.d.ts +2 -3
  189. package/dist/runtime/state-bridge.d.ts.map +1 -1
  190. package/dist/runtime/state-bridge.js +1 -0
  191. package/dist/setup/setup-audio.d.ts +12 -0
  192. package/dist/setup/setup-audio.d.ts.map +1 -1
  193. package/dist/setup/setup-audio.js +9 -10
  194. package/dist/setup/setup-renderer.d.ts +2 -25
  195. package/dist/setup/setup-renderer.d.ts.map +1 -1
  196. package/dist/setup/setup-renderer.js +2 -31
  197. package/dist/world3d-react/index.d.ts +15 -25
  198. package/dist/world3d-react/index.d.ts.map +1 -1
  199. package/dist/world3d-react/index.js +15 -25
  200. package/dist/world3d-react/r3f-root-factory.d.ts +28 -34
  201. package/dist/world3d-react/r3f-root-factory.d.ts.map +1 -1
  202. package/dist/world3d-react/r3f-root-factory.js +403 -57
  203. package/dist/world3d-react/rapier-physics-bridge.d.ts +30 -17
  204. package/dist/world3d-react/rapier-physics-bridge.d.ts.map +1 -1
  205. package/dist/world3d-react/rapier-physics-bridge.js +143 -58
  206. package/package.json +1 -1
  207. package/schemas/engine-capabilities.json +16 -14
  208. package/schemas/vgai-project.schema.json +35 -26
  209. package/src/adapter/adapter-module.ts +2 -4
  210. package/src/adapter/adapter-surface.ts +1 -1
  211. package/src/adapter/authoring.ts +19 -21
  212. package/src/adapter/binding.ts +468 -0
  213. package/src/adapter/colyseus-networking-adapter.ts +20 -5
  214. package/src/adapter/entrypoint-selection-readers.ts +66 -0
  215. package/src/adapter/entrypoint-selection-source.ts +1 -40
  216. package/src/adapter/finders/scenes-from-entrypoint-selection.ts +1 -48
  217. package/src/adapter/host-context.ts +45 -15
  218. package/src/adapter/index.ts +23 -11
  219. package/src/adapter/ingest/game-contract.ts +0 -3
  220. package/src/adapter/ingest/scene-capture.ts +190 -39
  221. package/src/adapter/ingest/visible-capture-window.ts +35 -0
  222. package/src/adapter/manifest-interpreter.ts +64 -0
  223. package/src/adapter/native-debug-module.ts +248 -15
  224. package/src/adapter/rapier-physics-adapter.ts +76 -20
  225. package/src/adapter/renderer-config.ts +3 -3
  226. package/src/adapter/root-adapter.ts +7 -7
  227. package/src/adapter/system-adapter.ts +43 -8
  228. package/src/adapter/system-seam-contract.ts +3 -3
  229. package/src/adapter/system-slot.ts +291 -0
  230. package/src/adapter/transform.ts +18 -1
  231. package/src/ai/navigation.ts +30 -3
  232. package/src/animation/anim-graph-types.ts +11 -3
  233. package/src/animation/theatre-clock-binding.ts +4 -4
  234. package/src/animation/xstate-animation-binding.ts +2 -2
  235. package/src/animation/xstate-animation-meta.ts +16 -0
  236. package/src/asset-formats/index.ts +4 -7
  237. package/src/asset-formats/render-env.ts +3 -6
  238. package/src/asset-parse-error.ts +2 -5
  239. package/src/audio/bus-mixer.ts +161 -0
  240. package/src/canvas-react/index.ts +5 -21
  241. package/src/canvas-react/pixi-react-root-factory.tsx +301 -39
  242. package/src/core/countdown-timer.ts +188 -0
  243. package/src/core/deferred-commands.ts +174 -0
  244. package/src/core/frame-pacing.ts +2 -2
  245. package/src/core/game-loop.ts +1 -3
  246. package/src/core/seeded-random.ts +1 -1
  247. package/src/core/sim-clock.ts +19 -8
  248. package/src/core/types.ts +1 -3
  249. package/src/defaults.ts +6 -14
  250. package/src/dev/instruments.ts +108 -11
  251. package/src/dev/register-render-vitals.ts +1 -4
  252. package/src/dev/render-debug-adapter.ts +2 -3
  253. package/src/dev/render-vitals.ts +3 -3
  254. package/src/dev/webgl-frame-capture.ts +1 -1
  255. package/src/index.ts +2 -5
  256. package/src/input/host-pointer.ts +230 -0
  257. package/src/input/input-manager.ts +49 -13
  258. package/src/input/input-types.ts +1 -1
  259. package/src/input/rebind-controller.ts +6 -8
  260. package/src/input/schema.ts +216 -188
  261. package/src/manifest/filename.ts +8 -12
  262. package/src/manifest/load.ts +17 -13
  263. package/src/manifest/schema.ts +55 -47
  264. package/src/physics/collider-dimensions.ts +8 -12
  265. package/src/pixi/scene-capture.ts +12 -15
  266. package/src/react/world-state.tsx +1 -1
  267. package/src/render/directional-shadow-fit.ts +156 -0
  268. package/src/render/environment-capture.ts +102 -0
  269. package/src/render/ibl-override-material.ts +170 -0
  270. package/src/render/light-camera-factory.ts +8 -11
  271. package/src/render/material-factory.ts +1 -1
  272. package/src/render/render-batch-system.ts +14 -6
  273. package/src/render/render-features.ts +17 -35
  274. package/src/runtime/create-runtime.ts +270 -108
  275. package/src/runtime/debug-registry.ts +84 -8
  276. package/src/runtime/dev-build.ts +2 -2
  277. package/src/runtime/game-input-seams.ts +108 -0
  278. package/src/runtime/game.ts +231 -85
  279. package/src/runtime/mount-game.ts +31 -57
  280. package/src/runtime/mount-manifest.ts +47 -36
  281. package/src/runtime/playtest.ts +22 -0
  282. package/src/runtime/state-bridge.ts +3 -3
  283. package/src/setup/setup-audio.ts +21 -11
  284. package/src/setup/setup-renderer.ts +2 -61
  285. package/src/world3d-react/index.ts +15 -31
  286. package/src/world3d-react/r3f-root-factory.tsx +451 -60
  287. package/src/world3d-react/rapier-physics-bridge.tsx +154 -58
  288. package/dist/adapter/setup-three-root-adapter.d.ts +0 -86
  289. package/dist/adapter/setup-three-root-adapter.d.ts.map +0 -1
  290. package/dist/adapter/setup-three-root-adapter.js +0 -908
  291. package/dist/animation/clip-map.d.ts +0 -12
  292. package/dist/animation/clip-map.d.ts.map +0 -1
  293. package/dist/animation/clip-map.js +0 -31
  294. package/dist/asset-registry.d.ts +0 -38
  295. package/dist/asset-registry.d.ts.map +0 -1
  296. package/dist/asset-registry.js +0 -66
  297. package/dist/canvas-react/engine-bridge.d.ts +0 -45
  298. package/dist/canvas-react/engine-bridge.d.ts.map +0 -1
  299. package/dist/canvas-react/engine-bridge.js +0 -45
  300. package/dist/canvas-react/pixi-react-adapter.d.ts +0 -77
  301. package/dist/canvas-react/pixi-react-adapter.d.ts.map +0 -1
  302. package/dist/canvas-react/pixi-react-adapter.js +0 -294
  303. package/dist/canvas-react/world-context.d.ts +0 -98
  304. package/dist/canvas-react/world-context.d.ts.map +0 -1
  305. package/dist/canvas-react/world-context.js +0 -173
  306. package/dist/dev/debug-draw.d.ts +0 -24
  307. package/dist/dev/debug-draw.d.ts.map +0 -1
  308. package/dist/dev/debug-draw.js +0 -73
  309. package/dist/render/auto-batcher.d.ts +0 -34
  310. package/dist/render/auto-batcher.d.ts.map +0 -1
  311. package/dist/render/auto-batcher.js +0 -140
  312. package/dist/render/lod.d.ts +0 -13
  313. package/dist/render/lod.d.ts.map +0 -1
  314. package/dist/render/lod.js +0 -16
  315. package/dist/runtime/types.d.ts +0 -303
  316. package/dist/runtime/types.d.ts.map +0 -1
  317. package/dist/runtime/types.js +0 -1
  318. package/dist/world3d-react/engine-bridge.d.ts +0 -47
  319. package/dist/world3d-react/engine-bridge.d.ts.map +0 -1
  320. package/dist/world3d-react/engine-bridge.js +0 -73
  321. package/dist/world3d-react/r3f-adapter.d.ts +0 -58
  322. package/dist/world3d-react/r3f-adapter.d.ts.map +0 -1
  323. package/dist/world3d-react/r3f-adapter.js +0 -634
  324. package/dist/world3d-react/world-context.d.ts +0 -182
  325. package/dist/world3d-react/world-context.d.ts.map +0 -1
  326. package/dist/world3d-react/world-context.js +0 -235
  327. package/src/adapter/setup-three-root-adapter.ts +0 -1032
  328. package/src/animation/clip-map.ts +0 -34
  329. package/src/asset-registry.ts +0 -89
  330. package/src/canvas-react/engine-bridge.ts +0 -59
  331. package/src/canvas-react/pixi-react-adapter.tsx +0 -356
  332. package/src/canvas-react/world-context.ts +0 -253
  333. package/src/dev/debug-draw.ts +0 -80
  334. package/src/render/auto-batcher.ts +0 -168
  335. package/src/render/lod.ts +0 -17
  336. package/src/runtime/types.ts +0 -328
  337. package/src/world3d-react/engine-bridge.ts +0 -86
  338. package/src/world3d-react/r3f-adapter.tsx +0 -717
  339. package/src/world3d-react/world-context.ts +0 -358
@@ -1,8 +1,6 @@
1
1
  /**
2
2
  * `r3fRootFactory` — mount a `three` root whose entry module DEFAULT-EXPORTS a
3
- * React component.
4
- *
5
- * The idiomatic-shape fix (D26). A model asked for an R3F world writes:
3
+ * React component. This is the ONE first-party three mount:
6
4
  *
7
5
  * ```tsx
8
6
  * export default function World() {
@@ -10,84 +8,432 @@
10
8
  * }
11
9
  * ```
12
10
  *
13
- * It does NOT write `export const adapter = createR3FAdapter({ id, content })`.
14
- * That export is pure vgai ceremony wrapped around a component the author
15
- * already wrote, and it is the second-largest non-idiomatic thing left in a
16
- * world file (the largest is the missing `<Canvas>`, which is a separate
17
- * question about who owns the canvas — see D26).
18
- *
19
- * The `dom` root already settled this convention: its factory reads
20
- * `entryModule.default` and wraps it. This is the same contract for three
21
- * roots, so both surfaces answer "what do I export?" the same way.
11
+ * The world is an ordinary R3F app. Everything vgai-shaped stays at the entry
12
+ * module's STATIC surface (`export { debug, systems } from './commands'`) and
13
+ * on the host's side of the seam; no vgai runtime context ever enters the
14
+ * React tree. The host controls Fiber's `frameloop: 'never'` scheduler, wires
15
+ * the game-scoped input seams from outside (`../runtime/game-input-seams.ts`),
16
+ * and installs `WorldProvider` off the Game handle so the lib-legal react
17
+ * doors (`useWorldState`, `useOptionalGame`, `useDebugProvider`) resolve.
22
18
  *
23
19
  * ## Why this is NOT in `mount-game.ts`
24
20
  *
25
- * `defaultThreeAdapterFactory` lives beside the registry, and the registry
26
- * module is deliberately dependency-free: its own doc notes threejs is safe to
27
- * ship there because `three` is already an unconditional engine dependency.
28
- * `@react-three/fiber` is NOT it is the PROJECT's dependency, and
29
- * `world3d-react` is the opt-in module that owns it. Putting this factory
30
- * there would make R3F unconditional for every game, including ones with no
31
- * React at all.
21
+ * The adapter registry module is deliberately dependency-free beyond `three`
22
+ * (already an unconditional engine dependency). `@react-three/fiber` is NOT
23
+ * it is the PROJECT's dependency, and `world3d-react` is the opt-in module
24
+ * that owns it. Putting this factory beside the registry would make R3F
25
+ * unconditional for every game, including ones with no React at all; instead
26
+ * each project's `main.ts` registers it (`registerAdapter('three',
27
+ * r3fRootFactory)`).
32
28
  *
33
- * ## Compatibility
29
+ * ## The mount mechanics (fiber v9, host-gated)
34
30
  *
35
- * An `adapter` export still wins, so existing worlds are untouched and a game
36
- * needing full control over its own `RootAdapter` keeps it.
31
+ * three.js identity: this module never imports `three` itself for scene
32
+ * objects fiber's internal `import * as THREE from 'three'` must resolve to
33
+ * the SAME instance the engine's `ThreeHostContext.three` points at, which the
34
+ * importing project guarantees via its Vite
35
+ * `resolve.dedupe: ['three', 'react', 'react-dom']`.
37
36
  */
38
- import { createElement } from 'react';
39
- import { createR3FAdapter } from './r3f-adapter';
37
+ import { advance, createRoot, extend, flushSync, events as pointerEvents, } from '@react-three/fiber';
38
+ import { createElement, Fragment, useEffect } from 'react';
39
+ import { registerRenderVitals } from '../dev/register-render-vitals';
40
+ import { createRenderDebugAdapter, frameCaptureContextFor, } from '../dev/render-debug-adapter';
41
+ import { collectRenderMemory } from '../dev/render-memory';
42
+ import { RENDER_SUBMIT_PHASE } from '../dev/render-vitals';
43
+ import { createWebGLFrameCapture } from '../dev/webgl-frame-capture';
44
+ import { WorldProvider } from '../react/world-state';
45
+ import { getDebugRegistry } from '../runtime/debug-registry';
46
+ import { devBuildEnabled } from '../runtime/dev-build';
47
+ import { DEFAULT_INPUT_MAP_PATH, wireGameInputSeams } from '../runtime/game-input-seams';
48
+ /**
49
+ * Build the `RootAdapter` for one default-exported R3F world. Mounts through
50
+ * react-three-fiber, gated entirely by the host's own loop and rendering
51
+ * through the host's own `WebGLRenderer` — never a second renderer, never a
52
+ * second `requestAnimationFrame` loop.
53
+ */
54
+ function threeWorldAdapter(id, component) {
55
+ const content = createElement(component);
56
+ return {
57
+ id,
58
+ async mount(host) {
59
+ // Headless honesty — every `mount()` must work headless: the conformance
60
+ // kit runs `mount()` in Node — no canvas, no WebGL, no fiber
61
+ // reconciler. Guard the ENTIRE fiber mount behind `!host.headless` and
62
+ // return a bare, real scene + camera instead (still `instanceof
63
+ // host.three.Scene` / `.Camera` — the identity rule holds even here,
64
+ // since this uses the host's own `three` instance, not a fresh import).
65
+ // Residual: the hierarchy is empty under headless conformance —
66
+ // recorded as design O2, not hidden; a react-nil-style headless
67
+ // reconciler mount is the known fix, out of scope.
68
+ if (host.headless) {
69
+ const scene = new host.three.Scene();
70
+ const camera = new host.three.PerspectiveCamera();
71
+ return {
72
+ kind: 'three',
73
+ scene,
74
+ camera,
75
+ drivesOwnLoop: false,
76
+ dispose() {
77
+ /* nothing was ever mounted */
78
+ },
79
+ };
80
+ }
81
+ // Fiber v9 made the THREE catalogue tree-shakeable: `<Canvas>` calls
82
+ // `extend(THREE)` for you, a bare `createRoot` does NOT — without this,
83
+ // the FIRST three intrinsic in the tree (`<color>`, `<ambientLight>`,
84
+ // …) throws "X is not part of the THREE namespace! Did you forget to
85
+ // extend?" at reconcile time. Extending with `host.three` (not a fresh
86
+ // `import * as THREE`) keeps the catalogue on the host's deduped three
87
+ // instance — the same identity rule the returned scene/camera rely on.
88
+ // `extend` merges into a module-global catalogue, so calling it once
89
+ // per mount is idempotent.
90
+ extend(host.three);
91
+ // THE HOST WIRES THIS GAME'S INPUT FROM OUTSIDE THE TREE: the project's
92
+ // declared map, and this root's `game.input.*` seams on the debug
93
+ // registry. Skipping those made a fresh scaffold's bot/autoplay/
94
+ // `vgai eval` input doors throw `DEBUG_INPUT_UNAVAILABLE` and left the
95
+ // declared actions unloaded. The conventional path is optional — quiet
96
+ // when the project ships no map, since a brand-new project declares no
97
+ // actions yet. Actions must exist before any component's first frame
98
+ // reads them — wait for the (never-rejecting) load before the first
99
+ // commit.
100
+ const gameDebugRegistry = host.game ? getDebugRegistry(host.game) : null;
101
+ if (gameDebugRegistry) {
102
+ await wireGameInputSeams(host, gameDebugRegistry, {
103
+ id,
104
+ inputMapPath: DEFAULT_INPUT_MAP_PATH,
105
+ optionalInputMap: true,
106
+ });
107
+ }
108
+ const canvas = host.surface.canvas;
109
+ const root = createRoot(canvas);
110
+ // `RootState` (the live scene/camera/gl fiber built) only arrives via
111
+ // the `onCreated` callback — `root.render()`'s return value is
112
+ // TECHNICALLY the same store, but `onCreated` is the supported hook, and
113
+ // waiting for it (rather than assuming the first commit already ran
114
+ // synchronously) is the honest choice under React 19's concurrent
115
+ // renderer, which does not guarantee a synchronous first commit the way
116
+ // legacy ReactDOM.render did.
117
+ let resolveState;
118
+ const statePromise = new Promise((resolve) => {
119
+ resolveState = resolve;
120
+ });
121
+ let resolveEffectsReady;
122
+ const effectsReady = new Promise((resolve) => {
123
+ resolveEffectsReady = resolve;
124
+ });
125
+ // Rendered after the game's content. React runs passive effects in tree
126
+ // order, so when this fires every provider/command effect in the
127
+ // initial tree has registered. A Play acknowledgement is a claim that
128
+ // the mounted game's command surface exists, not merely that Fiber
129
+ // allocated a scene.
130
+ function MountEffectsReady() {
131
+ useEffect(resolveEffectsReady, []);
132
+ return null;
133
+ }
134
+ // `root.configure()` is ASYNC in fiber v9 (`Promise<ReconcilerRoot>`) —
135
+ // it must be awaited before `render()`.
136
+ await root.configure({
137
+ // The engine's renderer, not a second one — fiber renders THROUGH
138
+ // `host.renderer` instead of constructing its own `WebGLRenderer`.
139
+ gl: host.renderer,
140
+ // …and the host's shadow map, not fiber's.
141
+ //
142
+ // `configure()` runs `gl.shadowMap.enabled = !!shadows`
143
+ // UNCONDITIONALLY, against its own default of `false`. Passing nothing
144
+ // therefore does not mean "leave it alone" — it means "turn shadows
145
+ // off on the renderer the host just configured" (FX-5: every three
146
+ // root silently read flat, with no error anywhere). Reading it back
147
+ // off the renderer is exactly "don't clobber": a boolean also pins
148
+ // `shadowMap.type` to `PCFSoftShadowMap`, which is what
149
+ // `createHostRenderer` already sets, so the round trip is lossless
150
+ // and the HOST stays the one deciding.
151
+ //
152
+ // Optional-chained even though `WebGLRenderer.shadowMap` is not
153
+ // optional: a headless/stub host renderer is a real shape in this
154
+ // repo's tests, and fiber's own `if (gl.shadowMap)` guard means the
155
+ // value it reads there is ignored anyway.
156
+ shadows: host.renderer.shadowMap?.enabled ?? false,
157
+ // The engine's gated loop is the ONLY loop — fiber must never run its
158
+ // own rAF (that would defeat editor pause; `drivesOwnLoop: false`).
159
+ frameloop: 'never',
160
+ // `RenderProps.size` types as the FULL `Size` (width/height/top/left),
161
+ // so `top`/`left` are pinned to 0 (this bridge always fills its whole
162
+ // canvas; no offset viewport).
163
+ size: { width: host.surface.width, height: host.surface.height, top: 0, left: 0 },
164
+ // Fiber's pointer-event manager, passed EXPLICITLY. There IS no
165
+ // default for a bare `createRoot`: the `events = createPointerEvents`
166
+ // default lives in the `<Canvas>` COMPONENT, which this mount does not
167
+ // use, and `configure` gates on `if (events && !state.events.handlers)`.
168
+ // With `events` undefined the store keeps its initial
169
+ // `{ priority, enabled, connected: false }` — no handlers, nothing
170
+ // bound to the canvas, no error — and every mesh-level pointer prop
171
+ // (`onClick`, `onPointerOver`, `onPointerMissed`) is dead. Proven by a
172
+ // control experiment differing ONLY in this property.
173
+ events: pointerEvents,
174
+ onCreated: (state) => resolveState(state),
175
+ });
176
+ // No `<StrictMode>` — the host mounts once; StrictMode's deliberate
177
+ // double-invoke of effects would double-subscribe `useFrame` callbacks
178
+ // against a host loop that only ticks once per frame.
179
+ //
180
+ // `WorldProvider` rides `host.game` — the React seam every dom root
181
+ // already has (`useWorldState`, `useOptionalGame`, `useDebugProvider` in
182
+ // lib code), and the only way a capability hook can reach the ONE
183
+ // game-scoped registry the editor/`vgai eval` read. `host.game` is
184
+ // genuinely absent in bare/headless hosts, so the provider is
185
+ // conditional and those hooks stay the inert no-ops they already
186
+ // document themselves to be.
187
+ const game = host.game;
188
+ const world = createElement(Fragment, null, content, createElement(MountEffectsReady, { key: 'vgai-mount-effects-ready' }));
189
+ const provided = game ? createElement(WorldProvider, { game }, world) : world;
190
+ root.render(provided);
191
+ // A reconcile-time crash (e.g. a missing `extend` catalogue entry)
192
+ // surfaces as an uncaught window error and `onCreated` never fires —
193
+ // without this guard, `mount()` would await `statePromise` FOREVER and
194
+ // silently wedge every world declared after this one (roots mount
195
+ // sequentially). Convert that class of failure into a loud mount error.
196
+ const state = await new Promise((resolve, reject) => {
197
+ const onError = (event) => {
198
+ cleanup();
199
+ root.unmount();
200
+ gameDebugRegistry?.strip(id);
201
+ reject(new Error(`three world "${id}": fiber crashed before its first commit — ${event.message} ` +
202
+ '(mount() fails loudly instead of hanging on onCreated)'));
203
+ };
204
+ const timer = setTimeout(() => {
205
+ cleanup();
206
+ root.unmount();
207
+ gameDebugRegistry?.strip(id);
208
+ reject(new Error(`three world "${id}": onCreated did not fire within 10s — the R3F tree never ` +
209
+ 'reached its first commit (mount() fails loudly instead of hanging)'));
210
+ }, 10_000);
211
+ const cleanup = () => {
212
+ clearTimeout(timer);
213
+ window.removeEventListener('error', onError);
214
+ };
215
+ window.addEventListener('error', onError);
216
+ void Promise.all([statePromise, effectsReady]).then(([s]) => {
217
+ cleanup();
218
+ resolve(s);
219
+ });
220
+ });
221
+ // Clock hardening: fiber's internal `update()` calls
222
+ // `state.clock.getDelta()` BEFORE its `frameloop:'never'` branch, and a
223
+ // RUNNING (or autoStart) `THREE.Clock` accumulates WALL time into
224
+ // `elapsedTime` as a side effect — skewing the deltas the 'never'
225
+ // branch derives from the game timestamps `update(dt)` feeds below.
226
+ // Stopped + autoStart=false makes `getDelta()` a pure no-op, so
227
+ // `useFrame` deltas come from game time alone.
228
+ state.clock.autoStart = false;
229
+ state.clock.stop();
230
+ // THE STORE IS THE TRUTH, NOT THE `onCreated` SNAPSHOT.
231
+ //
232
+ // `state` is one immutable zustand snapshot: every `set(...)` produces a
233
+ // NEW state object. A world that legitimately REPLACES the default
234
+ // camera — `useThree(s => s.set)({ camera })`, which is exactly what
235
+ // drei's `makeDefault` does — never reaches anything read off `state`
236
+ // directly, and fiber's own `update()` renders
237
+ // `state.gl.render(state.scene, state.camera)` off the state object it
238
+ // is HANDED. Measured: an authored `OrthographicCamera` world rendered
239
+ // from fiber's default (0, 0, 5) forever, culling every ground polygon.
240
+ // `state.get()` is fiber's own `StoreApi.getState`, carried on
241
+ // `RootState` for precisely this reason; it is a plain map read.
242
+ const live = () => state.get();
243
+ // --- Live render vitals, host-seeded ---------------------------------
244
+ // A running game must be able to explain its own frame cost through the
245
+ // debug registry, with no capability to install and nothing for a game
246
+ // to write. Gate: `devBuildEnabled()` (the ONE owner of "is this a dev
247
+ // context" — a ship build registers nothing and pays nothing) and a
248
+ // `Game` shell (the readings are folded out of that game's profiler
249
+ // frames). "Not headless" needs no term here: a headless mount returned
250
+ // above, before fiber was ever configured.
251
+ let renderVitals = null;
252
+ if (host.game && devBuildEnabled()) {
253
+ // The readings are folded out of profiler frames, and the profiler is
254
+ // a flag the editor already sets on play — under the dev gate this is
255
+ // the same cost arriving a little earlier, not a new one.
256
+ host.game.profiler.enabled = true;
257
+ if (!gameDebugRegistry) {
258
+ throw new Error(`three world "${id}": mounted Game has no debug registry.`);
259
+ }
260
+ renderVitals = registerRenderVitals({
261
+ registry: gameDebugRegistry,
262
+ worldId: id,
263
+ profiler: host.game.profiler,
264
+ scene: live().scene,
265
+ renderer: host.renderer,
266
+ });
267
+ }
268
+ // Engine-owned render introspection, built over the SAME renderer and
269
+ // scene Fiber uses. A design-time stand-in has no context and a
270
+ // headless mount returned above, so both honestly omit the capability.
271
+ const rendererContext = typeof host.renderer.getContext === 'function' ? host.renderer.getContext() : undefined;
272
+ const frameCaptureContext = frameCaptureContextFor(false, rendererContext);
273
+ const frameCapture = frameCaptureContext
274
+ ? createWebGLFrameCapture(frameCaptureContext)
275
+ : null;
276
+ const renderDebugWiring = frameCapture
277
+ ? createRenderDebugAdapter({
278
+ capture: frameCapture,
279
+ scene: state.scene,
280
+ memory: () => collectRenderMemory(live().scene, host.renderer.info),
281
+ })
282
+ : null;
283
+ // The session debug registry is host instrumentation, not component
284
+ // context. Publishing the existing adapter here lets coverage and
285
+ // editor panels see the same door without wrapping the R3F tree. The
286
+ // world's own declared systems merge in from the entry module at the
287
+ // Game layer (`installDeclaredSystemAdapters`).
288
+ const systemAdapters = gameDebugRegistry
289
+ ? { debug: gameDebugRegistry.adapter }
290
+ : {};
291
+ if (renderDebugWiring)
292
+ systemAdapters.renderDebug = renderDebugWiring.adapter;
293
+ // The engine drives every `useFrame` through the mounted world's
294
+ // `update(dt)` hook, never off a raw host-loop callback. That is the
295
+ // whole pause story: `runFrameImpl` (`runtime/game.ts`) calls
296
+ // `mounted.update?.(dt)` per substep for a host-driven world and SKIPS
297
+ // it while that world is frozen, and `Game.play.step()` ticks it exactly
298
+ // once.
299
+ //
300
+ // `advance(timestamp, runGlobalEffects, state)`'s `timestamp` is
301
+ // consumed as `THREE.Clock.elapsedTime` DIRECTLY when
302
+ // `frameloop:'never'` (verified against fiber's `update()`
303
+ // implementation): `delta = timestamp - clock.elapsedTime;
304
+ // clock.elapsedTime = timestamp`. So `timestamp` must be a
305
+ // monotonically increasing SECONDS value — GAME time, not wall time:
306
+ // `update` simply isn't called while this world is frozen, so
307
+ // accumulating its `dt` means fiber's clock does not advance across a
308
+ // pause. Wall clock would leak the pause duration into the first
309
+ // resumed frame as one giant `useFrame` delta.
310
+ let elapsed = 0;
311
+ return {
312
+ kind: 'three',
313
+ // Fiber's REAL `THREE.Scene`/`THREE.Camera`. Checkout development
314
+ // dedupes `three`; a registry-installed project and the prebundled
315
+ // editor can legitimately have distinct constructor identities, so
316
+ // editor boundaries recognize Three's stable structural brands
317
+ // (`isScene`, `isCamera`) rather than relying only on `instanceof`.
318
+ //
319
+ // Both are GETTERS over the live store, not snapshot fields: the host
320
+ // and the editor must see the camera the world actually renders
321
+ // through, including one the world swapped in after the first commit
322
+ // (see `live` above).
323
+ get scene() {
324
+ return live().scene;
325
+ },
326
+ get camera() {
327
+ return live().camera;
328
+ },
329
+ drivesOwnLoop: false,
330
+ // Adapter surface: `debug` (the shared game registry) and, when a real
331
+ // WebGL2 context exists, `renderDebug` are engine-seeded. The game's
332
+ // own capabilities arrive as the entry module's declared `systems`.
333
+ systems: systemAdapters,
334
+ update(dt) {
335
+ elapsed += dt;
336
+ const current = live();
337
+ renderDebugWiring?.beforeRender();
338
+ try {
339
+ const profiler = host.game?.profiler;
340
+ if (!profiler?.enabled) {
341
+ advance(elapsed, true, current);
342
+ return;
343
+ }
344
+ // The frame's CPU submission cost, produced where the draw
345
+ // actually happens: `advance()` under `frameloop: 'never'` is what
346
+ // calls `gl.render(scene, camera)`, and this bracket is also what
347
+ // makes this frame a PRESENTATION as far as the vitals fold is
348
+ // concerned (`../dev/render-vitals.ts`: no bracket, no display
349
+ // frame). The profiler's phase clock is a stack, so bracketing
350
+ // here cannot truncate an enclosing phase.
351
+ const info = host.renderer.info;
352
+ const passesBefore = info?.render.frame ?? 0;
353
+ profiler.beginPhase();
354
+ try {
355
+ advance(elapsed, true, current);
356
+ }
357
+ finally {
358
+ profiler.endPhase(RENDER_SUBMIT_PHASE);
359
+ }
360
+ // Checked, not assumed — a duck-typed/design-time renderer
361
+ // supplies only what a mount needs, and a mount with no counters
362
+ // must report NOTHING rather than throw once per frame. `gpuMs` is
363
+ // null: this path runs no GPU timer, and the profiler keeps the
364
+ // previous reading rather than inventing a zero.
365
+ if (info) {
366
+ profiler.reportRender({
367
+ gpuMs: null,
368
+ drawCalls: info.render.calls,
369
+ triangles: info.render.triangles,
370
+ geometries: info.memory.geometries,
371
+ textures: info.memory.textures,
372
+ renderPasses: info.render.frame - passesBefore,
373
+ });
374
+ }
375
+ }
376
+ finally {
377
+ renderDebugWiring?.afterRender();
378
+ }
379
+ },
380
+ resize(width, height) {
381
+ live().setSize(width, height);
382
+ },
383
+ dispose() {
384
+ // Reject/restore a pending capture while its renderables still
385
+ // exist, then unmount component effects and strip this world's
386
+ // registrations.
387
+ renderDebugWiring?.dispose();
388
+ // The editor audits and disposes the mounted game's window/document
389
+ // realm immediately after `session.stop()`. Fiber's ordinary
390
+ // `unmount()` submits a concurrent reconciler update, so component
391
+ // effect cleanups could still be pending at that boundary and the
392
+ // audit would truthfully report/reclaim listeners the world was
393
+ // about to remove itself. Force the unmount COMMIT (not fiber's
394
+ // deliberately delayed renderer/context disposal) to finish here: a
395
+ // stopped root owns no live component lifecycle beyond this method.
396
+ flushSync(() => root.unmount());
397
+ // The vitals fold owns two `profiler.subscribe` registrations —
398
+ // ended here, before the strip, so a subscription can never outlive
399
+ // the door it feeds.
400
+ renderVitals?.dispose();
401
+ renderVitals = null;
402
+ // `strip(id)` ends this world's PROVIDERS AND COMMANDS (the
403
+ // render-vitals door above is the one this mount registers under
404
+ // `id`); sibling roots are untouched. It does NOT end the input
405
+ // seams `wireGameInputSeams` set — see that module's LIFETIME note.
406
+ gameDebugRegistry?.strip(id);
407
+ },
408
+ };
409
+ },
410
+ };
411
+ }
40
412
  /**
41
413
  * What a three entry module MEANS, in one place.
42
414
  *
43
415
  * Two callers need this answer and must never disagree about it: the runtime
44
416
  * mount ({@link r3fRootFactory}, below) and the EDITOR's design session
45
417
  * (`packages/editor/src/authoring/r3f-design-session.ts`), which design-mounts
46
- * the same entry so edit mode authors the live fiber scene. When only the
47
- * runtime knew about the default-export shape, the converted starter played
48
- * fine under `npm run game` and edit mode showed "(no scene loaded)".
418
+ * the same entry so edit mode authors the live fiber scene.
49
419
  *
50
- * Returns `null` when the module is neither shape — the callers differ on what
51
- * to do about that (the factory throws, the design session declines the world
52
- * and leaves existing paths alone).
420
+ * Returns `null` when the module has no default-exported component — the
421
+ * callers differ on what to do about that (the factory throws, the design
422
+ * session declines the world and leaves existing paths alone).
53
423
  */
54
424
  export function resolveR3FEntryAdapter(entryModule, rootId) {
55
425
  const mod = entryModule;
56
- if (mod?.adapter)
57
- return mod.adapter;
58
- if (typeof mod?.default === 'function') {
59
- return createR3FAdapter({
60
- id: rootId,
61
- content: createElement(mod.default),
62
- // A default-exported R3F component is an ordinary R3F app. The host
63
- // controls Fiber's native `frameloop: 'never'` scheduler; the vgai
64
- // runtime CONTEXT (`useGameContext`, `ctx.systems`) never enters its
65
- // React tree.
66
- //
67
- // This flag governs that context and nothing else. It does NOT govern
68
- // `WorldProvider`: `useDebugProvider`/`useDebugCommand`/`useWorldState`
69
- // are the documented door a game declares its own state/commands
70
- // through, and they must work in the idiomatic shape — which is the
71
- // ONLY shape `vgai create` scaffolds. `r3f-adapter.tsx` therefore keys
72
- // that provider off `host.game`, not off this flag; see the comment
73
- // beside `const game = host.game` there for the measured symptom
74
- // (`vgai eval 'await game.providers()'` listing only built-ins).
75
- engineRuntime: false,
76
- });
77
- }
426
+ if (typeof mod?.default === 'function')
427
+ return threeWorldAdapter(rootId, mod.default);
78
428
  return null;
79
429
  }
80
430
  /**
81
431
  * Register with `registerAdapter('three', r3fRootFactory)`.
82
- *
83
- * Resolution order mirrors `defaultThreeAdapterFactory`'s (explicit adapter
84
- * wins) and then accepts the default-exported component.
85
432
  */
86
433
  export const r3fRootFactory = (root, ctx) => {
87
434
  const adapter = resolveR3FEntryAdapter(ctx.entryModule, root.id);
88
435
  if (adapter)
89
436
  return { kind: 'three', adapter };
90
437
  throw new Error(`r3fRootFactory: entry module "${root.entry ?? '(none)'}" for root "${root.id}" must ` +
91
- 'default-export a React component (`export default function World() { … }`) — or export ' +
92
- 'an `adapter` for full control.');
438
+ 'default-export a React component (`export default function World() { … }`).');
93
439
  };
@@ -4,11 +4,9 @@
4
4
  *
5
5
  * WHY IT EXISTS. `SystemAdapters.physics` is what the editor's transform
6
6
  * coordination (`freeze → apply → unfreeze`) and the universal
7
- * "Colliders drawn" instrument (`@engine/dev/instruments`) read. The
8
- * first-party imperative three root registers one from
9
- * `../adapter/setup-three-root-adapter.ts`; an R3F world builds no first-party
10
- * Rapier runtime at all (see `R3FGameContext`'s doc comment) — it does physics
11
- * with `@react-three/rapier` INSIDE the fiber tree, where nothing engine-side
7
+ * "Colliders drawn" instrument (`@engine/dev/instruments`) read. An R3F world
8
+ * builds no first-party Rapier runtime at all it does physics with
9
+ * `@react-three/rapier` INSIDE the fiber tree, where nothing engine-side
12
10
  * can see it. Result, before this: the colliders instrument read `null` (an
13
11
  * em-dash) and its toggle threw "no physics adapter" in every shipped example
14
12
  * and every scaffolded game.
@@ -40,24 +38,30 @@
40
38
  * is how a `SystemAdapters` member says "not supported".
41
39
  *
42
40
  * ── RESOURCE OWNERSHIP, STATED ONCE ─────────────────────────────────────────
43
- * OWNER: this component owns exactly ONE `SystemAdapters.physics` registration,
44
- * on the R3F root it is mounted in. It allocates nothing else no timers, no
45
- * subscriptions, no per-frame work (see the phase note below), and no Rapier
46
- * objects: the bodies it edits belong to `<Physics>`.
47
- * SHARERS: every reader of `game.systemAdapters.physics` the
48
- * colliders instrument, the editor's transform coordination. They read; they
49
- * never dispose.
50
- * TEARDOWN: the effect's cleanup, and nothing else — it clears the SAME key it
51
- * set (`registerSystemAdapter('physics', undefined)`, which
52
- * `Game.computeSystemAdapters` skips), so a play stop/start, a hot restart, or
53
- * a world that unmounts `<Physics>` leaves no stale adapter behind. Two
54
- * bridges in one root would fight over that one key; mount exactly one.
41
+ * OWNER: this component owns one physics ADAPTER, published onto the ONE
42
+ * module-scoped slot `rapierPhysicsSystem()` forwards to (GAME-GLOBALthe
43
+ * entry's static `systems.physics` declaration cannot be per-root). It allocates nothing
44
+ * else no timers, no subscriptions, no per-frame work (see the phase note
45
+ * below), and no Rapier objects: the bodies it edits belong to `<Physics>`.
46
+ * SHARERS: every reader of `game.systemAdapters.physics` the colliders
47
+ * instrument, the editor's transform coordination. They read; they never
48
+ * dispose.
49
+ * TEARDOWN: the effect's cleanup, and nothing else — it clears the SAME key
50
+ * and the slot it set (identity-guarded), so a play stop/start, a hot
51
+ * restart, or a world that unmounts `<Physics>` leaves no stale adapter
52
+ * behind. Because the slot is PAGE-global (see its doc below an engine
53
+ * module is shared across the editor's Play instances), mount exactly ONE
54
+ * bridge per PAGE: with two — a scene that mounts two, or two Play instances
55
+ * of one rapier world — the later mount wins the slot and the later unmount
56
+ * clears it while the earlier bridge is still live. That state is reported
57
+ * loudly the moment it starts (`warnOnContendedMount`).
55
58
  *
56
59
  * PHASE ORDERING: none to respect — this component registers pull-only
57
60
  * callbacks and runs no per-frame work. Every method it exposes is invoked
58
61
  * BETWEEN frames, by an editor gesture or an instrument command, never from a
59
62
  * phase.
60
63
  */
64
+ import type { PhysicsAdapter } from '../adapter/system-adapter';
61
65
  export interface RapierPhysicsBridgeProps {
62
66
  /**
63
67
  * Set the `debug` prop of the SAME `<Physics>` this bridge is mounted in.
@@ -73,4 +77,13 @@ export interface RapierPhysicsBridgeProps {
73
77
  * `useRapier()`). Renders nothing.
74
78
  */
75
79
  export declare function RapierPhysicsBridge({ onDebugChange }: RapierPhysicsBridgeProps): null;
80
+ /**
81
+ * The `systems.physics` slot for a default-exported world — declare it from
82
+ * the entry (`export const systems = { physics: rapierPhysicsSystem() }`) and
83
+ * keep `<RapierPhysicsBridge />` mounted inside `<Physics>`. Every call
84
+ * forwards to the LIVE bridge adapter, so the static declaration answers with
85
+ * the running world's physics; while none is mounted, every call refuses by
86
+ * name rather than fabricating.
87
+ */
88
+ export declare function rapierPhysicsSystem(): PhysicsAdapter;
76
89
  //# sourceMappingURL=rapier-physics-bridge.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"rapier-physics-bridge.d.ts","sourceRoot":"","sources":["../../src/world3d-react/rapier-physics-bridge.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AAkBH,MAAM,WAAW,wBAAwB;IACvC;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CAClD;AAkCD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,aAAa,EAAE,EAAE,wBAAwB,GAAG,IAAI,CA0PrF"}
1
+ {"version":3,"file":"rapier-physics-bridge.d.ts","sourceRoot":"","sources":["../../src/world3d-react/rapier-physics-bridge.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8DG;AAUH,OAAO,KAAK,EACV,cAAc,EAKf,MAAM,2BAA2B,CAAC;AAGnC,MAAM,WAAW,wBAAwB;IACvC;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CAClD;AAkCD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,aAAa,EAAE,EAAE,wBAAwB,GAAG,IAAI,CA0QrF;AAkED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,IAAI,cAAc,CAEpD"}