@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
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
* the ONE game-scoped registry (`getDebugRegistry(host.game)`), so a
|
|
12
12
|
* component's effect can `ctx.debug.registerStateProvider(...)` and be
|
|
13
13
|
* visible to `vgai eval`/the editor's debug panels.
|
|
14
|
-
* 2. **`ctx.input` is the game-owned `InputManager
|
|
14
|
+
* 2. **`ctx.input` is the game-owned `InputManager` when a Game hosts the
|
|
15
|
+
* root** (`host.game.input` — the
|
|
15
16
|
* same instance every sibling root shares), and this module registers the
|
|
16
17
|
* per-world debug-registry seams (`setVirtualInputTarget`/
|
|
17
18
|
* `setInputActionsSource`/`setInputTraceSource`) that make `game.input.*`
|
|
@@ -19,13 +20,14 @@
|
|
|
19
20
|
* `DEBUG_INPUT_UNAVAILABLE`. The project's input map loads through
|
|
20
21
|
* `Game.loadInputMap` (the game-owned, load-once path every root shares).
|
|
21
22
|
* A missing/unparseable map degrades LOUDLY without failing the mount — a
|
|
22
|
-
* canvas world with no declared actions is legal.
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
23
|
+
* canvas world with no declared actions is legal. A bare/foreign mount
|
|
24
|
+
* receives `null`, visibly in the type, rather than a throwing getter.
|
|
25
|
+
* 3. **Absent subsystems are absent from the type.** A canvas world builds no
|
|
26
|
+
* THREE scene/camera, Rapier world, composer, first-party audio/particles/
|
|
27
|
+
* debug-draw, or Three-flavoured `AssetCache`. Its explicit
|
|
28
|
+
* {@link CanvasGameContext} cannot name those fields, so misuse fails at
|
|
29
|
+
* the call site during typechecking instead of from a throwing getter at
|
|
30
|
+
* runtime.
|
|
29
31
|
*
|
|
30
32
|
* The Pixi handles themselves are deliberately NOT re-exposed here: a component
|
|
31
33
|
* reaches the live `Application` (and therefore `app.stage`, `app.renderer`,
|
|
@@ -42,6 +44,7 @@ import {
|
|
|
42
44
|
DEFAULT_SEEDED_RANDOM_SEED,
|
|
43
45
|
getSeededRandom,
|
|
44
46
|
} from '../core/seeded-random';
|
|
47
|
+
import type { SimClock } from '../core/sim-clock';
|
|
45
48
|
import { createSimClock, getSimClock, type SimClockInternal } from '../core/sim-clock';
|
|
46
49
|
import { createSystemRunner } from '../core/system-runner';
|
|
47
50
|
import {
|
|
@@ -49,7 +52,7 @@ import {
|
|
|
49
52
|
type DebugRegistry,
|
|
50
53
|
getDebugRegistry,
|
|
51
54
|
} from '../runtime/debug-registry';
|
|
52
|
-
import type {
|
|
55
|
+
import type { ComponentContextBase } from '../runtime/types';
|
|
53
56
|
|
|
54
57
|
/** The conventional input-map location every scaffolded project ships. Shared
|
|
55
58
|
* verbatim with the three lane — a project has ONE input map, whatever surface
|
|
@@ -70,12 +73,20 @@ export interface CanvasRootContextOptions {
|
|
|
70
73
|
readonly inputMapPath?: string | null | undefined;
|
|
71
74
|
}
|
|
72
75
|
|
|
76
|
+
/** The engine services a Canvas component genuinely receives. Pixi's own
|
|
77
|
+
* `useApplication()` and `Assets` remain the doors to rendering and assets.
|
|
78
|
+
* `disposeAfter` is excluded because it accepts a `THREE.Object3D`; Canvas
|
|
79
|
+
* code schedules `displayObject.destroy()` with `clock.after()` instead. */
|
|
80
|
+
export type CanvasGameContext = ComponentContextBase & {
|
|
81
|
+
readonly clock: Omit<SimClock, 'disposeAfter'>;
|
|
82
|
+
};
|
|
83
|
+
|
|
73
84
|
/** What {@link createCanvasRootContext} returns — the engine runtime one canvas
|
|
74
85
|
* world owns, plus the wiring hooks the adapter's `mount()` drives. */
|
|
75
86
|
export interface CanvasRootRuntime {
|
|
76
|
-
/** The
|
|
77
|
-
*
|
|
78
|
-
readonly ctx:
|
|
87
|
+
/** The explicit Canvas context this world's components read through
|
|
88
|
+
* `useGameContext`. See {@link CanvasGameContext}. */
|
|
89
|
+
readonly ctx: CanvasGameContext;
|
|
79
90
|
/** The world-local phase runner `mount().update(dt)` ticks. */
|
|
80
91
|
readonly systems: ReturnType<typeof createSystemRunner>;
|
|
81
92
|
/** The mounted world's `SystemAdapters` bag (`mounted.systems`) —
|
|
@@ -95,32 +106,6 @@ export interface CanvasRootRuntime {
|
|
|
95
106
|
dispose(): void;
|
|
96
107
|
}
|
|
97
108
|
|
|
98
|
-
/** Install a throwing getter for a `GameContext` field this world genuinely
|
|
99
|
-
* does not build — a component reaching for it fails LOUDLY at its own call
|
|
100
|
-
* site, naming the limitation, instead of reading `undefined` and breaking
|
|
101
|
-
* somewhere downstream. Non-enumerable so spreads/serialization of the ctx
|
|
102
|
-
* never trip it. */
|
|
103
|
-
function defineAbsentCtxField(
|
|
104
|
-
target: Record<string, unknown>,
|
|
105
|
-
worldId: string,
|
|
106
|
-
key: string,
|
|
107
|
-
why: string,
|
|
108
|
-
): void {
|
|
109
|
-
Object.defineProperty(target, key, {
|
|
110
|
-
configurable: true,
|
|
111
|
-
enumerable: false,
|
|
112
|
-
get(): never {
|
|
113
|
-
throw new Error(
|
|
114
|
-
`canvas world "${worldId}": ctx.${key} is not available — ${why} ` +
|
|
115
|
-
'(A canvas world ctx provides: input, systems, clock, debug, random, game, roots, ' +
|
|
116
|
-
"registerSystemAdapter. The Pixi runtime itself comes from @pixi/react's " +
|
|
117
|
-
'useApplication() — app.stage, app.renderer, app.ticker — and assets from Pixi`s own ' +
|
|
118
|
-
'Assets.)',
|
|
119
|
-
);
|
|
120
|
-
},
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
|
|
124
109
|
/**
|
|
125
110
|
* Build the engine runtime for one canvas world from a `CanvasHostContext`.
|
|
126
111
|
* React-free on purpose: everything here is plain engine wiring the adapter
|
|
@@ -145,13 +130,14 @@ export function createCanvasRootContext(
|
|
|
145
130
|
|
|
146
131
|
const ctxRaw: Record<string, unknown> = {
|
|
147
132
|
systems,
|
|
133
|
+
input: host.game?.input ?? null,
|
|
148
134
|
registerSystemAdapter: (kind: keyof SystemAdapters, adapter: SystemAdapters[typeof kind]) => {
|
|
149
135
|
// biome-ignore lint/suspicious/noExplicitAny: same per-key record write the three lane performs; correct by construction
|
|
150
136
|
(systemAdapters as any)[kind] = adapter;
|
|
151
137
|
host.game?.notifySystemAdaptersChanged();
|
|
152
138
|
},
|
|
153
139
|
};
|
|
154
|
-
const ctx = ctxRaw as unknown as
|
|
140
|
+
const ctx = ctxRaw as unknown as CanvasGameContext;
|
|
155
141
|
if (host.game) {
|
|
156
142
|
ctx.game = host.game;
|
|
157
143
|
ctx.roots = host.game.roots;
|
|
@@ -188,7 +174,7 @@ export function createCanvasRootContext(
|
|
|
188
174
|
);
|
|
189
175
|
},
|
|
190
176
|
});
|
|
191
|
-
|
|
177
|
+
ctxRaw['clock'] = clock;
|
|
192
178
|
|
|
193
179
|
// --- ctx.input + the debug-registry input seams — the game-owned
|
|
194
180
|
// InputManager, polled by the game-scoped runner each frame; this world
|
|
@@ -198,7 +184,6 @@ export function createCanvasRootContext(
|
|
|
198
184
|
if (host.game) {
|
|
199
185
|
const game = host.game;
|
|
200
186
|
const input = game.input;
|
|
201
|
-
ctxRaw['input'] = input;
|
|
202
187
|
debugRegistry.setInputActionsSource(id, () =>
|
|
203
188
|
input.actionNames().map((name) => ({ name, valueType: input.getActionValueType(name) })),
|
|
204
189
|
);
|
|
@@ -239,68 +224,8 @@ export function createCanvasRootContext(
|
|
|
239
224
|
);
|
|
240
225
|
});
|
|
241
226
|
}
|
|
242
|
-
} else {
|
|
243
|
-
defineAbsentCtxField(
|
|
244
|
-
ctxRaw,
|
|
245
|
-
id,
|
|
246
|
-
'input',
|
|
247
|
-
'input is game-owned and this mount has no Game host (host.game is absent — bare ' +
|
|
248
|
-
'harness/foreign host); mount through the vgai host to get the shared InputManager.',
|
|
249
|
-
);
|
|
250
227
|
}
|
|
251
228
|
|
|
252
|
-
// --- Loud absence for everything a canvas world genuinely does not build. ---
|
|
253
|
-
const threeWhy =
|
|
254
|
-
'a canvas world renders through PixiJS and owns no THREE scene graph; the world root is ' +
|
|
255
|
-
"the Pixi stage, reached with @pixi/react's useApplication().app.stage.";
|
|
256
|
-
defineAbsentCtxField(ctxRaw, id, 'scene', threeWhy);
|
|
257
|
-
defineAbsentCtxField(ctxRaw, id, 'camera', threeWhy);
|
|
258
|
-
defineAbsentCtxField(
|
|
259
|
-
ctxRaw,
|
|
260
|
-
id,
|
|
261
|
-
'sceneIndex',
|
|
262
|
-
'the live scene index walks a THREE.Scene; a canvas world has none (walk the Pixi display ' +
|
|
263
|
-
'tree from useApplication().app.stage instead).',
|
|
264
|
-
);
|
|
265
|
-
const rapierWhy =
|
|
266
|
-
'canvas worlds do not build the first-party Rapier runtime; drive physics from inside the ' +
|
|
267
|
-
'tree with the 2D physics library of your choice.';
|
|
268
|
-
defineAbsentCtxField(ctxRaw, id, 'rapierWorld', rapierWhy);
|
|
269
|
-
defineAbsentCtxField(ctxRaw, id, 'rapier', rapierWhy);
|
|
270
|
-
defineAbsentCtxField(ctxRaw, id, 'physics', rapierWhy);
|
|
271
|
-
defineAbsentCtxField(ctxRaw, id, 'collisions', rapierWhy);
|
|
272
|
-
defineAbsentCtxField(
|
|
273
|
-
ctxRaw,
|
|
274
|
-
id,
|
|
275
|
-
'assets',
|
|
276
|
-
'the canvas host context carries no AssetCache (that cache is a GLTF/texture loader ' +
|
|
277
|
-
"resolving THREE objects); load textures/spritesheets with Pixi's own `Assets`.",
|
|
278
|
-
);
|
|
279
|
-
defineAbsentCtxField(
|
|
280
|
-
ctxRaw,
|
|
281
|
-
id,
|
|
282
|
-
'composer',
|
|
283
|
-
'a canvas world has no postprocessing EffectComposer; use Pixi filters on a Container.',
|
|
284
|
-
);
|
|
285
|
-
defineAbsentCtxField(
|
|
286
|
-
ctxRaw,
|
|
287
|
-
id,
|
|
288
|
-
'audio',
|
|
289
|
-
'canvas worlds build no first-party audio context; use Web Audio directly (or @pixi/sound).',
|
|
290
|
-
);
|
|
291
|
-
defineAbsentCtxField(
|
|
292
|
-
ctxRaw,
|
|
293
|
-
id,
|
|
294
|
-
'particles',
|
|
295
|
-
'canvas worlds build no three.quarks particle runtime; use a Pixi particle container.',
|
|
296
|
-
);
|
|
297
|
-
defineAbsentCtxField(
|
|
298
|
-
ctxRaw,
|
|
299
|
-
id,
|
|
300
|
-
'debugDraw',
|
|
301
|
-
'canvas worlds build no debug-draw helper; draw into a Pixi `Graphics` in the tree.',
|
|
302
|
-
);
|
|
303
|
-
|
|
304
229
|
return {
|
|
305
230
|
ctx,
|
|
306
231
|
systems,
|
package/src/ecs/user-data.ts
CHANGED
|
@@ -111,6 +111,8 @@
|
|
|
111
111
|
* JSX props for renderer/editor integration.
|
|
112
112
|
* - `constraints` — live project-owned spatial constraints projected
|
|
113
113
|
* into the shared Inspector and viewport.
|
|
114
|
+
* - `authoringSubject` — transient identity/inspection for an ecosystem-native
|
|
115
|
+
* subject represented by an Object3D proxy.
|
|
114
116
|
* - `splineControlPoint` — index of a spline control-point drag handle.
|
|
115
117
|
* - `vcDirIdx` — view-cube face direction index (0=+X,1=-X,2=+Y,...).
|
|
116
118
|
*/
|
|
@@ -118,6 +120,7 @@
|
|
|
118
120
|
import type * as THREE from 'three';
|
|
119
121
|
import type { ParticleSystem } from 'three.quarks';
|
|
120
122
|
import type { ConstraintMark } from '../adapter/constraint';
|
|
123
|
+
import type { Object3DAuthoringSubjectMark } from '../adapter/object3d-authoring-subject';
|
|
121
124
|
import type { RapierEditableBody } from '../adapter/rapier-physics-adapter';
|
|
122
125
|
import type { ReflectionProbeMark } from '../adapter/reflection-probe';
|
|
123
126
|
import type { AnimationRuntimeInspection } from '../animation/runtime-inspection';
|
|
@@ -197,6 +200,7 @@ export interface UserDataSchema {
|
|
|
197
200
|
skeletonEnabled: boolean;
|
|
198
201
|
envObject: boolean;
|
|
199
202
|
constraints: readonly ConstraintMark[];
|
|
203
|
+
authoringSubject: Object3DAuthoringSubjectMark;
|
|
200
204
|
reflectionProbe: ReflectionProbeMark;
|
|
201
205
|
vcDirIdx: number;
|
|
202
206
|
/** W1a navmesh path-probe preview polyline: the routed waypoints, mirrored
|
|
@@ -252,6 +256,7 @@ export const UserDataKeys = {
|
|
|
252
256
|
skeletonEnabled: 'skeletonEnabled',
|
|
253
257
|
envObject: 'envObject',
|
|
254
258
|
constraints: 'constraints',
|
|
259
|
+
authoringSubject: 'authoringSubject',
|
|
255
260
|
reflectionProbe: 'reflectionProbe',
|
|
256
261
|
vcDirIdx: 'vcDirIdx',
|
|
257
262
|
navPathPoints: 'navPathPoints',
|
|
@@ -310,6 +310,9 @@ export class InputManager {
|
|
|
310
310
|
private mouseButtonsJustUp = new Set<number>();
|
|
311
311
|
private mouseDeltaX = 0;
|
|
312
312
|
private mouseDeltaY = 0;
|
|
313
|
+
/** Last `mousemove` client position. Device-poll only — action maps read delta, not this. */
|
|
314
|
+
private mouseClientX = 0;
|
|
315
|
+
private mouseClientY = 0;
|
|
313
316
|
private gamepads: (Gamepad | null)[] = [];
|
|
314
317
|
// Gamepad button edge tracking (P1.6c). Keys are `${gamepadIndex}:${buttonIndex}`.
|
|
315
318
|
// `Prev` is last frame's pressed set; `Down` is this frame's; the just-down/up
|
|
@@ -568,6 +571,8 @@ export class InputManager {
|
|
|
568
571
|
if (e.movementX !== 0 || e.movementY !== 0) this.lastActiveDevice = 'mouse';
|
|
569
572
|
this.mouseDeltaX += e.movementX;
|
|
570
573
|
this.mouseDeltaY += e.movementY;
|
|
574
|
+
this.mouseClientX = e.clientX;
|
|
575
|
+
this.mouseClientY = e.clientY;
|
|
571
576
|
};
|
|
572
577
|
|
|
573
578
|
/** F2 — real window blur: drop focus and flush held REAL-device state
|
|
@@ -1690,6 +1695,99 @@ export class InputManager {
|
|
|
1690
1695
|
return { x: this.mouseDeltaX, y: this.mouseDeltaY };
|
|
1691
1696
|
}
|
|
1692
1697
|
|
|
1698
|
+
/**
|
|
1699
|
+
* Device-poll door (not an action binding). Every getter checks
|
|
1700
|
+
* {@link inputActive} at READ time so a poll while play is off or the
|
|
1701
|
+
* tab is unfocused is inert — the same predicate `onKeyDown` / `onMouseMove`
|
|
1702
|
+
* already use. Inventing an implicit `registerAction` per key would define
|
|
1703
|
+
* bindings the project's own `.inputmap` never named.
|
|
1704
|
+
*/
|
|
1705
|
+
isDeviceKeyPressed(code: string): boolean {
|
|
1706
|
+
if (!this.inputActive) return false;
|
|
1707
|
+
return this.keysDown.has(code);
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
isDeviceKeyJustPressed(code: string): boolean {
|
|
1711
|
+
if (!this.inputActive) return false;
|
|
1712
|
+
return this.keysJustDown.has(code);
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
isDeviceKeyJustReleased(code: string): boolean {
|
|
1716
|
+
if (!this.inputActive) return false;
|
|
1717
|
+
return this.keysJustUp.has(code);
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
/** `button` is the DOM `MouseEvent.button` index (0 left, 1 middle, 2 right). */
|
|
1721
|
+
isDeviceMouseButtonPressed(button: number): boolean {
|
|
1722
|
+
if (!this.inputActive) return false;
|
|
1723
|
+
return this.mouseButtons.has(button);
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
isDeviceMouseButtonJustPressed(button: number): boolean {
|
|
1727
|
+
if (!this.inputActive) return false;
|
|
1728
|
+
return this.mouseButtonsJustDown.has(button);
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
isDeviceMouseButtonJustReleased(button: number): boolean {
|
|
1732
|
+
if (!this.inputActive) return false;
|
|
1733
|
+
return this.mouseButtonsJustUp.has(button);
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
/**
|
|
1737
|
+
* This-frame mouse delta in DOM space (Y +down). Callers that need Unity
|
|
1738
|
+
* `Pointer.delta` (Y-up) flip Y themselves.
|
|
1739
|
+
*/
|
|
1740
|
+
getDeviceMouseDelta(): { x: number; y: number } {
|
|
1741
|
+
if (!this.inputActive) return { x: 0, y: 0 };
|
|
1742
|
+
return { x: this.mouseDeltaX, y: this.mouseDeltaY };
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
/** Last `mousemove` client position in DOM space (origin top-left, Y +down). */
|
|
1746
|
+
getDeviceMousePosition(): { x: number; y: number } {
|
|
1747
|
+
if (!this.inputActive) return { x: 0, y: 0 };
|
|
1748
|
+
return { x: this.mouseClientX, y: this.mouseClientY };
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
hasDeviceGamepad(): boolean {
|
|
1752
|
+
if (!this.inputActive) return false;
|
|
1753
|
+
return this.gamepads.some((pad) => pad !== null);
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
isDeviceGamepadButtonPressed(button: number): boolean {
|
|
1757
|
+
if (!this.inputActive) return false;
|
|
1758
|
+
const index = this.firstConnectedGamepadIndex();
|
|
1759
|
+
if (index < 0) return false;
|
|
1760
|
+
return this.gamepadButtonsDown.has(`${index}:${button}`);
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
isDeviceGamepadButtonJustPressed(button: number): boolean {
|
|
1764
|
+
if (!this.inputActive) return false;
|
|
1765
|
+
const index = this.firstConnectedGamepadIndex();
|
|
1766
|
+
if (index < 0) return false;
|
|
1767
|
+
return this.gamepadButtonsJustDown.has(`${index}:${button}`);
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
isDeviceGamepadButtonJustReleased(button: number): boolean {
|
|
1771
|
+
if (!this.inputActive) return false;
|
|
1772
|
+
const index = this.firstConnectedGamepadIndex();
|
|
1773
|
+
if (index < 0) return false;
|
|
1774
|
+
return this.gamepadButtonsJustUp.has(`${index}:${button}`);
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
getDeviceGamepadAxis(axis: number): number {
|
|
1778
|
+
if (!this.inputActive) return 0;
|
|
1779
|
+
const index = this.firstConnectedGamepadIndex();
|
|
1780
|
+
if (index < 0) return 0;
|
|
1781
|
+
return this.gamepads[index]?.axes[axis] ?? 0;
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
private firstConnectedGamepadIndex(): number {
|
|
1785
|
+
for (let i = 0; i < this.gamepads.length; i++) {
|
|
1786
|
+
if (this.gamepads[i] !== null) return i;
|
|
1787
|
+
}
|
|
1788
|
+
return -1;
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1693
1791
|
/**
|
|
1694
1792
|
* F4 (spec §12 "Schema and Examples" — the F3 prompt-switching gap): which
|
|
1695
1793
|
* device family most recently produced real input, or `null` before any
|
|
@@ -2295,6 +2393,30 @@ export class InputManager {
|
|
|
2295
2393
|
element.addEventListener('click', this.onPointerLockClick);
|
|
2296
2394
|
}
|
|
2297
2395
|
|
|
2396
|
+
/**
|
|
2397
|
+
* Release pointer lock on the element {@link requestPointerLock} wired.
|
|
2398
|
+
*
|
|
2399
|
+
* Godot's `Input.mouse_mode = MOUSE_MODE_VISIBLE` is this call: drop the
|
|
2400
|
+
* lock if this manager holds it, and unwire the click-to-lock listener so a
|
|
2401
|
+
* later click does not recapture. Idempotent. A host with no
|
|
2402
|
+
* `document.exitPointerLock` (headless) still unwires — the lock never
|
|
2403
|
+
* existed there.
|
|
2404
|
+
*
|
|
2405
|
+
* Only the lock THIS manager requested is released. An editor viewport
|
|
2406
|
+
* holding lock on a different element is not ours to drop.
|
|
2407
|
+
*/
|
|
2408
|
+
exitPointerLock() {
|
|
2409
|
+
const el = this.pointerLockElement;
|
|
2410
|
+
if (el) {
|
|
2411
|
+
el.removeEventListener('click', this.onPointerLockClick);
|
|
2412
|
+
this.pointerLockElement = null;
|
|
2413
|
+
}
|
|
2414
|
+
this.pointerLocked = false;
|
|
2415
|
+
const doc = typeof document !== 'undefined' ? document : undefined;
|
|
2416
|
+
if (!doc || typeof doc.exitPointerLock !== 'function') return;
|
|
2417
|
+
if (el && doc.pointerLockElement === el) doc.exitPointerLock();
|
|
2418
|
+
}
|
|
2419
|
+
|
|
2298
2420
|
/** Clean up event listeners */
|
|
2299
2421
|
dispose() {
|
|
2300
2422
|
if (this.disposed) return;
|
package/src/manifest/schema.ts
CHANGED
|
@@ -398,7 +398,7 @@ export const LearnMetadataSchema = z
|
|
|
398
398
|
.optional()
|
|
399
399
|
.describe(
|
|
400
400
|
'Optional Learn-site lesson URL that teaches this project 1:1 ' +
|
|
401
|
-
'(e.g. https://vgai-learn.pages.dev/
|
|
401
|
+
'(e.g. https://vgai-learn.pages.dev/manual/physics/joints/)',
|
|
402
402
|
),
|
|
403
403
|
})
|
|
404
404
|
.strict()
|
package/src/pixi/authoring.ts
CHANGED
|
@@ -44,11 +44,246 @@ export type Overlay2D = Record<string, Override2D>;
|
|
|
44
44
|
function kindOf(o: Container): string {
|
|
45
45
|
const ctor = o.constructor?.name?.toLowerCase() ?? 'container';
|
|
46
46
|
if (ctor.includes('animatedsprite')) return 'animatedsprite';
|
|
47
|
+
if (ctor.includes('nineslice')) return 'nineslicesprite';
|
|
48
|
+
if (ctor.includes('tiling')) return 'tilingsprite';
|
|
47
49
|
if (ctor.includes('sprite')) return 'sprite';
|
|
48
50
|
if (ctor.includes('text')) return 'text';
|
|
51
|
+
if (ctor.includes('graphics')) return 'graphics';
|
|
49
52
|
return 'container';
|
|
50
53
|
}
|
|
51
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Hierarchy row text — the Pixi sibling of three's `object.name || object.type`.
|
|
57
|
+
*
|
|
58
|
+
* Authored `.label` first. Then a GAME name: a field, a bag key, or a class
|
|
59
|
+
* that owns this `.view`. Stock Pixi types (`Sprite`, `Graphics`, `_Container`)
|
|
60
|
+
* are not names — they used to win and hide every owner. Then the texture
|
|
61
|
+
* alias (`Sprite.from('satellite')`). Then a remaining game constructor
|
|
62
|
+
* (`TitleScreen`, `PrimaryButton`). Bare `new Container()` still reads as
|
|
63
|
+
* `container`.
|
|
64
|
+
*/
|
|
65
|
+
function displayLabel(o: Container): string {
|
|
66
|
+
const authored = authoredLabel(o);
|
|
67
|
+
if (authored) return authored;
|
|
68
|
+
const owner = viewOwnerLabel(o);
|
|
69
|
+
if (owner) return owner;
|
|
70
|
+
const asset = textureLabel(o);
|
|
71
|
+
if (asset) return asset;
|
|
72
|
+
const ctor = usefulCtorName(o);
|
|
73
|
+
if (ctor) return ctor;
|
|
74
|
+
return kindOf(o);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Pixi v8 stamps Sprite/Graphics `.label` with the class name. That is not an authored name. */
|
|
78
|
+
function authoredLabel(o: Container): string | undefined {
|
|
79
|
+
const authored = o.label?.trim();
|
|
80
|
+
if (!authored || isGenericPixiCtor(authored)) return undefined;
|
|
81
|
+
return authored;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function isMinifiedCtor(name: string): boolean {
|
|
85
|
+
return name.length <= 2 && name === name.toLowerCase();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Pixi's own display types. The shipped build prefixes them `_`. */
|
|
89
|
+
const STOCK_PIXI_CTOR = new Set([
|
|
90
|
+
'container',
|
|
91
|
+
'displayobject',
|
|
92
|
+
'sprite',
|
|
93
|
+
'animatedsprite',
|
|
94
|
+
'nineslicesprite',
|
|
95
|
+
'tilingsprite',
|
|
96
|
+
'text',
|
|
97
|
+
'bitmaptext',
|
|
98
|
+
'htmltext',
|
|
99
|
+
'graphics',
|
|
100
|
+
'mesh',
|
|
101
|
+
]);
|
|
102
|
+
|
|
103
|
+
function isGenericPixiCtor(name: string): boolean {
|
|
104
|
+
if (isMinifiedCtor(name) || name === 'Object' || name === 'Function') return true;
|
|
105
|
+
return STOCK_PIXI_CTOR.has(name.replace(/^_+/, '').toLowerCase());
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function textureLabel(object: Container): string | undefined {
|
|
109
|
+
const texture = (object as Container & { texture?: { label?: string } }).texture;
|
|
110
|
+
const raw = texture?.label?.trim();
|
|
111
|
+
if (!raw || /^(_?texture)?(empty|white)$/i.test(raw)) return undefined;
|
|
112
|
+
const leaf = raw.split('/').pop() ?? raw;
|
|
113
|
+
return leaf.replace(/\.[a-z0-9]+$/i, '') || undefined;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function usefulCtorName(value: object): string | undefined {
|
|
117
|
+
const name = value.constructor?.name;
|
|
118
|
+
if (!name || isGenericPixiCtor(name)) return undefined;
|
|
119
|
+
return name.replace(/^_+/, '') || undefined;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function fieldLabel(key: string): string | undefined {
|
|
123
|
+
const trimmed = key.replace(/^_+/, '');
|
|
124
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function isPixiDisplay(value: object): boolean {
|
|
128
|
+
return (
|
|
129
|
+
'addChild' in value &&
|
|
130
|
+
typeof (value as { addChild?: unknown }).addChild === 'function' &&
|
|
131
|
+
'children' in value
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Bubbo-style owner, identity only. TitleScreen keeps `this._cannon` and
|
|
137
|
+
* parents `this._cannon.view`. A grouping node is the field itself
|
|
138
|
+
* (`this._bottomAnimContainer`). A pool or atlas lives in a bag — Array,
|
|
139
|
+
* Map, Set, or an object literal (`_decor[]`, `allSystems`, `_parts[id]`).
|
|
140
|
+
* Scan each ancestor's own fields first — a flood walk of Pixi internals
|
|
141
|
+
* never reached `_cannon` inside the budget.
|
|
142
|
+
*/
|
|
143
|
+
function viewOwnerLabel(view: Container): string | undefined {
|
|
144
|
+
const seen = new WeakSet<object>();
|
|
145
|
+
let host: unknown = view.parent;
|
|
146
|
+
for (let hops = 0; hops < 8 && host && typeof host === 'object'; hops++) {
|
|
147
|
+
const found = labelFromHolder(host, view, seen) ?? findHeldOwner(host, view, 2, seen);
|
|
148
|
+
if (found) return found;
|
|
149
|
+
host = (host as { parent?: unknown }).parent;
|
|
150
|
+
}
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function isGameFieldKey(key: string): boolean {
|
|
155
|
+
// Skip display-list indices (`0`, `1`) and Pixi internals (`children`).
|
|
156
|
+
return /^_?[A-Za-z][A-Za-zA-Z0-9]*$/.test(key) && !PIXI_INTERNAL_KEY.has(key);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const PIXI_INTERNAL_KEY = new Set([
|
|
160
|
+
'parent',
|
|
161
|
+
'children',
|
|
162
|
+
'transform',
|
|
163
|
+
'position',
|
|
164
|
+
'scale',
|
|
165
|
+
'pivot',
|
|
166
|
+
'skew',
|
|
167
|
+
'worldTransform',
|
|
168
|
+
'localTransform',
|
|
169
|
+
]);
|
|
170
|
+
|
|
171
|
+
function labelFromHolder(host: object, view: Container, seen: WeakSet<object>): string | undefined {
|
|
172
|
+
if (seen.has(host)) return undefined;
|
|
173
|
+
seen.add(host);
|
|
174
|
+
const asBag = nameInBag(host, view, undefined, seen);
|
|
175
|
+
if (asBag) return asBag;
|
|
176
|
+
for (const [key, value] of Object.entries(host as Record<string, unknown>)) {
|
|
177
|
+
if (!isGameFieldKey(key)) continue;
|
|
178
|
+
const named = nameIfHoldsView(host, key, value, view);
|
|
179
|
+
if (named) return named;
|
|
180
|
+
const nested = nameInBag(value, view, key, seen);
|
|
181
|
+
if (nested) return nested;
|
|
182
|
+
}
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function nameInBag(
|
|
187
|
+
value: unknown,
|
|
188
|
+
view: Container,
|
|
189
|
+
fieldKey: string | undefined,
|
|
190
|
+
seen: WeakSet<object>,
|
|
191
|
+
): string | undefined {
|
|
192
|
+
const entries = bagEntries(value);
|
|
193
|
+
if (!entries) return undefined;
|
|
194
|
+
for (const [itemKey, item] of entries) {
|
|
195
|
+
const named = nameBagItem(value, item, usableBagKey(itemKey) ?? fieldKey, view, seen);
|
|
196
|
+
if (named) return named;
|
|
197
|
+
}
|
|
198
|
+
return undefined;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function nameBagItem(
|
|
202
|
+
bag: unknown,
|
|
203
|
+
item: unknown,
|
|
204
|
+
key: string | undefined,
|
|
205
|
+
view: Container,
|
|
206
|
+
seen: WeakSet<object>,
|
|
207
|
+
): string | undefined {
|
|
208
|
+
if (key) {
|
|
209
|
+
const named = nameIfHoldsView(
|
|
210
|
+
item && typeof item === 'object' ? item : (bag as object),
|
|
211
|
+
key,
|
|
212
|
+
item,
|
|
213
|
+
view,
|
|
214
|
+
);
|
|
215
|
+
if (named) return named;
|
|
216
|
+
} else if (item && typeof item === 'object' && (item as { view?: unknown }).view === view) {
|
|
217
|
+
const named = usefulCtorName(item);
|
|
218
|
+
if (named) return named;
|
|
219
|
+
}
|
|
220
|
+
if (item && typeof item === 'object' && !isPixiDisplay(item)) {
|
|
221
|
+
return labelFromHolder(item, view, seen);
|
|
222
|
+
}
|
|
223
|
+
return undefined;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function usableBagKey(key: string): string | undefined {
|
|
227
|
+
if (!key || /^\d+$/.test(key)) return undefined;
|
|
228
|
+
return isGameFieldKey(key) || key.includes('-') ? (fieldLabel(key) ?? key) : undefined;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function bagEntries(value: unknown): readonly [string, unknown][] | undefined {
|
|
232
|
+
if (!value || typeof value !== 'object') return undefined;
|
|
233
|
+
// Game arrays are allowed to be exotic subclasses. Do not dispatch through
|
|
234
|
+
// their overridable `map`: one shipped Pixi game returns its elements rather
|
|
235
|
+
// than `[key, value]` pairs there, which made hierarchy labeling throw while
|
|
236
|
+
// destructuring the result. The authoring seam treats game objects as
|
|
237
|
+
// untrusted observations, so normalize with indexed reads into our own array.
|
|
238
|
+
if (Array.isArray(value)) {
|
|
239
|
+
const entries: [string, unknown][] = [];
|
|
240
|
+
for (let index = 0; index < value.length; index++) {
|
|
241
|
+
entries.push([String(index), value[index]]);
|
|
242
|
+
}
|
|
243
|
+
return entries;
|
|
244
|
+
}
|
|
245
|
+
if (value instanceof Map) return [...value.entries()].map(([key, item]) => [String(key), item]);
|
|
246
|
+
if (value instanceof Set) return [...value].map((item, index) => [String(index), item]);
|
|
247
|
+
if (Object.getPrototypeOf(value) !== Object.prototype) return undefined;
|
|
248
|
+
return Object.entries(value as Record<string, unknown>);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function nameIfHoldsView(
|
|
252
|
+
host: object,
|
|
253
|
+
key: string,
|
|
254
|
+
value: unknown,
|
|
255
|
+
view: Container,
|
|
256
|
+
): string | undefined {
|
|
257
|
+
if (value === view) {
|
|
258
|
+
const field = fieldLabel(key);
|
|
259
|
+
if (field && field.toLowerCase() !== 'view') {
|
|
260
|
+
return usefulCtorName(value as object) ?? field;
|
|
261
|
+
}
|
|
262
|
+
return usefulCtorName(value as object) ?? usefulCtorName(host) ?? field;
|
|
263
|
+
}
|
|
264
|
+
if (value && typeof value === 'object' && (value as { view?: unknown }).view === view) {
|
|
265
|
+
return usefulCtorName(value) ?? fieldLabel(key);
|
|
266
|
+
}
|
|
267
|
+
return undefined;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function findHeldOwner(
|
|
271
|
+
held: object,
|
|
272
|
+
view: Container,
|
|
273
|
+
depth: number,
|
|
274
|
+
seen: WeakSet<object>,
|
|
275
|
+
): string | undefined {
|
|
276
|
+
const direct = labelFromHolder(held, view, seen);
|
|
277
|
+
if (direct) return direct;
|
|
278
|
+
if (depth <= 0) return undefined;
|
|
279
|
+
for (const value of Object.values(held as Record<string, unknown>)) {
|
|
280
|
+
if (!value || typeof value !== 'object' || isPixiDisplay(value)) continue;
|
|
281
|
+
const found = findHeldOwner(value, view, depth - 1, seen);
|
|
282
|
+
if (found) return found;
|
|
283
|
+
}
|
|
284
|
+
return undefined;
|
|
285
|
+
}
|
|
286
|
+
|
|
52
287
|
/**
|
|
53
288
|
* WHERE A ROW'S ID COMES FROM — the identity axis of the canvas authoring
|
|
54
289
|
* surface.
|
|
@@ -147,7 +382,7 @@ export class AuthoringAdapter2D {
|
|
|
147
382
|
const self = o as Container & { __authId?: string };
|
|
148
383
|
return {
|
|
149
384
|
id: self.__authId ?? '',
|
|
150
|
-
label:
|
|
385
|
+
label: displayLabel(o),
|
|
151
386
|
kind: kindOf(o),
|
|
152
387
|
parentId: (o.parent as (Container & { __authId?: string }) | null)?.__authId ?? null,
|
|
153
388
|
childIds: o.children.map((c) => (c as Container & { __authId?: string }).__authId ?? ''),
|
package/src/pixi/ingest.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { VisibleCaptureWindow } from '../adapter/ingest/visible-capture-window';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
type CapturedRuntime2D,
|
|
4
|
+
installSceneCapture2D,
|
|
5
|
+
type SceneCapture2DHandle,
|
|
6
|
+
} from './scene-capture';
|
|
3
7
|
|
|
4
8
|
/** A registered unmodified PixiJS game (the Pixi analog of `IngestGame`). */
|
|
5
9
|
export interface IngestGame2D {
|
|
@@ -39,9 +43,12 @@ export async function mountIngestGame2D(
|
|
|
39
43
|
opts: {
|
|
40
44
|
captureTimeoutMs?: number;
|
|
41
45
|
onWait?: (wait: VisibleCaptureWindow | null) => void;
|
|
46
|
+
acceptCapture?: (runtime: CapturedRuntime2D) => boolean;
|
|
42
47
|
} = {},
|
|
43
48
|
): Promise<IngestMount2D> {
|
|
44
|
-
const capture = installSceneCapture2D(pixiNamespace
|
|
49
|
+
const capture = installSceneCapture2D(pixiNamespace, {
|
|
50
|
+
...(opts.acceptCapture ? { accept: opts.acceptCapture } : {}),
|
|
51
|
+
});
|
|
45
52
|
try {
|
|
46
53
|
await game.load();
|
|
47
54
|
} catch (err) {
|