@volter/editor-game 0.5.68 → 0.5.69
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 +1 -1
- 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 +10 -10
- 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 +3 -3
- 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 +2 -2
- 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 +10 -10
- 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 +14 -14
- 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 +9 -9
- 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
|
@@ -52,7 +52,7 @@ import type {
|
|
|
52
52
|
} from '@volter/editor-project/adapter';
|
|
53
53
|
import { WRITE_ANCHOR_KINDS } from '@volter/editor-project/adapter';
|
|
54
54
|
import type { AdapterSurface } from '@volter/editor-project/adapter/adapter-surface';
|
|
55
|
-
import type {
|
|
55
|
+
import type { VolterGameContract } from '@volter/editor-project/adapter/ingest/game-contract';
|
|
56
56
|
import type { SystemAdapters } from '@volter/editor-project/adapter/system-adapter';
|
|
57
57
|
import { SYSTEM_ADAPTERS_SHAPE } from '@volter/editor-project/adapter/system-seam-contract';
|
|
58
58
|
import {
|
|
@@ -92,7 +92,7 @@ export {
|
|
|
92
92
|
* gets a row.
|
|
93
93
|
*
|
|
94
94
|
* The rest are contract facts about an ingested game — declarations only a
|
|
95
|
-
* `window.
|
|
95
|
+
* `window.volterGame` can make — and they grow with the contract; nothing
|
|
96
96
|
* switches exhaustively on this type, so no consumer breaks when it does.
|
|
97
97
|
*/
|
|
98
98
|
export type CapabilityCoverageSeam =
|
|
@@ -212,7 +212,7 @@ export interface SystemsPresence {
|
|
|
212
212
|
* and this file never calls any of them.
|
|
213
213
|
*/
|
|
214
214
|
export interface ContractPresence {
|
|
215
|
-
/** `readGameContract()` answered at all (a v1 `window.
|
|
215
|
+
/** `readGameContract()` answered at all (a v1 `window.volterGame`). */
|
|
216
216
|
readonly declared: boolean;
|
|
217
217
|
readonly root: boolean;
|
|
218
218
|
readonly start: boolean;
|
|
@@ -224,7 +224,7 @@ export interface ContractPresence {
|
|
|
224
224
|
}
|
|
225
225
|
|
|
226
226
|
/** Fold a read contract (or its absence) into presence flags. */
|
|
227
|
-
export function measureGameContract(contract:
|
|
227
|
+
export function measureGameContract(contract: VolterGameContract | null): ContractPresence {
|
|
228
228
|
const systems = contract?.systems;
|
|
229
229
|
return {
|
|
230
230
|
declared: contract !== null,
|
|
@@ -276,7 +276,7 @@ export interface WriteReachFacts {
|
|
|
276
276
|
* reads healthy while every one of the 12 belongs to one lane and a whole
|
|
277
277
|
* other lane — a world's physics-placed cargo, or its level-data records —
|
|
278
278
|
* goes unwritten and unexercised. A per-kind tally is what makes an
|
|
279
|
-
* exhaustive walk possible
|
|
279
|
+
* exhaustive walk possible.
|
|
280
280
|
*
|
|
281
281
|
* Optional, because callers older than the vocabulary supply none and a
|
|
282
282
|
* fabricated zero for every kind would read as a measurement.
|
|
@@ -307,7 +307,7 @@ export interface DataWriterFacts {
|
|
|
307
307
|
* actually holds an adapter for this slot. That is the strongest available
|
|
308
308
|
* statement: it is the same object the editor's panels will call.
|
|
309
309
|
* - `empty` — the game POSITIVELY answered "I have no X" through
|
|
310
|
-
* `window.
|
|
310
|
+
* `window.volterGame.systems.systemAdapters` (`evidence` is its own words).
|
|
311
311
|
* - `malformed` — a declaration that could not be honoured; `evidence` is the
|
|
312
312
|
* projection's reason. Reported so a broken shim is loud, never silently
|
|
313
313
|
* read as either of the two terminal states.
|
|
@@ -327,7 +327,7 @@ export interface SystemAdapterMeasurement {
|
|
|
327
327
|
* shared default would state wrongly.
|
|
328
328
|
*
|
|
329
329
|
* This is what keeps the derivation provenance-NEUTRAL. The default fix text
|
|
330
|
-
* below names `window.
|
|
330
|
+
* below names `window.volterGame.systems…`, which is true of a game that
|
|
331
331
|
* declares a contract and false of a first-party one; rather than have the
|
|
332
332
|
* row builder ask WHO produced the measurement, the measurement carries the
|
|
333
333
|
* answer as a fact (`coverage/system-adapter-coverage.ts` supplies it for a
|
|
@@ -474,7 +474,7 @@ type ResolvedCoverageFacts = Required<CapabilityCoverageFacts>;
|
|
|
474
474
|
|
|
475
475
|
/**
|
|
476
476
|
* The route for a game whose bytes may not be touched — which is EVERY
|
|
477
|
-
* repo-vendored ingest, by doctrine. "Declare `window.
|
|
477
|
+
* repo-vendored ingest, by doctrine. "Declare `window.volterGame.root`" is the
|
|
478
478
|
* mechanism, but a game nobody may edit cannot declare anything, so a fix that
|
|
479
479
|
* stopped there would be telling the reader to do something they are not
|
|
480
480
|
* allowed to do. Both host-side carriers now exist, so this names them and
|
|
@@ -483,8 +483,8 @@ type ResolvedCoverageFacts = Required<CapabilityCoverageFacts>;
|
|
|
483
483
|
const UNMODIFIABLE_ROUTE =
|
|
484
484
|
'a game whose bytes must stay unmodified declares it from a host-side carrier ' +
|
|
485
485
|
'instead — a contract shim beside the game (`ingest.contractShim` in its ' +
|
|
486
|
-
'
|
|
487
|
-
'public/ingest/simcity/
|
|
486
|
+
'volter.project.json, injected ahead of the game`s entry module; see ' +
|
|
487
|
+
'public/ingest/simcity/volter.shim.js) or a recorded patch';
|
|
488
488
|
|
|
489
489
|
const CONTRACT_FIX = (endpoint: string): string =>
|
|
490
490
|
`declare ${endpoint} in the game's own entry; ${UNMODIFIABLE_ROUTE}`;
|
|
@@ -656,7 +656,7 @@ function loopRow(facts: ResolvedCoverageFacts): CapabilityCoverageRow {
|
|
|
656
656
|
missing:
|
|
657
657
|
"nothing in this editor controls the game's frames: ▶/⏸ and Step do not stop it, and " +
|
|
658
658
|
'Edit mode cannot be quiet — it keeps simulating while you author',
|
|
659
|
-
fix: CONTRACT_FIX('window.
|
|
659
|
+
fix: CONTRACT_FIX('window.volterGame.lifecycle.pause + resume'),
|
|
660
660
|
};
|
|
661
661
|
}
|
|
662
662
|
// No verdict, and that is its own answer — never a gap. The caller supplies
|
|
@@ -681,7 +681,7 @@ function contractRootRow(facts: ResolvedCoverageFacts): CapabilityCoverageRow {
|
|
|
681
681
|
detail: `the game declares a root, but the seam failed — ${facts.contract.proof.root.detail}`,
|
|
682
682
|
missing:
|
|
683
683
|
'the host cannot trust that the declared element owns the game DOM rather than an unrelated or detached subtree',
|
|
684
|
-
fix: CONTRACT_FIX('window.
|
|
684
|
+
fix: CONTRACT_FIX('window.volterGame.root'),
|
|
685
685
|
};
|
|
686
686
|
}
|
|
687
687
|
return {
|
|
@@ -703,7 +703,7 @@ function contractRootRow(facts: ResolvedCoverageFacts): CapabilityCoverageRow {
|
|
|
703
703
|
'the host adopts the bare canvas, so any DOM this game owns beside it (HUD, overlays, ' +
|
|
704
704
|
'portals) is stranded at page level over the editor chrome instead of living in the ' +
|
|
705
705
|
'game pane',
|
|
706
|
-
fix: CONTRACT_FIX('window.
|
|
706
|
+
fix: CONTRACT_FIX('window.volterGame.root'),
|
|
707
707
|
};
|
|
708
708
|
}
|
|
709
709
|
|
|
@@ -716,7 +716,7 @@ function contractStartRow(facts: ResolvedCoverageFacts): CapabilityCoverageRow {
|
|
|
716
716
|
detail: `lifecycle.start is declared but failed — ${facts.contract.proof.start.detail}`,
|
|
717
717
|
missing:
|
|
718
718
|
'the mount cannot prove it starts cold and enters a session only when Play requests one',
|
|
719
|
-
fix: CONTRACT_FIX('window.
|
|
719
|
+
fix: CONTRACT_FIX('window.volterGame.lifecycle.start'),
|
|
720
720
|
};
|
|
721
721
|
}
|
|
722
722
|
return {
|
|
@@ -738,7 +738,7 @@ function contractStartRow(facts: ResolvedCoverageFacts): CapabilityCoverageRow {
|
|
|
738
738
|
'the mount cannot be COLD: this game runs its session side-effects (backend ' +
|
|
739
739
|
'connections, audio, narrative) as it loads, so opening it in the editor starts ' +
|
|
740
740
|
'playing it, and ▶ is a wire event the game may ignore',
|
|
741
|
-
fix: CONTRACT_FIX('window.
|
|
741
|
+
fix: CONTRACT_FIX('window.volterGame.lifecycle.start'),
|
|
742
742
|
};
|
|
743
743
|
}
|
|
744
744
|
|
|
@@ -792,7 +792,7 @@ function contractPauseRow(facts: ResolvedCoverageFacts): CapabilityCoverageRow {
|
|
|
792
792
|
status: 'gap',
|
|
793
793
|
detail: `pause/resume is declared but failed — ${failed.detail}`,
|
|
794
794
|
missing: pauseFallbackCost(facts),
|
|
795
|
-
fix: CONTRACT_FIX('window.
|
|
795
|
+
fix: CONTRACT_FIX('window.volterGame.lifecycle.pause + resume'),
|
|
796
796
|
};
|
|
797
797
|
}
|
|
798
798
|
return {
|
|
@@ -814,7 +814,7 @@ function contractPauseRow(facts: ResolvedCoverageFacts): CapabilityCoverageRow {
|
|
|
814
814
|
? 'a contract is declared, but it names no lifecycle.pause/resume'
|
|
815
815
|
: 'no game→host contract is declared at all',
|
|
816
816
|
missing: pauseFallbackCost(facts),
|
|
817
|
-
fix: CONTRACT_FIX('window.
|
|
817
|
+
fix: CONTRACT_FIX('window.volterGame.lifecycle.pause + resume'),
|
|
818
818
|
};
|
|
819
819
|
}
|
|
820
820
|
|
|
@@ -989,7 +989,7 @@ function systemsRow(facts: ResolvedCoverageFacts): CapabilityCoverageRow {
|
|
|
989
989
|
detail: `the systems carrier is declared but failed — ${facts.contract.proof.systems.detail}`,
|
|
990
990
|
missing:
|
|
991
991
|
'`game.commands()` / `game.providers()` cannot safely enumerate or invoke this carrier',
|
|
992
|
-
fix: CONTRACT_FIX('window.
|
|
992
|
+
fix: CONTRACT_FIX('window.volterGame.systems (commands + state)'),
|
|
993
993
|
};
|
|
994
994
|
}
|
|
995
995
|
return {
|
|
@@ -1013,7 +1013,7 @@ function systemsRow(facts: ResolvedCoverageFacts): CapabilityCoverageRow {
|
|
|
1013
1013
|
'this game exposes no verbs and no state to drive or read it with: `game.commands()` and ' +
|
|
1014
1014
|
'`game.providers()` enumerate nothing, so there is no `game.command(...)` to play it from ' +
|
|
1015
1015
|
`${commandLine('eval')} and no \`game.state(...)\` for ${commandLine('status')} to see anything of what it is doing`,
|
|
1016
|
-
fix: CONTRACT_FIX('window.
|
|
1016
|
+
fix: CONTRACT_FIX('window.volterGame.systems (commands + state)'),
|
|
1017
1017
|
};
|
|
1018
1018
|
}
|
|
1019
1019
|
|
|
@@ -1063,9 +1063,9 @@ function systemSlotFix(slot: SystemAdapterSlot): string {
|
|
|
1063
1063
|
);
|
|
1064
1064
|
}
|
|
1065
1065
|
if (slot === 'debug') {
|
|
1066
|
-
return CONTRACT_FIX('window.
|
|
1066
|
+
return CONTRACT_FIX('window.volterGame.systems (commands + state)');
|
|
1067
1067
|
}
|
|
1068
|
-
return CONTRACT_FIX(`window.
|
|
1068
|
+
return CONTRACT_FIX(`window.volterGame.systems.systemAdapters.${slot}`);
|
|
1069
1069
|
}
|
|
1070
1070
|
|
|
1071
1071
|
/**
|
|
@@ -1571,7 +1571,7 @@ export function formatCapabilityCoverageBlocks(
|
|
|
1571
1571
|
* The console door, ONCE PER MOUNT.
|
|
1572
1572
|
*
|
|
1573
1573
|
* The report is derived on demand (the status facet re-derives it on every
|
|
1574
|
-
* read, so `
|
|
1574
|
+
* read, so `volter-game-editor status` never serves a verdict older than the question), which
|
|
1575
1575
|
* makes "print it" a thing that could happen many times — per status poll, per
|
|
1576
1576
|
* re-render, in the limit per frame. The guard is a mount token: the same
|
|
1577
1577
|
* mount's report is emitted once and then never again, and a NEW mount emits
|
|
@@ -7,9 +7,9 @@ import {
|
|
|
7
7
|
type SeamEvidenceVerdict,
|
|
8
8
|
} from '@volter/editor-project/adapter';
|
|
9
9
|
import type {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
VolterGameContract,
|
|
11
|
+
VolterGameLifecycle,
|
|
12
|
+
VolterGameSystems,
|
|
13
13
|
} from '@volter/editor-project/adapter/ingest/game-contract';
|
|
14
14
|
import {
|
|
15
15
|
GAME_CONTRACT_SHAPE,
|
|
@@ -21,7 +21,7 @@ import { liveSeamEvidence } from '@volter/editor-sdk/kit/live-seam-evidence';
|
|
|
21
21
|
const EPOCHS = new WeakMap<object, string>();
|
|
22
22
|
let nextEpoch = 1;
|
|
23
23
|
|
|
24
|
-
export function gameContractEpoch(contract:
|
|
24
|
+
export function gameContractEpoch(contract: VolterGameContract): string {
|
|
25
25
|
const found = EPOCHS.get(contract);
|
|
26
26
|
if (found) return found;
|
|
27
27
|
const epoch = `contract-${nextEpoch++}`;
|
|
@@ -38,7 +38,7 @@ export interface GameContractEvidence {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
export function inspectGameContractSeams(options: {
|
|
41
|
-
readonly contract:
|
|
41
|
+
readonly contract: VolterGameContract;
|
|
42
42
|
readonly subject?: string | undefined;
|
|
43
43
|
readonly epoch?: string | undefined;
|
|
44
44
|
readonly receipts?: readonly SeamEvidenceReceipt[] | undefined;
|
|
@@ -46,21 +46,21 @@ export function inspectGameContractSeams(options: {
|
|
|
46
46
|
const subject = options.subject ?? 'game';
|
|
47
47
|
const epoch = options.epoch ?? gameContractEpoch(options.contract);
|
|
48
48
|
const receipts: SeamEvidenceReceipt[] = [
|
|
49
|
-
...inspectSeamShape<
|
|
49
|
+
...inspectSeamShape<VolterGameContract>({
|
|
50
50
|
prefix: 'contract',
|
|
51
51
|
value: options.contract,
|
|
52
52
|
shape: GAME_CONTRACT_SHAPE,
|
|
53
53
|
subject,
|
|
54
54
|
epoch,
|
|
55
55
|
}),
|
|
56
|
-
...inspectSeamShape<
|
|
56
|
+
...inspectSeamShape<VolterGameLifecycle>({
|
|
57
57
|
prefix: 'contract.lifecycle',
|
|
58
58
|
value: options.contract.lifecycle,
|
|
59
59
|
shape: GAME_LIFECYCLE_SHAPE,
|
|
60
60
|
subject,
|
|
61
61
|
epoch,
|
|
62
62
|
}),
|
|
63
|
-
...inspectSeamShape<
|
|
63
|
+
...inspectSeamShape<VolterGameSystems>({
|
|
64
64
|
prefix: 'contract.systems',
|
|
65
65
|
value: options.contract.systems,
|
|
66
66
|
shape: GAME_SYSTEMS_SHAPE,
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* something the product itself already acts on:
|
|
23
23
|
*
|
|
24
24
|
* - `export` reads what the export path ACTUALLY requires of a project, taken
|
|
25
|
-
* from the door's own source: `create-
|
|
25
|
+
* from the door's own source: `create-volter-project/src/deploy.ts` runs
|
|
26
26
|
* `npm run build` in the project (`runProjectBuild`) and stages the `dist/`
|
|
27
27
|
* that emits (`stageBuildOutput`). Ingest roots pass that door because the
|
|
28
28
|
* ingest project's own build is already its standalone game; arbitrary
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* `editor.*` family — every mounted root, native or ingested, gets a row per
|
|
9
9
|
* `AuthoringAdapter` provider. It deliberately keeps ONLY that family, on the
|
|
10
10
|
* grounds that the remaining facts are "the ingested lane's own questions".
|
|
11
|
-
* That is true of `contract.*` (a native game declares no `window.
|
|
11
|
+
* That is true of `contract.*` (a native game declares no `window.volterGame`
|
|
12
12
|
* and never will) and false of `system.*`: a `SystemAdapters` slot is read off
|
|
13
13
|
* the editor's OWN live registry (`authoring/active-systems.ts`), which a
|
|
14
14
|
* native game fills exactly as an ingested one does. So the slots went
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
* `networking` is the exception, and it is the whole point. Nothing engine-side
|
|
32
32
|
* builds it: a game registers a `NetworkingAdapter` itself. So its absence is
|
|
33
33
|
* ruled out only by the PROJECT'S OWN FILE — no `server` block in
|
|
34
|
-
* `
|
|
34
|
+
* `volter.project.json` means there is no room to describe. A game that DOES
|
|
35
35
|
* declare a room and binds nothing is `unanswered`, i.e. a standing warning.
|
|
36
36
|
*
|
|
37
37
|
* ## Provenance neutrality
|
|
@@ -59,7 +59,7 @@ import { inspectSystemAdapterSeam } from '@volter/editor-sdk/kit/system-seam-evi
|
|
|
59
59
|
* it. Both facts are read off things that already exist; neither is a guess. */
|
|
60
60
|
export interface NativeProjectFacts {
|
|
61
61
|
/**
|
|
62
|
-
* `
|
|
62
|
+
* `volter.project.json` declares a `server` block (a Colyseus room) — read from
|
|
63
63
|
* the active project view (`project-manager.ts`'s `ActiveProject.server`).
|
|
64
64
|
* The decisive project-level declaration for whether this game is networked.
|
|
65
65
|
*/
|
|
@@ -118,7 +118,7 @@ export interface NativeProjectFacts {
|
|
|
118
118
|
*/
|
|
119
119
|
const SLOT_LIBRARIES: Partial<Record<SystemAdapterSlot, readonly string[]>> = {
|
|
120
120
|
physics: ['@react-three/rapier', '@dimforge/rapier3d-compat', '@dimforge/rapier2d-compat'],
|
|
121
|
-
networking: ['colyseus.js'],
|
|
121
|
+
networking: ['@colyseus/sdk', 'colyseus.js'],
|
|
122
122
|
navigation: ['recast-navigation', '@recast-navigation/core'],
|
|
123
123
|
audio: ['tone', 'howler'],
|
|
124
124
|
};
|
|
@@ -213,7 +213,7 @@ function reasonAcknowledges(reason: string, library: string): boolean {
|
|
|
213
213
|
|
|
214
214
|
/** The file fact that rules `networking` out, in the file's own terms. */
|
|
215
215
|
const NO_SERVER_EVIDENCE =
|
|
216
|
-
"this project's
|
|
216
|
+
"this project's volter.project.json declares no `server` block, so there is no room for a " +
|
|
217
217
|
'NetworkingAdapter to describe';
|
|
218
218
|
|
|
219
219
|
/**
|
|
@@ -251,7 +251,7 @@ const ENGINE_OWNED_EVIDENCE: Partial<Record<SystemAdapterSlot, string>> = {
|
|
|
251
251
|
};
|
|
252
252
|
|
|
253
253
|
/** The fix a native game's author can actually act on, per slot. Never the
|
|
254
|
-
* ingested lane's `window.
|
|
254
|
+
* ingested lane's `window.volterGame` text, which would be false here. */
|
|
255
255
|
function nativeSlotFix(slot: SystemAdapterSlot, facts: NativeProjectFacts): string {
|
|
256
256
|
if (!facts.mounted) {
|
|
257
257
|
return 'nothing is mounted, so the live system registry has not been asked yet — enter Play, or open a project whose roots mount, and the slot reports its real state';
|
|
@@ -349,6 +349,19 @@ export function measureNativeSystemAdapters(
|
|
|
349
349
|
: `declared absent by root "${absence.rootId}": ${absence.reason}${lastMountNote}`,
|
|
350
350
|
};
|
|
351
351
|
}
|
|
352
|
+
// A Colyseus client is the editor's to observe: Play binds its observer of the game's room
|
|
353
|
+
// sockets (`adapter-runtime-bindings.ts`'s `withObservedNetworking`), so an unbound slot while
|
|
354
|
+
// nothing plays is that observer not yet attached, not an adapter the game owes.
|
|
355
|
+
if (slot === 'networking' && libraries.length > 0) {
|
|
356
|
+
return {
|
|
357
|
+
slot,
|
|
358
|
+
state: 'empty',
|
|
359
|
+
attestedBy: 'host',
|
|
360
|
+
evidence:
|
|
361
|
+
`this project ships ${libraries.join(' + ')}; the editor observes its room sockets from ` +
|
|
362
|
+
'Play, and nothing is connected while editing',
|
|
363
|
+
};
|
|
364
|
+
}
|
|
352
365
|
// Nothing declared, and the library IS here: the host's inference below
|
|
353
366
|
// would print a confident absence over real evidence to the contrary. That
|
|
354
367
|
// is an UNANSWERED question — the only state that is a work order — not a
|
|
@@ -9,14 +9,14 @@
|
|
|
9
9
|
* ordinary game code and there is no cooperative way for a game to know better.
|
|
10
10
|
*
|
|
11
11
|
* So the game realm's `location` is a facade: reads of the game's own
|
|
12
|
-
* query keep working, host control-plane keys (`play`, `project`, `
|
|
12
|
+
* query keep working, host control-plane keys (`play`, `project`, `volter-*`,
|
|
13
13
|
* …) are stripped from `search`/`href` (bubbo's `getUrlParam('play')` is a
|
|
14
14
|
* debug skip-to-Game; the editor's `?play=1` autoplay is not that flag),
|
|
15
15
|
* and every write that would NAVIGATE is refused with a `console.error`
|
|
16
16
|
* naming the game, the exact assignment, and this mechanism. Refused, never
|
|
17
17
|
* swallowed — a silent no-op would make "the quit button does nothing" an
|
|
18
18
|
* unexplainable bug, and the console line is a real product door
|
|
19
|
-
* (`
|
|
19
|
+
* (`volter-game-editor status` reports page console errors).
|
|
20
20
|
*
|
|
21
21
|
* `hash` is deliberately NOT refused: it changes the URL without leaving the
|
|
22
22
|
* document, and games use it for their own state.
|
|
@@ -49,7 +49,7 @@ const HOST_SEARCH_KEYS = new Set([
|
|
|
49
49
|
]);
|
|
50
50
|
|
|
51
51
|
function isHostSearchKey(key: string): boolean {
|
|
52
|
-
return HOST_SEARCH_KEYS.has(key) || key.startsWith('
|
|
52
|
+
return HOST_SEARCH_KEYS.has(key) || key.startsWith('volter');
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
/** The search string a game is allowed to read — host keys removed. */
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { commandLine } from '@volter/editor-sdk/kit/product-command';
|
|
2
2
|
/**
|
|
3
|
-
* THE MODULE LANE of `
|
|
3
|
+
* THE MODULE LANE of `volter-game-editor eval` — reach the RUNNING mount's own module
|
|
4
4
|
* instances, by served path, using URLs retained by their owning realm.
|
|
5
5
|
*
|
|
6
6
|
* WHY THE MOUNT STAMP IS LOAD-BEARING. Project modules are served per mount
|
|
7
|
-
* as `/<path>?
|
|
7
|
+
* as `/<path>?volter-mount=<id>` (`project-module-instance.ts` propagates the
|
|
8
8
|
* id through the whole project subtree), so browser module identity is
|
|
9
9
|
* per-instance BY URL. A bare `import('/src/sim/host.ts')` therefore loads a
|
|
10
10
|
* SECOND, never-ticked copy — the phantom-module trap the old string-command
|
|
@@ -25,15 +25,15 @@ import { commandLine } from '@volter/editor-sdk/kit/product-command';
|
|
|
25
25
|
// with a guard test on the one true owner.
|
|
26
26
|
/** Mirrors `project-module-url.ts`'s PROJECT_MOUNT_QUERY (guarded by test —
|
|
27
27
|
* the two must never drift). */
|
|
28
|
-
const PROJECT_MOUNT_QUERY = '
|
|
28
|
+
const PROJECT_MOUNT_QUERY = 'volter-mount';
|
|
29
29
|
|
|
30
30
|
let focusedInstanceProvider: (() => string) | null = null;
|
|
31
31
|
/** The packaged shell and source-served contributions share the host realm owner. */
|
|
32
32
|
function moduleUrls(instanceId: string): readonly string[] {
|
|
33
33
|
const host = globalThis as typeof globalThis & {
|
|
34
|
-
|
|
34
|
+
__volterGameModuleUrls?: (id: string) => readonly string[];
|
|
35
35
|
};
|
|
36
|
-
return host.
|
|
36
|
+
return host.__volterGameModuleUrls?.(instanceId) ?? [];
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/** Wired once by the editor shell (`command-listener.ts`) at boot. */
|
|
@@ -76,7 +76,7 @@ export function loadedGameModulePaths(instanceId: string): string[] {
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
/**
|
|
79
|
-
* The `modules` binding of `
|
|
79
|
+
* The `modules` binding of `volter-game-editor eval`'s `game.run` scope and of the
|
|
80
80
|
* `game-eval` op — a RESOLVER, `modules('src/sim/host.ts')`, that also
|
|
81
81
|
* ENUMERATES what it can resolve.
|
|
82
82
|
*
|
|
@@ -70,7 +70,7 @@ function captureOf(options?: boolean | AddEventListenerOptions | EventListenerOp
|
|
|
70
70
|
|
|
71
71
|
/** Mark carrying the text this observer last wrote, so its own write is not
|
|
72
72
|
* re-scoped and a game's next rewrite is. */
|
|
73
|
-
export const SCOPED_STYLE_MARK = '
|
|
73
|
+
export const SCOPED_STYLE_MARK = 'volterScopedCss';
|
|
74
74
|
|
|
75
75
|
/**
|
|
76
76
|
* The `@scope` rewriter, loaded on FIRST USE — the one page-side door to the
|
|
@@ -128,7 +128,7 @@ export function loadGameCssScoper(): Promise<(css: string) => string> {
|
|
|
128
128
|
* `url.fileURLToPath/pathToFileURL`,
|
|
129
129
|
* `source-map-js.SourceMapConsumer/SourceMapGenerator` — straight into the
|
|
130
130
|
* game realm's console, which made the console-silence contract unreachable in
|
|
131
|
-
* a fresh scaffold and every `
|
|
131
|
+
* a fresh scaffold and every `volter` verb exit non-zero (measured three times:
|
|
132
132
|
* the donut, cold-barrel and lit-lanes probes). The chain was
|
|
133
133
|
* `play-mode.enterPlayModeInner` -> `gated-globals.setGameSurface` ->
|
|
134
134
|
* `GameRealmPage.surface` -> `observeInjectedStyles` -> `import('../server/
|
|
@@ -356,7 +356,7 @@ export class GameRealmPage {
|
|
|
356
356
|
if (!surface) return null;
|
|
357
357
|
if (!this.headElement || this.headElement.parentElement !== surface) {
|
|
358
358
|
const head = document.createElement('div');
|
|
359
|
-
head.dataset['
|
|
359
|
+
head.dataset['volterGameHead'] = '';
|
|
360
360
|
head.hidden = true;
|
|
361
361
|
surface.prepend(head);
|
|
362
362
|
this.headElement = head;
|
|
@@ -26,7 +26,7 @@ import { disposeGameRealm } from './gated-globals';
|
|
|
26
26
|
*
|
|
27
27
|
* The report is the point: a game that released everything it registered is
|
|
28
28
|
* silent, and a game that did not is named, with the count, on the editor
|
|
29
|
-
* console (and therefore in `
|
|
29
|
+
* console (and therefore in `volter-game-editor console`). The editor reclaims either way.
|
|
30
30
|
*/
|
|
31
31
|
export function reclaimGameRealm(id: string, name: string): void {
|
|
32
32
|
const released = disposeGameRealm(id);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Live gameplay recording over the SAME clean game compositor used by
|
|
3
|
-
* `
|
|
3
|
+
* `volter-game-editor screenshot`: world canvases + DOM HUD, no editor chrome.
|
|
4
4
|
*
|
|
5
5
|
* This is intentionally a browser primitive, not a CLI workflow. The page
|
|
6
6
|
* owns `MediaRecorder` and the live pixels/audio, streams bounded chunks to
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* A counter for it was written, shipped and REMOVED in the same afternoon: it
|
|
22
22
|
* keyed on `domOverlays === 0` and therefore reported a clean zero over clips
|
|
23
23
|
* visibly missing their HUD. A measurement that can only ever say "fine" is
|
|
24
|
-
* worse than none, so the limit is stated — here, and in the ack `
|
|
24
|
+
* worse than none, so the limit is stated — here, and in the ack `volter-game-editor stop`
|
|
25
25
|
* prints — rather than counted. Read the world layer as complete evidence and
|
|
26
26
|
* the HUD as present-when-present.
|
|
27
27
|
*/
|
|
@@ -62,7 +62,7 @@ export interface GameplayRecordingStartOptions {
|
|
|
62
62
|
readonly canvasFrame?: CaptureOptions['canvasFrame'];
|
|
63
63
|
readonly audio?: AudioRecordingHandle | null | undefined;
|
|
64
64
|
/**
|
|
65
|
-
* The file this recording lands in, under the project's `.
|
|
65
|
+
* The file this recording lands in, under the project's `.volter/recordings/`.
|
|
66
66
|
* During Play, absent is named after the Gameplay Session; outside one it
|
|
67
67
|
* falls back to `play-latest.webm`. A name is the caller's own keepsake. The server owns the literals — see
|
|
68
68
|
* `editor-server.ts`'s `/__editor/recording/start`.
|
|
@@ -211,7 +211,7 @@ interface FrameClock {
|
|
|
211
211
|
* `setInterval` on the page is the obvious pacer and it is the wrong one for
|
|
212
212
|
* the case this recorder exists to serve. Chrome clamps a background tab's
|
|
213
213
|
* timers to 1 Hz, and to roughly 1/min once the tab has been hidden for five
|
|
214
|
-
* minutes — and an agent's `
|
|
214
|
+
* minutes — and an agent's `volter-game-editor edit` tab is hidden BY DESIGN (worktree
|
|
215
215
|
* sessions open behind the human's window; see `after-paint.ts` for the same
|
|
216
216
|
* fact stated for rAF). A 30 fps request would encode as a handful of frames.
|
|
217
217
|
*
|
|
@@ -370,7 +370,7 @@ function samplePreviewFrame(recording: ActiveRecording, mediaTimeMs: number): vo
|
|
|
370
370
|
*
|
|
371
371
|
* The refusal this replaced ("show the editor tab, then start recording")
|
|
372
372
|
* exempted exactly the sessions the recorder exists for: an agent's
|
|
373
|
-
* `
|
|
373
|
+
* `volter-game-editor edit` tab opens behind the human's window and stays hidden for the
|
|
374
374
|
* whole run, so every unattended playthrough — the ones with no human to
|
|
375
375
|
* describe what happened — would have produced no evidence at all. What a
|
|
376
376
|
* hidden tab actually costs is two things, and both are handled rather than
|
|
@@ -248,7 +248,7 @@ function realmFunction(value: unknown, receiver: object): unknown {
|
|
|
248
248
|
* one pauses its siblings. Neither is fixable in the presentation layer.
|
|
249
249
|
*
|
|
250
250
|
* The lookup key comes from the MODULE'S OWN URL: every project module served
|
|
251
|
-
* under a mount carries `?
|
|
251
|
+
* under a mount carries `?volter-mount=<id>` (the isolation transform), and the
|
|
252
252
|
* prelude passes `import.meta.url` to {@link gameRealmForModuleUrl}. So a
|
|
253
253
|
* module resolves its own realm with no plugin involvement and no threading.
|
|
254
254
|
*/
|
|
@@ -402,7 +402,7 @@ export function gameSurfaces(): { surface: HTMLElement; gate: () => boolean }[]
|
|
|
402
402
|
*/
|
|
403
403
|
function gateFor(id: string): () => boolean {
|
|
404
404
|
// AND THE SURFACE TERM (U2). `surfaceHoldsKeyboard()` is true standalone and
|
|
405
|
-
// under the frame is the workbench's own "is the
|
|
405
|
+
// under the frame is the workbench's own "is the volter pane the active
|
|
406
406
|
// editor?", so a keystroke aimed at Monaco in the group beside a running
|
|
407
407
|
// game never reaches the game's listeners. It is read HERE, once, rather
|
|
408
408
|
// than folded into each lane's own predicate, because this is the one place
|
|
@@ -434,7 +434,7 @@ function realmGenerationGate(id: string, live: RealmLiveness): () => boolean {
|
|
|
434
434
|
function mountIdOfUrl(moduleUrl: string): string {
|
|
435
435
|
const query = moduleUrl.split('?')[1];
|
|
436
436
|
if (query === undefined) return DEFAULT_REALM_ID;
|
|
437
|
-
return new URLSearchParams(query).get('
|
|
437
|
+
return new URLSearchParams(query).get('volter-mount') ?? DEFAULT_REALM_ID;
|
|
438
438
|
}
|
|
439
439
|
|
|
440
440
|
/** The realm whose code is synchronously executing right now. Console capture
|
|
@@ -865,12 +865,12 @@ export function makeGatedProxy<T extends EventTarget>(
|
|
|
865
865
|
};
|
|
866
866
|
|
|
867
867
|
/** Report a refused navigation on the page's own console — which is a real
|
|
868
|
-
* product door: `
|
|
868
|
+
* product door: `volter-game-editor status` reports page console errors. */
|
|
869
869
|
const refuseNavigation = (attempt: string): void => {
|
|
870
870
|
// A NEW native console.error site, suppressed to keep this task's diff at
|
|
871
871
|
// zero new lint warnings — and it is deliberately native rather than
|
|
872
872
|
// `editorConsole`: game code runs in the page, and the page console is the
|
|
873
|
-
// surface `
|
|
873
|
+
// surface `volter-game-editor status` already reads (same reasoning as
|
|
874
874
|
// `ingest-root-adapter.ts`'s loop-gate warning).
|
|
875
875
|
// biome-ignore lint/suspicious/noConsole: see comment above
|
|
876
876
|
console.error(refusedNavigationMessage(attempt));
|
|
@@ -1115,9 +1115,9 @@ function createOwnedRealmProxy<T extends EventTarget>(
|
|
|
1115
1115
|
if (refusalReported) return;
|
|
1116
1116
|
refusalReported = true;
|
|
1117
1117
|
const realm = options.realmId ? `"${options.realmId}"` : 'the default realm';
|
|
1118
|
-
// biome-ignore lint/suspicious/noConsole: game code runs in the page, and the page console is the surface `
|
|
1118
|
+
// biome-ignore lint/suspicious/noConsole: game code runs in the page, and the page console is the surface `volter-game-editor status` reads (same reasoning as `refuseNavigation` above)
|
|
1119
1119
|
console.warn(
|
|
1120
|
-
`
|
|
1120
|
+
`volter: a stopped game tried to register ${what} on its ${options.role} after its realm ` +
|
|
1121
1121
|
`(${realm}) was reclaimed; refused. Later refusals in this generation are silent.`,
|
|
1122
1122
|
);
|
|
1123
1123
|
};
|
|
@@ -1460,10 +1460,10 @@ function publishDefaultRealmGlobals(): void {
|
|
|
1460
1460
|
const g = globalThis as unknown as Record<string, unknown>;
|
|
1461
1461
|
const base = gameRealmForMountId();
|
|
1462
1462
|
loopGate = defaultLoopGate;
|
|
1463
|
-
g['
|
|
1464
|
-
g['
|
|
1465
|
-
g['
|
|
1466
|
-
g['
|
|
1463
|
+
g['__volterGameTimers'] = base.timers;
|
|
1464
|
+
g['__volterGameWindow'] = base.window;
|
|
1465
|
+
g['__volterGameDocument'] = base.document;
|
|
1466
|
+
g['__volterGameConsole'] = base.console;
|
|
1467
1467
|
}
|
|
1468
1468
|
|
|
1469
1469
|
/**
|
|
@@ -1494,8 +1494,8 @@ export function installGatedGameGlobals(): void {
|
|
|
1494
1494
|
// The microtask pop bounds the window: it runs when the current synchronous
|
|
1495
1495
|
// evaluation chunk yields, before the mount's own import().then
|
|
1496
1496
|
// continuation (queued later) can run editor code.
|
|
1497
|
-
g['
|
|
1498
|
-
g['
|
|
1497
|
+
g['__volterGameModuleUrls'] = loadedGameRealmModuleUrls;
|
|
1498
|
+
g['__volterGameRealm'] = (mountId: string): GameRealm => {
|
|
1499
1499
|
executingRealms.push(mountId);
|
|
1500
1500
|
queueMicrotask(() => {
|
|
1501
1501
|
const at = executingRealms.lastIndexOf(mountId);
|
|
@@ -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
|
}
|