@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,303 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Installs the loaded `vgai.adapter.ts` runtime bindings onto one mounted
|
|
3
|
+
* Game. Game modules stay native: adapter closures close over app-owned stores
|
|
4
|
+
* and functions, while this host-side file projects them onto the existing
|
|
5
|
+
* session command/state/input doors.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
installNativeDebugBindings,
|
|
10
|
+
installNativeSystemsBindings,
|
|
11
|
+
} from '@volter/game-runtime/adapter/native-debug-module';
|
|
12
|
+
import { publishDevInstruments } from '@volter/game-runtime/dev/instruments';
|
|
13
|
+
import {
|
|
14
|
+
DebugError,
|
|
15
|
+
type DebugRegistry,
|
|
16
|
+
type DebugVirtualInputTarget,
|
|
17
|
+
getDebugRegistry,
|
|
18
|
+
} from '@volter/game-runtime/runtime/debug-registry';
|
|
19
|
+
import type { Game } from '@volter/game-runtime/runtime/game';
|
|
20
|
+
import {
|
|
21
|
+
ADAPTER_INPUT_VALUE_TYPES,
|
|
22
|
+
type AdapterInputAction,
|
|
23
|
+
type AdapterInputBinding,
|
|
24
|
+
type AdapterInputValue,
|
|
25
|
+
type AdapterInputValueType,
|
|
26
|
+
type ObservationDeclaration,
|
|
27
|
+
} from '@volter/editor-project/adapter/adapter-module';
|
|
28
|
+
import type { ObservationBinding } from '@volter/editor-project/adapter/binding';
|
|
29
|
+
import { adapterInputBinding, adapterObservations } from '@volter/editor-core/adapter-observation';
|
|
30
|
+
|
|
31
|
+
const ADAPTER_REGISTRATION_ID = '__adapter__';
|
|
32
|
+
const installedGames = new WeakSet<Game>();
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Every root's harvested entry binding of one kind, in root order.
|
|
36
|
+
*
|
|
37
|
+
* A root whose registration carried no declaration has no binding at all (a
|
|
38
|
+
* bare harness `registerThreeRoot`), and a root whose entry module exports
|
|
39
|
+
* neither `debug` nor `systems` has no member — both are skipped, which is the
|
|
40
|
+
* same set the resolver used to accumulate into an array. Both installs are
|
|
41
|
+
* keyed by `rootId` internally, so order carries no meaning here.
|
|
42
|
+
*/
|
|
43
|
+
function entryBindings<K extends 'entryDebug' | 'entrySystems'>(
|
|
44
|
+
game: Game,
|
|
45
|
+
kind: K,
|
|
46
|
+
): NonNullable<ObservationBinding[K]>[] {
|
|
47
|
+
const out: NonNullable<ObservationBinding[K]>[] = [];
|
|
48
|
+
for (const root of game.roots) {
|
|
49
|
+
const harvested = root.binding?.observation[kind];
|
|
50
|
+
if (harvested) out.push(harvested as NonNullable<ObservationBinding[K]>);
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function installObservations(
|
|
56
|
+
game: Game,
|
|
57
|
+
registry: DebugRegistry,
|
|
58
|
+
declarations: readonly ObservationDeclaration[],
|
|
59
|
+
): void {
|
|
60
|
+
const debug = registry.forRoot(ADAPTER_REGISTRATION_ID);
|
|
61
|
+
for (const declaration of declarations) {
|
|
62
|
+
if (declaration.kind === 'state') {
|
|
63
|
+
debug.registerStateProvider(
|
|
64
|
+
declaration.id,
|
|
65
|
+
() => {
|
|
66
|
+
if (!declaration.answer) {
|
|
67
|
+
throw new DebugError(
|
|
68
|
+
'OBSERVATION_UNANSWERED',
|
|
69
|
+
`Observation "${declaration.id}" (state) is declared by this game's adapter with no answer.`,
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
return declaration.answer(game);
|
|
73
|
+
},
|
|
74
|
+
{ tier: 'assisted' },
|
|
75
|
+
);
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
debug.registerCommand(
|
|
79
|
+
declaration.id,
|
|
80
|
+
{
|
|
81
|
+
...(declaration.description ? { description: declaration.description } : {}),
|
|
82
|
+
locus: declaration.locus ?? 'client',
|
|
83
|
+
},
|
|
84
|
+
(...args: unknown[]) => {
|
|
85
|
+
if (!declaration.answer) {
|
|
86
|
+
throw new DebugError(
|
|
87
|
+
'OBSERVATION_UNANSWERED',
|
|
88
|
+
`Observation "${declaration.id}" (command) is declared by this game's adapter with no answer.`,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
return (declaration.answer as (game: unknown, ...args: unknown[]) => unknown)(
|
|
92
|
+
game,
|
|
93
|
+
...args,
|
|
94
|
+
);
|
|
95
|
+
},
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function actionMap(binding: AdapterInputBinding, game: Game): Map<string, AdapterInputValueType> {
|
|
101
|
+
const actions = binding.actions(game);
|
|
102
|
+
if (!Array.isArray(actions)) {
|
|
103
|
+
throw new Error('vgai.adapter.ts input.actions must return an array.');
|
|
104
|
+
}
|
|
105
|
+
const out = new Map<string, AdapterInputValueType>();
|
|
106
|
+
for (const candidate of actions as readonly Partial<AdapterInputAction>[]) {
|
|
107
|
+
if (
|
|
108
|
+
typeof candidate?.name !== 'string' ||
|
|
109
|
+
candidate.name.length === 0 ||
|
|
110
|
+
!ADAPTER_INPUT_VALUE_TYPES.includes(candidate.valueType as AdapterInputValueType)
|
|
111
|
+
) {
|
|
112
|
+
throw new Error(
|
|
113
|
+
'vgai.adapter.ts input.actions returned an invalid action; each action needs a non-empty ' +
|
|
114
|
+
'`name` and a supported `valueType`.',
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
if (out.has(candidate.name)) {
|
|
118
|
+
throw new Error(
|
|
119
|
+
`vgai.adapter.ts input.actions returned duplicate action "${candidate.name}".`,
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
out.set(candidate.name, candidate.valueType as AdapterInputValueType);
|
|
123
|
+
}
|
|
124
|
+
return out;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function valueMatches(type: AdapterInputValueType, value: AdapterInputValue): boolean {
|
|
128
|
+
if (type === 'digital') return typeof value === 'boolean';
|
|
129
|
+
if (type === 'scalar') return typeof value === 'number' && Number.isFinite(value);
|
|
130
|
+
return (
|
|
131
|
+
typeof value === 'object' &&
|
|
132
|
+
value !== null &&
|
|
133
|
+
typeof value.x === 'number' &&
|
|
134
|
+
Number.isFinite(value.x) &&
|
|
135
|
+
typeof value.y === 'number' &&
|
|
136
|
+
Number.isFinite(value.y)
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function installInput(game: Game, registry: DebugRegistry, binding: AdapterInputBinding): void {
|
|
141
|
+
if (!game.world(binding.root)) {
|
|
142
|
+
throw new Error(
|
|
143
|
+
`vgai.adapter.ts input.root names "${binding.root}", which is not a mounted manifest root.`,
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
type Scheduled = { tick: number; action: string; value: AdapterInputValue };
|
|
148
|
+
const scheduled: Scheduled[] = [];
|
|
149
|
+
const trace: Array<{ tick: number; action: string; value: AdapterInputValue }> = [];
|
|
150
|
+
let recording = false;
|
|
151
|
+
|
|
152
|
+
const requireAction = (action: string, value: AdapterInputValue): AdapterInputValueType => {
|
|
153
|
+
const type = actionMap(binding, game).get(action);
|
|
154
|
+
if (!type) {
|
|
155
|
+
throw new DebugError(
|
|
156
|
+
'INPUT_ACTION_NOT_FOUND',
|
|
157
|
+
`Adapter input has no action named "${action}".`,
|
|
158
|
+
{ registered: [...actionMap(binding, game).keys()] },
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
if (!valueMatches(type, value)) {
|
|
162
|
+
throw new DebugError(
|
|
163
|
+
'INPUT_ACTION_VALUE_TYPE',
|
|
164
|
+
`Adapter input action "${action}" expects ${type}, not ${typeof value}.`,
|
|
165
|
+
{ action, expected: type },
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
return type;
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const apply = (action: string, value: AdapterInputValue, tick: number): void => {
|
|
172
|
+
requireAction(action, value);
|
|
173
|
+
binding.set(game, action, value);
|
|
174
|
+
if (recording) trace.push({ tick, action, value });
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const runScheduled = (): void => {
|
|
178
|
+
const tick = registry.getGameTick();
|
|
179
|
+
// Preserve call order when several actuations target the same tick. A
|
|
180
|
+
// backwards splice is allocation-cheap but reverses them, making
|
|
181
|
+
// `set(true); set(false)` arrive as `false; true` at the app store.
|
|
182
|
+
const due = scheduled.filter((item) => item.tick === tick);
|
|
183
|
+
if (due.length === 0) return;
|
|
184
|
+
for (let i = scheduled.length - 1; i >= 0; i--) {
|
|
185
|
+
if (scheduled[i]!.tick === tick) scheduled.splice(i, 1);
|
|
186
|
+
}
|
|
187
|
+
for (const item of due) {
|
|
188
|
+
apply(item.action, item.value, tick);
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
game.systems.add('input', runScheduled, { name: 'adapter.input' });
|
|
192
|
+
|
|
193
|
+
const unsupportedSource = (method: string): never => {
|
|
194
|
+
throw new DebugError(
|
|
195
|
+
'ADAPTER_INPUT_SOURCE_UNSUPPORTED',
|
|
196
|
+
`${method} addresses an engine test source, but this game declares an app-owned action store. ` +
|
|
197
|
+
'Drive it by action name instead.',
|
|
198
|
+
);
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const target: DebugVirtualInputTarget = {
|
|
202
|
+
setVirtualAction(action, value) {
|
|
203
|
+
apply(action, value, registry.getGameTick());
|
|
204
|
+
return { delivered: true };
|
|
205
|
+
},
|
|
206
|
+
tapVirtualAction(action) {
|
|
207
|
+
const tick = registry.getGameTick();
|
|
208
|
+
apply(action, true, tick);
|
|
209
|
+
scheduled.push({ tick: tick + 1, action, value: false });
|
|
210
|
+
return { delivered: true };
|
|
211
|
+
},
|
|
212
|
+
clearVirtualActions() {
|
|
213
|
+
scheduled.length = 0;
|
|
214
|
+
binding.clear(game);
|
|
215
|
+
},
|
|
216
|
+
scheduleActionAtTick(tick, action, value) {
|
|
217
|
+
const current = registry.getGameTick();
|
|
218
|
+
if (!Number.isInteger(tick) || tick < current) {
|
|
219
|
+
throw new DebugError(
|
|
220
|
+
'TICK_ALREADY_PASSED',
|
|
221
|
+
`Cannot schedule adapter input for tick ${tick}; current tick is ${current}.`,
|
|
222
|
+
{ tick, currentTick: current },
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
// Validate now so a typo refuses at the call, not several ticks later.
|
|
226
|
+
requireAction(action, value);
|
|
227
|
+
scheduled.push({ tick, action, value });
|
|
228
|
+
},
|
|
229
|
+
startInputRecording() {
|
|
230
|
+
trace.length = 0;
|
|
231
|
+
recording = true;
|
|
232
|
+
},
|
|
233
|
+
stopInputRecording() {
|
|
234
|
+
recording = false;
|
|
235
|
+
},
|
|
236
|
+
isInputRecording() {
|
|
237
|
+
return recording;
|
|
238
|
+
},
|
|
239
|
+
injectAxis() {
|
|
240
|
+
unsupportedSource('injectAxis');
|
|
241
|
+
},
|
|
242
|
+
injectVector2() {
|
|
243
|
+
unsupportedSource('injectVector2');
|
|
244
|
+
},
|
|
245
|
+
injectPointerDelta() {
|
|
246
|
+
unsupportedSource('injectPointerDelta');
|
|
247
|
+
},
|
|
248
|
+
injectPointerPosition() {
|
|
249
|
+
unsupportedSource('injectPointerPosition');
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
registry.setInputActionsSource(binding.root, () =>
|
|
254
|
+
[...actionMap(binding, game)].map(([name, valueType]) => ({ name, valueType })),
|
|
255
|
+
);
|
|
256
|
+
registry.setInputTraceSource(binding.root, () => ({
|
|
257
|
+
version: 1,
|
|
258
|
+
seed: null,
|
|
259
|
+
fixedDt: game.loop.fixedDt,
|
|
260
|
+
ticks: [...trace],
|
|
261
|
+
}));
|
|
262
|
+
registry.setVirtualInputTarget(binding.root, target);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Install the current project's boundary declarations onto a completed mount.
|
|
267
|
+
*
|
|
268
|
+
* The entry modules' own `debug`/`systems` exports are read off the roots'
|
|
269
|
+
* BINDINGS (`binding.observation.entryDebug`/`entrySystems`) rather than
|
|
270
|
+
* hand-carried here. They used to travel a second time as two arrays returned
|
|
271
|
+
* by `resolveAllRootEntries` and threaded through play-mode — the same values
|
|
272
|
+
* the resolver had already put on each root's declaration, so two carriers for
|
|
273
|
+
* one fact, one of which had to be kept in sync by hand at every call site.
|
|
274
|
+
*/
|
|
275
|
+
export function installAdapterRuntimeBindings(game: Game): void {
|
|
276
|
+
if (installedGames.has(game)) return;
|
|
277
|
+
installNativeDebugBindings(game, entryBindings(game, 'entryDebug'));
|
|
278
|
+
installNativeSystemsBindings(game, entryBindings(game, 'entrySystems'));
|
|
279
|
+
// The engine's universal instruments (time scale, pause/frame-step,
|
|
280
|
+
// collider draw, frame time) — HOST-published, so every game gets them for
|
|
281
|
+
// zero lines and no in-world mount. The disposer is deliberately dropped:
|
|
282
|
+
// the one resource it would end (the frame-time subscription) lives on this
|
|
283
|
+
// Game, and this install runs once per Game (the WeakSet above), so the
|
|
284
|
+
// set's lifetime IS the Game's.
|
|
285
|
+
publishDevInstruments(game);
|
|
286
|
+
const registry = getDebugRegistry(game);
|
|
287
|
+
if (!registry) {
|
|
288
|
+
throw new Error('Cannot install adapter runtime bindings: mounted Game has no debug registry.');
|
|
289
|
+
}
|
|
290
|
+
// Ingest mounts already project declarations against their browser realm
|
|
291
|
+
// while assembling their own debug adapter. Native mounts publish either
|
|
292
|
+
// this registry object or no debug adapter, so only those install here.
|
|
293
|
+
const rootOwnsObservationProjection = game.roots.some(
|
|
294
|
+
(root) =>
|
|
295
|
+
root.mounted.systems?.debug !== undefined && root.mounted.systems.debug !== registry.adapter,
|
|
296
|
+
);
|
|
297
|
+
if (!rootOwnsObservationProjection) {
|
|
298
|
+
installObservations(game, registry, adapterObservations());
|
|
299
|
+
}
|
|
300
|
+
const input = adapterInputBinding();
|
|
301
|
+
if (input) installInput(game, registry, input);
|
|
302
|
+
installedGames.add(game);
|
|
303
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deferring a step until the browser has painted, in a tab that may never paint.
|
|
3
|
+
*
|
|
4
|
+
* READ THIS BEFORE WRITING `requestAnimationFrame` INTO A BOOT PATH.
|
|
5
|
+
* **rAF does not fire in a hidden tab, ever** — not throttled, not late:
|
|
6
|
+
* zero callbacks until the tab is foregrounded. And "hidden at boot" is the
|
|
7
|
+
* ORDINARY case for this editor, not an edge: every `vgai edit` session rooted
|
|
8
|
+
* in an agent worktree opens its one tab behind the human's window
|
|
9
|
+
* (`server/open-browser.ts` → `open -g`), and any `vgai edit` that fires while
|
|
10
|
+
* the browser is not frontmost lands the same way. Measured twice in one hour:
|
|
11
|
+
* an editor whose shell mount sat behind a bare double-rAF heartbeat for 20
|
|
12
|
+
* minutes with the relay answering, `document.visibilityState: "hidden"`, and
|
|
13
|
+
* zero console errors, while every `vgai play` refused with "the editor shell
|
|
14
|
+
* is not bound yet".
|
|
15
|
+
*
|
|
16
|
+
* A paint deferral is only ever an OPTIMISATION — let the committed chrome
|
|
17
|
+
* reach the compositor before something expensive (a WebGL context, a dock
|
|
18
|
+
* rebuild) starts. A hidden tab has no compositor turn to wait for, so the
|
|
19
|
+
* deferral has nothing left to buy and the work runs immediately. That is the
|
|
20
|
+
* whole rule: **the render loop may pause while hidden; nothing that makes the
|
|
21
|
+
* editor command-capable may.**
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* A visible tab whose rAF chain has produced nothing for this long is starved
|
|
26
|
+
* (occluded window, GPU stall, a browser that missed its own visibility
|
|
27
|
+
* event). The deferral is an optimisation, so it gives up rather than holding
|
|
28
|
+
* the caller's work hostage to a frame that is not coming.
|
|
29
|
+
*/
|
|
30
|
+
const PAINT_STARVATION_FALLBACK_MS = 1_000;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Run `step` after the current chrome has had its paint turn — or straight
|
|
34
|
+
* away when this tab cannot paint at all.
|
|
35
|
+
*
|
|
36
|
+
* Visible: two frames (the first commits the pending render, the second lands
|
|
37
|
+
* after the compositor has taken it), exactly the double-rAF this replaced.
|
|
38
|
+
* Hidden, or hidden while waiting, or rAF-starved: a timer runs it instead.
|
|
39
|
+
*
|
|
40
|
+
* Returns a cancel function; calling it after `step` has run is a no-op.
|
|
41
|
+
*/
|
|
42
|
+
export function scheduleAfterPaint(step: () => void): () => void {
|
|
43
|
+
let settled = false;
|
|
44
|
+
let firstFrame = 0;
|
|
45
|
+
let secondFrame = 0;
|
|
46
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
47
|
+
|
|
48
|
+
const hidden = (): boolean => typeof document !== 'undefined' && document.hidden;
|
|
49
|
+
|
|
50
|
+
const onVisibilityChange = (): void => {
|
|
51
|
+
// Going hidden mid-wait kills the armed rAF chain silently. Take the
|
|
52
|
+
// timer path the moment that happens rather than waiting out the
|
|
53
|
+
// starvation fallback.
|
|
54
|
+
if (hidden()) fire();
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const release = (): void => {
|
|
58
|
+
if (typeof cancelAnimationFrame === 'function') {
|
|
59
|
+
cancelAnimationFrame(firstFrame);
|
|
60
|
+
cancelAnimationFrame(secondFrame);
|
|
61
|
+
}
|
|
62
|
+
if (timer !== undefined) clearTimeout(timer);
|
|
63
|
+
if (typeof document !== 'undefined') {
|
|
64
|
+
document.removeEventListener('visibilitychange', onVisibilityChange);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
function fire(): void {
|
|
69
|
+
if (settled) return;
|
|
70
|
+
settled = true;
|
|
71
|
+
release();
|
|
72
|
+
step();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (hidden() || typeof requestAnimationFrame !== 'function') {
|
|
76
|
+
timer = setTimeout(fire, 0);
|
|
77
|
+
} else {
|
|
78
|
+
firstFrame = requestAnimationFrame(() => {
|
|
79
|
+
secondFrame = requestAnimationFrame(fire);
|
|
80
|
+
});
|
|
81
|
+
if (typeof document !== 'undefined') {
|
|
82
|
+
document.addEventListener('visibilitychange', onVisibilityChange);
|
|
83
|
+
}
|
|
84
|
+
timer = setTimeout(fire, PAINT_STARVATION_FALLBACK_MS);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return () => {
|
|
88
|
+
if (settled) return;
|
|
89
|
+
settled = true;
|
|
90
|
+
release();
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** One frame at 60Hz is ~16ms, so a visible tab's real frame always wins this
|
|
95
|
+
* race and only a tab that cannot paint takes the timer. */
|
|
96
|
+
const PAINT_POLL_FALLBACK_MS = 32;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Await one paint turn — the awaitable form of the rule above, for a poll loop
|
|
100
|
+
* that settles a surface frame by frame. A bare `await rAF` inside such a loop
|
|
101
|
+
* does not merely skip its optimisation in a hidden tab: it never completes an
|
|
102
|
+
* iteration, so a bounded loop hangs on its FIRST one, forever.
|
|
103
|
+
*/
|
|
104
|
+
export function nextPaint(): Promise<void> {
|
|
105
|
+
if (typeof requestAnimationFrame !== 'function') {
|
|
106
|
+
return new Promise((resolve) => setTimeout(resolve, PAINT_POLL_FALLBACK_MS));
|
|
107
|
+
}
|
|
108
|
+
return new Promise((resolve) => {
|
|
109
|
+
requestAnimationFrame(() => resolve());
|
|
110
|
+
setTimeout(resolve, PAINT_POLL_FALLBACK_MS);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The client half of `/__editor/configurations` (`server/routes/run.ts`): the project's
|
|
3
|
+
* declared run configurations and their runtime status, and the start/stop
|
|
4
|
+
* verbs the transport's picker and `vgai run` share.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { assertEditorServerResponse, editorServerJson } from '@volter/editor-core/editor-server-response';
|
|
8
|
+
import { BASE } from '@volter/editor-core/api/base';
|
|
9
|
+
|
|
10
|
+
export interface ConfigurationStatus {
|
|
11
|
+
readonly id: string;
|
|
12
|
+
readonly kind: string;
|
|
13
|
+
/** The kind's registered role, or `null` for a kind this host does not know. */
|
|
14
|
+
readonly role: 'run' | 'build' | null;
|
|
15
|
+
readonly describe: string | null;
|
|
16
|
+
readonly status: 'running' | 'stopped';
|
|
17
|
+
readonly pid?: number;
|
|
18
|
+
readonly since?: number;
|
|
19
|
+
readonly entry?: string;
|
|
20
|
+
readonly port?: number | null;
|
|
21
|
+
readonly run?: readonly string[];
|
|
22
|
+
readonly instances?: number | null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** The declared configurations with status. */
|
|
26
|
+
export async function listConfigurations(): Promise<ConfigurationStatus[]> {
|
|
27
|
+
const res = await fetch(`${BASE}/configurations`);
|
|
28
|
+
const body = await editorServerJson<{ configurations: ConfigurationStatus[] }>(
|
|
29
|
+
res,
|
|
30
|
+
'Could not list run configurations',
|
|
31
|
+
);
|
|
32
|
+
return body.configurations;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface StartConfigurationOutcome {
|
|
36
|
+
readonly ok: boolean;
|
|
37
|
+
readonly started: readonly string[];
|
|
38
|
+
readonly attached: readonly string[];
|
|
39
|
+
readonly ready: boolean;
|
|
40
|
+
readonly logs: Readonly<Record<string, readonly string[]>>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function startConfiguration(id: string): Promise<StartConfigurationOutcome> {
|
|
44
|
+
const res = await fetch(`${BASE}/configurations/${encodeURIComponent(id)}/start`, {
|
|
45
|
+
method: 'POST',
|
|
46
|
+
});
|
|
47
|
+
return editorServerJson<StartConfigurationOutcome>(
|
|
48
|
+
res,
|
|
49
|
+
`Could not start run configuration "${id}"`,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Start a build-role configuration; the response is the server's SSE log
|
|
54
|
+
* stream (`log` and `done` events), which the Build document consumes. */
|
|
55
|
+
export async function startBuild(id: string, signal?: AbortSignal | null): Promise<Response> {
|
|
56
|
+
const init: RequestInit = { method: 'POST' };
|
|
57
|
+
if (signal) init.signal = signal;
|
|
58
|
+
return fetch(`${BASE}/configurations/${encodeURIComponent(id)}/build`, init);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function stopConfiguration(id: string): Promise<void> {
|
|
62
|
+
const res = await fetch(`${BASE}/configurations/${encodeURIComponent(id)}/stop`, {
|
|
63
|
+
method: 'POST',
|
|
64
|
+
});
|
|
65
|
+
assertEditorServerResponse(res, `Could not stop run configuration "${id}"`);
|
|
66
|
+
}
|