@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
@@ -2,8 +2,8 @@
2
2
  * AuthoringAdapter — the editor's authoring contract. The editor talks to THIS,
3
3
  * keyed by opaque string node ids, instead of to a concrete document format.
4
4
  *
5
- * `.vscn` / `SceneEntity` is the private model of ONE implementer
6
- * (`VgaiSceneAuthoringAdapter`); an ingested game's adapter implements the same
5
+ * An implementer's document model is its own private business; an ingested
6
+ * game's adapter implements the same
7
7
  * providers directly over its live `Object3D` tree. The editor sees only the
8
8
  * interface and the advertised `capabilities` — it never branches on which
9
9
  * implementer it is talking to.
@@ -29,8 +29,7 @@ import type { Transform } from './transform';
29
29
  * Deliberately SMALL: a capability flag earns its place only when the shell
30
30
  * actually branches on it. Structural affordances (create/delete/reparent) are
31
31
  * gated by `structure` PROVIDER PRESENCE instead — the flag and the provider
32
- * cannot disagree that way. (WO-2 deleted `material`/`create`/`delete`/
33
- * `reparent`/`layout`, which no shell code ever read.)
32
+ * cannot disagree that way.
34
33
  */
35
34
  export interface AuthoringCapabilities {
36
35
  transform: boolean;
@@ -89,7 +88,7 @@ export type EditorNodeRole =
89
88
  | 'entity'
90
89
  | 'boundary';
91
90
 
92
- /** A node in the authoring hierarchy — format-neutral (not a `SceneEntity`). */
91
+ /** A node in the authoring hierarchy — format-neutral. */
93
92
  export interface EditorNode {
94
93
  /** STABLE id — survives reload (see ingest structural-path ids). */
95
94
  id: string;
@@ -202,9 +201,9 @@ export interface SelectionProvider {
202
201
  }
203
202
 
204
203
  /** Adapter-owned result of semantic selection resolution: the raw native/render
205
- * hit mapped to the node the author actually means. (The former
206
- * `inspectorId`/`transformId`/`boundaryId` aliases were never read by the
207
- * shell deleted in WO-2.) */
204
+ * hit mapped to the node the author actually means. ONE id, deliberately:
205
+ * separate inspector/transform/boundary aliases would have to agree, and
206
+ * nothing in the shell can enforce that. */
208
207
  export interface SelectionResolution {
209
208
  /** Hierarchy selection and Inspector subject. */
210
209
  id: string;
@@ -316,7 +315,7 @@ export interface TransformObservation {
316
315
  }
317
316
 
318
317
  export interface InspectorProvider {
319
- /** Schema-driven — NOT fixed to `SceneEntity`. */
318
+ /** Schema-driven — fixed to no document format. */
320
319
  properties(id: string): PropertyDescriptor[];
321
320
  get(id: string, path: string): unknown;
322
321
  /**
@@ -620,24 +619,24 @@ export interface PersistenceProvider {
620
619
  * subsequent persistence clears it. Auto-saving source adapters use this to
621
620
  * report a rolled-back source write without inventing a dirty document. */
622
621
  lastError?(): string | null;
623
- /** Save to the adapter's OWN source of truth (.vscn / overlay file / …). */
622
+ /** Save to the adapter's OWN source of truth (its source file, its own
623
+ * data, …) — whatever that adapter defines it to be. */
624
624
  save(): Promise<void>;
625
625
  /**
626
626
  * Human/agent-readable destination this provider persists to — e.g.
627
- * `"scenes/main.vscn.json"`, `"live-only (not saved)"`, or `"ephemeral
627
+ * `"src/world.tsx"`, `"live-only (not saved)"`, or `"ephemeral
628
628
  * (discarded on stop)"`. Drives the save-status UI and makes routing
629
629
  * inspectable (design §2). Implementers whose destination can change during
630
- * the session (e.g. the first-party provider tracks the loaded `.vscn` path)
630
+ * the session (a provider that tracks the currently focused document, say)
631
631
  * should expose this as a live getter rather than a value captured once.
632
632
  */
633
633
  readonly destination: string;
634
634
  /**
635
635
  * The reload contract (design §5): apply an external change to this
636
636
  * provider's persisted artifact into the RUNNING session (e.g. a file-watcher
637
- * update to the `.vscn` or the overlay). Absent ⇒ the host must remount to
638
- * pick up external changes (the honest floor). Implemented (T3.2 slice 3) by
639
- * the first-party provider (`applyExternalUpdate`) and the ingest overlay
640
- * provider (`applyOverlay`); absent (correctly) on the ephemeral provider.
637
+ * update to the artifact). Absent ⇒ the host must remount to
638
+ * pick up external changes the honest floor, and correctly what the
639
+ * ephemeral provider reports.
641
640
  *
642
641
  * `rawContent`, when the host has it (the file-watcher SSE payload carries the
643
642
  * artifact's exact bytes), is an OPTIONAL second parameter enabling own-echo
@@ -823,7 +822,7 @@ export interface ColorSampleProvider {
823
822
  }
824
823
 
825
824
  /**
826
- * WO-IDX1 — where a node's live object CAME FROM in the game's own truth.
825
+ * Where a node's live object CAME FROM in the game's own truth.
827
826
  *
828
827
  * `display` is the string the editor shows verbatim. The unanchored shape
829
828
  * carries a `reason` rather than a blank, because "we don't know" and "a
@@ -1172,10 +1171,9 @@ export interface AuthoringAdapter {
1172
1171
  * Observe transform GESTURES on this adapter's nodes — the one seam the
1173
1172
  * animation sequencer needs to record a drag as keyframes and to refresh its
1174
1173
  * design baselines. Deliberately narrow (one event shape, one consumer, no
1175
- * general event bus): before it existed, the sequencer could only observe
1176
- * the first-party store's own `.vscn` transform path, so a live or
1177
- * source-backed world's gestures were invisible to it. Absent ⇒ this
1178
- * adapter reports no gestures.
1174
+ * general event bus): without it the sequencer can only observe the store's
1175
+ * own transform path, so a live or source-backed world's gestures are
1176
+ * invisible to it. Absent ⇒ this adapter reports no gestures.
1179
1177
  */
1180
1178
  observeTransforms?(listener: (event: TransformObservation) => void): () => void;
1181
1179
  }
@@ -0,0 +1,468 @@
1
+ /**
2
+ * THE ROOT BINDING — a game's declaration of itself, bound, as ONE host-side
3
+ * value.
4
+ *
5
+ * A game is a native program plus a declaration of itself; the editor is a
6
+ * universal client of that declaration; the host must not know what it is
7
+ * hosting. Today that declaration arrives at the host as a scatter — a
8
+ * `RootInstance`, a `MountedRoot`, an `AuthoringAdapter` provider bag, a
9
+ * `SystemAdapters` slot map, a `ResolvedAdapterRoot`, a parsed
10
+ * `AdapterDefinition` — and every consumer re-assembles its own subset by
11
+ * hand. `RootBinding` is that same material REGROUPED under one name, by the
12
+ * FIVE protocol families a host actually talks in.
13
+ *
14
+ * ## This file declares the vocabulary AND performs the regrouping
15
+ *
16
+ * {@link createRootBinding} is the one place the five families are assembled,
17
+ * and it is a pure re-address of values it is HANDED — it loads nothing,
18
+ * fetches nothing, and constructs no provider. The editor's
19
+ * `binding-resolver.ts` (`resolveRootBinding(root, realm, adapterDef)`) is what
20
+ * gathers those values per realm and calls this; `RootInstance.binding`
21
+ * (`runtime/game.ts`) is where the result lives for the life of the mount.
22
+ * Keeping the assembly here rather than in the resolver is what makes the
23
+ * reference-equality rule below checkable in ONE function instead of once per
24
+ * realm.
25
+ *
26
+ * ## The one rule that makes this a regrouping and not an abstraction
27
+ *
28
+ * **Every member is a REFERENCE to the thing that already exists — never a
29
+ * facade, never a copy, never a wrapper.** `substrate.mounted` IS the
30
+ * `MountedRoot` the adapter returned. `observation.debugRegistry` IS the ONE
31
+ * game-scoped registry every root of that game shares (this binding neither
32
+ * owns it nor tears it down). And the providers that are read AND written
33
+ * through — `selection`, `transforms`, `inspector`, `instances`,
34
+ * `spatialHandles`, `boxEdit`, `text` — appear in BOTH
35
+ * {@link ProjectionBinding} and {@link TruthBinding} as literally the same
36
+ * object, so `binding.projection.selection === binding.truth.selection`. A
37
+ * facade at either address would make the two views disagree the first time
38
+ * anything stateful moved through one of them; reference equality is what
39
+ * makes "regrouping" a checkable claim instead of a promise.
40
+ */
41
+
42
+ import type { ResolvedAdapterRoot } from '../manifest/load';
43
+ import type { DebugRegistry } from '../runtime/debug-registry';
44
+ import type { AdapterDefinition } from './adapter-module';
45
+ import type {
46
+ AssetDropProvider,
47
+ AssetSubjectProvider,
48
+ AuthoringCapabilities,
49
+ AuthoringProvenance,
50
+ BoxEditProvider,
51
+ ColorSampleProvider,
52
+ ComponentInstancesProvider,
53
+ HierarchyProvider,
54
+ InspectorProvider,
55
+ PersistenceProvider,
56
+ PickProvider,
57
+ RectProvider,
58
+ RelatedSubjectsProvider,
59
+ SelectionProvider,
60
+ SpatialHandlesProvider,
61
+ StoriesProvider,
62
+ StructureProvider,
63
+ TextProvider,
64
+ TransformObservation,
65
+ TransformProvider,
66
+ TruthProvider,
67
+ } from './authoring';
68
+ import type { NativeDebugBinding, NativeSystemsBinding } from './native-debug-module';
69
+ import type { MountedRoot, RootStateObserver, SurfaceAdapter } from './root-adapter';
70
+ import type { SystemAdapters } from './system-adapter';
71
+
72
+ /**
73
+ * THE PROTOCOL VOCABULARY — the five families a host talks to a root in, and
74
+ * the ONLY place they are enumerated.
75
+ *
76
+ * This union is the single enumeration on purpose: `RootBinding`'s keys are
77
+ * pinned to it by {@link RootBindingKeysAreExactlyTheProtocolFamilies} below,
78
+ * so a sixth family cannot be added at one address and forgotten at the
79
+ * other, and no second list of these names may exist anywhere in the repo
80
+ * (`adapter-binding-protocol-families.test.ts` is the tripwire).
81
+ */
82
+ export type ProtocolFamily = 'substrate' | 'projection' | 'truth' | 'observation' | 'project';
83
+
84
+ /**
85
+ * WHAT MOUNTED, AND HOW IT RUNS.
86
+ *
87
+ * The adapter that produced the mount, the mount itself, and the two
88
+ * lifecycle facts that are properties of the ROOT rather than of the mounted
89
+ * handle (`pausable` is the manifest's per-world play/pause semantics;
90
+ * `loop` is the declared loop model the loop gate reports against). Every
91
+ * other lifecycle member — `update`, `fixedUpdate`, `setPaused`, `step`,
92
+ * `resize`, `dispose`, `disposeComplete`, `drivesOwnLoop` — is reached
93
+ * through `mounted`, unrepeated, because repeating them here would be the
94
+ * facade this binding exists not to be.
95
+ */
96
+ export interface SubstrateBinding {
97
+ /** The surface-tagged adapter the resolver produced for this root. */
98
+ readonly adapter: SurfaceAdapter;
99
+ /** The handle the adapter's `mount` returned — the same object, not a view. */
100
+ readonly mounted: MountedRoot;
101
+ /** Per-world play/pause semantics (D10's per-world default is `true`). */
102
+ readonly pausable: boolean;
103
+ /** The declared loop model: host-ticked, or the game owns its own rAF. */
104
+ readonly loop: ResolvedAdapterRoot['loop'];
105
+ }
106
+
107
+ /**
108
+ * READ VIEWS of the provider set — what the editor's panels ASK this root.
109
+ *
110
+ * `capabilities` and `hierarchy` are required because an `AuthoringAdapter`
111
+ * cannot exist without them; every other member is optional and absent means
112
+ * "this root does not support that", never a fabricated empty.
113
+ *
114
+ * The seven members marked STRADDLER also appear on {@link TruthBinding}, as
115
+ * the same object (see this module's header).
116
+ */
117
+ export interface ProjectionBinding {
118
+ readonly capabilities: AuthoringCapabilities;
119
+ readonly hierarchy: HierarchyProvider;
120
+ readonly provenance?: AuthoringProvenance | undefined;
121
+ /** STRADDLER. */
122
+ readonly selection?: SelectionProvider | undefined;
123
+ /** STRADDLER. */
124
+ readonly transforms?: TransformProvider | undefined;
125
+ /** STRADDLER. */
126
+ readonly inspector?: InspectorProvider | undefined;
127
+ /** STRADDLER. */
128
+ readonly instances?: ComponentInstancesProvider | undefined;
129
+ /** STRADDLER. */
130
+ readonly spatialHandles?: SpatialHandlesProvider | undefined;
131
+ /** STRADDLER. */
132
+ readonly boxEdit?: BoxEditProvider | undefined;
133
+ /** STRADDLER. */
134
+ readonly text?: TextProvider | undefined;
135
+ readonly assetSubject?: AssetSubjectProvider | undefined;
136
+ readonly related?: RelatedSubjectsProvider | undefined;
137
+ readonly rects?: RectProvider | undefined;
138
+ readonly pickable?: PickProvider | undefined;
139
+ readonly stories?: StoriesProvider | undefined;
140
+ readonly colorSample?: ColorSampleProvider | undefined;
141
+ }
142
+
143
+ /**
144
+ * WRITE VIEWS of the provider set — what an authored edit goes THROUGH.
145
+ *
146
+ * The write-only members (`structure`, `persistence`, `truth`, `assetDrop`)
147
+ * plus the seven straddlers, which are the same objects the projection holds.
148
+ */
149
+ export interface TruthBinding {
150
+ readonly structure?: StructureProvider | undefined;
151
+ readonly persistence?: PersistenceProvider | undefined;
152
+ /** Projection subject → source/data anchor and write lane, resolved together. */
153
+ readonly truth?: TruthProvider | undefined;
154
+ readonly assetDrop?: AssetDropProvider | undefined;
155
+ /** STRADDLER. */
156
+ readonly selection?: SelectionProvider | undefined;
157
+ /** STRADDLER. */
158
+ readonly transforms?: TransformProvider | undefined;
159
+ /** STRADDLER. */
160
+ readonly inspector?: InspectorProvider | undefined;
161
+ /** STRADDLER. */
162
+ readonly instances?: ComponentInstancesProvider | undefined;
163
+ /** STRADDLER. */
164
+ readonly spatialHandles?: SpatialHandlesProvider | undefined;
165
+ /** STRADDLER. */
166
+ readonly boxEdit?: BoxEditProvider | undefined;
167
+ /** STRADDLER. */
168
+ readonly text?: TextProvider | undefined;
169
+ }
170
+
171
+ /**
172
+ * WHAT THIS ROOT LETS ANYONE WATCH — references only.
173
+ *
174
+ * `debugRegistry` is the ONE registry the whole Game shares. This binding
175
+ * holds a reference to it and nothing more: it never creates it, never
176
+ * disposes it, and per-root teardown must not end it (a game-scoped resource
177
+ * destroyed by a root-scoped teardown is a bug this repo has already paid
178
+ * for once).
179
+ *
180
+ * `entryDebug`/`entrySystems` are the entry module's own statically declared
181
+ * bindings, harvested at resolve time and INSTALLED post-mount by
182
+ * `adapter-runtime-bindings` — they ride here rather than being re-read from
183
+ * the module a second time.
184
+ */
185
+ export interface ObservationBinding {
186
+ /** The root's system-adapter slots (physics/networking/navigation/audio/debug). */
187
+ readonly systems?: SystemAdapters | undefined;
188
+ /** Reference to the game-scoped registry. Never owned, never disposed here. */
189
+ readonly debugRegistry: DebugRegistry | null;
190
+ /** The ingested-world observation contract, when the adapter has one. */
191
+ readonly observe?: RootStateObserver | undefined;
192
+ /** Change notification → UI refresh. */
193
+ readonly subscribe?: ((listener: () => void) => () => void) | undefined;
194
+ /** Transform GESTURE observation — the animation sequencer's one seam. */
195
+ readonly observeTransforms?:
196
+ | ((listener: (event: TransformObservation) => void) => () => void)
197
+ | undefined;
198
+ /** The entry module's `debug` export, already validated. */
199
+ readonly entryDebug?: NativeDebugBinding | undefined;
200
+ /** The entry module's `systems` export, already validated. */
201
+ readonly entrySystems?: NativeSystemsBinding | undefined;
202
+ }
203
+
204
+ /**
205
+ * The entry module's static surface, WITH the realm's reduction stated.
206
+ *
207
+ * In dev, browser-storage and packaged realms the host holds the entry
208
+ * module's whole namespace. On the hosted deployment there is no `/@fs`, so
209
+ * what exists is a build-time bundle map and the host holds only the four
210
+ * keys that map carries. Saying so in the type is the point: a
211
+ * `Record<string, unknown>` at both addresses would let a consumer reach for
212
+ * an export that CANNOT be there on the hosted lane and only find out at
213
+ * runtime, on the deployment, in someone else's browser.
214
+ */
215
+ export type EntryStaticSurface =
216
+ | {
217
+ readonly reach: 'full';
218
+ /** The entry module's own namespace object. */
219
+ readonly module: Record<string, unknown>;
220
+ }
221
+ | {
222
+ readonly reach: 'bundleSubset';
223
+ readonly module: BundledEntrySubset;
224
+ };
225
+
226
+ /** Everything the hosted build's bundle map carries for one root. */
227
+ export interface BundledEntrySubset {
228
+ /** The entry's default-exported world component. */
229
+ readonly default: unknown;
230
+ readonly debug?: unknown;
231
+ readonly systems?: unknown;
232
+ readonly components?: unknown;
233
+ }
234
+
235
+ /**
236
+ * WHAT THE PROJECT DECLARED — the manifest root, the parsed `vgai.adapter.ts`
237
+ * definition, and the entry's static surface.
238
+ *
239
+ * `vgai.adapter.ts` LOADING stays where it is (`project-adapter.ts`); its
240
+ * parsed result is an INPUT to `resolveRootBinding`, not something this
241
+ * binding goes and fetches. That is deliberate: the binding is the hand-off's
242
+ * shape, never a second loader.
243
+ */
244
+ export interface ProjectBinding {
245
+ /** The manifest's own resolved root record. */
246
+ readonly root: ResolvedAdapterRoot;
247
+ /**
248
+ * The project's parsed `vgai.adapter.ts`, or `null` when it declares none.
249
+ *
250
+ * `project-adapter.ts` is still the only thing that LOADS it; the editor's
251
+ * `resolveComposition` asks that owner once per composition — waiting on the
252
+ * load in flight rather than reading past it — and hands the answer to
253
+ * `resolveRootBinding`, so every root of one game carries the same
254
+ * declaration. `null` is therefore a fact about the PROJECT: it shipped no
255
+ * declaration file and runs on the declared native default. Which table
256
+ * stood in (project, registry, or native) is a separate question, answered
257
+ * by `ProjectAdapterFacet.source`.
258
+ */
259
+ readonly definition: AdapterDefinition | null;
260
+ /** The entry module's static surface, realm-honest about its reach. */
261
+ readonly entry: EntryStaticSurface;
262
+ }
263
+
264
+ /**
265
+ * One root's whole declaration, bound.
266
+ *
267
+ * The keys ARE {@link ProtocolFamily} — pinned below, so the vocabulary and
268
+ * the value can never drift apart.
269
+ */
270
+ export interface RootBinding {
271
+ readonly substrate: SubstrateBinding;
272
+ /**
273
+ * Present only when this root's mount exposes an `AuthoringAdapter`.
274
+ *
275
+ * A `ProjectionBinding` cannot be fabricated — `capabilities`/`hierarchy`
276
+ * are required — so a mount with no authoring gets no projection. It gets
277
+ * the other four families, which are constructible from the declaration and
278
+ * the mount alone, because withholding those too would answer "what root is
279
+ * this, what did the project declare, what can be watched" with silence for
280
+ * a question the mount's authoring has nothing to do with.
281
+ *
282
+ * Absent is a REAL state, not a rare one: the editor supplies live authoring
283
+ * for native TSX roots itself (`play-mode.ts`'s per-root live adapters,
284
+ * composed into one `CompositeAuthoringAdapter`) rather than through the
285
+ * mount, so today every first-party three/canvas/dom root lands here. Moving
286
+ * that projection into the mount is its own program item; until it does, the
287
+ * absence is the honest report of where authoring lives.
288
+ */
289
+ readonly projection?: ProjectionBinding | undefined;
290
+ /** Present exactly when {@link projection} is — same authoring adapter, same
291
+ * condition; see there. */
292
+ readonly truth?: TruthBinding | undefined;
293
+ readonly observation: ObservationBinding;
294
+ readonly project: ProjectBinding;
295
+ }
296
+
297
+ /**
298
+ * Compile-time pin: `keyof RootBinding` and `ProtocolFamily` are the same set
299
+ * of names, in both directions. Adding a family to one address and not the
300
+ * other is a type error HERE, at the vocabulary, rather than a silent
301
+ * asymmetry every consumer inherits.
302
+ */
303
+ type MutuallyAssignable<A, B> = [A] extends [B] ? ([B] extends [A] ? true : false) : false;
304
+ type AssertTrue<T extends true> = T;
305
+ export type RootBindingKeysAreExactlyTheProtocolFamilies = AssertTrue<
306
+ MutuallyAssignable<keyof RootBinding, ProtocolFamily>
307
+ >;
308
+
309
+ /**
310
+ * Everything {@link createRootBinding} needs, and nothing it could go and get
311
+ * for itself.
312
+ *
313
+ * Each member is a value some OTHER owner already produced: the resolver
314
+ * mounted the adapter, the manifest loader resolved the root, `project-adapter`
315
+ * parsed the definition, the realm loaded the entry namespace, `createGame`
316
+ * owns the debug registry. This shape exists so that list is stated once, at
317
+ * the seam, instead of being re-derived per realm.
318
+ */
319
+ export interface RootBindingParts extends RootDeclaration {
320
+ readonly adapter: SurfaceAdapter;
321
+ readonly mounted: MountedRoot;
322
+ readonly pausable: boolean;
323
+ /** The GAME-scoped registry, borrowed. Never created or disposed here. */
324
+ readonly debugRegistry: DebugRegistry | null;
325
+ }
326
+
327
+ /**
328
+ * The DECLARATION half — everything a resolver knows BEFORE anything mounts.
329
+ *
330
+ * This is the half that travels: `binding-resolver.ts` produces one per root
331
+ * while it is resolving adapters, hands it to the host on the root's mount
332
+ * spec, and the host completes the binding at REGISTRATION — the first moment
333
+ * `mounted` exists at all. Splitting it here is what keeps the resolver from
334
+ * having to be present at mount time, and the host from having to know how an
335
+ * entry module was loaded.
336
+ */
337
+ export interface RootDeclaration {
338
+ /** The manifest's own resolved root record. `loop` rides on it. */
339
+ readonly root: ResolvedAdapterRoot;
340
+ /** The project's parsed `vgai.adapter.ts`, or `null` when it declares none —
341
+ * see {@link ProjectBinding.definition} for the current wiring truth. */
342
+ readonly definition: AdapterDefinition | null;
343
+ /** The entry module's static surface, realm-honest about its reach. */
344
+ readonly entry: EntryStaticSurface;
345
+ /** The entry module's validated `debug` export, harvested at resolve. */
346
+ readonly entryDebug?: NativeDebugBinding | undefined;
347
+ /** The entry module's validated `systems` export, harvested at resolve. */
348
+ readonly entrySystems?: NativeSystemsBinding | undefined;
349
+ }
350
+
351
+ /**
352
+ * REGROUP the parts into the five families. No loading, no defaulting, no
353
+ * fabrication: every member of the result is one of `parts`' own values or one
354
+ * of `parts.mounted.authoring`'s own provider objects, read straight off it.
355
+ *
356
+ * The straddlers are read ONCE into locals and written to both bindings, which
357
+ * is what makes `binding.projection.selection === binding.truth.selection` true
358
+ * by construction rather than by care —
359
+ * `adapter-binding-protocol-families.test.ts` asserts it over a value this
360
+ * function built.
361
+ *
362
+ * The two callback members are the one exception to "the same reference", and
363
+ * deliberately: `subscribe`/`observeTransforms` are METHODS on their adapter
364
+ * (`live-three-authoring-adapter` reads `this.store` inside `subscribe`), so a
365
+ * bare property read would hand the caller a function whose `this` is the
366
+ * observation binding. They are bound to the authoring adapter that owns them —
367
+ * the same function over the same receiver, with nothing interposed. They are
368
+ * not straddlers and carry no equality claim.
369
+ *
370
+ * An absent `mounted.authoring` costs this root its `projection` and `truth`
371
+ * and NOTHING ELSE. There is no such thing as an empty `ProjectionBinding` —
372
+ * `capabilities`/`hierarchy` are required — so those two families are simply
373
+ * not there, which is the honest report. The other three are built from the
374
+ * declaration and the mount, neither of which the authoring adapter has
375
+ * anything to do with; refusing them as well would answer "which root is
376
+ * this, what did the project declare, what can be watched" with silence, and
377
+ * that is what made `binding` unreadable for every native TSX root the editor
378
+ * plays (the editor supplies their authoring itself — see
379
+ * {@link RootBinding.projection}).
380
+ */
381
+ export function createRootBinding(parts: RootBindingParts): RootBinding {
382
+ const authoring = parts.mounted.authoring;
383
+
384
+ // Read each straddler ONCE — the two bindings below then hold these exact
385
+ // objects, not two reads of the same key.
386
+ const { selection, transforms, inspector, instances, spatialHandles, boxEdit, text } =
387
+ authoring ?? {};
388
+
389
+ return {
390
+ substrate: {
391
+ adapter: parts.adapter,
392
+ mounted: parts.mounted,
393
+ pausable: parts.pausable,
394
+ loop: parts.root.loop,
395
+ },
396
+ ...(authoring === undefined
397
+ ? {}
398
+ : projectionAndTruth(authoring, {
399
+ selection,
400
+ transforms,
401
+ inspector,
402
+ instances,
403
+ spatialHandles,
404
+ boxEdit,
405
+ text,
406
+ })),
407
+ observation: {
408
+ systems: parts.mounted.systems,
409
+ debugRegistry: parts.debugRegistry,
410
+ observe: parts.mounted.observe,
411
+ subscribe: authoring?.subscribe?.bind(authoring),
412
+ observeTransforms: authoring?.observeTransforms?.bind(authoring),
413
+ entryDebug: parts.entryDebug,
414
+ entrySystems: parts.entrySystems,
415
+ },
416
+ project: {
417
+ root: parts.root,
418
+ definition: parts.definition,
419
+ entry: parts.entry,
420
+ },
421
+ };
422
+ }
423
+
424
+ /** The two authoring-dependent families, built together from the ONE adapter
425
+ * and the straddlers already read off it — so the reference-equality rule is
426
+ * still decided in a single place. */
427
+ function projectionAndTruth(
428
+ authoring: NonNullable<MountedRoot['authoring']>,
429
+ straddlers: Pick<
430
+ ProjectionBinding,
431
+ 'selection' | 'transforms' | 'inspector' | 'instances' | 'spatialHandles' | 'boxEdit' | 'text'
432
+ >,
433
+ ): { projection: ProjectionBinding; truth: TruthBinding } {
434
+ const { selection, transforms, inspector, instances, spatialHandles, boxEdit, text } = straddlers;
435
+ return {
436
+ projection: {
437
+ capabilities: authoring.capabilities,
438
+ hierarchy: authoring.hierarchy,
439
+ provenance: authoring.provenance,
440
+ selection,
441
+ transforms,
442
+ inspector,
443
+ instances,
444
+ spatialHandles,
445
+ boxEdit,
446
+ text,
447
+ assetSubject: authoring.assetSubject,
448
+ related: authoring.related,
449
+ rects: authoring.rects,
450
+ pickable: authoring.pickable,
451
+ stories: authoring.stories,
452
+ colorSample: authoring.colorSample,
453
+ },
454
+ truth: {
455
+ structure: authoring.structure,
456
+ persistence: authoring.persistence,
457
+ truth: authoring.truth,
458
+ assetDrop: authoring.assetDrop,
459
+ selection,
460
+ transforms,
461
+ inspector,
462
+ instances,
463
+ spatialHandles,
464
+ boxEdit,
465
+ text,
466
+ },
467
+ };
468
+ }
@@ -109,12 +109,23 @@ export function createColyseusNetworkingAdapter(cfg: ColyseusNetworkingConfig):
109
109
  if (cfg.serverAuthoritative?.(o)) return false; // server owns it → inspect-only
110
110
  return cfg.ownerId(o) === cfg.localSessionId(); // only your own peer's objects
111
111
  },
112
- getConnectionState: () => cfg.connectionState?.() ?? 'disconnected',
113
- getRoomInfo: () => cfg.roomInfo?.() ?? null,
114
- getReplicationStats: () =>
115
- cfg.replicationStats?.() ?? { entities: 0, msgsInPerSec: 0, msgsOutPerSec: 0 },
112
+ // `subscribe` is the one member that still defaults, and honestly: an
113
+ // adapter with nothing to notify about has a real no-op subscription, and
114
+ // the caller's contract ("you will hear about changes") is kept — there are
115
+ // none. Every accessor below ANSWERS A QUESTION, and a default answer to a
116
+ // question nobody implemented is a fabricated measurement.
116
117
  subscribe: (cb) => cfg.subscribe?.(cb) ?? (() => {}),
117
118
  };
119
+ // The link-state accessors attach ONLY when supplied, exactly like the W3b
120
+ // capabilities below. They used to default — `'disconnected'`, `null`, and
121
+ // all-zero stats — which read as "we looked, and the game is idle and
122
+ // unconnected" for a config that had simply never been taught to look.
123
+ const connectionState = cfg.connectionState?.bind(cfg);
124
+ if (connectionState) adapter.getConnectionState = () => connectionState();
125
+ const roomInfo = cfg.roomInfo?.bind(cfg);
126
+ if (roomInfo) adapter.getRoomInfo = () => roomInfo();
127
+ const replicationStats = cfg.replicationStats?.bind(cfg);
128
+ if (replicationStats) adapter.getReplicationStats = () => replicationStats();
118
129
  // The W3b inspector capabilities attach ONLY when supplied: their absence
119
130
  // is the editor's degradation signal (`getStateSnapshot` in adapter ⇒ tree,
120
131
  // missing ⇒ "not provided by this adapter" — never a fabricated empty).
@@ -135,7 +146,11 @@ export function createColyseusNetworkingAdapter(cfg: ColyseusNetworkingConfig):
135
146
  // editor shows an editable field only when `setPlayerIdentity` is present.
136
147
  const playerIdentity = cfg.playerIdentity?.bind(cfg);
137
148
  if (playerIdentity) {
138
- adapter.getPlayerIdentity = () => playerIdentity() ?? {};
149
+ // `cfg.playerIdentity` may answer `undefined` "I read the game's state
150
+ // and it has no seat for this client yet". That is not `{}`, an identity
151
+ // that exists and happens to be blank; it is the reader's real answer, and
152
+ // the seam passes it through.
153
+ adapter.getPlayerIdentity = () => playerIdentity();
139
154
  }
140
155
  const setPlayerIdentity = cfg.setPlayerIdentity?.bind(cfg);
141
156
  if (setPlayerIdentity) {
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Pure TS-AST readers over an entrypoint's SELECTION TABLE — shared by the
3
+ * write side (`entrypoint-selection-source.ts`) and the read side
4
+ * (`finders/scenes-from-entrypoint-selection.ts`). These two must agree on
5
+ * what a selection table IS, or play remounts a key the finder never
6
+ * discovered; one spelling here is what makes that agreement structural.
7
+ * (The finder-import boundary bans importing from `finders/` — this sibling
8
+ * sits outside that directory precisely so both sides can share it.)
9
+ */
10
+
11
+ import ts from 'typescript';
12
+
13
+ /** The object literal a module-level `const <name> = { … }` binds. */
14
+ export function selectionTable(
15
+ sf: ts.SourceFile,
16
+ name: string,
17
+ ): ts.ObjectLiteralExpression | undefined {
18
+ for (const statement of sf.statements) {
19
+ if (!ts.isVariableStatement(statement)) continue;
20
+ for (const declaration of statement.declarationList.declarations) {
21
+ if (!ts.isIdentifier(declaration.name) || declaration.name.text !== name) continue;
22
+ const initializer = declaration.initializer;
23
+ if (initializer && ts.isObjectLiteralExpression(initializer)) return initializer;
24
+ }
25
+ }
26
+ return undefined;
27
+ }
28
+
29
+ /** A property's key when it is a plain identifier or string literal. */
30
+ export function propertyKey(property: ts.ObjectLiteralElementLike): string | undefined {
31
+ const name = property.name;
32
+ if (!name) return undefined;
33
+ if (ts.isIdentifier(name) || ts.isStringLiteral(name)) return name.text;
34
+ return undefined;
35
+ }
36
+
37
+ /**
38
+ * The identifier the entrypoint INDEXES the selection table with — the
39
+ * `activeScene` in `scenes[activeScene]`.
40
+ *
41
+ * `undefined` when the module never indexes the table; `null` when it does
42
+ * but not readably (several identifiers, or a non-identifier index). Both
43
+ * are honest "cannot be read" answers the callers turn into their own notes.
44
+ */
45
+ export function indexingIdentifier(
46
+ sf: ts.SourceFile,
47
+ selection: string,
48
+ ): string | undefined | null {
49
+ const names = new Set<string>();
50
+ let unreadable = false;
51
+ const visit = (node: ts.Node): void => {
52
+ if (
53
+ ts.isElementAccessExpression(node) &&
54
+ ts.isIdentifier(node.expression) &&
55
+ node.expression.text === selection
56
+ ) {
57
+ if (ts.isIdentifier(node.argumentExpression)) names.add(node.argumentExpression.text);
58
+ else unreadable = true;
59
+ }
60
+ ts.forEachChild(node, visit);
61
+ };
62
+ ts.forEachChild(sf, visit);
63
+ if (names.size === 0 && !unreadable) return undefined;
64
+ if (names.size !== 1 || unreadable) return null;
65
+ return [...names][0];
66
+ }