@volter/editor-game 0.5.65
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/LICENSE +661 -0
- package/NOTICE +23 -0
- package/contributions/asset-budget-asset.menu.ts +26 -0
- package/contributions/asset-budget.action.ts +18 -0
- package/contributions/asset-budget.document.tsx +21 -0
- package/contributions/asset-budget.menu.ts +22 -0
- package/contributions/audio-unlock.service.ts +17 -0
- package/contributions/audio.utility.tsx +15 -0
- package/contributions/autoplay.service.ts +48 -0
- package/contributions/bridge.command.ts +172 -0
- package/contributions/build-profiles.document.tsx +21 -0
- package/contributions/build-progress.status.tsx +45 -0
- package/contributions/build.action.ts +26 -0
- package/contributions/build.command.ts +27 -0
- package/contributions/build.header.tsx +37 -0
- package/contributions/build.menu.ts +31 -0
- package/contributions/build.service.ts +31 -0
- package/contributions/connection.status.tsx +43 -0
- package/contributions/coverage.service.ts +118 -0
- package/contributions/edit-mode-audio.service.ts +23 -0
- package/contributions/edit-mode-networking.service.ts +30 -0
- package/contributions/game-document.service.ts +25 -0
- package/contributions/game-eval.command.ts +210 -0
- package/contributions/game.layout.ts +19 -0
- package/contributions/gameplay.command.ts +255 -0
- package/contributions/generation.service.ts +96 -0
- package/contributions/generations.status.tsx +53 -0
- package/contributions/ingest.service.ts +80 -0
- package/contributions/instances.command.ts +75 -0
- package/contributions/navmesh.menu.ts +39 -0
- package/contributions/navmesh.service.ts +19 -0
- package/contributions/network.utility.tsx +17 -0
- package/contributions/play.command.ts +355 -0
- package/contributions/profiler.action.ts +16 -0
- package/contributions/profiler.menu.ts +22 -0
- package/contributions/profiler.utility.tsx +17 -0
- package/contributions/react/component-board.service.ts +21 -0
- package/contributions/react/design-time-mount.service.ts +60 -0
- package/contributions/react/pasteboard.action.ts +41 -0
- package/contributions/react/react-inspector.service.ts +85 -0
- package/contributions/react/story-documents.service.ts +44 -0
- package/contributions/scene-document.service.ts +32 -0
- package/contributions/state-watch.action.ts +17 -0
- package/contributions/state-watch.menu.ts +23 -0
- package/contributions/state-watch.utility.tsx +20 -0
- package/contributions/team-playtest.service.ts +124 -0
- package/contributions/three/camera-runtime.inspector.tsx +34 -0
- package/contributions/three/component-board.service.ts +24 -0
- package/contributions/three/component-verbs.command.ts +110 -0
- package/contributions/three/component-verbs.service.ts +92 -0
- package/contributions/three/constraints.inspector.tsx +34 -0
- package/contributions/three/model-asset-sections.service.ts +26 -0
- package/contributions/three/reflection-probe-capture.inspector.tsx +32 -0
- package/contributions/three/story-documents.service.ts +31 -0
- package/contributions/three/three-authoring.service.ts +66 -0
- package/contributions/transport.header.tsx +19 -0
- package/contributions/xstate-behavior.action.ts +42 -0
- package/contributions/xstate-behavior.document.tsx +73 -0
- package/contributions/xstate-behavior.inspector.tsx +28 -0
- package/contributions/xstate-behavior.menu.ts +23 -0
- package/package.json +144 -0
- package/src/asset-budget/AssetBudgetPanel.tsx +1172 -0
- package/src/asset-budget/asset-budget-model.ts +799 -0
- package/src/asset-budget/basis-encoder.ts +165 -0
- package/src/asset-budget/gltf-io.ts +154 -0
- package/src/asset-budget/gltf-optimize.ts +384 -0
- package/src/asset-budget/image-dims.ts +78 -0
- package/src/asset-budget/optimize-apply.ts +221 -0
- package/src/audio/AudioDebuggerPanel.tsx +457 -0
- package/src/audio/audio-debugger-model.ts +87 -0
- package/src/bridge/call.ts +60 -0
- package/src/bridge/dispatch.ts +459 -0
- package/src/bridge/live-frames.ts +25 -0
- package/src/bridge/screenshot.ts +316 -0
- package/src/build/BuildProfilesPanel.tsx +476 -0
- package/src/build/build-session.ts +247 -0
- package/src/build/format-bytes.ts +14 -0
- package/src/command-results.ts +36 -0
- package/src/coverage/live-authoring-surface.ts +30 -0
- package/src/coverage/live-project-verbs.ts +162 -0
- package/src/coverage/native-system-coverage.ts +143 -0
- package/src/coverage/root-coverage.ts +79 -0
- package/src/coverage/session-coverage.ts +193 -0
- package/src/design-system-stories/ApplicationChrome.stories.tsx +100 -0
- package/src/design-system-stories/InspectorNarrowBodies.stories.tsx +406 -0
- package/src/edit-mode/edit-mode-audio.ts +56 -0
- package/src/edit-mode/edit-mode-networking.ts +112 -0
- package/src/game-document/DevicePresetPicker.tsx +84 -0
- package/src/game-document/GameCaptureFrameButton.tsx +45 -0
- package/src/game-document/GameDocument.tsx +215 -0
- package/src/game-document/GamePanel.tsx +662 -0
- package/src/game-document/InstanceInspectorPicker.tsx +183 -0
- package/src/game-document/crowd-debug.ts +183 -0
- package/src/game-document/device-preview.ts +336 -0
- package/src/game-document/game-view-store.ts +107 -0
- package/src/game-document/physics-debug.ts +187 -0
- package/src/generation/GenerationActivity.tsx +421 -0
- package/src/generation/GenerationGallery.css +231 -0
- package/src/generation/generation-documents.tsx +338 -0
- package/src/generation/generation-jobs.ts +128 -0
- package/src/generation/generation-presentation.ts +257 -0
- package/src/host/adapter-reach.ts +445 -0
- package/src/host/adapter-runtime-bindings.ts +303 -0
- package/src/host/after-paint.ts +112 -0
- package/src/host/api/configurations.ts +66 -0
- package/src/host/authoring/babylon-authoring-adapter.ts +703 -0
- package/src/host/authoring/canvas-runtime-recognition.ts +50 -0
- package/src/host/authoring/contract-hierarchy-authoring.ts +144 -0
- package/src/host/authoring/contract-scenes-stories.ts +204 -0
- package/src/host/authoring/creation-site-related.ts +55 -0
- package/src/host/authoring/ephemeral-persistence.ts +29 -0
- package/src/host/authoring/gesture-persist.ts +84 -0
- package/src/host/authoring/ingest-data-writer.ts +232 -0
- package/src/host/authoring/ingest-source-persistence.ts +826 -0
- package/src/host/authoring/mount-isolated-pixi-screen.ts +250 -0
- package/src/host/authoring/mounted-authoring.ts +41 -0
- package/src/host/authoring/owned-pixi-ticker-listeners.ts +96 -0
- package/src/host/authoring/phaser-live-authoring-adapter.ts +265 -0
- package/src/host/authoring/pixi-authoring-adapter.ts +1554 -0
- package/src/host/authoring/pixi-creation-site-write-target.ts +60 -0
- package/src/host/authoring/pixi-isolation-assets.ts +25 -0
- package/src/host/authoring/pixi-live-write-target.ts +979 -0
- package/src/host/authoring/pixi-source-identity.ts +141 -0
- package/src/host/authoring/pixi-still-presentation.ts +71 -0
- package/src/host/authoring/pixi-structure-history.ts +237 -0
- package/src/host/authoring/pixi-transform-channels.ts +205 -0
- package/src/host/authoring/selection-remount-handoff.ts +23 -0
- package/src/host/authoring/source-persistence-backend.ts +373 -0
- package/src/host/authoring/source-refresh-revisions.ts +81 -0
- package/src/host/authoring/struct-write-pipe.ts +143 -0
- package/src/host/auto-frame-window.ts +89 -0
- package/src/host/binding-resolver.ts +393 -0
- package/src/host/browser-transpile.ts +631 -0
- package/src/host/canvas-entry-runtime.ts +95 -0
- package/src/host/components/CameraAuthoringOverlay.tsx +216 -0
- package/src/host/components/HeaderTelemetry.tsx +341 -0
- package/src/host/components/PixiIsolationSceneContent.tsx +280 -0
- package/src/host/components/ResolutionPicker.tsx +89 -0
- package/src/host/components/ThreeIsolationSceneContent.tsx +180 -0
- package/src/host/components/frame-debugger-model.ts +579 -0
- package/src/host/components/header-telemetry-model.ts +74 -0
- package/src/host/components/scene-document.tsx +447 -0
- package/src/host/components/utility-view-state.ts +87 -0
- package/src/host/components/world-root-stage-binding.tsx +133 -0
- package/src/host/components/world-root-stage.ts +1046 -0
- package/src/host/coverage/authoring-read-probe.ts +583 -0
- package/src/host/coverage/capability-coverage.ts +1587 -0
- package/src/host/coverage/coverage-accounting.ts +261 -0
- package/src/host/coverage/game-contract-seam-evidence.ts +82 -0
- package/src/host/coverage/project-verb-coverage.ts +148 -0
- package/src/host/coverage/system-adapter-coverage.ts +373 -0
- package/src/host/design-system-stories/StoryLayout.tsx +104 -0
- package/src/host/design-system-stories/fixtures/authoring.ts +247 -0
- package/src/host/design-system-stories/fixtures/editor-runtime.tsx +115 -0
- package/src/host/document-preview-three.ts +110 -0
- package/src/host/entry-adjudication.ts +89 -0
- package/src/host/game-location-guard.ts +150 -0
- package/src/host/game-module-access.ts +196 -0
- package/src/host/game-realm-page.ts +358 -0
- package/src/host/game-realm-reclaim.ts +55 -0
- package/src/host/game-realm-storage.ts +104 -0
- package/src/host/gameplay-export.ts +288 -0
- package/src/host/gameplay-recording.ts +717 -0
- package/src/host/gated-globals.ts +1511 -0
- package/src/host/history/json-history-resource.ts +254 -0
- package/src/host/ingest/registry.ts +261 -0
- package/src/host/instance-extract-actions.ts +120 -0
- package/src/host/instance-fork-actions.ts +120 -0
- package/src/host/play-control-hook.ts +26 -0
- package/src/host/playwright-shim.ts +479 -0
- package/src/host/projection/dom.ts +295 -0
- package/src/host/projection/pixi.ts +288 -0
- package/src/host/r3f-entry-runtime.ts +77 -0
- package/src/host/react-mount-runtime.ts +162 -0
- package/src/host/realm-services.ts +148 -0
- package/src/host/recording-preview.ts +106 -0
- package/src/host/roots/module-root.ts +203 -0
- package/src/host/roots/react-root.ts +181 -0
- package/src/host/same-realm-loop-gate.ts +544 -0
- package/src/host/scene-view-drawability.ts +69 -0
- package/src/host/sdk/tools.ts +31 -0
- package/src/host/served-bundle-runtime-modules.ts +331 -0
- package/src/host/server-log-bridge.ts +60 -0
- package/src/host/staged-projects.ts +24 -0
- package/src/host/stories/mounted-story-viewport-source.ts +64 -0
- package/src/host/stories/story-arg-descriptors.ts +50 -0
- package/src/host/stories/story-media-presence.ts +152 -0
- package/src/host/surface-content.ts +87 -0
- package/src/host/take-named-export.ts +23 -0
- package/src/host/three-ingest-runtime.ts +76 -0
- package/src/host/types-fastnoise-lite.d.ts +7 -0
- package/src/host/types-mikktspace.d.ts +20 -0
- package/src/host/types-troika-three-text.d.ts +7 -0
- package/src/host/use-active-performance-source.ts +53 -0
- package/src/host/viewport-pose-memory.ts +48 -0
- package/src/host/viewport-root-presentation.ts +40 -0
- package/src/ingest/active-ingest.ts +251 -0
- package/src/ingest/active-scene-navigation.ts +43 -0
- package/src/ingest/authoring/ingest-dom-surface-authoring.ts +191 -0
- package/src/ingest/authoring/ingest-root-adapter.ts +897 -0
- package/src/ingest/capture-wait-report.ts +122 -0
- package/src/ingest/deferred-ingest-play.ts +216 -0
- package/src/ingest/deferred-ingest-session.ts +23 -0
- package/src/ingest/discovery-public-ingest.ts +242 -0
- package/src/ingest/dom-stub-mark.ts +7 -0
- package/src/ingest/entry-load.ts +56 -0
- package/src/ingest/game-contract-realm.ts +34 -0
- package/src/ingest/game-pointer-lock.ts +89 -0
- package/src/ingest/held-scene-repaint.ts +73 -0
- package/src/ingest/host-surface-box.ts +174 -0
- package/src/ingest/ingest-boot-viewport.ts +54 -0
- package/src/ingest/ingest-canvas-scene-document.tsx +177 -0
- package/src/ingest/ingest-canvas-scene.ts +50 -0
- package/src/ingest/ingest-evidence-hook.ts +131 -0
- package/src/ingest/ingest-frame-snapshot.ts +183 -0
- package/src/ingest/ingest-play-commands.ts +134 -0
- package/src/ingest/ingest-play-control.ts +299 -0
- package/src/ingest/ingest-render-debug.ts +320 -0
- package/src/ingest/ingest-siblings.ts +487 -0
- package/src/ingest/ingest-status.ts +62 -0
- package/src/ingest/live-ingest-facet.ts +46 -0
- package/src/ingest/module-mode.ts +229 -0
- package/src/ingest/mount-canvas-ingest-root.ts +899 -0
- package/src/ingest/mount-coverage.ts +296 -0
- package/src/ingest/mount-dom-ingest-root.ts +282 -0
- package/src/ingest/mount-ingest-root.ts +744 -0
- package/src/ingest/mount-three-ingest-root.ts +366 -0
- package/src/ingest/resolve-canvas.ts +47 -0
- package/src/ingest/resolve-three.ts +123 -0
- package/src/ingest/served-bundle.ts +109 -0
- package/src/ingest/served-html-boot.ts +292 -0
- package/src/ingest/surface-canvas.ts +84 -0
- package/src/ingest/surface-dom.ts +84 -0
- package/src/ingest/surface-three.ts +128 -0
- package/src/ingest/types.ts +76 -0
- package/src/ingest/unmount-ingest-root.ts +224 -0
- package/src/navmesh/navmesh-actions.ts +27 -0
- package/src/navmesh/navmesh-handler.ts +237 -0
- package/src/navmesh/navmesh-workflow-store.ts +78 -0
- package/src/network/NetworkInspectorPanel.tsx +644 -0
- package/src/network/network-inspector-model.ts +225 -0
- package/src/play/play-boot-stall.ts +118 -0
- package/src/play/play-log-events.ts +26 -0
- package/src/play/play-mode.ts +2553 -0
- package/src/play/play-recording.ts +335 -0
- package/src/play/react-play-live-authoring.ts +165 -0
- package/src/play-bar/PlayBar.tsx +488 -0
- package/src/play-bar/PlayerCountPicker.tsx +100 -0
- package/src/profiler/FrameDebuggerPanel.tsx +458 -0
- package/src/profiler/PerformancePanel.tsx +1068 -0
- package/src/profiler/ProfilerPanel.tsx +53 -0
- package/src/profiler/frame-debugger-store.ts +97 -0
- package/src/profiler/main-thread-busy.ts +90 -0
- package/src/react/design-time-react-mount.ts +782 -0
- package/src/react/dom-authoring-adapter.ts +764 -0
- package/src/react/pasteboard-materialize.ts +154 -0
- package/src/react/react-inspector-section.tsx +2496 -0
- package/src/react/react-world-authoring-adapter.ts +3795 -0
- package/src/react/story-documents/story-args-section.ts +19 -0
- package/src/react/story-documents/story-documents.tsx +1380 -0
- package/src/react/story-paint-bounds.ts +94 -0
- package/src/react/ui-board-document.tsx +101 -0
- package/src/react/ui-board-title.ts +9 -0
- package/src/react/ui-component-board.ts +72 -0
- package/src/services/audio-pose-guard.ts +81 -0
- package/src/services/game-audio-unlock.ts +48 -0
- package/src/state-watch/StateWatchPanel.tsx +535 -0
- package/src/three/authoring/camera-runtime-inspector-section.tsx +135 -0
- package/src/three/authoring/constraint-inspector-section.tsx +189 -0
- package/src/three/authoring/design-time-renderer.ts +189 -0
- package/src/three/authoring/model-asset-inspector-section.css +41 -0
- package/src/three/authoring/model-asset-inspector-section.tsx +869 -0
- package/src/three/authoring/oid-source-persistence.ts +557 -0
- package/src/three/authoring/r3f-design-session.ts +1174 -0
- package/src/three/authoring/r3f-source-authoring-adapter.ts +5949 -0
- package/src/three/authoring/reflection-probe-inspector-section.tsx +76 -0
- package/src/three/authoring/spatial-audio-handles.ts +215 -0
- package/src/three/authoring/spatial-collider-handles.ts +229 -0
- package/src/three/authoring/spatial-joint-handles.ts +114 -0
- package/src/three/authoring/spatial-light-handles.ts +178 -0
- package/src/three/authoring/spatial-lod-handles.ts +93 -0
- package/src/three/authoring/spatial-particle-handles.ts +368 -0
- package/src/three/authoring/three-authoring-adapter.ts +1953 -0
- package/src/three/authoring/three-scene-identity.ts +19 -0
- package/src/three/authoring/three-spatial-handles.ts +161 -0
- package/src/three/authoring/typed-three-inspector.ts +528 -0
- package/src/three/component-verbs/extract-menu.ts +71 -0
- package/src/three/component-verbs/fork-menu.ts +78 -0
- package/src/three/component-verbs/internals-menu.ts +91 -0
- package/src/three/story-documents/three-story-documents.tsx +607 -0
- package/src/three/three-board/ThreeBoardDocument.tsx +888 -0
- package/src/three/three-board/board-framing.ts +412 -0
- package/src/three/three-board/board-layout.ts +401 -0
- package/src/three/three-board/board-scene.ts +901 -0
- package/src/three/three-board/three-component-board.ts +62 -0
- package/src/xstate/XStateBehaviorSection.tsx +130 -0
- package/src/xstate/XStateMachineInspector.tsx +566 -0
- package/src/xstate/character-animation-machine.fixture.ts +74 -0
- package/src/xstate/live-behaviors.ts +106 -0
- package/src/xstate/use-live-actor-state.ts +44 -0
- package/src/xstate/xstate-graph.ts +235 -0
- package/src/xstate/xstate-layout.ts +76 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NAVIGATION, REFUSED OUT LOUD — the fourth thing a page-owning game does that
|
|
3
|
+
* the editor cannot survive.
|
|
4
|
+
*
|
|
5
|
+
* A game that owns its tab is entitled to leave it: a main menu with a "quit"
|
|
6
|
+
* entry assigns `window.location.href`.
|
|
7
|
+
* Run in-realm, that assignment navigates the EDITOR away — the whole session,
|
|
8
|
+
* every other root, and any unsaved authoring with it. It is one line of
|
|
9
|
+
* ordinary game code and there is no cooperative way for a game to know better.
|
|
10
|
+
*
|
|
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`, `vgai-*`,
|
|
13
|
+
* …) are stripped from `search`/`href` (bubbo's `getUrlParam('play')` is a
|
|
14
|
+
* debug skip-to-Game; the editor's `?play=1` autoplay is not that flag),
|
|
15
|
+
* and every write that would NAVIGATE is refused with a `console.error`
|
|
16
|
+
* naming the game, the exact assignment, and this mechanism. Refused, never
|
|
17
|
+
* swallowed — a silent no-op would make "the quit button does nothing" an
|
|
18
|
+
* unexplainable bug, and the console line is a real product door
|
|
19
|
+
* (`vgai status` reports page console errors).
|
|
20
|
+
*
|
|
21
|
+
* `hash` is deliberately NOT refused: it changes the URL without leaving the
|
|
22
|
+
* document, and games use it for their own state.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/** The `Location` properties whose assignment leaves the current document. */
|
|
26
|
+
const NAVIGATING_PROPS = new Set<string>([
|
|
27
|
+
'href',
|
|
28
|
+
'protocol',
|
|
29
|
+
'host',
|
|
30
|
+
'hostname',
|
|
31
|
+
'port',
|
|
32
|
+
'pathname',
|
|
33
|
+
'search',
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
/** The `Location` methods that leave the current document. */
|
|
37
|
+
const NAVIGATING_METHODS = new Set<string>(['assign', 'replace', 'reload']);
|
|
38
|
+
|
|
39
|
+
/** Query keys the HOST owns. A game reading `location.search` must not see
|
|
40
|
+
* them: bubbo treats `play` as "skip the title and start the match." */
|
|
41
|
+
const HOST_SEARCH_KEYS = new Set([
|
|
42
|
+
'play',
|
|
43
|
+
'project',
|
|
44
|
+
'hub',
|
|
45
|
+
'scene',
|
|
46
|
+
'ingest',
|
|
47
|
+
'create-from',
|
|
48
|
+
'checkout_id',
|
|
49
|
+
]);
|
|
50
|
+
|
|
51
|
+
function isHostSearchKey(key: string): boolean {
|
|
52
|
+
return HOST_SEARCH_KEYS.has(key) || key.startsWith('vgai');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** The search string a game is allowed to read — host keys removed. */
|
|
56
|
+
export function gameVisibleSearch(search: string): string {
|
|
57
|
+
const raw = search.startsWith('?') ? search.slice(1) : search;
|
|
58
|
+
if (!raw) return '';
|
|
59
|
+
const kept = new URLSearchParams();
|
|
60
|
+
for (const [key, value] of new URLSearchParams(raw)) {
|
|
61
|
+
if (!isHostSearchKey(key)) kept.append(key, value);
|
|
62
|
+
}
|
|
63
|
+
const qs = kept.toString();
|
|
64
|
+
return qs.length > 0 ? `?${qs}` : '';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function gameVisibleHref(href: string): string {
|
|
68
|
+
try {
|
|
69
|
+
const url = new URL(href);
|
|
70
|
+
url.search = gameVisibleSearch(url.search);
|
|
71
|
+
return url.href;
|
|
72
|
+
} catch {
|
|
73
|
+
return href;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Wrap a real `Location` so game code can read its own query but never
|
|
79
|
+
* navigate with it, and never see the host's control-plane keys.
|
|
80
|
+
* `refuse` receives a human description of exactly what was attempted.
|
|
81
|
+
*/
|
|
82
|
+
export function guardedGameLocation(
|
|
83
|
+
real: Location,
|
|
84
|
+
refuse: (attempt: string) => void,
|
|
85
|
+
options?: {
|
|
86
|
+
/**
|
|
87
|
+
* `location.reload()` translated to what it MEANS on this surface. A
|
|
88
|
+
* page-owning game reloading its window is restarting ITSELF (cuberun's
|
|
89
|
+
* death-screen RESTART is literally `window.location.reload()`), and the
|
|
90
|
+
* realm's window IS the pane — so when the host can restart the game, the
|
|
91
|
+
* idiom is honored rather than refused. Returns true when handled; false
|
|
92
|
+
* falls back to the loud refusal. Measured: a human tester played cuberun
|
|
93
|
+
* and reported RESTART 'does nothing' (runhuman, 2026-08-28).
|
|
94
|
+
*/
|
|
95
|
+
readonly reloadGame?: () => boolean;
|
|
96
|
+
},
|
|
97
|
+
): Location {
|
|
98
|
+
// The proxy TARGET is a plain object, NEVER the real Location. `assign`,
|
|
99
|
+
// `replace` and `reload` are unforgeable own data properties (non-writable,
|
|
100
|
+
// non-configurable) on a real Location, and the proxy invariant for such
|
|
101
|
+
// properties requires the get trap to return the REAL value — so a proxy
|
|
102
|
+
// over the real object THROWS TypeError on every `location.reload` access
|
|
103
|
+
// instead of running the trap. Measured on cuberun's death-screen RESTART
|
|
104
|
+
// (`window.location.reload()`): the click dispatched, the handler died on
|
|
105
|
+
// its first property read, and nothing on screen said why — meaning the
|
|
106
|
+
// guard's refusal path for these three methods had NEVER fired on any
|
|
107
|
+
// tier. An empty target has no own properties, so every trap is free.
|
|
108
|
+
const dummy = Object.create(null) as Location;
|
|
109
|
+
return new Proxy(dummy, {
|
|
110
|
+
get(_target, prop) {
|
|
111
|
+
if (typeof prop === 'string' && NAVIGATING_METHODS.has(prop)) {
|
|
112
|
+
return (...args: unknown[]): void => {
|
|
113
|
+
if (prop === 'reload' && options?.reloadGame?.()) return;
|
|
114
|
+
refuse(`location.${prop}(${args.map((a) => String(a)).join(', ')})`);
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
if (prop === 'search') return gameVisibleSearch(String(Reflect.get(real, 'search') ?? ''));
|
|
118
|
+
if (prop === 'href') return gameVisibleHref(String(Reflect.get(real, 'href') ?? ''));
|
|
119
|
+
const value = Reflect.get(real, prop);
|
|
120
|
+
return typeof value === 'function'
|
|
121
|
+
? (value as (...a: unknown[]) => unknown).bind(real)
|
|
122
|
+
: value;
|
|
123
|
+
},
|
|
124
|
+
set(_target, prop, value) {
|
|
125
|
+
if (typeof prop === 'string' && NAVIGATING_PROPS.has(prop)) {
|
|
126
|
+
refuse(`location.${prop} = ${String(value)}`);
|
|
127
|
+
// Report success to the game: an assignment that THROWS would abort
|
|
128
|
+
// whatever routine tried it (the game's own menu handler), which is a
|
|
129
|
+
// second failure on top of the one we just prevented. The console line
|
|
130
|
+
// above is what makes this non-silent.
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
return Reflect.set(real, prop, value);
|
|
134
|
+
},
|
|
135
|
+
has(_target, prop) {
|
|
136
|
+
return Reflect.has(real, prop);
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** The one message shape for a refused navigation, so every surface that
|
|
142
|
+
* reports one reads the same. */
|
|
143
|
+
export function refusedNavigationMessage(attempt: string): string {
|
|
144
|
+
return (
|
|
145
|
+
`Ingested game attempted to navigate the editor away (${attempt}) — refused. ` +
|
|
146
|
+
"A game running in-realm shares the editor's document, so leaving it would take the " +
|
|
147
|
+
'whole session with it; game code sees a scoped `location` facade whose reads pass ' +
|
|
148
|
+
'through and whose navigating writes do not (packages/editor/src/game-location-guard.ts).'
|
|
149
|
+
);
|
|
150
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { commandLine } from '@volter/editor-core/product-command';
|
|
2
|
+
/**
|
|
3
|
+
* THE MODULE LANE of `vgai eval` — reach the RUNNING mount's own module
|
|
4
|
+
* instances, by served path, using URLs retained by their owning realm.
|
|
5
|
+
*
|
|
6
|
+
* WHY THE MOUNT STAMP IS LOAD-BEARING. Project modules are served per mount
|
|
7
|
+
* as `/<path>?vgai-mount=<id>` (`project-module-instance.ts` propagates the
|
|
8
|
+
* id through the whole project subtree), so browser module identity is
|
|
9
|
+
* per-instance BY URL. A bare `import('/src/sim/host.ts')` therefore loads a
|
|
10
|
+
* SECOND, never-ticked copy — the phantom-module trap the old string-command
|
|
11
|
+
* registry existed to dodge. Importing through the ACTIVE mount's stamped
|
|
12
|
+
* url returns the exact instance the game is running, from the browser's own
|
|
13
|
+
* module cache.
|
|
14
|
+
*
|
|
15
|
+
* This is a DEV-SERVER door: exported builds are bundles with no module
|
|
16
|
+
* urls, and their curated surface is the adapter's own exports. Errors here
|
|
17
|
+
* teach the contract instead of leaving it to inference.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
// DEPENDENCY-LIGHT ON PURPOSE: project tool contributions import this
|
|
21
|
+
// module for their faces, and a project's `tsc` walks its imports — pulling
|
|
22
|
+
// `play-mode` (and through it half the editor) breaks every game's own
|
|
23
|
+
// typecheck. The focused-instance read is therefore INJECTED by the editor
|
|
24
|
+
// at boot rather than imported, and the query key is inlined as a constant
|
|
25
|
+
// with a guard test on the one true owner.
|
|
26
|
+
/** Mirrors `project-module-url.ts`'s PROJECT_MOUNT_QUERY (guarded by test —
|
|
27
|
+
* the two must never drift). */
|
|
28
|
+
const PROJECT_MOUNT_QUERY = 'vgai-mount';
|
|
29
|
+
|
|
30
|
+
let focusedInstanceProvider: (() => string) | null = null;
|
|
31
|
+
/** The packaged shell and source-served contributions share the host realm owner. */
|
|
32
|
+
function moduleUrls(instanceId: string): readonly string[] {
|
|
33
|
+
const host = globalThis as typeof globalThis & {
|
|
34
|
+
__vgaiGameModuleUrls?: (id: string) => readonly string[];
|
|
35
|
+
};
|
|
36
|
+
return host.__vgaiGameModuleUrls?.(instanceId) ?? [];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Wired once by the editor shell (`command-listener.ts`) at boot. */
|
|
40
|
+
export function setFocusedInstanceProvider(provider: () => string): void {
|
|
41
|
+
focusedInstanceProvider = provider;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Validate/normalize a project-relative module path. Exported pure for the
|
|
45
|
+
* unit test; the rules are the error message. */
|
|
46
|
+
export function normalizeGameModulePath(path: string): string {
|
|
47
|
+
const clean = path.replace(/^\/+/, '');
|
|
48
|
+
if (!clean.startsWith('src/') || /[?#]/.test(clean)) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
`modules("${path}"): pass the project-relative served path, starting "src/" with its real ` +
|
|
51
|
+
'extension and no query — e.g. modules("src/sim/host.ts").',
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
return clean;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Every project module the mount has ACTUALLY LOADED, as the exact
|
|
59
|
+
* project-relative strings {@link importGameModule} accepts — `src/world.tsx`,
|
|
60
|
+
* not the `/@fs/…` pathname the browser fetched. This is the answer to "what
|
|
61
|
+
* can I reach?", and it is read from URLs recorded by module evaluation.
|
|
62
|
+
*
|
|
63
|
+
* One owner, two readers: the `modules.loaded` listing below and the
|
|
64
|
+
* not-loaded error message. They used to disagree — the error listed raw
|
|
65
|
+
* `/@fs/…` pathnames while its own first sentence told you to pass
|
|
66
|
+
* `src/…`, so the fix it printed was not the fix it named.
|
|
67
|
+
*/
|
|
68
|
+
export function loadedGameModulePaths(instanceId: string): string[] {
|
|
69
|
+
const paths = moduleUrls(instanceId).flatMap((name) => {
|
|
70
|
+
const { pathname, searchParams } = new URL(name);
|
|
71
|
+
if (searchParams.get(PROJECT_MOUNT_QUERY) !== instanceId) return [];
|
|
72
|
+
const at = pathname.lastIndexOf('/src/');
|
|
73
|
+
return at === -1 ? [] : [pathname.slice(at + 1)];
|
|
74
|
+
});
|
|
75
|
+
return [...new Set(paths)].sort();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* The `modules` binding of `vgai eval`'s `game.run` scope and of the
|
|
80
|
+
* `game-eval` op — a RESOLVER, `modules('src/sim/host.ts')`, that also
|
|
81
|
+
* ENUMERATES what it can resolve.
|
|
82
|
+
*
|
|
83
|
+
* WHY THE ENUMERATION EXISTS (measured, cold mug #2, 2026-08-29). There is no
|
|
84
|
+
* module registry here and there never was: `modules` is a function. So a cold
|
|
85
|
+
* agent's first move — `game.run(({ modules }) => Object.keys(modules))` —
|
|
86
|
+
* answered `[]` against a mount with twelve loaded modules, and `[]` reads as
|
|
87
|
+
* "this game registers none". A function whose only honest answer to
|
|
88
|
+
* `Object.keys` is an empty array is a door that lies when asked what is
|
|
89
|
+
* behind it. `loaded` is an enumerable own accessor, so `Object.keys(modules)`
|
|
90
|
+
* now names it and `modules.loaded` is the list of paths to pass back in.
|
|
91
|
+
*
|
|
92
|
+
* WHY THE RESULT IS A GUARDED PROMISE (measured, cold fox #3, 2026-08-29).
|
|
93
|
+
* `modules(path)` is ASYNC — it dynamic-imports the running mount's own url —
|
|
94
|
+
* and a cold agent who skipped the `await` got
|
|
95
|
+
* `TypeError: modules(...).liveFoxes is not a function`, which names the
|
|
96
|
+
* export, names the module, and says nothing whatsoever about promises. The
|
|
97
|
+
* misuse is exactly detectable, so it is now caught where it happens: reading
|
|
98
|
+
* any non-Promise member off the returned value throws a sentence naming the
|
|
99
|
+
* fix. That is the same treatment `loaded` got — a door that answers the
|
|
100
|
+
* question actually being asked instead of a true-but-useless fact.
|
|
101
|
+
*/
|
|
102
|
+
export function createGameModuleResolver(
|
|
103
|
+
instanceId?: string,
|
|
104
|
+
): ((path: string) => Promise<Record<string, unknown>>) & { readonly loaded: string[] } {
|
|
105
|
+
const resolve = (path: string) => awaitOnly(path, importGameModule(path, instanceId));
|
|
106
|
+
Object.defineProperty(resolve, 'loaded', {
|
|
107
|
+
enumerable: true,
|
|
108
|
+
get: () => {
|
|
109
|
+
const id = instanceId ?? focusedInstanceProvider?.();
|
|
110
|
+
return id ? loadedGameModulePaths(id) : [];
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
return resolve as ((path: string) => Promise<Record<string, unknown>>) & {
|
|
114
|
+
readonly loaded: string[];
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Everything a legitimate consumer of a promise reads off it. Anything else
|
|
119
|
+
* is a caller that forgot the `await` and is reaching for a module export. */
|
|
120
|
+
const PROMISE_MEMBERS = new Set(['then', 'catch', 'finally', 'constructor', 'toJSON']);
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* The promise `modules(path)` hands back, with one behaviour added: reading a
|
|
124
|
+
* member that is not a promise's own throws a sentence naming the `await`.
|
|
125
|
+
*
|
|
126
|
+
* `then`/`catch`/`finally` are forwarded BOUND to the real promise — a
|
|
127
|
+
* Proxy has no promise internal slots, so `Promise.prototype.then` called with
|
|
128
|
+
* the proxy as `this` would throw for the wrong reason and break every correct
|
|
129
|
+
* caller.
|
|
130
|
+
*/
|
|
131
|
+
function awaitOnly(
|
|
132
|
+
path: string,
|
|
133
|
+
promise: Promise<Record<string, unknown>>,
|
|
134
|
+
): Promise<Record<string, unknown>> {
|
|
135
|
+
return new Proxy(promise, {
|
|
136
|
+
get(target, key) {
|
|
137
|
+
if (key === 'then' || key === 'catch' || key === 'finally') {
|
|
138
|
+
return (Reflect.get(target, key) as (...args: unknown[]) => unknown).bind(target);
|
|
139
|
+
}
|
|
140
|
+
if (typeof key === 'symbol' || PROMISE_MEMBERS.has(key)) return Reflect.get(target, key);
|
|
141
|
+
throw new Error(
|
|
142
|
+
`modules("${path}") returns a PROMISE — it dynamic-imports the running mount's own copy ` +
|
|
143
|
+
`of that module — so \`.${String(key)}\` reads a promise, not your export. Await it: ` +
|
|
144
|
+
`\`const m = await modules("${path}"); m.${String(key)}\` (and make the \`game.run\` ` +
|
|
145
|
+
'callback `async`).',
|
|
146
|
+
);
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** The running mount's instance of one project module. */
|
|
152
|
+
export async function importGameModule(
|
|
153
|
+
path: string,
|
|
154
|
+
instanceId?: string,
|
|
155
|
+
): Promise<Record<string, unknown>> {
|
|
156
|
+
const clean = normalizeGameModulePath(path);
|
|
157
|
+
const id = instanceId ?? focusedInstanceProvider?.();
|
|
158
|
+
if (!id) {
|
|
159
|
+
throw new Error(`modules(): no play instance is mounted — enter Play first (${commandLine('play')}).`);
|
|
160
|
+
}
|
|
161
|
+
// Resolve the served base from what the browser ACTUALLY LOADED for this
|
|
162
|
+
// mount, never from a rebuilt path: Vite rewrites a mount's imports to its
|
|
163
|
+
// own resolved forms (root-relative in-repo, /@fs in a scaffold), and any
|
|
164
|
+
// byte of divergence yields a SECOND module instance — the phantom this
|
|
165
|
+
// lane exists to make impossible. Measured before this guard: an /@fs
|
|
166
|
+
// rebuild read a dead copy while the registered door watched the sweep run.
|
|
167
|
+
// ZERO URL CONSTRUCTION. Every attempt to rebuild the served url from
|
|
168
|
+
// parts produced a phantom second instance off by one byte (/@fs vs
|
|
169
|
+
// root-relative; a Vite-appended ?import). The only string guaranteed to
|
|
170
|
+
// name the RUNNING instance is the one the graph already fetched — so the
|
|
171
|
+
// accessor imports that string VERBATIM from its owning realm's evaluated
|
|
172
|
+
// module URLs, and refuses when the module has not been
|
|
173
|
+
// loaded by the game yet. The running graph's modules are, by definition,
|
|
174
|
+
// loaded — so this covers exactly what "touch the running game" means.
|
|
175
|
+
const suffix = `/${clean}`;
|
|
176
|
+
const url = moduleUrls(id).find((name) => {
|
|
177
|
+
const u = new URL(name);
|
|
178
|
+
return u.pathname.endsWith(suffix) && u.searchParams.get(PROJECT_MOUNT_QUERY) === id;
|
|
179
|
+
});
|
|
180
|
+
if (!url) {
|
|
181
|
+
const candidates = loadedGameModulePaths(id);
|
|
182
|
+
throw new Error(
|
|
183
|
+
`modules("${path}"): the running mount has not loaded that module (nothing to reach — a ` +
|
|
184
|
+
'module the game never imported has no live instance). Loaded project modules ' +
|
|
185
|
+
`(also readable as modules.loaded): ${candidates.join(', ') || '(none yet — play first)'}`,
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
return (await import(/* @vite-ignore */ url)) as Record<string, unknown>;
|
|
190
|
+
} catch (error) {
|
|
191
|
+
throw new Error(
|
|
192
|
+
`modules("${path}") could not load ${url} — the path must be the exact served file ` +
|
|
193
|
+
`(check the extension: .ts vs .tsx). ${error instanceof Error ? error.message : String(error)}`,
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
}
|