@vgai/engine 0.5.25 → 0.5.26
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 +10 -9
- package/dist/adapter/adapter-module.d.ts.map +1 -1
- package/dist/adapter/adapter-module.js +9 -10
- package/dist/adapter/binding.d.ts +4 -2
- package/dist/adapter/binding.d.ts.map +1 -1
- package/dist/adapter/binding.js +1 -1
- package/dist/adapter/index.d.ts +2 -2
- package/dist/adapter/index.d.ts.map +1 -1
- package/dist/adapter/index.js +1 -1
- package/dist/adapter/ingest/structural-ids.d.ts +1 -1
- package/dist/adapter/ingest/structural-ids.js +1 -1
- package/dist/adapter/root-adapter.d.ts +31 -9
- package/dist/adapter/root-adapter.d.ts.map +1 -1
- package/dist/adapter/root-seam-contract.d.ts +6 -1
- package/dist/adapter/root-seam-contract.d.ts.map +1 -1
- package/dist/adapter/root-seam-contract.js +2 -1
- package/dist/adapter/system-adapter.d.ts +10 -0
- package/dist/adapter/system-adapter.d.ts.map +1 -1
- package/dist/ai/navigation.d.ts +11 -0
- package/dist/ai/navigation.d.ts.map +1 -1
- package/dist/ai/navigation.js +11 -0
- package/dist/canvas-react/pixi-react-root-factory.d.ts.map +1 -1
- package/dist/canvas-react/pixi-react-root-factory.js +12 -3
- package/dist/pixi/authoring.d.ts +3 -3
- package/dist/pixi/authoring.d.ts.map +1 -1
- package/dist/pixi/authoring.js +2 -2
- package/dist/runtime/create-runtime.d.ts +12 -11
- package/dist/runtime/create-runtime.d.ts.map +1 -1
- package/dist/runtime/create-runtime.js +16 -15
- package/dist/runtime/game.d.ts +10 -12
- package/dist/runtime/game.d.ts.map +1 -1
- package/dist/runtime/game.js +12 -15
- package/dist/runtime/render-control.d.ts.map +1 -1
- package/dist/runtime/render-control.js +6 -1
- package/package.json +1 -1
- package/src/adapter/adapter-module.ts +12 -13
- package/src/adapter/binding.ts +4 -2
- package/src/adapter/index.ts +3 -1
- package/src/adapter/ingest/structural-ids.ts +1 -1
- package/src/adapter/root-adapter.ts +31 -9
- package/src/adapter/root-seam-contract.ts +4 -3
- package/src/adapter/system-adapter.ts +10 -0
- package/src/ai/navigation.ts +24 -0
- package/src/canvas-react/pixi-react-root-factory.tsx +17 -3
- package/src/pixi/authoring.ts +3 -3
- package/src/runtime/create-runtime.ts +27 -26
- package/src/runtime/game.ts +21 -30
- package/src/runtime/render-control.ts +6 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import type { AdapterSurface } from './adapter-surface';
|
|
4
4
|
import type {
|
|
5
|
-
|
|
5
|
+
MountedCanvasRoot,
|
|
6
6
|
MountedReactRoot,
|
|
7
7
|
MountedRootBase,
|
|
8
8
|
MountedThreeRoot,
|
|
@@ -36,7 +36,7 @@ export const ROOT_STATE_OBSERVER_SHAPE = defineSeamShape<RootStateObserver>()({
|
|
|
36
36
|
});
|
|
37
37
|
|
|
38
38
|
type ThreeSurface = Omit<MountedThreeRoot, keyof MountedRootBase>;
|
|
39
|
-
type CanvasSurface = Omit<
|
|
39
|
+
type CanvasSurface = Omit<MountedCanvasRoot, keyof MountedRootBase>;
|
|
40
40
|
type DomSurface = Omit<MountedReactRoot, keyof MountedRootBase>;
|
|
41
41
|
|
|
42
42
|
export const MOUNTED_THREE_SURFACE_SHAPE = defineSeamShape<ThreeSurface>()({
|
|
@@ -48,7 +48,8 @@ export const MOUNTED_THREE_SURFACE_SHAPE = defineSeamShape<ThreeSurface>()({
|
|
|
48
48
|
|
|
49
49
|
export const MOUNTED_CANVAS_SURFACE_SHAPE = defineSeamShape<CanvasSurface>()({
|
|
50
50
|
kind: { optional: false, kind: 'value', required: 'shape' },
|
|
51
|
-
|
|
51
|
+
canvas: { optional: false, kind: 'value', required: 'effect' },
|
|
52
|
+
substrate: { optional: false, kind: 'value', required: 'effect' },
|
|
52
53
|
});
|
|
53
54
|
|
|
54
55
|
export const MOUNTED_DOM_SURFACE_SHAPE = defineSeamShape<DomSurface>()({
|
|
@@ -357,6 +357,16 @@ export interface NavCrowdAgentState {
|
|
|
357
357
|
velocity: NavPoint;
|
|
358
358
|
radius: number;
|
|
359
359
|
height: number;
|
|
360
|
+
/** The crowd's own agent state. `invalid` is the expressible FAILURE — the
|
|
361
|
+
* agent is off the navmesh (a bad spawn or teleport) and will not move;
|
|
362
|
+
* `offmesh` is an off-mesh connection traversal. Absent on an adapter that
|
|
363
|
+
* predates this field — the editor draws such agents as walking. */
|
|
364
|
+
state?: 'invalid' | 'walking' | 'offmesh';
|
|
365
|
+
/** The agent's current move target, when the implementation reports one. */
|
|
366
|
+
target?: NavPoint;
|
|
367
|
+
/** The local path-corridor corners the agent is steering along — the
|
|
368
|
+
* REQUESTED PATH the debug draw projects. Empty or absent when idle. */
|
|
369
|
+
corners?: NavPoint[];
|
|
360
370
|
}
|
|
361
371
|
|
|
362
372
|
/**
|
package/src/ai/navigation.ts
CHANGED
|
@@ -31,8 +31,22 @@ export interface CrowdAgentSnapshot {
|
|
|
31
31
|
velocity: Vector3Like;
|
|
32
32
|
radius: number;
|
|
33
33
|
height: number;
|
|
34
|
+
/** Detour's own agent state. `invalid` is the crowd's expressible FAILURE —
|
|
35
|
+
* the agent is not on the navmesh (a bad spawn or teleport) and will not
|
|
36
|
+
* move until re-placed; `offmesh` is an off-mesh connection traversal. */
|
|
37
|
+
state: 'invalid' | 'walking' | 'offmesh';
|
|
38
|
+
/** The agent's current move target — detour's own answer, reported as the
|
|
39
|
+
* fact it is. Whether a request is ACTIVE is what {@link corners} says. */
|
|
40
|
+
target: Vector3Like;
|
|
41
|
+
/** The local path-corridor corners toward the target — the REQUESTED PATH
|
|
42
|
+
* the crowd is steering along right now (detour keeps a local corridor, not
|
|
43
|
+
* the whole route). Empty when the agent has nowhere to go. */
|
|
44
|
+
corners: Vector3Like[];
|
|
34
45
|
}
|
|
35
46
|
|
|
47
|
+
/** Detour `CrowdAgentState` values, named. */
|
|
48
|
+
const AGENT_STATES = ['invalid', 'walking', 'offmesh'] as const;
|
|
49
|
+
|
|
36
50
|
export interface NavMeshBuildParams {
|
|
37
51
|
cellSize?: number | undefined;
|
|
38
52
|
cellHeight?: number | undefined;
|
|
@@ -176,11 +190,21 @@ export class NavMeshManager {
|
|
|
176
190
|
return this.crowd.getAgents().map((a) => {
|
|
177
191
|
const p = a.interpolatedPosition;
|
|
178
192
|
const v = a.velocity();
|
|
193
|
+
const t = a.target();
|
|
194
|
+
// An invalid agent has no corridor, and detour's accessors are not
|
|
195
|
+
// guaranteed meaningful off the mesh — report the failure state with an
|
|
196
|
+
// empty path rather than reading through it.
|
|
197
|
+
const state = AGENT_STATES[a.state()] ?? 'invalid';
|
|
198
|
+
const corners =
|
|
199
|
+
state === 'invalid' ? [] : a.corners().map((c) => ({ x: c.x, y: c.y, z: c.z }));
|
|
179
200
|
return {
|
|
180
201
|
position: { x: p.x, y: p.y, z: p.z },
|
|
181
202
|
velocity: { x: v.x, y: v.y, z: v.z },
|
|
182
203
|
radius: a.radius,
|
|
183
204
|
height: a.height,
|
|
205
|
+
state,
|
|
206
|
+
target: { x: t.x, y: t.y, z: t.z },
|
|
207
|
+
corners,
|
|
184
208
|
};
|
|
185
209
|
});
|
|
186
210
|
}
|
|
@@ -80,6 +80,20 @@ type PixiReactRoot = ReturnType<typeof createRoot>;
|
|
|
80
80
|
interface CanvasEntryModuleExports {
|
|
81
81
|
/** The one shape: the world IS a component. */
|
|
82
82
|
readonly default?: ComponentType;
|
|
83
|
+
/** Imperative canvas substrates export their own host adapter. This is the
|
|
84
|
+
* same explicit boundary the three entry adjudicator accepts; gameplay
|
|
85
|
+
* modules remain ecosystem-native. */
|
|
86
|
+
readonly adapter?: unknown;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function isCanvasRootAdapter(candidate: unknown): candidate is RootAdapter<'canvas'> {
|
|
90
|
+
if (candidate === null || typeof candidate !== 'object') return false;
|
|
91
|
+
const value = candidate as Record<string, unknown>;
|
|
92
|
+
return (
|
|
93
|
+
typeof value['id'] === 'string' &&
|
|
94
|
+
value['id'].length > 0 &&
|
|
95
|
+
typeof value['mount'] === 'function'
|
|
96
|
+
);
|
|
83
97
|
}
|
|
84
98
|
|
|
85
99
|
/**
|
|
@@ -274,9 +288,8 @@ function canvasWorldAdapter(id: string, component: ComponentType): RootAdapter<'
|
|
|
274
288
|
|
|
275
289
|
return {
|
|
276
290
|
kind: 'canvas',
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
stage: mountedApp.stage,
|
|
291
|
+
canvas,
|
|
292
|
+
substrate: { name: 'pixi', root: mountedApp.stage },
|
|
280
293
|
drivesOwnLoop: false,
|
|
281
294
|
systems: systemAdapters,
|
|
282
295
|
disposeComplete,
|
|
@@ -337,6 +350,7 @@ export function resolveCanvasEntryAdapter(
|
|
|
337
350
|
rootId: string,
|
|
338
351
|
): RootAdapter<'canvas'> | null {
|
|
339
352
|
const mod = entryModule as CanvasEntryModuleExports | undefined;
|
|
353
|
+
if (isCanvasRootAdapter(mod?.adapter)) return mod.adapter;
|
|
340
354
|
if (typeof mod?.default === 'function') return canvasWorldAdapter(rootId, mod.default);
|
|
341
355
|
return null;
|
|
342
356
|
}
|
package/src/pixi/authoring.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { Container } from 'pixi.js';
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* AuthoringAdapter-2D — the Pixi analog of the editor AuthoringAdapter /
|
|
5
|
-
*
|
|
5
|
+
* ThreeAuthoringAdapter, implemented DIRECTLY over a live PixiJS display tree
|
|
6
6
|
* (no fabricated document — the anti-shim rule). Used by the editor to
|
|
7
7
|
* inspect + edit Pixi display-tree entities (first-party OR an ingested unmodified game),
|
|
8
8
|
* with stable structural-path ids so edits re-bind after the tree rebuilds.
|
|
@@ -668,7 +668,7 @@ export class AuthoringAdapter2D {
|
|
|
668
668
|
* it into the in-memory overlay so a later `serializeOverlay()`/save preserves it
|
|
669
669
|
* and a later `refresh()` re-applies it again. Ids absent from the current tree
|
|
670
670
|
* are skipped (orphaned, not an error) — the 2D analog of
|
|
671
|
-
* `
|
|
671
|
+
* `ThreeAuthoringAdapter.applyOverlay` (T3.2 slice 3). Used both for the initial
|
|
672
672
|
* reload (constructor `opts.overlay`) and for `PersistenceProvider.applyExternal`
|
|
673
673
|
* (re-apply a changed overlay file without a full remount).
|
|
674
674
|
*/
|
|
@@ -699,7 +699,7 @@ export class AuthoringAdapter2D {
|
|
|
699
699
|
for (const [id, ov] of Object.entries(this.overlay)) this.applyOverride(id, ov);
|
|
700
700
|
this.dirty = true;
|
|
701
701
|
}
|
|
702
|
-
/** Clear the dirty flag after a successful save (mirrors
|
|
702
|
+
/** Clear the dirty flag after a successful save (mirrors ThreeAuthoringAdapter). */
|
|
703
703
|
markClean(): void {
|
|
704
704
|
this.dirty = false;
|
|
705
705
|
}
|
|
@@ -3,7 +3,7 @@ import { assertNever } from '../adapter/adapter-surface';
|
|
|
3
3
|
import { createRootBinding, type RootBinding, type RootDeclaration } from '../adapter/binding';
|
|
4
4
|
import type { CanvasHostContext, DomHostContext, ThreeHostContext } from '../adapter/host-context';
|
|
5
5
|
import type {
|
|
6
|
-
|
|
6
|
+
MountedCanvasRoot,
|
|
7
7
|
MountedReactRoot,
|
|
8
8
|
MountedRoot,
|
|
9
9
|
MountedThreeRoot,
|
|
@@ -100,16 +100,17 @@ function bindRoot(
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
/**
|
|
103
|
-
* Register a canvas world onto the Game shell — the
|
|
103
|
+
* Register a canvas world onto the Game shell — the canvas analog of
|
|
104
104
|
* {@link registerThreeRoot}. `adapter` is the seam's own
|
|
105
105
|
* `RootAdapter<'canvas'>`; `RootInstance.adapter` itself only needs `.id`
|
|
106
106
|
* (`AdapterHandle`, `runtime/game.ts`), so this passes through with zero cast.
|
|
107
|
-
*
|
|
107
|
+
* The substrate root remains opaque here; the editor dispatches from the
|
|
108
|
+
* mount's explicit `substrate.name` declaration.
|
|
108
109
|
*/
|
|
109
|
-
export function
|
|
110
|
+
export function registerCanvasRoot(
|
|
110
111
|
game: GameInternal,
|
|
111
112
|
adapter: RootAdapter<'canvas'>,
|
|
112
|
-
mounted:
|
|
113
|
+
mounted: MountedCanvasRoot,
|
|
113
114
|
opts?: RegisterRootOptions,
|
|
114
115
|
): RootInstance {
|
|
115
116
|
const id = opts?.id ?? 'main';
|
|
@@ -120,7 +121,7 @@ export function registerPixiRoot(
|
|
|
120
121
|
pausable,
|
|
121
122
|
adapter,
|
|
122
123
|
mounted,
|
|
123
|
-
|
|
124
|
+
canvasRoot: mounted.substrate.root,
|
|
124
125
|
binding: bindRoot(game, opts?.declaration, { surface: 'canvas', adapter }, mounted, pausable),
|
|
125
126
|
});
|
|
126
127
|
game.registerRoot(world);
|
|
@@ -129,13 +130,13 @@ export function registerPixiRoot(
|
|
|
129
130
|
|
|
130
131
|
/**
|
|
131
132
|
* Register a React world onto the Game shell — the DOM
|
|
132
|
-
* analog of {@link registerThreeRoot}/{@link
|
|
133
|
+
* analog of {@link registerThreeRoot}/{@link registerCanvasRoot}. A react
|
|
133
134
|
* world has no `frame` hooks (react's own
|
|
134
135
|
* `createRoot` schedules its commits; `GameInternal.runFrame` correctly
|
|
135
136
|
* leaves a world with no `frame` untouched by its opaque-`update` fallback
|
|
136
137
|
* too, since `MountedReactGame` declares no `update`). `adapter` is typed
|
|
137
138
|
* as the real {@link ReactRootAdapter} shape (same reasoning as
|
|
138
|
-
* `
|
|
139
|
+
* `registerCanvasRoot`'s doc comment above) — `RootInstance.adapter` only
|
|
139
140
|
* needs `.id`, so this passes through with zero cast.
|
|
140
141
|
*/
|
|
141
142
|
export function registerReactRoot(
|
|
@@ -202,10 +203,10 @@ export interface ThreeRootMountSpec extends RootMountSpecBase {
|
|
|
202
203
|
|
|
203
204
|
/**
|
|
204
205
|
* `adapter` is the SEAM's `RootAdapter<'canvas'>` — a canvas root's mount
|
|
205
|
-
* honestly returns
|
|
206
|
+
* honestly returns a `MountedCanvasRoot`, and naming the real contract
|
|
206
207
|
* here is what keeps the resolver cast-free.
|
|
207
208
|
*/
|
|
208
|
-
export interface
|
|
209
|
+
export interface CanvasRootMountSpec extends RootMountSpecBase {
|
|
209
210
|
readonly kind: 'canvas';
|
|
210
211
|
readonly adapter: RootAdapter<'canvas'>;
|
|
211
212
|
}
|
|
@@ -234,7 +235,7 @@ export interface PixiRootMountSpec extends RootMountSpecBase {
|
|
|
234
235
|
* `kind`/`container` satisfy `MountedReactRoot` (`adapter/
|
|
235
236
|
* root-adapter.ts`) — `container` is the SAME `DomHostContext.container` the
|
|
236
237
|
* adapter's `mount` was handed (identity matters, mirroring `threeScene()`/
|
|
237
|
-
* `
|
|
238
|
+
* `canvasRoot()`'s "same instance the adapter mounted" contract); every
|
|
238
239
|
* `ReactRootAdapter` implementer echoes it back here so `mounted` alone
|
|
239
240
|
* (with no separately-threaded `container`) satisfies the union
|
|
240
241
|
* `RootInstanceInit.mounted`/`RootInstance.mounted` with zero cast.
|
|
@@ -259,14 +260,14 @@ export interface ReactRootAdapter extends RootAdapter<'dom'> {
|
|
|
259
260
|
mount(host: DomHostContext): Promise<MountedReactGame>;
|
|
260
261
|
}
|
|
261
262
|
|
|
262
|
-
/** Same reasoning as {@link
|
|
263
|
+
/** Same reasoning as {@link CanvasRootMountSpec} above: the seam's
|
|
263
264
|
* `RootAdapter<'dom'>` is what a DOM root actually guarantees. */
|
|
264
265
|
export interface ReactRootMountSpec extends RootMountSpecBase {
|
|
265
266
|
readonly kind: 'dom';
|
|
266
267
|
readonly adapter: RootAdapter<'dom'>;
|
|
267
268
|
}
|
|
268
269
|
|
|
269
|
-
export type RootMountSpec = ThreeRootMountSpec |
|
|
270
|
+
export type RootMountSpec = ThreeRootMountSpec | CanvasRootMountSpec | ReactRootMountSpec;
|
|
270
271
|
|
|
271
272
|
/**
|
|
272
273
|
* Mount N adapter roots (three + canvas + react)
|
|
@@ -450,7 +451,7 @@ interface OneRootResult {
|
|
|
450
451
|
|
|
451
452
|
/** Shared per-world mount inputs, computed once in `createRootsGameRuntime`'s
|
|
452
453
|
* loop (canvas stacking + one dpr) and threaded into whichever of
|
|
453
|
-
* `mountOneThreeRoot`/`
|
|
454
|
+
* `mountOneThreeRoot`/`mountOneCanvasRoot` this world's `kind` needs — split
|
|
454
455
|
* out so the orchestrating loop itself stays a simple dispatch. */
|
|
455
456
|
interface OneRootContext {
|
|
456
457
|
readonly game: GameInternal;
|
|
@@ -469,7 +470,7 @@ interface OneRootContext {
|
|
|
469
470
|
/** Mount one three `RootMountSpec` (canvas + renderer construction, per
|
|
470
471
|
* D5 §1/§4, then `registerThreeRoot`). Split out of
|
|
471
472
|
* `createRootsGameRuntime` purely to keep that function's own branching
|
|
472
|
-
* simple — see `
|
|
473
|
+
* simple — see `mountOneCanvasRoot` for the canvas sibling. */
|
|
473
474
|
async function mountOneThreeRoot(
|
|
474
475
|
spec: ThreeRootMountSpec,
|
|
475
476
|
ctx: OneRootContext,
|
|
@@ -539,7 +540,7 @@ async function mountOneThreeRoot(
|
|
|
539
540
|
}
|
|
540
541
|
|
|
541
542
|
/** Mount one canvas `RootMountSpec` (via its own `CanvasHostContext`, per D5
|
|
542
|
-
* §1/§3/§4, then `
|
|
543
|
+
* §1/§3/§4, then `registerCanvasRoot`) — the canvas sibling of
|
|
543
544
|
* `mountOneThreeRoot` above.
|
|
544
545
|
*
|
|
545
546
|
* Unlike `mountOneThreeRoot`, this needs no explicit `canvas.style.width`/
|
|
@@ -556,8 +557,8 @@ async function mountOneThreeRoot(
|
|
|
556
557
|
* already calls `session.resize()` unconditionally right after mount, so
|
|
557
558
|
* this never surfaces as a lasting bug the way three' buffer-only resize
|
|
558
559
|
* did (never self-healing, by design — see above). */
|
|
559
|
-
async function
|
|
560
|
-
spec:
|
|
560
|
+
async function mountOneCanvasRoot(
|
|
561
|
+
spec: CanvasRootMountSpec,
|
|
561
562
|
ctx: Omit<OneRootContext, 'assets'>,
|
|
562
563
|
): Promise<OneRootResult> {
|
|
563
564
|
const { game, canvas, w, h, dpr, headless, isBottom } = ctx;
|
|
@@ -572,7 +573,7 @@ async function mountOnePixiRoot(
|
|
|
572
573
|
preserveDrawingBuffer: true,
|
|
573
574
|
};
|
|
574
575
|
const mounted = await spec.adapter.mount(pixiHost);
|
|
575
|
-
|
|
576
|
+
registerCanvasRoot(game, spec.adapter, mounted, {
|
|
576
577
|
id: spec.id,
|
|
577
578
|
pausable: spec.pausable,
|
|
578
579
|
declaration: spec.declaration,
|
|
@@ -596,7 +597,7 @@ async function mountOnePixiRoot(
|
|
|
596
597
|
|
|
597
598
|
/**
|
|
598
599
|
* Mount one React `RootMountSpec` — the DOM sibling of
|
|
599
|
-
* `mountOneThreeRoot`/`
|
|
600
|
+
* `mountOneThreeRoot`/`mountOneCanvasRoot`. Unlike its canvas-backed siblings
|
|
600
601
|
* this returns NO `routerEntry`: a react world's DOM-root layer participates in
|
|
601
602
|
* D5's z-order/box stacking (the caller still creates and positions its `<div>`
|
|
602
603
|
* exactly like a canvas — see `createRootsGameRuntime`'s stack-building loop)
|
|
@@ -655,7 +656,7 @@ async function mountOneReactRoot(
|
|
|
655
656
|
* canvas for three/canvas, a DOM-root `<div>` layer for react — stacked
|
|
656
657
|
* per D5 §1, z-order/ties exactly matching
|
|
657
658
|
* `manifest/load.ts`'s sort, ONE `Game`, and registers every world onto it
|
|
658
|
-
* via `registerThreeRoot`/`
|
|
659
|
+
* via `registerThreeRoot`/`registerCanvasRoot`/`registerReactRoot` — the
|
|
659
660
|
* SAME wiring `test/create-runtime-worlds.test.ts` proves for the
|
|
660
661
|
* three/canvas pair. Worlds MOUNT in `roots` ARRAY order ("manifest
|
|
661
662
|
* declaration order" — the frame/registration axis), independent of
|
|
@@ -720,14 +721,14 @@ interface MountAllRootsResult {
|
|
|
720
721
|
* Mount + register every world spec, in ARRAY (declaration) order — split
|
|
721
722
|
* out of `createRootsGameRuntime` purely to keep that function's own
|
|
722
723
|
* cyclomatic complexity down (E4, same reason `mountOneThreeRoot`/
|
|
723
|
-
* `
|
|
724
|
+
* `mountOneCanvasRoot` are already split out). React roots contribute NO
|
|
724
725
|
* router entry ("DOM layers need no entry in the router's hit-test loop"):
|
|
725
726
|
* their layer's own `pointer-events` discipline handles claim/fall-through
|
|
726
727
|
* natively, with zero router involvement (see `mountOneReactRoot`'s doc
|
|
727
728
|
* comment).
|
|
728
729
|
*/
|
|
729
730
|
async function mountAllRootSpecs(
|
|
730
|
-
mountSpecs: (ThreeRootMountSpec |
|
|
731
|
+
mountSpecs: (ThreeRootMountSpec | CanvasRootMountSpec | ReactRootMountSpec)[],
|
|
731
732
|
bottomId: string | undefined,
|
|
732
733
|
inputs: MountAllRootsInputs,
|
|
733
734
|
): Promise<MountAllRootsResult> {
|
|
@@ -751,7 +752,7 @@ async function mountAllRootSpecs(
|
|
|
751
752
|
if (spec.kind === 'three') {
|
|
752
753
|
({ mountedEntry, routerEntry } = await mountOneThreeRoot(spec, oneCtx));
|
|
753
754
|
} else if (spec.kind === 'canvas') {
|
|
754
|
-
({ mountedEntry, routerEntry } = await
|
|
755
|
+
({ mountedEntry, routerEntry } = await mountOneCanvasRoot(spec, oneCtx));
|
|
755
756
|
} else {
|
|
756
757
|
// Exhaustiveness guard (§7.4-2): 'react' was already handled by the
|
|
757
758
|
// early `continue` above, so only a hypothetical 4th `AdapterSurface` can
|
|
@@ -876,7 +877,7 @@ async function createRootsGameRuntime(config: RootsRuntimeConfig): Promise<GameS
|
|
|
876
877
|
if (specs.length === 0) {
|
|
877
878
|
throw new Error('createGameRuntime: `roots` must contain at least one root.');
|
|
878
879
|
}
|
|
879
|
-
const mountSpecs = specs as (ThreeRootMountSpec |
|
|
880
|
+
const mountSpecs = specs as (ThreeRootMountSpec | CanvasRootMountSpec | ReactRootMountSpec)[];
|
|
880
881
|
|
|
881
882
|
const w = Math.max(
|
|
882
883
|
1,
|
|
@@ -992,7 +993,7 @@ async function createRootsGameRuntime(config: RootsRuntimeConfig): Promise<GameS
|
|
|
992
993
|
// --- Mount + register every world, in ARRAY (declaration) order. ---
|
|
993
994
|
// Split out into its own top-level function purely to keep
|
|
994
995
|
// `createRootsGameRuntime`'s own cyclomatic complexity down (E4) — same
|
|
995
|
-
// reason `mountOneThreeRoot`/`
|
|
996
|
+
// reason `mountOneThreeRoot`/`mountOneCanvasRoot` are already split out
|
|
996
997
|
// below.
|
|
997
998
|
let mounted: MountAllRootsResult;
|
|
998
999
|
try {
|
package/src/runtime/game.ts
CHANGED
|
@@ -5,11 +5,6 @@
|
|
|
5
5
|
* RootInstance retains its own mounted surface and optional capabilities.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
// TYPE-ONLY pixi import: `game.ts` must never value-import
|
|
9
|
-
// `pixi.js` — a value import would pull the Pixi runtime into 3D-only
|
|
10
|
-
// bundles that never touch a canvas root. Every pixi-typed field below is erased
|
|
11
|
-
// at compile time; nothing here constructs or calls into Pixi.
|
|
12
|
-
import type * as PIXI from 'pixi.js';
|
|
13
8
|
import type * as THREE from 'three';
|
|
14
9
|
import type { AdapterSurface as AdapterSurfaceLeaf } from '../adapter/adapter-surface';
|
|
15
10
|
import type { RootBinding } from '../adapter/binding';
|
|
@@ -180,21 +175,20 @@ export interface RootInstance {
|
|
|
180
175
|
* gain — see `AdapterHandle` below. */
|
|
181
176
|
readonly adapter: AdapterHandle;
|
|
182
177
|
/** The live mounted surface — one of `MountedRoot`'s kind-tagged shapes
|
|
183
|
-
* (T7.5; `MountedThreeRoot` for a three world, `
|
|
178
|
+
* (T7.5; `MountedThreeRoot` for a three world, `MountedCanvasRoot` for
|
|
184
179
|
* canvas, `MountedReactRoot` for react). */
|
|
185
180
|
readonly mounted: MountedRoot;
|
|
186
181
|
/** Kind-narrowed accessor: throws a descriptive error when this world is
|
|
187
182
|
* not a three world. */
|
|
188
183
|
threeScene(): THREE.Scene;
|
|
189
|
-
/** Kind-narrowed accessor for canvas roots
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
|
|
193
|
-
pixiStage(): PIXI.Container;
|
|
184
|
+
/** Kind-narrowed accessor for canvas roots: returns the native substrate
|
|
185
|
+
* root the adapter mounted. The host keeps it opaque; a substrate adapter
|
|
186
|
+
* that declared the matching name narrows `T`. */
|
|
187
|
+
canvasRoot<T = unknown>(): T;
|
|
194
188
|
/** Kind-narrowed accessor for DOM roots: returns the
|
|
195
189
|
* DOM-root layer `<div>` the host mounted this world's react tree into
|
|
196
190
|
* (the SAME element passed as `container` to `createRootInstance` —
|
|
197
|
-
* identity matters, mirroring `threeScene()`/`
|
|
191
|
+
* identity matters, mirroring `threeScene()`/`canvasRoot()`'s "same
|
|
198
192
|
* instance the adapter mounted" contract). Throws descriptively for a
|
|
199
193
|
* non-react world, or a react world built without a `container` (see
|
|
200
194
|
* `RootInstanceInit.container`). */
|
|
@@ -275,9 +269,9 @@ export interface RootInstanceInit {
|
|
|
275
269
|
readonly mounted: MountedRoot;
|
|
276
270
|
/** Required when `kind === 'three'` — backs `threeScene()`. */
|
|
277
271
|
readonly scene?: THREE.Scene | undefined;
|
|
278
|
-
/** Required when `kind === 'canvas'` — backs `
|
|
279
|
-
*
|
|
280
|
-
readonly
|
|
272
|
+
/** Required when `kind === 'canvas'` — backs `canvasRoot()`, symmetric with
|
|
273
|
+
* `scene` for three above. */
|
|
274
|
+
readonly canvasRoot?: unknown;
|
|
281
275
|
/** Required when `kind === 'dom'` — backs `reactRoot()` (T6.2 slice 1,
|
|
282
276
|
* symmetric with `scene`/`stage` above): the DOM-root layer `<div>` this
|
|
283
277
|
* world's react tree is mounted into. */
|
|
@@ -295,7 +289,7 @@ export interface RootInstanceInit {
|
|
|
295
289
|
/**
|
|
296
290
|
* Build a `RootInstance` whose kind-narrowed accessors throw descriptively
|
|
297
291
|
* on kind mismatch. This is the one place that builds
|
|
298
|
-
* `threeScene`/`
|
|
292
|
+
* `threeScene`/`canvasRoot`, so every world (however it's constructed, in this
|
|
299
293
|
* slice or later ones) gets identical throw behavior.
|
|
300
294
|
*/
|
|
301
295
|
export function createRootInstance(init: RootInstanceInit): RootInstance {
|
|
@@ -306,7 +300,7 @@ export function createRootInstance(init: RootInstanceInit): RootInstance {
|
|
|
306
300
|
adapter,
|
|
307
301
|
mounted,
|
|
308
302
|
scene,
|
|
309
|
-
|
|
303
|
+
canvasRoot,
|
|
310
304
|
container,
|
|
311
305
|
physics,
|
|
312
306
|
collisions,
|
|
@@ -327,13 +321,13 @@ export function createRootInstance(init: RootInstanceInit): RootInstance {
|
|
|
327
321
|
);
|
|
328
322
|
}
|
|
329
323
|
|
|
330
|
-
// Symmetric check for canvas
|
|
331
|
-
// has nothing for `
|
|
324
|
+
// Symmetric check for canvas — a canvas world with no native substrate root
|
|
325
|
+
// has nothing for `canvasRoot()` to return; fail loudly here, at
|
|
332
326
|
// construction, same as the three/scene check above.
|
|
333
|
-
if (kind === 'canvas' &&
|
|
327
|
+
if (kind === 'canvas' && (canvasRoot === undefined || canvasRoot === null)) {
|
|
334
328
|
throw new Error(
|
|
335
|
-
`RootInstance "${id}" (kind: canvas): a canvas world requires a
|
|
336
|
-
'pass `
|
|
329
|
+
`RootInstance "${id}" (kind: canvas): a canvas world requires a native substrate root — ` +
|
|
330
|
+
'pass `canvasRoot` in RootInstanceInit.',
|
|
337
331
|
);
|
|
338
332
|
}
|
|
339
333
|
|
|
@@ -405,23 +399,20 @@ export function createRootInstance(init: RootInstanceInit): RootInstance {
|
|
|
405
399
|
}
|
|
406
400
|
return scene;
|
|
407
401
|
},
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
// (T7.3 slice 2): construction above throws for `kind === 'canvas'`
|
|
411
|
-
// with no `stage`, symmetric with `threeScene()`'s `scene` guard.
|
|
412
|
-
if (kind !== 'canvas' || !stage) {
|
|
402
|
+
canvasRoot<T = unknown>(): T {
|
|
403
|
+
if (kind !== 'canvas' || canvasRoot === undefined || canvasRoot === null) {
|
|
413
404
|
throw new Error(
|
|
414
|
-
`RootInstance "${id}" (kind: ${kind}):
|
|
405
|
+
`RootInstance "${id}" (kind: ${kind}): canvasRoot() requested but this world is not ` +
|
|
415
406
|
'a canvas world',
|
|
416
407
|
);
|
|
417
408
|
}
|
|
418
|
-
return
|
|
409
|
+
return canvasRoot as T;
|
|
419
410
|
},
|
|
420
411
|
reactRoot(): HTMLElement {
|
|
421
412
|
// The `!container` branch a react world could hit here is now
|
|
422
413
|
// unreachable (T6.2 slice 1): construction above throws for
|
|
423
414
|
// `kind === 'dom'` with no `container`, symmetric with
|
|
424
|
-
// `threeScene()`/`
|
|
415
|
+
// `threeScene()`/`canvasRoot()`'s guards.
|
|
425
416
|
if (kind !== 'dom' || !container) {
|
|
426
417
|
throw new Error(
|
|
427
418
|
`RootInstance "${id}" (kind: ${kind}): reactRoot() requested but this world is not ` +
|
|
@@ -469,7 +469,12 @@ function countRoots(game: Game): PerfRootCount[] {
|
|
|
469
469
|
if (world.kind === 'three') {
|
|
470
470
|
counts.push({ id: world.id, kind: world.kind, ...countRootGraph(world.threeScene()) });
|
|
471
471
|
} else if (world.kind === 'canvas') {
|
|
472
|
-
|
|
472
|
+
// Node counting is a substrate projection, not a surface fact. Pixi's
|
|
473
|
+
// native tree exposes `children`; other canvas substrates are omitted
|
|
474
|
+
// until their adapter declares an honest counter.
|
|
475
|
+
if (world.mounted.kind === 'canvas' && world.mounted.substrate.name === 'pixi') {
|
|
476
|
+
counts.push({ id: world.id, kind: world.kind, ...countRootGraph(world.canvasRoot()) });
|
|
477
|
+
}
|
|
473
478
|
}
|
|
474
479
|
}
|
|
475
480
|
return counts;
|