@vgai/engine 0.5.14 → 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/manifest/schema.js +1 -1
- 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 +8 -8
- package/schemas/vgai-project.schema.json +1 -1
- 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/manifest/schema.ts +1 -1
- 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,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.
|
|
@@ -152,6 +158,109 @@ function propertyKey(property: ts.ObjectLiteralElementLike): string | undefined
|
|
|
152
158
|
return undefined;
|
|
153
159
|
}
|
|
154
160
|
|
|
161
|
+
/**
|
|
162
|
+
* The identifier the entrypoint INDEXES the selection table with — the
|
|
163
|
+
* `activeScene` in `scenes[activeScene]`.
|
|
164
|
+
*
|
|
165
|
+
* `undefined` when the module never indexes the table, or indexes it with
|
|
166
|
+
* anything other than one single identifier: both are honest "cannot be read",
|
|
167
|
+
* and the caller turns each into its own note.
|
|
168
|
+
*/
|
|
169
|
+
function indexingIdentifier(sf: ts.SourceFile, selection: string): string | undefined | null {
|
|
170
|
+
const names = new Set<string>();
|
|
171
|
+
let unreadable = false;
|
|
172
|
+
const visit = (node: ts.Node): void => {
|
|
173
|
+
if (
|
|
174
|
+
ts.isElementAccessExpression(node) &&
|
|
175
|
+
ts.isIdentifier(node.expression) &&
|
|
176
|
+
node.expression.text === selection
|
|
177
|
+
) {
|
|
178
|
+
if (ts.isIdentifier(node.argumentExpression)) names.add(node.argumentExpression.text);
|
|
179
|
+
else unreadable = true;
|
|
180
|
+
}
|
|
181
|
+
ts.forEachChild(node, visit);
|
|
182
|
+
};
|
|
183
|
+
ts.forEachChild(sf, visit);
|
|
184
|
+
if (names.size === 0 && !unreadable) return undefined; // never indexed at all
|
|
185
|
+
if (names.size !== 1 || unreadable) return null; // indexed, but not readably
|
|
186
|
+
return [...names][0];
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* The string a module-level `const <name> = '…'` binds, or `undefined`.
|
|
191
|
+
*
|
|
192
|
+
* `const` and a STRING LITERAL are both required: a `let` can be reassigned and
|
|
193
|
+
* a computed initializer is not a fact the source states, so neither can be
|
|
194
|
+
* reported as the key the game executes. A type annotation
|
|
195
|
+
* (`const activeScene: keyof typeof scenes = 'main'`) is irrelevant here — the
|
|
196
|
+
* initializer is what runs.
|
|
197
|
+
*/
|
|
198
|
+
function moduleLevelString(sf: ts.SourceFile, name: string): string | undefined {
|
|
199
|
+
for (const statement of sf.statements) {
|
|
200
|
+
if (!ts.isVariableStatement(statement)) continue;
|
|
201
|
+
if ((statement.declarationList.flags & ts.NodeFlags.Const) === 0) continue;
|
|
202
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
203
|
+
if (!ts.isIdentifier(declaration.name) || declaration.name.text !== name) continue;
|
|
204
|
+
const initializer = declaration.initializer;
|
|
205
|
+
if (initializer && ts.isStringLiteral(initializer)) return initializer.text;
|
|
206
|
+
return undefined;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return undefined;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* WHICH key the entrypoint mounts at its slot right now, read off the source
|
|
214
|
+
* the game itself executes.
|
|
215
|
+
*
|
|
216
|
+
* Every failure is a NOTE and answers `undefined`: an unread active key costs
|
|
217
|
+
* a retitle, while a guessed one would open the wrong composition as the
|
|
218
|
+
* region's own document.
|
|
219
|
+
*/
|
|
220
|
+
function activeSelectionKey(
|
|
221
|
+
entrypoint: EntrypointSource,
|
|
222
|
+
sf: ts.SourceFile,
|
|
223
|
+
selection: string,
|
|
224
|
+
keys: readonly string[],
|
|
225
|
+
notes: string[],
|
|
226
|
+
): string | undefined {
|
|
227
|
+
const identifier = indexingIdentifier(sf, selection);
|
|
228
|
+
if (identifier === undefined) {
|
|
229
|
+
notes.push(
|
|
230
|
+
`scenesFromEntrypointSelection: ${entrypoint.path} never indexes \`${selection}\`, so ` +
|
|
231
|
+
'which of its compositions the entrypoint mounts is not stated in the source and no ' +
|
|
232
|
+
'entry is settled as the active one.',
|
|
233
|
+
);
|
|
234
|
+
return undefined;
|
|
235
|
+
}
|
|
236
|
+
if (identifier === null) {
|
|
237
|
+
notes.push(
|
|
238
|
+
`scenesFromEntrypointSelection: ${entrypoint.path} indexes \`${selection}\` with ` +
|
|
239
|
+
'something other than one single identifier, so the key it mounts cannot be read ' +
|
|
240
|
+
'statically.',
|
|
241
|
+
);
|
|
242
|
+
return undefined;
|
|
243
|
+
}
|
|
244
|
+
const key = moduleLevelString(sf, identifier);
|
|
245
|
+
if (key === undefined) {
|
|
246
|
+
notes.push(
|
|
247
|
+
`scenesFromEntrypointSelection: ${entrypoint.path} indexes \`${selection}\` with ` +
|
|
248
|
+
`\`${identifier}\`, which is not a module-level \`const\` bound to a string literal, ` +
|
|
249
|
+
'so the key it mounts cannot be read statically.',
|
|
250
|
+
);
|
|
251
|
+
return undefined;
|
|
252
|
+
}
|
|
253
|
+
if (!keys.includes(key)) {
|
|
254
|
+
notes.push(
|
|
255
|
+
`scenesFromEntrypointSelection: ${entrypoint.path} mounts \`${selection}\` under ` +
|
|
256
|
+
`"${key}", which is not a key of that table. The entrypoint and its own selection ` +
|
|
257
|
+
'table disagree.',
|
|
258
|
+
);
|
|
259
|
+
return undefined;
|
|
260
|
+
}
|
|
261
|
+
return key;
|
|
262
|
+
}
|
|
263
|
+
|
|
155
264
|
/** The identifier a selection entry's value names, if it names one at all. */
|
|
156
265
|
function propertyIdentifier(property: ts.ObjectLiteralElementLike): string | undefined {
|
|
157
266
|
if (ts.isShorthandPropertyAssignment(property)) return property.name.text;
|
|
@@ -219,14 +328,17 @@ function degenerateEntry(entrypoint: EntrypointSource, sf: ts.SourceFile): Scene
|
|
|
219
328
|
};
|
|
220
329
|
}
|
|
221
330
|
|
|
222
|
-
/**
|
|
331
|
+
/**
|
|
332
|
+
* One entry per key of the entrypoint's own selection table, plus WHICH of
|
|
333
|
+
* them the entrypoint currently mounts at the slot (`active`).
|
|
334
|
+
*/
|
|
223
335
|
function selectionEntries(
|
|
224
336
|
entrypoint: EntrypointSource,
|
|
225
337
|
sf: ts.SourceFile,
|
|
226
338
|
selection: string,
|
|
227
339
|
resolveModule: ScenesFromEntrypointSelectionInput['resolveModule'],
|
|
228
340
|
notes: string[],
|
|
229
|
-
): SceneEntry[] {
|
|
341
|
+
): { entries: SceneEntry[]; active: string | undefined } {
|
|
230
342
|
const table = selectionTable(sf, selection);
|
|
231
343
|
if (!table) {
|
|
232
344
|
notes.push(
|
|
@@ -234,11 +346,12 @@ function selectionEntries(
|
|
|
234
346
|
`\`const ${selection} = { … }\` selection table. Nothing is assumed in its ` +
|
|
235
347
|
'place — fix the adapter’s `selection` or the entrypoint.',
|
|
236
348
|
);
|
|
237
|
-
return [];
|
|
349
|
+
return { entries: [], active: undefined };
|
|
238
350
|
}
|
|
239
351
|
|
|
240
352
|
const bindings = importedBindings(sf);
|
|
241
|
-
const
|
|
353
|
+
const keys: string[] = [];
|
|
354
|
+
const built: { key: string; label: string; source: SceneSource | undefined }[] = [];
|
|
242
355
|
for (const property of table.properties) {
|
|
243
356
|
const key = propertyKey(property);
|
|
244
357
|
if (key === undefined) {
|
|
@@ -249,23 +362,35 @@ function selectionEntries(
|
|
|
249
362
|
continue;
|
|
250
363
|
}
|
|
251
364
|
const identifier = propertyIdentifier(property);
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
resolveModule,
|
|
256
|
-
);
|
|
257
|
-
entries.push({
|
|
258
|
-
id: key,
|
|
365
|
+
keys.push(key);
|
|
366
|
+
built.push({
|
|
367
|
+
key,
|
|
259
368
|
label: identifier ?? key,
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
finder: 'scenesFromEntrypointSelection',
|
|
369
|
+
source: sourceFor(
|
|
370
|
+
identifier ? bindings.get(identifier) : undefined,
|
|
371
|
+
entrypoint.path,
|
|
372
|
+
resolveModule,
|
|
373
|
+
),
|
|
266
374
|
});
|
|
267
375
|
}
|
|
268
|
-
|
|
376
|
+
|
|
377
|
+
const active = activeSelectionKey(entrypoint, sf, selection, keys, notes);
|
|
378
|
+
const entries = built.map(({ key, label, source }) => ({
|
|
379
|
+
id: key,
|
|
380
|
+
label,
|
|
381
|
+
kind: 'scene' as const,
|
|
382
|
+
region: entrypoint.regionId,
|
|
383
|
+
authorable: true,
|
|
384
|
+
reach: {
|
|
385
|
+
kind: 'entrypoint-selection' as const,
|
|
386
|
+
selection,
|
|
387
|
+
key,
|
|
388
|
+
...(key === active ? { active: true } : {}),
|
|
389
|
+
},
|
|
390
|
+
...(source ? { source } : {}),
|
|
391
|
+
finder: 'scenesFromEntrypointSelection' as const,
|
|
392
|
+
}));
|
|
393
|
+
return { entries, active };
|
|
269
394
|
}
|
|
270
395
|
|
|
271
396
|
/**
|
|
@@ -280,6 +405,7 @@ export function scenesFromEntrypointSelection(
|
|
|
280
405
|
): FinderResult {
|
|
281
406
|
const notes: string[] = [];
|
|
282
407
|
const entries: SceneEntry[] = [];
|
|
408
|
+
const active: string[] = [];
|
|
283
409
|
|
|
284
410
|
for (const entrypoint of targetedEntrypoints(params, input.entrypoints, notes)) {
|
|
285
411
|
if (entrypoint.source === null) {
|
|
@@ -290,15 +416,20 @@ export function scenesFromEntrypointSelection(
|
|
|
290
416
|
continue;
|
|
291
417
|
}
|
|
292
418
|
const sf = parse(entrypoint.path, entrypoint.source);
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
);
|
|
419
|
+
if (params.selection === undefined) {
|
|
420
|
+
entries.push(degenerateEntry(entrypoint, sf));
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
const found = selectionEntries(entrypoint, sf, params.selection, input.resolveModule, notes);
|
|
424
|
+
entries.push(...found.entries);
|
|
425
|
+
if (found.active !== undefined) active.push(found.active);
|
|
298
426
|
}
|
|
299
427
|
|
|
300
|
-
//
|
|
301
|
-
//
|
|
302
|
-
|
|
303
|
-
|
|
428
|
+
// The ACTIVE key settles the default outright: the entrypoint states which
|
|
429
|
+
// composition it mounts, so this is the finder READING an answer rather than
|
|
430
|
+
// choosing between candidates. Failing that, one candidate settles it; two or
|
|
431
|
+
// more is a choice, and a choice belongs to the adapter author.
|
|
432
|
+
const settled =
|
|
433
|
+
active.length === 1 ? active[0] : entries.length === 1 ? entries[0]?.id : undefined;
|
|
434
|
+
return settled !== undefined ? { entries, default: settled, notes } : { entries, notes };
|
|
304
435
|
}
|
|
@@ -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
|
-
|
|
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
|
}
|
package/src/adapter/index.ts
CHANGED
|
@@ -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
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
|
104
|
-
|
|
105
|
-
|
|
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
|
|
147
|
-
|
|
148
|
-
|
|
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;
|