@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,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE BUILD SESSION — one truth for the three build surfaces this package
|
|
3
|
+
* contributes (the Build Profiles document, the native Output channel and
|
|
4
|
+
* the build-progress status item). Module scope on purpose: the old bottom
|
|
5
|
+
* BUILD tab lost its log whenever the pane unmounted mid-build, so the
|
|
6
|
+
* session outlives any one surface.
|
|
7
|
+
*
|
|
8
|
+
* Builds spawn `npm run <script>` on the editor's dev server, which is the
|
|
9
|
+
* only place a project's own scripts run.
|
|
10
|
+
*
|
|
11
|
+
* `startBuild` reveals the native Build output channel through the host door.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
listConfigurations,
|
|
16
|
+
startBuild as startBuildConfiguration,
|
|
17
|
+
} from '../host/api/configurations';
|
|
18
|
+
import type { EditorHostOutputDiagnostic } from '@volter/editor-sdk/host';
|
|
19
|
+
import { editorHost } from '@volter/editor-sdk/host';
|
|
20
|
+
import type { BuildReport } from '@volter/editor-sdk/session/build-report';
|
|
21
|
+
import { useEffect, useState } from 'react';
|
|
22
|
+
import { formatBytes } from './format-bytes';
|
|
23
|
+
|
|
24
|
+
const BUILD_OUTPUT_ID = 'build';
|
|
25
|
+
|
|
26
|
+
/** The Build Profiles document's contribution id (`build-profiles.document.tsx`),
|
|
27
|
+
* as `workspace.openContributedDocument` names it. */
|
|
28
|
+
export const BUILD_PROFILES_DOCUMENT_ID = 'build-profiles.document';
|
|
29
|
+
|
|
30
|
+
/** Open (or activate) the Build Profiles document — what every build entry
|
|
31
|
+
* point does: the header's Export button, the palette's build actions, the
|
|
32
|
+
* Window menu and the `show-build` verb. */
|
|
33
|
+
export function openBuildProfilesDocument(): boolean {
|
|
34
|
+
return editorHost().workspace.openContributedDocument(BUILD_PROFILES_DOCUMENT_ID);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** A build-role configuration's id (ARCHITECTURE-CORE §The project model). */
|
|
38
|
+
export type BuildTarget = string;
|
|
39
|
+
export type BuildPhase = 'idle' | 'building' | 'done';
|
|
40
|
+
|
|
41
|
+
export interface BuildResult {
|
|
42
|
+
ok: boolean;
|
|
43
|
+
code: number | null;
|
|
44
|
+
report?: BuildReport;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface BuildProfile {
|
|
48
|
+
readonly id: BuildTarget;
|
|
49
|
+
readonly label: string;
|
|
50
|
+
readonly subtitle: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The project's build-role configurations, as profiles. Empty until the
|
|
54
|
+
* manifest declares one; nothing is assumed. */
|
|
55
|
+
export function useBuildProfiles(): readonly BuildProfile[] {
|
|
56
|
+
const [profiles, setProfiles] = useState<readonly BuildProfile[]>([]);
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
let cancelled = false;
|
|
59
|
+
void listConfigurations()
|
|
60
|
+
.then((all) => {
|
|
61
|
+
if (cancelled) return;
|
|
62
|
+
setProfiles(
|
|
63
|
+
all
|
|
64
|
+
.filter((c) => c.role === 'build')
|
|
65
|
+
.map((c) => ({ id: c.id, label: c.id, subtitle: c.describe ?? c.kind })),
|
|
66
|
+
);
|
|
67
|
+
})
|
|
68
|
+
.catch(() => {
|
|
69
|
+
if (!cancelled) setProfiles([]);
|
|
70
|
+
});
|
|
71
|
+
return () => {
|
|
72
|
+
cancelled = true;
|
|
73
|
+
};
|
|
74
|
+
}, []);
|
|
75
|
+
return profiles;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface BuildSession {
|
|
79
|
+
readonly phase: BuildPhase;
|
|
80
|
+
readonly target: BuildTarget;
|
|
81
|
+
readonly log: readonly string[];
|
|
82
|
+
readonly result: BuildResult | null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
let _session: BuildSession = { phase: 'idle', target: '', log: [], result: null };
|
|
86
|
+
let _version = 0;
|
|
87
|
+
const _listeners = new Set<() => void>();
|
|
88
|
+
let _abort: AbortController | null = null;
|
|
89
|
+
|
|
90
|
+
function setSession(patch: Partial<BuildSession>): void {
|
|
91
|
+
_session = { ..._session, ...patch };
|
|
92
|
+
_version++;
|
|
93
|
+
publishBuildOutput();
|
|
94
|
+
for (const fn of _listeners) fn();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Subscribe to build-session changes. Returns unsubscribe. */
|
|
98
|
+
export function subscribeBuildSession(fn: () => void): () => void {
|
|
99
|
+
_listeners.add(fn);
|
|
100
|
+
return () => {
|
|
101
|
+
_listeners.delete(fn);
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Monotonic change counter — the `getSnapshot` for `useSyncExternalStore`. */
|
|
106
|
+
export function buildSessionVersion(): number {
|
|
107
|
+
return _version;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** The current build session (stable reference between changes). */
|
|
111
|
+
export function getBuildSession(): BuildSession {
|
|
112
|
+
return _session;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Back to idle (the `Build Again` reset). No-op mid-build. */
|
|
116
|
+
export function resetBuildSession(): void {
|
|
117
|
+
if (_session.phase === 'building') return;
|
|
118
|
+
setSession({ phase: 'idle', log: [], result: null });
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Abort a running build (the old panel's Cancel, unchanged semantics). */
|
|
122
|
+
export function cancelBuild(): void {
|
|
123
|
+
_abort?.abort();
|
|
124
|
+
setSession({ phase: 'idle' });
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** The build log is an Output channel; failures with source locations also reach Problems. */
|
|
128
|
+
export function showBuildOutput(): void {
|
|
129
|
+
publishBuildOutput();
|
|
130
|
+
editorHost().output.show(BUILD_OUTPUT_ID);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function publishBuildOutput(): void {
|
|
134
|
+
const session = getBuildSession();
|
|
135
|
+
let text = session.log.length ? `${session.log.join('\n')}\n` : '';
|
|
136
|
+
if (session.phase === 'done' && session.result) {
|
|
137
|
+
text += `\n${session.result.ok ? 'Build complete.' : `Build failed (exit code: ${session.result.code ?? 'unknown'}).`}\n`;
|
|
138
|
+
if (session.result.report) {
|
|
139
|
+
const report = session.result.report;
|
|
140
|
+
text += `${formatBytes(report.artifactBytes)} package · ${formatBytes(report.outputBytes)} output · ${report.fileCount} files\n`;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
editorHost().output.write(BUILD_OUTPUT_ID, 'Build', text, buildDiagnostics(text));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Only explicit compiler locations become markers. Unlocated failures stay in Output. */
|
|
147
|
+
export function buildDiagnostics(text: string): EditorHostOutputDiagnostic[] {
|
|
148
|
+
const diagnostics: EditorHostOutputDiagnostic[] = [];
|
|
149
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: compiler logs contain ANSI SGR escapes.
|
|
150
|
+
const plain = text.replace(/\u001b\[[0-9;]*m/g, '');
|
|
151
|
+
for (const line of plain.split(/\r?\n/)) {
|
|
152
|
+
// TypeScript pretty and plain forms; esbuild's explicit file:line:column errors.
|
|
153
|
+
const match =
|
|
154
|
+
line.match(/^(.+?)\((\d+),(\d+)\):\s*(error|warning)\s+(.*)$/) ??
|
|
155
|
+
line.match(/^(.+?):(\d+):(\d+)\s*[-:]?\s*(error|warning)\s*:?[ ]*(.*)$/i);
|
|
156
|
+
if (!match) continue;
|
|
157
|
+
const path = match[1]!.trim();
|
|
158
|
+
if (path.startsWith('/') || path.split(/[\\/]/).includes('..')) continue;
|
|
159
|
+
diagnostics.push({
|
|
160
|
+
path,
|
|
161
|
+
line: Math.max(1, Number(match[2])),
|
|
162
|
+
column: Math.max(1, Number(match[3])),
|
|
163
|
+
severity: match[4]!.toLowerCase() === 'warning' ? 'warning' : 'error',
|
|
164
|
+
message: match[5]!,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
return diagnostics;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Run a build — the `startExport` SSE stream, consumed at module scope so the
|
|
172
|
+
* session outlives any one surface. Reveals the Build Output tab so the log
|
|
173
|
+
* has somewhere visible to stream.
|
|
174
|
+
*/
|
|
175
|
+
export async function startBuild(target: BuildTarget): Promise<void> {
|
|
176
|
+
if (_session.phase === 'building') return;
|
|
177
|
+
setSession({ phase: 'building', target, log: [], result: null });
|
|
178
|
+
showBuildOutput();
|
|
179
|
+
|
|
180
|
+
const controller = new AbortController();
|
|
181
|
+
_abort = controller;
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
const res = await startBuildConfiguration(target, controller.signal);
|
|
185
|
+
|
|
186
|
+
if (!res.ok) {
|
|
187
|
+
const err = (await res.json().catch(() => ({ error: 'Build request failed' }))) as {
|
|
188
|
+
error?: string;
|
|
189
|
+
};
|
|
190
|
+
setSession({
|
|
191
|
+
phase: 'done',
|
|
192
|
+
log: [..._session.log, `Error: ${err.error ?? res.statusText}`],
|
|
193
|
+
result: { ok: false, code: null },
|
|
194
|
+
});
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const reader = res.body!.getReader();
|
|
199
|
+
const decoder = new TextDecoder();
|
|
200
|
+
let buffer = '';
|
|
201
|
+
|
|
202
|
+
while (true) {
|
|
203
|
+
const { done, value } = await reader.read();
|
|
204
|
+
if (done) break;
|
|
205
|
+
buffer += decoder.decode(value, { stream: true });
|
|
206
|
+
|
|
207
|
+
const parts = buffer.split('\n\n');
|
|
208
|
+
buffer = parts.pop()!;
|
|
209
|
+
|
|
210
|
+
for (const part of parts) {
|
|
211
|
+
if (part.startsWith(':')) continue;
|
|
212
|
+
const lines = part.split('\n');
|
|
213
|
+
let event = '';
|
|
214
|
+
let data = '';
|
|
215
|
+
for (const line of lines) {
|
|
216
|
+
if (line.startsWith('event: ')) event = line.slice(7);
|
|
217
|
+
else if (line.startsWith('data: ')) data = line.slice(6);
|
|
218
|
+
}
|
|
219
|
+
if (event === 'log') {
|
|
220
|
+
let lineText: string;
|
|
221
|
+
try {
|
|
222
|
+
lineText = JSON.parse(data) as string;
|
|
223
|
+
} catch {
|
|
224
|
+
lineText = data;
|
|
225
|
+
}
|
|
226
|
+
setSession({ log: [..._session.log, lineText] });
|
|
227
|
+
} else if (event === 'done') {
|
|
228
|
+
let result: BuildResult;
|
|
229
|
+
try {
|
|
230
|
+
result = JSON.parse(data) as BuildResult;
|
|
231
|
+
} catch {
|
|
232
|
+
result = { ok: false, code: null };
|
|
233
|
+
}
|
|
234
|
+
setSession({ phase: 'done', result });
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
} catch (err: unknown) {
|
|
239
|
+
if ((err as Error).name !== 'AbortError') {
|
|
240
|
+
setSession({
|
|
241
|
+
phase: 'done',
|
|
242
|
+
log: [..._session.log, `Error: ${(err as Error).message}`],
|
|
243
|
+
result: { ok: false, code: null },
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Byte sizes as the build surfaces print them (B / KB / MB / GB, two
|
|
2
|
+
* significant places under 10). Shared by the output log's result line and
|
|
3
|
+
* the Build Profiles document's report summary. */
|
|
4
|
+
export function formatBytes(bytes: number): string {
|
|
5
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
6
|
+
const units = ['KB', 'MB', 'GB'];
|
|
7
|
+
let value = bytes / 1024;
|
|
8
|
+
let unit = units[0]!;
|
|
9
|
+
for (let index = 1; index < units.length && value >= 1024; index++) {
|
|
10
|
+
value /= 1024;
|
|
11
|
+
unit = units[index]!;
|
|
12
|
+
}
|
|
13
|
+
return `${value >= 10 ? value.toFixed(1) : value.toFixed(2)} ${unit}`;
|
|
14
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two result builders the game skew's command verbs answer with — copied
|
|
3
|
+
* out of the editor's `command-listener.ts` with the verbs that used them
|
|
4
|
+
* (WORK.md §The workbench), because they are result SHAPES, not host API: a
|
|
5
|
+
* few lines of object literal each, owned by whoever produces the answer.
|
|
6
|
+
*
|
|
7
|
+
* They live in `src/` rather than inside one contribution module because
|
|
8
|
+
* three of this package's command contributions (`gameplay`, `bridge`,
|
|
9
|
+
* `game-eval`) answer with the same two shapes, and a third copy of a
|
|
10
|
+
* structured-error mapping is how `data.code` drifts between verbs the SDK
|
|
11
|
+
* matches on by code.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { EditorCommandResult } from '@volter/editor-sdk/commands';
|
|
15
|
+
|
|
16
|
+
/** The genuinely-no-live-world answer: a structured failure, never a
|
|
17
|
+
* fabricated success. */
|
|
18
|
+
export const notPlayingResult = (): EditorCommandResult => ({
|
|
19
|
+
ok: false,
|
|
20
|
+
error: 'not in play mode — start play before using the debug seam',
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
/** Surface a thrown engine error carrying a machine-readable `code`
|
|
24
|
+
* (`DebugError`, `InputActionError` — duck-typed, so no engine-module
|
|
25
|
+
* identity coupling) as a structured relay failure: `data.code` plus the
|
|
26
|
+
* error's own data (registered-name lists, zod issues). The SDK maps these
|
|
27
|
+
* to operation error codes — never by parsing message prose. */
|
|
28
|
+
export function structuredErrorResult(err: unknown): EditorCommandResult {
|
|
29
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
30
|
+
const e = err as { code?: unknown; data?: unknown };
|
|
31
|
+
if (typeof e?.code === 'string') {
|
|
32
|
+
const extra = typeof e.data === 'object' && e.data !== null ? e.data : {};
|
|
33
|
+
return { ok: false, error: message, data: { ...extra, code: e.code } };
|
|
34
|
+
}
|
|
35
|
+
return { ok: false, error: message };
|
|
36
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project-level authoring-surface coverage: the Edit documents and Content
|
|
3
|
+
* pieces the resolved adapter table actually presents. Runtime capture alone
|
|
4
|
+
* cannot answer this; a game may mount perfectly while exposing no authoring
|
|
5
|
+
* surface at all.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
type CapabilityCoverageReport,
|
|
10
|
+
deriveCapabilityCoverage,
|
|
11
|
+
} from '../host/coverage/capability-coverage';
|
|
12
|
+
import { projectAdapterFacet } from '@volter/editor-core/project-adapter';
|
|
13
|
+
import { getCurrentProject } from '@volter/editor-core/project-manager';
|
|
14
|
+
import { authoringSurfaceFromTable } from '@volter/editor-core/scene-document-plan';
|
|
15
|
+
import { projectStoriesReady } from '@volter/editor-core/stories/story-registry';
|
|
16
|
+
import { availableWorkspaceDocuments } from '@volter/editor-core/workspace-available-documents';
|
|
17
|
+
import { openWorkspaceDocuments } from '@volter/editor-core/workspace-document-registry';
|
|
18
|
+
|
|
19
|
+
export function authoringSurfaceCoverage(): CapabilityCoverageReport | null {
|
|
20
|
+
const facet = projectAdapterFacet();
|
|
21
|
+
if (!facet || !projectStoriesReady()) return null;
|
|
22
|
+
return deriveCapabilityCoverage({
|
|
23
|
+
worldId: getCurrentProject()?.config.name ?? 'project',
|
|
24
|
+
authoringSurface: authoringSurfaceFromTable(
|
|
25
|
+
facet.scenes,
|
|
26
|
+
openWorkspaceDocuments().map((document) => document.descriptor.id),
|
|
27
|
+
availableWorkspaceDocuments().map((document) => document.descriptor.id),
|
|
28
|
+
),
|
|
29
|
+
});
|
|
30
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE `project.*` COVERAGE OF THE OPEN PROJECT — the live half of
|
|
3
|
+
* `coverage/project-verb-coverage.ts`.
|
|
4
|
+
*
|
|
5
|
+
* It reads the session's own singletons plus two project files the browser
|
|
6
|
+
* cannot read for itself, and hands the facts to the pure derivation. The
|
|
7
|
+
* report goes out the doors coverage already uses — `vgai status`'s
|
|
8
|
+
* `projectCoverage` facet, and the editor console via
|
|
9
|
+
* `coverage/session-coverage.ts`, which unions this family with the other two
|
|
10
|
+
* so one headline counts them all. No new transport, no new panel: the console
|
|
11
|
+
* sync forwards every warning to the server's unresolved ledger, which is what
|
|
12
|
+
* makes an absent verb a STANDING condition rather than a line somebody
|
|
13
|
+
* scrolled past.
|
|
14
|
+
*
|
|
15
|
+
* ## Why this family does NOT stand down for an ingested mount
|
|
16
|
+
*
|
|
17
|
+
* `coverage/native-system-coverage.ts` steps aside for an ingest because that
|
|
18
|
+
* lane has a STRONGER measurement of the same slots (the game's declared
|
|
19
|
+
* carrier). Nothing of the sort exists here: "can this project build" is a
|
|
20
|
+
* question about the opened PROJECT, and a vendored game opened as a
|
|
21
|
+
* project has it just as much as a scaffolded one. One producer, both lanes —
|
|
22
|
+
* which is also why every other report passes `projectVerbs: []`.
|
|
23
|
+
*
|
|
24
|
+
* ## Asking is part of the job
|
|
25
|
+
*
|
|
26
|
+
* The file-facts source is cached-on-first-ask and asked by nothing else, so
|
|
27
|
+
* this module KICKS OFF the read rather than reporting `unanswered` forever —
|
|
28
|
+
* a row whose whole point is "nobody looked" must not be the reason nobody
|
|
29
|
+
* looks.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import {
|
|
33
|
+
type CapabilityCoverageReport,
|
|
34
|
+
deriveCapabilityCoverage,
|
|
35
|
+
} from '../host/coverage/capability-coverage';
|
|
36
|
+
import {
|
|
37
|
+
measureProjectVerbs,
|
|
38
|
+
type ProjectRootFact,
|
|
39
|
+
type ProjectVerbFacts,
|
|
40
|
+
} from '../host/coverage/project-verb-coverage';
|
|
41
|
+
import { editorServerJson } from '@volter/editor-core/editor-server-response';
|
|
42
|
+
import { getCurrentProject } from '@volter/editor-core/project-manager';
|
|
43
|
+
|
|
44
|
+
/** What the server answers on `/__editor/project-verbs` — facts only. */
|
|
45
|
+
interface ProjectFileFacts {
|
|
46
|
+
readonly scripts: readonly string[] | null;
|
|
47
|
+
readonly hasPackageJson: boolean;
|
|
48
|
+
readonly dependencies?: readonly string[] | null;
|
|
49
|
+
readonly roots: readonly ProjectRootFact[] | null;
|
|
50
|
+
readonly unavailable: string | null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Unread until the server answers, and unread is NOT "there is none". */
|
|
54
|
+
let _files: ProjectFileFacts | null = null;
|
|
55
|
+
let _request: Promise<ProjectFileFacts> | null = null;
|
|
56
|
+
/** Which project `_files` describes, so a project switch re-asks instead of
|
|
57
|
+
* reporting the previous project's scripts against the new one. */
|
|
58
|
+
let _filesFor: string | null = null;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Drop the cached file facts so the next read re-asks the dev server. Called
|
|
62
|
+
* on `vgai restart` (a restart is precisely "my project's files changed —
|
|
63
|
+
* re-read them"; the probe-measured failure was the dependency detector
|
|
64
|
+
* grading rows against a package.json edit two restarts old) and by tests.
|
|
65
|
+
*/
|
|
66
|
+
export function invalidateProjectFileFacts(): void {
|
|
67
|
+
_files = null;
|
|
68
|
+
_request = null;
|
|
69
|
+
_filesFor = null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Ask the dev server for the project's own files, once per open project.
|
|
74
|
+
*
|
|
75
|
+
* A transport failure is NOT an absence: it lands as `scripts: null` with the
|
|
76
|
+
* reason, which the rules report as unmeasured. There is exactly one safe
|
|
77
|
+
* direction for this question to fail in.
|
|
78
|
+
*/
|
|
79
|
+
async function loadProjectFileFacts(
|
|
80
|
+
rootPath: string,
|
|
81
|
+
fetchImpl: typeof fetch = fetch,
|
|
82
|
+
): Promise<ProjectFileFacts> {
|
|
83
|
+
if (_files && _filesFor === rootPath) return _files;
|
|
84
|
+
if (_request && _filesFor === rootPath) return _request;
|
|
85
|
+
_filesFor = rootPath;
|
|
86
|
+
_files = null;
|
|
87
|
+
_request = (async () => {
|
|
88
|
+
let answer: ProjectFileFacts;
|
|
89
|
+
try {
|
|
90
|
+
const res = await fetchImpl('/__editor/project-verbs');
|
|
91
|
+
answer = await editorServerJson<ProjectFileFacts>(res, 'project-verbs did not answer');
|
|
92
|
+
} catch (error) {
|
|
93
|
+
answer = {
|
|
94
|
+
scripts: null,
|
|
95
|
+
hasPackageJson: false,
|
|
96
|
+
dependencies: null,
|
|
97
|
+
roots: null,
|
|
98
|
+
unavailable: `the editor could not reach its dev server to read this project's own files (${
|
|
99
|
+
error instanceof Error ? error.message : String(error)
|
|
100
|
+
})`,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
_files = answer;
|
|
104
|
+
return answer;
|
|
105
|
+
})();
|
|
106
|
+
return _request;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* The open project's dependency NAMES, off the SAME single fetch the verb rows
|
|
111
|
+
* use — the `system.*` family's detector input.
|
|
112
|
+
*
|
|
113
|
+
* `null` is "not answered yet / not answerable" (an unlanded fetch, an
|
|
114
|
+
* unreadable package.json), and every caller must treat it as such:
|
|
115
|
+
* an absent library list is not evidence that a library is absent, and reading
|
|
116
|
+
* it as one would fabricate the very reassurance the detector exists to
|
|
117
|
+
* withhold. Deliberately does NOT kick off the fetch itself —
|
|
118
|
+
* {@link projectVerbFacts} owns that, so there is one request and one cache.
|
|
119
|
+
*/
|
|
120
|
+
export function projectDependencyNames(): readonly string[] | null {
|
|
121
|
+
const project = getCurrentProject();
|
|
122
|
+
if (!project) return null;
|
|
123
|
+
const files = _filesFor === project.rootPath ? _files : null;
|
|
124
|
+
return files?.dependencies ?? null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Everything the rules read, assembled from the live session — and the reads
|
|
129
|
+
* kicked off where they have not happened yet.
|
|
130
|
+
*
|
|
131
|
+
* `null` when there is no open project at all: a verb report about no project
|
|
132
|
+
* would be three fabricated gaps, which is worse than no rows.
|
|
133
|
+
*/
|
|
134
|
+
export function projectVerbFacts(): ProjectVerbFacts | null {
|
|
135
|
+
const project = getCurrentProject();
|
|
136
|
+
if (!project) return null;
|
|
137
|
+
void loadProjectFileFacts(project.rootPath);
|
|
138
|
+
const files = _filesFor === project.rootPath ? _files : null;
|
|
139
|
+
return {
|
|
140
|
+
scripts: files?.scripts ?? null,
|
|
141
|
+
hasPackageJson: files?.hasPackageJson ?? false,
|
|
142
|
+
roots: files?.roots ?? null,
|
|
143
|
+
unavailable:
|
|
144
|
+
files?.unavailable ??
|
|
145
|
+
(files ? null : 'the editor has not finished reading this project’s own files yet'),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* The `project.*` report for the open project, re-derived on every read — the
|
|
151
|
+
* scripts a project declares change while it is open (`vgai add` writes them),
|
|
152
|
+
* so a cached answer would start lying.
|
|
153
|
+
*/
|
|
154
|
+
export function projectVerbCoverage(): CapabilityCoverageReport | null {
|
|
155
|
+
const facts = projectVerbFacts();
|
|
156
|
+
if (!facts) return null;
|
|
157
|
+
const project = getCurrentProject();
|
|
158
|
+
return deriveCapabilityCoverage({
|
|
159
|
+
worldId: project?.config.name ?? 'project',
|
|
160
|
+
projectVerbs: measureProjectVerbs(facts),
|
|
161
|
+
});
|
|
162
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE `SystemAdapters` COVERAGE OF A NATIVE MOUNT — the live half of
|
|
3
|
+
* `coverage/system-adapter-coverage.ts`.
|
|
4
|
+
*
|
|
5
|
+
* It reads the session's own singletons and hands the facts to the pure
|
|
6
|
+
* derivation. The report goes out the two doors coverage already uses —
|
|
7
|
+
* `vgai status`'s `systemCoverage` facet, and the editor console via
|
|
8
|
+
* `coverage/session-coverage.ts`, which unions this family with the others so
|
|
9
|
+
* one headline counts them all — so an unbound slot becomes a STANDING
|
|
10
|
+
* condition rather than a one-off line. No new transport, no new panel, and no
|
|
11
|
+
* dismiss: the console sync forwards every warning to the server's unresolved
|
|
12
|
+
* ledger, which is what makes it persistent.
|
|
13
|
+
*
|
|
14
|
+
* ## Why this is a separate report from `coverage/root-coverage.ts`
|
|
15
|
+
*
|
|
16
|
+
* A `SystemAdapters` slot is GAME-scoped, not root-scoped:
|
|
17
|
+
* `runtime/game.ts`'s `computeSystemAdapters` merges every mounted root's bag
|
|
18
|
+
* into ONE registry, and `authoring/active-systems.ts` is the single object the
|
|
19
|
+
* editor's panels call. Deriving it per root would print the same six rows once
|
|
20
|
+
* per root and invent a per-root ownership the engine does not have.
|
|
21
|
+
*
|
|
22
|
+
* ## Why it stands down for an ingested mount
|
|
23
|
+
*
|
|
24
|
+
* `ingest/mount-coverage.ts` already measures the same slots for an ingest,
|
|
25
|
+
* against the game's DECLARED carrier — strictly stronger evidence, because a
|
|
26
|
+
* declaration can distinguish "I have no physics" from "nobody looked". Where
|
|
27
|
+
* that measurement exists it is the one that runs; this module is the answer
|
|
28
|
+
* for the mounts that have no contract to read.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { getActiveSystems } from '@volter/editor-core/authoring/active-systems';
|
|
32
|
+
import { mountedRootSubjects } from '@volter/editor-core/authoring/mounted-root-subjects';
|
|
33
|
+
import {
|
|
34
|
+
type CapabilityCoverageReport,
|
|
35
|
+
deriveCapabilityCoverage,
|
|
36
|
+
} from '../host/coverage/capability-coverage';
|
|
37
|
+
import { measureNativeSystemAdapters } from '../host/coverage/system-adapter-coverage';
|
|
38
|
+
import { liveCoverage } from '@volter/editor-core/live-session-registry';
|
|
39
|
+
import { getCurrentProject } from '@volter/editor-core/project-manager';
|
|
40
|
+
import { toolContributionPlay } from '@volter/editor-core/tool-contribution-play';
|
|
41
|
+
import type { DeclaredSystemAbsence, Game } from '@volter/game-runtime/runtime/game';
|
|
42
|
+
import { projectDependencyNames } from './live-project-verbs';
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* What the mounted roots' `systems` tables declared ABSENT, read off the live
|
|
46
|
+
* Game — the DETECT-OR-DECLARE input that turns a derived excuse into the
|
|
47
|
+
* game's own answer.
|
|
48
|
+
*
|
|
49
|
+
* Reached through `tool-contribution-play.ts` rather than `play-mode.ts` on
|
|
50
|
+
* purpose: importing play-mode here would drag the whole runtime boot chain
|
|
51
|
+
* into every consumer of this module (its own header states the rule).
|
|
52
|
+
*
|
|
53
|
+
* While a Game is live its declarations are read directly. While stopped, the
|
|
54
|
+
* last mount's reading is retained per project and labeled as such — the
|
|
55
|
+
* declaration is a static fact about the entry's `systems` table, and
|
|
56
|
+
* forgetting it at stop demoted the game's own answer to a host inference.
|
|
57
|
+
* `[]` only when this project has never mounted in this session.
|
|
58
|
+
*/
|
|
59
|
+
function declaredAbsences(): {
|
|
60
|
+
absences: readonly DeclaredSystemAbsence[];
|
|
61
|
+
fromLastMount: boolean;
|
|
62
|
+
} {
|
|
63
|
+
const game = toolContributionPlay()?.game as Game | undefined;
|
|
64
|
+
const projectPath = getCurrentProject()?.rootPath ?? null;
|
|
65
|
+
if (game) {
|
|
66
|
+
if (projectPath !== null) {
|
|
67
|
+
lastMountAbsences = { projectPath, absences: game.declaredSystemAbsences ?? [] };
|
|
68
|
+
}
|
|
69
|
+
return { absences: game.declaredSystemAbsences ?? [], fromLastMount: false };
|
|
70
|
+
}
|
|
71
|
+
// Stopped. The declaration is a static fact about the entry's `systems`
|
|
72
|
+
// table, so the LAST mount's reading remains the game's own answer — the
|
|
73
|
+
// probe-measured failure was this very row demoting to "the HOST's
|
|
74
|
+
// observation" the moment play stopped, silently understating the game's
|
|
75
|
+
// declared answer as an inference. Retained per project, labeled as the
|
|
76
|
+
// last-mount reading by the derivation.
|
|
77
|
+
if (lastMountAbsences !== null && lastMountAbsences.projectPath === projectPath) {
|
|
78
|
+
return { absences: lastMountAbsences.absences, fromLastMount: true };
|
|
79
|
+
}
|
|
80
|
+
return { absences: [], fromLastMount: false };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** The last play mount's declared absences, per project — see above. */
|
|
84
|
+
let lastMountAbsences: {
|
|
85
|
+
projectPath: string;
|
|
86
|
+
absences: readonly DeclaredSystemAbsence[];
|
|
87
|
+
} | null = null;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* The native system-adapter report, or `null` when this session is not the one
|
|
91
|
+
* being described: an ingested mount (whose own measurement is better) or a
|
|
92
|
+
* session with no open project (nothing to read a `server` block from, and
|
|
93
|
+
* guessing would be the fabrication the anti-shim rule forbids).
|
|
94
|
+
*
|
|
95
|
+
* Re-derived on every read. The registry gains and loses adapters across a play
|
|
96
|
+
* enter/exit, so a cached answer would start lying.
|
|
97
|
+
*/
|
|
98
|
+
export function nativeSystemCoverage(): CapabilityCoverageReport | null {
|
|
99
|
+
// A lane grading its own contracts (an ingested game) owns this family.
|
|
100
|
+
if (liveCoverage() !== null) return null;
|
|
101
|
+
const project = getCurrentProject();
|
|
102
|
+
if (!project) return null;
|
|
103
|
+
const mounted = mountedRootSubjects();
|
|
104
|
+
// Nothing mounted ⇒ the registry has not been ASKED, and "a probe that has
|
|
105
|
+
// not run yet reports info, never gap" (the derivation's own rule). The
|
|
106
|
+
// measured violation: a pre-mount session emitted "7 of 7 system seams
|
|
107
|
+
// MISSING" into the server's unresolved ledger — seven gaps against a mount
|
|
108
|
+
// that did not exist — and the entry stood beside the real post-mount report
|
|
109
|
+
// forever. An unasked question is not a family with something to say.
|
|
110
|
+
if (mounted.length === 0) return null;
|
|
111
|
+
// An isolated foreign document is a real AUTHORING mount, so root coverage
|
|
112
|
+
// must grade its editor providers, but it is not a native engine Game and
|
|
113
|
+
// owns no game-scoped SystemAdapters registry. Treating that document as a
|
|
114
|
+
// native mount fabricated a `system.debug` failure the moment an adapter-
|
|
115
|
+
// owned scene was opened. Stand down only when every mounted subject says it
|
|
116
|
+
// is foreign; a mixed project with any native root still has a real native
|
|
117
|
+
// game registry to measure.
|
|
118
|
+
if (mounted.every((subject) => subject.adapter.provenance?.source === 'foreign')) return null;
|
|
119
|
+
const declared = declaredAbsences();
|
|
120
|
+
return deriveCapabilityCoverage({
|
|
121
|
+
worldId: project.config.name,
|
|
122
|
+
systemAdapters: measureNativeSystemAdapters(getActiveSystems(), {
|
|
123
|
+
// A `process` configuration WITH A PORT — the same rule
|
|
124
|
+
// `edit-mode-networking.ts`'s `readServerConfig` already used, and the
|
|
125
|
+
// one this line was missing. `process` is the general "a process the
|
|
126
|
+
// project runs beside the host" kind, not a synonym for "Colyseus
|
|
127
|
+
// server": the `arena` example declares two port-less ones that choose
|
|
128
|
+
// a camera, and grading on `kind` alone reported a missing
|
|
129
|
+
// NetworkingAdapter for a single-player game, telling its author to
|
|
130
|
+
// register one because "this project declares a `server` block" when it
|
|
131
|
+
// declares nothing of the sort. A matchmaker is a listener; no port, no
|
|
132
|
+
// server. (`vgai edit`'s auto-boot had the identical bug — see
|
|
133
|
+
// `packages/vgai-cli/src/colyseus-autoboot.ts`'s `readDeclaredProcess`.)
|
|
134
|
+
declaresServer: (project.config.configurations ?? []).some(
|
|
135
|
+
(c) => c.kind === 'process' && typeof (c as { port?: unknown }).port === 'number',
|
|
136
|
+
),
|
|
137
|
+
mounted: true,
|
|
138
|
+
declaredAbsences: declared.absences,
|
|
139
|
+
absencesFromLastMount: declared.fromLastMount,
|
|
140
|
+
dependencies: projectDependencyNames(),
|
|
141
|
+
}),
|
|
142
|
+
});
|
|
143
|
+
}
|