@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,316 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `bridge-screenshot` and the recording verbs' shared frame plumbing, moved
|
|
3
|
+
* out of the editor's `command-listener.ts` with the verbs they serve
|
|
4
|
+
* (WORK.md §The workbench). Play mode, the play/gameplay recorders, the
|
|
5
|
+
* composite capture and the editor console are reached through the
|
|
6
|
+
* `@editor/*` alias until those modules move too. Nothing here is host API.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { commandLine } from '@volter/editor-core/product-command';
|
|
10
|
+
import {
|
|
11
|
+
type CaptureFlatness,
|
|
12
|
+
CaptureLayerError,
|
|
13
|
+
capturePlayComposite,
|
|
14
|
+
sampleFlatness,
|
|
15
|
+
} from '@volter/editor-core/composite-screenshot';
|
|
16
|
+
import { editorConsole } from '@volter/editor-core/editor-console';
|
|
17
|
+
import { presentationSurface } from '@volter/editor-core/presentation-surface';
|
|
18
|
+
import type { EditorCommandMessage, EditorCommandResult } from '@volter/editor-sdk/commands';
|
|
19
|
+
import { notPlayingResult, structuredErrorResult } from '../command-results';
|
|
20
|
+
import { isIngestActive } from '../ingest/active-ingest';
|
|
21
|
+
import {
|
|
22
|
+
getInstanceCanvas,
|
|
23
|
+
getInstanceContainer,
|
|
24
|
+
getPlayRuntimeAccess,
|
|
25
|
+
isPlayModeActive,
|
|
26
|
+
} from '../play/play-mode';
|
|
27
|
+
import { livePlayRecording } from '../play/play-recording';
|
|
28
|
+
import { hasLiveDebugPlane } from './dispatch';
|
|
29
|
+
import { captureLiveCanvasFrame } from './live-frames';
|
|
30
|
+
|
|
31
|
+
/** `bridge-screenshot` relay op handler — captures the live play-mode game
|
|
32
|
+
* view and returns it in the same `{base64, mimeType}` shape
|
|
33
|
+
* `capture-viewport` already uses. #146: the capture is now the FULL game
|
|
34
|
+
* stack — canvas(es) plus the DOM UI layers (React adapter roots,
|
|
35
|
+
* react-world layers) composited via `capturePlayComposite` — because a
|
|
36
|
+
* canvas-only frame of a game whose score/health lives in the HUD is not
|
|
37
|
+
* "what the game looks like". When the composite leg can't run (no
|
|
38
|
+
* container, no DOM Image/XMLSerializer — e.g. the node-side relay tests —
|
|
39
|
+
* or a rasterization failure), it degrades to the original canvas-only
|
|
40
|
+
* `toDataURL` capture, marked
|
|
41
|
+
* honestly with `composite: false`. A caller (`RelayTransport.screenshot`
|
|
42
|
+
* in `@volter/editor-live`) writes the PNG to disk — this relay op stays a pure
|
|
43
|
+
* "hand back the pixels" primitive, matching `bridge-call`'s own
|
|
44
|
+
* session-generic, run-lifecycle-free shape.
|
|
45
|
+
*
|
|
46
|
+
* Both legs also carry the PIXEL-honesty fields — `flatness` (how much of the
|
|
47
|
+
* frame is one flat surface, with the caller-facing sentence written here
|
|
48
|
+
* where the pixels are) and `loopRecoveryFrame` (this frame exists because
|
|
49
|
+
* capture recovered a starved runtime with one deterministic tick). Shaped by
|
|
50
|
+
* {@link screenshotResult} so the two legs cannot drift apart.
|
|
51
|
+
*
|
|
52
|
+
* BOTH legs read a canvas, and a canvas is only readable after its frame when
|
|
53
|
+
* its WebGL context was created with `preserveDrawingBuffer: true`. Every
|
|
54
|
+
* canvas the vgai RUNTIME mounts sets it (`@volter/game-runtime/runtime/create-runtime`,
|
|
55
|
+
* D5 §1/§4); a canvas an INGESTED game created is the game's own and usually
|
|
56
|
+
* does not, which read as a silently BLACK screenshot over a perfectly
|
|
57
|
+
* healthy running game. {@link liveCanvasFrame} is the answer for that
|
|
58
|
+
* case — one seam over both substrates' recovery mechanisms — and it
|
|
59
|
+
* is `null` for everything else, leaving the first-party path untouched. */
|
|
60
|
+
|
|
61
|
+
/** The clip a delivered still belongs to — see {@link screenshotRecordingNotice}. */
|
|
62
|
+
interface GameCaptureRecordingNote {
|
|
63
|
+
readonly path: string;
|
|
64
|
+
readonly startedAt: string;
|
|
65
|
+
/** The sentence the caller shows verbatim, written here where the pixels are. */
|
|
66
|
+
readonly notice: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function screenshotResult(
|
|
70
|
+
base64: string,
|
|
71
|
+
parts: {
|
|
72
|
+
composite: boolean;
|
|
73
|
+
layers?: { canvases: number; domOverlays: number };
|
|
74
|
+
flatness: CaptureFlatness | null | undefined;
|
|
75
|
+
loopRecoveryFrame: boolean;
|
|
76
|
+
},
|
|
77
|
+
): EditorCommandResult {
|
|
78
|
+
const recording = screenshotRecordingNotice();
|
|
79
|
+
return {
|
|
80
|
+
ok: true,
|
|
81
|
+
data: {
|
|
82
|
+
base64,
|
|
83
|
+
mimeType: 'image/png',
|
|
84
|
+
composite: parts.composite,
|
|
85
|
+
...(parts.layers ? { layers: parts.layers } : {}),
|
|
86
|
+
...(parts.flatness ? { flatness: parts.flatness } : {}),
|
|
87
|
+
...(parts.loopRecoveryFrame ? { loopRecoveryFrame: true } : {}),
|
|
88
|
+
...(recording ? { recording } : {}),
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* WHAT A STILL CAN AND CANNOT ANSWER, said ON the still.
|
|
95
|
+
*
|
|
96
|
+
* A screenshot answers "what does it look like". It cannot answer "did the
|
|
97
|
+
* jump land", "did the enemy path around the wall", "did the shot register" —
|
|
98
|
+
* a single frame answers those only by luck, and a lucky answer is
|
|
99
|
+
* indistinguishable from a real one, which is how a broken game gets reported
|
|
100
|
+
* as working. So every capture taken during a recorded run carries the clip's
|
|
101
|
+
* coordinates: the file, the wall-clock start its offsets are measured from,
|
|
102
|
+
* and the play log whose event timestamps convert an event into an offset.
|
|
103
|
+
*
|
|
104
|
+
* THIS USED TO BE A REFUSAL, AND THE REFUSAL WAS THE DEFECT (2026-08-29,
|
|
105
|
+
* cold fox #3). Because `vgai play` always records, refusing the still made
|
|
106
|
+
* `vgai screenshot` dead for the rest of the session — and the look lane's
|
|
107
|
+
* questions ("is the ear proportion right", "is the fox sunk into the
|
|
108
|
+
* ground") are exactly the ones a still DOES answer. The refusal was
|
|
109
|
+
* discouraging a wrong question by making a right one impossible, and the
|
|
110
|
+
* cold agents' whole loop is play → look.
|
|
111
|
+
*
|
|
112
|
+
* Nothing about the clip changed: play still records unconditionally
|
|
113
|
+
* (`play-recording.ts` — the evidence must not depend on anyone remembering
|
|
114
|
+
* to ask for it), and the capture reads the canvas and composites the DOM
|
|
115
|
+
* layers into an offscreen image, which the recorder's own stream never sees.
|
|
116
|
+
* What changed is that the redirect is now attached to a delivered frame
|
|
117
|
+
* instead of replacing it.
|
|
118
|
+
*
|
|
119
|
+
* No tool instructions here on purpose: the reader knows how to extract frames
|
|
120
|
+
* from a WebM. What they cannot know without being told is WHERE the clip is,
|
|
121
|
+
* WHEN it started, and HOW MANY frames constitute looking.
|
|
122
|
+
*/
|
|
123
|
+
function screenshotRecordingNotice(): GameCaptureRecordingNote | null {
|
|
124
|
+
const live = livePlayRecording();
|
|
125
|
+
if (!live) return null;
|
|
126
|
+
return {
|
|
127
|
+
path: live.path,
|
|
128
|
+
startedAt: live.startedAt,
|
|
129
|
+
notice: [
|
|
130
|
+
`This run is being RECORDED — the still above is one frame of ${live.path}.`,
|
|
131
|
+
'A still cannot answer a question about a moving game (did the jump land, did the shot',
|
|
132
|
+
'register). If that is the question, read the clip:',
|
|
133
|
+
'',
|
|
134
|
+
` recording: ${live.path}${live.rotates ? ` (the next ${commandLine('play')} replaces this file)` : ' (named — never rotated)'}`,
|
|
135
|
+
...(live.format === 'canvas-dom'
|
|
136
|
+
? [
|
|
137
|
+
` HUD replay: ${live.replayPath}`,
|
|
138
|
+
' The raw WebM contains the canvas only. Reconstruct HUD stills with',
|
|
139
|
+
' editor.recording.captureReplay, or export a full video with editor.recording.exportReplay.',
|
|
140
|
+
]
|
|
141
|
+
: []),
|
|
142
|
+
` startedAt: ${live.startedAt} (clip offset 0)`,
|
|
143
|
+
` play log: ${live.logFile === null ? '(none — nothing is logging this run)' : `logs/${live.logFile}`} (event timestamps → clip offsets)`,
|
|
144
|
+
'',
|
|
145
|
+
'THE BAR for a temporal question: examine AT LEAST 10 frames spanning no more than 5',
|
|
146
|
+
'seconds around the moment. One frame, or ten frames spread across the run, is the same',
|
|
147
|
+
'guess a single screenshot was.',
|
|
148
|
+
'',
|
|
149
|
+
`The clip is finalized and readable when play stops — ${commandLine('stop')}, or the idle`,
|
|
150
|
+
`auto-stop after ${Math.round(live.idleAutoStopMs / 1000)}s with no session command and no player input.`,
|
|
151
|
+
].join('\n'),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export async function handleBridgeScreenshot(
|
|
156
|
+
cmd: EditorCommandMessage,
|
|
157
|
+
): Promise<EditorCommandResult> {
|
|
158
|
+
// A recorded run does NOT gate this capture — `screenshotResult` attaches the
|
|
159
|
+
// clip's coordinates to the frame instead (see `screenshotRecordingNotice`).
|
|
160
|
+
// Same reachability rule `handleBridgeCall` uses, and for the same reason:
|
|
161
|
+
// an ingested game's mount IS the running game, so it never creates a
|
|
162
|
+
// first-party play session and `isPlayModeActive()` alone made
|
|
163
|
+
// `vgai screenshot` permanently unusable against ingest. Loosening the gate
|
|
164
|
+
// costs nothing in honesty — the ingest mount renders into the very
|
|
165
|
+
// container `getInstanceContainer()` returns (`play-mode.ts`'s
|
|
166
|
+
// `getGameContainer` is shared with the ingest mount paths), and when there is
|
|
167
|
+
// genuinely nothing to capture both legs below still refuse by name with
|
|
168
|
+
// `BRIDGE_SCREENSHOT_UNAVAILABLE`.
|
|
169
|
+
//
|
|
170
|
+
// `isIngestActive()` is the third reader, and it is what makes the refusal
|
|
171
|
+
// TRUE. `hasLiveDebugPlane()` covers an ingest that publishes a debug plane
|
|
172
|
+
// projected from a declared contract — which a canvas ingest has no way to
|
|
173
|
+
// do (`mount-canvas-ingest-root.ts` publishes `setActiveSystems({})`), so
|
|
174
|
+
// `vgai screenshot` answered "not in play mode" over a mounted, running,
|
|
175
|
+
// self-ticking game whose play state the mount had itself set to `playing`.
|
|
176
|
+
// A lying refusal is worse than a missing feature: it sends the reader to
|
|
177
|
+
// `vgai play`, which is not the thing that was wrong. The question this gate
|
|
178
|
+
// asks is "is a game surface live here", and a live ingest is one.
|
|
179
|
+
if (!isPlayModeActive() && !hasLiveDebugPlane() && !isIngestActive()) return notPlayingResult();
|
|
180
|
+
// Capture the ADDRESSED seat, not always the primary. `game.instance(id)`
|
|
181
|
+
// sends its id here, so `game.instance('2').screenshot()` grabs seat 2's game
|
|
182
|
+
// stack; an unaddressed call still captures the primary, and a caller that
|
|
183
|
+
// wants every seat captures each id in turn (one image per instance).
|
|
184
|
+
const instance = cmd['instance'] as string | undefined;
|
|
185
|
+
const container = getInstanceContainer(instance);
|
|
186
|
+
// WHICH SURFACE CARRIES THIS GAME'S PICTURE — a read, not a sniff, in both
|
|
187
|
+
// halves. `getInstanceCanvas` resolves the ELEMENT through
|
|
188
|
+
// `presentation-surface.ts` (the host's own mount stamp, then a self-booting
|
|
189
|
+
// game's contract, then the labelled measured sweep), and `medium` is what
|
|
190
|
+
// the adapter's region table DECLARES this project is presented on.
|
|
191
|
+
const canvas = getInstanceCanvas(instance);
|
|
192
|
+
const medium = presentationSurface(container).medium;
|
|
193
|
+
// A starved canvas is provably stale, but a React-only root has no canvas:
|
|
194
|
+
// its DOM is self-driven and `capturePlayComposite` has its own bounded
|
|
195
|
+
// on-demand paint fallback. The gate keys on the presentation surface
|
|
196
|
+
// EXISTING, which is the question it always meant to ask — and that existence
|
|
197
|
+
// check now reads a declaration before it measures anything.
|
|
198
|
+
const runtime = getPlayRuntimeAccess();
|
|
199
|
+
let loopRecoveryFrame = false;
|
|
200
|
+
let compositeFailure: unknown = null;
|
|
201
|
+
if (canvas && runtime?.loop.liveness === 'loop-starved') {
|
|
202
|
+
if (cmd['refreshStarvedFrame'] === true && runtime.runTicks) {
|
|
203
|
+
try {
|
|
204
|
+
runtime.runTicks(1, { render: 'last' });
|
|
205
|
+
// Stamp the RESULT, not just the log: a frame that exists because the
|
|
206
|
+
// capture drove the game is current but nobody was watching it, and
|
|
207
|
+
// every caller (CLI, poke surface, relay transport) has to be able to
|
|
208
|
+
// say so without inferring it from its own retry bookkeeping.
|
|
209
|
+
loopRecoveryFrame = true;
|
|
210
|
+
} catch (err) {
|
|
211
|
+
return structuredErrorResult(err);
|
|
212
|
+
}
|
|
213
|
+
} else {
|
|
214
|
+
return {
|
|
215
|
+
ok: false,
|
|
216
|
+
error:
|
|
217
|
+
"cannot capture a fresh game frame while the host loop reports 'loop-starved' — " +
|
|
218
|
+
'inspect the separate visibility readings, let presentation resume, or request a ' +
|
|
219
|
+
'deterministic loop-recovery frame',
|
|
220
|
+
data: { code: 'BRIDGE_SCREENSHOT_STALE' },
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (
|
|
225
|
+
container &&
|
|
226
|
+
typeof Image !== 'undefined' &&
|
|
227
|
+
typeof XMLSerializer !== 'undefined' &&
|
|
228
|
+
typeof container.getBoundingClientRect === 'function'
|
|
229
|
+
) {
|
|
230
|
+
try {
|
|
231
|
+
const composite = await capturePlayComposite(container, {
|
|
232
|
+
canvasFrame: captureLiveCanvasFrame,
|
|
233
|
+
// The DECLARED medium, from the adapter's region table. `undefined`
|
|
234
|
+
// (no table loaded yet) leaves the compositor-settle heuristic on its
|
|
235
|
+
// own canvas count, exactly as before.
|
|
236
|
+
...(medium === 'unknown' ? {} : { presentsOnCanvas: medium === 'canvas' }),
|
|
237
|
+
});
|
|
238
|
+
if (composite.layers.canvases === 0 && composite.layers.domOverlays === 0) {
|
|
239
|
+
throw new CaptureLayerError(
|
|
240
|
+
medium === 'dom' ? 'dom-overlay' : 'composite-output',
|
|
241
|
+
'no game layers were mounted',
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
return screenshotResult(composite.base64, {
|
|
245
|
+
composite: true,
|
|
246
|
+
layers: composite.layers,
|
|
247
|
+
flatness: composite.flatness,
|
|
248
|
+
loopRecoveryFrame,
|
|
249
|
+
});
|
|
250
|
+
} catch (error) {
|
|
251
|
+
compositeFailure = error;
|
|
252
|
+
editorConsole.warn(
|
|
253
|
+
`Full-stack screenshot failed; returning the canvas-only fallback — ${error instanceof Error ? error.message : String(error)}`,
|
|
254
|
+
'screenshot',
|
|
255
|
+
);
|
|
256
|
+
// Degrade to the canvas-only leg below — a HUD-less capture beats no
|
|
257
|
+
// capture, and `composite: false` keeps the degradation visible.
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
// A native React game has no canvas by design. The composite leg above is
|
|
261
|
+
// therefore the PRIMARY capture path for a React-only root, not an
|
|
262
|
+
// enhancement after a canvas precondition. Only fail once both the full
|
|
263
|
+
// game-stack capture and the canvas fallback are unavailable.
|
|
264
|
+
if (!canvas) {
|
|
265
|
+
const compositeReason =
|
|
266
|
+
compositeFailure instanceof Error ? compositeFailure.message : String(compositeFailure);
|
|
267
|
+
return {
|
|
268
|
+
ok: false,
|
|
269
|
+
error:
|
|
270
|
+
compositeFailure !== null
|
|
271
|
+
? `full game-stack capture failed — ${compositeReason}; no canvas layer is available as a fallback`
|
|
272
|
+
: isIngestActive()
|
|
273
|
+
? 'the ingest mount is live but no DOM overlay or canvas layer is mounted for capture'
|
|
274
|
+
: 'no play-mode DOM overlay or canvas layer is mounted yet',
|
|
275
|
+
data: { code: 'BRIDGE_SCREENSHOT_UNAVAILABLE' },
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
try {
|
|
279
|
+
// Same seam as the composite leg, for the same reason: a game's own canvas
|
|
280
|
+
// is unreadable this late, so ask for a same-frame copy first and fall back
|
|
281
|
+
// to reading the canvas itself.
|
|
282
|
+
const frameModule = await import('@volter/editor-core/live-canvas-frame');
|
|
283
|
+
const readable =
|
|
284
|
+
frameModule.readablePngSource(await frameModule.liveCanvasFrame(canvas)) ?? canvas;
|
|
285
|
+
const dataUrl = readable.toDataURL('image/png');
|
|
286
|
+
const comma = dataUrl.indexOf(',');
|
|
287
|
+
const base64 = comma >= 0 ? dataUrl.slice(comma + 1) : dataUrl;
|
|
288
|
+
// The degraded leg gets the same honesty check as the composite one — a
|
|
289
|
+
// canvas-only frame of a buried camera is exactly as blank.
|
|
290
|
+
return screenshotResult(base64, {
|
|
291
|
+
composite: false,
|
|
292
|
+
flatness: sampleFlatness(readable, canvas.ownerDocument),
|
|
293
|
+
loopRecoveryFrame,
|
|
294
|
+
});
|
|
295
|
+
} catch (err) {
|
|
296
|
+
// e.g. a tainted canvas (a cross-origin texture loaded with no CORS
|
|
297
|
+
// headers, which WebGL refuses to read back even with
|
|
298
|
+
// preserveDrawingBuffer:true) — a distinct, rarer failure from "no
|
|
299
|
+
// canvas at all" above, surfaced honestly rather than swallowed.
|
|
300
|
+
return {
|
|
301
|
+
ok: false,
|
|
302
|
+
error:
|
|
303
|
+
`bridge-screenshot: canvas layer capture failed — ${
|
|
304
|
+
err instanceof Error ? err.message : String(err)
|
|
305
|
+
}` +
|
|
306
|
+
(compositeFailure === null
|
|
307
|
+
? ''
|
|
308
|
+
: `; full game-stack capture also failed — ${
|
|
309
|
+
compositeFailure instanceof Error
|
|
310
|
+
? compositeFailure.message
|
|
311
|
+
: String(compositeFailure)
|
|
312
|
+
}`),
|
|
313
|
+
data: { code: 'BRIDGE_SCREENSHOT_UNAVAILABLE' },
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
}
|