@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,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `__vgaiIngest` / `__vgaiIngest2D` / `__vgaiIngestReact` dev hooks: the
|
|
3
|
+
* headless-readable evidence a live ingest mount publishes for automation,
|
|
4
|
+
* screenshots and `vgai doctor`.
|
|
5
|
+
*
|
|
6
|
+
* Everything here is EVIDENCE, not control: names read off the live adapter,
|
|
7
|
+
* counters read off the live gate, the coverage report re-derived on demand.
|
|
8
|
+
* Nothing is inferred from the route or the adapter's identity — a capability
|
|
9
|
+
* appears here because the mounted object actually has it.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { getActiveSystems } from '@volter/editor-core/authoring/active-systems';
|
|
13
|
+
import { loadIngestOwnership } from '../host/authoring/ingest-source-persistence';
|
|
14
|
+
import { gameLoopGate } from '../host/gated-globals';
|
|
15
|
+
import type { DomAuthoringAdapter } from '../react/dom-authoring-adapter';
|
|
16
|
+
import type { AuthoringAdapter } from '@volter/editor-project/adapter/authoring';
|
|
17
|
+
import type { IngestMount } from './authoring/ingest-root-adapter';
|
|
18
|
+
import { ingestCoverageReport } from './mount-coverage';
|
|
19
|
+
|
|
20
|
+
/** The window key each surface's mount publishes its evidence under. */
|
|
21
|
+
export type IngestHookKey = '__vgaiIngest' | '__vgaiIngest2D' | '__vgaiIngestReact';
|
|
22
|
+
|
|
23
|
+
const AUTHORING_PROVIDER_KEYS = [
|
|
24
|
+
'hierarchy',
|
|
25
|
+
'selection',
|
|
26
|
+
'transforms',
|
|
27
|
+
'inspector',
|
|
28
|
+
'assetSubject',
|
|
29
|
+
'structure',
|
|
30
|
+
'persistence',
|
|
31
|
+
'pickable',
|
|
32
|
+
'rects',
|
|
33
|
+
'boxEdit',
|
|
34
|
+
'text',
|
|
35
|
+
'colorSample',
|
|
36
|
+
'stories',
|
|
37
|
+
'truth',
|
|
38
|
+
'assetDrop',
|
|
39
|
+
] as const satisfies readonly (keyof AuthoringAdapter)[];
|
|
40
|
+
|
|
41
|
+
/** Names only providers that exist on the LIVE adapter. This is the shelf's
|
|
42
|
+
* authoring-family evidence; no capability is inferred from adapter identity. */
|
|
43
|
+
export function authoringProviderNames(adapter: AuthoringAdapter): string[] {
|
|
44
|
+
return AUTHORING_PROVIDER_KEYS.filter((name) => adapter[name] !== undefined);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** The shared evidence fields every surface-specific dev hook publishes. */
|
|
48
|
+
export function ingestHookEvidence(adapter: AuthoringAdapter): Record<string, unknown> {
|
|
49
|
+
return {
|
|
50
|
+
coverage: () => ingestCoverageReport(),
|
|
51
|
+
prepareCoverage: () => loadIngestOwnership(),
|
|
52
|
+
authoringProviders: () => authoringProviderNames(adapter),
|
|
53
|
+
// Registered-adapter names deliberately come from the live registry, not
|
|
54
|
+
// from the coverage report's game-contract row (WORK.md's explicit rule).
|
|
55
|
+
engineSystems: () => Object.keys(getActiveSystems()),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The loop fields the `__vgaiIngest` dev hook publishes — the measured verdict
|
|
61
|
+
* plus the gate's own counters, so automation can drive Play/Pause/Step through
|
|
62
|
+
* exactly the seam the editor's own buttons use rather than a parallel one.
|
|
63
|
+
*/
|
|
64
|
+
export function defineLoopHookFields(target: Record<string, unknown>, mount: IngestMount): void {
|
|
65
|
+
// `defineProperty`, NOT a spread: the verdict is itself live (the gate
|
|
66
|
+
// re-measures it at every hold) and spreading an accessor evaluates it
|
|
67
|
+
// once, which would freeze the claim at mount time and start lying the moment
|
|
68
|
+
// the game's shape changed.
|
|
69
|
+
Object.defineProperties(target, {
|
|
70
|
+
loop: {
|
|
71
|
+
enumerable: true,
|
|
72
|
+
get: () => mount.realmLoopVerdict?.()?.loop ?? null,
|
|
73
|
+
},
|
|
74
|
+
loopReason: {
|
|
75
|
+
enumerable: true,
|
|
76
|
+
get: () => mount.realmLoopVerdict?.()?.reason ?? null,
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
// S-5: the gate's own counters — what a pause actually held back. Before
|
|
80
|
+
// this there was NOTHING to read about whether a pause held anything, which
|
|
81
|
+
// is how "the gate reports success while gating nothing" survived. `null`
|
|
82
|
+
// when no gate is installed; never a fabricated number.
|
|
83
|
+
target['realmLoopStats'] = () => gameLoopGate()?.stats() ?? null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* D-L1 — the LIVE value behind
|
|
88
|
+
* `__vgaiIngestReact.domEvidence()`, shared by both react ingest routes.
|
|
89
|
+
* Evaluated at scrape time (a function result, never memoized),
|
|
90
|
+
* same "read the live state fresh on every call" discipline as
|
|
91
|
+
* `__vgaiSiblingMounts` (D-H1).
|
|
92
|
+
*
|
|
93
|
+
* `elementCount` prefers the DOM's real `querySelectorAll('*').length` — a
|
|
94
|
+
* real browser `HTMLElement` (what `reactRoot()` actually returns in
|
|
95
|
+
* production) always has it — and falls back to the adapter's own
|
|
96
|
+
* structural-walk size (`nodeCount()`) only when the mounted root doesn't
|
|
97
|
+
* expose `querySelectorAll` at all (this repo's own vitest-node adapter
|
|
98
|
+
* fixtures have no real DOM). Never a fabricated number either way: both
|
|
99
|
+
* paths count real, currently-present elements.
|
|
100
|
+
*
|
|
101
|
+
* `reactRootPresent` checks for a React 19 `createRoot` own-property mount
|
|
102
|
+
* stamp (`__reactContainer$<hash>`) on `root` itself — the SAME element
|
|
103
|
+
* `reactRoot()` returned, which is exactly the element `createRoot(...)`
|
|
104
|
+
* rendered into (`create-runtime.ts`'s `DomHostContext.container` doc
|
|
105
|
+
* comment: "the adapter's `mount` renders its react tree INTO this exact
|
|
106
|
+
* element via `createRoot`").
|
|
107
|
+
*/
|
|
108
|
+
export function reactDomEvidence(
|
|
109
|
+
root: HTMLElement,
|
|
110
|
+
adapter: DomAuthoringAdapter,
|
|
111
|
+
): { elementCount: number; hierarchyNodeCount: number; reactRootPresent: boolean } {
|
|
112
|
+
const withQuery = root as unknown as { querySelectorAll?: (sel: string) => ArrayLike<unknown> };
|
|
113
|
+
const elementCount =
|
|
114
|
+
typeof withQuery.querySelectorAll === 'function'
|
|
115
|
+
? withQuery.querySelectorAll('*').length
|
|
116
|
+
: adapter.nodeCount();
|
|
117
|
+
const reactRootPresent = Object.keys(root as object).some((k) =>
|
|
118
|
+
k.startsWith('__reactContainer'),
|
|
119
|
+
);
|
|
120
|
+
return { elementCount, hierarchyNodeCount: adapter.nodeCount(), reactRootPresent };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Publish a mount's evidence object. The ONE place these keys are written. */
|
|
124
|
+
export function publishIngestHook(key: IngestHookKey, hook: Record<string, unknown>): void {
|
|
125
|
+
(window as unknown as Record<string, unknown>)[key] = hook;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Drop a torn-down mount's evidence. The ONE place these keys are removed. */
|
|
129
|
+
export function clearIngestHook(key: IngestHookKey): void {
|
|
130
|
+
delete (window as unknown as Record<string, unknown>)[key];
|
|
131
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SAME-FRAME PIXELS for an ingested game's canvas — the reason `vgai
|
|
3
|
+
* screenshot` no longer returns black over a running ingest.
|
|
4
|
+
*
|
|
5
|
+
* ## The failure
|
|
6
|
+
*
|
|
7
|
+
* `composite-screenshot.ts` reads a game's canvas with `drawImage`, and that
|
|
8
|
+
* only works on a WebGL canvas whose context was created with
|
|
9
|
+
* `preserveDrawingBuffer: true`. Every canvas the vgai RUNTIME mounts sets it
|
|
10
|
+
* (`@volter/game-runtime/runtime/create-runtime`), which is why the composite leg has always
|
|
11
|
+
* worked for first-party play. An INGESTED game creates its own canvas: the
|
|
12
|
+
* racing-game's `<Canvas>` passes no `gl` prop, so fiber's default
|
|
13
|
+
* (`preserveDrawingBuffer: false`) applies, the browser discards the drawing
|
|
14
|
+
* buffer after compositing, and a `drawImage` from a later task — which is
|
|
15
|
+
* exactly what the composite leg does, after `waitForPaint`/
|
|
16
|
+
* `waitForFiniteMotion` — reads BLACK. Silently: the composite's near-black
|
|
17
|
+
* retry is gated on there being no canvas at all.
|
|
18
|
+
*
|
|
19
|
+
* ## Why this and not `preserveDrawingBuffer`
|
|
20
|
+
*
|
|
21
|
+
* The rejected alternative was to make the ingested canvas readable — a global
|
|
22
|
+
* `HTMLCanvasElement.prototype.getContext` patch forcing the flag. Three things
|
|
23
|
+
* are wrong with it and none are stylistic: the realm's lexical
|
|
24
|
+
* `window`/`document` shadow deliberately skips `node_modules`
|
|
25
|
+
* (`server/game-globals-shadow.ts`), and R3F creates the canvas inside
|
|
26
|
+
* `@react-three/fiber`, so a realm-scoped trap cannot reach it at all; a global
|
|
27
|
+
* one sits in front of the EDITOR's own canvases too and needs a host-vs-game
|
|
28
|
+
* identity filter, which is the exact timing-sensitive problem
|
|
29
|
+
* `SceneCaptureOptions.isHostRenderer` was painfully introduced to solve; and
|
|
30
|
+
* it makes every ingested game pay a permanent per-frame cost so a screenshot
|
|
31
|
+
* can occasionally be taken.
|
|
32
|
+
*
|
|
33
|
+
* The drawing buffer is not "unreadable", it is unreadable LATE. Read inside
|
|
34
|
+
* the game's own render call and the pixels are there, no flag required. The
|
|
35
|
+
* host already has that bracket and did not have to invent it: the capture trap
|
|
36
|
+
* stands between an ingested game and its own `WebGLRenderer.render`
|
|
37
|
+
* (`@volter/threejs-runtime/adapter/ingest/scene-capture`), and its `RenderPassHooks.after()`
|
|
38
|
+
* runs synchronously inside that call — the same bracket `renderDebug` uses.
|
|
39
|
+
* So this is arm-and-deliver on an existing seam, in the shape
|
|
40
|
+
* `dev/render-debug-adapter.ts`'s `captureFrame()` already established: zero
|
|
41
|
+
* cost until armed, and it works regardless of the game's GL flags (a vanilla
|
|
42
|
+
* three ingest, or any future game that opts out, is covered for free).
|
|
43
|
+
*
|
|
44
|
+
* ## RESOURCE OWNERSHIP
|
|
45
|
+
*
|
|
46
|
+
* OWNER: the live three ingest mount. `wireIngestSystems`
|
|
47
|
+
* (`ingest-render-debug.ts`) is the one installer — it owns the single
|
|
48
|
+
* `setRenderPassHooks` slot and multiplexes it — and
|
|
49
|
+
* `ingest-root-adapter.ts`'s `dispose()` is the ONE teardown path, through
|
|
50
|
+
* {@link clearIngestFrameSource}. SHARERS: none; at most one ingest session is
|
|
51
|
+
* mounted at a time (`mount-ingest-root.ts` fails a second by name), which is
|
|
52
|
+
* why the armed state below is module-scoped rather than per-mount.
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
/** What the module needs from the captured runtime to take a snapshot. */
|
|
56
|
+
export interface IngestFrameSource {
|
|
57
|
+
readonly renderer: {
|
|
58
|
+
readonly domElement?: unknown;
|
|
59
|
+
getRenderTarget?(): unknown;
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** The live mount's source, or `null` between mounts. */
|
|
64
|
+
let source: IngestFrameSource | null = null;
|
|
65
|
+
|
|
66
|
+
interface ArmedSnapshot {
|
|
67
|
+
readonly settle: (image: HTMLCanvasElement | null) => void;
|
|
68
|
+
readonly timer: ReturnType<typeof setTimeout>;
|
|
69
|
+
/** The most recent copy taken this frame; see {@link deliverArmedIngestFrame}
|
|
70
|
+
* for why the LAST one is the answer and not the first. */
|
|
71
|
+
image: HTMLCanvasElement | null;
|
|
72
|
+
delivering: boolean;
|
|
73
|
+
}
|
|
74
|
+
let armed: ArmedSnapshot | null = null;
|
|
75
|
+
|
|
76
|
+
/** Install the live mount's runtime. Called by the one installer. */
|
|
77
|
+
export function setIngestFrameSource(next: IngestFrameSource | null): void {
|
|
78
|
+
source = next;
|
|
79
|
+
if (!next) clearIngestFrameSnapshot();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Teardown: drop the source AND settle any armed wait, so a screenshot in
|
|
83
|
+
* flight when the mount goes down resolves (as "no frame") instead of hanging
|
|
84
|
+
* until its timeout. */
|
|
85
|
+
export function clearIngestFrameSource(): void {
|
|
86
|
+
source = null;
|
|
87
|
+
clearIngestFrameSnapshot();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function clearIngestFrameSnapshot(): void {
|
|
91
|
+
const pending = armed;
|
|
92
|
+
armed = null;
|
|
93
|
+
if (pending) {
|
|
94
|
+
clearTimeout(pending.timer);
|
|
95
|
+
pending.settle(null);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* The game's own canvas, or `null` when there is no live ingest runtime.
|
|
101
|
+
*
|
|
102
|
+
* Deliberately NOT an `instanceof HTMLCanvasElement` test: this module is
|
|
103
|
+
* imported by the command relay, which runs headless in Node for the relay
|
|
104
|
+
* suites — there `HTMLCanvasElement` is not even defined, and the reference
|
|
105
|
+
* alone would throw on every first-party screenshot. The only question a
|
|
106
|
+
* caller asks is identity ("is the canvas I found the ingest mount's?"), which
|
|
107
|
+
* needs no class.
|
|
108
|
+
*/
|
|
109
|
+
export function ingestFrameCanvas(): HTMLCanvasElement | null {
|
|
110
|
+
const element = source?.renderer.domElement;
|
|
111
|
+
return typeof element === 'object' && element !== null ? (element as HTMLCanvasElement) : null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Called from inside the game's render pass (the capture trap's `after`).
|
|
116
|
+
* Copies the live drawing buffer when a snapshot is armed AND this render went
|
|
117
|
+
* to the canvas rather than to an offscreen target — a game that bakes a
|
|
118
|
+
* render target mid-frame (racing-game's minimap does) must not have that pass
|
|
119
|
+
* mistaken for its picture.
|
|
120
|
+
*
|
|
121
|
+
* THE LAST CANVAS PASS OF THE FRAME IS THE PICTURE, not the first, and that is
|
|
122
|
+
* a MEASURED correction rather than a precaution. A frame is often several
|
|
123
|
+
* canvas-targeted `render()` calls: racing-game's minimap draws the world,
|
|
124
|
+
* then `clearDepth()`, then the map overlay — all to the canvas — and drei's
|
|
125
|
+
* `<Hud>` has the identical shape. Settling on the first bracket produced a
|
|
126
|
+
* capture with the minimap MISSING, i.e. a screenshot that silently omitted a
|
|
127
|
+
* HUD layer the human can see. So each qualifying pass overwrites the copy and
|
|
128
|
+
* the promise settles one macrotask later, once the frame's JS has finished
|
|
129
|
+
* and no further pass can arrive.
|
|
130
|
+
*
|
|
131
|
+
* Never throws: it runs inside the game's own frame.
|
|
132
|
+
*/
|
|
133
|
+
export function deliverArmedIngestFrame(): void {
|
|
134
|
+
const pending = armed;
|
|
135
|
+
if (!pending || !source) return;
|
|
136
|
+
try {
|
|
137
|
+
const renderer = source.renderer;
|
|
138
|
+
if (typeof renderer.getRenderTarget === 'function' && renderer.getRenderTarget() !== null) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const canvas = ingestFrameCanvas();
|
|
142
|
+
if (!canvas || canvas.width === 0 || canvas.height === 0) return;
|
|
143
|
+
const copy = pending.image ?? canvas.ownerDocument.createElement('canvas');
|
|
144
|
+
copy.width = canvas.width;
|
|
145
|
+
copy.height = canvas.height;
|
|
146
|
+
const ctx = copy.getContext('2d');
|
|
147
|
+
if (!ctx) return;
|
|
148
|
+
ctx.drawImage(canvas, 0, 0);
|
|
149
|
+
pending.image = copy;
|
|
150
|
+
if (pending.delivering) return;
|
|
151
|
+
pending.delivering = true;
|
|
152
|
+
setTimeout(() => {
|
|
153
|
+
if (armed !== pending) return;
|
|
154
|
+
armed = null;
|
|
155
|
+
clearTimeout(pending.timer);
|
|
156
|
+
pending.settle(pending.image);
|
|
157
|
+
}, 0);
|
|
158
|
+
} catch {
|
|
159
|
+
// A snapshot that cannot be taken is a capture that degrades to the
|
|
160
|
+
// ordinary canvas read; it is never the game's frame breaking.
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Ask for the next frame the ingested game draws to its canvas, as a readable
|
|
166
|
+
* 2D canvas. Resolves `null` when nothing is mounted, when the game does not
|
|
167
|
+
* render inside `timeoutMs` (a paused or hidden game), or when the mount goes
|
|
168
|
+
* down first — every one of which means "capture it the ordinary way".
|
|
169
|
+
*
|
|
170
|
+
* Only one wait at a time; a second arm supersedes the first, which settles
|
|
171
|
+
* `null`.
|
|
172
|
+
*/
|
|
173
|
+
export function armIngestFrameSnapshot(timeoutMs = 2000): Promise<HTMLCanvasElement | null> {
|
|
174
|
+
if (!source) return Promise.resolve(null);
|
|
175
|
+
clearIngestFrameSnapshot();
|
|
176
|
+
return new Promise<HTMLCanvasElement | null>((resolve) => {
|
|
177
|
+
const timer = setTimeout(() => {
|
|
178
|
+
if (armed?.timer === timer) armed = null;
|
|
179
|
+
resolve(null);
|
|
180
|
+
}, timeoutMs);
|
|
181
|
+
armed = { settle: resolve, timer, image: null, delivering: false };
|
|
182
|
+
});
|
|
183
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Relay play-control dispatch for an INGEST session — the SimCity ledger's S-2
|
|
3
|
+
* ("`vgai play` destroys a working source-mount").
|
|
4
|
+
*
|
|
5
|
+
* ## The defect
|
|
6
|
+
*
|
|
7
|
+
* `command-listener.ts`'s `play`/`stop`/`pause`/`resume`/`step` cases called
|
|
8
|
+
* straight into `play-mode.ts`, which is the FIRST-PARTY runtime boot. That
|
|
9
|
+
* boot mounts the manifest's roots into `getGameContainer()` — the same element
|
|
10
|
+
* an ingest mount already occupies — and a captured ingest is not a
|
|
11
|
+
* host-mounted `RootAdapter`. Play
|
|
12
|
+
* therefore always failed on an ingest project and ran `exitPlayMode()` in its
|
|
13
|
+
* own catch, which unconditionally reset the systems slot, popped the adopted
|
|
14
|
+
* scene, restored the pre-ingest authoring adapter, cleared the game input gate
|
|
15
|
+
* and set `playState: 'stopped'` — tearing the live mount's editor-side
|
|
16
|
+
* ownership out from under it, with no remount.
|
|
17
|
+
*
|
|
18
|
+
* The editor's own Play button never had this problem: `components/PlayBar.tsx`
|
|
19
|
+
* early-returns an entirely separate ingest toolbar wired to
|
|
20
|
+
* `getIngestPlayControl()`. The relay was the one play surface that did not
|
|
21
|
+
* branch, which is exactly why the defect only ever showed up through the CLI.
|
|
22
|
+
*
|
|
23
|
+
* ## Why a separate module
|
|
24
|
+
*
|
|
25
|
+
* So the dispatch is a pure function over the control surface, testable with a
|
|
26
|
+
* stub and no editor/browser at all (`packages/editor/test/ingest-play-commands.test.ts`).
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import {
|
|
30
|
+
type CommandResult,
|
|
31
|
+
type IngestPlayCommandType,
|
|
32
|
+
isIngestPlayCommandType,
|
|
33
|
+
} from '@volter/editor-sdk/session/command-table';
|
|
34
|
+
|
|
35
|
+
/** The shape {@link handleIngestPlayControl} needs — structurally satisfied by
|
|
36
|
+
* `ingest/ingest-play-control.ts`'s `IngestPlayControl`, named locally so this module imports
|
|
37
|
+
* no session state. */
|
|
38
|
+
export interface IngestPlaySurface {
|
|
39
|
+
readonly playing: boolean;
|
|
40
|
+
play(): void;
|
|
41
|
+
pause(): void;
|
|
42
|
+
resume(): void;
|
|
43
|
+
readonly canStep: boolean;
|
|
44
|
+
step(): void;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Dispatch one relay play-control command to the live ingest session, or return
|
|
49
|
+
* `null` when this command is not a play control (the caller then runs its
|
|
50
|
+
* ordinary switch).
|
|
51
|
+
*
|
|
52
|
+
* `stop` maps to PAUSE by default, deliberately. An ingest session's mount is
|
|
53
|
+
* the editor's open document, not a play session — there is no "stop" that
|
|
54
|
+
* could unmount it and leave the editor in a sensible state, and destroying the
|
|
55
|
+
* mount is the very defect this module exists to fix. Halting the game's loop
|
|
56
|
+
* is what "stop the game" can honestly mean here, and it is what the editor's
|
|
57
|
+
* own ingest toolbar offers.
|
|
58
|
+
*
|
|
59
|
+
* `stopEndsRun` inverts exactly that premise, and nothing else. A root that
|
|
60
|
+
* declares its own world component mounts the GAME only when Play starts
|
|
61
|
+
* (`deferred-ingest-play.ts`), so its mount is a play run rather than the open
|
|
62
|
+
* document: ⏹ genuinely ends it and the editor returns to the design-time
|
|
63
|
+
* Scene. This dispatch then declines `stop` (returns `null`) so the caller's
|
|
64
|
+
* ordinary `exitPlayMode()` runs. ▶/⏸/step still belong here either way —
|
|
65
|
+
* they drive the ingested game's OWN loop, which no first-party session owns.
|
|
66
|
+
*
|
|
67
|
+
* `step` REFUSES when the mount cannot honestly step (`canStep` is false
|
|
68
|
+
* whenever no serve-time loop gate measured control over this game's frames) —
|
|
69
|
+
* a silent no-op would be a fabricated ack.
|
|
70
|
+
*/
|
|
71
|
+
/**
|
|
72
|
+
* One handler per claimed verb, keyed by the command table's own play-control
|
|
73
|
+
* subset. A `Record` rather than a switch on purpose: adding a play verb to
|
|
74
|
+
* `command-table.ts` makes this object stop compiling until it has a handler,
|
|
75
|
+
* which a switch would not, and the vocabulary is then read from ONE place
|
|
76
|
+
* instead of being spelled again here.
|
|
77
|
+
*
|
|
78
|
+
* `null` means "not mine" — the caller runs its ordinary dispatch.
|
|
79
|
+
*/
|
|
80
|
+
type IngestPlayOutcome =
|
|
81
|
+
/** Driven; the caller gets the standard `{ ingest: true, playing }` ack. */
|
|
82
|
+
| 'driven'
|
|
83
|
+
/** Not this lane's after all; the caller runs its ordinary dispatch. */
|
|
84
|
+
| 'declined'
|
|
85
|
+
/** A refusal with its own reason. */
|
|
86
|
+
| CommandResult;
|
|
87
|
+
|
|
88
|
+
const INGEST_PLAY_HANDLERS: Record<
|
|
89
|
+
IngestPlayCommandType,
|
|
90
|
+
(ingest: IngestPlaySurface, opts?: { readonly stopEndsRun?: boolean }) => IngestPlayOutcome
|
|
91
|
+
> = {
|
|
92
|
+
play: (ingest) => {
|
|
93
|
+
ingest.play();
|
|
94
|
+
return 'driven';
|
|
95
|
+
},
|
|
96
|
+
resume: (ingest) => {
|
|
97
|
+
ingest.resume();
|
|
98
|
+
return 'driven';
|
|
99
|
+
},
|
|
100
|
+
stop: (ingest, opts) => {
|
|
101
|
+
if (opts?.stopEndsRun) return 'declined';
|
|
102
|
+
ingest.pause();
|
|
103
|
+
return 'driven';
|
|
104
|
+
},
|
|
105
|
+
pause: (ingest) => {
|
|
106
|
+
ingest.pause();
|
|
107
|
+
return 'driven';
|
|
108
|
+
},
|
|
109
|
+
step: (ingest) => {
|
|
110
|
+
if (!ingest.canStep) {
|
|
111
|
+
return {
|
|
112
|
+
ok: false,
|
|
113
|
+
error:
|
|
114
|
+
'this ingested game cannot be stepped — no serve-time loop gate measured control ' +
|
|
115
|
+
'over its frames, so a single-frame step would be a button that lies (see ' +
|
|
116
|
+
'ingest/same-realm-loop-gate.ts)',
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
ingest.step();
|
|
120
|
+
return 'driven';
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export function handleIngestPlayControl(
|
|
125
|
+
ingest: IngestPlaySurface,
|
|
126
|
+
type: string,
|
|
127
|
+
opts?: { readonly stopEndsRun?: boolean },
|
|
128
|
+
): CommandResult | null {
|
|
129
|
+
if (!isIngestPlayCommandType(type)) return null;
|
|
130
|
+
const outcome = INGEST_PLAY_HANDLERS[type](ingest, opts);
|
|
131
|
+
if (outcome === 'declined') return null;
|
|
132
|
+
if (outcome !== 'driven') return outcome;
|
|
133
|
+
return { ok: true, data: { ingest: true, playing: ingest.playing } };
|
|
134
|
+
}
|