@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,487 @@
1
+ /**
2
+ * Composite sibling mounts — D-V2/D-V3/D-V4/D-V7 (residual), extended by D-B1/
3
+ * D-B3 (Slice B): when a manifest declares exactly
4
+ * one `{ ingest }` world alongside OTHER roots ("siblings"),
5
+ * `ingest/mount-ingest-root.ts`'s composite routes mount the ingest world through their
6
+ * existing enter path and then call {@link mountIngestSiblings} here to mount
7
+ * every OTHER world in manifest order (already z-sorted by the loader),
8
+ * degrading loudly per world rather than silently ignoring them (the F24/F9
9
+ * anti-pattern this closes):
10
+ *
11
+ * - a builtin `dom` world — a bare (no `<WorldProvider>`) DOM layer, D-V3.
12
+ * There is no native `Game` beside an ingest world, so wrapping one would
13
+ * fabricate first-party data (anti-shim) — the sibling's own `useWorldState`/
14
+ * `useGame` calls are simply unavailable, and this says so once per world.
15
+ * Has no loop at all, so its `SiblingMount.setPaused` stays `undefined`
16
+ * (D-B1) — never a fabricated no-op.
17
+ * - `{ module }` (any kind) OR a builtin `three`/`canvas` world
18
+ * (D-B3 — the residual this closes: a second `{ ingest }` world is
19
+ * structurally unreachable here, D-V2 already fails the whole route
20
+ * before any sibling loop runs, so `resolveAllRoots`'s one-element call
21
+ * below never hits its `MULTI_WORLD_ALLOWED_IDENTITIES` guard, which only
22
+ * applies for `roots.length > 1`) — a SCOPED single-world runtime, D-V4:
23
+ * the exact resolve+mount `module-mode.ts`'s `mountModuleRootRuntime`
24
+ * proves out, reused via `mountModuleRootRuntime`, but with NONE of that
25
+ * route's global side effects (no `setActiveAuthoring`/`setActiveSystems`,
26
+ * no viewport-tab switch, no `__vgaiModule` hook, no play-surface writes).
27
+ * Its `SiblingMount.setPaused` (D-B1) is wired straight from the scoped
28
+ * `GameSession.pause()`/`.resume()` `mountModuleRootRuntime` hands back —
29
+ * the SAME honest, already-existing capability `play-mode.ts`'s own
30
+ * `pausePlayMode`/`resumePlayMode` call on the PRIMARY route's session
31
+ * (`create-runtime.ts`'s `Game.play.pause()`/`.resume()`, gated per
32
+ * `pausable` world) — never a fabricated pause.
33
+ * - anything else — `rejectUnmountableSibling`'s named, loud non-goal for a
34
+ * genuinely unroutable shape (there is none known today; every currently
35
+ * resolvable identity is handled above): a per-world mount-failure entry,
36
+ * never silence.
37
+ *
38
+ * A failing sibling NEVER unmounts the ingest world: each failure is caught
39
+ * individually and appended to the (D-V5) multi-entry mount-failure report;
40
+ * the loop continues to the next sibling. Every successfully-mounted
41
+ * sibling's handle is returned so `ingest/mount-ingest-root.ts` can store it on the
42
+ * unified `_active` slot, dispose it (reverse order) on exit, and fan pause/
43
+ * resume out to it (D-B2, `getIngestPlayControl()`).
44
+ */
45
+
46
+ import {
47
+ addMountFailureReport,
48
+ formatMountFailureMessage,
49
+ } from '@volter/editor-core/authoring/mount-failure-report';
50
+ import { withStoreSelection } from '../host/authoring/mounted-authoring';
51
+ import { makeNoAuthoringAdapter } from '@volter/editor-core/authoring/no-authoring-adapter';
52
+ import { PixiAuthoringAdapter } from '../host/authoring/pixi-authoring-adapter';
53
+ import { createLiveCanvasWriteTarget } from '../host/authoring/pixi-live-write-target';
54
+ import { resolveCanvasPixiForEditor } from '../host/canvas-entry-runtime';
55
+ import { CrashNullBoundary } from '@volter/editor-core/crash-null-boundary';
56
+ import { editorConsole } from '@volter/editor-core/editor-console';
57
+ import type { EditorShellStore } from '@volter/editor-core/editor-shell-store';
58
+ import { authoringJournal } from '../host/history/json-history-resource';
59
+ import { activeRealmServices } from '../host/realm-services';
60
+ import { resolveReactAdapterRootComponent } from '../host/roots/react-root';
61
+ import { sourceWriteBackendIfPrimed } from '@volter/editor-core/ui-source/tier-source-write-backend';
62
+ import { DomAuthoringAdapter } from '../react/dom-authoring-adapter';
63
+ import { ReactRootAuthoringAdapter, walkOidTree } from '../react/react-world-authoring-adapter';
64
+ import { beginProjectMountEpoch } from '@volter/editor-sdk/session/project-module-url';
65
+ import { createPhysics2DRegistry } from '@volter/game-runtime/pixi/physics-registry';
66
+ import { createPhysicsAdapter2D } from '@volter/game-runtime/pixi/system-adapters';
67
+ import type { AuthoringAdapter } from '@volter/editor-project/adapter';
68
+ import { declaredRoots } from '@volter/editor-project/adapter/manifest-interpreter';
69
+ import type { ResolvedAdapterRoot, ResolvedGameManifest } from '@volter/editor-project/manifest/load';
70
+ import { structuralThree } from '../three/authoring/three-authoring-adapter';
71
+ import { createElement } from 'react';
72
+ import { flushSync } from 'react-dom';
73
+ import { createRoot } from 'react-dom/client';
74
+ import { mountModuleRootRuntime } from './module-mode';
75
+
76
+ /** One mounted sibling world's handle — stored on `ingest/active-ingest.ts`'s unified
77
+ * `_active` slot and disposed (reverse order) by `exitActiveIngest()`. */
78
+ export interface SiblingMount {
79
+ readonly worldId: string;
80
+ /**
81
+ * D-B1 (Slice B): an OPTIONAL, honest pause capability — present only when
82
+ * this sibling's own mounted session exposes a REAL pause (today: every
83
+ * `mountScopedRuntimeSibling` kind, via its scoped
84
+ * `GameSession.pause()`/`.resume()`). Absent (never a fake no-op) for a
85
+ * kind with no loop to gate at all — the bare `default-react` sibling.
86
+ * `getIngestPlayControl()` (`ingest/mount-ingest-root.ts`, D-B2) calls this
87
+ * `?.`-optionally after its own primary-surface call, mirroring the
88
+ * reverse-order dispose fan-out below.
89
+ */
90
+ setPaused?(paused: boolean): void;
91
+ /**
92
+ * An OPTIONAL, HONEST authoring surface for this sibling — present iff `store`
93
+ * was supplied to {@link mountIngestSiblings} (every production caller
94
+ * supplies one; the parameter stays optional only so the one pre-existing test
95
+ * that calls this module directly without a store keeps compiling/running
96
+ * unchanged). When present, it is NEVER a fabricated stand-in:
97
+ * - a scoped-runtime sibling (`{ module }`, or a builtin
98
+ * `three`/`canvas` world) gets its OWN mounted
99
+ * `AuthoringAdapter` (`mounted.authoring`), store-selection-wrapped
100
+ * (`withStoreSelection`) — or, when it exposes none, the SAME named
101
+ * no-authoring floor the primary module route uses
102
+ * (`makeNoAuthoringAdapter`), so the world still appears in the hierarchy
103
+ * as a group with its honest label, never silently omitted.
104
+ * - a bare `default-react` sibling gets a {@link ReactRootAuthoringAdapter}
105
+ * over its own layer DOM root — an un-instrumented entry (no
106
+ * `data-oid`) yields an EMPTY subtree under its group node, never
107
+ * fabricated nodes.
108
+ * `ingest/mount-ingest-root.ts`'s composite routes promote to a `CompositeAuthoringAdapter`
109
+ * over the primary + every sibling's `authoring` once any sibling mounts
110
+ * (D-E2); a `SiblingMount` with no `authoring` (store omitted) simply
111
+ * cannot participate in that promotion.
112
+ */
113
+ readonly authoring?: AuthoringAdapter;
114
+ dispose(): void;
115
+ }
116
+
117
+ /** Position/size/z-order every sibling layer shares — same absolute-fill
118
+ * convention `create-runtime.ts`'s own react-world DOM layer uses
119
+ * (`mountOneReactRoot`), so a sibling stacks exactly like a native world
120
+ * would have. `pointerEvents` differs per sibling kind below (module roots
121
+ * own real canvases that need real hit-testing; the react sibling's layer
122
+ * stays `none` by the same HUD-overlay convention `mountOneReactRoot` uses,
123
+ * its rendered content opts back in per-element). */
124
+ function styleSiblingLayer(
125
+ layer: HTMLElement,
126
+ world: ResolvedAdapterRoot,
127
+ pointerEvents: string,
128
+ ): void {
129
+ layer.style.position = 'absolute';
130
+ layer.style.top = '0';
131
+ layer.style.left = '0';
132
+ layer.style.width = '100%';
133
+ layer.style.height = '100%';
134
+ layer.style.zIndex = String(world.zOrder ?? 0);
135
+ layer.style.pointerEvents = pointerEvents;
136
+ }
137
+
138
+ /**
139
+ * D-V3: mount a `default-react` sibling as a bare DOM layer — no
140
+ * `<WorldProvider>` wrap (anti-shim: there is no native `Game` to hand it).
141
+ * Reuses `binding-resolver.ts`'s exact entry-loading step
142
+ * (`resolveReactAdapterRootComponent` — the same D4 `scene`-forbidden/
143
+ * `entry`-required checks and default-export validation
144
+ * `resolveDefaultReactAdapter` itself uses) rather than duplicating it.
145
+ *
146
+ * If the entry component throws at mount/first render (most commonly: it
147
+ * calls `useGame`/`useWorldState` with no provider in the tree), the error
148
+ * boundary above catches it, this function removes the layer it just
149
+ * created, and RETHROWS so the caller's uniform per-sibling catch
150
+ * (`mountOneIngestSibling`'s caller, {@link mountIngestSiblings}) can turn it
151
+ * into a named mount-failure-report entry for THIS world, exactly like any
152
+ * other sibling failure — the ingest world stays mounted regardless.
153
+ */
154
+ async function mountDefaultReactSibling(
155
+ world: ResolvedAdapterRoot,
156
+ gameContainer: HTMLElement,
157
+ projectRoot: string,
158
+ store: EditorShellStore | undefined,
159
+ ): Promise<SiblingMount> {
160
+ // A sibling react layer is mounted on its own, outside a composition
161
+ // resolve, so it opens its OWN mount epoch — it shares no project module
162
+ // graph with anything else by construction.
163
+ const realm = await activeRealmServices(projectRoot, beginProjectMountEpoch());
164
+ const Entry = await resolveReactAdapterRootComponent(world, realm);
165
+
166
+ const layer = document.createElement('div');
167
+ layer.dataset['vgaiRootSurface'] = 'true';
168
+ styleSiblingLayer(layer, world, 'none');
169
+ gameContainer.appendChild(layer);
170
+
171
+ // D-V3: one structured note per world naming the degrade — never silent
172
+ // about the missing bridge.
173
+ editorConsole.log(
174
+ `react sibling "${world.id}": no native Game beside an ingest world — useWorldState unavailable`,
175
+ 'ingest',
176
+ );
177
+
178
+ const root = createRoot(layer);
179
+ let caught: unknown = null;
180
+ // `flushSync` forces the initial mount (render + commit + error-boundary
181
+ // recovery, if the entry throws) to complete SYNCHRONOUSLY, so `caught` is
182
+ // reliably populated by the time this call returns — react-dom's default
183
+ // scheduler does not guarantee that for a bare `root.render()` call (a
184
+ // production API, not a test-only affordance: this host needs the throw/
185
+ // no-throw outcome immediately, to decide whether to keep the layer or
186
+ // tear it down and report a named failure).
187
+ flushSync(() => {
188
+ root.render(
189
+ createElement(
190
+ CrashNullBoundary,
191
+ {
192
+ onCaught: (error: unknown) => {
193
+ caught = error;
194
+ },
195
+ },
196
+ createElement(Entry),
197
+ ),
198
+ );
199
+ });
200
+
201
+ if (caught !== null) {
202
+ root.unmount();
203
+ layer.remove();
204
+ throw caught instanceof Error ? caught : new Error(String(caught));
205
+ }
206
+
207
+ // Once mounted, probe the REAL committed layer with `walkOidTree` — a
208
+ // partially-stamped tree counts as stamped (the walk's transparent-skip
209
+ // contract already handles instrumentation gaps), so this is a single "any
210
+ // node at all?" check, not a completeness one. A non-empty tree keeps the
211
+ // exact D-E1 construction below (a STAMPED first-party entry — the
212
+ // sanctioned JSX write-back seam). A genuinely EMPTY tree (no `data-oid`
213
+ // reachable at all — e.g. an ingest-REACT sibling swept into the D-Y4
214
+ // foreign-game exclusion, or an entry that rendered nothing yet) falls back
215
+ // to `DomAuthoringAdapter`: a LIVE-ONLY surface over the live DOM, no
216
+ // `SourceWriteBackend` of any kind — never a write seam over source the
217
+ // stamping pipeline declined to instrument. Only computed when a store was
218
+ // supplied (see `SiblingMount.authoring`'s doc comment) — never fabricated.
219
+ let authoring: ReactRootAuthoringAdapter | DomAuthoringAdapter | undefined;
220
+ if (store) {
221
+ const stamped = walkOidTree(layer).nodes.size > 0;
222
+ if (stamped) {
223
+ // D-E1: the exact play-mode react-branch construction (`play-mode.ts`
224
+ // `installMultiRootAuthoring` :341-347) over THIS sibling's own layer
225
+ // DOM root — no `<WorldProvider>`/`DomHostContext` required. Whether the
226
+ // recorder exists is what the HOST serves, never how the editor shell was
227
+ // built — `ui-source/tier-source-write-backend.ts`, and the packaged-
228
+ // editor bug the old `import.meta.env.DEV` gate carried here.
229
+ const writeBackend = sourceWriteBackendIfPrimed('An ingest sibling’s react layer');
230
+ authoring = new ReactRootAuthoringAdapter(layer, store, {
231
+ ...(writeBackend ? { writeBackend } : {}),
232
+ });
233
+ } else {
234
+ authoring = new DomAuthoringAdapter(layer, store, {
235
+ journal: authoringJournal(world.id),
236
+ });
237
+ }
238
+ }
239
+
240
+ return {
241
+ worldId: world.id,
242
+ ...(authoring ? { authoring } : {}),
243
+ dispose(): void {
244
+ root.unmount();
245
+ layer.remove();
246
+ },
247
+ };
248
+ }
249
+
250
+ /**
251
+ * D-V4/D-B3: mount a `{ module }` sibling (any kind) OR a native
252
+ * builtin `three`/`canvas` sibling through the SCOPED single-world
253
+ * runtime `module-mode.ts`'s `mountModuleRootRuntime` provides — the exact
254
+ * resolve+mount the primary module route performs, reused here with none
255
+ * of that route's global side effects: no `setActiveAuthoring`/
256
+ * `setActiveSystems`, no viewport-tab switch, no `__vgaiModule` hook, no
257
+ * mount-failure-report clear. Mounts into its OWN layer div (never straight
258
+ * into the shared `gameContainer` the way the primary/single-world routes
259
+ * do) so its internal canvas/DOM stacking never collides with the ingest
260
+ * world's own canvas/DOM layer already in that container.
261
+ *
262
+ * `mountModuleRootRuntime` is kind-agnostic BY CONSTRUCTION here, not just
263
+ * for `{ module }` adapters: it calls `resolveAllRoots` with a ONE-element
264
+ * `roots` array, and `resolveAllRoots`'s `MULTI_WORLD_ALLOWED_IDENTITIES`
265
+ * scope guard only fires for `roots.length > 1` — so `resolveRootBinding`'s
266
+ * full identity switch (including `three`/`canvas`) is reached
267
+ * directly, unmodified, with the guard never consulted (D-B3's ground-truth
268
+ * finding; `MULTI_WORLD_ALLOWED_IDENTITIES` itself stays byte-identical).
269
+ *
270
+ * D-B1: the returned `SiblingMount.setPaused` calls the scoped
271
+ * `GameSession.pause()`/`.resume()` `mountModuleRootRuntime` hands back —
272
+ * the SAME honest, already-existing capability `play-mode.ts`'s own
273
+ * `pausePlayMode`/`resumePlayMode` call on the PRIMARY route's session
274
+ * (`create-runtime.ts`'s `Game.play.pause()`/`.resume()`, gated per
275
+ * `pausable` world within this scoped, single-world `Game`) — never a
276
+ * fabricated no-op.
277
+ *
278
+ * D-G1 (closing the residual this doc comment used to name): the
279
+ * mounted world's authoring surface converges with play-mode's own
280
+ * multi-world construction (`play-mode.ts`'s `installMultiRootAuthoring`,
281
+ * verbatim per branch):
282
+ * 1. `mounted.authoring` present (a `{ module }` adapter that ships its own
283
+ * surface, with its own persistence if any) — kept byte-for-byte,
284
+ * store-selection-wrapped, exactly as before this slice. NEVER wrapped
285
+ * in an editor-owned adapter: the module's own choice wins.
286
+ * 2. Else `worldInstance.kind === 'three'` (a native `default-three`
287
+ * sibling, or a `{ module }` sibling that mounted a three world with no
288
+ * authoring of its own) — a real three authoring adapter over
289
+ * `worldInstance.threeScene()` with structural identity and creation-site
290
+ * persistence (`structuralThree`), whose edits are LIVE-ONLY unless
291
+ * every consent gate is open.
292
+ * 3. Else `kind === 'canvas'` — the canvas analog through
293
+ * `PixiAuthoringAdapter`, physics wired via
294
+ * `createPhysicsAdapter2D(worldInstance.physics2d ??
295
+ * createPhysics2DRegistry())` exactly like play-mode's own branch.
296
+ * 4. Else (no mounted.authoring, not a first-party-authorable kind) — the
297
+ * named `makeNoAuthoringAdapter` floor, unchanged.
298
+ * D-G3: `composite-authoring-adapter.ts`'s persistence routing (OR-dirty,
299
+ * per-child save, `' + '`-joined destinations) needs NO changes — it keys on
300
+ * `capabilities.persist`, and a live-only child simply never joins that set.
301
+ */
302
+ async function mountScopedRuntimeSibling(
303
+ manifest: ResolvedGameManifest,
304
+ world: ResolvedAdapterRoot,
305
+ gameContainer: HTMLElement,
306
+ projectRoot: string,
307
+ store: EditorShellStore | undefined,
308
+ ): Promise<SiblingMount> {
309
+ const layer = document.createElement('div');
310
+ styleSiblingLayer(layer, world, 'auto');
311
+ gameContainer.appendChild(layer);
312
+
313
+ try {
314
+ const w = gameContainer.clientWidth || 800;
315
+ const h = gameContainer.clientHeight || 600;
316
+ const { session } = await mountModuleRootRuntime(manifest, world, projectRoot, layer, w, h);
317
+ // D-G1: `session.game.roots[0]` is the SAME `RootInstance` shape
318
+ // play-mode iterates (`kind`, `threeScene()`, `canvasRoot()`, `physics2d`) —
319
+ // a single-element `resolveAllRoots` call always registers exactly one.
320
+ const worldInstance = session.game.roots[0]!;
321
+ const mountedAuthoring = worldInstance.mounted.authoring;
322
+ let authoring: AuthoringAdapter | undefined;
323
+ if (store) {
324
+ if (mountedAuthoring) {
325
+ // D-E1 (byte-stable): the module's own surface (and its own
326
+ // persistence, if any) wins — never wrap an editor-owned adapter
327
+ // around it.
328
+ authoring = withStoreSelection(mountedAuthoring, store);
329
+ } else if (worldInstance.kind === 'three') {
330
+ // `frameControl: 'host'` from this mount's own tick-ownership fact:
331
+ // the sibling's scoped `GameSession` drives the frame and this
332
+ // adapter is handed no `loop` handle that could stop it, so a
333
+ // mid-session gizmo edit must refuse rather than be overwritten on
334
+ // the next frame (omitting it fails OPEN — see
335
+ // `ThreeAuthoringOptions.frameControl`). The session's own
336
+ // `pause()`/`resume()` is deliberately NOT wired in as `loop`: it is
337
+ // gated per-world by `pausable`, so a `pausable: false` sibling would
338
+ // silently be back to the same overwritten edit.
339
+ authoring = structuralThree(store, worldInstance.threeScene(), {
340
+ frameControl: 'host',
341
+ journal: authoringJournal(world.id),
342
+ });
343
+ } else if (worldInstance.kind === 'canvas') {
344
+ if (
345
+ worldInstance.mounted.kind !== 'canvas' ||
346
+ worldInstance.mounted.substrate.name !== 'pixi'
347
+ ) {
348
+ authoring =
349
+ worldInstance.mounted.authoring ??
350
+ makeNoAuthoringAdapter(
351
+ store,
352
+ `${world.id} (${worldInstance.mounted.kind === 'canvas' ? worldInstance.mounted.substrate.name : 'unknown'} authoring unavailable)`,
353
+ );
354
+ } else {
355
+ const physics = createPhysicsAdapter2D(
356
+ worldInstance.physics2d ?? createPhysics2DRegistry(),
357
+ );
358
+ authoring = new PixiAuthoringAdapter(worldInstance.canvasRoot(), store, {
359
+ // A module root's own source mounts this stage, so it is the
360
+ // project graph's under the packaged runtime — see
361
+ // `../vite-plugin-module-doorways.ts`.
362
+ pixi: await resolveCanvasPixiForEditor(),
363
+ target: createLiveCanvasWriteTarget({ physics }),
364
+ journal: authoringJournal(world.id),
365
+ // The sibling's own layer IS its surface: `mountModuleRootRuntime`
366
+ // creates this world's canvas inside it, filling it exactly.
367
+ surface: () => layer,
368
+ });
369
+ }
370
+ } else {
371
+ authoring = makeNoAuthoringAdapter(store, `${world.id} (no authoring surface)`);
372
+ }
373
+ }
374
+ return {
375
+ worldId: world.id,
376
+ ...(authoring ? { authoring } : {}),
377
+ setPaused(paused: boolean): void {
378
+ if (paused) session.pause();
379
+ else session.resume();
380
+ },
381
+ dispose(): void {
382
+ session.stop();
383
+ layer.remove();
384
+ },
385
+ };
386
+ } catch (err) {
387
+ layer.remove();
388
+ throw err;
389
+ }
390
+ }
391
+
392
+ /**
393
+ * D-V7 (updated by D-B3): a sibling reaching here is neither a builtin `dom`
394
+ * world nor routable through the scoped runtime above (`{ module }` of any
395
+ * kind, or a builtin `three`/`canvas` world) — a genuinely unroutable shape.
396
+ * No such shape is known to exist today (every currently-resolvable adapter
397
+ * identity is handled above); this is a named, loud non-goal for a
398
+ * hypothetical future one, never silently ignored (the F24 anti-pattern) and
399
+ * never half-mounted. (A second `{ ingest }` world is structurally
400
+ * unreachable here — D-V2's route-level guard already fails the whole
401
+ * composite before any sibling loop runs.)
402
+ */
403
+ function rejectUnmountableSibling(world: ResolvedAdapterRoot): never {
404
+ throw new Error(
405
+ `sibling world "${world.id}" (kind: ${world.surface}, adapter identity: ` +
406
+ `"${world.adapter.identity}") has no known composite-sibling mount path — every ` +
407
+ 'currently-resolvable identity (three/canvas/dom/module) is ' +
408
+ 'routed above; this is reached only for a genuinely unroutable shape.',
409
+ );
410
+ }
411
+
412
+ /** Which mount shape a sibling world takes, by adapter identity alone.
413
+ *
414
+ * Split out of {@link mountOneIngestSibling} so the routing TRUTH is
415
+ * checkable without mounting anything: the builtin `canvas` identity used to
416
+ * fall through to {@link rejectUnmountableSibling} while this module's own
417
+ * doc comment and that function's thrown message both named it as routed.
418
+ * There was no mechanism gap behind that — `mountScopedRuntimeSibling` calls
419
+ * `resolveAllRoots` with a ONE-element array, so `resolveRootBinding`'s full
420
+ * identity switch (including `canvas`) is reached unmodified, and the mount's
421
+ * own authoring branch already had a `kind === 'canvas'` case waiting for a
422
+ * world that could never arrive.
423
+ *
424
+ * `null` ⇒ genuinely unroutable (the caller rejects loudly). */
425
+ export function siblingMountRoute(
426
+ world: ResolvedAdapterRoot,
427
+ ): 'scoped-runtime' | 'react-layer' | null {
428
+ if (world.adapter.type === 'module') return 'scoped-runtime';
429
+ if (world.adapter.identity === 'three' || world.adapter.identity === 'canvas') {
430
+ return 'scoped-runtime';
431
+ }
432
+ if (world.adapter.identity === 'dom') return 'react-layer';
433
+ return null;
434
+ }
435
+
436
+ async function mountOneIngestSibling(
437
+ manifest: ResolvedGameManifest,
438
+ world: ResolvedAdapterRoot,
439
+ gameContainer: HTMLElement,
440
+ projectRoot: string,
441
+ store: EditorShellStore | undefined,
442
+ ): Promise<SiblingMount> {
443
+ switch (siblingMountRoute(world)) {
444
+ case 'scoped-runtime':
445
+ return mountScopedRuntimeSibling(manifest, world, gameContainer, projectRoot, store);
446
+ case 'react-layer':
447
+ return mountDefaultReactSibling(world, gameContainer, projectRoot, store);
448
+ default:
449
+ rejectUnmountableSibling(world);
450
+ }
451
+ }
452
+
453
+ /**
454
+ * D-V2: mount every world in `manifest.roots` OTHER than the primary ingest
455
+ * world (`ingestRootId`), in manifest order (already z-sorted by the
456
+ * loader). A failing sibling never unmounts the ingest world or stops the
457
+ * loop — it is caught, reported (D-V5's multi-entry mount-failure report,
458
+ * deduped by worldId), and the loop continues to the next sibling.
459
+ */
460
+ export async function mountIngestSiblings(
461
+ manifest: ResolvedGameManifest,
462
+ ingestRootId: string,
463
+ gameContainer: HTMLElement,
464
+ projectRoot: string,
465
+ // D-E1: optional ONLY so the one pre-existing direct caller
466
+ // (`ingest-composite-sibling-pause.test.ts`, which never inspects
467
+ // `SiblingMount.authoring`) keeps compiling/running unchanged — every
468
+ // production caller (`ingest/mount-ingest-root.ts`'s composite routes) supplies one.
469
+ store?: EditorShellStore,
470
+ ): Promise<SiblingMount[]> {
471
+ const mounted: SiblingMount[] = [];
472
+ for (const world of declaredRoots(manifest)) {
473
+ if (world.id === ingestRootId) continue;
474
+ try {
475
+ mounted.push(await mountOneIngestSibling(manifest, world, gameContainer, projectRoot, store));
476
+ } catch (err) {
477
+ editorConsole.error(`Composite sibling "${world.id}" failed to mount: ${err}`, 'ingest');
478
+ addMountFailureReport({
479
+ worldId: world.id,
480
+ kind: world.surface,
481
+ identity: world.adapter.identity,
482
+ message: formatMountFailureMessage(err),
483
+ });
484
+ }
485
+ }
486
+ return mounted;
487
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * The INGEST LANE's status facet — what `session.reportFacet('ingest')`
3
+ * publishes about a mounted unmodified game (`live-ingest-facet.ts` builds it).
4
+ *
5
+ * The derivation of the SESSION's reported play state is NOT here: it names no
6
+ * lane and is the host's (`@editor/reported-play-state.ts`, asked of the
7
+ * live-session registry). This module is only the lane's own description of
8
+ * what it mounted.
9
+ */
10
+
11
+ import type { CapabilityCoverageReport } from '../host/coverage/capability-coverage';
12
+ import type { MeasuredLoop } from '../host/same-realm-loop-gate';
13
+
14
+ /**
15
+ * The live ingest session, as the control API reports it. Every field is read
16
+ * from the session itself — never inferred from the manifest or the route.
17
+ *
18
+ * The facet's mere PRESENCE is the mount truth: `ingest/active-ingest.ts` assigns its
19
+ * `_active` slot only after a mount actually succeeded, and clears it on every
20
+ * teardown and every failure path. There is deliberately no `mounted: true`
21
+ * field — a field that can never be false is not evidence.
22
+ */
23
+ export interface IngestStatusFacet {
24
+ /** The mounted world's manifest id. */
25
+ readonly worldId: string;
26
+ /** The session's render substrate (`ingest/active-ingest.ts`'s `IngestKind`). */
27
+ readonly kind: string;
28
+ /** `IngestPlayControl.playing` — whether the game's loop was last told to run. */
29
+ readonly playing: boolean;
30
+ /**
31
+ * The MEASURED loop verdict WITH its evidence
32
+ * (`same-realm-loop-gate.ts`'s `MeasuredLoop`), or `null` when no probe
33
+ * has run yet.
34
+ *
35
+ * Never inferred from the manifest — and now structurally unable to be: the
36
+ * manifest declares the SAME two words as an author's intent
37
+ * (`@volter/editor-project/manifest/schema`'s `loop`), so a bare string here left a
38
+ * declaration and a measurement indistinguishable to every reader of this
39
+ * facet. Evidence is producible only by the probe.
40
+ */
41
+ readonly loop: MeasuredLoop | null;
42
+ /** Why — the probe's own words, so a reader can see what was measured rather
43
+ * than take the one-word verdict on faith. */
44
+ readonly loopReason: string | null;
45
+ /** Whether a single-frame step is a real operation on this mount. */
46
+ readonly canStep: boolean;
47
+ /**
48
+ * WHAT THE EDITOR CANNOT DO with this game, and the mechanism that would fix
49
+ * each gap — the derived seam report (`coverage/capability-coverage.ts`). Rides
50
+ * along on the facet so a CLI reader gets the same answer the editor console
51
+ * printed at mount, instead of "the editor just has less" arriving as silence
52
+ * in every door at once.
53
+ *
54
+ * Re-derived on every read (the loop verdict, the ownership answer and the
55
+ * registered adapters all move after mount), so it is never a stale claim.
56
+ *
57
+ * `null` means NOT MEASURED — the coverage inputs are recorded by the three
58
+ * mount paths, so a pixi/react ingest has no report yet rather than a
59
+ * fabricated clean one. Absence of a report is not a report of no gaps.
60
+ */
61
+ readonly coverage: CapabilityCoverageReport | null;
62
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * The live session as the control API reports it — the assembly half of
3
+ * `ingest/ingest-status.ts`.
4
+ *
5
+ * It lives beside that module rather than inside it because `ingest-status.ts`
6
+ * is deliberately PURE (it derives a reported play state from facts a caller
7
+ * hands it, and imports no session state, which is what makes its whole
8
+ * decision unit-testable with no browser). This half is the opposite by
9
+ * nature: it reads the live singletons.
10
+ */
11
+
12
+ import { measuredLoop } from '../host/same-realm-loop-gate';
13
+ import { activeIngest } from './active-ingest';
14
+ import { getIngestPlayControl, ingestPlaying } from './ingest-play-control';
15
+ import type { IngestStatusFacet } from './ingest-status';
16
+ import { ingestCoverageReport } from './mount-coverage';
17
+
18
+ /**
19
+ * S-1 (the SimCity ledger's false-alive status): the live ingest session as the
20
+ * control API reports it, or `null` when nothing is ingested.
21
+ *
22
+ * Read straight off the session slot, which is assigned only after a mount
23
+ * SUCCEEDS and cleared by every teardown and failure path — so a non-null facet
24
+ * is itself the mount evidence, and a failed mount can no longer present itself
25
+ * as a live one. `loop` is the MEASURED verdict or `null`; it is never inferred
26
+ * from the route.
27
+ */
28
+ export function ingestStatusFacet(): IngestStatusFacet | null {
29
+ const active = activeIngest();
30
+ if (!active) return null;
31
+ // The MEASURED verdict. `null` means no verdict exists yet (the probe runs
32
+ // at pause), never "this route does not gate".
33
+ const mount = active.kind === 'three' ? active.session.mount : undefined;
34
+ const canvasLoop = active.kind === 'canvas' ? active.session.loop?.() : undefined;
35
+ const loop = measuredLoop(mount?.realmLoopVerdict?.()) ?? canvasLoop?.loop ?? null;
36
+ const loopReason = mount?.realmLoopVerdict?.()?.reason ?? canvasLoop?.reason ?? null;
37
+ return {
38
+ worldId: active.worldId,
39
+ kind: active.kind,
40
+ playing: ingestPlaying(),
41
+ loop,
42
+ loopReason,
43
+ canStep: getIngestPlayControl()?.canStep ?? false,
44
+ coverage: ingestCoverageReport(),
45
+ };
46
+ }