@vgai/engine 0.5.22 → 0.5.24

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 (303) 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-seams.d.ts +5 -0
  28. package/dist/adapter/ingest/game-contract-seams.d.ts.map +1 -1
  29. package/dist/adapter/ingest/game-contract-seams.js +1 -0
  30. package/dist/adapter/ingest/game-contract.d.ts +22 -3
  31. package/dist/adapter/ingest/game-contract.d.ts.map +1 -1
  32. package/dist/adapter/ingest/game-contract.js +0 -3
  33. package/dist/adapter/ingest/scene-capture.d.ts +19 -23
  34. package/dist/adapter/ingest/scene-capture.d.ts.map +1 -1
  35. package/dist/adapter/ingest/scene-capture.js +143 -17
  36. package/dist/adapter/ingest/visible-capture-window.d.ts +34 -0
  37. package/dist/adapter/ingest/visible-capture-window.d.ts.map +1 -1
  38. package/dist/adapter/native-debug-module.d.ts +40 -5
  39. package/dist/adapter/native-debug-module.d.ts.map +1 -1
  40. package/dist/adapter/native-debug-module.js +36 -9
  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 +2 -2
  45. package/dist/adapter/renderer-config.js +2 -2
  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/core/frame-pacing.d.ts +1 -1
  82. package/dist/core/frame-pacing.js +2 -2
  83. package/dist/core/game-loop.d.ts +1 -3
  84. package/dist/core/game-loop.d.ts.map +1 -1
  85. package/dist/core/game-loop.js +1 -3
  86. package/dist/core/seeded-random.d.ts +1 -1
  87. package/dist/core/sim-clock.d.ts +5 -6
  88. package/dist/core/sim-clock.d.ts.map +1 -1
  89. package/dist/core/sim-clock.js +4 -5
  90. package/dist/core/types.d.ts +1 -3
  91. package/dist/core/types.d.ts.map +1 -1
  92. package/dist/defaults.d.ts +6 -14
  93. package/dist/defaults.d.ts.map +1 -1
  94. package/dist/defaults.js +6 -14
  95. package/dist/dev/register-render-vitals.d.ts +1 -4
  96. package/dist/dev/register-render-vitals.d.ts.map +1 -1
  97. package/dist/dev/register-render-vitals.js +1 -4
  98. package/dist/dev/render-debug-adapter.d.ts +2 -3
  99. package/dist/dev/render-debug-adapter.d.ts.map +1 -1
  100. package/dist/dev/render-debug-adapter.js +2 -3
  101. package/dist/dev/render-vitals.d.ts +3 -3
  102. package/dist/dev/render-vitals.js +3 -3
  103. package/dist/dev/webgl-frame-capture.d.ts +1 -1
  104. package/dist/dev/webgl-frame-capture.js +1 -1
  105. package/dist/index.d.ts +2 -5
  106. package/dist/index.d.ts.map +1 -1
  107. package/dist/index.js +2 -4
  108. package/dist/input/host-pointer.d.ts +122 -0
  109. package/dist/input/host-pointer.d.ts.map +1 -0
  110. package/dist/input/host-pointer.js +160 -0
  111. package/dist/input/input-manager.d.ts +18 -5
  112. package/dist/input/input-manager.d.ts.map +1 -1
  113. package/dist/input/input-manager.js +33 -13
  114. package/dist/input/rebind-controller.d.ts.map +1 -1
  115. package/dist/input/rebind-controller.js +6 -8
  116. package/dist/input/schema.d.ts.map +1 -1
  117. package/dist/input/schema.js +56 -28
  118. package/dist/manifest/filename.d.ts.map +1 -1
  119. package/dist/manifest/filename.js +8 -12
  120. package/dist/manifest/load.d.ts +10 -10
  121. package/dist/manifest/load.d.ts.map +1 -1
  122. package/dist/manifest/load.js +9 -6
  123. package/dist/manifest/schema.d.ts +17 -21
  124. package/dist/manifest/schema.d.ts.map +1 -1
  125. package/dist/manifest/schema.js +45 -35
  126. package/dist/physics/collider-dimensions.d.ts +8 -12
  127. package/dist/physics/collider-dimensions.d.ts.map +1 -1
  128. package/dist/physics/collider-dimensions.js +8 -12
  129. package/dist/pixi/scene-capture.d.ts +11 -13
  130. package/dist/pixi/scene-capture.d.ts.map +1 -1
  131. package/dist/react/world-state.d.ts +1 -1
  132. package/dist/react/world-state.d.ts.map +1 -1
  133. package/dist/render/light-camera-factory.d.ts +5 -7
  134. package/dist/render/light-camera-factory.d.ts.map +1 -1
  135. package/dist/render/light-camera-factory.js +8 -11
  136. package/dist/render/material-factory.js +1 -1
  137. package/dist/render/render-batch-system.d.ts.map +1 -1
  138. package/dist/render/render-features.d.ts +17 -6
  139. package/dist/render/render-features.d.ts.map +1 -1
  140. package/dist/render/render-features.js +0 -28
  141. package/dist/runtime/create-runtime.d.ts +34 -25
  142. package/dist/runtime/create-runtime.d.ts.map +1 -1
  143. package/dist/runtime/create-runtime.js +218 -93
  144. package/dist/runtime/debug-bridge.d.ts +12 -0
  145. package/dist/runtime/debug-bridge.d.ts.map +1 -1
  146. package/dist/runtime/debug-bridge.js +4 -0
  147. package/dist/runtime/debug-registry.d.ts +95 -8
  148. package/dist/runtime/debug-registry.d.ts.map +1 -1
  149. package/dist/runtime/debug-registry.js +24 -3
  150. package/dist/runtime/dev-build.d.ts +2 -2
  151. package/dist/runtime/dev-build.js +2 -2
  152. package/dist/runtime/game-input-seams.d.ts +2 -2
  153. package/dist/runtime/game-input-seams.d.ts.map +1 -1
  154. package/dist/runtime/game.d.ts +102 -41
  155. package/dist/runtime/game.d.ts.map +1 -1
  156. package/dist/runtime/game.js +80 -54
  157. package/dist/runtime/mount-game.d.ts +0 -16
  158. package/dist/runtime/mount-game.d.ts.map +1 -1
  159. package/dist/runtime/mount-game.js +8 -39
  160. package/dist/runtime/mount-manifest.d.ts +22 -14
  161. package/dist/runtime/mount-manifest.d.ts.map +1 -1
  162. package/dist/runtime/mount-manifest.js +18 -18
  163. package/dist/runtime/playtest.d.ts +22 -0
  164. package/dist/runtime/playtest.d.ts.map +1 -0
  165. package/dist/runtime/playtest.js +12 -0
  166. package/dist/runtime/run-ticks-settled.d.ts +34 -0
  167. package/dist/runtime/run-ticks-settled.d.ts.map +1 -0
  168. package/dist/runtime/run-ticks-settled.js +59 -0
  169. package/dist/runtime/state-bridge.d.ts +2 -3
  170. package/dist/runtime/state-bridge.d.ts.map +1 -1
  171. package/dist/runtime/state-bridge.js +1 -0
  172. package/dist/setup/setup-audio.d.ts +12 -0
  173. package/dist/setup/setup-audio.d.ts.map +1 -1
  174. package/dist/setup/setup-audio.js +9 -10
  175. package/dist/setup/setup-renderer.d.ts +2 -25
  176. package/dist/setup/setup-renderer.d.ts.map +1 -1
  177. package/dist/setup/setup-renderer.js +2 -31
  178. package/dist/world3d-react/r3f-root-factory.d.ts +7 -7
  179. package/dist/world3d-react/r3f-root-factory.js +7 -7
  180. package/dist/world3d-react/rapier-physics-bridge.d.ts +9 -8
  181. package/dist/world3d-react/rapier-physics-bridge.d.ts.map +1 -1
  182. package/dist/world3d-react/rapier-physics-bridge.js +100 -48
  183. package/package.json +1 -1
  184. package/schemas/engine-capabilities.json +16 -14
  185. package/schemas/vgai-project.schema.json +35 -26
  186. package/src/adapter/adapter-module.ts +2 -4
  187. package/src/adapter/adapter-surface.ts +1 -1
  188. package/src/adapter/authoring.ts +19 -21
  189. package/src/adapter/binding.ts +468 -0
  190. package/src/adapter/colyseus-networking-adapter.ts +20 -5
  191. package/src/adapter/entrypoint-selection-readers.ts +66 -0
  192. package/src/adapter/entrypoint-selection-source.ts +1 -40
  193. package/src/adapter/finders/scenes-from-entrypoint-selection.ts +1 -48
  194. package/src/adapter/host-context.ts +45 -15
  195. package/src/adapter/index.ts +23 -11
  196. package/src/adapter/ingest/game-contract-seams.ts +1 -0
  197. package/src/adapter/ingest/game-contract.ts +22 -3
  198. package/src/adapter/ingest/scene-capture.ts +190 -39
  199. package/src/adapter/ingest/visible-capture-window.ts +35 -0
  200. package/src/adapter/native-debug-module.ts +66 -9
  201. package/src/adapter/rapier-physics-adapter.ts +76 -20
  202. package/src/adapter/renderer-config.ts +2 -2
  203. package/src/adapter/root-adapter.ts +7 -7
  204. package/src/adapter/system-adapter.ts +43 -8
  205. package/src/adapter/system-seam-contract.ts +3 -3
  206. package/src/adapter/system-slot.ts +291 -0
  207. package/src/adapter/transform.ts +18 -1
  208. package/src/ai/navigation.ts +30 -3
  209. package/src/animation/anim-graph-types.ts +11 -3
  210. package/src/animation/theatre-clock-binding.ts +4 -4
  211. package/src/animation/xstate-animation-binding.ts +2 -2
  212. package/src/animation/xstate-animation-meta.ts +16 -0
  213. package/src/asset-formats/index.ts +4 -7
  214. package/src/asset-formats/render-env.ts +3 -6
  215. package/src/asset-parse-error.ts +2 -5
  216. package/src/audio/bus-mixer.ts +161 -0
  217. package/src/core/frame-pacing.ts +2 -2
  218. package/src/core/game-loop.ts +1 -3
  219. package/src/core/seeded-random.ts +1 -1
  220. package/src/core/sim-clock.ts +5 -6
  221. package/src/core/types.ts +1 -3
  222. package/src/defaults.ts +6 -14
  223. package/src/dev/register-render-vitals.ts +1 -4
  224. package/src/dev/render-debug-adapter.ts +2 -3
  225. package/src/dev/render-vitals.ts +3 -3
  226. package/src/dev/webgl-frame-capture.ts +1 -1
  227. package/src/index.ts +2 -5
  228. package/src/input/host-pointer.ts +230 -0
  229. package/src/input/input-manager.ts +34 -13
  230. package/src/input/input-types.ts +1 -1
  231. package/src/input/rebind-controller.ts +6 -8
  232. package/src/input/schema.ts +216 -188
  233. package/src/manifest/filename.ts +8 -12
  234. package/src/manifest/load.ts +17 -13
  235. package/src/manifest/schema.ts +55 -47
  236. package/src/physics/collider-dimensions.ts +8 -12
  237. package/src/pixi/scene-capture.ts +12 -15
  238. package/src/react/world-state.tsx +1 -1
  239. package/src/render/light-camera-factory.ts +8 -11
  240. package/src/render/material-factory.ts +1 -1
  241. package/src/render/render-batch-system.ts +14 -6
  242. package/src/render/render-features.ts +17 -35
  243. package/src/runtime/create-runtime.ts +270 -108
  244. package/src/runtime/debug-bridge.ts +16 -0
  245. package/src/runtime/debug-registry.ts +121 -8
  246. package/src/runtime/dev-build.ts +2 -2
  247. package/src/runtime/game-input-seams.ts +2 -2
  248. package/src/runtime/game.ts +180 -80
  249. package/src/runtime/mount-game.ts +8 -56
  250. package/src/runtime/mount-manifest.ts +43 -34
  251. package/src/runtime/playtest.ts +22 -0
  252. package/src/runtime/run-ticks-settled.ts +72 -0
  253. package/src/runtime/state-bridge.ts +3 -3
  254. package/src/setup/setup-audio.ts +21 -11
  255. package/src/setup/setup-renderer.ts +2 -61
  256. package/src/world3d-react/r3f-root-factory.tsx +7 -7
  257. package/src/world3d-react/rapier-physics-bridge.tsx +107 -50
  258. package/dist/adapter/setup-three-root-adapter.d.ts +0 -86
  259. package/dist/adapter/setup-three-root-adapter.d.ts.map +0 -1
  260. package/dist/adapter/setup-three-root-adapter.js +0 -908
  261. package/dist/animation/clip-map.d.ts +0 -12
  262. package/dist/animation/clip-map.d.ts.map +0 -1
  263. package/dist/animation/clip-map.js +0 -31
  264. package/dist/asset-registry.d.ts +0 -38
  265. package/dist/asset-registry.d.ts.map +0 -1
  266. package/dist/asset-registry.js +0 -66
  267. package/dist/canvas-react/engine-bridge.d.ts +0 -45
  268. package/dist/canvas-react/engine-bridge.d.ts.map +0 -1
  269. package/dist/canvas-react/engine-bridge.js +0 -45
  270. package/dist/canvas-react/pixi-react-adapter.d.ts +0 -77
  271. package/dist/canvas-react/pixi-react-adapter.d.ts.map +0 -1
  272. package/dist/canvas-react/pixi-react-adapter.js +0 -294
  273. package/dist/canvas-react/world-context.d.ts +0 -98
  274. package/dist/canvas-react/world-context.d.ts.map +0 -1
  275. package/dist/canvas-react/world-context.js +0 -173
  276. package/dist/dev/debug-draw.d.ts +0 -24
  277. package/dist/dev/debug-draw.d.ts.map +0 -1
  278. package/dist/dev/debug-draw.js +0 -73
  279. package/dist/render/auto-batcher.d.ts +0 -34
  280. package/dist/render/auto-batcher.d.ts.map +0 -1
  281. package/dist/render/auto-batcher.js +0 -140
  282. package/dist/render/lod.d.ts +0 -13
  283. package/dist/render/lod.d.ts.map +0 -1
  284. package/dist/render/lod.js +0 -16
  285. package/dist/runtime/types.d.ts +0 -303
  286. package/dist/runtime/types.d.ts.map +0 -1
  287. package/dist/runtime/types.js +0 -1
  288. package/dist/world3d-react/engine-bridge.d.ts +0 -47
  289. package/dist/world3d-react/engine-bridge.d.ts.map +0 -1
  290. package/dist/world3d-react/engine-bridge.js +0 -73
  291. package/dist/world3d-react/r3f-adapter.d.ts +0 -58
  292. package/dist/world3d-react/r3f-adapter.d.ts.map +0 -1
  293. package/dist/world3d-react/r3f-adapter.js +0 -634
  294. package/dist/world3d-react/world-context.d.ts +0 -182
  295. package/dist/world3d-react/world-context.d.ts.map +0 -1
  296. package/dist/world3d-react/world-context.js +0 -235
  297. package/src/adapter/setup-three-root-adapter.ts +0 -1032
  298. package/src/animation/clip-map.ts +0 -34
  299. package/src/asset-registry.ts +0 -89
  300. package/src/dev/debug-draw.ts +0 -80
  301. package/src/render/auto-batcher.ts +0 -168
  302. package/src/render/lod.ts +0 -17
  303. package/src/runtime/types.ts +0 -328
@@ -0,0 +1,12 @@
1
+ /**
2
+ * WHO IS PLAYING, and under what identity — the host's own declaration for one
3
+ * private play run or coordinated team test.
4
+ *
5
+ * Its own module because it is a PUBLISHED path: a project's Colyseus
6
+ * networking adapter (the `colyseus` capability, copied into every game) names
7
+ * this type, so it must live somewhere that survives. The engine never joins a
8
+ * room and never couples this authoring identity to a networking library —
9
+ * games may pass `roomKey` as a Colyseus filter/join option, and that is the
10
+ * whole of the coupling.
11
+ */
12
+ export {};
@@ -0,0 +1,34 @@
1
+ /**
2
+ * `runTicksWhenSettled` — the settled-aware tick driver behind BOTH session run-ticks doors
3
+ * (the bridge's `window.__vgai.runTicksSettled`, the editor relay's `run-ticks` case), one
4
+ * implementation with byte-identical semantics across doors, exactly as `runTicks` itself is
5
+ * shared (D17).
6
+ *
7
+ * Why it exists: `Game.runTicks` is deliberately SYNCHRONOUS, but a game may be intentionally
8
+ * BETWEEN worlds when a tick arrives — `reload_current_scene` unmounts the running scene and
9
+ * the fresh one arrives on an ASYNC React commit. A tick driven into that gap runs against
10
+ * nothing, and WHICH tick first runs the fresh world then depends on wall timing between
11
+ * driver calls — measured on the starter-kit fidelity drive: the same script produced 7 or 8
12
+ * post-respawn walked ticks depending on how long the driver idled between ticks. The fidelity
13
+ * contract (docs/MIGRATE-FIDELITY.md) treats a nondeterministic channel as a MECHANISM defect,
14
+ * and this is the mechanism: ticks must not race remounts.
15
+ *
16
+ * So this driver runs the budget ONE tick at a time and, before each tick, waits for every
17
+ * registered world-settled probe ({@link DebugRegistry.registerWorldSettledProbe} — declared by
18
+ * the game through its `debug.settled` entry export) to answer `true`. The wall-clock poll is
19
+ * sound because the OUTCOME no longer depends on timing: however long the commit takes, the
20
+ * next tick is always the fresh world's first. A game that registers no probe gets the old
21
+ * behavior exactly (every wait resolves immediately).
22
+ *
23
+ * The wait is BOUNDED and loud: a remount that never settles is a real defect, and
24
+ * `WORLD_UNSETTLED_TIMEOUT` names it rather than letting the driver hang.
25
+ */
26
+ import { type DebugRegistry, type RunTicksOptions } from './debug-registry';
27
+ export declare function runTicksWhenSettled(registry: Pick<DebugRegistry, 'worldSettled' | 'getRunTicksTarget'>, n: number, opts?: RunTicksOptions,
28
+ /** Test-only overrides — real callers leave this unset (the same seam
29
+ * convention as fast-forward's `batchTicks`). */
30
+ waits?: {
31
+ timeoutMs?: number;
32
+ pollMs?: number;
33
+ }): Promise<void>;
34
+ //# sourceMappingURL=run-ticks-settled.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-ticks-settled.d.ts","sourceRoot":"","sources":["../../src/runtime/run-ticks-settled.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,EAAc,KAAK,aAAa,EAAE,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAQxF,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,IAAI,CAAC,aAAa,EAAE,cAAc,GAAG,mBAAmB,CAAC,EACnE,CAAC,EAAE,MAAM,EACT,IAAI,CAAC,EAAE,eAAe;AACtB;kDACkD;AAClD,KAAK,CAAC,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAC9C,OAAO,CAAC,IAAI,CAAC,CA+Bf"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * `runTicksWhenSettled` — the settled-aware tick driver behind BOTH session run-ticks doors
3
+ * (the bridge's `window.__vgai.runTicksSettled`, the editor relay's `run-ticks` case), one
4
+ * implementation with byte-identical semantics across doors, exactly as `runTicks` itself is
5
+ * shared (D17).
6
+ *
7
+ * Why it exists: `Game.runTicks` is deliberately SYNCHRONOUS, but a game may be intentionally
8
+ * BETWEEN worlds when a tick arrives — `reload_current_scene` unmounts the running scene and
9
+ * the fresh one arrives on an ASYNC React commit. A tick driven into that gap runs against
10
+ * nothing, and WHICH tick first runs the fresh world then depends on wall timing between
11
+ * driver calls — measured on the starter-kit fidelity drive: the same script produced 7 or 8
12
+ * post-respawn walked ticks depending on how long the driver idled between ticks. The fidelity
13
+ * contract (docs/MIGRATE-FIDELITY.md) treats a nondeterministic channel as a MECHANISM defect,
14
+ * and this is the mechanism: ticks must not race remounts.
15
+ *
16
+ * So this driver runs the budget ONE tick at a time and, before each tick, waits for every
17
+ * registered world-settled probe ({@link DebugRegistry.registerWorldSettledProbe} — declared by
18
+ * the game through its `debug.settled` entry export) to answer `true`. The wall-clock poll is
19
+ * sound because the OUTCOME no longer depends on timing: however long the commit takes, the
20
+ * next tick is always the fresh world's first. A game that registers no probe gets the old
21
+ * behavior exactly (every wait resolves immediately).
22
+ *
23
+ * The wait is BOUNDED and loud: a remount that never settles is a real defect, and
24
+ * `WORLD_UNSETTLED_TIMEOUT` names it rather than letting the driver hang.
25
+ */
26
+ import { DebugError } from './debug-registry';
27
+ /** A remount is a couple of React commits on an idle main thread; polling faster buys nothing
28
+ * and slower delays every settled check by the poll period. */
29
+ const SETTLED_POLL_MS = 4;
30
+ /** A commit that hasn't landed after this long is not "in flight", it is stuck. */
31
+ const SETTLED_TIMEOUT_MS = 10_000;
32
+ export async function runTicksWhenSettled(registry, n, opts,
33
+ /** Test-only overrides — real callers leave this unset (the same seam
34
+ * convention as fast-forward's `batchTicks`). */
35
+ waits) {
36
+ const target = registry.getRunTicksTarget();
37
+ if (target === null) {
38
+ throw new DebugError('RUN_TICKS_UNAVAILABLE', 'runTicksWhenSettled: no run-ticks target is wired — no Game has mounted yet');
39
+ }
40
+ const timeoutMs = waits?.timeoutMs ?? SETTLED_TIMEOUT_MS;
41
+ const pollMs = waits?.pollMs ?? SETTLED_POLL_MS;
42
+ const render = opts?.render ?? 'last';
43
+ for (let i = 0; i < n; i++) {
44
+ if (!registry.worldSettled()) {
45
+ const deadline = Date.now() + timeoutMs;
46
+ while (!registry.worldSettled()) {
47
+ if (Date.now() > deadline) {
48
+ throw new DebugError('WORLD_UNSETTLED_TIMEOUT', `runTicksWhenSettled: a world-settled probe still answers false after ${timeoutMs}ms — ` +
49
+ 'a scene remount (or a broken probe) is stuck, and driving ticks into it would race it');
50
+ }
51
+ await new Promise((resolve) => setTimeout(resolve, pollMs));
52
+ }
53
+ }
54
+ const isFinalTick = i === n - 1;
55
+ // Same per-tick render policy `Game.runTicks` applies across its own loop.
56
+ const tickRender = render === 'all' ? 'all' : render === 'none' ? 'none' : isFinalTick ? 'last' : 'none';
57
+ target.runTicks(1, { render: tickRender });
58
+ }
59
+ }
@@ -46,9 +46,8 @@ export interface GameStateBridge {
46
46
  export interface GameStateBridgeInternal extends GameStateBridge {
47
47
  /** Bump `frameVersion` by one and notify every current subscriber, in
48
48
  * subscription order. Subscriber errors are isolated (per-subscriber
49
- * try/catch, loud `console.error`, never swallowed silently) — mirrors
50
- * the error-isolation idiom in `ecs/component-manager.ts`'s per-component
51
- * `update()` calls: one offending subscriber must not stop its siblings
49
+ * try/catch, loud `console.error`, never swallowed silently): one
50
+ * offending subscriber must not stop its siblings
52
51
  * from being notified, and must not break the frame loop. */
53
52
  bump(): void;
54
53
  }
@@ -1 +1 @@
1
- {"version":3,"file":"state-bridge.d.ts","sourceRoot":"","sources":["../../src/runtime/state-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,wDAAwD;AACxD,MAAM,WAAW,eAAe;IAC9B;;;;kEAI8D;IAC9D,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B;6EACyE;IACzE,SAAS,CAAC,OAAO,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;CAC5C;AAED;;;;;mEAKmE;AACnE,MAAM,WAAW,uBAAwB,SAAQ,eAAe;IAC9D;;;;;kEAK8D;IAC9D,IAAI,IAAI,IAAI,CAAC;CACd;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,uBAAuB,CA8B3D"}
1
+ {"version":3,"file":"state-bridge.d.ts","sourceRoot":"","sources":["../../src/runtime/state-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,wDAAwD;AACxD,MAAM,WAAW,eAAe;IAC9B;;;;kEAI8D;IAC9D,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B;6EACyE;IACzE,SAAS,CAAC,OAAO,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;CAC5C;AAED;;;;;mEAKmE;AACnE,MAAM,WAAW,uBAAwB,SAAQ,eAAe;IAC9D;;;;kEAI8D;IAC9D,IAAI,IAAI,IAAI,CAAC;CACd;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,uBAAuB,CA+B3D"}
@@ -54,6 +54,7 @@ export function createStateBridge() {
54
54
  onFrame();
55
55
  }
56
56
  catch (err) {
57
+ // biome-ignore lint/suspicious/noConsole: a throwing subscriber must not take the notify loop down, and the console (captured by the editor session ledger) is the engine runtime's one report channel.
57
58
  console.error('[state-bridge] subscriber threw:', err);
58
59
  }
59
60
  }
@@ -1,4 +1,5 @@
1
1
  import * as THREE from 'three';
2
+ import { type AudioBusMixer } from '../audio/bus-mixer';
2
3
  export interface AudioContext {
3
4
  listener: THREE.AudioListener;
4
5
  masterGain: GainNode;
@@ -7,6 +8,17 @@ export interface AudioContext {
7
8
  sfx: GainNode;
8
9
  voice: GainNode;
9
10
  };
11
+ /**
12
+ * The named-bus mixer the three below are buses OF (`../audio/bus-mixer.ts`).
13
+ *
14
+ * `buses` is a fixed struct because those three names are the ones the engine
15
+ * itself routes; a game with an `ambience`, a `ui` or a per-character bus asks
16
+ * the mixer for it by name — `audio.mixer.bus('ambience', 'master')` mints it
17
+ * under the master gain, `audio.mixer.setDb('ambience', -6)` sets it in
18
+ * decibels — and `audio.mixer.disconnect()` is the ONE teardown for all of
19
+ * them, the three struct members included.
20
+ */
21
+ mixer: AudioBusMixer;
10
22
  /** Call this on first user interaction (click/key) to resume audio context */
11
23
  resume: () => void;
12
24
  }
@@ -1 +1 @@
1
- {"version":3,"file":"setup-audio.d.ts","sourceRoot":"","sources":["../../src/setup/setup-audio.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,KAAK,CAAC,aAAa,CAAC;IAC9B,UAAU,EAAE,QAAQ,CAAC;IACrB,KAAK,EAAE;QACL,KAAK,EAAE,QAAQ,CAAC;QAChB,GAAG,EAAE,QAAQ,CAAC;QACd,KAAK,EAAE,QAAQ,CAAC;KACjB,CAAC;IACF,8EAA8E;IAC9E,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,GAAG,YAAY,CAuC7D"}
1
+ {"version":3,"file":"setup-audio.d.ts","sourceRoot":"","sources":["../../src/setup/setup-audio.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,KAAK,aAAa,EAAuB,MAAM,oBAAoB,CAAC;AAE7E,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,KAAK,CAAC,aAAa,CAAC;IAC9B,UAAU,EAAE,QAAQ,CAAC;IACrB,KAAK,EAAE;QACL,KAAK,EAAE,QAAQ,CAAC;QAChB,GAAG,EAAE,QAAQ,CAAC;QACd,KAAK,EAAE,QAAQ,CAAC;KACjB,CAAC;IACF;;;;;;;;;OASG;IACH,KAAK,EAAE,aAAa,CAAC;IACrB,8EAA8E;IAC9E,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB;AAKD;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,GAAG,YAAY,CAkC7D"}
@@ -1,4 +1,7 @@
1
1
  import * as THREE from 'three';
2
+ import { createAudioBusMixer } from '../audio/bus-mixer';
3
+ /** The bus every other bus routes into, and the node `masterGain` is. */
4
+ const MASTER_BUS = 'master';
2
5
  /**
3
6
  * Creates the audio listener and bus hierarchy.
4
7
  *
@@ -9,21 +12,16 @@ export function setupAudio(camera) {
9
12
  const listener = new THREE.AudioListener();
10
13
  camera.add(listener);
11
14
  const ctx = listener.context;
12
- const masterGain = ctx.createGain();
13
- masterGain.connect(ctx.destination);
15
+ const mixer = createAudioBusMixer({ context: ctx, destination: ctx.destination });
16
+ const masterGain = mixer.bus(MASTER_BUS);
14
17
  // Route the Three.js listener through masterGain so that THREE.Audio /
15
18
  // THREE.PositionalAudio objects are also silenced by the mute button.
16
19
  listener.gain.disconnect();
17
20
  listener.gain.connect(masterGain);
18
- const musicGain = ctx.createGain();
21
+ const musicGain = mixer.bus('music', MASTER_BUS);
19
22
  musicGain.gain.value = 0.7;
20
- musicGain.connect(masterGain);
21
- const sfxGain = ctx.createGain();
22
- sfxGain.gain.value = 1.0;
23
- sfxGain.connect(masterGain);
24
- const voiceGain = ctx.createGain();
25
- voiceGain.gain.value = 1.0;
26
- voiceGain.connect(masterGain);
23
+ const sfxGain = mixer.bus('sfx', MASTER_BUS);
24
+ const voiceGain = mixer.bus('voice', MASTER_BUS);
27
25
  return {
28
26
  listener,
29
27
  masterGain,
@@ -32,6 +30,7 @@ export function setupAudio(camera) {
32
30
  sfx: sfxGain,
33
31
  voice: voiceGain,
34
32
  },
33
+ mixer,
35
34
  resume() {
36
35
  if (ctx.state === 'suspended') {
37
36
  ctx.resume();
@@ -1,13 +1,7 @@
1
- import { EffectComposer } from 'postprocessing';
1
+ import { type EffectComposer } from 'postprocessing';
2
2
  import * as THREE from 'three';
3
3
  import type { RenderEnvironment } from '../asset-formats/render-env';
4
4
  import { type RenderScope } from '../render/render-settings';
5
- export interface RendererContext {
6
- renderer: THREE.WebGLRenderer;
7
- scene: THREE.Scene;
8
- camera: THREE.PerspectiveCamera;
9
- composer: EffectComposer;
10
- }
11
5
  /**
12
6
  * Apply the live renderer-level render settings (shadows, resolution scale) from
13
7
  * the resolved cascade. `antialias`/`backend` are reload-only (renderer re-init)
@@ -22,7 +16,7 @@ export declare function applyRendererSettings(renderer: THREE.WebGLRenderer, ren
22
16
  /**
23
17
  * Create ONLY the WebGL renderer. The HOST owns the renderer (see
24
18
  * `ThreeHostContext.renderer`); the game/adapter owns the scene/camera/composer it
25
- * renders into. Split out of `setupRenderer` for the adapter inversion.
19
+ * renders into.
26
20
  *
27
21
  * `rendering` is the scene-level render scope (`env.rendering`); it seeds the
28
22
  * reload-only `antialias` option at construction and the live shadow/resolution
@@ -39,23 +33,6 @@ export declare function createHostRenderer(canvas: HTMLCanvasElement, width?: nu
39
33
  alpha?: boolean;
40
34
  preserveDrawingBuffer?: boolean;
41
35
  }): THREE.WebGLRenderer;
42
- /**
43
- * Create the scene, camera, and postprocessing composer that a first-party game
44
- * renders into, given a host-provided renderer. The GPU-only `composer` is
45
- * created here; a headless host (no GPU) skips this entirely.
46
- */
47
- export declare function createSceneView(renderer: THREE.WebGLRenderer, width?: number, height?: number): {
48
- scene: THREE.Scene;
49
- camera: THREE.PerspectiveCamera;
50
- composer: EffectComposer;
51
- };
52
- /**
53
- * Creates the Three.js renderer, scene, camera, and postprocessing pipeline.
54
- *
55
- * This is direct Three.js setup — no abstraction. Edit freely. (Now a thin
56
- * combination of {@link createHostRenderer} + {@link createSceneView}.)
57
- */
58
- export declare function setupRenderer(canvas: HTMLCanvasElement, width?: number, height?: number): RendererContext;
59
36
  /**
60
37
  * Rebuild composer passes from scene environment settings.
61
38
  * Scenes without toneMapping default to ACES (the runtime default).
@@ -1 +1 @@
1
- {"version":3,"file":"setup-renderer.d.ts","sourceRoot":"","sources":["../../src/setup/setup-renderer.ts"],"names":[],"mappings":"AAGA,OAAO,EAQL,cAAc,EAoBf,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAO/B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAGrE,OAAO,EAAE,KAAK,WAAW,EAAyB,MAAM,2BAA2B,CAAC;AAEpF,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,KAAK,CAAC,aAAa,CAAC;IAC9B,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC;IACnB,MAAM,EAAE,KAAK,CAAC,iBAAiB,CAAC;IAChC,QAAQ,EAAE,cAAc,CAAC;CAC1B;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,KAAK,CAAC,aAAa,EAC7B,SAAS,CAAC,EAAE,WAAW,EACvB,cAAc,SAAuC,GACpD,IAAI,CAKN;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,iBAAiB,EACzB,KAAK,SAAoB,EACzB,MAAM,SAAqB,EAC3B,SAAS,CAAC,EAAE,WAAW,EACvB,IAAI,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,qBAAqB,CAAC,EAAE,OAAO,CAAA;CAAE,GAC1D,KAAK,CAAC,aAAa,CAwBrB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,KAAK,CAAC,aAAa,EAC7B,KAAK,SAAoB,EACzB,MAAM,SAAqB,GAC1B;IAAE,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC;IAAC,MAAM,EAAE,KAAK,CAAC,iBAAiB,CAAC;IAAC,QAAQ,EAAE,cAAc,CAAA;CAAE,CAyBnF;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,iBAAiB,EACzB,KAAK,SAAoB,EACzB,MAAM,SAAqB,GAC1B,eAAe,CAIjB;AAkBD;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,cAAc,EACxB,QAAQ,EAAE,KAAK,CAAC,aAAa,EAC7B,KAAK,EAAE,KAAK,CAAC,KAAK,EAClB,MAAM,EAAE,KAAK,CAAC,MAAM,EACpB,GAAG,CAAC,EAAE,iBAAiB,EACvB,SAAS,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC,EACvC,UAAU,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,GACjC,IAAI,CAsVN;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,cAAc,EACxB,QAAQ,EAAE,KAAK,CAAC,aAAa,EAC7B,KAAK,EAAE,KAAK,CAAC,KAAK,EAClB,MAAM,EAAE,KAAK,CAAC,MAAM,EACpB,GAAG,CAAC,EAAE,iBAAiB,EACvB,OAAO,CAAC,EAAE;IACR,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;IACxC,UAAU,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CACpC,GACA,IAAI,CAeN"}
1
+ {"version":3,"file":"setup-renderer.d.ts","sourceRoot":"","sources":["../../src/setup/setup-renderer.ts"],"names":[],"mappings":"AAGA,OAAO,EAQL,KAAK,cAAc,EAoBpB,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAO/B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAGrE,OAAO,EAAE,KAAK,WAAW,EAAyB,MAAM,2BAA2B,CAAC;AAEpF;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,KAAK,CAAC,aAAa,EAC7B,SAAS,CAAC,EAAE,WAAW,EACvB,cAAc,SAAuC,GACpD,IAAI,CAKN;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,iBAAiB,EACzB,KAAK,SAAoB,EACzB,MAAM,SAAqB,EAC3B,SAAS,CAAC,EAAE,WAAW,EACvB,IAAI,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,qBAAqB,CAAC,EAAE,OAAO,CAAA;CAAE,GAC1D,KAAK,CAAC,aAAa,CAwBrB;AAkBD;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,cAAc,EACxB,QAAQ,EAAE,KAAK,CAAC,aAAa,EAC7B,KAAK,EAAE,KAAK,CAAC,KAAK,EAClB,MAAM,EAAE,KAAK,CAAC,MAAM,EACpB,GAAG,CAAC,EAAE,iBAAiB,EACvB,SAAS,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC,EACvC,UAAU,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,GACjC,IAAI,CAsVN;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,cAAc,EACxB,QAAQ,EAAE,KAAK,CAAC,aAAa,EAC7B,KAAK,EAAE,KAAK,CAAC,KAAK,EAClB,MAAM,EAAE,KAAK,CAAC,MAAM,EACpB,GAAG,CAAC,EAAE,iBAAiB,EACvB,OAAO,CAAC,EAAE;IACR,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;IACxC,UAAU,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CACpC,GACA,IAAI,CAeN"}
@@ -1,6 +1,6 @@
1
1
  /// <reference path="../types-n8ao.d.ts" />
2
2
  import { N8AOPostPass } from 'n8ao';
3
- import { BloomEffect, BrightnessContrastEffect, ChromaticAberrationEffect, ColorDepthEffect, DepthOfFieldEffect, DotScreenEffect, EffectComposer, EffectPass, GlitchEffect, GodRaysEffect, GridEffect, HueSaturationEffect, LensDistortionEffect, NoiseEffect, NormalPass, OutlineEffect, PixelationEffect, RenderPass, ScanlineEffect, SepiaEffect, ShockWaveEffect, SMAAEffect, SMAAPreset, SSAOEffect, TiltShiftEffect, VignetteEffect, } from 'postprocessing';
3
+ import { BloomEffect, BrightnessContrastEffect, ChromaticAberrationEffect, ColorDepthEffect, DepthOfFieldEffect, DotScreenEffect, EffectPass, GlitchEffect, GodRaysEffect, GridEffect, HueSaturationEffect, LensDistortionEffect, NoiseEffect, NormalPass, OutlineEffect, PixelationEffect, RenderPass, ScanlineEffect, SepiaEffect, ShockWaveEffect, SMAAEffect, SMAAPreset, SSAOEffect, TiltShiftEffect, VignetteEffect, } from 'postprocessing';
4
4
  import * as THREE from 'three';
5
5
  import { MotionBlurEffect, SSGIEffect, SSREffect, VelocityDepthNormalPass, } from '../../vendor/realism-effects/dist/index.js';
6
6
  import { DEFAULTS } from '../defaults';
@@ -25,7 +25,7 @@ export function applyRendererSettings(renderer, rendering, basePixelRatio = Math
25
25
  /**
26
26
  * Create ONLY the WebGL renderer. The HOST owns the renderer (see
27
27
  * `ThreeHostContext.renderer`); the game/adapter owns the scene/camera/composer it
28
- * renders into. Split out of `setupRenderer` for the adapter inversion.
28
+ * renders into.
29
29
  *
30
30
  * `rendering` is the scene-level render scope (`env.rendering`); it seeds the
31
31
  * reload-only `antialias` option at construction and the live shadow/resolution
@@ -63,35 +63,6 @@ export function createHostRenderer(canvas, width = window.innerWidth, height = w
63
63
  detectKtx2Support(renderer);
64
64
  return renderer;
65
65
  }
66
- /**
67
- * Create the scene, camera, and postprocessing composer that a first-party game
68
- * renders into, given a host-provided renderer. The GPU-only `composer` is
69
- * created here; a headless host (no GPU) skips this entirely.
70
- */
71
- export function createSceneView(renderer, width = window.innerWidth, height = window.innerHeight) {
72
- const safeWidth = Math.max(1, width);
73
- const safeHeight = Math.max(1, height);
74
- const scene = new THREE.Scene();
75
- scene.background = new THREE.Color(0x1a1a2e);
76
- const camera = new THREE.PerspectiveCamera(DEFAULTS.camera.fov, safeWidth / safeHeight, DEFAULTS.camera.near, DEFAULTS.camera.far);
77
- camera.position.set(0, 5, 10);
78
- camera.lookAt(0, 0, 0);
79
- const composer = new EffectComposer(renderer);
80
- composer.addPass(new RenderPass(scene, camera));
81
- composer.addPass(new EffectPass(camera, new BloomEffect({ luminanceThreshold: DEFAULTS.postProcessing.bloom.luminanceThreshold })));
82
- return { scene, camera, composer };
83
- }
84
- /**
85
- * Creates the Three.js renderer, scene, camera, and postprocessing pipeline.
86
- *
87
- * This is direct Three.js setup — no abstraction. Edit freely. (Now a thin
88
- * combination of {@link createHostRenderer} + {@link createSceneView}.)
89
- */
90
- export function setupRenderer(canvas, width = window.innerWidth, height = window.innerHeight) {
91
- const renderer = createHostRenderer(canvas, width, height);
92
- const { scene, camera, composer } = createSceneView(renderer, width, height);
93
- return { renderer, scene, camera, composer };
94
- }
95
66
  const toneMappingModes = {
96
67
  none: THREE.NoToneMapping,
97
68
  linear: THREE.LinearToneMapping,
@@ -18,13 +18,13 @@
18
18
  *
19
19
  * ## Why this is NOT in `mount-game.ts`
20
20
  *
21
- * `defaultThreeAdapterFactory` lives beside the registry, and the registry
22
- * module is deliberately dependency-free: its own doc notes threejs is safe to
23
- * ship there because `three` is already an unconditional engine dependency.
24
- * `@react-three/fiber` is NOT it is the PROJECT's dependency, and
25
- * `world3d-react` is the opt-in module that owns it. Putting this factory
26
- * there would make R3F unconditional for every game, including ones with no
27
- * 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)`).
28
28
  *
29
29
  * ## The mount mechanics (fiber v9, host-gated)
30
30
  *
@@ -18,13 +18,13 @@
18
18
  *
19
19
  * ## Why this is NOT in `mount-game.ts`
20
20
  *
21
- * `defaultThreeAdapterFactory` lives beside the registry, and the registry
22
- * module is deliberately dependency-free: its own doc notes threejs is safe to
23
- * ship there because `three` is already an unconditional engine dependency.
24
- * `@react-three/fiber` is NOT it is the PROJECT's dependency, and
25
- * `world3d-react` is the opt-in module that owns it. Putting this factory
26
- * there would make R3F unconditional for every game, including ones with no
27
- * 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)`).
28
28
  *
29
29
  * ## The mount mechanics (fiber v9, host-gated)
30
30
  *
@@ -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.
@@ -51,9 +49,12 @@
51
49
  * TEARDOWN: the effect's cleanup, and nothing else — it clears the SAME key
52
50
  * and the slot it set (identity-guarded), so a play stop/start, a hot
53
51
  * restart, or a world that unmounts `<Physics>` leaves no stale adapter
54
- * behind. Because the slot is game-global, mount exactly ONE bridge per
55
- * GAME: with two, the later mount wins the slot and the later unmount clears
56
- * it while the earlier bridge is still live.
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`).
57
58
  *
58
59
  * PHASE ORDERING: none to respect — this component registers pull-only
59
60
  * callbacks and runs no per-frame work. Every method it exposes is invoked
@@ -1 +1 @@
1
- {"version":3,"file":"rapier-physics-bridge.d.ts","sourceRoot":"","sources":["../../src/world3d-react/rapier-physics-bridge.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6DG;AASH,OAAO,KAAK,EACV,cAAc,EAKf,MAAM,2BAA2B,CAAC;AAEnC,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,CA0OrF;AAqBD;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,IAAI,cAAc,CAyBpD"}
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"}
@@ -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.
@@ -51,9 +49,12 @@
51
49
  * TEARDOWN: the effect's cleanup, and nothing else — it clears the SAME key
52
50
  * and the slot it set (identity-guarded), so a play stop/start, a hot
53
51
  * restart, or a world that unmounts `<Physics>` leaves no stale adapter
54
- * behind. Because the slot is game-global, mount exactly ONE bridge per
55
- * GAME: with two, the later mount wins the slot and the later unmount clears
56
- * it while the earlier bridge is still live.
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`).
57
58
  *
58
59
  * PHASE ORDERING: none to respect — this component registers pull-only
59
60
  * callbacks and runs no per-frame work. Every method it exposes is invoked
@@ -64,6 +65,7 @@ import { useRapier } from '@react-three/rapier';
64
65
  import { useEffect } from 'react';
65
66
  import * as THREE from 'three';
66
67
  import { createRapierBodyEditing, } from '../adapter/rapier-physics-adapter';
68
+ import { createSystemSlot } from '../adapter/system-slot';
67
69
  function lateEntityObject(states, nodeId) {
68
70
  let found;
69
71
  for (const state of states) {
@@ -119,6 +121,25 @@ export function RapierPhysicsBridge({ onDebugChange }) {
119
121
  return undefined;
120
122
  return owningBody(object, rigidBodyStates.values());
121
123
  };
124
+ /**
125
+ * The same question, with the two negative answers KEPT APART for the
126
+ * editing protocol — see {@link RapierBodyLookup}. This world's search
127
+ * space is the `<RigidBody>` subtrees, so "no object here" really is "not a
128
+ * node of mine", not "a node of mine without physics".
129
+ */
130
+ const bodyLookup = (nodeId) => {
131
+ const object = lateEntityObject(rigidBodyStates.values(), nodeId);
132
+ if (!object)
133
+ return { kind: 'unresolved' };
134
+ const body = owningBody(object, rigidBodyStates.values());
135
+ return body ? { kind: 'body', body } : { kind: 'no-body' };
136
+ };
137
+ /** Rapier's own name for a shape type, via the enum's reverse mapping —
138
+ * so an unprojected collider reports WHICH shape it is (`TriMesh`,
139
+ * `HeightField`, …) instead of a bare number, and a shape added by a
140
+ * future rapier still names itself. */
141
+ const rapierShapeName = (shapeType) => rapier.ShapeType[shapeType] ??
142
+ `shapeType ${shapeType}`;
122
143
  const colliderShape = (collider) => {
123
144
  switch (collider.shapeType()) {
124
145
  case rapier.ShapeType.Cuboid: {
@@ -133,8 +154,13 @@ export function RapierPhysicsBridge({ onDebugChange }) {
133
154
  halfHeight: collider.halfHeight(),
134
155
  radius: collider.radius(),
135
156
  };
157
+ // Rapier has many more shapes than this seam projects (TriMesh,
158
+ // HeightField, ConvexPolyhedron, Cylinder, Cone, …). The collider is
159
+ // REAL; only our plain-data projection of it is missing, so it is
160
+ // reported as itself rather than dropped — a body whose only collider
161
+ // is a trimesh used to inspect as a body with no colliders at all.
136
162
  default:
137
- return null;
163
+ return { type: 'unsupported', kind: rapierShapeName(collider.shapeType()) };
138
164
  }
139
165
  };
140
166
  const colliders = (nodeId) => {
@@ -146,8 +172,6 @@ export function RapierPhysicsBridge({ onDebugChange }) {
146
172
  if (state.collider.parent()?.handle !== body.handle)
147
173
  continue;
148
174
  const shape = colliderShape(state.collider);
149
- if (!shape)
150
- continue;
151
175
  state.object.updateWorldMatrix(true, false);
152
176
  const position = state.collider.translation();
153
177
  const rotation = state.collider.rotation();
@@ -182,6 +206,11 @@ export function RapierPhysicsBridge({ onDebugChange }) {
182
206
  collider.setHalfHeight(shape.halfHeight);
183
207
  collider.setRadius(shape.radius);
184
208
  break;
209
+ // A shape this seam does not project has no dimensions to write back.
210
+ // `colliders()` reports it so the body's physics is not invisible; that
211
+ // is a READ, and it does not make the shape editable.
212
+ case 'unsupported':
213
+ break;
185
214
  }
186
215
  };
187
216
  const jointType = (type) => {
@@ -279,17 +308,18 @@ export function RapierPhysicsBridge({ onDebugChange }) {
279
308
  joint.setAnchor2(value);
280
309
  };
281
310
  const adapter = {
282
- ...createRapierBodyEditing(bodyFor),
311
+ ...createRapierBodyEditing(bodyLookup),
283
312
  setDebugDrawEnabled: (enabled) => onDebugChange(enabled),
284
313
  colliders,
285
314
  previewCollider,
286
315
  joints,
287
316
  previewJointAnchor,
288
317
  };
289
- liveAdapter = adapter;
318
+ warnOnContendedMount();
319
+ const detach = physicsSlot.attach(adapter);
290
320
  return () => {
291
- if (liveAdapter === adapter)
292
- liveAdapter = null;
321
+ liveBridgeCount -= 1;
322
+ detach();
293
323
  };
294
324
  }, [colliderStates, onDebugChange, rapier, rigidBodyStates, world]);
295
325
  return null;
@@ -297,17 +327,61 @@ export function RapierPhysicsBridge({ onDebugChange }) {
297
327
  // ---------------------------------------------------------------------------
298
328
  // The entry-declaration half
299
329
  // ---------------------------------------------------------------------------
300
- /** The mounted bridge's adapter, while one is live. Module-scoped on purpose:
301
- * the entry's `systems` declaration is static, the rapier world is not, and
302
- * this slot is the seam between the two. */
303
- let liveAdapter = null;
304
- function requireLiveAdapter() {
305
- if (!liveAdapter) {
306
- throw new Error('rapierPhysicsSystem: no <RapierPhysicsBridge> is mounted inside a live <Physics> right ' +
307
- 'now the world is not playing, or the scene never mounted the bridge.');
308
- }
309
- return liveAdapter;
330
+ /**
331
+ * The bridge's slot. Module-scoped on purpose: the entry's `systems`
332
+ * declaration is static, the rapier world is not, and this slot is the seam
333
+ * between the two. Module scope here means one per PAGE, not one per game:
334
+ * this file is an ENGINE module, reached by bare specifier, so the editor's
335
+ * multi-instance Play (which stamps mount ids onto PROJECT-owned specifiers
336
+ * only `server/project-module-instance.ts`) shares this one slot across
337
+ * every mounted instance. Two live instances of a rapier world therefore
338
+ * contend for it: the LAST bridge to mount wins every instance's declared
339
+ * `systems.physics`, and the loser's detach is dropped by the identity guard.
340
+ * {@link warnOnContendedMount} makes that state loud at the moment it
341
+ * happens; making the slot instance-scoped needs a per-mount rendezvous the
342
+ * context-free declaration (`rapierPhysicsSystem()` at project-module scope)
343
+ * does not yet have.
344
+ *
345
+ * Every member is an ACTION: each one either edits the sim or reads a live
346
+ * Rapier handle, and all of them arrive at gesture rate (a gizmo drag, an
347
+ * instrument command), never from a per-frame loop — so refusing by name is
348
+ * always the right answer, and a fabricated empty never is.
349
+ * `debugDraw`/`contactPoints` are OMITTED, not stubbed, for the anti-shim
350
+ * reason stated at the top of this file; a member the config does not list
351
+ * does not exist on the slot.
352
+ */
353
+ let liveBridgeCount = 0;
354
+ /** The page-scope contention above, made LOUD instead of silent (never a
355
+ * silent degrade): a second concurrent bridge mount — a scene that mounted
356
+ * two, or a second Play instance of a rapier world — is reported the moment
357
+ * it happens, naming what breaks and until when. */
358
+ function warnOnContendedMount() {
359
+ liveBridgeCount += 1;
360
+ if (liveBridgeCount <= 1)
361
+ return;
362
+ // biome-ignore lint/suspicious/noConsole: the standing-warning channel for a capability the adapter cannot reach — the editor console surfaces it during play
363
+ console.warn(`[rapierPhysicsSystem] ${liveBridgeCount} <RapierPhysicsBridge> mounts are live on this ` +
364
+ 'page, but the declared physics slot is one per page (engine modules are shared across ' +
365
+ "Play instances). Every instance's `systems.physics` now forwards to the LAST bridge " +
366
+ 'mounted, so physics gizmo edits and instruments address that world — not necessarily ' +
367
+ 'the one selected — until the extra mounts unmount.');
310
368
  }
369
+ const physicsSlot = createSystemSlot({
370
+ name: 'rapierPhysicsSystem',
371
+ unattachedMessage: 'no <RapierPhysicsBridge> is mounted inside a live <Physics> right now — the world is not ' +
372
+ 'playing, or the scene never mounted the bridge.',
373
+ members: {
374
+ ownerOf: { kind: 'action' },
375
+ freeze: { kind: 'action' },
376
+ commit: { kind: 'action' },
377
+ unfreeze: { kind: 'action' },
378
+ setDebugDrawEnabled: { kind: 'action' },
379
+ colliders: { kind: 'action' },
380
+ previewCollider: { kind: 'action' },
381
+ joints: { kind: 'action' },
382
+ previewJointAnchor: { kind: 'action' },
383
+ },
384
+ });
311
385
  /**
312
386
  * The `systems.physics` slot for a default-exported world — declare it from
313
387
  * the entry (`export const systems = { physics: rapierPhysicsSystem() }`) and
@@ -317,27 +391,5 @@ function requireLiveAdapter() {
317
391
  * name rather than fabricating.
318
392
  */
319
393
  export function rapierPhysicsSystem() {
320
- // Every forward REQUIRES the live adapter and its member — a silent no-op
321
- // or fabricated empty answer is the shape the anti-shim rule forbids. The
322
- // bridge's adapter always carries all five optional members, so the member
323
- // checks only ever fire against a foreign replacement.
324
- const requireMember = (member) => {
325
- const adapter = requireLiveAdapter();
326
- const value = adapter[member];
327
- if (typeof value !== 'function') {
328
- throw new Error(`rapierPhysicsSystem: the live physics adapter has no ${String(member)}().`);
329
- }
330
- return value.bind(adapter);
331
- };
332
- return {
333
- ownerOf: (nodeId) => requireLiveAdapter().ownerOf(nodeId),
334
- freeze: (nodeId) => requireLiveAdapter().freeze(nodeId),
335
- commit: (nodeId, t) => requireLiveAdapter().commit(nodeId, t),
336
- unfreeze: (nodeId) => requireLiveAdapter().unfreeze(nodeId),
337
- setDebugDrawEnabled: (enabled) => requireMember('setDebugDrawEnabled')(enabled),
338
- colliders: (nodeId) => requireMember('colliders')(nodeId),
339
- previewCollider: (colliderId, shape) => requireMember('previewCollider')(colliderId, shape),
340
- joints: (nodeId) => requireMember('joints')(nodeId),
341
- previewJointAnchor: (jointId, endpoint, anchor) => requireMember('previewJointAnchor')(jointId, endpoint, anchor),
342
- };
394
+ return physicsSlot.slot;
343
395
  }