@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.
Files changed (302) hide show
  1. package/LICENSE +661 -0
  2. package/NOTICE +23 -0
  3. package/contributions/asset-budget-asset.menu.ts +26 -0
  4. package/contributions/asset-budget.action.ts +18 -0
  5. package/contributions/asset-budget.document.tsx +21 -0
  6. package/contributions/asset-budget.menu.ts +22 -0
  7. package/contributions/audio-unlock.service.ts +17 -0
  8. package/contributions/audio.utility.tsx +15 -0
  9. package/contributions/autoplay.service.ts +48 -0
  10. package/contributions/bridge.command.ts +172 -0
  11. package/contributions/build-profiles.document.tsx +21 -0
  12. package/contributions/build-progress.status.tsx +45 -0
  13. package/contributions/build.action.ts +26 -0
  14. package/contributions/build.command.ts +27 -0
  15. package/contributions/build.header.tsx +37 -0
  16. package/contributions/build.menu.ts +31 -0
  17. package/contributions/build.service.ts +31 -0
  18. package/contributions/connection.status.tsx +43 -0
  19. package/contributions/coverage.service.ts +118 -0
  20. package/contributions/edit-mode-audio.service.ts +23 -0
  21. package/contributions/edit-mode-networking.service.ts +30 -0
  22. package/contributions/game-document.service.ts +25 -0
  23. package/contributions/game-eval.command.ts +210 -0
  24. package/contributions/game.layout.ts +19 -0
  25. package/contributions/gameplay.command.ts +255 -0
  26. package/contributions/generation.service.ts +96 -0
  27. package/contributions/generations.status.tsx +53 -0
  28. package/contributions/ingest.service.ts +80 -0
  29. package/contributions/instances.command.ts +75 -0
  30. package/contributions/navmesh.menu.ts +39 -0
  31. package/contributions/navmesh.service.ts +19 -0
  32. package/contributions/network.utility.tsx +17 -0
  33. package/contributions/play.command.ts +355 -0
  34. package/contributions/profiler.action.ts +16 -0
  35. package/contributions/profiler.menu.ts +22 -0
  36. package/contributions/profiler.utility.tsx +17 -0
  37. package/contributions/react/component-board.service.ts +21 -0
  38. package/contributions/react/design-time-mount.service.ts +60 -0
  39. package/contributions/react/pasteboard.action.ts +41 -0
  40. package/contributions/react/react-inspector.service.ts +85 -0
  41. package/contributions/react/story-documents.service.ts +44 -0
  42. package/contributions/scene-document.service.ts +32 -0
  43. package/contributions/state-watch.action.ts +17 -0
  44. package/contributions/state-watch.menu.ts +23 -0
  45. package/contributions/state-watch.utility.tsx +20 -0
  46. package/contributions/team-playtest.service.ts +124 -0
  47. package/contributions/three/camera-runtime.inspector.tsx +34 -0
  48. package/contributions/three/component-board.service.ts +24 -0
  49. package/contributions/three/component-verbs.command.ts +110 -0
  50. package/contributions/three/component-verbs.service.ts +92 -0
  51. package/contributions/three/constraints.inspector.tsx +34 -0
  52. package/contributions/three/model-asset-sections.service.ts +26 -0
  53. package/contributions/three/reflection-probe-capture.inspector.tsx +32 -0
  54. package/contributions/three/story-documents.service.ts +31 -0
  55. package/contributions/three/three-authoring.service.ts +66 -0
  56. package/contributions/transport.header.tsx +19 -0
  57. package/contributions/xstate-behavior.action.ts +42 -0
  58. package/contributions/xstate-behavior.document.tsx +73 -0
  59. package/contributions/xstate-behavior.inspector.tsx +28 -0
  60. package/contributions/xstate-behavior.menu.ts +23 -0
  61. package/package.json +144 -0
  62. package/src/asset-budget/AssetBudgetPanel.tsx +1172 -0
  63. package/src/asset-budget/asset-budget-model.ts +799 -0
  64. package/src/asset-budget/basis-encoder.ts +165 -0
  65. package/src/asset-budget/gltf-io.ts +154 -0
  66. package/src/asset-budget/gltf-optimize.ts +384 -0
  67. package/src/asset-budget/image-dims.ts +78 -0
  68. package/src/asset-budget/optimize-apply.ts +221 -0
  69. package/src/audio/AudioDebuggerPanel.tsx +457 -0
  70. package/src/audio/audio-debugger-model.ts +87 -0
  71. package/src/bridge/call.ts +60 -0
  72. package/src/bridge/dispatch.ts +459 -0
  73. package/src/bridge/live-frames.ts +25 -0
  74. package/src/bridge/screenshot.ts +316 -0
  75. package/src/build/BuildProfilesPanel.tsx +476 -0
  76. package/src/build/build-session.ts +247 -0
  77. package/src/build/format-bytes.ts +14 -0
  78. package/src/command-results.ts +36 -0
  79. package/src/coverage/live-authoring-surface.ts +30 -0
  80. package/src/coverage/live-project-verbs.ts +162 -0
  81. package/src/coverage/native-system-coverage.ts +143 -0
  82. package/src/coverage/root-coverage.ts +79 -0
  83. package/src/coverage/session-coverage.ts +193 -0
  84. package/src/design-system-stories/ApplicationChrome.stories.tsx +100 -0
  85. package/src/design-system-stories/InspectorNarrowBodies.stories.tsx +406 -0
  86. package/src/edit-mode/edit-mode-audio.ts +56 -0
  87. package/src/edit-mode/edit-mode-networking.ts +112 -0
  88. package/src/game-document/DevicePresetPicker.tsx +84 -0
  89. package/src/game-document/GameCaptureFrameButton.tsx +45 -0
  90. package/src/game-document/GameDocument.tsx +215 -0
  91. package/src/game-document/GamePanel.tsx +662 -0
  92. package/src/game-document/InstanceInspectorPicker.tsx +183 -0
  93. package/src/game-document/crowd-debug.ts +183 -0
  94. package/src/game-document/device-preview.ts +336 -0
  95. package/src/game-document/game-view-store.ts +107 -0
  96. package/src/game-document/physics-debug.ts +187 -0
  97. package/src/generation/GenerationActivity.tsx +421 -0
  98. package/src/generation/GenerationGallery.css +231 -0
  99. package/src/generation/generation-documents.tsx +338 -0
  100. package/src/generation/generation-jobs.ts +128 -0
  101. package/src/generation/generation-presentation.ts +257 -0
  102. package/src/host/adapter-reach.ts +445 -0
  103. package/src/host/adapter-runtime-bindings.ts +303 -0
  104. package/src/host/after-paint.ts +112 -0
  105. package/src/host/api/configurations.ts +66 -0
  106. package/src/host/authoring/babylon-authoring-adapter.ts +703 -0
  107. package/src/host/authoring/canvas-runtime-recognition.ts +50 -0
  108. package/src/host/authoring/contract-hierarchy-authoring.ts +144 -0
  109. package/src/host/authoring/contract-scenes-stories.ts +204 -0
  110. package/src/host/authoring/creation-site-related.ts +55 -0
  111. package/src/host/authoring/ephemeral-persistence.ts +29 -0
  112. package/src/host/authoring/gesture-persist.ts +84 -0
  113. package/src/host/authoring/ingest-data-writer.ts +232 -0
  114. package/src/host/authoring/ingest-source-persistence.ts +826 -0
  115. package/src/host/authoring/mount-isolated-pixi-screen.ts +250 -0
  116. package/src/host/authoring/mounted-authoring.ts +41 -0
  117. package/src/host/authoring/owned-pixi-ticker-listeners.ts +96 -0
  118. package/src/host/authoring/phaser-live-authoring-adapter.ts +265 -0
  119. package/src/host/authoring/pixi-authoring-adapter.ts +1554 -0
  120. package/src/host/authoring/pixi-creation-site-write-target.ts +60 -0
  121. package/src/host/authoring/pixi-isolation-assets.ts +25 -0
  122. package/src/host/authoring/pixi-live-write-target.ts +979 -0
  123. package/src/host/authoring/pixi-source-identity.ts +141 -0
  124. package/src/host/authoring/pixi-still-presentation.ts +71 -0
  125. package/src/host/authoring/pixi-structure-history.ts +237 -0
  126. package/src/host/authoring/pixi-transform-channels.ts +205 -0
  127. package/src/host/authoring/selection-remount-handoff.ts +23 -0
  128. package/src/host/authoring/source-persistence-backend.ts +373 -0
  129. package/src/host/authoring/source-refresh-revisions.ts +81 -0
  130. package/src/host/authoring/struct-write-pipe.ts +143 -0
  131. package/src/host/auto-frame-window.ts +89 -0
  132. package/src/host/binding-resolver.ts +393 -0
  133. package/src/host/browser-transpile.ts +631 -0
  134. package/src/host/canvas-entry-runtime.ts +95 -0
  135. package/src/host/components/CameraAuthoringOverlay.tsx +216 -0
  136. package/src/host/components/HeaderTelemetry.tsx +341 -0
  137. package/src/host/components/PixiIsolationSceneContent.tsx +280 -0
  138. package/src/host/components/ResolutionPicker.tsx +89 -0
  139. package/src/host/components/ThreeIsolationSceneContent.tsx +180 -0
  140. package/src/host/components/frame-debugger-model.ts +579 -0
  141. package/src/host/components/header-telemetry-model.ts +74 -0
  142. package/src/host/components/scene-document.tsx +447 -0
  143. package/src/host/components/utility-view-state.ts +87 -0
  144. package/src/host/components/world-root-stage-binding.tsx +133 -0
  145. package/src/host/components/world-root-stage.ts +1046 -0
  146. package/src/host/coverage/authoring-read-probe.ts +583 -0
  147. package/src/host/coverage/capability-coverage.ts +1587 -0
  148. package/src/host/coverage/coverage-accounting.ts +261 -0
  149. package/src/host/coverage/game-contract-seam-evidence.ts +82 -0
  150. package/src/host/coverage/project-verb-coverage.ts +148 -0
  151. package/src/host/coverage/system-adapter-coverage.ts +373 -0
  152. package/src/host/design-system-stories/StoryLayout.tsx +104 -0
  153. package/src/host/design-system-stories/fixtures/authoring.ts +247 -0
  154. package/src/host/design-system-stories/fixtures/editor-runtime.tsx +115 -0
  155. package/src/host/document-preview-three.ts +110 -0
  156. package/src/host/entry-adjudication.ts +89 -0
  157. package/src/host/game-location-guard.ts +150 -0
  158. package/src/host/game-module-access.ts +196 -0
  159. package/src/host/game-realm-page.ts +358 -0
  160. package/src/host/game-realm-reclaim.ts +55 -0
  161. package/src/host/game-realm-storage.ts +104 -0
  162. package/src/host/gameplay-export.ts +288 -0
  163. package/src/host/gameplay-recording.ts +717 -0
  164. package/src/host/gated-globals.ts +1511 -0
  165. package/src/host/history/json-history-resource.ts +254 -0
  166. package/src/host/ingest/registry.ts +261 -0
  167. package/src/host/instance-extract-actions.ts +120 -0
  168. package/src/host/instance-fork-actions.ts +120 -0
  169. package/src/host/play-control-hook.ts +26 -0
  170. package/src/host/playwright-shim.ts +479 -0
  171. package/src/host/projection/dom.ts +295 -0
  172. package/src/host/projection/pixi.ts +288 -0
  173. package/src/host/r3f-entry-runtime.ts +77 -0
  174. package/src/host/react-mount-runtime.ts +162 -0
  175. package/src/host/realm-services.ts +148 -0
  176. package/src/host/recording-preview.ts +106 -0
  177. package/src/host/roots/module-root.ts +203 -0
  178. package/src/host/roots/react-root.ts +181 -0
  179. package/src/host/same-realm-loop-gate.ts +544 -0
  180. package/src/host/scene-view-drawability.ts +69 -0
  181. package/src/host/sdk/tools.ts +31 -0
  182. package/src/host/served-bundle-runtime-modules.ts +331 -0
  183. package/src/host/server-log-bridge.ts +60 -0
  184. package/src/host/staged-projects.ts +24 -0
  185. package/src/host/stories/mounted-story-viewport-source.ts +64 -0
  186. package/src/host/stories/story-arg-descriptors.ts +50 -0
  187. package/src/host/stories/story-media-presence.ts +152 -0
  188. package/src/host/surface-content.ts +87 -0
  189. package/src/host/take-named-export.ts +23 -0
  190. package/src/host/three-ingest-runtime.ts +76 -0
  191. package/src/host/types-fastnoise-lite.d.ts +7 -0
  192. package/src/host/types-mikktspace.d.ts +20 -0
  193. package/src/host/types-troika-three-text.d.ts +7 -0
  194. package/src/host/use-active-performance-source.ts +53 -0
  195. package/src/host/viewport-pose-memory.ts +48 -0
  196. package/src/host/viewport-root-presentation.ts +40 -0
  197. package/src/ingest/active-ingest.ts +251 -0
  198. package/src/ingest/active-scene-navigation.ts +43 -0
  199. package/src/ingest/authoring/ingest-dom-surface-authoring.ts +191 -0
  200. package/src/ingest/authoring/ingest-root-adapter.ts +897 -0
  201. package/src/ingest/capture-wait-report.ts +122 -0
  202. package/src/ingest/deferred-ingest-play.ts +216 -0
  203. package/src/ingest/deferred-ingest-session.ts +23 -0
  204. package/src/ingest/discovery-public-ingest.ts +242 -0
  205. package/src/ingest/dom-stub-mark.ts +7 -0
  206. package/src/ingest/entry-load.ts +56 -0
  207. package/src/ingest/game-contract-realm.ts +34 -0
  208. package/src/ingest/game-pointer-lock.ts +89 -0
  209. package/src/ingest/held-scene-repaint.ts +73 -0
  210. package/src/ingest/host-surface-box.ts +174 -0
  211. package/src/ingest/ingest-boot-viewport.ts +54 -0
  212. package/src/ingest/ingest-canvas-scene-document.tsx +177 -0
  213. package/src/ingest/ingest-canvas-scene.ts +50 -0
  214. package/src/ingest/ingest-evidence-hook.ts +131 -0
  215. package/src/ingest/ingest-frame-snapshot.ts +183 -0
  216. package/src/ingest/ingest-play-commands.ts +134 -0
  217. package/src/ingest/ingest-play-control.ts +299 -0
  218. package/src/ingest/ingest-render-debug.ts +320 -0
  219. package/src/ingest/ingest-siblings.ts +487 -0
  220. package/src/ingest/ingest-status.ts +62 -0
  221. package/src/ingest/live-ingest-facet.ts +46 -0
  222. package/src/ingest/module-mode.ts +229 -0
  223. package/src/ingest/mount-canvas-ingest-root.ts +899 -0
  224. package/src/ingest/mount-coverage.ts +296 -0
  225. package/src/ingest/mount-dom-ingest-root.ts +282 -0
  226. package/src/ingest/mount-ingest-root.ts +744 -0
  227. package/src/ingest/mount-three-ingest-root.ts +366 -0
  228. package/src/ingest/resolve-canvas.ts +47 -0
  229. package/src/ingest/resolve-three.ts +123 -0
  230. package/src/ingest/served-bundle.ts +109 -0
  231. package/src/ingest/served-html-boot.ts +292 -0
  232. package/src/ingest/surface-canvas.ts +84 -0
  233. package/src/ingest/surface-dom.ts +84 -0
  234. package/src/ingest/surface-three.ts +128 -0
  235. package/src/ingest/types.ts +76 -0
  236. package/src/ingest/unmount-ingest-root.ts +224 -0
  237. package/src/navmesh/navmesh-actions.ts +27 -0
  238. package/src/navmesh/navmesh-handler.ts +237 -0
  239. package/src/navmesh/navmesh-workflow-store.ts +78 -0
  240. package/src/network/NetworkInspectorPanel.tsx +644 -0
  241. package/src/network/network-inspector-model.ts +225 -0
  242. package/src/play/play-boot-stall.ts +118 -0
  243. package/src/play/play-log-events.ts +26 -0
  244. package/src/play/play-mode.ts +2553 -0
  245. package/src/play/play-recording.ts +335 -0
  246. package/src/play/react-play-live-authoring.ts +165 -0
  247. package/src/play-bar/PlayBar.tsx +488 -0
  248. package/src/play-bar/PlayerCountPicker.tsx +100 -0
  249. package/src/profiler/FrameDebuggerPanel.tsx +458 -0
  250. package/src/profiler/PerformancePanel.tsx +1068 -0
  251. package/src/profiler/ProfilerPanel.tsx +53 -0
  252. package/src/profiler/frame-debugger-store.ts +97 -0
  253. package/src/profiler/main-thread-busy.ts +90 -0
  254. package/src/react/design-time-react-mount.ts +782 -0
  255. package/src/react/dom-authoring-adapter.ts +764 -0
  256. package/src/react/pasteboard-materialize.ts +154 -0
  257. package/src/react/react-inspector-section.tsx +2496 -0
  258. package/src/react/react-world-authoring-adapter.ts +3795 -0
  259. package/src/react/story-documents/story-args-section.ts +19 -0
  260. package/src/react/story-documents/story-documents.tsx +1380 -0
  261. package/src/react/story-paint-bounds.ts +94 -0
  262. package/src/react/ui-board-document.tsx +101 -0
  263. package/src/react/ui-board-title.ts +9 -0
  264. package/src/react/ui-component-board.ts +72 -0
  265. package/src/services/audio-pose-guard.ts +81 -0
  266. package/src/services/game-audio-unlock.ts +48 -0
  267. package/src/state-watch/StateWatchPanel.tsx +535 -0
  268. package/src/three/authoring/camera-runtime-inspector-section.tsx +135 -0
  269. package/src/three/authoring/constraint-inspector-section.tsx +189 -0
  270. package/src/three/authoring/design-time-renderer.ts +189 -0
  271. package/src/three/authoring/model-asset-inspector-section.css +41 -0
  272. package/src/three/authoring/model-asset-inspector-section.tsx +869 -0
  273. package/src/three/authoring/oid-source-persistence.ts +557 -0
  274. package/src/three/authoring/r3f-design-session.ts +1174 -0
  275. package/src/three/authoring/r3f-source-authoring-adapter.ts +5949 -0
  276. package/src/three/authoring/reflection-probe-inspector-section.tsx +76 -0
  277. package/src/three/authoring/spatial-audio-handles.ts +215 -0
  278. package/src/three/authoring/spatial-collider-handles.ts +229 -0
  279. package/src/three/authoring/spatial-joint-handles.ts +114 -0
  280. package/src/three/authoring/spatial-light-handles.ts +178 -0
  281. package/src/three/authoring/spatial-lod-handles.ts +93 -0
  282. package/src/three/authoring/spatial-particle-handles.ts +368 -0
  283. package/src/three/authoring/three-authoring-adapter.ts +1953 -0
  284. package/src/three/authoring/three-scene-identity.ts +19 -0
  285. package/src/three/authoring/three-spatial-handles.ts +161 -0
  286. package/src/three/authoring/typed-three-inspector.ts +528 -0
  287. package/src/three/component-verbs/extract-menu.ts +71 -0
  288. package/src/three/component-verbs/fork-menu.ts +78 -0
  289. package/src/three/component-verbs/internals-menu.ts +91 -0
  290. package/src/three/story-documents/three-story-documents.tsx +607 -0
  291. package/src/three/three-board/ThreeBoardDocument.tsx +888 -0
  292. package/src/three/three-board/board-framing.ts +412 -0
  293. package/src/three/three-board/board-layout.ts +401 -0
  294. package/src/three/three-board/board-scene.ts +901 -0
  295. package/src/three/three-board/three-component-board.ts +62 -0
  296. package/src/xstate/XStateBehaviorSection.tsx +130 -0
  297. package/src/xstate/XStateMachineInspector.tsx +566 -0
  298. package/src/xstate/character-animation-machine.fixture.ts +74 -0
  299. package/src/xstate/live-behaviors.ts +106 -0
  300. package/src/xstate/use-live-actor-state.ts +44 -0
  301. package/src/xstate/xstate-graph.ts +235 -0
  302. 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
+ }