@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,744 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE door: mounting a project whose manifest declares an `{ ingest }` root is
|
|
3
|
+
* ordinary root-adapter dispatch, exactly like every other root — this module
|
|
4
|
+
* is the dispatcher.
|
|
5
|
+
*
|
|
6
|
+
* It answers one question per surface ("does the OPEN PROJECT declare an
|
|
7
|
+
* `{ ingest }` root, and which surface is it?"), hands off to the per-surface
|
|
8
|
+
* mount module (`mount-three-ingest-root.ts` / `mount-canvas-ingest-root.ts` /
|
|
9
|
+
* `mount-dom-ingest-root.ts`), mounts a composite's remaining roots beside it
|
|
10
|
+
* (`ingest-siblings.ts`), and re-exports the mount/unmount API its callers
|
|
11
|
+
* drive. The measurement half lives in `mount-coverage.ts`, the live session in
|
|
12
|
+
* `active-ingest.ts`, teardown in `unmount-ingest-root.ts` — nothing here
|
|
13
|
+
* measures anything.
|
|
14
|
+
*
|
|
15
|
+
* Dispatching is ALL it does, and that is what keeps the mount-failure seam
|
|
16
|
+
* connected: the Game document belongs to whoever mounts into its container,
|
|
17
|
+
* so each per-surface mount module acquires it (`acquireGameDocument`) right
|
|
18
|
+
* before it reads `liveDocumentContainer()` and reports its own missing-container
|
|
19
|
+
* refusal. A route here that acquired first and refused on its own would
|
|
20
|
+
* pre-empt that mount — the failure would then be a bare console line that
|
|
21
|
+
* never reaches {@link reportFailedIngestMount}, which is precisely the
|
|
22
|
+
* trace-less failure D-W3/S-1 exists to eliminate. Every route's ONLY failure
|
|
23
|
+
* path is therefore its catch block.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { getAuthoringOverride, setActiveAuthoring } from '@volter/editor-core/authoring/active-adapter';
|
|
27
|
+
import {
|
|
28
|
+
CompositeAuthoringAdapter,
|
|
29
|
+
type CompositeChild,
|
|
30
|
+
} from '@volter/editor-core/authoring/composite-authoring-adapter';
|
|
31
|
+
import {
|
|
32
|
+
addMountFailureReport,
|
|
33
|
+
clearMountFailureReports,
|
|
34
|
+
formatMountFailureMessage,
|
|
35
|
+
getMountFailureReports,
|
|
36
|
+
type MountFailureReport,
|
|
37
|
+
} from '@volter/editor-core/authoring/mount-failure-report';
|
|
38
|
+
import { editorConsole } from '@volter/editor-core/editor-console';
|
|
39
|
+
import type { EditorShellStore } from '@volter/editor-core/editor-shell-store';
|
|
40
|
+
import { registerEditorStateFacet } from '@volter/editor-core/editor-state-facets';
|
|
41
|
+
import { liveDocumentContainer } from '@volter/editor-core/live-document';
|
|
42
|
+
import { fetchGameManifest, isManifestAbsence } from '@volter/editor-core/manifest-project';
|
|
43
|
+
import { getCurrentProject } from '@volter/editor-core/project-manager';
|
|
44
|
+
import { onShellStore } from '@volter/editor-core/shell-store-door';
|
|
45
|
+
import { ingestRoots, rootById } from '@volter/editor-project/adapter/manifest-interpreter';
|
|
46
|
+
import { MANIFEST_FILENAME } from '@volter/editor-project/manifest/filename';
|
|
47
|
+
import type { ResolvedAdapterRoot, ResolvedGameManifest } from '@volter/editor-project/manifest/load';
|
|
48
|
+
import { activeIngest, attachIngestSiblings } from './active-ingest';
|
|
49
|
+
import { captureWaitStatus } from './capture-wait-report';
|
|
50
|
+
import { resetIngestPlaySurface } from './ingest-play-control';
|
|
51
|
+
// TYPE-ONLY (D-V2): `ingest-siblings.ts` imports from `module-mode.ts`, and a
|
|
52
|
+
// value import here would risk closing that cycle at the wrong end. The actual
|
|
53
|
+
// `mountIngestSiblings` call below is a dynamic `import()`, right where it's
|
|
54
|
+
// used (`mountCompositeSiblings`); only the TYPE is needed at the top level,
|
|
55
|
+
// and type-only imports are erased before the cycle could ever matter.
|
|
56
|
+
import type { SiblingMount } from './ingest-siblings';
|
|
57
|
+
import { ingestStatusFacet } from './live-ingest-facet';
|
|
58
|
+
import {
|
|
59
|
+
exitActiveIngest,
|
|
60
|
+
unmountCanvasIngestRoot,
|
|
61
|
+
unmountDomIngestRoot,
|
|
62
|
+
unmountThreeIngestRoot,
|
|
63
|
+
} from './unmount-ingest-root';
|
|
64
|
+
|
|
65
|
+
export {
|
|
66
|
+
activeIngestKind,
|
|
67
|
+
type IngestKind,
|
|
68
|
+
isIngestActive,
|
|
69
|
+
isIngestActive2D,
|
|
70
|
+
isIngestActiveReact,
|
|
71
|
+
} from './active-ingest';
|
|
72
|
+
export { getIngestPlayControl, type IngestPlayControl } from './ingest-play-control';
|
|
73
|
+
export { ingestStatusFacet } from './live-ingest-facet';
|
|
74
|
+
export {
|
|
75
|
+
unmountCanvasIngestRoot,
|
|
76
|
+
unmountDomIngestRoot,
|
|
77
|
+
unmountThreeIngestRoot,
|
|
78
|
+
} from './unmount-ingest-root';
|
|
79
|
+
|
|
80
|
+
type CanvasMounts = typeof import('./mount-canvas-ingest-root');
|
|
81
|
+
type DomMounts = typeof import('./mount-dom-ingest-root');
|
|
82
|
+
type ThreeMounts = typeof import('./mount-three-ingest-root');
|
|
83
|
+
|
|
84
|
+
const canvasMounts = () => import('./mount-canvas-ingest-root');
|
|
85
|
+
const domMounts = () => import('./mount-dom-ingest-root');
|
|
86
|
+
const threeMounts = () => import('./mount-three-ingest-root');
|
|
87
|
+
|
|
88
|
+
export function mountCanvasIngestRoot(
|
|
89
|
+
...args: Parameters<CanvasMounts['mountCanvasIngestRoot']>
|
|
90
|
+
): ReturnType<CanvasMounts['mountCanvasIngestRoot']> {
|
|
91
|
+
return canvasMounts().then((module) => module.mountCanvasIngestRoot(...args));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function mountCanvasIngestRootById(
|
|
95
|
+
...args: Parameters<CanvasMounts['mountCanvasIngestRootById']>
|
|
96
|
+
): ReturnType<CanvasMounts['mountCanvasIngestRootById']> {
|
|
97
|
+
return canvasMounts().then((module) => module.mountCanvasIngestRootById(...args));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function mountCanvasIngestRootFromManifest(
|
|
101
|
+
...args: Parameters<CanvasMounts['mountCanvasIngestRootFromManifest']>
|
|
102
|
+
): ReturnType<CanvasMounts['mountCanvasIngestRootFromManifest']> {
|
|
103
|
+
return canvasMounts().then((module) => module.mountCanvasIngestRootFromManifest(...args));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function mountDomIngestRoot(
|
|
107
|
+
...args: Parameters<DomMounts['mountDomIngestRoot']>
|
|
108
|
+
): ReturnType<DomMounts['mountDomIngestRoot']> {
|
|
109
|
+
return domMounts().then((module) => module.mountDomIngestRoot(...args));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function mountDomIngestRootFromManifest(
|
|
113
|
+
...args: Parameters<DomMounts['mountDomIngestRootFromManifest']>
|
|
114
|
+
): ReturnType<DomMounts['mountDomIngestRootFromManifest']> {
|
|
115
|
+
return domMounts().then((module) => module.mountDomIngestRootFromManifest(...args));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function mountThreeIngestRoot(
|
|
119
|
+
...args: Parameters<ThreeMounts['mountThreeIngestRoot']>
|
|
120
|
+
): ReturnType<ThreeMounts['mountThreeIngestRoot']> {
|
|
121
|
+
return threeMounts().then((module) => module.mountThreeIngestRoot(...args));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function mountThreeIngestRootById(
|
|
125
|
+
...args: Parameters<ThreeMounts['mountThreeIngestRootById']>
|
|
126
|
+
): ReturnType<ThreeMounts['mountThreeIngestRootById']> {
|
|
127
|
+
return threeMounts().then((module) => module.mountThreeIngestRootById(...args));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function mountThreeIngestRootFromManifest(
|
|
131
|
+
...args: Parameters<ThreeMounts['mountThreeIngestRootFromManifest']>
|
|
132
|
+
): ReturnType<ThreeMounts['mountThreeIngestRootFromManifest']> {
|
|
133
|
+
return threeMounts().then((module) => module.mountThreeIngestRootFromManifest(...args));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
let _boundStore: EditorShellStore | null = null;
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* The editor store, so the dev hook / UI can launch a mount — bound on the
|
|
140
|
+
* store's ARRIVAL (`shell-store-door.ts`), once per project session
|
|
141
|
+
* (`AppRoot` unmounts/remounts `DefaultEditorLayout` under a fresh
|
|
142
|
+
* `EditorProvider` on "return to project screen" -> reopen).
|
|
143
|
+
*
|
|
144
|
+
* D-W3: the arrival is the earliest point every new project session reaches,
|
|
145
|
+
* so it is where a PREVIOUS project's stale mount-failure report is dropped.
|
|
146
|
+
* The rest of the project-switch teardown — the live ingest session itself
|
|
147
|
+
* (`active-ingest.ts`'s slot and the `active-adapter.ts` globals it sets), the
|
|
148
|
+
* play session, the System adapters and the session measurement ledgers — is
|
|
149
|
+
* `project-session-reset.ts`, fired by `project-manager.ts`'s
|
|
150
|
+
* `onProjectSessionEnd`.
|
|
151
|
+
*/
|
|
152
|
+
onShellStore((store) => {
|
|
153
|
+
_boundStore = store;
|
|
154
|
+
clearMountFailureReports();
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// What only the ingest lane knows of the state report
|
|
158
|
+
// (`editor-state-facets.ts`): the live ingest session's OWN facts (world id,
|
|
159
|
+
// surface reached, whether its loop is running, the MEASURED loop verdict)
|
|
160
|
+
// — null when nothing is ingested; a non-null facet IS the mount evidence —
|
|
161
|
+
// and the state BETWEEN mounted and failed: a mount still waiting for the
|
|
162
|
+
// game's first frame, a budget of VISIBLE time (parked on a hidden tab).
|
|
163
|
+
registerEditorStateFacet(() => ({
|
|
164
|
+
ingest: ingestStatusFacet(),
|
|
165
|
+
ingestCaptureWait: captureWaitStatus(),
|
|
166
|
+
}));
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* S-1, the other half: a mount that FAILED must leave the editor in a stopped,
|
|
170
|
+
* empty state — never in the half-entered "playing" one its own prologue set up.
|
|
171
|
+
*
|
|
172
|
+
* The per-surface mount paths flip `store.setPlayState('playing')` BEFORE the
|
|
173
|
+
* mount is attempted (that flag is what swaps the viewport onto the live
|
|
174
|
+
* scene). When the mount then throws, that flag, plus any authoring
|
|
175
|
+
* adapter/adopted scene a partial mount installed, used to survive — so
|
|
176
|
+
* `vgai status` reported `playing`, the hierarchy kept presenting whatever tree
|
|
177
|
+
* was underneath, and the only trace of the failure was one console line. This
|
|
178
|
+
* is the single teardown every failure path runs: drop the partial session,
|
|
179
|
+
* reset the play surface, and record + broadcast the failure so the status item
|
|
180
|
+
* actually shows.
|
|
181
|
+
*
|
|
182
|
+
* Safe to call when nothing mounted at all (`exitActiveIngest` and
|
|
183
|
+
* `exitPlayScene` are both no-ops on an empty slot/stack), which is what makes
|
|
184
|
+
* it callable from any catch block without first proving how far the mount got.
|
|
185
|
+
*/
|
|
186
|
+
function reportFailedIngestMount(store: EditorShellStore, report: MountFailureReport): void {
|
|
187
|
+
editorConsole.error(
|
|
188
|
+
`Ingest mount failed for "${report.worldId}" (${report.identity}): ${report.message}`,
|
|
189
|
+
'ingest',
|
|
190
|
+
);
|
|
191
|
+
// Tear down whatever the mount DID publish. Deliberately only this: when
|
|
192
|
+
// the slot was never assigned, nothing overrode the edit-mode authoring
|
|
193
|
+
// composite or adopted a scene, so clearing those here would blank the
|
|
194
|
+
// hierarchy for the project's OTHER roots too. `exitActiveIngest` is a no-op
|
|
195
|
+
// on an empty slot and the full per-kind teardown otherwise.
|
|
196
|
+
exitActiveIngest();
|
|
197
|
+
resetIngestPlaySurface();
|
|
198
|
+
// The prologue's `setPlayState('playing')` must never outlive a failed mount
|
|
199
|
+
// — that flag is what `collectState` reported as a live game (S-1).
|
|
200
|
+
store.setPlayState('stopped');
|
|
201
|
+
addMountFailureReport(report);
|
|
202
|
+
// `addMountFailureReport` notifies the status item through the slot's own
|
|
203
|
+
// subscribe; this broadcast is for the STORE-subscribed panels (hierarchy,
|
|
204
|
+
// play controls) that must re-render off the teardown above — a failed
|
|
205
|
+
// mount never reaches the success path's `notifyIngestEdit()`.
|
|
206
|
+
store.notifyIngestEdit();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* THE manifest read for the whole ingest route chain — and the one place a
|
|
211
|
+
* failure to read it is allowed to end the chain.
|
|
212
|
+
*
|
|
213
|
+
* Each `tryManifestIngestRoute*` used to fetch this for itself behind
|
|
214
|
+
* `.catch(() => null)` and then `return false`. ABSENCE and FAILURE therefore
|
|
215
|
+
* had the same shape, so a project that declared a perfectly good ingest root
|
|
216
|
+
* in a manifest the strict loader rejected — one unrecognized key is enough,
|
|
217
|
+
* the schema is `.strict()` — was declined by all three routes, fell past the
|
|
218
|
+
* hosted lane, and mounted NOTHING, with not one line anywhere saying why:
|
|
219
|
+
* a Boundary row with 0 children over a black frame and a silent console.
|
|
220
|
+
* An in-tree fixture's mount failure was loud through the same session,
|
|
221
|
+
* because `mountCanvasIngestRootById` takes a DISCOVERED descriptor and never
|
|
222
|
+
* reads a manifest at all — the two lanes disagreed about whether failing to
|
|
223
|
+
* mount is something the reader gets told.
|
|
224
|
+
*
|
|
225
|
+
* `null` still means "this chain has nothing to route", but only ever after
|
|
226
|
+
* the reason has been either (a) genuine absence — this project has no
|
|
227
|
+
* manifest, which is not an ingest project and not an error — or (b) said out
|
|
228
|
+
* loud.
|
|
229
|
+
*/
|
|
230
|
+
async function readIngestChainManifest(): Promise<ResolvedGameManifest | null> {
|
|
231
|
+
// Same realm split as `module-mode.ts`: a storage-backed project's manifest
|
|
232
|
+
// reads through the realm; staged hosted content has its own discovery.
|
|
233
|
+
try {
|
|
234
|
+
return await fetchGameManifest();
|
|
235
|
+
} catch (err) {
|
|
236
|
+
if (isManifestAbsence(err)) return null; // not a manifest project; nothing to explain
|
|
237
|
+
editorConsole.error(
|
|
238
|
+
`Ingest routing: this project's ${MANIFEST_FILENAME} could not be read, so no root was ` +
|
|
239
|
+
`mounted from it — ${formatMountFailureMessage(err)}`,
|
|
240
|
+
'ingest',
|
|
241
|
+
);
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* D-V2 (composite): every world in `manifest.roots` whose adapter is `{
|
|
248
|
+
* ingest }` — a composite manifest caps this at exactly one (see each
|
|
249
|
+
* `tryManifestIngestRoute*` below); ≥2 is a loud named error via {@link
|
|
250
|
+
* reportTooManyIngestRoots}, never a silent first-wins pick.
|
|
251
|
+
*/
|
|
252
|
+
function findIngestRoots(manifest: ResolvedGameManifest): ResolvedAdapterRoot[] {
|
|
253
|
+
return ingestRoots(manifest);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* D-V2: the "composite declares ≥2 ingest roots" violation — named, listing
|
|
258
|
+
* every offending world id, reported once (into BOTH `editorConsole` and the
|
|
259
|
+
* D-V5 multi-entry mount-failure report, one entry per offending world so
|
|
260
|
+
* the status item names all of them) by whichever `tryManifestIngestRoute*`
|
|
261
|
+
* happens to run first (module route order: three, pixi, react — see
|
|
262
|
+
* {@link autoLaunchIngest}) — the caller returns `true` right after this
|
|
263
|
+
* (claims the route), so the other two ingest routes never also report it.
|
|
264
|
+
*/
|
|
265
|
+
function reportTooManyIngestRoots(ingestRoots: ResolvedAdapterRoot[]): void {
|
|
266
|
+
const ids = ingestRoots.map((w) => `"${w.id}"`).join(', ');
|
|
267
|
+
const message =
|
|
268
|
+
'a composite manifest declares exactly ONE ingest world in this slice — found ' +
|
|
269
|
+
`${ingestRoots.length}: ${ids}.`;
|
|
270
|
+
editorConsole.error(`Manifest composite ingest route: ${message}`, 'ingest');
|
|
271
|
+
for (const world of ingestRoots) {
|
|
272
|
+
addMountFailureReport({
|
|
273
|
+
worldId: world.id,
|
|
274
|
+
kind: world.surface,
|
|
275
|
+
identity: world.adapter.identity,
|
|
276
|
+
message,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* D-V2: mount every OTHER world in a composite manifest (every world besides
|
|
283
|
+
* `ingestRootId`, the one just mounted through its own per-surface path) via
|
|
284
|
+
* `ingest-siblings.ts`'s `mountIngestSiblings`, and attach the resulting
|
|
285
|
+
* handles onto the live slot so `exitActiveIngest` tears them down.
|
|
286
|
+
* Dynamically imported — `ingest-siblings.ts` imports from `module-mode.ts`,
|
|
287
|
+
* and a static import here would risk closing that cycle at the wrong end
|
|
288
|
+
* (mirrors {@link autoLaunchIngest}'s own dynamic import of `module-mode.ts`
|
|
289
|
+
* below, for the identical reason). Never throws: `mountIngestSiblings` already
|
|
290
|
+
* catches every individual sibling's failure into the mount-failure report.
|
|
291
|
+
* Returns the mounted handles so the caller can promote authoring (D-E2,
|
|
292
|
+
* {@link promoteToCompositeAuthoring}) — `store` is threaded through so each
|
|
293
|
+
* sibling gets an honest `SiblingMount.authoring` (D-E1).
|
|
294
|
+
*/
|
|
295
|
+
async function mountCompositeSiblings(
|
|
296
|
+
manifest: ResolvedGameManifest,
|
|
297
|
+
ingestRootId: string,
|
|
298
|
+
gameContainer: HTMLElement,
|
|
299
|
+
projectRoot: string,
|
|
300
|
+
store: EditorShellStore,
|
|
301
|
+
): Promise<SiblingMount[]> {
|
|
302
|
+
const { mountIngestSiblings } = await import('./ingest-siblings');
|
|
303
|
+
const siblings = await mountIngestSiblings(
|
|
304
|
+
manifest,
|
|
305
|
+
ingestRootId,
|
|
306
|
+
gameContainer,
|
|
307
|
+
projectRoot,
|
|
308
|
+
store,
|
|
309
|
+
);
|
|
310
|
+
attachIngestSiblings(siblings);
|
|
311
|
+
return siblings;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* D-E2 (promotion on the ingest composite): when a composite's siblings
|
|
316
|
+
* actually mounted (`siblings.length > 0`), promote the active authoring
|
|
317
|
+
* override — currently the PRIMARY ingest world's own plain adapter, installed
|
|
318
|
+
* by the route's own mount that just ran immediately before this call — to
|
|
319
|
+
* a `CompositeAuthoringAdapter` whose ordered children are the primary world
|
|
320
|
+
* FIRST (it is the route's anchor) followed by every mounted sibling in
|
|
321
|
+
* manifest order, each routed by its own manifest `kind`. A single-world mount
|
|
322
|
+
* (`siblings.length === 0`) is untouched — byte-stable, no group node, no
|
|
323
|
+
* wrapper: §1F's single-world clause.
|
|
324
|
+
*
|
|
325
|
+
* The primary's own adapter is read back via `getAuthoringOverride()` rather
|
|
326
|
+
* than threaded as a parameter — every composite route's own mount call
|
|
327
|
+
* already installed it via `setActiveAuthoring` synchronously before
|
|
328
|
+
* this runs (mirrors `attachIngestSiblings`'s identical "just installed"
|
|
329
|
+
* assumption); `null` (defensive only — nothing in this synchronous
|
|
330
|
+
* call chain clears it in between) is a no-op, same guard shape.
|
|
331
|
+
*/
|
|
332
|
+
function promoteToCompositeAuthoring(
|
|
333
|
+
manifest: ResolvedGameManifest,
|
|
334
|
+
ingestRoot: ResolvedAdapterRoot,
|
|
335
|
+
siblings: readonly SiblingMount[],
|
|
336
|
+
): void {
|
|
337
|
+
if (siblings.length === 0) return;
|
|
338
|
+
const primaryAdapter = getAuthoringOverride();
|
|
339
|
+
if (!primaryAdapter) return;
|
|
340
|
+
// A readable hybrid ingest is already a small surface composite (Canvas +
|
|
341
|
+
// nested DOM UI). Flatten those children before adding manifest siblings:
|
|
342
|
+
// CompositeAuthoringAdapter's synthetic `world:*` ids belong to ONE
|
|
343
|
+
// composite and may never be nested inside another composite that would
|
|
344
|
+
// interpret them as its own group ids.
|
|
345
|
+
const primaryChildren: CompositeChild[] =
|
|
346
|
+
primaryAdapter instanceof CompositeAuthoringAdapter
|
|
347
|
+
? primaryAdapter.childAdapters().map((child) => ({
|
|
348
|
+
worldId: child.worldId,
|
|
349
|
+
kind: child.kind,
|
|
350
|
+
adapter: child.adapter,
|
|
351
|
+
role: child.role,
|
|
352
|
+
zOrder: child.zOrder,
|
|
353
|
+
...(child.label !== undefined ? { label: child.label } : {}),
|
|
354
|
+
...(child.parentRootId !== undefined ? { parentRootId: child.parentRootId } : {}),
|
|
355
|
+
}))
|
|
356
|
+
: [{ worldId: ingestRoot.id, kind: ingestRoot.surface, adapter: primaryAdapter }];
|
|
357
|
+
const children: CompositeChild[] = [
|
|
358
|
+
...primaryChildren,
|
|
359
|
+
...siblings.map((s) => ({
|
|
360
|
+
worldId: s.worldId,
|
|
361
|
+
kind: rootById(manifest, s.worldId)?.surface ?? 'unknown',
|
|
362
|
+
// D-E1: every successfully-mounted SiblingMount gets an honest
|
|
363
|
+
// `authoring` whenever a store was supplied — every composite route
|
|
364
|
+
// below supplies one, so this is never actually undefined in
|
|
365
|
+
// production; the non-null assertion mirrors D-E2's own spec.
|
|
366
|
+
adapter: s.authoring!,
|
|
367
|
+
})),
|
|
368
|
+
];
|
|
369
|
+
setActiveAuthoring(new CompositeAuthoringAdapter(children));
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Route 1 of {@link autoLaunchIngest} (T3.3 slice 3, §1E): a manifest-backed
|
|
374
|
+
* project declaring a three-surface `{ ingest }` root mounts straight from
|
|
375
|
+
* that root. Returns `true` if this route applied (whether it succeeded or
|
|
376
|
+
* logged an error) — the caller must not also try route 2 in that case
|
|
377
|
+
* (§1B's "manifest wins" discovery order); `false` means "no manifest, no
|
|
378
|
+
* ingest root at all, or the one ingest root isn't ingest-three", so the
|
|
379
|
+
* next route (canvas, dom, then the hosted lane) still gets a chance.
|
|
380
|
+
*
|
|
381
|
+
* The manifest is HANDED IN ({@link readIngestChainManifest} reads it once for
|
|
382
|
+
* the chain) rather than fetched here. It was fetched per route behind a
|
|
383
|
+
* swallowing `catch`, which made "this project has no manifest" and "this
|
|
384
|
+
* project's manifest is unreadable" the same silent answer — see that
|
|
385
|
+
* function for the dark mount that produced.
|
|
386
|
+
*
|
|
387
|
+
* D-V2 (F24 composite): scans ALL roots for `{ ingest }` adapters, not just
|
|
388
|
+
* `roots[0]` — 0 ingest roots declines (unchanged non-ingest-manifest
|
|
389
|
+
* behavior); ≥2 claims the route and fails loudly
|
|
390
|
+
* ({@link reportTooManyIngestRoots}), mounting nothing; exactly 1 whose
|
|
391
|
+
* identity is `ingest-three` mounts THAT world (wherever it sits in the
|
|
392
|
+
* array) through the existing mount path, then mounts every other world as a
|
|
393
|
+
* sibling ({@link mountCompositeSiblings}) — a sibling failure never
|
|
394
|
+
* unmounts the ingest world (it is caught internally, per-world, by
|
|
395
|
+
* `ingest-siblings.ts`).
|
|
396
|
+
*/
|
|
397
|
+
async function tryManifestIngestRoute(
|
|
398
|
+
store: EditorShellStore,
|
|
399
|
+
manifest: ResolvedGameManifest | null,
|
|
400
|
+
): Promise<boolean> {
|
|
401
|
+
if (!manifest) return false;
|
|
402
|
+
const ingestRoots = findIngestRoots(manifest);
|
|
403
|
+
if (ingestRoots.length === 0) return false;
|
|
404
|
+
if (ingestRoots.length > 1) {
|
|
405
|
+
reportTooManyIngestRoots(ingestRoots);
|
|
406
|
+
return true;
|
|
407
|
+
}
|
|
408
|
+
const ingestRoot = ingestRoots[0]!;
|
|
409
|
+
if (ingestRoot.adapter.identity !== 'ingest-three') return false;
|
|
410
|
+
|
|
411
|
+
const project = getCurrentProject();
|
|
412
|
+
if (!project) {
|
|
413
|
+
editorConsole.error(
|
|
414
|
+
'Manifest declares an ingest-three world but no project is open — cannot auto-ingest',
|
|
415
|
+
'ingest',
|
|
416
|
+
);
|
|
417
|
+
return true;
|
|
418
|
+
}
|
|
419
|
+
try {
|
|
420
|
+
await mountThreeIngestRootFromManifest(store, ingestRoot, project.rootPath);
|
|
421
|
+
// D-V2: mount every OTHER world as a composite sibling — a no-op loop
|
|
422
|
+
// when this is a single-world manifest (nothing else to iterate).
|
|
423
|
+
const gameContainer = liveDocumentContainer();
|
|
424
|
+
if (gameContainer) {
|
|
425
|
+
const siblings = await mountCompositeSiblings(
|
|
426
|
+
manifest,
|
|
427
|
+
ingestRoot.id,
|
|
428
|
+
gameContainer,
|
|
429
|
+
project.rootPath,
|
|
430
|
+
store,
|
|
431
|
+
);
|
|
432
|
+
// D-E2: promote to a composite once any sibling actually mounted —
|
|
433
|
+
// byte-stable (no-op) for a single-world manifest.
|
|
434
|
+
promoteToCompositeAuthoring(manifest, ingestRoot, siblings);
|
|
435
|
+
}
|
|
436
|
+
} catch (err) {
|
|
437
|
+
// D-W3 (F9) + S-1: a console line was, until now, the ONLY trace this
|
|
438
|
+
// failure left anywhere in the UI — the default Untitled scene has
|
|
439
|
+
// already loaded by this point (§0.4), so nothing visibly changed, and
|
|
440
|
+
// the half-entered `playState: 'playing'` made the dead world report
|
|
441
|
+
// itself alive. `reportFailedIngestMount` is the one path that resets the
|
|
442
|
+
// surface, records the failure report AND broadcasts it.
|
|
443
|
+
reportFailedIngestMount(store, {
|
|
444
|
+
worldId: ingestRoot.id,
|
|
445
|
+
kind: ingestRoot.surface,
|
|
446
|
+
identity: ingestRoot.adapter.identity,
|
|
447
|
+
message: formatMountFailureMessage(err),
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
return true;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Canvas sibling of {@link tryManifestIngestRoute} (T6.1 slice 4): a
|
|
455
|
+
* manifest-backed project declaring an `ingest-pixi` root mounts straight from
|
|
456
|
+
* that root. Same "applied or not" contract, same D-V2 composite
|
|
457
|
+
* scan/route-selection/sibling-mount shape as its three sibling above.
|
|
458
|
+
*/
|
|
459
|
+
async function tryManifestIngestRoute2D(
|
|
460
|
+
store: EditorShellStore,
|
|
461
|
+
manifest: ResolvedGameManifest | null,
|
|
462
|
+
): Promise<boolean> {
|
|
463
|
+
if (!manifest) return false;
|
|
464
|
+
const ingestRoots = findIngestRoots(manifest);
|
|
465
|
+
if (ingestRoots.length === 0) return false;
|
|
466
|
+
if (ingestRoots.length > 1) {
|
|
467
|
+
reportTooManyIngestRoots(ingestRoots);
|
|
468
|
+
return true;
|
|
469
|
+
}
|
|
470
|
+
const ingestRoot = ingestRoots[0]!;
|
|
471
|
+
if (ingestRoot.adapter.identity !== 'ingest-pixi') return false;
|
|
472
|
+
|
|
473
|
+
const project = getCurrentProject();
|
|
474
|
+
if (!project) {
|
|
475
|
+
editorConsole.error(
|
|
476
|
+
'Manifest declares an ingest-pixi world but no project is open — cannot auto-ingest',
|
|
477
|
+
'ingest',
|
|
478
|
+
);
|
|
479
|
+
return true;
|
|
480
|
+
}
|
|
481
|
+
try {
|
|
482
|
+
await mountCanvasIngestRootFromManifest(store, ingestRoot, project.rootPath);
|
|
483
|
+
const gameContainer = liveDocumentContainer();
|
|
484
|
+
if (gameContainer) {
|
|
485
|
+
const siblings = await mountCompositeSiblings(
|
|
486
|
+
manifest,
|
|
487
|
+
ingestRoot.id,
|
|
488
|
+
gameContainer,
|
|
489
|
+
project.rootPath,
|
|
490
|
+
store,
|
|
491
|
+
);
|
|
492
|
+
// D-E2: promote to a composite once any sibling actually mounted —
|
|
493
|
+
// byte-stable (no-op) for a single-world manifest.
|
|
494
|
+
promoteToCompositeAuthoring(manifest, ingestRoot, siblings);
|
|
495
|
+
}
|
|
496
|
+
} catch (err) {
|
|
497
|
+
// D-W3 (F9) + S-1 — see tryManifestIngestRoute's identical comment above.
|
|
498
|
+
reportFailedIngestMount(store, {
|
|
499
|
+
worldId: ingestRoot.id,
|
|
500
|
+
kind: ingestRoot.surface,
|
|
501
|
+
identity: ingestRoot.adapter.identity,
|
|
502
|
+
message: formatMountFailureMessage(err),
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
return true;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* DOM sibling of {@link tryManifestIngestRoute}/{@link tryManifestIngestRoute2D}
|
|
510
|
+
* (D-Y2, slice S2): a manifest-backed project declaring an `ingest-react` root
|
|
511
|
+
* mounts the native-React ingest session straight from that root (external
|
|
512
|
+
* folder — `vgai edit <folder>` — not the in-tree `ingest/games/<id>` fixture
|
|
513
|
+
* registry). Same "applied or not" contract, same D-V2 composite scan/
|
|
514
|
+
* route-selection/sibling-mount shape as its three/canvas siblings above.
|
|
515
|
+
*/
|
|
516
|
+
async function tryManifestIngestRouteReact(
|
|
517
|
+
store: EditorShellStore,
|
|
518
|
+
manifest: ResolvedGameManifest | null,
|
|
519
|
+
): Promise<boolean> {
|
|
520
|
+
if (!manifest) return false;
|
|
521
|
+
const ingestRoots = findIngestRoots(manifest);
|
|
522
|
+
if (ingestRoots.length === 0) return false;
|
|
523
|
+
if (ingestRoots.length > 1) {
|
|
524
|
+
reportTooManyIngestRoots(ingestRoots);
|
|
525
|
+
return true;
|
|
526
|
+
}
|
|
527
|
+
const ingestRoot = ingestRoots[0]!;
|
|
528
|
+
if (ingestRoot.adapter.identity !== 'ingest-react') return false;
|
|
529
|
+
|
|
530
|
+
const project = getCurrentProject();
|
|
531
|
+
if (!project) {
|
|
532
|
+
editorConsole.error(
|
|
533
|
+
'Manifest declares an ingest-react world but no project is open — cannot auto-ingest',
|
|
534
|
+
'ingest',
|
|
535
|
+
);
|
|
536
|
+
return true;
|
|
537
|
+
}
|
|
538
|
+
try {
|
|
539
|
+
await mountDomIngestRootFromManifest(store, manifest, ingestRoot, project.rootPath);
|
|
540
|
+
const gameContainer = liveDocumentContainer();
|
|
541
|
+
if (gameContainer) {
|
|
542
|
+
const siblings = await mountCompositeSiblings(
|
|
543
|
+
manifest,
|
|
544
|
+
ingestRoot.id,
|
|
545
|
+
gameContainer,
|
|
546
|
+
project.rootPath,
|
|
547
|
+
store,
|
|
548
|
+
);
|
|
549
|
+
// D-E2: promote to a composite once any sibling actually mounted —
|
|
550
|
+
// byte-stable (no-op) for a single-world manifest.
|
|
551
|
+
promoteToCompositeAuthoring(manifest, ingestRoot, siblings);
|
|
552
|
+
}
|
|
553
|
+
} catch (err) {
|
|
554
|
+
// D-W3 (F9) + S-1 — see tryManifestIngestRoute's identical comment above.
|
|
555
|
+
reportFailedIngestMount(store, {
|
|
556
|
+
worldId: ingestRoot.id,
|
|
557
|
+
kind: ingestRoot.surface,
|
|
558
|
+
identity: ingestRoot.adapter.identity,
|
|
559
|
+
message: formatMountFailureMessage(err),
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
return true;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* Hosted entry point, called once after `loadInitialScene`: mount whichever
|
|
567
|
+
* `{ ingest }` root the open project declares, from whichever discovery route
|
|
568
|
+
* applies, so ingestion needs no dev hook. Checked in order (§1B's "manifest
|
|
569
|
+
* wins" discovery order):
|
|
570
|
+
*
|
|
571
|
+
* 1. Manifest route, `{ module }` (T3.4 slice 2, `./module-mode.ts`'s
|
|
572
|
+
* `tryManifestModuleRoute`) — a manifest's first world declaring a custom
|
|
573
|
+
* adapter module auto-mounts through the generic
|
|
574
|
+
* `RootAdapter`/`ThreeHostContext` contract (or, since D-Y1, the
|
|
575
|
+
* WORLDS PATH for a non-three module world — see that file) and installs ITS
|
|
576
|
+
* OWN `MountedThreeRoot.authoring` as the active override, so the SAME
|
|
577
|
+
* adapter-driven hierarchy/inspector panels below drive it. Mutually
|
|
578
|
+
* exclusive with routes 2–4 by construction (a world resolves to exactly one
|
|
579
|
+
* adapter identity).
|
|
580
|
+
* 2. Manifest route, three (T3.3 slice 3, {@link tryManifestIngestRoute}) — the
|
|
581
|
+
* CLI-on-folder AC ("point the CLI at an ingest game folder never registered
|
|
582
|
+
* in source -> opens, plays, edits"). Browser-mode builds fall straight
|
|
583
|
+
* through to route 5.
|
|
584
|
+
* 3. Manifest route, canvas (T6.1 slice 4, {@link tryManifestIngestRoute2D}) —
|
|
585
|
+
* the discovery path for an `ingest-pixi` world.
|
|
586
|
+
* 4. Manifest route, dom (D-Y2, {@link tryManifestIngestRouteReact}) —
|
|
587
|
+
* the external-folder discovery path for an `ingest-react` world. A
|
|
588
|
+
* manifest's first world resolves to exactly one adapter identity, so
|
|
589
|
+
* routes 1–4 are mutually exclusive in practice; all are tried since
|
|
590
|
+
* neither can tell that in advance without fetching.
|
|
591
|
+
*
|
|
592
|
+
* There is no sixth route by id. A vendored game IS a project and opens through
|
|
593
|
+
* the session like every other one, which is what routes 2–4 already read.
|
|
594
|
+
*/
|
|
595
|
+
export function autoLaunchIngest(store: EditorShellStore): Promise<void> {
|
|
596
|
+
return dedupeAutoLaunch(store, () => autoLaunchIngestInner(store));
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* ONE auto-launch per (session, open project) — closing the
|
|
601
|
+
* double-entry the #745 review measured.
|
|
602
|
+
*
|
|
603
|
+
* Two independent bootstraps call {@link autoLaunchIngest}:
|
|
604
|
+
* `world-root-stage.ts`'s boot chain and `NonThreeAuthoringBootstrap.tsx`'s. Both
|
|
605
|
+
* are correct to — neither can know whether the other exists for this project —
|
|
606
|
+
* so the same launch happens twice. `serializeEntry` keeps that
|
|
607
|
+
* from leaving two live sessions, but serializing is not the same as
|
|
608
|
+
* deduplicating: the second call still tears the first mount down and MOUNTS THE
|
|
609
|
+
* GAME AGAIN. For anything with a module-level side effect that is a coin
|
|
610
|
+
* flip — a game registering a named easing curve at import time dies on the
|
|
611
|
+
* second construction with `Custom ease already registered`.
|
|
612
|
+
*
|
|
613
|
+
* The ARCHITECTURE-CORE contract behind problem 17 says it plainly: LOADING
|
|
614
|
+
* CONSTRUCTS, and it constructs once. So the launch is memoized on the store
|
|
615
|
+
* (session identity — a `WeakMap` keeps that from leaking) crossed with the
|
|
616
|
+
* OPEN PROJECT, which is precisely what determines what the launch will do:
|
|
617
|
+
* every route above reads the open project (locally the session's, hosted the
|
|
618
|
+
* one `?project=<id>` resolved) and nothing reads a URL. Both callers await the
|
|
619
|
+
* SAME promise and observe the same outcome, including a rejection.
|
|
620
|
+
*/
|
|
621
|
+
const _autoLaunched = new WeakMap<EditorShellStore, Map<string, Promise<void>>>();
|
|
622
|
+
|
|
623
|
+
function dedupeAutoLaunch(store: EditorShellStore, run: () => Promise<void>): Promise<void> {
|
|
624
|
+
let perStore = _autoLaunched.get(store);
|
|
625
|
+
if (!perStore) {
|
|
626
|
+
perStore = new Map();
|
|
627
|
+
_autoLaunched.set(store, perStore);
|
|
628
|
+
}
|
|
629
|
+
// `?? ''` because the key only has to be STABLE per session: a boot with no
|
|
630
|
+
// project open yet (the local no-project dev server, a harness stub) has
|
|
631
|
+
// exactly one launch to dedupe, so one key is the right answer there rather
|
|
632
|
+
// than a crash on the boot path.
|
|
633
|
+
const key = getCurrentProject()?.rootPath ?? '';
|
|
634
|
+
const existing = perStore.get(key);
|
|
635
|
+
if (existing) return existing;
|
|
636
|
+
const started = run();
|
|
637
|
+
perStore.set(key, started);
|
|
638
|
+
return started;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/** TEST-ONLY: forget this store's auto-launch memo (see {@link dedupeAutoLaunch}). */
|
|
642
|
+
export function resetAutoLaunchDedupeForTests(store: EditorShellStore): void {
|
|
643
|
+
_autoLaunched.delete(store);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/**
|
|
647
|
+
* THE route chain, with no opinion about when it runs. Called at boot by
|
|
648
|
+
* {@link autoLaunchIngest} and at the play edge by
|
|
649
|
+
* `deferred-ingest-play.ts`'s `mountDeferredIngestForPlay` — one mount, two
|
|
650
|
+
* moments, so a deferred root's Play is byte-for-byte the mount that used to
|
|
651
|
+
* happen at boot rather than a second implementation of it.
|
|
652
|
+
*/
|
|
653
|
+
export async function launchIngestRoutes(store: EditorShellStore): Promise<void> {
|
|
654
|
+
const { tryManifestModuleRoute } = await import('./module-mode');
|
|
655
|
+
if (await tryManifestModuleRoute(store)) return;
|
|
656
|
+
// ONE read for all three manifest routes (see `readIngestChainManifest`):
|
|
657
|
+
// they used to fetch it three times and swallow every failure identically.
|
|
658
|
+
const manifest = await readIngestChainManifest();
|
|
659
|
+
if (await tryManifestIngestRoute(store, manifest)) return;
|
|
660
|
+
if (await tryManifestIngestRoute2D(store, manifest)) return;
|
|
661
|
+
await tryManifestIngestRouteReact(store, manifest);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
async function autoLaunchIngestInner(store: EditorShellStore): Promise<void> {
|
|
665
|
+
// Edit already owns pieces (a named world component, or isolation-document
|
|
666
|
+
// scene tabs) — do not construct the live game underneath them. Play
|
|
667
|
+
// mounts it (`deferred-ingest-play.ts`). Land on the default Edit piece.
|
|
668
|
+
const { deferredIngestPlayActive, ingestMountIsDeferredToPlay } = await import(
|
|
669
|
+
'./deferred-ingest-play'
|
|
670
|
+
);
|
|
671
|
+
if (await ingestMountIsDeferredToPlay()) {
|
|
672
|
+
// Story discovery can make the second boot caller arrive after the user
|
|
673
|
+
// has already pressed ▶ and the deferred mount has claimed the session.
|
|
674
|
+
// That late BOOT intent is stale: landing on Edit here used to switch the
|
|
675
|
+
// running game out from under the user, and once cold landing also owned
|
|
676
|
+
// the required hold it paused the game as well. Play is the newer owner;
|
|
677
|
+
// never let an old bootstrap override it.
|
|
678
|
+
if (deferredIngestPlayActive()) return;
|
|
679
|
+
const { landIngestBootInEdit } = await import('./ingest-boot-viewport');
|
|
680
|
+
landIngestBootInEdit(store);
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
await launchIngestRoutes(store);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// Dev hook so the editor UI / e2e can drive a mount against the bound store.
|
|
687
|
+
// The `typeof window` half matters: this module is imported by
|
|
688
|
+
// `command-listener.ts` (S-2 routes relayed play control to the ingest surface),
|
|
689
|
+
// and several of that module's suites run under the Node vitest environment
|
|
690
|
+
// where a bare top-level `window` write is a load-time ReferenceError.
|
|
691
|
+
if (import.meta.env.DEV && typeof window !== 'undefined') {
|
|
692
|
+
(window as unknown as Record<string, unknown>)['__vgaiIngestApi'] = {
|
|
693
|
+
enter: (gameId: string) => {
|
|
694
|
+
if (!_boundStore) throw new Error('ingest roots not bound');
|
|
695
|
+
return mountThreeIngestRootById(_boundStore, gameId);
|
|
696
|
+
},
|
|
697
|
+
exit: unmountThreeIngestRoot,
|
|
698
|
+
// This hook is the per-surface (three) API — keep it slot-specific;
|
|
699
|
+
// `isIngestActive` itself is kind-agnostic as of F26.
|
|
700
|
+
isActive: () => activeIngest()?.kind === 'three',
|
|
701
|
+
};
|
|
702
|
+
// Canvas sibling (T6.1 slice 4) — same shape, over the canvas ingest session.
|
|
703
|
+
(window as unknown as Record<string, unknown>)['__vgaiIngestApi2D'] = {
|
|
704
|
+
enter: (gameId: string) => {
|
|
705
|
+
if (!_boundStore) throw new Error('ingest roots not bound');
|
|
706
|
+
return mountCanvasIngestRootById(_boundStore, gameId);
|
|
707
|
+
},
|
|
708
|
+
exit: unmountCanvasIngestRoot,
|
|
709
|
+
isActive: () => activeIngest()?.kind === 'canvas',
|
|
710
|
+
};
|
|
711
|
+
// DOM sibling (Track N, N2) — same shape, over the native-React ingest
|
|
712
|
+
// session.
|
|
713
|
+
(window as unknown as Record<string, unknown>)['__vgaiIngestApiReact'] = {
|
|
714
|
+
enter: (folderId: string) => {
|
|
715
|
+
if (!_boundStore) throw new Error('ingest roots not bound');
|
|
716
|
+
return mountDomIngestRoot(_boundStore, folderId);
|
|
717
|
+
},
|
|
718
|
+
exit: unmountDomIngestRoot,
|
|
719
|
+
isActive: () => activeIngest()?.kind === 'dom',
|
|
720
|
+
};
|
|
721
|
+
// Headless-readable mount outcomes —
|
|
722
|
+
// `packages/editor/src/authoring/mount-failure-report.ts`'s multi-entry
|
|
723
|
+
// list is otherwise only ever read by the status bar's `mount-failure`
|
|
724
|
+
// contribution (a React component), invisible to `page.evaluate()`. This is
|
|
725
|
+
// a plain function returning the LIVE list — `vgai doctor`'s browser
|
|
726
|
+
// phase is this surface's first consumer, but any other dev/e2e caller can
|
|
727
|
+
// poll it the same way `__vgaiIngestApi*` are already polled.
|
|
728
|
+
(window as unknown as Record<string, unknown>)['__vgaiMountFailureReports'] = () =>
|
|
729
|
+
getMountFailureReports();
|
|
730
|
+
// D-H1 (Slice B): headless-readable per-sibling signal — the live slot's
|
|
731
|
+
// `siblings` (D-V2's composite-sibling handles, `ingest-siblings.ts`'s
|
|
732
|
+
// `SiblingMount[]`) is otherwise invisible to `page.evaluate()`, same blind spot
|
|
733
|
+
// `__vgaiMountFailureReports` closed for mount failures. A plain function
|
|
734
|
+
// returning a LIVE view (never a snapshot, re-reads the slot on every call) of
|
|
735
|
+
// honest per-sibling flags only — no capture/draw proof, see
|
|
736
|
+
// `DoctorSiblingEvidence`'s doc comment (`packages/editor/src/doctor/report.ts`).
|
|
737
|
+
(window as unknown as Record<string, unknown>)['__vgaiSiblingMounts'] = () =>
|
|
738
|
+
(activeIngest()?.siblings ?? []).map((s) => ({
|
|
739
|
+
worldId: s.worldId,
|
|
740
|
+
setPausedPresent: typeof s.setPaused === 'function',
|
|
741
|
+
authoringPresent: !!s.authoring,
|
|
742
|
+
authoringPersist: s.authoring?.capabilities.persist === true,
|
|
743
|
+
}));
|
|
744
|
+
}
|