@volter/editor-game 0.5.68 → 0.5.70
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/NOTICE +7 -7
- package/contributions/autoplay.service.ts +1 -1
- package/contributions/build-profiles.document.tsx +1 -1
- package/contributions/build-progress.status.tsx +2 -2
- package/contributions/build.header.tsx +1 -1
- package/contributions/connection.status.tsx +1 -1
- package/contributions/coverage.service.ts +2 -2
- package/contributions/game-eval.command.ts +6 -6
- package/contributions/gameplay.command.ts +2 -2
- package/contributions/generations.status.tsx +1 -1
- package/contributions/ingest.service.ts +2 -2
- package/contributions/play.command.ts +6 -6
- package/contributions/react/react-inspector.service.ts +2 -2
- package/package.json +8 -8
- package/serving/index.ts +1 -1
- package/src/asset-budget/AssetBudgetPanel.tsx +1 -1
- package/src/asset-budget/asset-budget-model.ts +14 -14
- package/src/asset-budget/optimize-apply.ts +8 -8
- package/src/bridge/call.ts +3 -3
- package/src/bridge/dispatch.ts +12 -12
- package/src/bridge/screenshot.ts +6 -6
- package/src/build/BuildProfilesPanel.tsx +8 -8
- package/src/canvas/canvas-board/CanvasBoardDocument.tsx +2 -2
- package/src/canvas/canvas-board/canvas-component-board.ts +1 -1
- package/src/canvas/canvas-design-mount.ts +1 -1
- package/src/coverage/live-project-verbs.ts +3 -3
- package/src/coverage/native-system-coverage.ts +3 -3
- package/src/coverage/root-coverage.ts +2 -2
- package/src/coverage/session-coverage.ts +2 -2
- package/src/design-system-stories/InspectorNarrowBodies.stories.tsx +2 -2
- package/src/game-document/DevicePresetPicker.tsx +6 -6
- package/src/game-document/GameCaptureFrameButton.tsx +1 -1
- package/src/game-document/GameDocument.tsx +2 -2
- package/src/game-document/GamePanel.tsx +9 -9
- package/src/game-document/InstanceInspectorPicker.tsx +1 -1
- package/src/game-document/device-preview.ts +4 -4
- package/src/game-document/physics-debug.ts +2 -2
- package/src/generation/GenerationActivity.tsx +22 -22
- package/src/generation/GenerationGallery.css +79 -79
- package/src/generation/generation-documents.tsx +25 -25
- package/src/generation/generation-presentation.ts +1 -1
- package/src/host/adapter-reach.ts +1 -1
- package/src/host/adapter-runtime-bindings.ts +5 -5
- package/src/host/after-paint.ts +3 -3
- package/src/host/api/configurations.ts +1 -1
- package/src/host/authoring/canvas-runtime-recognition.ts +2 -2
- package/src/host/authoring/contract-hierarchy-authoring.ts +6 -6
- package/src/host/authoring/contract-scenes-stories.ts +5 -5
- package/src/host/authoring/ingest-data-writer.ts +1 -1
- package/src/host/authoring/ingest-source-persistence.ts +2 -2
- package/src/host/authoring/mount-isolated-pixi-screen.ts +1 -1
- package/src/host/authoring/pixi-authoring-adapter.ts +60 -4
- package/src/host/authoring/pixi-creatable-kinds.ts +18 -0
- package/src/host/authoring/pixi-live-write-target.ts +6 -5
- package/src/host/authoring/pixi-source-write-target.ts +5 -2
- package/src/host/authoring/pixi-structure-history.ts +3 -3
- package/src/host/binding-resolver.ts +6 -6
- package/src/host/browser-transpile.ts +11 -11
- package/src/host/canvas-preview-frames.ts +1 -1
- package/src/host/components/CameraAuthoringOverlay.tsx +14 -14
- package/src/host/components/HeaderTelemetry.tsx +11 -11
- package/src/host/components/PixiIsolationSceneContent.tsx +2 -2
- package/src/host/components/ResolutionPicker.tsx +4 -4
- package/src/host/components/scene-document.tsx +3 -3
- package/src/host/components/world-root-stage-binding.tsx +1 -1
- package/src/host/components/world-root-stage.ts +1 -1
- package/src/host/coverage/authoring-read-probe.ts +1 -1
- package/src/host/coverage/capability-coverage.ts +22 -22
- package/src/host/coverage/game-contract-seam-evidence.ts +8 -8
- package/src/host/coverage/project-verb-coverage.ts +1 -1
- package/src/host/coverage/system-adapter-coverage.ts +19 -6
- package/src/host/game-location-guard.ts +4 -4
- package/src/host/game-module-access.ts +6 -6
- package/src/host/game-realm-page.ts +3 -3
- package/src/host/game-realm-reclaim.ts +1 -1
- package/src/host/gameplay-recording.ts +5 -5
- package/src/host/gated-globals.ts +13 -13
- package/src/host/ingest/registry.ts +14 -14
- package/src/host/play-control-hook.ts +1 -1
- package/src/host/playwright-shim.ts +3 -3
- package/src/host/projection/pixi.ts +2 -2
- package/src/host/react-mount-runtime.ts +1 -1
- package/src/host/roots/canvas-root.tsx +2 -2
- package/src/host/roots/r3f-root.tsx +1 -1
- package/src/host/scene-view-drawability.ts +1 -1
- package/src/host/sdk/tools.ts +1 -1
- package/src/host/staged-projects.ts +1 -1
- package/src/host/stories/story-pixi-preview.ts +1 -1
- package/src/host/stories/story-three-preview.ts +1 -1
- package/src/host/three-ingest-runtime.ts +1 -1
- package/src/ingest/active-ingest.ts +1 -1
- package/src/ingest/authoring/ingest-dom-surface-authoring.ts +4 -4
- package/src/ingest/authoring/ingest-root-adapter.ts +11 -11
- package/src/ingest/capture-wait-report.ts +4 -4
- package/src/ingest/discovery-public-ingest.ts +4 -4
- package/src/ingest/dom-stub-mark.ts +1 -1
- package/src/ingest/entry-load.ts +1 -1
- package/src/ingest/game-contract-realm.ts +3 -3
- package/src/ingest/game-pointer-lock.ts +1 -1
- package/src/ingest/ingest-canvas-scene-document.tsx +2 -2
- package/src/ingest/ingest-evidence-hook.ts +6 -6
- package/src/ingest/ingest-frame-snapshot.ts +2 -2
- package/src/ingest/ingest-play-commands.ts +1 -1
- package/src/ingest/ingest-play-control.ts +11 -11
- package/src/ingest/ingest-render-debug.ts +5 -5
- package/src/ingest/ingest-siblings.ts +3 -3
- package/src/ingest/module-mode.ts +2 -2
- package/src/ingest/mount-canvas-ingest-root.ts +10 -10
- package/src/ingest/mount-coverage.ts +3 -3
- package/src/ingest/mount-dom-ingest-root.ts +7 -7
- package/src/ingest/mount-ingest-root.ts +11 -11
- package/src/ingest/mount-three-ingest-root.ts +5 -5
- package/src/ingest/resolve-three.ts +2 -2
- package/src/ingest/served-bundle.ts +6 -6
- package/src/ingest/served-html-boot.ts +8 -8
- package/src/ingest/surface-dom.ts +9 -9
- package/src/ingest/surface-three.ts +1 -1
- package/src/ingest/types.ts +1 -1
- package/src/ingest/unmount-ingest-root.ts +3 -3
- package/src/network/NetworkInspectorPanel.tsx +2 -2
- package/src/play/play-boot-stall.ts +1 -1
- package/src/play/play-mode.ts +31 -30
- package/src/play/play-recording.ts +12 -12
- package/src/play-bar/PlayBar.tsx +8 -8
- package/src/play-bar/PlayerCountPicker.tsx +3 -3
- package/src/profiler/FrameDebuggerPanel.tsx +2 -2
- package/src/profiler/PerformancePanel.tsx +4 -4
- package/src/react/design-time-react-mount.ts +5 -5
- package/src/react/dom-authoring-adapter.ts +3 -3
- package/src/react/react-inspector-section.tsx +2 -2
- package/src/react/react-world-authoring-adapter.ts +20 -20
- package/src/react/story-documents/story-documents.tsx +4 -4
- package/src/react/ui-component-board.ts +1 -1
- package/src/runtime/adapter/audio-meter.ts +1 -1
- package/src/runtime/adapter/ingest/contract-debug-adapter.ts +4 -4
- package/src/runtime/adapter/ingest/contract-system-adapters.ts +9 -9
- package/src/runtime/adapter/ingest/merge-debug-adapters.ts +2 -2
- package/src/runtime/adapter/ingest/observation-debug-adapter.ts +1 -1
- package/src/runtime/adapter/native-debug-module.ts +5 -5
- package/src/runtime/audio/pose-guard.ts +1 -1
- package/src/runtime/core/game-scoped-slot.ts +1 -1
- package/src/runtime/create-runtime.ts +15 -15
- package/src/runtime/debug-bridge.ts +25 -25
- package/src/runtime/debug-registry.ts +17 -17
- package/src/runtime/dev/instruments.ts +1 -1
- package/src/runtime/dev/performance-profiler.ts +2 -2
- package/src/runtime/dev/register-render-vitals.ts +2 -2
- package/src/runtime/dev/static-batch-advisor.ts +3 -3
- package/src/runtime/dev-build.ts +3 -3
- package/src/runtime/game.ts +5 -5
- package/src/runtime/input-router.ts +1 -1
- package/src/runtime/mount-manifest.ts +10 -10
- package/src/runtime/pixi/authoring.ts +1 -1
- package/src/runtime/pixi/ingest.ts +1 -1
- package/src/runtime/presentation.ts +1 -1
- package/src/runtime/render-control.ts +21 -21
- package/src/runtime/render-seed.ts +3 -3
- package/src/runtime/run-ticks-settled.ts +1 -1
- package/src/services/game-audio.ts +3 -3
- package/src/services/game-network.ts +4 -4
- package/src/three/authoring/model-asset-inspector-section.css +15 -15
- package/src/three/authoring/model-asset-inspector-section.tsx +7 -7
- package/src/three/authoring/r3f-design-session.ts +10 -10
- package/src/three/authoring/r3f-source-authoring-adapter.ts +5 -5
- package/src/three/authoring/three-authoring-adapter.ts +5 -4
- package/src/three/authoring/typed-three-inspector.ts +1 -1
- package/src/three/story-documents/three-story-documents.tsx +3 -3
- package/src/three/three-board/board-scene.ts +9 -9
- package/src/three/three-board/three-component-board.ts +1 -1
|
@@ -47,7 +47,7 @@ import type { ResolvedAdapter, ResolvedAdapterRoot } from '@volter/editor-projec
|
|
|
47
47
|
import { loadGameManifest, type ResolvedGameManifest } from '@volter/editor-project/manifest/load';
|
|
48
48
|
|
|
49
49
|
/** The one manifest glob. Every in-tree ingest fixture, whatever its surface. */
|
|
50
|
-
const manifestModules = import.meta.glob('./games/*/
|
|
50
|
+
const manifestModules = import.meta.glob('./games/*/volter.project.json', {
|
|
51
51
|
eager: true,
|
|
52
52
|
import: 'default',
|
|
53
53
|
}) as Record<string, unknown>;
|
|
@@ -58,7 +58,7 @@ const manifestModules = import.meta.glob('./games/*/vgai.project.json', {
|
|
|
58
58
|
* the editor's realm. `.d.ts` is excluded: an ambient declaration file
|
|
59
59
|
* is not a module and must never become a dynamic-import chunk.
|
|
60
60
|
*
|
|
61
|
-
* `
|
|
61
|
+
* `volter.adapter.ts` is excluded too, and for a different reason: it is not a
|
|
62
62
|
* GAME module at all. It is host-realm DECLARATION (see {@link adapterModules}),
|
|
63
63
|
* read without booting anything, and a manifest that named it as an `entry` or
|
|
64
64
|
* a `contractShim` would be nonsense — so it is not in the set those fields
|
|
@@ -68,15 +68,15 @@ export const entryModules = import.meta.glob([
|
|
|
68
68
|
'./games/*/*.js',
|
|
69
69
|
'./games/*/*.ts',
|
|
70
70
|
'!./games/*/*.d.ts',
|
|
71
|
-
'!./games/*/
|
|
71
|
+
'!./games/*/volter.adapter.ts',
|
|
72
72
|
]) as Record<string, () => Promise<unknown>>;
|
|
73
73
|
|
|
74
74
|
/**
|
|
75
|
-
* THE ADAPTER GLOB — one `
|
|
75
|
+
* THE ADAPTER GLOB — one `volter.adapter.ts` per ingest game, keyed by game id.
|
|
76
76
|
*
|
|
77
77
|
* ARCHITECTURE-CORE §The editor protocol: "Placement follows REALM: game-realm
|
|
78
78
|
* code (a shim that runs inside the game) lives with the game's bundle;
|
|
79
|
-
* host-realm declaration (`
|
|
79
|
+
* host-realm declaration (`volter.adapter.ts`) lives where the host can import it
|
|
80
80
|
* as a real module — a repo-vendored bundle's adapter file lives in the host's
|
|
81
81
|
* in-tree registry, never inside the verbatim-served bundle." This directory IS
|
|
82
82
|
* that registry, so it holds BOTH populations:
|
|
@@ -86,7 +86,7 @@ export const entryModules = import.meta.glob([
|
|
|
86
86
|
* its bytes are served verbatim from `public/ingest/<id>/` and hashed by
|
|
87
87
|
* `vendor/games/verify-unaltered.mjs`, so nothing host-owned may go there.
|
|
88
88
|
*
|
|
89
|
-
* A folder with no `
|
|
89
|
+
* A folder with no `volter.project.json` is invisible to {@link manifestModules}
|
|
90
90
|
* and therefore to {@link discoverIngestEntries} — the second population adds no
|
|
91
91
|
* fixtures, only declarations.
|
|
92
92
|
*
|
|
@@ -98,18 +98,18 @@ export const entryModules = import.meta.glob([
|
|
|
98
98
|
* be used to fix that file"). Vite resolves the glob statically in dev and in
|
|
99
99
|
* a production build alike, so no generated dispatch table is needed.
|
|
100
100
|
*/
|
|
101
|
-
const adapterModules = import.meta.glob('./games/*/
|
|
101
|
+
const adapterModules = import.meta.glob('./games/*/volter.adapter.ts') as Record<
|
|
102
102
|
string,
|
|
103
103
|
() => Promise<unknown>
|
|
104
104
|
>;
|
|
105
105
|
|
|
106
106
|
/** The adapter module's filename — the contract's ONE spelling, host side. */
|
|
107
|
-
const ADAPTER_FILENAME = '
|
|
107
|
+
const ADAPTER_FILENAME = 'volter.adapter.ts';
|
|
108
108
|
|
|
109
109
|
/** This directory's repo path, so a published `modulePath` names a real file. */
|
|
110
110
|
const REGISTRY_REPO_DIR = 'packages/editor/src/ingest/games';
|
|
111
111
|
|
|
112
|
-
const ADAPTER_KEY_RE = /^\.\/games\/([^/]+)\/
|
|
112
|
+
const ADAPTER_KEY_RE = /^\.\/games\/([^/]+)\/volter\.adapter\.ts$/;
|
|
113
113
|
|
|
114
114
|
/**
|
|
115
115
|
* THE LOOKUP KEY IS THE GAME ID; THE STORAGE KEY IS THE FOLDER NAME. What makes
|
|
@@ -124,8 +124,8 @@ const ADAPTER_KEY_RE = /^\.\/games\/([^/]+)\/vgai\.adapter\.ts$/;
|
|
|
124
124
|
*
|
|
125
125
|
* The invariant is therefore `folder id === the id that game's manifest
|
|
126
126
|
* declares`, for BOTH populations this registry serves — in-tree fixtures
|
|
127
|
-
* (`./games/<id>/
|
|
128
|
-
* (`public/ingest/<id>/
|
|
127
|
+
* (`./games/<id>/volter.project.json`) and public bundles
|
|
128
|
+
* (`public/ingest/<id>/volter.project.json`, whose registry folder holds only the
|
|
129
129
|
* adapter file). It is guarded ONCE, statically, in
|
|
130
130
|
* `packages/editor/test/ingest-adapter-modules.test.ts`, which reads both id
|
|
131
131
|
* spaces off disk — the only realm that can, since `public/` is outside the
|
|
@@ -167,7 +167,7 @@ export const assetUrls = import.meta.glob('./games/*/assets/**', {
|
|
|
167
167
|
import: 'default',
|
|
168
168
|
}) as Record<string, string>;
|
|
169
169
|
|
|
170
|
-
const MANIFEST_KEY_RE = /^\.\/games\/([^/]+)\/
|
|
170
|
+
const MANIFEST_KEY_RE = /^\.\/games\/([^/]+)\/volter\.project\.json$/;
|
|
171
171
|
|
|
172
172
|
/** Folder-relative glob key for a file inside a discovered game's folder. */
|
|
173
173
|
export function gameFileKey(folderId: string, relPath: string): string {
|
|
@@ -206,14 +206,14 @@ function buildEntry(folderId: string, raw: unknown): IngestEntry {
|
|
|
206
206
|
const roots = declaredRoots(manifest);
|
|
207
207
|
if (roots.length !== 1) {
|
|
208
208
|
throw new Error(
|
|
209
|
-
`ingest registry: "${folderId}/
|
|
209
|
+
`ingest registry: "${folderId}/volter.project.json" must declare exactly one root ` +
|
|
210
210
|
`(found ${roots.length}).`,
|
|
211
211
|
);
|
|
212
212
|
}
|
|
213
213
|
const root = roots[0]!;
|
|
214
214
|
if (root.adapter.type !== 'ingest') {
|
|
215
215
|
throw new Error(
|
|
216
|
-
`ingest registry: "${folderId}/
|
|
216
|
+
`ingest registry: "${folderId}/volter.project.json"'s root must be an { ingest } root ` +
|
|
217
217
|
`(found adapter identity "${root.adapter.identity}").`,
|
|
218
218
|
);
|
|
219
219
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* `PlayBar` renders two different transports — the native one (title "Play
|
|
5
5
|
* (run game)") and the ingest one (label "Run ingested game") — and anything
|
|
6
6
|
* outside the component that needs to find "the play control" was matching a
|
|
7
|
-
* LABEL.
|
|
7
|
+
* LABEL. A probe polled for the native string as its editor-readiness
|
|
8
8
|
* boundary and therefore waited out its whole timeout budget on every ingest
|
|
9
9
|
* project, which renders the other one (SimCity ingest dogfood, S-6).
|
|
10
10
|
*
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* `page-script` relay op constructs one per call and hands it to a
|
|
9
9
|
* reconstructed step function; a real `@playwright/test` `Page` drives the SAME
|
|
10
10
|
* step file when the spec runs under `PageTransport`
|
|
11
|
-
* (`packages/
|
|
11
|
+
* (`packages/volter-live/src/game-client/client.ts`) in CI. That duality is why
|
|
12
12
|
* `createPageShim`'s return value is typed `as Page` — every implemented member
|
|
13
13
|
* matches Playwright's own shape closely enough that a step written against it
|
|
14
14
|
* compiles against the real type too.
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
* under both `PageTransport` (real Playwright, real closures) and
|
|
42
42
|
* `RelayTransport` (this shim, serialized) must be written as though ALWAYS
|
|
43
43
|
* serialized — inline every value the step needs; see
|
|
44
|
-
* `packages/
|
|
44
|
+
* `packages/volter-live/src/game-client/client.ts`'s `GameClient.page()` doc comment for
|
|
45
45
|
* the client-facing half of this contract.
|
|
46
46
|
*/
|
|
47
47
|
|
|
@@ -61,7 +61,7 @@ export class PageShimUnsupportedError extends Error {
|
|
|
61
61
|
`playwright-shim: ${objectName}.${member} is not supported by the in-page shim. ` +
|
|
62
62
|
`Supported ${objectName} members: ${supported.join(', ')}. ` +
|
|
63
63
|
'This shim is DOM-only — canvas gameplay input must use game.input.*, while React-only ' +
|
|
64
|
-
'DOM games may use Page keyboard/locator input. See packages/editor/src/playwright-shim.ts for ' +
|
|
64
|
+
'DOM games may use Page keyboard/locator input. See packages/editor-game/src/host/playwright-shim.ts for ' +
|
|
65
65
|
"the full supported subset and the wire's closure-capture limitation.",
|
|
66
66
|
);
|
|
67
67
|
this.name = 'PageShimUnsupportedError';
|
|
@@ -179,8 +179,8 @@ export class PixiProjector {
|
|
|
179
179
|
if (rect.width <= 0 || rect.height <= 0) return null;
|
|
180
180
|
const mapped = this.mapping?.pointFromClient?.(clientX, clientY, rect) ?? null;
|
|
181
181
|
if (this.mapping?.pointFromClient) return mapped;
|
|
182
|
-
const logicalWidth = Number(surface.dataset?.['
|
|
183
|
-
const logicalHeight = Number(surface.dataset?.['
|
|
182
|
+
const logicalWidth = Number(surface.dataset?.['volterStageWidth'] ?? rect.width);
|
|
183
|
+
const logicalHeight = Number(surface.dataset?.['volterStageHeight'] ?? rect.height);
|
|
184
184
|
if (!(logicalWidth > 0) || !(logicalHeight > 0)) return null;
|
|
185
185
|
return {
|
|
186
186
|
x: (clientX - rect.left) * (logicalWidth / rect.width),
|
|
@@ -54,7 +54,7 @@ function staticReactRootMountRuntime(): ReactRootMountRuntime {
|
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
56
|
* Import + shape-check the packaged runtime's synthetic
|
|
57
|
-
* `/
|
|
57
|
+
* `/__volter-react-world-runtime` module (`vite-plugin-module-doorways.ts`,
|
|
58
58
|
* registered only by `packaged.ts`'s project-rooted Vite instance and by
|
|
59
59
|
* `vitest.config.ts` for test coverage — never by `dev.ts`/the root
|
|
60
60
|
* `vite.config.ts`). A loud, named failure if the module resolves but is
|
|
@@ -134,7 +134,7 @@ function canvasWorldAdapter(
|
|
|
134
134
|
throw new Error(
|
|
135
135
|
`canvas world "${id}": this lane has already mounted this canvas. @pixi/react keys ` +
|
|
136
136
|
'its reconciler roots off the canvas element, so re-using one would resurrect a ' +
|
|
137
|
-
'root whose Application is destroyed. Hand each mount a fresh canvas (every
|
|
137
|
+
'root whose Application is destroyed. Hand each mount a fresh canvas (every volter ' +
|
|
138
138
|
'host already does).',
|
|
139
139
|
);
|
|
140
140
|
}
|
|
@@ -308,7 +308,7 @@ function canvasWorldAdapter(
|
|
|
308
308
|
mountedApp.ticker.update(elapsedMs);
|
|
309
309
|
},
|
|
310
310
|
// Pixi's own hit test finds only interactive objects (`eventMode` static or dynamic), so a
|
|
311
|
-
//
|
|
311
|
+
// 2D layer claims its tappable objects and lets every other point fall through to the world beneath.
|
|
312
312
|
hitTest(x: number, y: number): boolean {
|
|
313
313
|
if (disposed) return false;
|
|
314
314
|
// Pixi points its boundary at the last rendered scene inside each of its own pointer
|
|
@@ -208,7 +208,7 @@ function threeWorldAdapter(id: string, component: ComponentType, runtime: R3FRun
|
|
|
208
208
|
Fragment,
|
|
209
209
|
null,
|
|
210
210
|
content,
|
|
211
|
-
createElement(MountEffectsReady, { key: '
|
|
211
|
+
createElement(MountEffectsReady, { key: 'volter-mount-effects-ready' }),
|
|
212
212
|
);
|
|
213
213
|
// A reconcile-time crash (e.g. a missing `extend` catalogue entry)
|
|
214
214
|
// surfaces as an uncaught window error and `onCreated` never fires —
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* throw, attributes it to that scene, and stops re-throwing it forever.
|
|
22
22
|
*
|
|
23
23
|
* The game is NOT lost when this fires: its own renderer keeps drawing into its
|
|
24
|
-
* own canvas, which the host adopted, so the Game tab and `
|
|
24
|
+
* own canvas, which the host adopted, so the Game tab and `volter-game-editor screenshot`
|
|
25
25
|
* show the real thing. What is lost is the editor-camera Scene view of it,
|
|
26
26
|
* which is exactly what the message says.
|
|
27
27
|
*/
|
package/src/host/sdk/tools.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* input/result schemas, structured error codes, and the metadata a caller reads
|
|
6
6
|
* before invoking (`permission`, `host`, `mutates`, `supportsDryRun`). The
|
|
7
7
|
* contract exists because a tool is called across a process boundary — CLI to
|
|
8
|
-
* editor to node host, and `
|
|
8
|
+
* editor to node host, and `volter mcp` to an external agent over stdio — where
|
|
9
9
|
* you cannot throw. `ToolRegistry.dispatch` turns every expected failure
|
|
10
10
|
* (unknown name, bad input, an `impl` throw, a bad return) into a typed outcome
|
|
11
11
|
* instead.
|
|
@@ -350,7 +350,7 @@ const FIRST_FRAME_TIMEOUT_MS = PIXI_MOUNT_TIMEOUT_MS;
|
|
|
350
350
|
* Wait until every collected `Application` has PRESENTED a frame with content
|
|
351
351
|
* in it — the readiness test a capture of a canvas story owes itself.
|
|
352
352
|
*
|
|
353
|
-
* MEASURED DEFECT (this is why the function exists): `
|
|
353
|
+
* MEASURED DEFECT (this is why the function exists): `volter-game-editor screenshot
|
|
354
354
|
* <file>.stories.tsx` photographed a `@pixi/react` story blank. The capture
|
|
355
355
|
* lane's own readiness test is "the container has committed child nodes", and
|
|
356
356
|
* `<Application>` commits its `<canvas>` on the FIRST commit — before
|
|
@@ -150,7 +150,7 @@ export interface MountedStoryObject3D {
|
|
|
150
150
|
|
|
151
151
|
/** The name of the wrapper `<group>` every story is rendered into, so its root
|
|
152
152
|
* `Object3D` can be extracted unambiguously from fiber's scene. */
|
|
153
|
-
export const PREVIEW_ROOT_NAME = '
|
|
153
|
+
export const PREVIEW_ROOT_NAME = 'volter:story-preview-root';
|
|
154
154
|
|
|
155
155
|
/** How long to wait for fiber's first commit before giving up. The LAST-RESORT
|
|
156
156
|
* ceiling only: a reconcile-time crash is now caught by
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* `canvas-entry-runtime.ts`'s `resolveCanvasPixiForEditor`.
|
|
5
5
|
*
|
|
6
6
|
* See `../vite-plugin-module-doorways.ts` for the measured failure this
|
|
7
|
-
* closes and for why the ingest lane does not borrow `/
|
|
7
|
+
* closes and for why the ingest lane does not borrow `/__volter-r3f-runtime`.
|
|
8
8
|
*
|
|
9
9
|
* A mount site resolves this ONCE and hands what it gets to everything it
|
|
10
10
|
* builds for that game. Nothing further down reaches for a static import: a
|
|
@@ -108,7 +108,7 @@ export type ActiveIngest = (
|
|
|
108
108
|
) & {
|
|
109
109
|
siblings: SiblingMount[];
|
|
110
110
|
/** The mounted world's id — the SAME id every log line and the
|
|
111
|
-
* `
|
|
111
|
+
* `__volterIngest` hook's `gameId` names. Carried on the slot (rather than
|
|
112
112
|
* re-derived) so the status facet can report WHICH world is live
|
|
113
113
|
* without a second source of truth (S-1). */
|
|
114
114
|
worldId: string;
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
walkOidTree,
|
|
21
21
|
} from '../../react/react-world-authoring-adapter';
|
|
22
22
|
import type { AuthoringAdapter } from '@volter/editor-project/adapter';
|
|
23
|
-
import type {
|
|
23
|
+
import type { VolterGameContract } from '@volter/editor-project/adapter/ingest/game-contract';
|
|
24
24
|
|
|
25
25
|
type ReadableDomRoot = OverlayElement & DomElementLike & { readonly ownerDocument?: Document };
|
|
26
26
|
|
|
@@ -50,7 +50,7 @@ function iframeDocumentRoot(hostEl: HTMLElement): ReadableDomRoot | null {
|
|
|
50
50
|
|
|
51
51
|
function resolveGameDomRoot(
|
|
52
52
|
hostEl: HTMLElement,
|
|
53
|
-
declaredRoot:
|
|
53
|
+
declaredRoot: VolterGameContract['root'] | undefined,
|
|
54
54
|
): ReadableDomRoot | null {
|
|
55
55
|
if (isReadableDomRoot(declaredRoot)) return declaredRoot;
|
|
56
56
|
return iframeDocumentRoot(hostEl) ?? (isReadableDomRoot(hostEl) ? hostEl : null);
|
|
@@ -143,7 +143,7 @@ export function withDetectedDomSurface(args: {
|
|
|
143
143
|
primary: AuthoringAdapter;
|
|
144
144
|
worldId: string;
|
|
145
145
|
hostEl: HTMLElement;
|
|
146
|
-
declaredRoot?:
|
|
146
|
+
declaredRoot?: VolterGameContract['root'];
|
|
147
147
|
store: EditorShellStore;
|
|
148
148
|
}): AuthoringAdapter {
|
|
149
149
|
const gameRoot = resolveGameDomRoot(args.hostEl, args.declaredRoot);
|
|
@@ -158,7 +158,7 @@ export function withDetectedDomSurface(args: {
|
|
|
158
158
|
// refuse every detected DOM sibling before the ingest could mount.
|
|
159
159
|
const domUiJournalId = `${args.worldId}/dom-ui`;
|
|
160
160
|
const liveUiRoot: DomElementLike & OidElementLike = {
|
|
161
|
-
tagName: '
|
|
161
|
+
tagName: 'volter-dom-surface',
|
|
162
162
|
id: '',
|
|
163
163
|
className: '',
|
|
164
164
|
get children() {
|
|
@@ -385,7 +385,7 @@ function bootWindowPageErrors(startedAt: number): string[] {
|
|
|
385
385
|
/**
|
|
386
386
|
* Wait for the game's world to be BUILT, declaration first.
|
|
387
387
|
*
|
|
388
|
-
* A game that declares `window.
|
|
388
|
+
* A game that declares `window.volterGame.ready` is awaited exactly once and the
|
|
389
389
|
* measured poll never runs — the game states when it is done, so the host has
|
|
390
390
|
* nothing to estimate. Everything else keeps {@link waitForSceneToSettle},
|
|
391
391
|
* which is the MEASURED fallback and is labelled as such wherever readiness is
|
|
@@ -408,7 +408,7 @@ async function awaitWorldReady(
|
|
|
408
408
|
return 'declared';
|
|
409
409
|
} catch (err) {
|
|
410
410
|
editorConsole.error(
|
|
411
|
-
`Ingest game "${gameId}" declared \`window.
|
|
411
|
+
`Ingest game "${gameId}" declared \`window.volterGame.ready\` and it REJECTED (${String(err)}) ` +
|
|
412
412
|
'— the world was already captured, so the host fell back to the measured settle wait. ' +
|
|
413
413
|
'This root now reports readiness as measured.',
|
|
414
414
|
'ingest',
|
|
@@ -442,8 +442,8 @@ async function waitForSceneToSettle(scene: THREE.Scene, maxMs = 5000): Promise<v
|
|
|
442
442
|
* Some games append their canvas to `document.body` — reparent it into the
|
|
443
443
|
* host. A DOM-hybrid game (React/R3F with HTML UI: HUDs, overlay panels, drei
|
|
444
444
|
* `<Html>` portals) can declare the element that OWNS its canvas via
|
|
445
|
-
* `window.
|
|
446
|
-
* `
|
|
445
|
+
* `window.volterGame.root` (the declared game→host contract, game-contract.ts;
|
|
446
|
+
* `__volterGameRoot` is the pre-contract alias). Adopting only the bare canvas
|
|
447
447
|
* would strand that UI at page level over the editor chrome, and following the
|
|
448
448
|
* canvas would require the game to know host layout internals — an arcane
|
|
449
449
|
* demand. Adopt the declared root wholesale so the game's own DOM structure
|
|
@@ -472,7 +472,7 @@ function adoptGameDomRoot(hostEl: HTMLElement, surface: HTMLElement): HTMLElemen
|
|
|
472
472
|
const realmWindow = ingestGameRealmWindow();
|
|
473
473
|
const declaredRoot =
|
|
474
474
|
readIngestGameContract()?.root ??
|
|
475
|
-
(realmWindow as unknown as {
|
|
475
|
+
(realmWindow as unknown as { __volterGameRoot?: unknown }).__volterGameRoot;
|
|
476
476
|
const declared =
|
|
477
477
|
declaredRoot instanceof HTMLElement && declaredRoot.contains(surface) ? declaredRoot : surface;
|
|
478
478
|
if (declared.contains(hostEl)) return hostEl;
|
|
@@ -516,7 +516,7 @@ export async function mountIngestGame(
|
|
|
516
516
|
// Host policy, not per-game CSS — see `game-realm-page.ts` for the four
|
|
517
517
|
// page-shaped assumptions this covers and why each one breaks an in-realm
|
|
518
518
|
// mount. Registered on the DEFAULT realm because an ingest descriptor's
|
|
519
|
-
// module urls carry no `?
|
|
519
|
+
// module urls carry no `?volter-mount=` (binding-resolver.ts's
|
|
520
520
|
// `resolveIngestDescriptor` builds bare `fsImportPath` urls).
|
|
521
521
|
const realmPage = setGameSurface(hostEl);
|
|
522
522
|
|
|
@@ -614,10 +614,10 @@ export async function mountIngestGame(
|
|
|
614
614
|
// Signal COLD MOUNT before the entry executes: editor mounts are for
|
|
615
615
|
// inspection first, so a session-driven game MAY defer its side-effects
|
|
616
616
|
// (backend connection, narrative, audio) until the editor's play control
|
|
617
|
-
// dispatches '
|
|
617
|
+
// dispatches 'volter:ingest-play' (getIngestPlayControl, ingest/mount-ingest-root.ts).
|
|
618
618
|
// Rendering must continue regardless — capture needs a frame — and games
|
|
619
619
|
// that ignore the flag behave exactly as before (opt-in, never demanded).
|
|
620
|
-
(window as unknown as {
|
|
620
|
+
(window as unknown as { __volterMountCold?: boolean }).__volterMountCold = true;
|
|
621
621
|
// THE BOOT WINDOW OPENS HERE (M29). Everything the page throws from this
|
|
622
622
|
// instant until the mount resolves belongs to this game's boot, and it is the
|
|
623
623
|
// fact that separates "crashed before ready" from "never became ready" — the
|
|
@@ -650,7 +650,7 @@ export async function mountIngestGame(
|
|
|
650
650
|
// boots in the background parks here instead of dying, with the trap still
|
|
651
651
|
// installed, so the first frame after the human foregrounds the tab is the
|
|
652
652
|
// captured one. `setCaptureWait` is what keeps that park from reading as a
|
|
653
|
-
// hung mount — `
|
|
653
|
+
// hung mount — `volter-game-editor status` names it.
|
|
654
654
|
rt = await capture.waitForCapture({
|
|
655
655
|
timeoutMs,
|
|
656
656
|
onWait: (wait) => setCaptureWait(game.id, wait),
|
|
@@ -797,10 +797,10 @@ export async function mountIngestGame(
|
|
|
797
797
|
// well after `setPaused` returned. Nothing else would notify the store, and
|
|
798
798
|
// `collectState` is push-based — the control API would keep serving a
|
|
799
799
|
// snapshot taken before the measurement existed, reporting `loop: null`
|
|
800
|
-
// forever. Broadcast so `
|
|
800
|
+
// forever. Broadcast so `volter-game-editor status` sees the verdict it just produced.
|
|
801
801
|
onVerdict: () => store.shell.notifyIngestEdit(),
|
|
802
802
|
});
|
|
803
|
-
// The game's DECLARED system surface (`window.
|
|
803
|
+
// The game's DECLARED system surface (`window.volterGame.systems`), projected
|
|
804
804
|
// onto the host's ordinary `SystemAdapters.debug`. `activateCapturedThreeIngest`
|
|
805
805
|
// hands `mounted.systems` straight to `setActiveSystems`, so a shim that
|
|
806
806
|
// declares verbs makes `game.commands()`/`game.state()` work through the same
|
|
@@ -7,13 +7,13 @@
|
|
|
7
7
|
* background — the normal human path — no longer burns its window unable to
|
|
8
8
|
* draw. What it does instead is WAIT, potentially for as long as the human
|
|
9
9
|
* takes to come back to the tab, and a wait is exactly the state every door
|
|
10
|
-
* used to report as silence: nothing mounted, nothing failed, `
|
|
10
|
+
* used to report as silence: nothing mounted, nothing failed, `volter-game-editor status`
|
|
11
11
|
* counting down a clock that was not running. So the wait is published:
|
|
12
|
-
* `collectState`'s `ingestCaptureWait` carries it, and `
|
|
12
|
+
* `collectState`'s `ingestCaptureWait` carries it, and `volter-game-editor status` prints
|
|
13
13
|
* "waiting for first visible frame — tab is hidden" instead of a countdown.
|
|
14
14
|
*
|
|
15
15
|
* A LIVE VIEW, never a snapshot — same rule as `IngestMount.realmLoopVerdict`
|
|
16
|
-
* and the `
|
|
16
|
+
* and the `__volterIngest` hook's `reflected`/`sceneChildren`: the elapsed
|
|
17
17
|
* numbers move on their own, so a frozen copy starts lying the instant it is
|
|
18
18
|
* taken. The slot holds the window itself and derives the report at read time.
|
|
19
19
|
*
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
import { notifyLiveSessionsChanged } from '@volter/editor-sdk/kit/live-session-registry';
|
|
28
28
|
import type { VisibleCaptureWindow } from '@volter/threejs-runtime/adapter/ingest/visible-capture-window';
|
|
29
29
|
|
|
30
|
-
/** The wait as `
|
|
30
|
+
/** The wait as `volter-game-editor status` reports it. */
|
|
31
31
|
export interface CaptureWaitStatus {
|
|
32
32
|
/** The world whose first frame is being waited for. */
|
|
33
33
|
readonly worldId: string;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* The three sibling of `surface-canvas.ts` (the pixi lane, which is the idiom
|
|
5
5
|
* benchmark this file is modelled on): a game vendored under
|
|
6
|
-
* `public/ingest/<id>/` carries its own declarative `
|
|
6
|
+
* `public/ingest/<id>/` carries its own declarative `volter.project.json`, and
|
|
7
7
|
* its id resolves it into the SAME `IngestGame` descriptor every other three
|
|
8
8
|
* ingest route produces. Adding a fourth vendored game is a folder plus a
|
|
9
9
|
* manifest — no edit to this file, no per-game page, no branch anywhere.
|
|
@@ -81,7 +81,7 @@ function readVendoredIngestRoot(
|
|
|
81
81
|
const roots = declaredRoots(manifest);
|
|
82
82
|
if (roots.length !== 1) {
|
|
83
83
|
throw new Error(
|
|
84
|
-
`vendored ingest discovery: "${folderId}/
|
|
84
|
+
`vendored ingest discovery: "${folderId}/volter.project.json" must declare exactly one root ` +
|
|
85
85
|
`(found ${roots.length}).`,
|
|
86
86
|
);
|
|
87
87
|
}
|
|
@@ -92,7 +92,7 @@ function readVendoredIngestRoot(
|
|
|
92
92
|
root.adapter.identity !== expect.identity
|
|
93
93
|
) {
|
|
94
94
|
throw new Error(
|
|
95
|
-
`vendored ingest discovery: "${folderId}/
|
|
95
|
+
`vendored ingest discovery: "${folderId}/volter.project.json" must declare one ` +
|
|
96
96
|
`${expect.surface} { ingest } root (found surface "${root.surface}", adapter identity ` +
|
|
97
97
|
`"${root.adapter.identity}").`,
|
|
98
98
|
);
|
|
@@ -161,7 +161,7 @@ export function buildPublicCanvasIngestGame(
|
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
/**
|
|
164
|
-
* Resolve a vendored game id against `public/ingest/<id>/
|
|
164
|
+
* Resolve a vendored game id against `public/ingest/<id>/volter.project.json`.
|
|
165
165
|
*
|
|
166
166
|
* `null` means "no such vendored three ingest" (no manifest served at that
|
|
167
167
|
* path) — the caller falls through to its own unknown-id report. A manifest
|
package/src/ingest/entry-load.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* editor's realm, preceded by the host-added contract module the manifest names
|
|
4
4
|
* in `ingest.contractShim` when it declares one.
|
|
5
5
|
*
|
|
6
|
-
* ORDER IS THE WHOLE CONTRACT. The shim assigns `window.
|
|
6
|
+
* ORDER IS THE WHOLE CONTRACT. The shim assigns `window.volterGame` synchronously
|
|
7
7
|
* at top level (see the field's schema description), so it must be evaluated
|
|
8
8
|
* BEFORE the game's entry — a game that reads the declaration during its own
|
|
9
9
|
* module evaluation would otherwise see nothing, and a shim that lazily
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { gameRealmForMountId } from '../host/gated-globals';
|
|
2
2
|
import {
|
|
3
3
|
readGameContract,
|
|
4
|
-
type
|
|
4
|
+
type VolterGameContract,
|
|
5
5
|
} from '@volter/editor-project/adapter/ingest/game-contract';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* The browser realm used by the singleton ingest routes.
|
|
9
9
|
*
|
|
10
|
-
* Vendored ingest entry graphs deliberately carry no `?
|
|
10
|
+
* Vendored ingest entry graphs deliberately carry no `?volter-mount=` query:
|
|
11
11
|
* they share the editor's Pixi/Three packages and run in the default gated
|
|
12
12
|
* realm. Realm hardening keeps game-owned expandos on that realm's window
|
|
13
13
|
* proxy, so host code must read the contract from the same proxy rather than
|
|
@@ -28,7 +28,7 @@ export function ingestGameRealmWindow(): Window {
|
|
|
28
28
|
* reached by every `bridge-call`) degrading instead of throwing
|
|
29
29
|
* `window is not defined` at them.
|
|
30
30
|
*/
|
|
31
|
-
export function readIngestGameContract():
|
|
31
|
+
export function readIngestGameContract(): VolterGameContract | null {
|
|
32
32
|
if (typeof window === 'undefined') return null;
|
|
33
33
|
return readGameContract(ingestGameRealmWindow());
|
|
34
34
|
}
|
|
@@ -59,7 +59,7 @@ export function installGamePointerLockGate(): void {
|
|
|
59
59
|
if (entry && !entry.gate()) {
|
|
60
60
|
// A NEW native console.error site, suppressed to keep this task's diff at
|
|
61
61
|
// zero new lint warnings — and native on purpose: this fires from a game's
|
|
62
|
-
// own click handler in the page, and the page console is what `
|
|
62
|
+
// own click handler in the page, and the page console is what `volter-game-editor
|
|
63
63
|
// status` reads.
|
|
64
64
|
// biome-ignore lint/suspicious/noConsole: see comment above
|
|
65
65
|
console.error(
|
|
@@ -136,8 +136,8 @@ export function IngestCanvasSceneContent({
|
|
|
136
136
|
<div
|
|
137
137
|
ref={containerRef}
|
|
138
138
|
data-testid="world-document:ingest-canvas-scene"
|
|
139
|
-
data-
|
|
140
|
-
data-
|
|
139
|
+
data-volter-backdrop-color={active ? CANVAS_SCENE_BACKGROUND : undefined}
|
|
140
|
+
data-volter-backdrop-policy={active ? 'dark-frost' : undefined}
|
|
141
141
|
style={{
|
|
142
142
|
position: 'absolute',
|
|
143
143
|
inset: 0,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The `
|
|
2
|
+
* The `__volterIngest` / `__volterIngest2D` / `__volterIngestReact` dev hooks: the
|
|
3
3
|
* headless-readable evidence a live ingest mount publishes for automation,
|
|
4
|
-
* screenshots and
|
|
4
|
+
* screenshots and probes.
|
|
5
5
|
*
|
|
6
6
|
* Everything here is EVIDENCE, not control: names read off the live adapter,
|
|
7
7
|
* counters read off the live gate, the coverage report re-derived on demand.
|
|
@@ -18,7 +18,7 @@ import type { IngestMount } from './authoring/ingest-root-adapter';
|
|
|
18
18
|
import { ingestCoverageReport } from './mount-coverage';
|
|
19
19
|
|
|
20
20
|
/** The window key each surface's mount publishes its evidence under. */
|
|
21
|
-
export type IngestHookKey = '
|
|
21
|
+
export type IngestHookKey = '__volterIngest' | '__volterIngest2D' | '__volterIngestReact';
|
|
22
22
|
|
|
23
23
|
const AUTHORING_PROVIDER_KEYS = [
|
|
24
24
|
'hierarchy',
|
|
@@ -57,7 +57,7 @@ export function ingestHookEvidence(adapter: AuthoringAdapter): Record<string, un
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
/**
|
|
60
|
-
* The loop fields the `
|
|
60
|
+
* The loop fields the `__volterIngest` dev hook publishes — the measured verdict
|
|
61
61
|
* plus the gate's own counters, so automation can drive Play/Pause/Step through
|
|
62
62
|
* exactly the seam the editor's own buttons use rather than a parallel one.
|
|
63
63
|
*/
|
|
@@ -85,10 +85,10 @@ export function defineLoopHookFields(target: Record<string, unknown>, mount: Ing
|
|
|
85
85
|
|
|
86
86
|
/**
|
|
87
87
|
* D-L1 — the LIVE value behind
|
|
88
|
-
* `
|
|
88
|
+
* `__volterIngestReact.domEvidence()`, shared by both react ingest routes.
|
|
89
89
|
* Evaluated at scrape time (a function result, never memoized),
|
|
90
90
|
* same "read the live state fresh on every call" discipline as
|
|
91
|
-
* `
|
|
91
|
+
* `__volterSiblingMounts` (D-H1).
|
|
92
92
|
*
|
|
93
93
|
* `elementCount` prefers the DOM's real `querySelectorAll('*').length` — a
|
|
94
94
|
* real browser `HTMLElement` (what `reactRoot()` actually returns in
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* SAME-FRAME PIXELS for an ingested game's canvas — the reason `
|
|
2
|
+
* SAME-FRAME PIXELS for an ingested game's canvas — the reason `volter-game-editor
|
|
3
3
|
* screenshot` no longer returns black over a running ingest.
|
|
4
4
|
*
|
|
5
5
|
* ## The failure
|
|
6
6
|
*
|
|
7
7
|
* `composite-screenshot.ts` reads a game's canvas with `drawImage`, and that
|
|
8
8
|
* only works on a WebGL canvas whose context was created with
|
|
9
|
-
* `preserveDrawingBuffer: true`. Every canvas the
|
|
9
|
+
* `preserveDrawingBuffer: true`. Every canvas the volter RUNTIME mounts sets it
|
|
10
10
|
* (`@volter/editor-game/runtime/create-runtime`), which is why the composite leg has always
|
|
11
11
|
* worked for first-party play. An INGESTED game creates its own canvas: the
|
|
12
12
|
* racing-game's `<Canvas>` passes no `gl` prop, so fiber's default
|