@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.
- package/dist/adapter/adapter-module.d.ts +228 -17
- package/dist/adapter/adapter-module.d.ts.map +1 -1
- package/dist/adapter/adapter-module.js +224 -42
- package/dist/adapter/authoring-seam-contract.d.ts +530 -0
- package/dist/adapter/authoring-seam-contract.d.ts.map +1 -0
- package/dist/adapter/authoring-seam-contract.js +162 -0
- package/dist/adapter/authoring.d.ts +237 -50
- package/dist/adapter/authoring.d.ts.map +1 -1
- package/dist/adapter/authoring.js +2 -1
- package/dist/adapter/entrypoint-selection-source.d.ts +35 -0
- package/dist/adapter/entrypoint-selection-source.d.ts.map +1 -0
- package/dist/adapter/entrypoint-selection-source.js +151 -0
- package/dist/adapter/finders/scenes-from-entrypoint-selection.d.ts +7 -1
- package/dist/adapter/finders/scenes-from-entrypoint-selection.d.ts.map +1 -1
- package/dist/adapter/finders/scenes-from-entrypoint-selection.js +142 -21
- package/dist/adapter/first-party-systems.d.ts +13 -3
- package/dist/adapter/first-party-systems.d.ts.map +1 -1
- package/dist/adapter/first-party-systems.js +10 -3
- package/dist/adapter/index.d.ts +7 -2
- package/dist/adapter/index.d.ts.map +1 -1
- package/dist/adapter/index.js +4 -0
- package/dist/adapter/ingest/contract-system-adapters.d.ts +1 -1
- package/dist/adapter/ingest/contract-system-adapters.d.ts.map +1 -1
- package/dist/adapter/ingest/contract-system-adapters.js +13 -25
- package/dist/adapter/ingest/game-contract-seams.d.ts +214 -0
- package/dist/adapter/ingest/game-contract-seams.d.ts.map +1 -0
- package/dist/adapter/ingest/game-contract-seams.js +62 -0
- package/dist/adapter/ingest/game-contract.d.ts +5 -3
- package/dist/adapter/ingest/game-contract.d.ts.map +1 -1
- package/dist/adapter/ingest/game-contract.js +4 -1
- package/dist/adapter/ingest/merge-debug-adapters.d.ts +52 -0
- package/dist/adapter/ingest/merge-debug-adapters.d.ts.map +1 -0
- package/dist/adapter/ingest/merge-debug-adapters.js +141 -0
- package/dist/adapter/ingest/observation-debug-adapter.d.ts +56 -0
- package/dist/adapter/ingest/observation-debug-adapter.d.ts.map +1 -0
- package/dist/adapter/ingest/observation-debug-adapter.js +131 -0
- package/dist/adapter/ingest/scene-capture.d.ts +38 -13
- package/dist/adapter/ingest/scene-capture.d.ts.map +1 -1
- package/dist/adapter/ingest/scene-capture.js +101 -24
- package/dist/adapter/ingest/visible-capture-window.d.ts +22 -5
- package/dist/adapter/ingest/visible-capture-window.d.ts.map +1 -1
- package/dist/adapter/ingest/visible-capture-window.js +46 -7
- package/dist/adapter/native-debug-module.d.ts +43 -0
- package/dist/adapter/native-debug-module.d.ts.map +1 -0
- package/dist/adapter/native-debug-module.js +230 -0
- package/dist/adapter/object3d-authoring-subject.d.ts +32 -0
- package/dist/adapter/object3d-authoring-subject.d.ts.map +1 -0
- package/dist/adapter/object3d-authoring-subject.js +25 -0
- package/dist/adapter/root-adapter.d.ts +21 -56
- package/dist/adapter/root-adapter.d.ts.map +1 -1
- package/dist/adapter/root-adapter.js +4 -20
- package/dist/adapter/root-seam-contract.d.ts +124 -0
- package/dist/adapter/root-seam-contract.d.ts.map +1 -0
- package/dist/adapter/root-seam-contract.js +36 -0
- package/dist/adapter/seam-evidence.d.ts +118 -0
- package/dist/adapter/seam-evidence.d.ts.map +1 -0
- package/dist/adapter/seam-evidence.js +231 -0
- package/dist/adapter/setup-three-root-adapter.d.ts +2 -3
- package/dist/adapter/setup-three-root-adapter.d.ts.map +1 -1
- package/dist/adapter/setup-three-root-adapter.js +2 -3
- package/dist/adapter/system-adapter.d.ts +6 -1
- package/dist/adapter/system-adapter.d.ts.map +1 -1
- package/dist/adapter/system-seam-contract.d.ts +354 -0
- package/dist/adapter/system-seam-contract.d.ts.map +1 -0
- package/dist/adapter/system-seam-contract.js +95 -0
- package/dist/animation/xstate-animation-binding.d.ts +6 -0
- package/dist/animation/xstate-animation-binding.d.ts.map +1 -1
- package/dist/animation/xstate-animation-binding.js +142 -4
- package/dist/asset-formats/collider.d.ts +1 -1
- package/dist/asset-formats/mesh.d.ts +2 -2
- package/dist/asset-formats/particles.d.ts +1 -1
- package/dist/canvas-react/engine-bridge.d.ts +6 -6
- package/dist/canvas-react/engine-bridge.d.ts.map +1 -1
- package/dist/canvas-react/engine-bridge.js +2 -2
- package/dist/canvas-react/index.d.ts +2 -2
- package/dist/canvas-react/index.d.ts.map +1 -1
- package/dist/canvas-react/index.js +1 -1
- package/dist/canvas-react/pixi-react-adapter.d.ts +15 -9
- package/dist/canvas-react/pixi-react-adapter.d.ts.map +1 -1
- package/dist/canvas-react/pixi-react-adapter.js +43 -29
- package/dist/canvas-react/pixi-react-root-factory.d.ts.map +1 -1
- package/dist/canvas-react/pixi-react-root-factory.js +4 -0
- package/dist/canvas-react/world-context.d.ts +22 -12
- package/dist/canvas-react/world-context.d.ts.map +1 -1
- package/dist/canvas-react/world-context.js +12 -51
- package/dist/ecs/user-data.d.ts +5 -0
- package/dist/ecs/user-data.d.ts.map +1 -1
- package/dist/ecs/user-data.js +3 -0
- package/dist/input/input-manager.d.ts +49 -0
- package/dist/input/input-manager.d.ts.map +1 -1
- package/dist/input/input-manager.js +127 -0
- package/dist/manifest/schema.d.ts +9 -9
- package/dist/pixi/authoring.d.ts.map +1 -1
- package/dist/pixi/authoring.js +220 -1
- package/dist/pixi/ingest.d.ts +2 -1
- package/dist/pixi/ingest.d.ts.map +1 -1
- package/dist/pixi/ingest.js +4 -2
- package/dist/pixi/scene-capture.d.ts +9 -1
- package/dist/pixi/scene-capture.d.ts.map +1 -1
- package/dist/pixi/scene-capture.js +9 -6
- package/dist/render/particles-factory.d.ts +13 -6
- package/dist/render/particles-factory.d.ts.map +1 -1
- package/dist/render/particles-factory.js +6 -3
- package/dist/runtime/create-runtime.d.ts +23 -46
- package/dist/runtime/create-runtime.d.ts.map +1 -1
- package/dist/runtime/create-runtime.js +22 -67
- package/dist/runtime/game.d.ts +29 -71
- package/dist/runtime/game.d.ts.map +1 -1
- package/dist/runtime/game.js +15 -54
- package/dist/runtime/mount-game.d.ts.map +1 -1
- package/dist/runtime/mount-game.js +12 -1
- package/dist/runtime/mount-manifest.d.ts.map +1 -1
- package/dist/runtime/mount-manifest.js +4 -5
- package/dist/runtime/types.d.ts +18 -0
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/setup/setup-renderer.d.ts +3 -4
- package/dist/setup/setup-renderer.d.ts.map +1 -1
- package/dist/setup/setup-renderer.js +3 -4
- package/dist/world3d-react/engine-bridge.d.ts +1 -1
- package/dist/world3d-react/engine-bridge.js +1 -1
- package/dist/world3d-react/r3f-adapter.d.ts +7 -0
- package/dist/world3d-react/r3f-adapter.d.ts.map +1 -1
- package/dist/world3d-react/r3f-adapter.js +50 -21
- package/dist/world3d-react/r3f-root-factory.d.ts.map +1 -1
- package/dist/world3d-react/r3f-root-factory.js +4 -0
- package/dist/world3d-react/world-context.d.ts +9 -3
- package/dist/world3d-react/world-context.d.ts.map +1 -1
- package/dist/world3d-react/world-context.js +5 -18
- package/package.json +1 -1
- package/schemas/engine-capabilities.json +7 -7
- package/src/adapter/adapter-module.ts +407 -50
- package/src/adapter/authoring-seam-contract.ts +211 -0
- package/src/adapter/authoring.ts +242 -50
- package/src/adapter/entrypoint-selection-source.ts +159 -0
- package/src/adapter/finders/scenes-from-entrypoint-selection.ts +159 -28
- package/src/adapter/first-party-systems.ts +17 -3
- package/src/adapter/index.ts +51 -1
- package/src/adapter/ingest/contract-system-adapters.ts +26 -30
- package/src/adapter/ingest/game-contract-seams.ts +85 -0
- package/src/adapter/ingest/game-contract.ts +9 -4
- package/src/adapter/ingest/merge-debug-adapters.ts +193 -0
- package/src/adapter/ingest/observation-debug-adapter.ts +158 -0
- package/src/adapter/ingest/scene-capture.ts +126 -23
- package/src/adapter/ingest/visible-capture-window.ts +63 -9
- package/src/adapter/native-debug-module.ts +307 -0
- package/src/adapter/object3d-authoring-subject.ts +50 -0
- package/src/adapter/root-adapter.ts +21 -56
- package/src/adapter/root-seam-contract.ts +56 -0
- package/src/adapter/seam-evidence.ts +336 -0
- package/src/adapter/setup-three-root-adapter.ts +4 -6
- package/src/adapter/system-adapter.ts +6 -1
- package/src/adapter/system-seam-contract.ts +121 -0
- package/src/animation/xstate-animation-binding.ts +151 -4
- package/src/canvas-react/engine-bridge.ts +6 -6
- package/src/canvas-react/index.ts +2 -1
- package/src/canvas-react/pixi-react-adapter.tsx +55 -34
- package/src/canvas-react/pixi-react-root-factory.tsx +4 -0
- package/src/canvas-react/world-context.ts +26 -101
- package/src/ecs/user-data.ts +5 -0
- package/src/input/input-manager.ts +122 -0
- package/src/pixi/authoring.ts +236 -1
- package/src/pixi/ingest.ts +9 -2
- package/src/pixi/scene-capture.ts +20 -5
- package/src/render/particles-factory.ts +16 -6
- package/src/runtime/create-runtime.ts +41 -106
- package/src/runtime/game.ts +41 -111
- package/src/runtime/mount-game.ts +19 -1
- package/src/runtime/mount-manifest.ts +4 -5
- package/src/runtime/types.ts +22 -0
- package/src/setup/setup-renderer.ts +3 -4
- package/src/world3d-react/engine-bridge.ts +1 -1
- package/src/world3d-react/r3f-adapter.tsx +64 -24
- package/src/world3d-react/r3f-root-factory.tsx +4 -0
- 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
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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
|
|
66
|
-
*
|
|
67
|
-
*
|
|
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
|
|
107
|
-
*
|
|
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
|
|
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
|
|
171
|
-
* {@link AdapterSurface}
|
|
172
|
-
*
|
|
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
|
|
177
|
-
*
|
|
178
|
-
*
|
|
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
|
-
*
|
|
189
|
-
*
|
|
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.
|
|
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
|
-
*
|
|
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`).
|
|
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
|
+
});
|