@vgai/engine 0.5.15 → 0.5.16

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 (174) hide show
  1. package/dist/adapter/adapter-module.d.ts +228 -17
  2. package/dist/adapter/adapter-module.d.ts.map +1 -1
  3. package/dist/adapter/adapter-module.js +224 -42
  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 -1
  14. package/dist/adapter/finders/scenes-from-entrypoint-selection.d.ts.map +1 -1
  15. package/dist/adapter/finders/scenes-from-entrypoint-selection.js +142 -21
  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 +21 -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 +124 -0
  53. package/dist/adapter/root-seam-contract.d.ts.map +1 -0
  54. package/dist/adapter/root-seam-contract.js +36 -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 +2 -3
  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 +43 -29
  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/ecs/user-data.d.ts +5 -0
  87. package/dist/ecs/user-data.d.ts.map +1 -1
  88. package/dist/ecs/user-data.js +3 -0
  89. package/dist/input/input-manager.d.ts +49 -0
  90. package/dist/input/input-manager.d.ts.map +1 -1
  91. package/dist/input/input-manager.js +127 -0
  92. package/dist/manifest/schema.d.ts +9 -9
  93. package/dist/pixi/authoring.d.ts.map +1 -1
  94. package/dist/pixi/authoring.js +220 -1
  95. package/dist/pixi/ingest.d.ts +2 -1
  96. package/dist/pixi/ingest.d.ts.map +1 -1
  97. package/dist/pixi/ingest.js +4 -2
  98. package/dist/pixi/scene-capture.d.ts +9 -1
  99. package/dist/pixi/scene-capture.d.ts.map +1 -1
  100. package/dist/pixi/scene-capture.js +9 -6
  101. package/dist/render/particles-factory.d.ts +13 -6
  102. package/dist/render/particles-factory.d.ts.map +1 -1
  103. package/dist/render/particles-factory.js +6 -3
  104. package/dist/runtime/create-runtime.d.ts +23 -46
  105. package/dist/runtime/create-runtime.d.ts.map +1 -1
  106. package/dist/runtime/create-runtime.js +22 -67
  107. package/dist/runtime/game.d.ts +29 -71
  108. package/dist/runtime/game.d.ts.map +1 -1
  109. package/dist/runtime/game.js +15 -54
  110. package/dist/runtime/mount-game.d.ts.map +1 -1
  111. package/dist/runtime/mount-game.js +12 -1
  112. package/dist/runtime/mount-manifest.d.ts.map +1 -1
  113. package/dist/runtime/mount-manifest.js +4 -5
  114. package/dist/runtime/types.d.ts +18 -0
  115. package/dist/runtime/types.d.ts.map +1 -1
  116. package/dist/setup/setup-renderer.d.ts +3 -4
  117. package/dist/setup/setup-renderer.d.ts.map +1 -1
  118. package/dist/setup/setup-renderer.js +3 -4
  119. package/dist/world3d-react/engine-bridge.d.ts +1 -1
  120. package/dist/world3d-react/engine-bridge.js +1 -1
  121. package/dist/world3d-react/r3f-adapter.d.ts +7 -0
  122. package/dist/world3d-react/r3f-adapter.d.ts.map +1 -1
  123. package/dist/world3d-react/r3f-adapter.js +50 -21
  124. package/dist/world3d-react/r3f-root-factory.d.ts.map +1 -1
  125. package/dist/world3d-react/r3f-root-factory.js +4 -0
  126. package/dist/world3d-react/world-context.d.ts +9 -3
  127. package/dist/world3d-react/world-context.d.ts.map +1 -1
  128. package/dist/world3d-react/world-context.js +5 -18
  129. package/package.json +1 -1
  130. package/schemas/engine-capabilities.json +7 -7
  131. package/src/adapter/adapter-module.ts +407 -50
  132. package/src/adapter/authoring-seam-contract.ts +211 -0
  133. package/src/adapter/authoring.ts +242 -50
  134. package/src/adapter/entrypoint-selection-source.ts +159 -0
  135. package/src/adapter/finders/scenes-from-entrypoint-selection.ts +159 -28
  136. package/src/adapter/first-party-systems.ts +17 -3
  137. package/src/adapter/index.ts +51 -1
  138. package/src/adapter/ingest/contract-system-adapters.ts +26 -30
  139. package/src/adapter/ingest/game-contract-seams.ts +85 -0
  140. package/src/adapter/ingest/game-contract.ts +9 -4
  141. package/src/adapter/ingest/merge-debug-adapters.ts +193 -0
  142. package/src/adapter/ingest/observation-debug-adapter.ts +158 -0
  143. package/src/adapter/ingest/scene-capture.ts +126 -23
  144. package/src/adapter/ingest/visible-capture-window.ts +63 -9
  145. package/src/adapter/native-debug-module.ts +307 -0
  146. package/src/adapter/object3d-authoring-subject.ts +50 -0
  147. package/src/adapter/root-adapter.ts +21 -56
  148. package/src/adapter/root-seam-contract.ts +56 -0
  149. package/src/adapter/seam-evidence.ts +336 -0
  150. package/src/adapter/setup-three-root-adapter.ts +4 -6
  151. package/src/adapter/system-adapter.ts +6 -1
  152. package/src/adapter/system-seam-contract.ts +121 -0
  153. package/src/animation/xstate-animation-binding.ts +151 -4
  154. package/src/canvas-react/engine-bridge.ts +6 -6
  155. package/src/canvas-react/index.ts +2 -1
  156. package/src/canvas-react/pixi-react-adapter.tsx +55 -34
  157. package/src/canvas-react/pixi-react-root-factory.tsx +4 -0
  158. package/src/canvas-react/world-context.ts +26 -101
  159. package/src/ecs/user-data.ts +5 -0
  160. package/src/input/input-manager.ts +122 -0
  161. package/src/pixi/authoring.ts +236 -1
  162. package/src/pixi/ingest.ts +9 -2
  163. package/src/pixi/scene-capture.ts +20 -5
  164. package/src/render/particles-factory.ts +16 -6
  165. package/src/runtime/create-runtime.ts +41 -106
  166. package/src/runtime/game.ts +41 -111
  167. package/src/runtime/mount-game.ts +19 -1
  168. package/src/runtime/mount-manifest.ts +4 -5
  169. package/src/runtime/types.ts +22 -0
  170. package/src/setup/setup-renderer.ts +3 -4
  171. package/src/world3d-react/engine-bridge.ts +1 -1
  172. package/src/world3d-react/r3f-adapter.tsx +64 -24
  173. package/src/world3d-react/r3f-root-factory.tsx +4 -0
  174. package/src/world3d-react/world-context.ts +11 -36
@@ -0,0 +1,307 @@
1
+ /**
2
+ * The ordinary module surface a native game may expose to its own debugger.
3
+ *
4
+ * A root entry re-exports one `debug` object from the project's command module:
5
+ *
6
+ * export { debug } from './commands';
7
+ *
8
+ * The command module imports no vgai API. Its functions are the application's
9
+ * own functions over its own stores; the native adapter merely projects that
10
+ * existing registry onto the session debug/input doors after the root mounts.
11
+ * Loading through the root entry is load-bearing: the functions close over the
12
+ * exact module graph the mounted application uses, never a separately evaluated
13
+ * copy of its stores.
14
+ */
15
+
16
+ import {
17
+ DebugError,
18
+ type DebugRegistry,
19
+ type DebugVirtualInputTarget,
20
+ getDebugRegistry,
21
+ } from '../runtime/debug-registry';
22
+ import type { Game } from '../runtime/game';
23
+
24
+ export const NATIVE_INPUT_VALUE_TYPES = [
25
+ 'digital',
26
+ 'scalar',
27
+ 'vector2',
28
+ 'pointerDelta',
29
+ 'pointerPosition',
30
+ ] as const;
31
+ export type NativeInputValueType = (typeof NATIVE_INPUT_VALUE_TYPES)[number];
32
+ export type NativeInputValue = boolean | number | { readonly x: number; readonly y: number };
33
+
34
+ export interface NativeDebugModule {
35
+ readonly commands?: Readonly<Record<string, (...args: unknown[]) => unknown>>;
36
+ readonly state?: Readonly<Record<string, () => unknown>>;
37
+ readonly input?: {
38
+ readonly actions: Readonly<Record<string, NativeInputValueType>>;
39
+ readonly set: (action: string, value: NativeInputValue) => void;
40
+ readonly clear: () => void;
41
+ /** Optional native-scheduler operation. The adapter never synthesizes one. */
42
+ readonly tap?: (action: string) => void;
43
+ /** Optional native-scheduler operation. The adapter never adds a host phase. */
44
+ readonly scheduleAtTick?: (tick: number, action: string, value: NativeInputValue) => void;
45
+ };
46
+ }
47
+
48
+ type NativeInputBinding = NonNullable<NativeDebugModule['input']>;
49
+
50
+ export interface NativeDebugBinding {
51
+ readonly rootId: string;
52
+ readonly debug: NativeDebugModule;
53
+ }
54
+
55
+ function isRecord(value: unknown): value is Record<string, unknown> {
56
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
57
+ }
58
+
59
+ function rejectUnknownKeys(value: Record<string, unknown>, allowed: readonly string[], at: string) {
60
+ const unknown = Object.keys(value).filter((key) => !allowed.includes(key));
61
+ if (unknown.length > 0) {
62
+ throw new Error(
63
+ `${at} has unknown ${unknown.length === 1 ? 'key' : 'keys'}: ${unknown.join(', ')}`,
64
+ );
65
+ }
66
+ }
67
+
68
+ function functionRecord(
69
+ value: unknown,
70
+ at: string,
71
+ ): Record<string, (...args: unknown[]) => unknown> {
72
+ if (!isRecord(value)) throw new Error(`${at} must be an object of named functions.`);
73
+ const out: Record<string, (...args: unknown[]) => unknown> = {};
74
+ for (const [name, candidate] of Object.entries(value)) {
75
+ if (!name) throw new Error(`${at} contains an empty name.`);
76
+ if (typeof candidate !== 'function') throw new Error(`${at}.${name} must be a function.`);
77
+ out[name] = candidate as (...args: unknown[]) => unknown;
78
+ }
79
+ return out;
80
+ }
81
+
82
+ function parseActions(value: unknown): Record<string, NativeInputValueType> {
83
+ if (!isRecord(value)) {
84
+ throw new Error('debug.input.actions must be an object mapping action names to value types.');
85
+ }
86
+ const actions: Record<string, NativeInputValueType> = {};
87
+ for (const [name, valueType] of Object.entries(value)) {
88
+ if (!name) throw new Error('debug.input.actions contains an empty action name.');
89
+ if (!NATIVE_INPUT_VALUE_TYPES.includes(valueType as NativeInputValueType)) {
90
+ throw new Error(
91
+ `debug.input.actions.${name} must be one of ${NATIVE_INPUT_VALUE_TYPES.join(', ')}.`,
92
+ );
93
+ }
94
+ actions[name] = valueType as NativeInputValueType;
95
+ }
96
+ return actions;
97
+ }
98
+
99
+ function parseInput(value: unknown): NativeInputBinding {
100
+ if (!isRecord(value)) throw new Error('debug.input must be an object.');
101
+ rejectUnknownKeys(value, ['actions', 'set', 'clear', 'tap', 'scheduleAtTick'], 'debug.input');
102
+ const set = value['set'];
103
+ const clear = value['clear'];
104
+ const tap = value['tap'];
105
+ const scheduleAtTick = value['scheduleAtTick'];
106
+ if (typeof set !== 'function') throw new Error('debug.input.set must be a function.');
107
+ if (typeof clear !== 'function') throw new Error('debug.input.clear must be a function.');
108
+ if (tap !== undefined && typeof tap !== 'function') {
109
+ throw new Error('debug.input.tap must be a function when provided.');
110
+ }
111
+ if (scheduleAtTick !== undefined && typeof scheduleAtTick !== 'function') {
112
+ throw new Error('debug.input.scheduleAtTick must be a function when provided.');
113
+ }
114
+ return {
115
+ actions: parseActions(value['actions']),
116
+ set: set as NativeInputBinding['set'],
117
+ clear: clear as NativeInputBinding['clear'],
118
+ ...(typeof tap === 'function' ? { tap: tap as NonNullable<NativeInputBinding['tap']> } : {}),
119
+ ...(typeof scheduleAtTick === 'function'
120
+ ? {
121
+ scheduleAtTick: scheduleAtTick as NonNullable<NativeInputBinding['scheduleAtTick']>,
122
+ }
123
+ : {}),
124
+ };
125
+ }
126
+
127
+ /** Read the single native `debug` export from an already-loaded root module. */
128
+ export function nativeDebugBindingFromEntryModule(
129
+ rootId: string,
130
+ entryModule: unknown,
131
+ ): NativeDebugBinding | null {
132
+ if (!isRecord(entryModule) || entryModule['debug'] === undefined) return null;
133
+ if (!isRecord(entryModule['debug'])) {
134
+ throw new Error(`Root "${rootId}" exports \`debug\`, but it is not an object.`);
135
+ }
136
+ const raw = entryModule['debug'];
137
+ rejectUnknownKeys(raw, ['commands', 'state', 'input'], `Root "${rootId}" debug export`);
138
+
139
+ const commands =
140
+ raw['commands'] === undefined ? undefined : functionRecord(raw['commands'], 'debug.commands');
141
+ const state =
142
+ raw['state'] === undefined ? undefined : functionRecord(raw['state'], 'debug.state');
143
+ const input = raw['input'] === undefined ? undefined : parseInput(raw['input']);
144
+ if (!commands && !state && !input) {
145
+ throw new Error(`Root "${rootId}" exports an empty \`debug\` object.`);
146
+ }
147
+ return {
148
+ rootId,
149
+ debug: {
150
+ ...(commands ? { commands } : {}),
151
+ ...(state ? { state } : {}),
152
+ ...(input ? { input } : {}),
153
+ },
154
+ };
155
+ }
156
+
157
+ function valueMatches(type: NativeInputValueType, value: NativeInputValue): boolean {
158
+ if (type === 'digital') return typeof value === 'boolean';
159
+ if (type === 'scalar') return typeof value === 'number' && Number.isFinite(value);
160
+ return (
161
+ typeof value === 'object' &&
162
+ value !== null &&
163
+ typeof value.x === 'number' &&
164
+ Number.isFinite(value.x) &&
165
+ typeof value.y === 'number' &&
166
+ Number.isFinite(value.y)
167
+ );
168
+ }
169
+
170
+ function installInput(
171
+ game: Game,
172
+ registry: DebugRegistry,
173
+ binding: NativeDebugBinding,
174
+ input: NativeInputBinding,
175
+ ): void {
176
+ const trace: Array<{ tick: number; action: string; value: NativeInputValue }> = [];
177
+ let recording = false;
178
+
179
+ const requireAction = (action: string, value: NativeInputValue): NativeInputValueType => {
180
+ const type = input.actions[action];
181
+ if (!type) {
182
+ throw new DebugError(
183
+ 'INPUT_ACTION_NOT_FOUND',
184
+ `Native input has no action named "${action}".`,
185
+ {
186
+ registered: Object.keys(input.actions),
187
+ },
188
+ );
189
+ }
190
+ if (!valueMatches(type, value)) {
191
+ throw new DebugError(
192
+ 'INPUT_ACTION_VALUE_TYPE',
193
+ `Native input action "${action}" expects ${type}, not ${typeof value}.`,
194
+ { action, expected: type },
195
+ );
196
+ }
197
+ return type;
198
+ };
199
+
200
+ const apply = (action: string, value: NativeInputValue, tick: number): void => {
201
+ requireAction(action, value);
202
+ input.set(action, value);
203
+ if (recording) trace.push({ tick, action, value });
204
+ };
205
+
206
+ const unsupportedSource = (method: string): never => {
207
+ throw new DebugError(
208
+ 'NATIVE_INPUT_SOURCE_UNSUPPORTED',
209
+ `${method} addresses an engine test source, but this game exposes an app-owned action store. Drive it by action name instead.`,
210
+ );
211
+ };
212
+ const target: DebugVirtualInputTarget = {
213
+ setVirtualAction(action, value) {
214
+ apply(action, value, registry.getGameTick());
215
+ return { delivered: true };
216
+ },
217
+ tapVirtualAction(action) {
218
+ if (!input.tap) {
219
+ throw new DebugError(
220
+ 'NATIVE_INPUT_TAP_UNSUPPORTED',
221
+ `Native input for root "${binding.rootId}" does not export tap(); use setVirtualAction through the app-owned store.`,
222
+ );
223
+ }
224
+ requireAction(action, true);
225
+ input.tap(action);
226
+ return { delivered: true };
227
+ },
228
+ clearVirtualActions() {
229
+ input.clear();
230
+ },
231
+ scheduleActionAtTick(tick, action, value) {
232
+ const currentTick = registry.getGameTick();
233
+ if (!Number.isInteger(tick) || tick < currentTick) {
234
+ throw new DebugError(
235
+ 'TICK_ALREADY_PASSED',
236
+ `Cannot schedule native input for tick ${tick}; current tick is ${currentTick}.`,
237
+ { tick, currentTick },
238
+ );
239
+ }
240
+ requireAction(action, value);
241
+ if (!input.scheduleAtTick) {
242
+ throw new DebugError(
243
+ 'NATIVE_INPUT_SCHEDULE_UNSUPPORTED',
244
+ `Native input for root "${binding.rootId}" does not export scheduleAtTick(); the adapter will not create a host scheduler for it.`,
245
+ );
246
+ }
247
+ input.scheduleAtTick(tick, action, value);
248
+ },
249
+ startInputRecording() {
250
+ trace.length = 0;
251
+ recording = true;
252
+ },
253
+ stopInputRecording() {
254
+ recording = false;
255
+ },
256
+ isInputRecording() {
257
+ return recording;
258
+ },
259
+ injectAxis() {
260
+ unsupportedSource('injectAxis');
261
+ },
262
+ injectVector2() {
263
+ unsupportedSource('injectVector2');
264
+ },
265
+ injectPointerDelta() {
266
+ unsupportedSource('injectPointerDelta');
267
+ },
268
+ injectPointerPosition() {
269
+ unsupportedSource('injectPointerPosition');
270
+ },
271
+ };
272
+
273
+ registry.setInputActionsSource(binding.rootId, () =>
274
+ Object.entries(input.actions).map(([name, valueType]) => ({ name, valueType })),
275
+ );
276
+ registry.setInputTraceSource(binding.rootId, () => ({
277
+ version: 1,
278
+ seed: null,
279
+ fixedDt: game.loop.fixedDt,
280
+ ticks: [...trace],
281
+ }));
282
+ registry.setVirtualInputTarget(binding.rootId, target);
283
+ }
284
+
285
+ /** Project native registries onto the existing session debugger after mount. */
286
+ export function installNativeDebugBindings(
287
+ game: Game,
288
+ bindings: readonly NativeDebugBinding[],
289
+ ): void {
290
+ if (bindings.length === 0) return;
291
+ const registry = getDebugRegistry(game);
292
+ if (!registry)
293
+ throw new Error('Cannot install native debug bindings: mounted Game has no debug registry.');
294
+ for (const binding of bindings) {
295
+ if (!game.world(binding.rootId)) {
296
+ throw new Error(`Native debug binding names unmounted root "${binding.rootId}".`);
297
+ }
298
+ const root = registry.forRoot(binding.rootId);
299
+ for (const [name, read] of Object.entries(binding.debug.state ?? {})) {
300
+ root.registerStateProvider(name, read, { tier: 'assisted' });
301
+ }
302
+ for (const [name, command] of Object.entries(binding.debug.commands ?? {})) {
303
+ root.registerCommand(name, { locus: 'client' }, command);
304
+ }
305
+ if (binding.debug.input) installInput(game, registry, binding, binding.debug.input);
306
+ }
307
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Transient authoring identity for a native subject represented by an
3
+ * Object3D proxy.
4
+ *
5
+ * Some ecosystem-native objects are not Object3Ds themselves: Rapier bodies
6
+ * and joints are the first examples. A project library may already own a
7
+ * viewport proxy for one of those objects. This mark lets the ordinary
8
+ * Object3D authoring projection name and inspect that proxy without teaching
9
+ * the editor shell the library's nouns or creating a persisted sidecar.
10
+ *
11
+ * The mark is observation only. Its values are read from the native owner at
12
+ * the moment they are requested, and any authored change still belongs in the
13
+ * owning TS/TSX or ecosystem artifact.
14
+ */
15
+
16
+ import type * as THREE from 'three';
17
+ import { deleteUserData, getUserData, setUserData } from '../ecs/user-data';
18
+ import type { PropertyDescriptor } from './authoring';
19
+
20
+ export interface Object3DAuthoringSubjectField extends PropertyDescriptor {
21
+ /** Read the current value from the native subject. */
22
+ readonly value: () => unknown;
23
+ }
24
+
25
+ export interface Object3DAuthoringSubjectMark {
26
+ readonly label: string;
27
+ readonly kind: string;
28
+ readonly typeLabel?: string;
29
+ readonly fields?: () => readonly Object3DAuthoringSubjectField[];
30
+ /** Presentation-only selection feedback for proxies hidden by default. */
31
+ readonly selectionChanged?: (selected: boolean) => void;
32
+ }
33
+
34
+ export function object3DAuthoringSubjectOf(
35
+ object: THREE.Object3D | null | undefined,
36
+ ): Object3DAuthoringSubjectMark | null {
37
+ const mark = getUserData(object, 'authoringSubject');
38
+ return mark && typeof mark.label === 'string' && typeof mark.kind === 'string' ? mark : null;
39
+ }
40
+
41
+ export function setObject3DAuthoringSubject(
42
+ object: THREE.Object3D,
43
+ mark: Object3DAuthoringSubjectMark,
44
+ ): void {
45
+ setUserData(object, 'authoringSubject', mark);
46
+ }
47
+
48
+ export function clearObject3DAuthoringSubject(object: THREE.Object3D): void {
49
+ deleteUserData(object, 'authoringSubject');
50
+ }
@@ -1,26 +1,10 @@
1
1
  /**
2
2
  * RootAdapter — the host ⇄ runtime contract. A ROOT is *anything that mounts*.
3
3
  *
4
- * This is the interface the HOST depends on. It replaces the old
5
- * `{ setup, components }` shape: the host no longer knows about `GameSetupFn`
6
- * that is the internal of ONE implementer
7
- * (`SetupThreeRootAdapter`). First-party content and an unmodified external game
8
- * are peer implementers of THIS interface; the host has no branch on which.
9
- *
10
- * It was `GameAdapter` until WO-9 #60, and the rename is an ONTOLOGY fix, not
11
- * taste. Under D20 a *game* is the manifest's whole `roots` array — that is
12
- * what `runtime/game.ts`'s `Game` is. Every implementer of THIS interface
13
- * adapts exactly ONE root. So `Game` and `GameAdapter` sat at different levels
14
- * of the ontology while sharing a name-stem, which reads as though one were
15
- * the adapter for the other. The surrounding vocabulary had already moved:
16
- * `MountedRootBase`, `MountedRoot`, `MountedRootFor`, `RootStateObserver`,
17
- * `SurfaceAdapterFor`, `RootInstance`, `mountManifestRoots` — and, decisively,
18
- * the resolver that PRODUCES these (`resolveRootAdapter`, `RootAdapterSchema`,
19
- * `resolveThreeRootAdapter`) already said root. The interface was the last
20
- * holdout, out of step with its own resolver.
21
- *
22
- * The `K = 'three'` default below is KEPT deliberately (see the note on the
23
- * interface).
4
+ * This is the interface the host depends on. `GameSetupFn` is private to one
5
+ * implementer (`SetupThreeRootAdapter`); first-party content and unmodified
6
+ * external games are peer root adapters. A game is the manifest's complete
7
+ * roots array, while each adapter owns exactly one root.
24
8
  */
25
9
 
26
10
  import type { Container } from 'pixi.js';
@@ -62,12 +46,9 @@ export interface RootStateObserver {
62
46
  }
63
47
 
64
48
  /**
65
- * Everything a mounted world provides EXCEPT its render surface: THREE.Scene/
66
- * THREE.Camera typing is replaced with kind-tagged world surfaces. Split out
67
- * of the old flat
68
- * `MountedThreeRoot` so a non-threejs world can carry its own kind-appropriate
69
- * surface field (a pixi `stage`, a react `container`) instead of being
70
- * force-fit through THREE `scene`/`camera` fields it doesn't have.
49
+ * Everything a mounted world provides except its render surface. Tagged
50
+ * mounted-root variants carry their native surface fields: a Three scene and
51
+ * camera, a Pixi stage, or a DOM container.
71
52
  */
72
53
  export interface MountedRootBase {
73
54
  /**
@@ -103,11 +84,8 @@ export interface MountedRootBase {
103
84
  readonly observe?: RootStateObserver;
104
85
  }
105
86
 
106
- /** A live, mounted three world. The host obtains `scene`/`camera` to
107
- * render+author. Until WO-9 task #9 this type also had a `MountedGame` alias,
108
- * which every pre-T7.5 threejs call site named; the alias is deleted, because
109
- * what it aliased is one three ROOT and a game is the manifest's whole set of
110
- * roots (D20). */
87
+ /** A live, mounted Three world. The host obtains `scene`/`camera` to render
88
+ * and author it. */
111
89
  export interface MountedThreeRoot extends MountedRootBase {
112
90
  readonly kind: 'three';
113
91
  /** The live scene + camera the editor inspects/renders for authoring. */
@@ -151,8 +129,7 @@ export interface MountedReactRoot extends MountedRootBase {
151
129
  readonly container: HTMLElement;
152
130
  }
153
131
 
154
- /** Every kind of live, mounted world (T7.5) — the union `RootInstance.mounted`
155
- * is typed against now, replacing the THREE-only `MountedThreeRoot`. */
132
+ /** Every kind of live, mounted world. */
156
133
  export type MountedRoot = MountedThreeRoot | MountedPixiRoot | MountedReactRoot;
157
134
 
158
135
  /** Map a {@link AdapterSurface} to its mounted-world shape (mirrors `NodeOf`/
@@ -167,30 +144,22 @@ export type MountedRootFor<K extends AdapterSurface> = K extends 'three'
167
144
  : never;
168
145
 
169
146
  /**
170
- * The interface every game implements to run on the host. Generic over
171
- * {@link AdapterSurface} (T7.5) so a non-threejs implementer's `mount` returns
172
- * its OWN kind-tagged surface instead of being cast through the three shape
173
- * defaults to `'three'` so every pre-T7.5 implementer/call site
174
- * (`RootAdapter`, unparameterized) keeps compiling unchanged.
147
+ * The interface every root implements to run on the host. Generic over
148
+ * {@link AdapterSurface} so `mount` receives and returns its native surface
149
+ * types. Bare `RootAdapter` denotes the first-party Three surface.
175
150
  *
176
- * `K` parameterizes BOTH halves of `mount` (P-8): until then only the RETURN
177
- * type was kind-tagged, while the PARAMETER was always the three-shaped
178
- * `ThreeHostContext` so a `RootAdapter<'canvas'>` was, by its own type, handed a
179
- * `THREE.WebGLRenderer` it cannot use, and the production Pixi/React adapters
180
- * could not be `RootAdapter`s at all (they were separate structural types over
181
- * `CanvasHostContext`/`DomHostContext`, and `RootAdapter<'canvas'>` existed only in
182
- * tests). With `HostContextFor<K>` the surface tag picks the context type, and
183
- * those production adapters are now genuine `RootAdapter<K>` subtypes —
151
+ * `K` parameterizes both halves of `mount`: `HostContextFor<K>` selects the
152
+ * host context and `MountedRootFor<K>` selects the mounted result. Production
153
+ * adapters are genuine `RootAdapter<K>` subtypes
184
154
  * `Pixi2DRootAdapter extends RootAdapter<'canvas'>`, `ReactRootAdapter extends
185
155
  * RootAdapter<'dom'>` (`runtime/create-runtime.ts`).
186
156
  */
187
157
  /**
188
- * On the `= 'three'` default (WO-9 #60 considered removing it, and did not):
189
- * this default is type-level only: an author who writes bare `RootAdapter` for
158
+ * The `= 'three'` default is type-level only: an author who writes bare
159
+ * `RootAdapter` for
190
160
  * a pixi root gets a compile error on `mount`'s return type, because
191
161
  * `MountedRootFor<'three'>` demands `scene`/`camera` a `MountedPixiRoot` has
192
- * not got. Wrong-by-default is caught by tsc, so removing this default would
193
- * be churn across ~125 bare call sites for no defect it could catch.
162
+ * not got.
194
163
  */
195
164
  export interface RootAdapter<K extends AdapterSurface = 'three'> {
196
165
  /** Stable id (telemetry/registry/conformance). */
@@ -203,17 +172,13 @@ export interface RootAdapter<K extends AdapterSurface = 'three'> {
203
172
  /**
204
173
  * One surface tag paired with an adapter typed FOR that surface (WO-34).
205
174
  *
206
- * Why this exists: a resolver that turns a manifest root into a mountable
175
+ * A resolver that turns a manifest root into a mountable
207
176
  * adapter handles every surface in one function, so its return type has to
208
177
  * span all three. Returning the bare union `RootAdapter<'three'> |
209
178
  * RootAdapter<'canvas'> | RootAdapter<'dom'>` does NOT work — a union of
210
179
  * function types INTERSECTS its parameters, so `.mount(host)` becomes
211
180
  * uncallable with any single host value (no one object is simultaneously a
212
- * `ThreeHostContext`, a `CanvasHostContext` and a `DomHostContext`). The historical
213
- * workaround was to declare the whole channel `RootAdapter<'three'>` and
214
- * re-assert the real kind at each call site with `as unknown as` — kind
215
- * ERASURE: the surface tag the manifest already knew was thrown away and
216
- * guessed back.
181
+ * `ThreeHostContext`, a `CanvasHostContext` and a `DomHostContext`).
217
182
  *
218
183
  * Carrying the tag ALONGSIDE the adapter fixes that structurally: this is a
219
184
  * discriminated union on `surface`, so `switch (resolved.surface)` narrows
@@ -0,0 +1,56 @@
1
+ /** Exhaustive proof requirements for substrate mount/lifecycle seams. */
2
+
3
+ import type { AdapterSurface } from './adapter-surface';
4
+ import type {
5
+ MountedPixiRoot,
6
+ MountedReactRoot,
7
+ MountedRootBase,
8
+ MountedThreeRoot,
9
+ RootAdapter,
10
+ RootStateObserver,
11
+ } from './root-adapter';
12
+ import { defineSeamShape } from './seam-evidence';
13
+
14
+ export const ROOT_ADAPTER_SHAPE = defineSeamShape<RootAdapter<AdapterSurface>>()({
15
+ id: { optional: false, kind: 'value', required: 'shape' },
16
+ mount: { optional: false, kind: 'function', required: 'effect' },
17
+ });
18
+
19
+ export const MOUNTED_ROOT_BASE_SHAPE = defineSeamShape<MountedRootBase>()({
20
+ drivesOwnLoop: { optional: false, kind: 'value', required: 'shape' },
21
+ update: { optional: true, kind: 'function', required: 'effect' },
22
+ fixedUpdate: { optional: true, kind: 'function', required: 'effect' },
23
+ setPaused: { optional: true, kind: 'function', required: 'effect' },
24
+ step: { optional: true, kind: 'function', required: 'effect' },
25
+ resize: { optional: true, kind: 'function', required: 'effect' },
26
+ dispose: { optional: false, kind: 'function', required: 'effect' },
27
+ authoring: { optional: true, kind: 'value', required: 'operation' },
28
+ systems: { optional: true, kind: 'value', required: 'operation' },
29
+ observe: { optional: true, kind: 'value', required: 'effect' },
30
+ });
31
+
32
+ export const ROOT_STATE_OBSERVER_SHAPE = defineSeamShape<RootStateObserver>()({
33
+ subscribe: { optional: false, kind: 'function', required: 'effect' },
34
+ snapshot: { optional: false, kind: 'function', required: 'operation' },
35
+ });
36
+
37
+ type ThreeSurface = Omit<MountedThreeRoot, keyof MountedRootBase>;
38
+ type CanvasSurface = Omit<MountedPixiRoot, keyof MountedRootBase>;
39
+ type DomSurface = Omit<MountedReactRoot, keyof MountedRootBase>;
40
+
41
+ export const MOUNTED_THREE_SURFACE_SHAPE = defineSeamShape<ThreeSurface>()({
42
+ kind: { optional: false, kind: 'value', required: 'shape' },
43
+ scene: { optional: false, kind: 'value', required: 'effect' },
44
+ camera: { optional: false, kind: 'value', required: 'effect' },
45
+ rendererConfig: { optional: true, kind: 'value', required: 'shape' },
46
+ });
47
+
48
+ export const MOUNTED_CANVAS_SURFACE_SHAPE = defineSeamShape<CanvasSurface>()({
49
+ kind: { optional: false, kind: 'value', required: 'shape' },
50
+ stage: { optional: false, kind: 'value', required: 'effect' },
51
+ });
52
+
53
+ export const MOUNTED_DOM_SURFACE_SHAPE = defineSeamShape<DomSurface>()({
54
+ kind: { optional: false, kind: 'value', required: 'shape' },
55
+ container: { optional: false, kind: 'value', required: 'effect' },
56
+ });