@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,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The play-state control surface over whatever `{ ingest }` root is live —
|
|
3
|
+
* the one seam `components/PlayBar.tsx` and the CLI relay
|
|
4
|
+
* (`ingest/ingest-play-commands.ts`) drive ▶/⏸/Step through.
|
|
5
|
+
*
|
|
6
|
+
* Reads the single session slot (`active-ingest.ts`) and nothing else; the
|
|
7
|
+
* mount paths call {@link setIngestPlaying} and the teardown path calls
|
|
8
|
+
* {@link resetIngestPlaySurface}.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { gameContractEpoch } from '../host/coverage/game-contract-seam-evidence';
|
|
12
|
+
import { recordLiveSeamEvidence } from '@volter/editor-core/coverage/live-seam-evidence';
|
|
13
|
+
import { editorConsole } from '@volter/editor-core/editor-console';
|
|
14
|
+
import { activateLiveDocument } from '@volter/editor-core/live-document';
|
|
15
|
+
import type { VgaiGameContract } from '@volter/editor-project/adapter/ingest/game-contract';
|
|
16
|
+
import { activeIngest, type IngestLifecycleControl } from './active-ingest';
|
|
17
|
+
import { readIngestGameContract } from './game-contract-realm';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The play-state control surface for the CURRENT ingest session (F17/F21):
|
|
21
|
+
* mounts are COLD by doctrine — the adapter sets `window.__vgaiMountCold`
|
|
22
|
+
* before the game's entry executes, and a session-driven game may defer its
|
|
23
|
+
* side-effects (backend connection, narrative, audio) until the first
|
|
24
|
+
* `play()`. Everything is contract-first (game-contract.ts): `lifecycle.start`
|
|
25
|
+
* is called AT MOST ONCE per mount — later ▶ presses resume rather than
|
|
26
|
+
* re-start, so games never need an idempotent start;
|
|
27
|
+
* Mounting normalizes `lifecycle.pause`/`resume` or the substrate's real
|
|
28
|
+
* `setPaused` fallback into ONE {@link IngestLifecycleControl}. This module's
|
|
29
|
+
* runtime consumers never choose between raw mechanisms, and a malformed
|
|
30
|
+
* half-pair never borrows its missing half from the fallback. Games that
|
|
31
|
+
* declare no `start` still get the pre-contract wire event.
|
|
32
|
+
*/
|
|
33
|
+
export interface IngestPlayControl {
|
|
34
|
+
readonly playing: boolean;
|
|
35
|
+
play(): void;
|
|
36
|
+
pause(): void;
|
|
37
|
+
resume(): void;
|
|
38
|
+
/**
|
|
39
|
+
* True only when the loop gate actually holds this game's
|
|
40
|
+
* frames (`ingest/same-realm-loop-gate.ts`), which is the only condition
|
|
41
|
+
* under which a single-frame step is a real thing rather than a button that
|
|
42
|
+
* lies.
|
|
43
|
+
*/
|
|
44
|
+
readonly canStep: boolean;
|
|
45
|
+
/** Advance exactly one frame batch and hold again. No-op when `canStep` is false. */
|
|
46
|
+
step(): void;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let _ingestPlaying = false;
|
|
50
|
+
|
|
51
|
+
/** Whether the live session's loop was last told to run (the status facet's
|
|
52
|
+
* `playing`). */
|
|
53
|
+
export function ingestPlaying(): boolean {
|
|
54
|
+
return _ingestPlaying;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* THE one write to the ingest play state.
|
|
59
|
+
*
|
|
60
|
+
* An ingested game's input is gated by the same mechanisms a first-party play
|
|
61
|
+
* session's is (the engine's `InputManager.setEnabled` and `gated-globals.ts`'s
|
|
62
|
+
* lexical window/document shadow), driven by the input gate the mount installs.
|
|
63
|
+
*/
|
|
64
|
+
export function setIngestPlaying(playing: boolean): void {
|
|
65
|
+
_ingestPlaying = playing;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* F26: the play surface's state is session-global (one active session,
|
|
70
|
+
* whatever its kind), so EVERY per-kind exit path resets it — the playing
|
|
71
|
+
* flag plus the cold-mount/played window globals the entry paths set.
|
|
72
|
+
*/
|
|
73
|
+
export function resetIngestPlaySurface(): void {
|
|
74
|
+
setIngestPlaying(false);
|
|
75
|
+
const w = window as unknown as { __vgaiMountCold?: boolean; __vgaiIngestPlayed?: boolean };
|
|
76
|
+
delete w.__vgaiMountCold;
|
|
77
|
+
delete w.__vgaiIngestPlayed;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* F26 helper: the active session's kind + its own honest pause capability
|
|
82
|
+
* (D-V1: dispatches on the single slot, never a per-kind global).
|
|
83
|
+
* `null` when nothing is ingested.
|
|
84
|
+
*/
|
|
85
|
+
export interface IngestLifecycleFallback {
|
|
86
|
+
readonly setPaused?: ((paused: boolean) => void) | undefined;
|
|
87
|
+
readonly pauseGap?: string | undefined;
|
|
88
|
+
readonly step?: (() => void) | undefined;
|
|
89
|
+
readonly canStep?: (() => boolean) | undefined;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function runContractLifecycleEffect(
|
|
93
|
+
contract: VgaiGameContract,
|
|
94
|
+
member: 'start' | 'pause' | 'resume',
|
|
95
|
+
run: () => void,
|
|
96
|
+
): void {
|
|
97
|
+
const receipt = (outcome: 'pass' | 'fail', detail: string): void =>
|
|
98
|
+
recordLiveSeamEvidence({
|
|
99
|
+
seam: `contract.lifecycle.${member}`,
|
|
100
|
+
subject: 'game',
|
|
101
|
+
epoch: gameContractEpoch(contract),
|
|
102
|
+
stage: 'effect',
|
|
103
|
+
outcome,
|
|
104
|
+
source: 'consumer',
|
|
105
|
+
detail,
|
|
106
|
+
});
|
|
107
|
+
try {
|
|
108
|
+
run();
|
|
109
|
+
receipt('pass', `the mounted ingest lifecycle consumed lifecycle.${member}`);
|
|
110
|
+
} catch (error) {
|
|
111
|
+
receipt(
|
|
112
|
+
'fail',
|
|
113
|
+
`lifecycle.${member} failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
114
|
+
);
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Normalize raw lifecycle declarations and a substrate fallback ONCE, at
|
|
121
|
+
* mount. The declared pause/resume pair wins as one indivisible capability;
|
|
122
|
+
* a half-pair is malformed and may never silently mix with the fallback.
|
|
123
|
+
* Step belongs only to the fallback that actually took the hold.
|
|
124
|
+
*/
|
|
125
|
+
export function bindIngestLifecycle(
|
|
126
|
+
fallback: IngestLifecycleFallback = {},
|
|
127
|
+
): IngestLifecycleControl {
|
|
128
|
+
const contract = readIngestGameContract();
|
|
129
|
+
const declared = contract?.lifecycle;
|
|
130
|
+
const start = () => {
|
|
131
|
+
if (contract && declared?.start) {
|
|
132
|
+
runContractLifecycleEffect(contract, 'start', () => declared.start?.());
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
window.dispatchEvent(new CustomEvent('vgai:ingest-play'));
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const hasPause = typeof declared?.pause === 'function';
|
|
139
|
+
const hasResume = typeof declared?.resume === 'function';
|
|
140
|
+
if (contract && hasPause !== hasResume) {
|
|
141
|
+
return {
|
|
142
|
+
start,
|
|
143
|
+
pauseGap: hasPause
|
|
144
|
+
? 'the game contract declares lifecycle.pause without lifecycle.resume'
|
|
145
|
+
: 'the game contract declares lifecycle.resume without lifecycle.pause',
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
if (contract && hasPause && hasResume) {
|
|
149
|
+
return {
|
|
150
|
+
start,
|
|
151
|
+
setPaused: (paused) =>
|
|
152
|
+
runContractLifecycleEffect(contract, paused ? 'pause' : 'resume', () => {
|
|
153
|
+
if (paused) declared?.pause?.();
|
|
154
|
+
else declared?.resume?.();
|
|
155
|
+
}),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
if (fallback.setPaused) {
|
|
159
|
+
return {
|
|
160
|
+
start,
|
|
161
|
+
setPaused: fallback.setPaused,
|
|
162
|
+
...(fallback.step ? { step: fallback.step } : {}),
|
|
163
|
+
...(fallback.canStep ? { canStep: fallback.canStep } : {}),
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
start,
|
|
168
|
+
pauseGap:
|
|
169
|
+
fallback.pauseGap ??
|
|
170
|
+
'the game declares no lifecycle.pause/resume pair and its mounted adapter exposes no setPaused capability',
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function activeIngestPauseGap(): string | null {
|
|
175
|
+
const active = activeIngest();
|
|
176
|
+
if (!active) return null;
|
|
177
|
+
return active.session.lifecycle.pauseGap ?? null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* D-B2 (Slice B): fan a pause/resume state out to
|
|
182
|
+
* every mounted composite sibling's OPTIONAL `SiblingMount.setPaused`
|
|
183
|
+
* (`ingest-siblings.ts`), AFTER the primary surface's own pause/resume
|
|
184
|
+
* already ran — mirrors `exitActiveIngest`'s reverse-order dispose
|
|
185
|
+
* fan-out (the same "one bad sibling never breaks the rest"
|
|
186
|
+
* isolation: per-sibling try/catch, loud log on failure, never swallowed
|
|
187
|
+
* silently). A sibling with no `setPaused` at all (the bare `default-react`
|
|
188
|
+
* sibling, D-B1 — it has no loop to gate) is silently skipped via the
|
|
189
|
+
* optional call — never an error, never a fabricated pause. A no-op (empty
|
|
190
|
+
* `siblings`, or no live session at all) for every non-composite entry
|
|
191
|
+
* path, unchanged.
|
|
192
|
+
*/
|
|
193
|
+
function fanOutSiblingPause(paused: boolean): void {
|
|
194
|
+
const active = activeIngest();
|
|
195
|
+
if (!active) return;
|
|
196
|
+
for (const sibling of active.siblings) {
|
|
197
|
+
try {
|
|
198
|
+
sibling.setPaused?.(paused);
|
|
199
|
+
} catch (err) {
|
|
200
|
+
editorConsole.error(`Composite sibling setPaused(${paused}) failed: ${err}`, 'ingest');
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* The DECLARED contract for whatever is ingested right now.
|
|
207
|
+
*
|
|
208
|
+
* The game runs in the editor's own realm and declares on the host window —
|
|
209
|
+
* either from its own entry, or from the host-added module the manifest names
|
|
210
|
+
* in `ingest.contractShim`, which the mount imports first.
|
|
211
|
+
*/
|
|
212
|
+
export function activeIngestContract(): VgaiGameContract | null {
|
|
213
|
+
return readIngestGameContract();
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function getIngestPlayControl(): IngestPlayControl | null {
|
|
217
|
+
const lifecycle = activeIngest()?.session.lifecycle;
|
|
218
|
+
if (!lifecycle?.setPaused) return null;
|
|
219
|
+
/** HOLD content time through the mount-normalized lifecycle capability. */
|
|
220
|
+
const hold = () => {
|
|
221
|
+
lifecycle.setPaused?.(true);
|
|
222
|
+
fanOutSiblingPause(true);
|
|
223
|
+
setIngestPlaying(false);
|
|
224
|
+
};
|
|
225
|
+
/** RELEASE through the exact same normalized capability that took the hold. */
|
|
226
|
+
const release = () => {
|
|
227
|
+
lifecycle.setPaused?.(false);
|
|
228
|
+
fanOutSiblingPause(false);
|
|
229
|
+
setIngestPlaying(true);
|
|
230
|
+
};
|
|
231
|
+
return {
|
|
232
|
+
get playing() {
|
|
233
|
+
return _ingestPlaying;
|
|
234
|
+
},
|
|
235
|
+
play() {
|
|
236
|
+
// ▶ BRINGS THE GAME DOCUMENT TO THE FRONT. A boot-mounted ingest lands
|
|
237
|
+
// in Edit on purpose (`ingest-boot-viewport.ts`), so without this the
|
|
238
|
+
// one gesture that starts the game left the person on the authoring
|
|
239
|
+
// Scene — for a root whose content exists only under its own loop, the
|
|
240
|
+
// "No renderable content" card, with the game running unseen behind it
|
|
241
|
+
// (walk 3, beat 19: `playState: "playing"`, `activeViewportTab: "edit"`,
|
|
242
|
+
// one canvas in the page). `live-document.ts` owns that activation, and
|
|
243
|
+
// going through it is also what writes the play tab the input gate
|
|
244
|
+
// reads — so the game is drivable and not merely visible. A play-time
|
|
245
|
+
// mount already did this for itself (`deferred-ingest-play.ts`).
|
|
246
|
+
activateLiveDocument();
|
|
247
|
+
// Late-constructed game clients must not re-defer: the flag records
|
|
248
|
+
// that play already happened this session — and makes ▶-after-⏸ a
|
|
249
|
+
// RESUME, so `lifecycle.start` fires at most once per mount.
|
|
250
|
+
const w = window as unknown as { __vgaiIngestPlayed?: boolean };
|
|
251
|
+
if (w.__vgaiIngestPlayed) return release();
|
|
252
|
+
w.__vgaiIngestPlayed = true;
|
|
253
|
+
lifecycle.start();
|
|
254
|
+
// A mount ends HELD (see `ingestContentTimeForMode`), so the first ▶ is
|
|
255
|
+
// ALSO the release of that hold — same path every later ▶ takes.
|
|
256
|
+
release();
|
|
257
|
+
},
|
|
258
|
+
pause: hold,
|
|
259
|
+
resume: release,
|
|
260
|
+
canStep: !!lifecycle.step && (lifecycle.canStep?.() ?? true),
|
|
261
|
+
step() {
|
|
262
|
+
if (lifecycle.canStep?.() === false) return;
|
|
263
|
+
lifecycle.step?.();
|
|
264
|
+
setIngestPlaying(false);
|
|
265
|
+
},
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* THE hold/release decision for an ingested game's content time — pure, so the
|
|
271
|
+
* rule is checkable without a browser, a mount or a clock.
|
|
272
|
+
*
|
|
273
|
+
* The rule is the ontology's, not this lane's: LOADING CONSTRUCTS, PLAY RUNS
|
|
274
|
+
* (ARCHITECTURE-CORE). Content time follows the editor's MODE and nothing else
|
|
275
|
+
* — not whether a mount just finished, not how far a game got into its own
|
|
276
|
+
* boot. A mount is simply the first moment this decision is evaluated, which is
|
|
277
|
+
* why "mount in Edit" needs no case of its own.
|
|
278
|
+
*/
|
|
279
|
+
export function ingestContentTimeForMode(mode: 'edit' | 'play'): 'held' | 'released' {
|
|
280
|
+
return mode === 'edit' ? 'held' : 'released';
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Apply {@link ingestContentTimeForMode} to the live session, through the play
|
|
285
|
+
* control's OWN pause path — so a game that declares `lifecycle.pause` is held
|
|
286
|
+
* by its own hand rather than by an outside-in loop gate that may not reach it.
|
|
287
|
+
*
|
|
288
|
+
* Only the HOLD half acts here, because only the hold half has a caller: ▶
|
|
289
|
+
* (`play`/`resume` above) is the one thing that releases, and it is a user
|
|
290
|
+
* action, never something a mode read may fabricate. Returns whether it held,
|
|
291
|
+
* so a caller can log honestly rather than assume.
|
|
292
|
+
*/
|
|
293
|
+
export function holdIngestContentTimeForMode(mode: 'edit' | 'play'): boolean {
|
|
294
|
+
if (ingestContentTimeForMode(mode) !== 'held') return false;
|
|
295
|
+
const control = getIngestPlayControl();
|
|
296
|
+
if (!control) return false;
|
|
297
|
+
control.pause();
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `renderDebug` for an INGESTED world — the engine-owned auto-registration
|
|
3
|
+
* the first-party mount already does (`setup-three-root-adapter.ts` builds the
|
|
4
|
+
* same wiring under the same real-WebGL2-context guard), reached for a game
|
|
5
|
+
* whose render loop the host does not own.
|
|
6
|
+
*
|
|
7
|
+
* ## Why this is host-owned and not per-game
|
|
8
|
+
*
|
|
9
|
+
* `RenderDebugAdapter` needs three things: a real WebGL2 context, the live scene
|
|
10
|
+
* to attribute draws against, and a bracket around the render pass. A captured
|
|
11
|
+
* ingest mount has all three and the host already holds them — the capture trap
|
|
12
|
+
* (`@volter/threejs-runtime/adapter/ingest/scene-capture`) stands between the game and its own
|
|
13
|
+
* `WebGLRenderer.render`, which is the ONLY place a host can see a
|
|
14
|
+
* self-driven game's pass begin and end. So no game declares this slot: it is
|
|
15
|
+
* derived from the mount, exactly like the first-party path, and a game that
|
|
16
|
+
* declared one would be shimming what the host can measure.
|
|
17
|
+
*
|
|
18
|
+
* ## Why the game's own `three` instance is not a problem here
|
|
19
|
+
*
|
|
20
|
+
* `dev/render-debug-adapter.ts` never uses `instanceof`: it duck-types
|
|
21
|
+
* renderables (`isMesh`/`isPoints`/…) and patches `onBeforeRender` on the
|
|
22
|
+
* objects themselves. `dev/render-memory.ts` is the same shape
|
|
23
|
+
* (`isTexture`/`isBufferGeometry` flags, numeric format enums), so neither
|
|
24
|
+
* depends on the captured objects being the host's own classes.
|
|
25
|
+
*
|
|
26
|
+
* Returns `null` — the honest absence, which leaves the Frame debugger showing
|
|
27
|
+
* its own "no adapter" notice — whenever the mount has no real WebGL2 context to
|
|
28
|
+
* capture from (a headless harness, a stub renderer).
|
|
29
|
+
*
|
|
30
|
+
* ## The CANVAS lane, and what it can and cannot say
|
|
31
|
+
*
|
|
32
|
+
* The same three things are needed on the Pixi surface, and the mount holds two
|
|
33
|
+
* and a half of them. `webgl-frame-capture.ts` is GL-LEVEL — it shadows the
|
|
34
|
+
* five WebGL2 draw entry points on whatever real context it is handed and knows
|
|
35
|
+
* nothing about three — so it is reused verbatim over the captured Pixi
|
|
36
|
+
* renderer's own `gl`. The bracket is the renderer's own published seam, its
|
|
37
|
+
* `prerender`/`postrender` runners (`@volter/game-runtime/pixi/render-pass-bracket`).
|
|
38
|
+
*
|
|
39
|
+
* What is NOT there is per-draw attribution, and it is absent for a structural
|
|
40
|
+
* reason rather than a missing patch point: three calls `onBeforeRender` on the
|
|
41
|
+
* object it is about to draw, whereas Pixi's batcher merges many display
|
|
42
|
+
* objects into a single draw, so no display object IS the author of a given
|
|
43
|
+
* draw. `createCanvasIngestRenderDebug` therefore omits `scene` and every draw
|
|
44
|
+
* lands in `totals.unattributed` — the Frame debugger reads that count and says
|
|
45
|
+
* so, which is the honest answer and not a degraded one.
|
|
46
|
+
*
|
|
47
|
+
* Render memory is absent on this lane for the same kind of reason.
|
|
48
|
+
* `RenderMemorySnapshot` is a WHOLE-snapshot shape with non-nullable geometry
|
|
49
|
+
* counts and byte totals; Pixi's renderer publishes one real managed collection
|
|
50
|
+
* (`renderer.texture.managedTextures`) and no managed geometry or program
|
|
51
|
+
* collection at all, so filling the geometry half with zeros is exactly the
|
|
52
|
+
* fabrication the anti-shim rule forbids. The capability is therefore ABSENT
|
|
53
|
+
* (`memorySnapshot` undefined), which `frame-debugger-model.ts` already reports
|
|
54
|
+
* per-capability and the Profiler's memory view already renders as its own
|
|
55
|
+
* notice.
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
import { adapterObservations } from '@volter/editor-core/adapter-observation';
|
|
59
|
+
import { createContractDebugAdapter } from '@volter/game-runtime/adapter/ingest/contract-debug-adapter';
|
|
60
|
+
import {
|
|
61
|
+
type ContractSurface,
|
|
62
|
+
projectContractSystemAdapters,
|
|
63
|
+
} from '@volter/game-runtime/adapter/ingest/contract-system-adapters';
|
|
64
|
+
import { mergeDebugAdapters } from '@volter/game-runtime/adapter/ingest/merge-debug-adapters';
|
|
65
|
+
import { createObservationDebugAdapter } from '@volter/game-runtime/adapter/ingest/observation-debug-adapter';
|
|
66
|
+
import {
|
|
67
|
+
createRenderDebugAdapter,
|
|
68
|
+
frameCaptureContextFor,
|
|
69
|
+
type RenderDebugWiring,
|
|
70
|
+
} from '@volter/game-runtime/dev/render-debug-adapter';
|
|
71
|
+
import { collectRenderMemory } from '@volter/game-runtime/dev/render-memory';
|
|
72
|
+
import { createWebGLFrameCapture } from '@volter/game-runtime/dev/webgl-frame-capture';
|
|
73
|
+
import { pixiRenderingContext } from '@volter/game-runtime/pixi/render-pass-bracket';
|
|
74
|
+
import type { ObservationDeclaration } from '@volter/editor-project/adapter/adapter-module';
|
|
75
|
+
import type { VgaiGameSystems } from '@volter/editor-project/adapter/ingest/game-contract';
|
|
76
|
+
import type { SystemAdapters } from '@volter/editor-project/adapter/system-adapter';
|
|
77
|
+
import type { RenderPassHooks } from '@volter/threejs-runtime/adapter/ingest/scene-capture';
|
|
78
|
+
import type * as THREE from 'three';
|
|
79
|
+
import { ingestGameRealmWindow } from './game-contract-realm';
|
|
80
|
+
import {
|
|
81
|
+
deliverArmedIngestFrame,
|
|
82
|
+
type IngestFrameSource,
|
|
83
|
+
setIngestFrameSource,
|
|
84
|
+
} from './ingest-frame-snapshot';
|
|
85
|
+
|
|
86
|
+
/** The pieces of a captured runtime this needs. Structural so a headless test
|
|
87
|
+
* can hand in a mock GL context and a plain scene, exactly as the first-party
|
|
88
|
+
* render-debug suite does. */
|
|
89
|
+
export interface IngestRenderDebugRuntime {
|
|
90
|
+
readonly scene: THREE.Object3D;
|
|
91
|
+
readonly renderer: {
|
|
92
|
+
getContext?(): unknown;
|
|
93
|
+
readonly info?: THREE.WebGLInfo | undefined;
|
|
94
|
+
/** Read by the same-frame snapshot only (`ingest-frame-snapshot.ts`); a
|
|
95
|
+
* runtime without them simply has no snapshot capability. */
|
|
96
|
+
readonly domElement?: unknown;
|
|
97
|
+
getRenderTarget?(): unknown;
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Build the render-debug wiring for a captured ingest runtime, or `null` when
|
|
103
|
+
* the mount cannot host frame capture. The caller installs
|
|
104
|
+
* `beforeRender`/`afterRender` as the capture handle's render-pass hooks and
|
|
105
|
+
* calls `dispose()` on unmount.
|
|
106
|
+
*/
|
|
107
|
+
export function createIngestRenderDebug(rt: IngestRenderDebugRuntime): RenderDebugWiring | null {
|
|
108
|
+
// Reads a renderer the host did not construct: an unfamiliar/stub shape must
|
|
109
|
+
// degrade to "no adapter", never break the mount.
|
|
110
|
+
let glContext: unknown;
|
|
111
|
+
try {
|
|
112
|
+
glContext = typeof rt.renderer.getContext === 'function' ? rt.renderer.getContext() : undefined;
|
|
113
|
+
} catch {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
// `headless: false` — this path only runs for a mount that actually rendered a
|
|
117
|
+
// frame; `frameCaptureContextFor` is the ONE predicate deciding whether the
|
|
118
|
+
// context is real, shared with the first-party mount so both sides agree.
|
|
119
|
+
const context = frameCaptureContextFor(false, glContext);
|
|
120
|
+
if (!context) return null;
|
|
121
|
+
const info = rt.renderer.info;
|
|
122
|
+
return createRenderDebugAdapter({
|
|
123
|
+
capture: createWebGLFrameCapture(context),
|
|
124
|
+
scene: rt.scene,
|
|
125
|
+
// `memorySnapshot` is the W3a-style OPTIONAL capability: attached only when
|
|
126
|
+
// the renderer exposes its own `info`, absent otherwise (never a zeroed
|
|
127
|
+
// snapshot standing in for a count nobody read).
|
|
128
|
+
...(info ? { memory: () => collectRenderMemory(rt.scene, info) } : {}),
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** The pieces of a captured CANVAS runtime this needs — just the game's own
|
|
133
|
+
* Pixi renderer, because the two things the three shape carries beyond it
|
|
134
|
+
* (a traversable scene, a `renderer.info`) have no honest Pixi analogue (see
|
|
135
|
+
* the module header). Structural, so a headless test hands in a plain object
|
|
136
|
+
* with a mock `gl` and a pair of runners. */
|
|
137
|
+
export interface CanvasIngestRenderDebugRuntime {
|
|
138
|
+
readonly renderer: unknown;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Build the render-debug wiring for a captured PIXI ingest runtime, or `null`
|
|
143
|
+
* when the mount cannot host frame capture — no real WebGL2 context (a WebGPU
|
|
144
|
+
* or headless renderer), or a renderer that publishes no render-pass runners to
|
|
145
|
+
* bracket. Both are the same honest absence the three lane's `null` is.
|
|
146
|
+
*
|
|
147
|
+
* The caller installs `beforeRender`/`afterRender` through
|
|
148
|
+
* {@link installPixiRenderPassBracket} and calls `dispose()` on unmount.
|
|
149
|
+
*/
|
|
150
|
+
export function createCanvasIngestRenderDebug(
|
|
151
|
+
rt: CanvasIngestRenderDebugRuntime,
|
|
152
|
+
): RenderDebugWiring | null {
|
|
153
|
+
// Reads a renderer the host did not construct: an unfamiliar/stub shape must
|
|
154
|
+
// degrade to "no adapter", never break the mount.
|
|
155
|
+
let glContext: unknown;
|
|
156
|
+
try {
|
|
157
|
+
glContext = pixiRenderingContext(rt.renderer);
|
|
158
|
+
} catch {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
// The SAME predicate the three lane uses, so both surfaces agree on what
|
|
162
|
+
// counts as a real context. `headless: false` — this path only runs for a
|
|
163
|
+
// mount whose game already rendered a capturable frame.
|
|
164
|
+
const context = frameCaptureContextFor(false, glContext);
|
|
165
|
+
if (!context) return null;
|
|
166
|
+
return createRenderDebugAdapter({
|
|
167
|
+
capture: createWebGLFrameCapture(context),
|
|
168
|
+
// No `scene`: this surface has no per-draw annotation seam, so every draw
|
|
169
|
+
// is honestly unattributed. No `memory`: see the module header.
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** What a three ingest mount publishes to `setActiveSystems`, plus the
|
|
174
|
+
* render-debug handle its `dispose()` has to release. */
|
|
175
|
+
export interface IngestSystemsWiring {
|
|
176
|
+
/** Spread onto the `MountedThreeRoot`. Empty when nothing was reachable —
|
|
177
|
+
* the caller omits `systems` entirely then, keeping the pre-contract floor. */
|
|
178
|
+
readonly systems: SystemAdapters;
|
|
179
|
+
/** Non-null only when a real frame capture was built; `dispose()` on unmount. */
|
|
180
|
+
readonly renderDebug: RenderDebugWiring | null;
|
|
181
|
+
/**
|
|
182
|
+
* One sentence per name BOTH debug feeders declared — the game's own contract
|
|
183
|
+
* and its adapter's observation table (`merge-debug-adapters.ts`). Empty is
|
|
184
|
+
* the norm; non-empty is a defect in whichever of the two the reader owns, and
|
|
185
|
+
* the mount says so out loud rather than leaving the coded refusal to be
|
|
186
|
+
* discovered by whoever calls the name first.
|
|
187
|
+
*/
|
|
188
|
+
readonly debugCollisions: readonly string[];
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* THE one place an ingest mount's `SystemAdapters` bag is assembled — on EITHER
|
|
193
|
+
* surface — so a shim never has to know anything about the mount, and no second
|
|
194
|
+
* assembly can drift into publishing a different surface from the same
|
|
195
|
+
* declaration.
|
|
196
|
+
*
|
|
197
|
+
* Four sources, each with a different provenance:
|
|
198
|
+
* - `debug` — projected from the contract's own `commands`/`state`;
|
|
199
|
+
* - `debug`, AGAIN — projected from the loaded ADAPTER's observation
|
|
200
|
+
* declarations (`@volter/game-runtime/adapter/ingest/observation-debug-adapter`).
|
|
201
|
+
* These two are peers onto ONE slot, so they are MERGED
|
|
202
|
+
* (`merge-debug-adapters.ts`) rather than layered: a name declared by both
|
|
203
|
+
* is refused by a coded error naming both sources, never shadowed;
|
|
204
|
+
* - the game-declarable slots — projected from its `systemAdapters` carrier
|
|
205
|
+
* (an IMPLEMENTED-EMPTY slot is deliberately NOT bound; it is an answer the
|
|
206
|
+
* coverage report reads, not an adapter);
|
|
207
|
+
* - `renderDebug` — engine-owned, derived from the captured runtime, and wired
|
|
208
|
+
* to that surface's render-pass bracket.
|
|
209
|
+
*
|
|
210
|
+
* The observation half is read HERE rather than threaded in by each mount, and
|
|
211
|
+
* that is the point of "the one place": the adapter's table is a PROJECT fact
|
|
212
|
+
* the loader already resolved (`project-adapter.ts` loads it per project, not
|
|
213
|
+
* per mount), so a lane cannot forget to pass it. Both parameters exist only as
|
|
214
|
+
* a test seam.
|
|
215
|
+
*
|
|
216
|
+
* `runtime` and `canvasRuntime` are the SAME source in two surface shapes, and
|
|
217
|
+
* are mutually exclusive: a mount has captured a three renderer or a Pixi one,
|
|
218
|
+
* never both. They stay separate types rather than one widened type because the
|
|
219
|
+
* three shape's `scene`/`info` have no honest Pixi analogue — a union that
|
|
220
|
+
* merely made them optional would let a three mount silently lose its
|
|
221
|
+
* attribution. What they share is this function and the `setRenderPassHooks`
|
|
222
|
+
* slot; the MECHANISM behind that slot is each surface's own (the render
|
|
223
|
+
* accessor-trap for three, the renderer's runners for Pixi).
|
|
224
|
+
*/
|
|
225
|
+
export function wireIngestSystems(opts: {
|
|
226
|
+
readonly contractSystems: VgaiGameSystems | undefined;
|
|
227
|
+
/**
|
|
228
|
+
* THIS MOUNT'S SURFACE, stated rather than inferred from which runtime is
|
|
229
|
+
* present — the recognized-Phaser/Babylon canvas branch captures neither
|
|
230
|
+
* renderer and would infer nothing. The projection needs it to check the
|
|
231
|
+
* physics slot's keying (`contract-system-adapters.ts` header).
|
|
232
|
+
*/
|
|
233
|
+
readonly surface: ContractSurface;
|
|
234
|
+
/** Absent when there is no captured THREE renderer to bracket. */
|
|
235
|
+
readonly runtime?: IngestRenderDebugRuntime | undefined;
|
|
236
|
+
/** Absent when there is no captured PIXI renderer to bracket. */
|
|
237
|
+
readonly canvasRuntime?: CanvasIngestRenderDebugRuntime | undefined;
|
|
238
|
+
/**
|
|
239
|
+
* The mount's OWN resolved physics carrier, which REPLACES the projected
|
|
240
|
+
* `physics` slot when supplied.
|
|
241
|
+
*
|
|
242
|
+
* The canvas mount is the case and the reason: the carrier its gizmo path
|
|
243
|
+
* actually uses is the declared one COMPOSED with the host's registry-backed
|
|
244
|
+
* one (`mount-canvas-ingest-root.ts`), and the bag must publish that same
|
|
245
|
+
* object — a coverage row reading `bound` off a different instance than the
|
|
246
|
+
* editor calls would be true about nothing. Absent ⇒ the projection's own
|
|
247
|
+
* slot stands, which is every other mount.
|
|
248
|
+
*/
|
|
249
|
+
readonly physics?: SystemAdapters['physics'] | undefined;
|
|
250
|
+
readonly setRenderPassHooks?: ((hooks: RenderPassHooks | null) => void) | undefined;
|
|
251
|
+
/**
|
|
252
|
+
* TEST SEAM. Omitted — every shipped mount — this is the loaded adapter's own
|
|
253
|
+
* table, which is what makes the projection impossible for a lane to forget.
|
|
254
|
+
*/
|
|
255
|
+
readonly observation?: readonly ObservationDeclaration[] | undefined;
|
|
256
|
+
/**
|
|
257
|
+
* The mounted game handle an observation `answer` is evaluated against.
|
|
258
|
+
*
|
|
259
|
+
* Omitted — every shipped mount — it is THIS REALM: an ingest mount runs the
|
|
260
|
+
* game's own modules in its gated realm, so the game's public handles are on
|
|
261
|
+
* that realm's window proxy (not the editor tab's real `window`) and that is
|
|
262
|
+
* the only honest thing to hand a foreign game's closure
|
|
263
|
+
* (`observation-debug-adapter.ts` states the whole argument). Passed
|
|
264
|
+
* explicitly only by tests, which hand a fake realm.
|
|
265
|
+
*/
|
|
266
|
+
readonly game?: unknown;
|
|
267
|
+
}): IngestSystemsWiring {
|
|
268
|
+
const contractDebug = createContractDebugAdapter(opts.contractSystems);
|
|
269
|
+
const observationDebug = createObservationDebugAdapter(
|
|
270
|
+
opts.observation ?? adapterObservations(),
|
|
271
|
+
'game' in opts ? opts.game : typeof window === 'undefined' ? null : ingestGameRealmWindow(),
|
|
272
|
+
);
|
|
273
|
+
const { adapter: debug, collisions } = mergeDebugAdapters([
|
|
274
|
+
{ label: "the game's own contract (window.vgaiGame.systems)", adapter: contractDebug },
|
|
275
|
+
{ label: "its adapter's observation table (vgai.adapter.ts)", adapter: observationDebug },
|
|
276
|
+
]);
|
|
277
|
+
const declared = projectContractSystemAdapters(opts.contractSystems, opts.surface);
|
|
278
|
+
const renderDebug = opts.runtime
|
|
279
|
+
? createIngestRenderDebug(opts.runtime)
|
|
280
|
+
: opts.canvasRuntime
|
|
281
|
+
? createCanvasIngestRenderDebug(opts.canvasRuntime)
|
|
282
|
+
: null;
|
|
283
|
+
// ONE bracket, TWO consumers. `setRenderPassHooks` is a single-writer slot
|
|
284
|
+
// (last writer wins), so this is the only place that may install it, and
|
|
285
|
+
// anything needing the game's render pass multiplexes here rather than
|
|
286
|
+
// opening a second seam. The second consumer is the same-frame canvas
|
|
287
|
+
// snapshot (`ingest-frame-snapshot.ts`) that makes `vgai screenshot` readable
|
|
288
|
+
// over a game whose canvas has no `preserveDrawingBuffer`; it costs nothing
|
|
289
|
+
// until armed, and `deliverArmedIngestFrame` no-ops while no source is set —
|
|
290
|
+
// which is the canvas lane's state, because a Pixi ingest is already
|
|
291
|
+
// photographable through its own registered `Application`
|
|
292
|
+
// (`stories/story-pixi-preview.ts`, registered at the canvas mount) and needs
|
|
293
|
+
// no second route to the same pixels.
|
|
294
|
+
if (opts.runtime) setIngestFrameSource(opts.runtime as IngestFrameSource);
|
|
295
|
+
if (opts.setRenderPassHooks && (renderDebug || opts.runtime)) {
|
|
296
|
+
opts.setRenderPassHooks({
|
|
297
|
+
// Neither hook may throw — they run inside the game's own render call.
|
|
298
|
+
before: () => {
|
|
299
|
+
renderDebug?.beforeRender();
|
|
300
|
+
},
|
|
301
|
+
after: () => {
|
|
302
|
+
try {
|
|
303
|
+
renderDebug?.afterRender();
|
|
304
|
+
} finally {
|
|
305
|
+
deliverArmedIngestFrame();
|
|
306
|
+
}
|
|
307
|
+
},
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
return {
|
|
311
|
+
systems: {
|
|
312
|
+
...declared.bound,
|
|
313
|
+
...(opts.physics ? { physics: opts.physics } : {}),
|
|
314
|
+
...(debug ? { debug } : {}),
|
|
315
|
+
...(renderDebug ? { renderDebug: renderDebug.adapter } : {}),
|
|
316
|
+
},
|
|
317
|
+
renderDebug,
|
|
318
|
+
debugCollisions: collisions,
|
|
319
|
+
};
|
|
320
|
+
}
|