@vgai/engine 0.5.15 → 0.5.17

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 (220) hide show
  1. package/dist/adapter/adapter-module.d.ts +229 -33
  2. package/dist/adapter/adapter-module.d.ts.map +1 -1
  3. package/dist/adapter/adapter-module.js +222 -50
  4. package/dist/adapter/authoring-seam-contract.d.ts +530 -0
  5. package/dist/adapter/authoring-seam-contract.d.ts.map +1 -0
  6. package/dist/adapter/authoring-seam-contract.js +162 -0
  7. package/dist/adapter/authoring.d.ts +237 -50
  8. package/dist/adapter/authoring.d.ts.map +1 -1
  9. package/dist/adapter/authoring.js +2 -1
  10. package/dist/adapter/entrypoint-selection-source.d.ts +35 -0
  11. package/dist/adapter/entrypoint-selection-source.d.ts.map +1 -0
  12. package/dist/adapter/entrypoint-selection-source.js +151 -0
  13. package/dist/adapter/finders/scenes-from-entrypoint-selection.d.ts +7 -3
  14. package/dist/adapter/finders/scenes-from-entrypoint-selection.d.ts.map +1 -1
  15. package/dist/adapter/finders/scenes-from-entrypoint-selection.js +145 -27
  16. package/dist/adapter/first-party-systems.d.ts +13 -3
  17. package/dist/adapter/first-party-systems.d.ts.map +1 -1
  18. package/dist/adapter/first-party-systems.js +10 -3
  19. package/dist/adapter/index.d.ts +7 -2
  20. package/dist/adapter/index.d.ts.map +1 -1
  21. package/dist/adapter/index.js +4 -0
  22. package/dist/adapter/ingest/contract-system-adapters.d.ts +1 -1
  23. package/dist/adapter/ingest/contract-system-adapters.d.ts.map +1 -1
  24. package/dist/adapter/ingest/contract-system-adapters.js +13 -25
  25. package/dist/adapter/ingest/game-contract-seams.d.ts +214 -0
  26. package/dist/adapter/ingest/game-contract-seams.d.ts.map +1 -0
  27. package/dist/adapter/ingest/game-contract-seams.js +62 -0
  28. package/dist/adapter/ingest/game-contract.d.ts +5 -3
  29. package/dist/adapter/ingest/game-contract.d.ts.map +1 -1
  30. package/dist/adapter/ingest/game-contract.js +4 -1
  31. package/dist/adapter/ingest/merge-debug-adapters.d.ts +52 -0
  32. package/dist/adapter/ingest/merge-debug-adapters.d.ts.map +1 -0
  33. package/dist/adapter/ingest/merge-debug-adapters.js +141 -0
  34. package/dist/adapter/ingest/observation-debug-adapter.d.ts +56 -0
  35. package/dist/adapter/ingest/observation-debug-adapter.d.ts.map +1 -0
  36. package/dist/adapter/ingest/observation-debug-adapter.js +131 -0
  37. package/dist/adapter/ingest/scene-capture.d.ts +38 -13
  38. package/dist/adapter/ingest/scene-capture.d.ts.map +1 -1
  39. package/dist/adapter/ingest/scene-capture.js +101 -24
  40. package/dist/adapter/ingest/visible-capture-window.d.ts +22 -5
  41. package/dist/adapter/ingest/visible-capture-window.d.ts.map +1 -1
  42. package/dist/adapter/ingest/visible-capture-window.js +46 -7
  43. package/dist/adapter/native-debug-module.d.ts +43 -0
  44. package/dist/adapter/native-debug-module.d.ts.map +1 -0
  45. package/dist/adapter/native-debug-module.js +230 -0
  46. package/dist/adapter/object3d-authoring-subject.d.ts +32 -0
  47. package/dist/adapter/object3d-authoring-subject.d.ts.map +1 -0
  48. package/dist/adapter/object3d-authoring-subject.js +25 -0
  49. package/dist/adapter/root-adapter.d.ts +25 -56
  50. package/dist/adapter/root-adapter.d.ts.map +1 -1
  51. package/dist/adapter/root-adapter.js +4 -20
  52. package/dist/adapter/root-seam-contract.d.ts +129 -0
  53. package/dist/adapter/root-seam-contract.d.ts.map +1 -0
  54. package/dist/adapter/root-seam-contract.js +37 -0
  55. package/dist/adapter/seam-evidence.d.ts +118 -0
  56. package/dist/adapter/seam-evidence.d.ts.map +1 -0
  57. package/dist/adapter/seam-evidence.js +231 -0
  58. package/dist/adapter/setup-three-root-adapter.d.ts +2 -3
  59. package/dist/adapter/setup-three-root-adapter.d.ts.map +1 -1
  60. package/dist/adapter/setup-three-root-adapter.js +11 -12
  61. package/dist/adapter/system-adapter.d.ts +6 -1
  62. package/dist/adapter/system-adapter.d.ts.map +1 -1
  63. package/dist/adapter/system-seam-contract.d.ts +354 -0
  64. package/dist/adapter/system-seam-contract.d.ts.map +1 -0
  65. package/dist/adapter/system-seam-contract.js +95 -0
  66. package/dist/animation/xstate-animation-binding.d.ts +6 -0
  67. package/dist/animation/xstate-animation-binding.d.ts.map +1 -1
  68. package/dist/animation/xstate-animation-binding.js +142 -4
  69. package/dist/asset-formats/collider.d.ts +1 -1
  70. package/dist/asset-formats/mesh.d.ts +2 -2
  71. package/dist/asset-formats/particles.d.ts +1 -1
  72. package/dist/canvas-react/engine-bridge.d.ts +6 -6
  73. package/dist/canvas-react/engine-bridge.d.ts.map +1 -1
  74. package/dist/canvas-react/engine-bridge.js +2 -2
  75. package/dist/canvas-react/index.d.ts +2 -2
  76. package/dist/canvas-react/index.d.ts.map +1 -1
  77. package/dist/canvas-react/index.js +1 -1
  78. package/dist/canvas-react/pixi-react-adapter.d.ts +15 -9
  79. package/dist/canvas-react/pixi-react-adapter.d.ts.map +1 -1
  80. package/dist/canvas-react/pixi-react-adapter.js +129 -64
  81. package/dist/canvas-react/pixi-react-root-factory.d.ts.map +1 -1
  82. package/dist/canvas-react/pixi-react-root-factory.js +4 -0
  83. package/dist/canvas-react/world-context.d.ts +22 -12
  84. package/dist/canvas-react/world-context.d.ts.map +1 -1
  85. package/dist/canvas-react/world-context.js +12 -51
  86. package/dist/data/curve.d.ts +1 -1
  87. package/dist/data/curve.js +2 -2
  88. package/dist/dev/instruments.d.ts +4 -4
  89. package/dist/dev/instruments.js +4 -4
  90. package/dist/dev/register-render-vitals.d.ts +2 -2
  91. package/dist/dev/register-render-vitals.js +2 -2
  92. package/dist/dev/static-batch-advisor.d.ts +1 -1
  93. package/dist/dev/static-batch-advisor.js +2 -2
  94. package/dist/ecs/user-data.d.ts +5 -0
  95. package/dist/ecs/user-data.d.ts.map +1 -1
  96. package/dist/ecs/user-data.js +3 -0
  97. package/dist/input/input-manager.d.ts +64 -0
  98. package/dist/input/input-manager.d.ts.map +1 -1
  99. package/dist/input/input-manager.js +149 -0
  100. package/dist/manifest/load.d.ts +0 -3
  101. package/dist/manifest/load.d.ts.map +1 -1
  102. package/dist/manifest/load.js +0 -4
  103. package/dist/manifest/schema.d.ts +9 -11
  104. package/dist/manifest/schema.d.ts.map +1 -1
  105. package/dist/manifest/schema.js +2 -23
  106. package/dist/pixi/authoring.d.ts +33 -0
  107. package/dist/pixi/authoring.d.ts.map +1 -1
  108. package/dist/pixi/authoring.js +301 -2
  109. package/dist/pixi/ingest.d.ts +2 -1
  110. package/dist/pixi/ingest.d.ts.map +1 -1
  111. package/dist/pixi/ingest.js +4 -2
  112. package/dist/pixi/scene-capture.d.ts +9 -1
  113. package/dist/pixi/scene-capture.d.ts.map +1 -1
  114. package/dist/pixi/scene-capture.js +9 -6
  115. package/dist/react/world-state.d.ts +12 -0
  116. package/dist/react/world-state.d.ts.map +1 -1
  117. package/dist/react/world-state.js +14 -0
  118. package/dist/render/particles-factory.d.ts +13 -6
  119. package/dist/render/particles-factory.d.ts.map +1 -1
  120. package/dist/render/particles-factory.js +6 -3
  121. package/dist/render/soft-particle-depth.d.ts +3 -3
  122. package/dist/render/soft-particle-depth.d.ts.map +1 -1
  123. package/dist/render/soft-particle-depth.js +8 -13
  124. package/dist/runtime/create-runtime.d.ts +25 -62
  125. package/dist/runtime/create-runtime.d.ts.map +1 -1
  126. package/dist/runtime/create-runtime.js +58 -105
  127. package/dist/runtime/dev-build.d.ts +37 -0
  128. package/dist/runtime/dev-build.d.ts.map +1 -0
  129. package/dist/runtime/dev-build.js +49 -0
  130. package/dist/runtime/game.d.ts +38 -72
  131. package/dist/runtime/game.d.ts.map +1 -1
  132. package/dist/runtime/game.js +40 -55
  133. package/dist/runtime/mount-game.d.ts.map +1 -1
  134. package/dist/runtime/mount-game.js +12 -1
  135. package/dist/runtime/mount-manifest.d.ts +0 -13
  136. package/dist/runtime/mount-manifest.d.ts.map +1 -1
  137. package/dist/runtime/mount-manifest.js +5 -25
  138. package/dist/runtime/types.d.ts +18 -0
  139. package/dist/runtime/types.d.ts.map +1 -1
  140. package/dist/setup/setup-renderer.d.ts +3 -4
  141. package/dist/setup/setup-renderer.d.ts.map +1 -1
  142. package/dist/setup/setup-renderer.js +3 -4
  143. package/dist/world3d-react/engine-bridge.d.ts +1 -1
  144. package/dist/world3d-react/engine-bridge.d.ts.map +1 -1
  145. package/dist/world3d-react/engine-bridge.js +29 -4
  146. package/dist/world3d-react/index.d.ts +1 -1
  147. package/dist/world3d-react/index.js +1 -1
  148. package/dist/world3d-react/r3f-adapter.d.ts +7 -0
  149. package/dist/world3d-react/r3f-adapter.d.ts.map +1 -1
  150. package/dist/world3d-react/r3f-adapter.js +132 -28
  151. package/dist/world3d-react/r3f-root-factory.d.ts.map +1 -1
  152. package/dist/world3d-react/r3f-root-factory.js +14 -0
  153. package/dist/world3d-react/rapier-physics-bridge.d.ts +4 -4
  154. package/dist/world3d-react/rapier-physics-bridge.js +3 -3
  155. package/dist/world3d-react/world-context.d.ts +52 -3
  156. package/dist/world3d-react/world-context.d.ts.map +1 -1
  157. package/dist/world3d-react/world-context.js +94 -64
  158. package/dist-config/config.js.map +1 -1
  159. package/package.json +1 -1
  160. package/schemas/engine-capabilities.json +8 -8
  161. package/schemas/vgai-project.schema.json +1 -6
  162. package/src/adapter/adapter-module.ts +405 -72
  163. package/src/adapter/authoring-seam-contract.ts +211 -0
  164. package/src/adapter/authoring.ts +242 -50
  165. package/src/adapter/entrypoint-selection-source.ts +159 -0
  166. package/src/adapter/finders/scenes-from-entrypoint-selection.ts +162 -36
  167. package/src/adapter/first-party-systems.ts +17 -3
  168. package/src/adapter/index.ts +51 -1
  169. package/src/adapter/ingest/contract-system-adapters.ts +26 -30
  170. package/src/adapter/ingest/game-contract-seams.ts +85 -0
  171. package/src/adapter/ingest/game-contract.ts +9 -4
  172. package/src/adapter/ingest/merge-debug-adapters.ts +193 -0
  173. package/src/adapter/ingest/observation-debug-adapter.ts +158 -0
  174. package/src/adapter/ingest/scene-capture.ts +126 -23
  175. package/src/adapter/ingest/visible-capture-window.ts +63 -9
  176. package/src/adapter/native-debug-module.ts +307 -0
  177. package/src/adapter/object3d-authoring-subject.ts +50 -0
  178. package/src/adapter/root-adapter.ts +25 -56
  179. package/src/adapter/root-seam-contract.ts +57 -0
  180. package/src/adapter/seam-evidence.ts +336 -0
  181. package/src/adapter/setup-three-root-adapter.ts +13 -15
  182. package/src/adapter/system-adapter.ts +6 -1
  183. package/src/adapter/system-seam-contract.ts +121 -0
  184. package/src/animation/xstate-animation-binding.ts +151 -4
  185. package/src/canvas-react/engine-bridge.ts +6 -6
  186. package/src/canvas-react/index.ts +2 -1
  187. package/src/canvas-react/pixi-react-adapter.tsx +143 -77
  188. package/src/canvas-react/pixi-react-root-factory.tsx +4 -0
  189. package/src/canvas-react/world-context.ts +26 -101
  190. package/src/data/curve.ts +2 -2
  191. package/src/dev/instruments.ts +4 -4
  192. package/src/dev/register-render-vitals.ts +2 -2
  193. package/src/dev/static-batch-advisor.ts +2 -2
  194. package/src/ecs/user-data.ts +5 -0
  195. package/src/input/input-manager.ts +145 -0
  196. package/src/manifest/load.ts +0 -7
  197. package/src/manifest/schema.ts +2 -25
  198. package/src/pixi/authoring.ts +329 -2
  199. package/src/pixi/ingest.ts +9 -2
  200. package/src/pixi/scene-capture.ts +20 -5
  201. package/src/react/world-state.tsx +15 -0
  202. package/src/render/particles-factory.ts +16 -6
  203. package/src/render/soft-particle-depth.ts +8 -13
  204. package/src/runtime/create-runtime.ts +79 -158
  205. package/src/runtime/dev-build.ts +49 -0
  206. package/src/runtime/game.ts +72 -113
  207. package/src/runtime/mount-game.ts +19 -1
  208. package/src/runtime/mount-manifest.ts +5 -37
  209. package/src/runtime/types.ts +22 -0
  210. package/src/setup/setup-renderer.ts +3 -4
  211. package/src/world3d-react/engine-bridge.ts +29 -4
  212. package/src/world3d-react/index.ts +1 -1
  213. package/src/world3d-react/r3f-adapter.tsx +140 -31
  214. package/src/world3d-react/r3f-root-factory.tsx +14 -0
  215. package/src/world3d-react/rapier-physics-bridge.tsx +4 -4
  216. package/src/world3d-react/world-context.ts +111 -87
  217. package/dist/runtime/dev-layers.d.ts +0 -34
  218. package/dist/runtime/dev-layers.d.ts.map +0 -1
  219. package/dist/runtime/dev-layers.js +0 -46
  220. package/src/runtime/dev-layers.ts +0 -46
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Serve-time rewrite of an entrypoint's swap-slot key.
3
+ *
4
+ * The scenes finder (`adapter/finders/scenes-from-entrypoint-selection.ts`)
5
+ * READS the same facts this module WRITES: a module-level `const <selection>
6
+ * = { … }` table, indexed by one identifier whose own module-level `const`
7
+ * is a string literal (`const activeScene = 'main'`). This is not an
8
+ * extraction of that finder — the finder stays the adapter-selected discovery
9
+ * algorithm, import-banned everywhere except the adapter loader. The rewrite
10
+ * is a host-owned mount parameter: play remounts the same entrypoint with a
11
+ * different declared key, without a new adapter slot and without mutating
12
+ * the file on disk.
13
+ *
14
+ * `typescript` is the same parser the finder already uses. This module is
15
+ * host-side only — a game's `vgai.adapter.ts` must not import it.
16
+ */
17
+
18
+ import ts from 'typescript';
19
+
20
+ export type EntrypointSelectionRewrite =
21
+ | { readonly ok: true; readonly source: string; readonly changed: boolean }
22
+ | { readonly ok: false; readonly reason: string };
23
+
24
+ function parse(path: string, source: string): ts.SourceFile {
25
+ return ts.createSourceFile(path, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
26
+ }
27
+
28
+ function selectionTable(sf: ts.SourceFile, name: string): ts.ObjectLiteralExpression | undefined {
29
+ for (const statement of sf.statements) {
30
+ if (!ts.isVariableStatement(statement)) continue;
31
+ for (const declaration of statement.declarationList.declarations) {
32
+ if (!ts.isIdentifier(declaration.name) || declaration.name.text !== name) continue;
33
+ const initializer = declaration.initializer;
34
+ if (initializer && ts.isObjectLiteralExpression(initializer)) return initializer;
35
+ }
36
+ }
37
+ return undefined;
38
+ }
39
+
40
+ function propertyKey(property: ts.ObjectLiteralElementLike): string | undefined {
41
+ const name = property.name;
42
+ if (!name) return undefined;
43
+ if (ts.isIdentifier(name) || ts.isStringLiteral(name)) return name.text;
44
+ return undefined;
45
+ }
46
+
47
+ function tableKeys(table: ts.ObjectLiteralExpression): string[] {
48
+ const keys: string[] = [];
49
+ for (const property of table.properties) {
50
+ const key = propertyKey(property);
51
+ if (key !== undefined) keys.push(key);
52
+ }
53
+ return keys;
54
+ }
55
+
56
+ /** The identifier in `scenes[activeScene]`, or a failure to read one. */
57
+ function indexingIdentifier(sf: ts.SourceFile, selection: string): string | undefined | null {
58
+ const names = new Set<string>();
59
+ let unreadable = false;
60
+ const visit = (node: ts.Node): void => {
61
+ if (
62
+ ts.isElementAccessExpression(node) &&
63
+ ts.isIdentifier(node.expression) &&
64
+ node.expression.text === selection
65
+ ) {
66
+ if (ts.isIdentifier(node.argumentExpression)) names.add(node.argumentExpression.text);
67
+ else unreadable = true;
68
+ }
69
+ ts.forEachChild(node, visit);
70
+ };
71
+ ts.forEachChild(sf, visit);
72
+ if (names.size === 0 && !unreadable) return undefined;
73
+ if (names.size !== 1 || unreadable) return null;
74
+ return [...names][0];
75
+ }
76
+
77
+ function moduleLevelStringLiteral(sf: ts.SourceFile, name: string): ts.StringLiteral | undefined {
78
+ for (const statement of sf.statements) {
79
+ if (!ts.isVariableStatement(statement)) continue;
80
+ if ((statement.declarationList.flags & ts.NodeFlags.Const) === 0) continue;
81
+ for (const declaration of statement.declarationList.declarations) {
82
+ if (!ts.isIdentifier(declaration.name) || declaration.name.text !== name) continue;
83
+ const initializer = declaration.initializer;
84
+ if (initializer && ts.isStringLiteral(initializer)) return initializer;
85
+ return undefined;
86
+ }
87
+ }
88
+ return undefined;
89
+ }
90
+
91
+ /**
92
+ * Rewrite the entrypoint so its swap slot mounts `key` instead of the
93
+ * source-declared occupant.
94
+ *
95
+ * Fails (never guesses) when the source is not the template's shape: no
96
+ * table, a key the table does not declare, or an index that is not a
97
+ * module-level `const` string. A no-op rewrite (`key` already at the slot)
98
+ * is still `ok` — remounting the same key is a real restart.
99
+ */
100
+ export function rewriteEntrypointSelectionKey(
101
+ source: string,
102
+ path: string,
103
+ selection: string,
104
+ key: string,
105
+ ): EntrypointSelectionRewrite {
106
+ const sf = parse(path, source);
107
+ const table = selectionTable(sf, selection);
108
+ if (!table) {
109
+ return {
110
+ ok: false,
111
+ reason:
112
+ `${path} declares no module-level \`const ${selection} = { … }\` selection table, ` +
113
+ 'so the host cannot remount it at another key.',
114
+ };
115
+ }
116
+ const keys = tableKeys(table);
117
+ if (!keys.includes(key)) {
118
+ return {
119
+ ok: false,
120
+ reason:
121
+ `"${key}" is not a key of ${path}'s \`${selection}\` table ` +
122
+ `(known: ${keys.length > 0 ? keys.join(', ') : 'none'}).`,
123
+ };
124
+ }
125
+ const identifier = indexingIdentifier(sf, selection);
126
+ if (identifier === undefined) {
127
+ return {
128
+ ok: false,
129
+ reason: `${path} never indexes \`${selection}\`, so there is no slot key to rewrite.`,
130
+ };
131
+ }
132
+ if (identifier === null) {
133
+ return {
134
+ ok: false,
135
+ reason:
136
+ `${path} indexes \`${selection}\` with something other than one single identifier, ` +
137
+ 'so the host cannot rewrite the key it mounts.',
138
+ };
139
+ }
140
+ const literal = moduleLevelStringLiteral(sf, identifier);
141
+ if (!literal) {
142
+ return {
143
+ ok: false,
144
+ reason:
145
+ `${path} indexes \`${selection}\` with \`${identifier}\`, which is not a ` +
146
+ 'module-level `const` bound to a string literal — the running game cannot be ' +
147
+ 'sent to another key by rewriting the slot.',
148
+ };
149
+ }
150
+ if (literal.text === key) return { ok: true, source, changed: false };
151
+ const start = literal.getStart(sf);
152
+ const end = literal.getEnd();
153
+ const quote = source[start] ?? "'";
154
+ return {
155
+ ok: true,
156
+ source: `${source.slice(0, start)}${quote}${key}${quote}${source.slice(end)}`,
157
+ changed: true,
158
+ };
159
+ }
@@ -15,7 +15,13 @@
15
15
  *
16
16
  * Two answers, and the difference is declared rather than sniffed:
17
17
  * - the adapter names a `selection` identifier ⇒ read that module-level
18
- * table and answer one entry per key, each reachable through it;
18
+ * table and answer one entry per key, each reachable through it — plus
19
+ * WHICH key the entrypoint indexes it with right now (`scenes[activeScene]`
20
+ * → `const activeScene = 'main'`), marked `reach.active`. That reference is
21
+ * load-bearing for the same reason the table is: the running world renders
22
+ * exactly that key's composition, which is why the host can treat the
23
+ * active entry as the region's own standing document rather than a second
24
+ * tab beside it;
19
25
  * - the adapter names none ⇒ the entrypoint mounts ONE composition, and the
20
26
  * answer is the single-scene DEGENERATE table: the region's own
21
27
  * composition, opened by mounting the region.
@@ -47,8 +53,6 @@ export interface EntrypointSource {
47
53
  readonly path: string;
48
54
  /** The module's bytes; `null` when they could not be read. */
49
55
  readonly source: string | null;
50
- /** The root's declared `dev` flag — a dev layer is the game's own dev GUI. */
51
- readonly dev: boolean;
52
56
  }
53
57
 
54
58
  export interface ScenesFromEntrypointSelectionInput {
@@ -152,6 +156,109 @@ function propertyKey(property: ts.ObjectLiteralElementLike): string | undefined
152
156
  return undefined;
153
157
  }
154
158
 
159
+ /**
160
+ * The identifier the entrypoint INDEXES the selection table with — the
161
+ * `activeScene` in `scenes[activeScene]`.
162
+ *
163
+ * `undefined` when the module never indexes the table, or indexes it with
164
+ * anything other than one single identifier: both are honest "cannot be read",
165
+ * and the caller turns each into its own note.
166
+ */
167
+ function indexingIdentifier(sf: ts.SourceFile, selection: string): string | undefined | null {
168
+ const names = new Set<string>();
169
+ let unreadable = false;
170
+ const visit = (node: ts.Node): void => {
171
+ if (
172
+ ts.isElementAccessExpression(node) &&
173
+ ts.isIdentifier(node.expression) &&
174
+ node.expression.text === selection
175
+ ) {
176
+ if (ts.isIdentifier(node.argumentExpression)) names.add(node.argumentExpression.text);
177
+ else unreadable = true;
178
+ }
179
+ ts.forEachChild(node, visit);
180
+ };
181
+ ts.forEachChild(sf, visit);
182
+ if (names.size === 0 && !unreadable) return undefined; // never indexed at all
183
+ if (names.size !== 1 || unreadable) return null; // indexed, but not readably
184
+ return [...names][0];
185
+ }
186
+
187
+ /**
188
+ * The string a module-level `const <name> = '…'` binds, or `undefined`.
189
+ *
190
+ * `const` and a STRING LITERAL are both required: a `let` can be reassigned and
191
+ * a computed initializer is not a fact the source states, so neither can be
192
+ * reported as the key the game executes. A type annotation
193
+ * (`const activeScene: keyof typeof scenes = 'main'`) is irrelevant here — the
194
+ * initializer is what runs.
195
+ */
196
+ function moduleLevelString(sf: ts.SourceFile, name: string): string | undefined {
197
+ for (const statement of sf.statements) {
198
+ if (!ts.isVariableStatement(statement)) continue;
199
+ if ((statement.declarationList.flags & ts.NodeFlags.Const) === 0) continue;
200
+ for (const declaration of statement.declarationList.declarations) {
201
+ if (!ts.isIdentifier(declaration.name) || declaration.name.text !== name) continue;
202
+ const initializer = declaration.initializer;
203
+ if (initializer && ts.isStringLiteral(initializer)) return initializer.text;
204
+ return undefined;
205
+ }
206
+ }
207
+ return undefined;
208
+ }
209
+
210
+ /**
211
+ * WHICH key the entrypoint mounts at its slot right now, read off the source
212
+ * the game itself executes.
213
+ *
214
+ * Every failure is a NOTE and answers `undefined`: an unread active key costs
215
+ * a retitle, while a guessed one would open the wrong composition as the
216
+ * region's own document.
217
+ */
218
+ function activeSelectionKey(
219
+ entrypoint: EntrypointSource,
220
+ sf: ts.SourceFile,
221
+ selection: string,
222
+ keys: readonly string[],
223
+ notes: string[],
224
+ ): string | undefined {
225
+ const identifier = indexingIdentifier(sf, selection);
226
+ if (identifier === undefined) {
227
+ notes.push(
228
+ `scenesFromEntrypointSelection: ${entrypoint.path} never indexes \`${selection}\`, so ` +
229
+ 'which of its compositions the entrypoint mounts is not stated in the source and no ' +
230
+ 'entry is settled as the active one.',
231
+ );
232
+ return undefined;
233
+ }
234
+ if (identifier === null) {
235
+ notes.push(
236
+ `scenesFromEntrypointSelection: ${entrypoint.path} indexes \`${selection}\` with ` +
237
+ 'something other than one single identifier, so the key it mounts cannot be read ' +
238
+ 'statically.',
239
+ );
240
+ return undefined;
241
+ }
242
+ const key = moduleLevelString(sf, identifier);
243
+ if (key === undefined) {
244
+ notes.push(
245
+ `scenesFromEntrypointSelection: ${entrypoint.path} indexes \`${selection}\` with ` +
246
+ `\`${identifier}\`, which is not a module-level \`const\` bound to a string literal, ` +
247
+ 'so the key it mounts cannot be read statically.',
248
+ );
249
+ return undefined;
250
+ }
251
+ if (!keys.includes(key)) {
252
+ notes.push(
253
+ `scenesFromEntrypointSelection: ${entrypoint.path} mounts \`${selection}\` under ` +
254
+ `"${key}", which is not a key of that table. The entrypoint and its own selection ` +
255
+ 'table disagree.',
256
+ );
257
+ return undefined;
258
+ }
259
+ return key;
260
+ }
261
+
155
262
  /** The identifier a selection entry's value names, if it names one at all. */
156
263
  function propertyIdentifier(property: ts.ObjectLiteralElementLike): string | undefined {
157
264
  if (ts.isShorthandPropertyAssignment(property)) return property.name.text;
@@ -175,11 +282,8 @@ function sourceFor(
175
282
  /**
176
283
  * Which entrypoints this selection targets.
177
284
  *
178
- * The RULE form is every exported-composition region that is not a declared
179
- * DEV layer. Reading `dev` is reading the manifest's own declaration — the dev
180
- * GUI is edit-time chrome, not authorable game content — never a name
181
- * heuristic. A named region with no matching entrypoint becomes a note, not a
182
- * silent omission.
285
+ * The RULE form is every exported-composition region. A named region with no
286
+ * matching entrypoint becomes a note, not a silent omission.
183
287
  */
184
288
  function targetedEntrypoints(
185
289
  params: ScenesFromEntrypointSelectionParams,
@@ -187,7 +291,7 @@ function targetedEntrypoints(
187
291
  notes: string[],
188
292
  ): EntrypointSource[] {
189
293
  if (params.regions === EXPORTED_COMPOSITION_REGIONS) {
190
- return entrypoints.filter((entrypoint) => !entrypoint.dev);
294
+ return [...entrypoints];
191
295
  }
192
296
  const found: EntrypointSource[] = [];
193
297
  for (const regionId of params.regions) {
@@ -219,14 +323,17 @@ function degenerateEntry(entrypoint: EntrypointSource, sf: ts.SourceFile): Scene
219
323
  };
220
324
  }
221
325
 
222
- /** One entry per key of the entrypoint's own selection table. */
326
+ /**
327
+ * One entry per key of the entrypoint's own selection table, plus WHICH of
328
+ * them the entrypoint currently mounts at the slot (`active`).
329
+ */
223
330
  function selectionEntries(
224
331
  entrypoint: EntrypointSource,
225
332
  sf: ts.SourceFile,
226
333
  selection: string,
227
334
  resolveModule: ScenesFromEntrypointSelectionInput['resolveModule'],
228
335
  notes: string[],
229
- ): SceneEntry[] {
336
+ ): { entries: SceneEntry[]; active: string | undefined } {
230
337
  const table = selectionTable(sf, selection);
231
338
  if (!table) {
232
339
  notes.push(
@@ -234,11 +341,12 @@ function selectionEntries(
234
341
  `\`const ${selection} = { … }\` selection table. Nothing is assumed in its ` +
235
342
  'place — fix the adapter’s `selection` or the entrypoint.',
236
343
  );
237
- return [];
344
+ return { entries: [], active: undefined };
238
345
  }
239
346
 
240
347
  const bindings = importedBindings(sf);
241
- const entries: SceneEntry[] = [];
348
+ const keys: string[] = [];
349
+ const built: { key: string; label: string; source: SceneSource | undefined }[] = [];
242
350
  for (const property of table.properties) {
243
351
  const key = propertyKey(property);
244
352
  if (key === undefined) {
@@ -249,23 +357,35 @@ function selectionEntries(
249
357
  continue;
250
358
  }
251
359
  const identifier = propertyIdentifier(property);
252
- const source = sourceFor(
253
- identifier ? bindings.get(identifier) : undefined,
254
- entrypoint.path,
255
- resolveModule,
256
- );
257
- entries.push({
258
- id: key,
360
+ keys.push(key);
361
+ built.push({
362
+ key,
259
363
  label: identifier ?? key,
260
- kind: 'scene',
261
- region: entrypoint.regionId,
262
- authorable: true,
263
- reach: { kind: 'entrypoint-selection', selection, key },
264
- ...(source ? { source } : {}),
265
- finder: 'scenesFromEntrypointSelection',
364
+ source: sourceFor(
365
+ identifier ? bindings.get(identifier) : undefined,
366
+ entrypoint.path,
367
+ resolveModule,
368
+ ),
266
369
  });
267
370
  }
268
- return entries;
371
+
372
+ const active = activeSelectionKey(entrypoint, sf, selection, keys, notes);
373
+ const entries = built.map(({ key, label, source }) => ({
374
+ id: key,
375
+ label,
376
+ kind: 'scene' as const,
377
+ region: entrypoint.regionId,
378
+ authorable: true,
379
+ reach: {
380
+ kind: 'entrypoint-selection' as const,
381
+ selection,
382
+ key,
383
+ ...(key === active ? { active: true } : {}),
384
+ },
385
+ ...(source ? { source } : {}),
386
+ finder: 'scenesFromEntrypointSelection' as const,
387
+ }));
388
+ return { entries, active };
269
389
  }
270
390
 
271
391
  /**
@@ -280,6 +400,7 @@ export function scenesFromEntrypointSelection(
280
400
  ): FinderResult {
281
401
  const notes: string[] = [];
282
402
  const entries: SceneEntry[] = [];
403
+ const active: string[] = [];
283
404
 
284
405
  for (const entrypoint of targetedEntrypoints(params, input.entrypoints, notes)) {
285
406
  if (entrypoint.source === null) {
@@ -290,15 +411,20 @@ export function scenesFromEntrypointSelection(
290
411
  continue;
291
412
  }
292
413
  const sf = parse(entrypoint.path, entrypoint.source);
293
- entries.push(
294
- ...(params.selection === undefined
295
- ? [degenerateEntry(entrypoint, sf)]
296
- : selectionEntries(entrypoint, sf, params.selection, input.resolveModule, notes)),
297
- );
414
+ if (params.selection === undefined) {
415
+ entries.push(degenerateEntry(entrypoint, sf));
416
+ continue;
417
+ }
418
+ const found = selectionEntries(entrypoint, sf, params.selection, input.resolveModule, notes);
419
+ entries.push(...found.entries);
420
+ if (found.active !== undefined) active.push(found.active);
298
421
  }
299
422
 
300
- // One candidate settles the default; two or more is a choice, and a choice
301
- // belongs to the adapter author.
302
- const only = entries.length === 1 ? entries[0] : undefined;
303
- return only ? { entries, default: only.id, notes } : { entries, notes };
423
+ // The ACTIVE key settles the default outright: the entrypoint states which
424
+ // composition it mounts, so this is the finder READING an answer rather than
425
+ // choosing between candidates. Failing that, one candidate settles it; two or
426
+ // more is a choice, and a choice belongs to the adapter author.
427
+ const settled =
428
+ active.length === 1 ? active[0] : entries.length === 1 ? entries[0]?.id : undefined;
429
+ return settled !== undefined ? { entries, default: settled, notes } : { entries, notes };
304
430
  }
@@ -20,6 +20,7 @@ import type {
20
20
  AudioMeterFrame,
21
21
  AudioMeterHandle,
22
22
  AudioRecordingHandle,
23
+ NavBakeParams,
23
24
  NavigationAdapter,
24
25
  NavPoint,
25
26
  } from './system-adapter';
@@ -229,14 +230,27 @@ export function createAudioSystemAdapter(audio: GameAudio): AudioAdapter {
229
230
 
230
231
  /** First-party `NavigationAdapter` over the engine `NavMeshManager` — the
231
232
  * blessed recast implementation, including the optional bake/export/crowd
232
- * capabilities the editor's Navigation panel binds through. */
233
- export function createNavigationAdapter(nav: NavMeshManager): NavigationAdapter {
233
+ * capabilities the editor's Navigation panel binds through.
234
+ *
235
+ * `clear` is deliberately owner-supplied: a manager cannot know which gameplay
236
+ * objects retain CrowdAgent handles, so disposing it generically would leave
237
+ * those handles pointing at freed WASM memory. A game that grants clear owns
238
+ * releasing/rebinding those references in the same callback. `bake` may be
239
+ * overridden for the same reason when a live crowd must be rebuilt. */
240
+ export function createNavigationAdapter(
241
+ nav: NavMeshManager,
242
+ ownerCapabilities: {
243
+ readonly bake?: (meshes: THREE.Mesh[], params?: NavBakeParams) => boolean;
244
+ readonly clear?: (scene: THREE.Scene) => void;
245
+ } = {},
246
+ ): NavigationAdapter {
234
247
  return {
235
248
  hasNavMesh: () => nav.hasNavMesh(),
236
249
  findPath: (start: NavPoint, end: NavPoint) => nav.findPath(start, end) as NavPoint[],
237
250
  debugMesh: (scene: THREE.Scene) => nav.getDebugMesh(scene) as unknown as THREE.Object3D | null,
238
- bake: (meshes, params) => nav.buildFromMeshes(meshes, params),
251
+ bake: ownerCapabilities.bake ?? ((meshes, params) => nav.buildFromMeshes(meshes, params)),
239
252
  exportData: () => nav.exportData(),
253
+ ...(ownerCapabilities.clear ? { clear: ownerCapabilities.clear } : {}),
240
254
  crowdAgents: () => nav.getCrowdAgents(),
241
255
  };
242
256
  }
@@ -33,6 +33,7 @@ export type {
33
33
  AuthoringCapabilities,
34
34
  AuthoringProvenance,
35
35
  AuthoringProviderKey,
36
+ AuthoringTruth,
36
37
  BoxEditProvider,
37
38
  BoxEditReferencePoint,
38
39
  ColorSampleProvider,
@@ -40,7 +41,6 @@ export type {
40
41
  ComponentInstanceDescription,
41
42
  ComponentInstanceOverride,
42
43
  ComponentInstancesProvider,
43
- CreationSiteProvider,
44
44
  DOMRectLike,
45
45
  EditorNode,
46
46
  EditorNodeRole,
@@ -62,12 +62,18 @@ export type {
62
62
  SpatialPoint3,
63
63
  StoriesProvider,
64
64
  StoryRef,
65
+ StructuralClipboardOutcome,
66
+ StructuralIdsWrite,
67
+ StructuralIdWrite,
68
+ StructuralWriteOutcome,
65
69
  StructureProvider,
66
70
  TextProvider,
67
71
  TransformChannel,
68
72
  TransformEditability,
69
73
  TransformObservation,
70
74
  TransformProvider,
75
+ TransformSourceCommitProvider,
76
+ TruthProvider,
71
77
  WriteAck,
72
78
  WriteAnchorKind,
73
79
  } from './authoring';
@@ -77,6 +83,11 @@ export {
77
83
  measureAuthoringProviders,
78
84
  WRITE_ANCHOR_KINDS,
79
85
  } from './authoring';
86
+ export {
87
+ AUTHORING_ADAPTER_SHAPE,
88
+ AUTHORING_PROVIDER_SHAPES,
89
+ HIERARCHY_PROVIDER_SHAPE,
90
+ } from './authoring-seam-contract';
80
91
  export type { ColyseusNetworkingConfig } from './colyseus-networking-adapter';
81
92
  export type {
82
93
  CanvasHostContext,
@@ -97,6 +108,32 @@ export type {
97
108
  SurfaceAdapter,
98
109
  SurfaceAdapterFor,
99
110
  } from './root-adapter';
111
+ export {
112
+ MOUNTED_CANVAS_SURFACE_SHAPE,
113
+ MOUNTED_DOM_SURFACE_SHAPE,
114
+ MOUNTED_ROOT_BASE_SHAPE,
115
+ MOUNTED_THREE_SURFACE_SHAPE,
116
+ ROOT_ADAPTER_SHAPE,
117
+ ROOT_STATE_OBSERVER_SHAPE,
118
+ } from './root-seam-contract';
119
+ export type {
120
+ SeamEvidenceOutcome,
121
+ SeamEvidenceReceipt,
122
+ SeamEvidenceSource,
123
+ SeamEvidenceState,
124
+ SeamEvidenceVerdict,
125
+ SeamProofStage,
126
+ SeamShape,
127
+ } from './seam-evidence';
128
+ export {
129
+ defineSeamShape,
130
+ gradeSeamCarrier,
131
+ gradeSeamEvidence,
132
+ inspectSeamShape,
133
+ SEAM_PROOF_STAGES,
134
+ SeamEvidenceLedger,
135
+ seamStageAtLeast,
136
+ } from './seam-evidence';
100
137
  export type { MountedSetupThreeRoot, SetupThreeRootConfig } from './setup-three-root-adapter';
101
138
  export type {
102
139
  AudioAdapter,
@@ -111,6 +148,7 @@ export type {
111
148
  CameraRuntimeSnapshot,
112
149
  CameraRuntimeTransition,
113
150
  ConnectionState,
151
+ DebugAdapter,
114
152
  NavCrowdAgentState,
115
153
  NavigationAdapter,
116
154
  NavPoint,
@@ -136,4 +174,16 @@ export type {
136
174
  /** The two narrowings of the tagged `SystemAdapters['physics']` union — see
137
175
  * `PhysicsAdapter`'s comment for why it is tagged at all. */
138
176
  export { displayKeyedPhysics, nodeKeyedPhysics } from './system-adapter';
177
+ export {
178
+ AUDIO_ADAPTER_SHAPE,
179
+ CAMERA_ADAPTER_SHAPE,
180
+ DEBUG_ADAPTER_SHAPE,
181
+ NAVIGATION_ADAPTER_SHAPE,
182
+ NETWORKING_ADAPTER_SHAPE,
183
+ PHYSICS_2D_ADAPTER_SHAPE,
184
+ PHYSICS_ADAPTER_SHAPE,
185
+ RENDER_DEBUG_ADAPTER_SHAPE,
186
+ SYSTEM_ADAPTERS_SHAPE,
187
+ SYSTEM_PROVIDER_SHAPES,
188
+ } from './system-seam-contract';
139
189
  export type { Transform, TransformOwner } from './transform';
@@ -43,7 +43,16 @@
43
43
  */
44
44
 
45
45
  import type { SystemAdapters } from '../system-adapter';
46
+ import {
47
+ AUDIO_ADAPTER_SHAPE,
48
+ CAMERA_ADAPTER_SHAPE,
49
+ NAVIGATION_ADAPTER_SHAPE,
50
+ NETWORKING_ADAPTER_SHAPE,
51
+ PHYSICS_ADAPTER_SHAPE,
52
+ RENDER_DEBUG_ADAPTER_SHAPE,
53
+ } from '../system-seam-contract';
46
54
  import type { VgaiGameSystemAdapters, VgaiGameSystemEmpty, VgaiGameSystems } from './game-contract';
55
+ import { GAME_SYSTEM_ADAPTERS_SHAPE } from './game-contract-seams';
47
56
 
48
57
  /** The slots a game may declare through the contract (`debug` is projected from
49
58
  * `commands`/`state` instead — see {@link VgaiGameSystemAdapters}). */
@@ -58,25 +67,14 @@ export type ContractSystemSlot = keyof VgaiGameSystemAdapters;
58
67
  * is typed as keys of its own adapter), so it cannot drift into naming a member
59
68
  * that does not exist.
60
69
  */
61
- const REQUIRED_MEMBERS: {
62
- [K in ContractSystemSlot]: readonly (keyof NonNullable<SystemAdapters[K]>)[];
63
- } = {
64
- physics: ['ownerOf', 'freeze', 'commit', 'unfreeze'],
65
- networking: [
66
- 'peers',
67
- 'networkId',
68
- 'authority',
69
- 'editable',
70
- 'getConnectionState',
71
- 'getRoomInfo',
72
- 'getReplicationStats',
73
- 'subscribe',
74
- ],
75
- navigation: ['hasNavMesh', 'findPath', 'debugMesh'],
76
- audio: ['setMuted', 'isMuted'],
77
- camera: ['snapshot', 'subscribe'],
78
- renderDebug: ['captureFrame'],
79
- };
70
+ const SYSTEM_SHAPES = {
71
+ physics: PHYSICS_ADAPTER_SHAPE,
72
+ networking: NETWORKING_ADAPTER_SHAPE,
73
+ navigation: NAVIGATION_ADAPTER_SHAPE,
74
+ audio: AUDIO_ADAPTER_SHAPE,
75
+ camera: CAMERA_ADAPTER_SHAPE,
76
+ renderDebug: RENDER_DEBUG_ADAPTER_SHAPE,
77
+ } as const satisfies Record<ContractSystemSlot, object>;
80
78
 
81
79
  /**
82
80
  * The render surface a declaration is being read ON — the same three words
@@ -100,14 +98,9 @@ const PHYSICS_KEYING_BY_SURFACE: Readonly<Record<ContractSurface, 'node-id' | 'd
100
98
 
101
99
  /** Fixed slot order, so two reports of the same mount are diffable line for
102
100
  * line (the same rule the coverage report's row order follows). */
103
- export const CONTRACT_SYSTEM_SLOTS: readonly ContractSystemSlot[] = [
104
- 'physics',
105
- 'networking',
106
- 'navigation',
107
- 'audio',
108
- 'camera',
109
- 'renderDebug',
110
- ];
101
+ export const CONTRACT_SYSTEM_SLOTS = Object.keys(
102
+ GAME_SYSTEM_ADAPTERS_SHAPE,
103
+ ) as ContractSystemSlot[];
111
104
 
112
105
  /** One slot the game positively answered as having nothing behind it. */
113
106
  export interface ContractSystemEmptySlot {
@@ -143,9 +136,12 @@ function isEmptyRecord(value: object): value is VgaiGameSystemEmpty {
143
136
 
144
137
  function missingMembers(slot: ContractSystemSlot, value: object): string[] {
145
138
  const missing: string[] = [];
146
- for (const member of REQUIRED_MEMBERS[slot]) {
147
- if (typeof (value as Record<string, unknown>)[member as string] !== 'function') {
148
- missing.push(member as string);
139
+ for (const [member, spec] of Object.entries(
140
+ SYSTEM_SHAPES[slot] as Readonly<Record<string, { readonly optional: boolean }>>,
141
+ )) {
142
+ if (spec.optional) continue;
143
+ if (typeof (value as Record<string, unknown>)[member] !== 'function') {
144
+ missing.push(member);
149
145
  }
150
146
  }
151
147
  return missing;