@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,355 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE PLAY VERBS of the session wire (`@volter/editor-sdk/commands`, a
|
|
3
|
+
* `workspace.command` contribution): `play`, `stop`, `pause`, `resume`,
|
|
4
|
+
* `step`, and the paused run's `bridge-recording-export`.
|
|
5
|
+
*
|
|
6
|
+
* These left `command-listener.ts`'s switch when Play left the host (WORK.md
|
|
7
|
+
* §The workbench, P3b). What did NOT leave is the host's dispatch PROLOGUE,
|
|
8
|
+
* which still runs before any contributed handler is looked up and therefore
|
|
9
|
+
* still owns the two gates that are not Play's: the ingest play-control latch
|
|
10
|
+
* (a captured ingest answers `play`/`stop`/`pause`/`resume`/`step` itself —
|
|
11
|
+
* P4's move) and the mount-failure refusal. The prologue also refuses every
|
|
12
|
+
* OTHER command while a video export owns a paused run, reading
|
|
13
|
+
* `gameplay-export-state.ts`, which stays host-visible for exactly that (the
|
|
14
|
+
* PlayBar reads it too). The AbortController behind that flag is this file's:
|
|
15
|
+
* `stop` falls THROUGH the prologue so the handler below can cancel the export
|
|
16
|
+
* before tearing the run down.
|
|
17
|
+
*
|
|
18
|
+
* Host internals Play still holds are reached through the `@editor/*` alias
|
|
19
|
+
* the editor's Vite serves to every contribution (precedent:
|
|
20
|
+
* `instances.command.ts`); the doors a lane is meant to use are
|
|
21
|
+
* `@volter/editor-sdk/host`.
|
|
22
|
+
*
|
|
23
|
+
* `play-recording.ts` is Play's own (`../src/play/`): its idle watchdog reads
|
|
24
|
+
* every relayed command through `host.session.onCommandDispatched`, the door
|
|
25
|
+
* P4b added, so the stamp is no longer a host call. `gameplay-recording.ts`
|
|
26
|
+
* stays in the host — `components/GameplaySessionTimeline.tsx`, which
|
|
27
|
+
* `ToolHost.tsx` mounts directly for the `workspace.analytics` point, imports
|
|
28
|
+
* it, and no contribution point injects a panel's header component.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { getActiveSystems } from '@volter/editor-core/authoring/active-systems';
|
|
32
|
+
import { setGameplayExportActive } from '@volter/editor-core/gameplay-export-state';
|
|
33
|
+
import { stopGameplayRecording } from '../src/host/gameplay-recording';
|
|
34
|
+
import { liveInstanceContainer } from '@volter/editor-core/live-session-registry';
|
|
35
|
+
import type { CommandContribution } from '@volter/editor-sdk/commands';
|
|
36
|
+
import { editorHost } from '@volter/editor-sdk/host';
|
|
37
|
+
import type { OfflineAudioRenderer } from '@volter/editor-project/adapter';
|
|
38
|
+
import { flushSync } from 'react-dom';
|
|
39
|
+
import { notPlayingResult, structuredErrorResult } from '../src/command-results';
|
|
40
|
+
import {
|
|
41
|
+
enterPlayMode,
|
|
42
|
+
exitPlayMode,
|
|
43
|
+
getPlayRuntimeAccess,
|
|
44
|
+
pausePlayMode,
|
|
45
|
+
resumePlayMode,
|
|
46
|
+
stepPlayMode,
|
|
47
|
+
} from '../src/play/play-mode';
|
|
48
|
+
import { beginPlayRecording, endPlayRecording, notePlayActivity } from '../src/play/play-recording';
|
|
49
|
+
|
|
50
|
+
export const point = 'workspace.command';
|
|
51
|
+
|
|
52
|
+
/** Pixel recovery is a command-time concern. Keeping this indirection here
|
|
53
|
+
* avoids loading the Pixi extraction stack merely because the control relay
|
|
54
|
+
* is listening for a future screenshot/recording command. */
|
|
55
|
+
async function captureLiveCanvasFrame(
|
|
56
|
+
canvas: HTMLCanvasElement,
|
|
57
|
+
): Promise<CanvasImageSource | null> {
|
|
58
|
+
const { liveCanvasFrame } = await import('@volter/editor-core/live-canvas-frame');
|
|
59
|
+
return liveCanvasFrame(canvas);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Both recording entry points keep a hidden native game's pixels current. */
|
|
63
|
+
function refreshRecordingFrame(): void {
|
|
64
|
+
const runtime = getPlayRuntimeAccess();
|
|
65
|
+
if (runtime?.loop.liveness === 'loop-starved') runtime.runTicks?.(1, { render: 'last' });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The fixed-step export's audio, or `undefined` when this world has none.
|
|
70
|
+
*
|
|
71
|
+
* Two facts have to line up, and BOTH come from the live run, never from a
|
|
72
|
+
* default. The world's `AudioAdapter` must implement `renderOffline` (the
|
|
73
|
+
* offline, deterministic sibling of the recorder's real-time
|
|
74
|
+
* `acquireRecordingStream` — a paused run being stepped makes no real-time
|
|
75
|
+
* sound at all), and the run must be able to say WHERE IT STANDS on its own
|
|
76
|
+
* canonical clock, because the render is asked for an ABSOLUTE sim window.
|
|
77
|
+
* `time` is the engine's built-in debug state provider
|
|
78
|
+
* (`engine/src/runtime/debug-registry.ts`), the same one the play log stamps
|
|
79
|
+
* entries from.
|
|
80
|
+
*
|
|
81
|
+
* A world that cannot answer the clock question — an ingest root with no tick
|
|
82
|
+
* runner — gets no audio rather than a fabricated `start: 0`, which would
|
|
83
|
+
* silently render the wrong slice of the score against the right frames.
|
|
84
|
+
*/
|
|
85
|
+
function exportAudioSource(): { startSeconds: number; render: OfflineAudioRenderer } | undefined {
|
|
86
|
+
const audio = getActiveSystems().audio;
|
|
87
|
+
const render = audio?.renderOffline;
|
|
88
|
+
if (!audio || !render) return undefined;
|
|
89
|
+
let startSeconds: number | null = null;
|
|
90
|
+
try {
|
|
91
|
+
const time = getActiveSystems().debug?.state('time') as { simSeconds?: number } | undefined;
|
|
92
|
+
if (typeof time?.simSeconds === 'number' && Number.isFinite(time.simSeconds)) {
|
|
93
|
+
startSeconds = time.simSeconds;
|
|
94
|
+
}
|
|
95
|
+
} catch {
|
|
96
|
+
startSeconds = null;
|
|
97
|
+
}
|
|
98
|
+
if (startSeconds === null) return undefined;
|
|
99
|
+
return { startSeconds, render: render.bind(audio) };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** The in-flight video export's cancel handle. Module state, like the flag it
|
|
103
|
+
* raises in `gameplay-export-state.ts`: one paused run, one export. */
|
|
104
|
+
let gameplayExportAbort: AbortController | null = null;
|
|
105
|
+
|
|
106
|
+
export const commands: CommandContribution['commands'] = {
|
|
107
|
+
play: {
|
|
108
|
+
// `play` boots the game, including its full async `setup()` (asset loads,
|
|
109
|
+
// procedural worldgen); under the generic budget any substantial game
|
|
110
|
+
// reported "did not respond" while play in fact succeeded.
|
|
111
|
+
timeoutMs: 120_000,
|
|
112
|
+
derivedRefresh: 'always',
|
|
113
|
+
handle: async (cmd) => {
|
|
114
|
+
// Fix for the ghost-runtime bug — deliberately no "already running"
|
|
115
|
+
// guard here: `play` on an already-playing (or still-booting) session
|
|
116
|
+
// is a RESTART, not a no-op, matching the SDK's own documented contract
|
|
117
|
+
// (`play.start`'s summary: "Start (or restart) play mode" —
|
|
118
|
+
// `packages/vgai-sdk/src/play/lifecycle-operations.ts`). What used to
|
|
119
|
+
// make a restart unsafe was `enterPlayMode()` having no reentrancy
|
|
120
|
+
// guard for the async-boot window before it assigns `_session` — two
|
|
121
|
+
// overlapping calls ran their entire prologues concurrently and
|
|
122
|
+
// corrupted shared module state (proven by a real console-patching
|
|
123
|
+
// stack overflow — see `play-mode-reentry-ghost.test.ts`). That is
|
|
124
|
+
// fixed at the source in `enterPlayMode()` itself (a FIFO queue
|
|
125
|
+
// serializes every invocation), so every caller of THIS handler — CLI,
|
|
126
|
+
// SDK, a UI Play-button click racing a relayed command — gets a safe,
|
|
127
|
+
// clean restart for free without needing a guard here too.
|
|
128
|
+
try {
|
|
129
|
+
// D15/T-D15.6 (objection-4 fix) — `vgai play --seed <n>` relays as
|
|
130
|
+
// `cmd['seed']`; `enterPlayMode`'s explicit-config seed leg (beats
|
|
131
|
+
// manifest.determinism.defaultSeed/?vgai-seed=).
|
|
132
|
+
const seed = cmd['seed'];
|
|
133
|
+
// `vgai play --name <text>` rides the same wire as `--seed`: a plain
|
|
134
|
+
// field on the relayed command, slugified server-side into this run's
|
|
135
|
+
// log filename and journal line. Absent, everything is unchanged.
|
|
136
|
+
const runName = cmd['name'];
|
|
137
|
+
await enterPlayMode(
|
|
138
|
+
typeof seed === 'number' ? seed : undefined,
|
|
139
|
+
undefined,
|
|
140
|
+
typeof runName === 'string' ? runName : null,
|
|
141
|
+
);
|
|
142
|
+
} catch (err) {
|
|
143
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
144
|
+
}
|
|
145
|
+
// PD-1 — never ack a play that did not start. `enterPlayMode()` resolving
|
|
146
|
+
// does NOT mean play is running: it also resolves for every path that
|
|
147
|
+
// abandoned this run (a Stop or a newer play landed during the async
|
|
148
|
+
// boot, invalidating this generation). Those used to return `{ok:true}`,
|
|
149
|
+
// and the CLI — seeing `playState: 'stopped'` a moment later — fell back
|
|
150
|
+
// to "✗ Play mode failed — check logs/play-*.jsonl", a message naming no
|
|
151
|
+
// cause and pointing at a log that says nothing about it. Ack the
|
|
152
|
+
// measured truth (the live registry, the same predicate `collectState`
|
|
153
|
+
// derives `playState` from) instead of the call returning.
|
|
154
|
+
// The other honest answers are adapter-owned: a root with design-time
|
|
155
|
+
// pieces is a deferred ingest Play session, a boot-mounted ingest runs
|
|
156
|
+
// through its explicit play-control latch, and a standalone `{ module }`
|
|
157
|
+
// route owns its module session. None allocates `play-mode.ts`'s
|
|
158
|
+
// first-party session object, and answering "play did not start" over a
|
|
159
|
+
// visibly running game is the same fabrication in the other direction.
|
|
160
|
+
if (!editorHost().live.playing()) {
|
|
161
|
+
return {
|
|
162
|
+
ok: false,
|
|
163
|
+
error:
|
|
164
|
+
'Play did not start: the run was superseded before it finished booting (a stop, or a ' +
|
|
165
|
+
'newer play, landed during startup). Nothing is running.',
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
// EVERY PLAY RECORDS. There is no flag and no opt-in — see
|
|
169
|
+
// `play-recording.ts` for why the capability had to stop being one an
|
|
170
|
+
// agent must remember to switch on. `cmd['record']` only NAMES the file
|
|
171
|
+
// (`vgai play --record <name>`); its absence names the clip after the
|
|
172
|
+
// durable Gameplay Session.
|
|
173
|
+
//
|
|
174
|
+
// Started here rather than inside `enterPlayMode` on purpose: this
|
|
175
|
+
// handler is the RELAYED play (`vgai play`, the SDK's `play.start`),
|
|
176
|
+
// which is exactly the population the recording exists for. A human
|
|
177
|
+
// clicking the editor's own Play button is their own witness and gets
|
|
178
|
+
// play unchanged.
|
|
179
|
+
const record = cmd['record'];
|
|
180
|
+
const recording = await beginPlayRecording(
|
|
181
|
+
liveInstanceContainer() ?? editorHost().workspace.liveDocument.container(),
|
|
182
|
+
{
|
|
183
|
+
name: typeof record === 'string' ? record : null,
|
|
184
|
+
audio: getActiveSystems().audio?.acquireRecordingStream?.() ?? null,
|
|
185
|
+
canvasFrame: captureLiveCanvasFrame,
|
|
186
|
+
// A hidden tab's game loop is STOPPED (`engine/src/core/game-loop.ts`
|
|
187
|
+
// never arms rAF while `document.hidden`), so its canvas holds one
|
|
188
|
+
// frozen frame. This is the still-capture path's own recovery — one
|
|
189
|
+
// deterministic tick, rendered — applied per recorded frame.
|
|
190
|
+
refreshFrame: refreshRecordingFrame,
|
|
191
|
+
},
|
|
192
|
+
);
|
|
193
|
+
return { ok: true, ...(recording ? { data: { recording } } : {}) };
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
stop: {
|
|
197
|
+
// `stop` waits on teardown (dispose chain, Rapier world free, renderer
|
|
198
|
+
// teardown) — routinely past the generic budget under software rendering.
|
|
199
|
+
timeoutMs: 30_000,
|
|
200
|
+
derivedRefresh: 'always',
|
|
201
|
+
handle: async () => {
|
|
202
|
+
// A video export OWNS the paused run it is stepping, and Stop is the one
|
|
203
|
+
// command the host prologue lets past its refusal so it can cancel it.
|
|
204
|
+
gameplayExportAbort?.abort(new Error('Video export cancelled by Stop.'));
|
|
205
|
+
// Finalize the clip BEFORE the surface it is photographing is torn down:
|
|
206
|
+
// a WebM is unreadable until its recorder closes it, and compositing
|
|
207
|
+
// through a teardown truncates the last seconds — which are the ones
|
|
208
|
+
// someone stopping the run wanted to look at.
|
|
209
|
+
const capture = await endPlayRecording('stop');
|
|
210
|
+
exitPlayMode();
|
|
211
|
+
return { ok: true, ...(capture ? { data: { recording: capture } } : {}) };
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
pause: {
|
|
215
|
+
derivedRefresh: 'always',
|
|
216
|
+
handle: () => {
|
|
217
|
+
pausePlayMode();
|
|
218
|
+
return { ok: true };
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
resume: {
|
|
222
|
+
derivedRefresh: 'always',
|
|
223
|
+
handle: () => {
|
|
224
|
+
resumePlayMode();
|
|
225
|
+
return { ok: true };
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
step: {
|
|
229
|
+
derivedRefresh: 'always',
|
|
230
|
+
handle: () => {
|
|
231
|
+
stepPlayMode();
|
|
232
|
+
return { ok: true };
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
// The video export hands control to the GAME (it steps a paused run frame
|
|
236
|
+
// by frame), so its budget is the game's, not the editor's.
|
|
237
|
+
'bridge-recording-export': {
|
|
238
|
+
timeoutMs: 600_000,
|
|
239
|
+
derivedRefresh: 'none',
|
|
240
|
+
handle: async (cmd) => {
|
|
241
|
+
const host = editorHost();
|
|
242
|
+
if (host.session.playState() !== 'paused')
|
|
243
|
+
return { ok: false, error: 'Pause the game before exporting video.' };
|
|
244
|
+
const container = liveInstanceContainer();
|
|
245
|
+
if (!container) return notPlayingResult();
|
|
246
|
+
if (gameplayExportAbort) return { ok: false, error: 'A video export is already active.' };
|
|
247
|
+
const { exportGameplayVideo, validateGameplayExport, assertGameplayVideoSupport } =
|
|
248
|
+
await import('../src/host/gameplay-export');
|
|
249
|
+
const exportOptions = {
|
|
250
|
+
frames: Number(cmd['frames']),
|
|
251
|
+
fps: cmd['fps'] === undefined ? 30 : Number(cmd['fps']),
|
|
252
|
+
};
|
|
253
|
+
try {
|
|
254
|
+
validateGameplayExport(exportOptions);
|
|
255
|
+
} catch (error) {
|
|
256
|
+
return structuredErrorResult(error);
|
|
257
|
+
}
|
|
258
|
+
if (gameplayExportAbort) return { ok: false, error: 'A video export is already active.' };
|
|
259
|
+
const controller = new AbortController();
|
|
260
|
+
gameplayExportAbort = controller;
|
|
261
|
+
setGameplayExportActive(true);
|
|
262
|
+
const deadline = setTimeout(
|
|
263
|
+
() => controller.abort(new Error('Video export exceeded its ten-minute deadline.')),
|
|
264
|
+
590_000,
|
|
265
|
+
);
|
|
266
|
+
const playStartedAt = host.live.runWindow()?.startedAt ?? null;
|
|
267
|
+
const unsubscribe = host.session.subscribe(() => {
|
|
268
|
+
if (
|
|
269
|
+
host.session.playState() !== 'paused' ||
|
|
270
|
+
(host.live.runWindow()?.startedAt ?? null) !== playStartedAt
|
|
271
|
+
) {
|
|
272
|
+
controller.abort(new Error('Video export cancelled: the Play session changed.'));
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
// The world's own audio for the sim window the frame walk will cover.
|
|
276
|
+
// `renderOffline` is the fixed-step sibling of the recorder's real-time
|
|
277
|
+
// `acquireRecordingStream`; a world that does not implement it exports a
|
|
278
|
+
// genuinely video-only file, said so in the result and in the clip note.
|
|
279
|
+
const offlineAudio = exportAudioSource();
|
|
280
|
+
try {
|
|
281
|
+
await assertGameplayVideoSupport(container);
|
|
282
|
+
controller.signal.throwIfAborted();
|
|
283
|
+
const { gameplayRecordingActive } = await import('../src/host/gameplay-recording');
|
|
284
|
+
if (gameplayRecordingActive()) await stopGameplayRecording();
|
|
285
|
+
const { blob, ...result } = await exportGameplayVideo(container, exportOptions, {
|
|
286
|
+
step: () => flushSync(() => stepPlayMode()),
|
|
287
|
+
assertActive: () => {
|
|
288
|
+
notePlayActivity();
|
|
289
|
+
if (
|
|
290
|
+
host.session.playState() !== 'paused' ||
|
|
291
|
+
liveInstanceContainer() !== container ||
|
|
292
|
+
(host.live.runWindow()?.startedAt ?? null) !== playStartedAt
|
|
293
|
+
) {
|
|
294
|
+
throw new Error('Video export interrupted: its game must remain paused and mounted.');
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
canvasFrame: captureLiveCanvasFrame,
|
|
298
|
+
signal: controller.signal,
|
|
299
|
+
...(offlineAudio ? { audio: offlineAudio } : {}),
|
|
300
|
+
});
|
|
301
|
+
const {
|
|
302
|
+
beginGameplayRecordingSink,
|
|
303
|
+
appendGameplayRecordingChunk,
|
|
304
|
+
finishGameplayRecordingSink,
|
|
305
|
+
abortGameplayRecordingSink,
|
|
306
|
+
} = await import('@volter/editor-core/editor-api');
|
|
307
|
+
controller.signal.throwIfAborted();
|
|
308
|
+
const sink = await beginGameplayRecordingSink(
|
|
309
|
+
{
|
|
310
|
+
startedAt: new Date().toISOString(),
|
|
311
|
+
mimeType: 'video/webm',
|
|
312
|
+
purpose: 'export',
|
|
313
|
+
name: typeof cmd['name'] === 'string' ? cmd['name'] : `export-${Date.now()}`,
|
|
314
|
+
},
|
|
315
|
+
controller.signal,
|
|
316
|
+
);
|
|
317
|
+
try {
|
|
318
|
+
for (
|
|
319
|
+
let offset = 0, sequence = 0;
|
|
320
|
+
offset < blob.size;
|
|
321
|
+
offset += 4 * 1024 * 1024, sequence++
|
|
322
|
+
) {
|
|
323
|
+
controller.signal.throwIfAborted();
|
|
324
|
+
await appendGameplayRecordingChunk(
|
|
325
|
+
sink,
|
|
326
|
+
sequence,
|
|
327
|
+
blob.slice(offset, offset + 4 * 1024 * 1024),
|
|
328
|
+
controller.signal,
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
controller.signal.throwIfAborted();
|
|
332
|
+
await finishGameplayRecordingSink(
|
|
333
|
+
sink,
|
|
334
|
+
result.audio
|
|
335
|
+
? `fixed-step export with audio (${result.audio.codec}, ${result.audio.channels}ch @ ${result.audio.sampleRate} Hz)`
|
|
336
|
+
: 'fixed-step export, video only: this world has no AudioAdapter.renderOffline',
|
|
337
|
+
controller.signal,
|
|
338
|
+
);
|
|
339
|
+
controller.signal.throwIfAborted();
|
|
340
|
+
} catch (error) {
|
|
341
|
+
await abortGameplayRecordingSink(sink);
|
|
342
|
+
throw error;
|
|
343
|
+
}
|
|
344
|
+
return { ok: true, data: { ...result, path: sink.path } };
|
|
345
|
+
} catch (error) {
|
|
346
|
+
return structuredErrorResult(error);
|
|
347
|
+
} finally {
|
|
348
|
+
unsubscribe();
|
|
349
|
+
clearTimeout(deadline);
|
|
350
|
+
gameplayExportAbort = null;
|
|
351
|
+
setGameplayExportActive(false);
|
|
352
|
+
}
|
|
353
|
+
},
|
|
354
|
+
},
|
|
355
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Open Profiler` in the command palette — the same reveal as Debug ▸
|
|
3
|
+
* Profiler, through the action point.
|
|
4
|
+
*/
|
|
5
|
+
import { profilerView } from '../src/host/components/utility-view-state';
|
|
6
|
+
import type { ActionContribution } from '@volter/editor-sdk/chrome';
|
|
7
|
+
|
|
8
|
+
export const point = 'workspace.action';
|
|
9
|
+
|
|
10
|
+
export const actions: ActionContribution['actions'] = [
|
|
11
|
+
{
|
|
12
|
+
id: 'profiler',
|
|
13
|
+
label: 'Open Profiler',
|
|
14
|
+
execute: () => profilerView.open('profiler'),
|
|
15
|
+
},
|
|
16
|
+
];
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Debug ▸ Profiler — the menu item that selects the bench's Profiler view and
|
|
3
|
+
* reveals the drawer tab this package contributes. The host namespaces every
|
|
4
|
+
* contributed id under `tool:`, so the utility revealed is
|
|
5
|
+
* `tool:profiler.utility` (`profilerView.open` names it).
|
|
6
|
+
*/
|
|
7
|
+
import { profilerView } from '../src/host/components/utility-view-state';
|
|
8
|
+
import type { MenuContribution } from '@volter/editor-sdk/chrome';
|
|
9
|
+
|
|
10
|
+
export const point = 'workspace.menu';
|
|
11
|
+
|
|
12
|
+
export const menu: MenuContribution = {
|
|
13
|
+
menu: 'debug',
|
|
14
|
+
items: [
|
|
15
|
+
{
|
|
16
|
+
id: 'profiler',
|
|
17
|
+
label: 'Profiler',
|
|
18
|
+
testId: 'menu-profiler',
|
|
19
|
+
execute: () => profilerView.open('profiler'),
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Profiler as a drawer tab (W4/W4b): the engine's instrument bench —
|
|
3
|
+
* frame cost over time (Profiler) and the single-frame draw-call capture
|
|
4
|
+
* (Frame) over the inspected session's `RenderDebugAdapter` and the active
|
|
5
|
+
* document's registered render loop. Always on offer: the editor's own
|
|
6
|
+
* documents register profiler sources in edit mode, and Play supplies the
|
|
7
|
+
* running game's.
|
|
8
|
+
*/
|
|
9
|
+
import { ProfilerPanel } from '../src/profiler/ProfilerPanel';
|
|
10
|
+
|
|
11
|
+
export const point = 'workspace.utility';
|
|
12
|
+
export const title = 'Profiler';
|
|
13
|
+
export const order = 20;
|
|
14
|
+
|
|
15
|
+
export default function ProfilerUtility() {
|
|
16
|
+
return <ProfilerPanel />;
|
|
17
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE DOM MEDIUM'S COMPONENT BOARD (`@volter/editor-sdk/services`, a
|
|
3
|
+
* `workspace.service` contribution): `@volter/editor-game` tells the host's component
|
|
4
|
+
* board registry that this project's `dom` stories have a `UI` board, when it
|
|
5
|
+
* exists, and what to say when it does not.
|
|
6
|
+
*
|
|
7
|
+
* TRANSCRIBED from `design-time-mount.service.ts` beside it — the same
|
|
8
|
+
* package, the same point, a sibling registry. The board module is loaded
|
|
9
|
+
* eagerly rather than behind a dynamic `import()` (which that mount uses)
|
|
10
|
+
* because `presence()` is asked the moment the host reconciles, not when a
|
|
11
|
+
* candidate appears: a promise cannot answer a verdict thunk.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { registerComponentBoard } from '@volter/editor-core/component-board-registry';
|
|
15
|
+
import { uiComponentBoard } from '../../src/react/ui-component-board';
|
|
16
|
+
|
|
17
|
+
export const point = 'workspace.service';
|
|
18
|
+
|
|
19
|
+
export function start(): () => void {
|
|
20
|
+
return registerComponentBoard(uiComponentBoard);
|
|
21
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE DOM MEDIUM'S DESIGN-TIME MOUNT (`@volter/editor-sdk/services`, a
|
|
3
|
+
* `workspace.service` contribution): `@volter/editor-game` tells the host's design-time
|
|
4
|
+
* mount registry how a `dom` world mounts at design time, and when its
|
|
5
|
+
* mounted layers go stale.
|
|
6
|
+
*
|
|
7
|
+
* It is a `workspace.service` for the same reason the React/DOM Inspector
|
|
8
|
+
* beside it is: a mount is not a document, so it never opens and never
|
|
9
|
+
* persists, and what a contribution pass owns is when its registration
|
|
10
|
+
* exists. Unlike that one the stop is REAL — the registration is the only
|
|
11
|
+
* thing standing between a `dom` root and the "no package registered a mount
|
|
12
|
+
* for this medium" disclosure, so a pass that ends must take it back.
|
|
13
|
+
*
|
|
14
|
+
* THE MOUNT IS LAZY, and that is transcribed rather than invented: the canvas
|
|
15
|
+
* medium's mount has been behind a dynamic `import()` since it was written
|
|
16
|
+
* (so `pixi.js`/`@pixi/react` enter a bundle only for a project that declares
|
|
17
|
+
* a canvas root), and the same argument holds here — the board's module
|
|
18
|
+
* reaches the story registry, Storybook's preview API, the React mount
|
|
19
|
+
* runtime and the React authoring adapter. A project with no `dom` root never
|
|
20
|
+
* loads any of it, and this contribution module stays a handful of lines in
|
|
21
|
+
* the build's eager graph.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { registerDesignTimeMount } from '@volter/editor-core/authoring/design-time-mount-registry';
|
|
25
|
+
|
|
26
|
+
export const point = 'workspace.service';
|
|
27
|
+
|
|
28
|
+
export function start(): () => void {
|
|
29
|
+
return registerDesignTimeMount({
|
|
30
|
+
kind: 'dom',
|
|
31
|
+
owner: '@volter/editor-game/react/design-time-mount',
|
|
32
|
+
// The `ResolvedAdapter['identity']` a mount failure is reported under —
|
|
33
|
+
// the literal the host's own catch block used to spell.
|
|
34
|
+
identity: 'default-react',
|
|
35
|
+
// Deliberately absent: a dom layer's design-time hover/press testing of
|
|
36
|
+
// real UI competes with authoring gestures, so it stays behind the `eye`
|
|
37
|
+
// row's `interactive` toggle. The canvas medium declares the opposite.
|
|
38
|
+
mount: async (candidate, layer, context) => {
|
|
39
|
+
const { mountReactDesignLayer } = await import('../../src/react/design-time-react-mount');
|
|
40
|
+
return mountReactDesignLayer(candidate, layer, context);
|
|
41
|
+
},
|
|
42
|
+
reprojectWhen: (invalidate) => {
|
|
43
|
+
let stopped = false;
|
|
44
|
+
let stop: (() => void) | null = null;
|
|
45
|
+
// The hook's own module is the mount's, so binding it eagerly would
|
|
46
|
+
// load the board's whole graph for a project that has no dom root. The
|
|
47
|
+
// host only calls this when a dom candidate exists, so loading here is
|
|
48
|
+
// exactly the right moment.
|
|
49
|
+
void import('../../src/react/design-time-react-mount').then((module) => {
|
|
50
|
+
if (stopped) return;
|
|
51
|
+
stop = module.reprojectWhenStoriesRepublish(invalidate);
|
|
52
|
+
});
|
|
53
|
+
return () => {
|
|
54
|
+
stopped = true;
|
|
55
|
+
stop?.();
|
|
56
|
+
stop = null;
|
|
57
|
+
};
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* "MATERIALIZE PASTEBOARD FROM UI BOARD" in the command palette
|
|
3
|
+
* (`@volter/editor-sdk/chrome`, a `workspace.action` contribution) — the ONE
|
|
4
|
+
* action that turns the UI board's derived auto-grid into an authored
|
|
5
|
+
* pasteboard `.tsx`.
|
|
6
|
+
*
|
|
7
|
+
* It was a row in the host's own `action-registry.ts:404-423`, lazily
|
|
8
|
+
* importing `pasteboard-materialize.ts` — a host action about THIS package's
|
|
9
|
+
* design-time surface, which is the only thing that kept both files in the
|
|
10
|
+
* host (WORK.md §The open-source launch item 17). Shape transcribed from
|
|
11
|
+
* `@volter/editor-game/contributions/profiler.action.ts`: a static `actions` array, no
|
|
12
|
+
* subscribe, because this action's availability never changes — it either
|
|
13
|
+
* materializes or it refuses by name.
|
|
14
|
+
*
|
|
15
|
+
* THE OUTCOME LANDS IN THE EDITOR CONSOLE EITHER WAY, unchanged from the row
|
|
16
|
+
* it left: the success sentence names the file (which also opens), a refusal
|
|
17
|
+
* names its remedy.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { editorConsole } from '@volter/editor-core/editor-console';
|
|
21
|
+
import type { ActionContribution } from '@volter/editor-sdk/chrome';
|
|
22
|
+
|
|
23
|
+
export const point = 'workspace.action';
|
|
24
|
+
|
|
25
|
+
export const actions: ActionContribution['actions'] = [
|
|
26
|
+
{
|
|
27
|
+
id: 'pasteboard.materialize',
|
|
28
|
+
label: 'Materialize Pasteboard from UI Board',
|
|
29
|
+
execute: async () => {
|
|
30
|
+
const { materializePasteboard } = await import('../../src/react/pasteboard-materialize');
|
|
31
|
+
try {
|
|
32
|
+
editorConsole.log(`[pasteboard] ${await materializePasteboard()}`, 'authoring');
|
|
33
|
+
} catch (error) {
|
|
34
|
+
editorConsole.error(
|
|
35
|
+
`[pasteboard] materialize refused: ${error instanceof Error ? error.message : String(error)}`,
|
|
36
|
+
'authoring',
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
];
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE REACT/DOM INSPECTOR (`@volter/editor-sdk/services`, a `workspace.service`
|
|
3
|
+
* contribution): the rich CSS/layout section that stands in for the shell's
|
|
4
|
+
* generic property grid on a node owned by a react or DOM adapter — borders,
|
|
5
|
+
* radii, shadows, gradients, filters, fonts, alignment, the breakpoint strip
|
|
6
|
+
* and the eyedropper, each descriptor mapped to its widget instead of a text
|
|
7
|
+
* field.
|
|
8
|
+
*
|
|
9
|
+
* ## Why this is a package and not the host's
|
|
10
|
+
*
|
|
11
|
+
* It was `packages/editor/src/authoring/react-inspector-section.tsx`,
|
|
12
|
+
* registered at module scope and reached by ONE line in the host's entry:
|
|
13
|
+
* `main.tsx:33`, `import './authoring/react-inspector-section';`. A bare
|
|
14
|
+
* side-effect import has no `from`, so the closure gate's walk never followed
|
|
15
|
+
* it — and behind it came the whole React/DOM design-time surface, 28 files
|
|
16
|
+
* and 14,274 lines, into every editor boot, a `models` build that authors no
|
|
17
|
+
* DOM included. The gate was corrected first (PR #7225) so this cut could be
|
|
18
|
+
* measured rather than asserted.
|
|
19
|
+
*
|
|
20
|
+
* ## Why its own package rather than `@volter/editor-game`
|
|
21
|
+
*
|
|
22
|
+
* Because `@volter/editor-game` already imports the surface this section drives from
|
|
23
|
+
* two unrelated lanes — `src/play/react-play-live-authoring.ts` and the
|
|
24
|
+
* ingest lane's DOM mount both reach `ReactRootAuthoringAdapter` — and the
|
|
25
|
+
* planned design skew (a Figma-shaped canvas) wants the same surface and is
|
|
26
|
+
* not a game. A surface a game editor and a design editor both author lives
|
|
27
|
+
* in neither.
|
|
28
|
+
*
|
|
29
|
+
* ## The seam, which is not a new one
|
|
30
|
+
*
|
|
31
|
+
* `@editor/inspector-section-registry` is the host's own registry and has been
|
|
32
|
+
* "the PERMANENT, sanctioned adapter-module seam" since it was written; the
|
|
33
|
+
* package REGISTERS, and the host names no lane. The service point is
|
|
34
|
+
* transcribed from `@volter/editor-game`'s `story-documents.service.ts`, which installs
|
|
35
|
+
* a story-scoped Inspector section the same way and for the same reason: a
|
|
36
|
+
* section is not a document, so it never opens and never persists, and what a
|
|
37
|
+
* contribution pass owns is when its registration exists.
|
|
38
|
+
*
|
|
39
|
+
* The `tool.inspector` point was CONSIDERED AND REJECTED with the reason here:
|
|
40
|
+
* it pins a contributed section to the wrench glyph at
|
|
41
|
+
* `CONTRIBUTED_SECTION_ORDER + declared`, while this section CLAIMS
|
|
42
|
+
* `PROPERTIES_SECTION_ID` and composes the node's own property GROUPS in
|
|
43
|
+
* `GROUP_SECTION_ORDER`. Routing it through that point would have moved the
|
|
44
|
+
* blocks below every contributed section and left the thin generic grid
|
|
45
|
+
* showing beside them.
|
|
46
|
+
*
|
|
47
|
+
* ## What has NOT moved, and why
|
|
48
|
+
*
|
|
49
|
+
* THE ADAPTERS THEMSELVES MOVED HERE on 2026-09-18 (phase 1 unit 12):
|
|
50
|
+
* `react-world-authoring-adapter.ts` and `dom-authoring-adapter.ts` are
|
|
51
|
+
* `@volter/editor-game/src/react/` files now. The blocker this docblock used to name —
|
|
52
|
+
* "`authoring/design-time-layers.ts` CONSTRUCTS the React adapter" — is gone,
|
|
53
|
+
* because the design-time mount is a REGISTRATION now
|
|
54
|
+
* (`@editor/authoring/design-time-mount-registry`), so both constructions
|
|
55
|
+
* moved into this package with `mountReactLayer` and the pair had no host
|
|
56
|
+
* importer left. `@volter/editor-game`'s play-live authoring and four modules of its
|
|
57
|
+
* ingest lane import them as `@volter/editor-game/react/…`; that package declares the
|
|
58
|
+
* dependency.
|
|
59
|
+
*
|
|
60
|
+
* Still host files, reached through `@editor/*` — the shape units 4 and 5
|
|
61
|
+
* landed — and each with the host importer that keeps it there:
|
|
62
|
+
* the DOM projection and the CSS inspection estate
|
|
63
|
+
* (`components/RootSelectionOverlay.tsx`); the surgical JSX writer and the
|
|
64
|
+
* structural write pipe (`authoring/r3f-source-authoring-adapter.ts`,
|
|
65
|
+
* `@vgai/canvas/pixi-source-write-target.ts`); the breakpoint state
|
|
66
|
+
* (`components/BoardRulers.tsx`); `authoring/react-story-board.ts`, the
|
|
67
|
+
* board's geometry, which four host surfaces read
|
|
68
|
+
* (`components/RootSelectionOverlay.tsx:98`,
|
|
69
|
+
* `components/ReactCanvasControls.tsx:15`, `pasteboard-materialize.ts:20`,
|
|
70
|
+
* `canvas-board/CanvasBoardDocument.tsx:57`) because they draw chrome OVER
|
|
71
|
+
* the board. Each is its own unit with its own seam; none of them is in the
|
|
72
|
+
* host's EAGER closure, which is what these cuts buy.
|
|
73
|
+
*/
|
|
74
|
+
|
|
75
|
+
import { ensureReactInspectorSectionRegistered } from '../../src/react/react-inspector-section';
|
|
76
|
+
|
|
77
|
+
export const point = 'workspace.service';
|
|
78
|
+
|
|
79
|
+
export function start(): () => void {
|
|
80
|
+
ensureReactInspectorSectionRegistered();
|
|
81
|
+
// The stop is a no-op, and that is the same lifetime this had as a host
|
|
82
|
+
// module: its registration group is HMR-owned, and the section outlives any
|
|
83
|
+
// one contribution pass. Nothing here holds a resource a pass may end.
|
|
84
|
+
return () => {};
|
|
85
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE ISOLATED STORY DOCUMENT (`@volter/editor-sdk/services`, a
|
|
3
|
+
* `workspace.service` contribution): the center document a portable CSF story
|
|
4
|
+
* opens as on the DOM/canvas surface, with its `Story Args` Inspector section
|
|
5
|
+
* and its `Actions`/`Interactions`/`Accessibility` utilities, plus the
|
|
6
|
+
* Storybook Docs companion.
|
|
7
|
+
*
|
|
8
|
+
* The three-medium turntable is `@volter/editor-game`'s own contribution of the same
|
|
9
|
+
* name. They were one service while both documents lived in `@volter/editor-game`;
|
|
10
|
+
* splitting them is what lets a product compose one medium without the other,
|
|
11
|
+
* and neither registers anything the other needs.
|
|
12
|
+
*
|
|
13
|
+
* A SERVICE and not a `workspace.document`: this is not ONE tab the host opens
|
|
14
|
+
* by contribution id — it is a kind with an unbounded set of documents, one
|
|
15
|
+
* per (module, story), each carrying its own arg overrides and action log.
|
|
16
|
+
* What the contribution pass owns is when its registrations exist; the
|
|
17
|
+
* documents themselves open through their addresses
|
|
18
|
+
* (`@editor/document-open-registry` for the address,
|
|
19
|
+
* `@editor/workspace-document-restore` for the persisted state), so the kit
|
|
20
|
+
* names no module here.
|
|
21
|
+
*
|
|
22
|
+
* What stays the KIT's, deliberately: the story REGISTRY and discovery, the
|
|
23
|
+
* declared medium and the project's story regions, the arg descriptors and the
|
|
24
|
+
* Content grid's component thumbnail — portable CSF is the kit's design-time
|
|
25
|
+
* state (ARCHITECTURE-CORE §The target shape), read by the project adapter,
|
|
26
|
+
* the inspection projection, the coverage families and the Edit tab row.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { ensureStoryContributionsRegistered } from '../../src/react/story-documents/story-documents';
|
|
30
|
+
|
|
31
|
+
export const point = 'workspace.service';
|
|
32
|
+
|
|
33
|
+
export function start(): () => void {
|
|
34
|
+
// The module registers its opener and restorer at module scope; this call
|
|
35
|
+
// installs the story-scoped Inspector section and utilities, which used to
|
|
36
|
+
// happen at editor boot. It is idempotent and returns nothing; the
|
|
37
|
+
// registration group it owns is HMR-safe by construction.
|
|
38
|
+
ensureStoryContributionsRegistered();
|
|
39
|
+
// The stop is a no-op, and that is the same lifetime this had as a host
|
|
40
|
+
// module: its registration group is HMR-owned, and an open story document
|
|
41
|
+
// outlives any one contribution pass. Nothing here holds a resource a pass
|
|
42
|
+
// may end.
|
|
43
|
+
return () => {};
|
|
44
|
+
}
|