@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,296 @@
1
+ /**
2
+ * THE INSTRUMENT: what a mount MEASURED about itself, assembled from the live
3
+ * session slot and handed to the pure derivation in `coverage/capability-coverage.ts`.
4
+ *
5
+ * Everything here is a measurement or the plumbing of one — the mount-time
6
+ * one-shot probe (the editor capabilities reached),
7
+ * the live re-read of the loop verdict, the once-per-mount
8
+ * console door, and the report the status facet and the `__vgaiIngest` hook
9
+ * both serve. Mount PLUMBING lives in the `mount-*-ingest-root.ts` siblings
10
+ * and deliberately not here: a reader asking "what did the editor actually
11
+ * measure about this game, and where does that number come from" should have
12
+ * exactly this file open.
13
+ */
14
+
15
+ import type { AdapterReach } from '../host/adapter-reach';
16
+ import { getActiveSystems } from '@volter/editor-core/authoring/active-systems';
17
+ import { ingestDataWriterNow } from '../host/authoring/ingest-data-writer';
18
+ import { ingestOwnershipNow } from '../host/authoring/ingest-source-persistence';
19
+ import {
20
+ type CapabilityCoverageReport,
21
+ createCapabilityCoverageConsole,
22
+ type DataWriterFacts,
23
+ deriveCapabilityCoverage,
24
+ measureGameContract,
25
+ SYSTEM_ADAPTER_SLOTS,
26
+ type SystemAdapterMeasurement,
27
+ type WriteReachFacts,
28
+ } from '../host/coverage/capability-coverage';
29
+ import { inspectSystemAdapterSeam } from '@volter/editor-core/coverage/system-seam-evidence';
30
+ import { editorConsole } from '@volter/editor-core/editor-console';
31
+ import {
32
+ type MeasuredLoop,
33
+ measuredLoop,
34
+ type SameRealmLoopVerdict,
35
+ } from '../host/same-realm-loop-gate';
36
+ import {
37
+ type ContractSurface,
38
+ projectContractSystemAdapters,
39
+ } from '@volter/game-runtime/adapter/ingest/contract-system-adapters';
40
+ import type { VgaiGameContract } from '@volter/editor-project/adapter/ingest/game-contract';
41
+ import type { SystemAdapters } from '@volter/editor-project/adapter/system-adapter';
42
+ import { activeIngest } from './active-ingest';
43
+ import type { IngestMount } from './authoring/ingest-root-adapter';
44
+ import { activeIngestContract } from './ingest-play-control';
45
+
46
+ /**
47
+ * The half of {@link deriveCapabilityCoverage}'s facts that is a property of THIS
48
+ * mount and cannot be re-read later: the editor capabilities this mount
49
+ * reached, with the mechanism that reached them. Everything else the
50
+ * coverage report needs (the loop verdict, the declared
51
+ * contract — its systems surface included — and the ownership answer) is read
52
+ * live at report time, because all of them move after the mount and a frozen
53
+ * copy would start lying.
54
+ */
55
+ export interface MountCoverageInputs {
56
+ readonly reach: AdapterReach | null;
57
+ /** How this mount reached the game's runtime, in the mount's own words,
58
+ * carried into the warning a missing capability prints. */
59
+ readonly reachMechanism: string | null;
60
+ /** Read on demand because a loop verdict may arrive only after the first
61
+ * pause probe. Every surface supplies its own real control verdict, and a
62
+ * route with no gate at all supplies the reason there will never be one. */
63
+ readonly loop: () => { loop: MeasuredLoop | null; reason: string | null };
64
+ /**
65
+ * How much of the mounted world an edit can actually be written back to, read
66
+ * LIVE for the same reason the loop verdict is: a world keeps streaming
67
+ * objects in, and a count frozen at first capture would start lying.
68
+ *
69
+ * ABSENT ⇒ this route has no three authoring adapter to ask (a DOM or
70
+ * canvas ingest), and the persistence row says only what ownership answered —
71
+ * never a reach claim nobody measured.
72
+ */
73
+ readonly writeReach?: (() => WriteReachFacts) | undefined;
74
+ }
75
+
76
+ /**
77
+ * The console door for the coverage report (`coverage/capability-coverage.ts`) —
78
+ * gaps as one warning block, the rest as one info block, ONCE per mount.
79
+ */
80
+ const _coverageConsole = createCapabilityCoverageConsole((block) => {
81
+ if (block.level === 'warn') editorConsole.warn(block.message, 'ingest');
82
+ else editorConsole.log(block.message, 'ingest');
83
+ });
84
+
85
+ /** Distinguishes consecutive mounts of the SAME world for the console door's
86
+ * once-per-mount guard — remounting a game is a new answer, not a repeat. */
87
+ let _mountSeq = 0;
88
+ let _mountToken = '';
89
+
90
+ /** Open a new coverage answer for `sessionId`: a new mount is a new report even
91
+ * for the same world, and the console door's once-guard keys on this token. */
92
+ export function beginMountCoverage(sessionId: string): void {
93
+ _mountToken = `${sessionId}#${++_mountSeq}`;
94
+ }
95
+
96
+ /**
97
+ * The loop facts for a three-route mount, read live.
98
+ *
99
+ * A verdict is a MEASUREMENT, produced by the gate's own probe at the first
100
+ * pause — and it travels WITH the evidence it was concluded from
101
+ * ({@link MeasuredLoop}), which is what keeps it distinguishable from the
102
+ * manifest's declared intent word. Where the mount installed no gate at all the
103
+ * honest answer is "no verdict will ever be measured here", never the
104
+ * pending-probe sentence, which promises a measurement that does not come.
105
+ */
106
+ export function threeMountLoopFacts(mount: IngestMount): {
107
+ loop: MeasuredLoop | null;
108
+ reason: string | null;
109
+ } {
110
+ const loop = measuredLoop(mount.realmLoopVerdict?.());
111
+ const reason = mount.realmLoopVerdict?.()?.reason ?? null;
112
+ if (loop !== null || reason !== null) return { loop, reason };
113
+ if (mount.realmLoopVerdict) return { loop: null, reason: null };
114
+ return { loop: null, reason: LOOP_PROBE_ABSENT };
115
+ }
116
+
117
+ /**
118
+ * The loop facts for a native-React (`dom`) mount — its SIBLING to
119
+ * {@link threeMountLoopFacts}, and the reason it exists as a named function
120
+ * rather than a literal at the mount site.
121
+ *
122
+ * This route used to assert `loop: 'gated'` outright, on the strength of the
123
+ * sentence below. That sentence is true and it is still printed, but it
124
+ * describes a MECHANISM, not a measurement of one: no probe on this route has
125
+ * ever held this game's scheduling and watched whether anything ran anyway
126
+ * (`ingest/same-realm-loop-gate.ts`'s `verifySameRealmLoopControl` is the only
127
+ * thing that can answer that, and nothing calls it here). The word it emitted
128
+ * was the manifest's DECLARED intent vocabulary wearing a measurement's
129
+ * clothes, so the coverage report printed `loop ✓ gated` about every
130
+ * native-React ingest — including one whose own `setInterval` the host's play
131
+ * pause never touches.
132
+ *
133
+ * Same shape and same reason as `mount-canvas-ingest-root.ts`'s two branches,
134
+ * which reach the identical conclusion about their own unprobed controls.
135
+ */
136
+ export function domMountLoopFacts(): { loop: null; reason: string } {
137
+ return {
138
+ loop: null,
139
+ reason:
140
+ "⏸ calls the host runtime's own play pause on this route, but no probe has measured " +
141
+ "whether that reaches this game's frames — an unmodified React game that schedules its " +
142
+ 'own timers is not held by the host loop',
143
+ };
144
+ }
145
+
146
+ /** What a mount with no loop gate at all can honestly say about its frames. */
147
+ export const LOOP_PROBE_ABSENT =
148
+ 'this mount installs no loop gate, so no verdict will ever be measured here — nothing in ' +
149
+ "the editor holds this game's frames";
150
+
151
+ /**
152
+ * The terminal state of every `SystemAdapters` slot for a live mount.
153
+ *
154
+ * TWO independent sources, deliberately: `bound` is read off the editor's own
155
+ * live registry (the very object its panels call, so a slot that "registered"
156
+ * but never reached the registry reports honestly), while `empty`/`malformed`
157
+ * come from the game's declared carrier. Nothing consults the route or the
158
+ * game's id, and no slot is bound and empty at once — a bound adapter wins,
159
+ * because the registry is the stronger measurement.
160
+ *
161
+ * Exported and pure over its inputs so the whole table is unit-testable with no
162
+ * browser and no live session.
163
+ */
164
+ export function measureSystemAdapters(
165
+ active: SystemAdapters,
166
+ contract: VgaiGameContract | null,
167
+ /** The mount's surface when the caller has one — see
168
+ * `contract-system-adapters.ts`'s header for the one slot it decides
169
+ * (`physics`, whose vocabulary follows the surface). */
170
+ surface?: ContractSurface | undefined,
171
+ ): readonly SystemAdapterMeasurement[] {
172
+ const projection = projectContractSystemAdapters(contract?.systems, surface);
173
+ const empty = new Map(projection.empty.map((e) => [e.slot as string, e.evidence]));
174
+ const malformed = new Map(projection.malformed.map((m) => [m.slot as string, m.reason]));
175
+ return SYSTEM_ADAPTER_SLOTS.map((slot): SystemAdapterMeasurement => {
176
+ if (active[slot] != null) {
177
+ return {
178
+ slot,
179
+ state: 'bound',
180
+ proof: inspectSystemAdapterSeam({
181
+ slot,
182
+ adapter: active[slot] as NonNullable<SystemAdapters[typeof slot]>,
183
+ subject: 'game',
184
+ }),
185
+ };
186
+ }
187
+ const evidence = empty.get(slot);
188
+ if (evidence !== undefined) return { slot, state: 'empty', evidence };
189
+ const reason = malformed.get(slot);
190
+ if (reason !== undefined) return { slot, state: 'malformed', evidence: reason };
191
+ return { slot, state: 'unanswered' };
192
+ });
193
+ }
194
+
195
+ /**
196
+ * THE coverage report for the live mount — re-derived on every read (see
197
+ * {@link MountCoverageInputs} for what is frozen at mount and what is not).
198
+ * `null` when nothing is mounted, or when the mounted route recorded no
199
+ * coverage inputs: not measured is its own answer, never a clean one.
200
+ *
201
+ * The contract comes from `activeIngestContract` — the same read
202
+ * `getIngestPlayControl` acts on, so a game that declared its contract
203
+ * somewhere the host cannot see reports the same gap the editor actually has,
204
+ * and one that declared it through a host-added contract shim beside the game
205
+ * gets credit for exactly what the play control can use.
206
+ */
207
+ /**
208
+ * The declared data writer's state, in the coverage vocabulary. `absent` — the
209
+ * common case — is `null`, not a gap: a game with no data file to write is not
210
+ * missing anything.
211
+ */
212
+ function dataWriterFacts(): DataWriterFacts | null {
213
+ const state = ingestDataWriterNow();
214
+ switch (state.state) {
215
+ case 'absent':
216
+ return null;
217
+ case 'loading':
218
+ return { state: 'loading' };
219
+ case 'ready':
220
+ return { state: 'ready', dataFile: state.writer.dataFile };
221
+ default:
222
+ return { state: 'failed', reason: state.reason };
223
+ }
224
+ }
225
+
226
+ export function ingestCoverageReport(): CapabilityCoverageReport | null {
227
+ const active = activeIngest();
228
+ const inputs = active?.coverage;
229
+ if (!active || !inputs) return null;
230
+ const ownership = ingestOwnershipNow();
231
+ const loop = inputs.loop();
232
+ const contract = activeIngestContract();
233
+ return deriveCapabilityCoverage({
234
+ worldId: active.worldId,
235
+ reach: inputs.reach,
236
+ // The session's own render substrate IS the surface kind the coverage
237
+ // derivation needs (`IngestKind` and `AdapterSurface` are the same three
238
+ // words by construction), so nothing has to be declared twice.
239
+ surface: active.kind,
240
+ reachMechanism: inputs.reachMechanism,
241
+ loop: loop.loop,
242
+ loopReason: loop.reason,
243
+ contract: measureGameContract(contract),
244
+ // `active.kind` IS the surface (`IngestKind` and `ContractSurface` are the
245
+ // same three words by construction), so the physics slot's keying is
246
+ // checked against the surface this mount actually is.
247
+ systemAdapters: measureSystemAdapters(getActiveSystems(), contract, active.kind),
248
+ ownership: ownership
249
+ ? {
250
+ writable: ownership.writable,
251
+ reason: ownership.reason ?? null,
252
+ recorder: ownership.recorder ?? null,
253
+ }
254
+ : null,
255
+ dataWriter: dataWriterFacts(),
256
+ // The `project.*` family is about the opened PROJECT, not this mount, and
257
+ // it has one producer for both lanes (`@volter/editor-game/coverage/live-project-verbs.ts`).
258
+ // Reporting it here too would print two verdicts for one capability.
259
+ projectVerbs: [],
260
+ writeReach: inputs.writeReach?.() ?? null,
261
+ });
262
+ }
263
+
264
+ /**
265
+ * Record this mount's frozen coverage inputs and say the result out loud, once.
266
+ *
267
+ * Called from every mount path right after the session is published, so the
268
+ * report describes a mount that exists.
269
+ */
270
+ export function recordMountCoverage(inputs: MountCoverageInputs): void {
271
+ const active = activeIngest();
272
+ if (!active) return;
273
+ active.coverage = inputs;
274
+ const report = ingestCoverageReport();
275
+ if (report) _coverageConsole.report(_mountToken, report);
276
+ }
277
+
278
+ /**
279
+ * Report the MEASURED loop verdict for a three ingest mount (honesty
280
+ * gate, ARCHITECTURE-CORE §Editor).
281
+ *
282
+ * `loop` is a claim about whether anything in this editor actually controls the
283
+ * game's frames, so it is only ever printed from the gate's own probe
284
+ * (`ingest/same-realm-loop-gate.ts`) — never inferred from the manifest. A
285
+ * route that does no gating at all passes `undefined` and this says nothing,
286
+ * which is also honest.
287
+ */
288
+ export function reportMeasuredLoop(
289
+ worldId: string,
290
+ verdict: SameRealmLoopVerdict | undefined,
291
+ ): void {
292
+ if (!verdict) return;
293
+ const line = `Ingest "${worldId}" loop: ${verdict.loop} — ${verdict.reason}`;
294
+ if (verdict.loop === 'gated') editorConsole.log(line, 'ingest');
295
+ else editorConsole.warn(line, 'adapter');
296
+ }
@@ -0,0 +1,282 @@
1
+ /**
2
+ * Mount a `{ ingest }` root on the DOM surface — an unmodified native-React
3
+ * game, mounted through the EXACT SAME react-world host stack `default-react`/
4
+ * multi-world react roots use, NOT the capture-based three/canvas bridge (there
5
+ * is no scene to capture; the mounted tree IS React, D-N1).
6
+ */
7
+
8
+ import { measureAdapterReach } from '../host/adapter-reach';
9
+ import { setActiveAuthoring } from '@volter/editor-core/authoring/active-adapter';
10
+ import { setActiveSystems } from '@volter/editor-core/authoring/active-systems';
11
+ import { clearMountFailureReports } from '@volter/editor-core/authoring/mount-failure-report';
12
+ import { resolveAllRoots } from '../host/binding-resolver';
13
+ import { editorConsole } from '@volter/editor-core/editor-console';
14
+ import type { EditorShellStore } from '@volter/editor-core/editor-shell-store';
15
+ import { setGameInputGate } from '../host/gated-globals';
16
+ import { authoringJournal } from '../host/history/json-history-resource';
17
+ import { acquireLiveDocument, liveDocumentContainer } from '@volter/editor-core/live-document';
18
+ import { DomAuthoringAdapter } from '../react/dom-authoring-adapter';
19
+ import type { ResolvedAdapterRoot, ResolvedGameManifest } from '@volter/editor-project/manifest/load';
20
+ import { serializeEntry, setActiveIngest } from './active-ingest';
21
+ import { landIngestBootInEdit } from './ingest-boot-viewport';
22
+ import { ingestHookEvidence, publishIngestHook, reactDomEvidence } from './ingest-evidence-hook';
23
+ import { bindIngestLifecycle } from './ingest-play-control';
24
+ import { domMountLoopFacts, type MountCoverageInputs, recordMountCoverage } from './mount-coverage';
25
+ import { getIngestGameDom, ingestGameDomProjectRoot } from './surface-dom';
26
+ import { exitActiveIngest } from './unmount-ingest-root';
27
+
28
+ /** How a react ingest reaches the game: it mounts through the host's own
29
+ * native React runtime, so whatever the editor cannot do here is the
30
+ * adapter's own missing surface, not an unreachable realm. */
31
+ const REACT_MECHANISM =
32
+ "this world mounts in the host's native React runtime, so the editor is limited only by " +
33
+ 'what its authoring adapter provides';
34
+
35
+ /** The coverage inputs both react routes record — identical by construction,
36
+ * because both mount through the same native runtime. */
37
+ function domMountCoverage(adapter: DomAuthoringAdapter): MountCoverageInputs {
38
+ return {
39
+ reach: measureAdapterReach({ authoring: adapter }),
40
+ reachMechanism: REACT_MECHANISM,
41
+ loop: domMountLoopFacts,
42
+ };
43
+ }
44
+
45
+ /**
46
+ * Mount a discovered native-React fixture game (`ingest/surface-dom.ts`'s glob
47
+ * discovery is the lookup).
48
+ *
49
+ * Reuses `resolveAllRoots` + universal `createGameRuntime` rather than
50
+ * hand-building a `DomHostContext`/`Game`: a
51
+ * single-element `roots` array gets EVERY host concern for free (the
52
+ * absolutely-positioned DOM-root layer, a real `Game`, dispose via
53
+ * `session.stop()`) with zero reinvention — `resolveAllRoots`'s
54
+ * `MULTI_WORLD_ALLOWED_IDENTITIES` gate only fires for `roots.length > 1`
55
+ * (see that function's doc comment), so a ONE-element manifest containing an
56
+ * `ingest-react` world resolves cleanly through it despite that identity
57
+ * being excluded from real multi-world manifests.
58
+ *
59
+ * D-N8 v2 / D-K4: the active authoring override is a real
60
+ * `DomAuthoringAdapter` — a structural-path DOM walker over the mounted
61
+ * react tree, applying edits live (never to source, see that file's doc
62
+ * comment). The published `reach` is MEASURED off this real adapter
63
+ * (`adapter-reach.ts`), not hard-coded, so every capability it does not
64
+ * provide keeps warning.
65
+ */
66
+ async function mountDomIngestRootInner(store: EditorShellStore, folderId: string): Promise<void> {
67
+ const game = getIngestGameDom(folderId);
68
+ if (!game) {
69
+ editorConsole.error(`Unknown react ingest game: ${folderId}`, 'ingest');
70
+ return;
71
+ }
72
+ const projectRoot = ingestGameDomProjectRoot(folderId);
73
+ if (!projectRoot) {
74
+ editorConsole.error(
75
+ `React ingest "${folderId}": no __VGAI_ENGINE_ROOT__ available to resolve its entry`,
76
+ 'ingest',
77
+ );
78
+ return;
79
+ }
80
+ // The Game document exists only while a runtime does, so install it (and
81
+ // wait for its panel to commit) before reading the container it owns.
82
+ await acquireLiveDocument();
83
+ const gameContainer = liveDocumentContainer();
84
+ if (!gameContainer) {
85
+ editorConsole.error('Game container not mounted — cannot ingest (react)', 'ingest');
86
+ return;
87
+ }
88
+ // D-V1: tear down WHATEVER kind is currently active, not just a prior
89
+ // react session — closes the cross-kind leak (F26 residual).
90
+ exitActiveIngest();
91
+
92
+ store.setPlayState('playing');
93
+ editorConsole.log(`Ingesting unmodified native-React game: ${game.manifest.name}`, 'ingest');
94
+
95
+ // F26: same cold-mount announcement as the other ingest routes — the react
96
+ // world's entry executes in the editor realm and may declare the game
97
+ // contract to defer its session until ▶. Opt-in, never demanded.
98
+ (window as unknown as { __vgaiMountCold?: boolean }).__vgaiMountCold = true;
99
+ const { createGameRuntime } = await import('@volter/game-runtime/runtime/create-runtime');
100
+ const specs = await resolveAllRoots(game.manifest, projectRoot);
101
+ const w = gameContainer.clientWidth;
102
+ const h = gameContainer.clientHeight;
103
+ const session = await createGameRuntime({
104
+ container: gameContainer,
105
+ roots: specs,
106
+ width: w,
107
+ height: h,
108
+ });
109
+
110
+ // D-K4: construct the real authoring adapter over the mounted world's live
111
+ // DOM root (its style edits are live-only — there is no source path here and
112
+ // no sidecar any more). The adapter has its own `selection` provider
113
+ // (store-backed, same shape as `ReactRootAuthoringAdapter.selection`), so —
114
+ // unlike a `{ module }` adapter with none — it is installed directly, never
115
+ // `withStoreSelection`-wrapped.
116
+ const root = session.game.roots[0]!.reactRoot();
117
+ // A held surface: its journal is the WORLD's, so undo survives a remount of
118
+ // this game (`../history/json-history-resource.ts`).
119
+ const adapter = new DomAuthoringAdapter(root, store, {
120
+ journal: authoringJournal(folderId),
121
+ });
122
+ // Unlike the capture routes, a react ingest mounts through the NATIVE
123
+ // runtime — its Game.play pause/resume IS the honest control (D10). Named
124
+ // (rather than inlined below) so `setPausedPresent`'s D-L1 `typeof`
125
+ // check reads the SAME real control the session actually installs, never
126
+ // an independent literal.
127
+ const setPaused = (p: boolean) => (p ? session.game.play.pause() : session.game.play.resume());
128
+ const lifecycle = bindIngestLifecycle({ setPaused });
129
+
130
+ setActiveIngest({
131
+ kind: 'dom',
132
+ session: {
133
+ store,
134
+ lifecycle,
135
+ stop: () => session.stop(),
136
+ adapter,
137
+ },
138
+ siblings: [],
139
+ worldId: folderId,
140
+ });
141
+ // Same play-mode input gate every other same-realm ingest route drives (R2c).
142
+ // The former per-surface `games-2d`/`games-react` fixture
143
+ // trees into the single `ingest/games` tree, which IS what
144
+ // `game-globals-shadow.ts` roots its shadow at — so a native-React fixture's
145
+ // own listeners are shadowed on the same terms as every other surface's now
146
+ // (this comment used to record the opposite, from the R-N6 residual).
147
+ setGameInputGate(() => store.playState === 'playing' && store.activeViewportTab === 'play');
148
+
149
+ setActiveAuthoring(adapter);
150
+ setActiveSystems({});
151
+ landIngestBootInEdit(store);
152
+ store.notifyIngestEdit();
153
+ clearMountFailureReports(); // D-W3: mount succeeded
154
+
155
+ const coverage = domMountCoverage(adapter);
156
+ recordMountCoverage(coverage);
157
+
158
+ publishIngestHook('__vgaiIngestReact', {
159
+ gameId: folderId,
160
+ worldId: game.worldId,
161
+ ...ingestHookEvidence(adapter),
162
+ noAuthoring: false,
163
+ // D-K4: MEASURED off the real adapter (`adapter-reach.ts`) — a
164
+ // hierarchy+inspector+persistence adapter with no `transforms` reports a
165
+ // standing `transforms` gap, never a hard-coded claim.
166
+ reach: coverage.reach,
167
+ // D-L1: the doctor MOUNTED-bar evidence accessor —
168
+ // see `reactDomEvidence`'s own doc comment.
169
+ domEvidence: () => reactDomEvidence(root, adapter),
170
+ // D-L1: honest `typeof === 'function'` check on the REAL session
171
+ // control this route just installed above — never an independent
172
+ // literal `true` (mirrors `__vgaiSiblingMounts`'s identical discipline).
173
+ setPausedPresent: typeof lifecycle.setPaused === 'function',
174
+ });
175
+ }
176
+
177
+ /**
178
+ * D-Y2 (slice S2) — the manifest-route sibling of {@link mountDomIngestRoot}: an
179
+ * EXTERNAL-FOLDER `ingest-react` world (the opened project's OWN
180
+ * `vgai.project.json`, not the in-tree `ingest/games/<id>` fixture registry)
181
+ * enters the same react ingest session. Exactly the same roots-path recipe
182
+ * (one-element `resolveAllRoots` + roots-path `createGameRuntime`) and the same
183
+ * `DomAuthoringAdapter` D-K4 wiring — only the SOURCE of
184
+ * `world`/`projectRoot` differs (the opened project's manifest +
185
+ * `project.rootPath`, via `mount-ingest-root.ts`'s react route, instead of
186
+ * `getIngestGameDom`/`ingestGameDomProjectRoot`'s vendored-fixture lookup).
187
+ * `resolveIngestReactAdapter`'s own guards (`entry` required, no `scene`) run
188
+ * unchanged inside `resolveAllRoots` — this function
189
+ * adds no new validation of its own, mirroring
190
+ * `mountCanvasIngestRootFromManifest`'s relationship to its vendored sibling.
191
+ */
192
+ export async function mountDomIngestRootFromManifest(
193
+ store: EditorShellStore,
194
+ manifest: ResolvedGameManifest,
195
+ world: ResolvedAdapterRoot,
196
+ projectRoot: string,
197
+ ): Promise<void> {
198
+ // The Game document exists only while a runtime does, so install it (and
199
+ // wait for its panel to commit) before reading the container it owns.
200
+ await acquireLiveDocument();
201
+ const gameContainer = liveDocumentContainer();
202
+ if (!gameContainer) {
203
+ editorConsole.error('Game container not mounted — cannot ingest (react)', 'ingest');
204
+ return;
205
+ }
206
+ // D-V1: tear down WHATEVER kind is currently active, not just a prior
207
+ // react session — closes the cross-kind leak (F26 residual).
208
+ exitActiveIngest();
209
+
210
+ store.setPlayState('playing');
211
+ editorConsole.log(
212
+ `Ingesting unmodified native-React game (external folder): ${world.id}`,
213
+ 'ingest',
214
+ );
215
+
216
+ // F26: same cold-mount announcement the vendored route makes — an
217
+ // external-folder game may declare the game contract too.
218
+ (window as unknown as { __vgaiMountCold?: boolean }).__vgaiMountCold = true;
219
+ const { createGameRuntime } = await import('@volter/game-runtime/runtime/create-runtime');
220
+ const specs = await resolveAllRoots({ ...manifest, roots: [world] }, projectRoot);
221
+ const w = gameContainer.clientWidth;
222
+ const h = gameContainer.clientHeight;
223
+ const session = await createGameRuntime({
224
+ container: gameContainer,
225
+ roots: specs,
226
+ width: w,
227
+ height: h,
228
+ });
229
+
230
+ // D-K4: same adapter construction as the vendored route.
231
+ const root = session.game.roots[0]!.reactRoot();
232
+ const adapter = new DomAuthoringAdapter(root, store, {
233
+ journal: authoringJournal(world.id),
234
+ });
235
+ // Same honest control as the vendored route (D10): a manifest-routed react
236
+ // ingest mounts through the NATIVE runtime, so Game.play pause/resume IS
237
+ // real. Named (D-L1) so `setPausedPresent` checks the SAME real control.
238
+ const setPaused = (p: boolean) => (p ? session.game.play.pause() : session.game.play.resume());
239
+ const lifecycle = bindIngestLifecycle({ setPaused });
240
+
241
+ setActiveIngest({
242
+ kind: 'dom',
243
+ session: {
244
+ store,
245
+ lifecycle,
246
+ stop: () => session.stop(),
247
+ adapter,
248
+ },
249
+ siblings: [],
250
+ worldId: world.id,
251
+ });
252
+ setGameInputGate(() => store.playState === 'playing' && store.activeViewportTab === 'play');
253
+
254
+ setActiveAuthoring(adapter);
255
+ setActiveSystems({});
256
+ landIngestBootInEdit(store);
257
+ store.notifyIngestEdit();
258
+ clearMountFailureReports(); // D-W3: mount succeeded
259
+
260
+ const coverage = domMountCoverage(adapter);
261
+ recordMountCoverage(coverage);
262
+
263
+ publishIngestHook('__vgaiIngestReact', {
264
+ worldId: world.id,
265
+ ...ingestHookEvidence(adapter),
266
+ noAuthoring: false,
267
+ // D-K4: see mountDomIngestRoot's identical comment — MEASURED, never
268
+ // hard-coded.
269
+ reach: coverage.reach,
270
+ // D-L1: see mountDomIngestRoot's identical comment.
271
+ domEvidence: () => reactDomEvidence(root, adapter),
272
+ setPausedPresent: typeof lifecycle.setPaused === 'function',
273
+ });
274
+ }
275
+
276
+ /**
277
+ * Mount a discovered native-React fixture game by its folder id. Serialized
278
+ * through the shared entry queue; see `active-ingest.ts`'s `serializeEntry`.
279
+ */
280
+ export function mountDomIngestRoot(store: EditorShellStore, folderId: string): Promise<void> {
281
+ return serializeEntry(() => mountDomIngestRootInner(store, folderId));
282
+ }