@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,162 @@
1
+ /**
2
+ * The react-world mount's OWN react — the sixth realm doorway, finally living
3
+ * beside its five siblings (`r3f-entry-runtime`, `canvas-entry-runtime`,
4
+ * `three-ingest-runtime`, `story-dom-runtime`, `story-three-preview-runtime`)
5
+ * instead of inside the root resolver.
6
+ *
7
+ * Every one of those modules answers the same question for its own subject:
8
+ * *whose copy of this library do we build with?* Under dev the answer
9
+ * is always the editor's own static imports — one shared Vite instance plus the
10
+ * root `vite.config.ts`'s `resolve.dedupe` already collapse the editor's and
11
+ * the project's react onto one instance. Under the PACKAGED runtime the editor
12
+ * shell is a prebuilt static bundle with its own baked-in `react`/`react-dom`,
13
+ * disconnected from the separate project-rooted Vite instance the entry
14
+ * component's hooks resolve `react` through — mounting with the shell's copy
15
+ * produces two react instances and `useContext` throws on a `null` dispatcher
16
+ * (confirmed live; see `packaged.ts`'s header).
17
+ */
18
+
19
+ import { WorldProvider as EngineWorldProvider } from '@volter/game-runtime/react/world-state';
20
+ import type { Game } from '@volter/game-runtime/runtime/game';
21
+ import {
22
+ type ComponentType,
23
+ createElement,
24
+ useEffect as reactUseEffect,
25
+ useRef as reactUseRef,
26
+ } from 'react';
27
+ import { flushSync } from 'react-dom';
28
+ import { createRoot } from 'react-dom/client';
29
+ import { REACT_WORLD_RUNTIME_PATH } from '@volter/editor-sdk/host';
30
+ import { isPackagedRuntime } from '@volter/editor-core/packaged-runtime';
31
+
32
+ /**
33
+ * The pieces of "react itself" a react-world mount needs:
34
+ * `createElement`/`createRoot` (to build + render the `<WorldProvider><Entry/>
35
+ * </WorldProvider>` tree) and the engine-published `WorldProvider`.
36
+ */
37
+ export interface ReactRootMountRuntime {
38
+ createElement: typeof createElement;
39
+ createRoot: typeof createRoot;
40
+ useEffect: typeof reactUseEffect;
41
+ useRef: typeof reactUseRef;
42
+ /** `react-dom`'s synchronous-flush escape hatch — the design-time react
43
+ * layer mount needs it from the SAME react-dom peer as `createRoot` above
44
+ * (both are entry points of the same installed package). */
45
+ flushSync: typeof flushSync;
46
+ engineWorldProvider: ComponentType<{ game: Game; children?: unknown }>;
47
+ }
48
+
49
+ /** The dev default: this module's own static imports. */
50
+ function staticReactRootMountRuntime(): ReactRootMountRuntime {
51
+ return {
52
+ createElement,
53
+ createRoot,
54
+ useEffect: reactUseEffect,
55
+ useRef: reactUseRef,
56
+ flushSync,
57
+ engineWorldProvider: EngineWorldProvider as unknown as ComponentType<{
58
+ game: Game;
59
+ children?: unknown;
60
+ }>,
61
+ };
62
+ }
63
+
64
+ /**
65
+ * Import + shape-check the packaged runtime's synthetic
66
+ * `/__vgai-react-world-runtime` module (`vite-plugin-module-doorways.ts`,
67
+ * registered only by `packaged.ts`'s project-rooted Vite instance and by
68
+ * `vitest.config.ts` for test coverage — never by `dev.ts`/the root
69
+ * `vite.config.ts`). A loud, named failure if the module resolves but is
70
+ * malformed — never a silent fall-through that would re-introduce the dual-
71
+ * instance bug it exists to prevent.
72
+ */
73
+ async function loadPackagedReactRootMountRuntime(): Promise<ReactRootMountRuntime> {
74
+ const mod = (await import(/* @vite-ignore */ REACT_WORLD_RUNTIME_PATH)) as {
75
+ createElement?: unknown;
76
+ createRoot?: unknown;
77
+ useEffect?: unknown;
78
+ useRef?: unknown;
79
+ flushSync?: unknown;
80
+ EngineWorldProvider?: unknown;
81
+ };
82
+ if (
83
+ typeof mod.createElement !== 'function' ||
84
+ typeof mod.createRoot !== 'function' ||
85
+ typeof mod.useEffect !== 'function' ||
86
+ typeof mod.useRef !== 'function' ||
87
+ typeof mod.flushSync !== 'function' ||
88
+ typeof mod.EngineWorldProvider !== 'function'
89
+ ) {
90
+ throw new Error(
91
+ "The packaged runtime's synthetic react-world-runtime module " +
92
+ `("${REACT_WORLD_RUNTIME_PATH}") did not export createElement/createRoot/useEffect/useRef/flushSync/` +
93
+ 'EngineWorldProvider as functions — see vite-plugin-module-doorways.ts.',
94
+ );
95
+ }
96
+ return {
97
+ createElement: mod.createElement as typeof createElement,
98
+ createRoot: mod.createRoot as typeof createRoot,
99
+ useEffect: mod.useEffect as typeof reactUseEffect,
100
+ useRef: mod.useRef as typeof reactUseRef,
101
+ flushSync: mod.flushSync as typeof flushSync,
102
+ engineWorldProvider: mod.EngineWorldProvider as ComponentType<{
103
+ game: Game;
104
+ children?: unknown;
105
+ }>,
106
+ };
107
+ }
108
+
109
+ let cachedPackagedReactRootMountRuntime: Promise<ReactRootMountRuntime> | null = null;
110
+
111
+ /**
112
+ * Resolve the react-world mount's own react. A react world is an ISOLATED
113
+ * `createRoot` (its own DOM subtree / react tree) that does NOT need to share
114
+ * react with the editor's own UI — it only needs to be internally consistent
115
+ * (the wrapper + `WorldProvider` + the project's entry component all sharing
116
+ * ONE react instance), which is exactly what the packaged branch restores.
117
+ *
118
+ * Memoized twice over: `isPackagedRuntime()` is itself session-memoized, and
119
+ * the packaged module load is cached here, so this performs at most one
120
+ * `/__editor/project` fetch and one dynamic import per editor session however
121
+ * many react roots mount.
122
+ */
123
+ export async function resolveReactRootMountRuntime(): Promise<ReactRootMountRuntime> {
124
+ const packaged = await isPackagedRuntime();
125
+ if (!packaged) return staticReactRootMountRuntime();
126
+ if (!cachedPackagedReactRootMountRuntime) {
127
+ cachedPackagedReactRootMountRuntime = loadPackagedReactRootMountRuntime();
128
+ }
129
+ return cachedPackagedReactRootMountRuntime;
130
+ }
131
+
132
+ /**
133
+ * `WorldProvider` is the canonical engine export from
134
+ * `@volter/game-runtime/react/world-state`, reached through the mount runtime above so
135
+ * the project's copy is used under the packaged runtime. Hosts and game
136
+ * entries therefore share ONE module and one React context identity.
137
+ *
138
+ * WHY THIS IS AN ENGINE EXPORT AND NOT A PER-PROJECT FILE — a finding verified
139
+ * empirically, kept because the trap it describes is still live for anyone
140
+ * tempted to reintroduce a project-local provider. Back when each project
141
+ * owned its own bridge module, a hand-built runtime `import('/@fs/' +
142
+ * projectRoot + '/…')` for it did NOT reliably land on the same module
143
+ * instance a react-world entry's own STATIC relative import resolved to:
144
+ * Vite's dev server treated the two differently at the HTTP level (a
145
+ * query-string divergence, confirmed by logging `import.meta.url` inside two
146
+ * otherwise-identical fetches) even though both targeted the identical file on
147
+ * disk, and browser ES module identity is keyed by the exact request URL. That
148
+ * alone produced TWO separate `createContext()` calls. The whole apparatus is
149
+ * gone: one engine module, no per-project path to diverge on.
150
+ */
151
+ export async function resolveWorldProviderForProject(): Promise<
152
+ ComponentType<{ game: Game; children?: unknown }>
153
+ > {
154
+ const runtime = await resolveReactRootMountRuntime();
155
+ return runtime.engineWorldProvider;
156
+ }
157
+
158
+ /** Test-only: reset the packaged react-world mount runtime cache between
159
+ * tests that toggle `isPackagedRuntime()`'s own (separately memoized) result. */
160
+ export function resetReactRootMountRuntimeCacheForTest(): void {
161
+ cachedPackagedReactRootMountRuntime = null;
162
+ }
@@ -0,0 +1,148 @@
1
+ /**
2
+ * THE REALM — where this editor session's project modules come from, as ONE
3
+ * value instead of a three-way branch repeated in every root resolver.
4
+ *
5
+ * ## The fork this closes
6
+ *
7
+ * `resolveDefaultThreeAdapter`, `resolveDefaultCanvasAdapter` and
8
+ * `resolveReactAdapterRootComponent` each carried their OWN copy of the same
9
+ * question, and the copies had already drifted. There is one dispatch now, and
10
+ * it is {@link RealmServices.loadEntryModule}.
11
+ *
12
+ * ## ONE LOADING LANE, TWO RUNTIMES
13
+ *
14
+ * A project module is fetched ONE way — `/@fs/` through the session's own dev
15
+ * server — because the session IS the editor's host. What varies is the
16
+ * RUNTIME: whose copy of react/fiber/pixi/three the mount builds with, the
17
+ * editor SHELL's or the PROJECT's own module graph.
18
+ *
19
+ * dev = fs × shell
20
+ * packaged = fs × project graph
21
+ *
22
+ * ## What is here, and what is deliberately NOT
23
+ *
24
+ * The RUNTIME axis is already single-owner per subject — `r3f-entry-runtime`,
25
+ * `canvas-entry-runtime`, `react-mount-runtime`, `three-ingest-runtime` each
26
+ * decide it once, for everyone, from `isPackagedRuntime()`. This module names
27
+ * the ones root binding actually opens so a resolver asks the realm rather
28
+ * than importing four doorways by hand; it does not re-implement them, and it
29
+ * does not list the two STORY doorways (`story-dom-runtime`,
30
+ * `story-three-preview-runtime`) — their consumers are story/preview surfaces,
31
+ * not root binding, so a member here would have no caller.
32
+ *
33
+ * Ingest's served-bundle-vs-`/@fs/` choice is likewise NOT here: it is a
34
+ * per-PROJECT property (how that one game was vendored), so two ingested games
35
+ * open in the same realm can answer differently. It lives with the ingest code
36
+ * (`ingest/resolve-three.ts`).
37
+ *
38
+ * ## Reject, never guess
39
+ *
40
+ * Construction AWAITS `isPackagedRuntime()`, so a probe that could not get an
41
+ * answer REJECTS through this function to the mount site. Nothing here spends
42
+ * "I don't know" as "dev" — see `packaged-runtime.ts`'s header for the
43
+ * measured session-long mis-mount that latch caused.
44
+ */
45
+
46
+ import {
47
+ type EntrypointSelectionOverride,
48
+ type ProjectMountEpoch,
49
+ projectEntryImportUrl,
50
+ } from '@volter/editor-sdk/session/project-module-url';
51
+ import type { AdapterSurface } from '@volter/editor-project/adapter/adapter-surface';
52
+ import { resolveCanvasEntryAdapterForEditor } from './canvas-entry-runtime';
53
+ import { isPackagedRuntime } from '@volter/editor-core/packaged-runtime';
54
+ import { type ReactRootMountRuntime, resolveReactRootMountRuntime } from './react-mount-runtime';
55
+ import {
56
+ resolveThreeIngestRuntimeForEditor,
57
+ type ThreeIngestRuntime,
58
+ } from './three-ingest-runtime';
59
+
60
+ /** Which of the two this session is in. Reported, never inferred twice. */
61
+ export type RealmId = 'dev' | 'packaged';
62
+
63
+ /**
64
+ * The realm's services, bound to ONE project root and ONE mount epoch — a
65
+ * realm value is "how THIS project's modules are reached in THIS mount
66
+ * generation", which is why it is constructed per resolve rather than being a
67
+ * module-level singleton.
68
+ */
69
+ export interface RealmServices {
70
+ readonly id: RealmId;
71
+ /** The project this realm was constructed for. */
72
+ readonly projectRoot: string;
73
+ /** The mount generation every module url of this composition shares. */
74
+ readonly epoch: ProjectMountEpoch;
75
+ /**
76
+ * Import one root's entry module.
77
+ *
78
+ * `rootId` and `surface` identify the root for the caller's own errors;
79
+ * dispatch is the entry PATH. `selectionOverride` is the dev server's
80
+ * entrypoint swap slot, rewritten at serve time.
81
+ */
82
+ loadEntryModule(
83
+ entry: string,
84
+ rootId: string,
85
+ surface: AdapterSurface,
86
+ selectionOverride?: EntrypointSelectionOverride | undefined,
87
+ ): Promise<Record<string, unknown>>;
88
+ /** The entry namespace this realm hands back is the module's WHOLE
89
+ * namespace — the honest input to `RootDeclaration.entry`'s `reach`
90
+ * discriminant, which kept a second value while a bundle-map lane
91
+ * existed. */
92
+ readonly entryReach: 'full';
93
+ /** The canvas doorway: turn a canvas entry namespace into its adapter. */
94
+ readonly pixiRuntime: typeof resolveCanvasEntryAdapterForEditor;
95
+ /** The react-world mount's own react/react-dom/WorldProvider. */
96
+ readonly reactDomRuntime: () => Promise<ReactRootMountRuntime>;
97
+ /** The three-ingest doorway: the namespace an ingested three game captures
98
+ * against. */
99
+ readonly threeIngestRuntime: () => Promise<ThreeIngestRuntime>;
100
+ }
101
+
102
+ const SHARED_RUNTIMES = {
103
+ pixiRuntime: resolveCanvasEntryAdapterForEditor,
104
+ reactDomRuntime: resolveReactRootMountRuntime,
105
+ threeIngestRuntime: resolveThreeIngestRuntimeForEditor,
106
+ } as const;
107
+
108
+ /**
109
+ * dev AND packaged: `/@fs/` through the dev server, always with the mount
110
+ * epoch's cache-buster (`projectEntryImportUrl` — never a bare url; a
111
+ * byte-identical specifier is served out of the browser's module registry on
112
+ * remount while its cache-busted siblings are re-transformed, leaving two live
113
+ * copies of one module-level `let` and no error).
114
+ */
115
+ function fsLoader(projectRoot: string, epoch: ProjectMountEpoch) {
116
+ return async (
117
+ entry: string,
118
+ _rootId: string,
119
+ _surface: AdapterSurface,
120
+ selectionOverride?: EntrypointSelectionOverride | undefined,
121
+ ): Promise<Record<string, unknown>> =>
122
+ (await import(
123
+ /* @vite-ignore */ projectEntryImportUrl(projectRoot, entry, epoch, selectionOverride)
124
+ )) as Record<string, unknown>;
125
+ }
126
+
127
+ /**
128
+ * Construct this session's realm for one project and one mount epoch.
129
+ *
130
+ * REJECTS rather than guessing when `isPackagedRuntime()` could not get an
131
+ * answer — see this module's header.
132
+ */
133
+ export async function activeRealmServices(
134
+ projectRoot: string,
135
+ epoch: ProjectMountEpoch,
136
+ ): Promise<RealmServices> {
137
+ // The LOADING axis is `/@fs/` either way; only the runtime doorways differ,
138
+ // and each of those decides for itself from the same probe.
139
+ const packaged = await isPackagedRuntime();
140
+ return {
141
+ id: packaged ? 'packaged' : 'dev',
142
+ projectRoot,
143
+ epoch,
144
+ loadEntryModule: fsLoader(projectRoot, epoch),
145
+ entryReach: 'full',
146
+ ...SHARED_RUNTIMES,
147
+ };
148
+ }
@@ -0,0 +1,106 @@
1
+ /** Two-Hz filmstrip encoding. HTMLCanvasElement.toBlob can synchronously flush
2
+ * GPU work before returning; keeping only its callback async does not protect
3
+ * the game loop. Transfer an ImageBitmap; draw/downscale and encode in a worker.
4
+ * One pending image bounds memory. Stop settles it and closes every owned bitmap.
5
+ * Unsupported workers/canvases report a preview error; never silently fall back
6
+ * to blocking the main thread. The video recorder can continue without previews.
7
+ */
8
+ export function createRecordingPreviewEncoder() {
9
+ let worker: Worker | null = null;
10
+ let stopped = false;
11
+ let pending: { resolve: (blob: Blob) => void; reject: (error: Error) => void } | null = null;
12
+ const source = `
13
+ let canvas;
14
+ onmessage = async ({data}) => {
15
+ const {bitmap, width, height, type, quality} = data;
16
+ try {
17
+ if (!canvas) canvas = new OffscreenCanvas(width, height);
18
+ if (canvas.width !== width) canvas.width = width;
19
+ if (canvas.height !== height) canvas.height = height;
20
+ const context = canvas.getContext('2d');
21
+ if (!context) throw new Error('No worker canvas context');
22
+ context.globalCompositeOperation = 'copy';
23
+ context.drawImage(bitmap, 0, 0, width, height);
24
+ bitmap.close();
25
+ const blob = await canvas.convertToBlob({type, quality});
26
+ postMessage({blob});
27
+ } catch (error) {
28
+ bitmap.close();
29
+ postMessage({error: error instanceof Error ? error.message : String(error)});
30
+ }
31
+ };
32
+ `;
33
+ function fail(error: Error) {
34
+ const job = pending;
35
+ pending = null;
36
+ worker?.terminate();
37
+ worker = null;
38
+ job?.reject(error);
39
+ }
40
+ return {
41
+ encode(
42
+ canvas: HTMLCanvasElement,
43
+ width: number,
44
+ height: number,
45
+ type: string,
46
+ quality: number,
47
+ ): Promise<Blob> {
48
+ if (stopped) return Promise.reject(new Error('Recording preview encoder stopped'));
49
+ if (pending) return Promise.reject(new Error('Recording preview already in flight'));
50
+ return new Promise<Blob>((resolve, reject) => {
51
+ const job = { resolve, reject };
52
+ pending = job;
53
+ try {
54
+ if (!worker) {
55
+ const url = URL.createObjectURL(new Blob([source], { type: 'text/javascript' }));
56
+ try {
57
+ worker = new Worker(url);
58
+ } finally {
59
+ URL.revokeObjectURL(url);
60
+ }
61
+ worker.onmessage = ({ data }: MessageEvent<{ blob?: Blob; error?: string }>) => {
62
+ if (data.error || !data.blob) {
63
+ fail(new Error(data.error ?? 'No recording preview image'));
64
+ return;
65
+ }
66
+ const job = pending;
67
+ pending = null;
68
+ job?.resolve(data.blob);
69
+ };
70
+ worker.onerror = (event) => {
71
+ event.preventDefault();
72
+ fail(new Error(event.message || 'Recording preview worker failed'));
73
+ };
74
+ worker.onmessageerror = () =>
75
+ fail(new Error('Recording preview worker message failed'));
76
+ }
77
+ // Snapshot cost belongs in the main-thread profile too. Do not replace
78
+ // this with toDataURL/getImageData: those force synchronous readback.
79
+ createImageBitmap(canvas).then(
80
+ (bitmap) => {
81
+ if (stopped || pending !== job || !worker) {
82
+ bitmap.close();
83
+ return;
84
+ }
85
+ try {
86
+ worker.postMessage({ bitmap, width, height, type, quality }, [bitmap]);
87
+ } catch (error) {
88
+ bitmap.close();
89
+ fail(error instanceof Error ? error : new Error(String(error)));
90
+ }
91
+ },
92
+ (error) => {
93
+ if (pending === job) fail(error instanceof Error ? error : new Error(String(error)));
94
+ },
95
+ );
96
+ } catch (error) {
97
+ fail(error instanceof Error ? error : new Error(String(error)));
98
+ }
99
+ });
100
+ },
101
+ dispose() {
102
+ stopped = true;
103
+ fail(new Error('Recording preview encoder stopped'));
104
+ },
105
+ };
106
+ }
@@ -0,0 +1,203 @@
1
+ /**
2
+ * A `{ module }` root: the project's OWN adapter module is the mechanism.
3
+ *
4
+ * The editor imports the game folder's declared module (path-guarded before,
5
+ * shape-checked after — both loud, named failures, never a silent `undefined`
6
+ * or a crash deep inside `mount`), then wraps its `mount` so that what the
7
+ * editor actually REACHES is measured off the real mounted surface and every
8
+ * capability short of a native root's is warned about by name.
9
+ */
10
+
11
+ import {
12
+ type ProjectMountEpoch,
13
+ projectEntryImportUrl,
14
+ } from '@volter/editor-sdk/session/project-module-url';
15
+ import { relativePathRejection } from '@volter/editor-sdk/session/relative-path-guard';
16
+ import type { RootAdapter, SurfaceAdapter } from '@volter/editor-project/adapter';
17
+ import type { AdapterSurface } from '@volter/editor-project/adapter/adapter-surface';
18
+ import { assertNever } from '@volter/editor-project/adapter/adapter-surface';
19
+ import type { HostContextFor } from '@volter/editor-project/adapter/host-context';
20
+ import type { MountedRootFor } from '@volter/editor-project/adapter/root-adapter';
21
+ import type { ResolvedAdapterRoot } from '@volter/editor-project/manifest/load';
22
+ import { formatCapabilityWarning, measureAdapterReach } from '../adapter-reach';
23
+
24
+ /**
25
+ * Validate `adapter.module` is a game-folder-RELATIVE path with no `..`
26
+ * escape. The rule itself is `relative-path-guard.ts`'s
27
+ * `relativePathRejection` — the ONE spelling every trust boundary in the
28
+ * editor shares. Only the MESSAGES are local: a manifest author needs to be
29
+ * told which declaration is wrong.
30
+ *
31
+ * Returns the normalized (slash-joined, no leading/trailing slash) path — the
32
+ * one thing the shared predicate does not do, because normalization is not a
33
+ * trust decision.
34
+ */
35
+ export function guardModulePath(world: ResolvedAdapterRoot, moduleId: string): string {
36
+ const fail = (reason: string): never => {
37
+ throw new Error(
38
+ `resolveRootBinding: world "${world.id}" declares { module: "${moduleId}" } — ${reason} ` +
39
+ '(the module path must be game-folder-relative, normalized, with no `..` escape).',
40
+ );
41
+ };
42
+
43
+ const rejection = relativePathRejection(moduleId);
44
+ if (rejection === 'empty') fail('the module path is empty');
45
+ if (rejection === 'nul') fail('the module path contains a NUL byte');
46
+ if (rejection === 'absolute') fail('the module path is absolute, not game-folder-relative');
47
+ if (rejection === 'traversal') {
48
+ fail('the module path contains a `..` segment and would escape the game folder');
49
+ }
50
+ // POSIX-only beyond the shared rule: a module id reaches Vite as a URL.
51
+ if (moduleId.includes('\\')) fail('the module path contains a backslash (not POSIX-relative)');
52
+
53
+ const normalized = moduleId
54
+ .split('/')
55
+ .filter((seg) => seg.length > 0 && seg !== '.')
56
+ .join('/');
57
+ if (normalized === '') fail('the module path resolves to nothing (empty after normalization)');
58
+
59
+ return normalized;
60
+ }
61
+
62
+ /**
63
+ * Runtime shape every `RootAdapter` must satisfy — the module branch cannot
64
+ * trust TypeScript alone (the imported value is `unknown` until checked).
65
+ *
66
+ * Parameterized by surface: the checked SHAPE is surface-independent
67
+ * (`{ id, mount }` are the same members for every `K`), so the caller passes
68
+ * the world's DECLARED surface and gets a `RootAdapter<K>` back. The surface
69
+ * itself is a manifest CLAIM no structural check can verify — that is exactly
70
+ * what {@link withCapabilityWarning} measures post-mount, against the real
71
+ * mounted root, and what `module-mode.ts` re-checks against the mounted
72
+ * result's own `kind` tag.
73
+ */
74
+ function guardModuleShape<K extends AdapterSurface>(
75
+ world: ResolvedAdapterRoot,
76
+ moduleId: string,
77
+ candidate: unknown,
78
+ ): asserts candidate is RootAdapter<K> {
79
+ const fail = (reason: string): never => {
80
+ throw new Error(
81
+ `resolveRootBinding: world "${world.id}"'s custom adapter module "${moduleId}" ${reason} ` +
82
+ '— a { module } adapter must default-export an object shaped like `RootAdapter`: ' +
83
+ '`{ id: string, mount(host): Promise<MountedThreeRoot> }`.',
84
+ );
85
+ };
86
+
87
+ if (candidate === null || typeof candidate !== 'object') {
88
+ fail('has no default export (or it is not an object)');
89
+ }
90
+ const obj = candidate as Record<string, unknown>;
91
+ if (typeof obj['id'] !== 'string' || obj['id'].length === 0) {
92
+ fail('default-exports an object with no non-empty string `id`');
93
+ }
94
+ if (typeof obj['mount'] !== 'function') {
95
+ fail('default-exports an object with no `mount` function');
96
+ }
97
+ }
98
+
99
+ /** How a `{ module }` world reaches the editor: the project's own adapter
100
+ * module IS the mechanism, so anything missing is missing from that module. */
101
+ const MODULE_ADAPTER_MECHANISM =
102
+ 'this world mounts through the project’s own adapter module, so every capability the editor ' +
103
+ 'has here is one that module hands it';
104
+
105
+ /**
106
+ * Wrap a custom adapter's `mount` so that, once it resolves, what the editor
107
+ * actually REACHES is measured off the real mounted surface. There is nothing
108
+ * to compare against and nothing to declare: the bar is a native root, and a
109
+ * gap keeps warning until it is closed. The warning never blocks the mount.
110
+ *
111
+ * The `editorConsole.warn(...)` call is a LAZY dynamic import: this resolver is
112
+ * imported by headless Node/vitest suites with no `window`, and
113
+ * `editor-console.ts`'s module top level touches `window` unconditionally.
114
+ */
115
+ function withCapabilityWarning<K extends AdapterSurface>(
116
+ world: ResolvedAdapterRoot,
117
+ adapter: RootAdapter<K>,
118
+ ): RootAdapter<K> {
119
+ return {
120
+ id: adapter.id,
121
+ async mount(host: HostContextFor<K>): Promise<MountedRootFor<K>> {
122
+ const mounted = await adapter.mount(host);
123
+ const warning = formatCapabilityWarning(
124
+ world.id,
125
+ measureAdapterReach(mounted),
126
+ MODULE_ADAPTER_MECHANISM,
127
+ // The MOUNT's own surface, so a provider this surface cannot have (a
128
+ // DOM root's 3D `transforms`) is not warned about forever. Read off
129
+ // `mounted`, not `world`, for the same reason the reach beside it is:
130
+ // this is what actually mounted, not what was declared.
131
+ mounted.kind,
132
+ );
133
+ if (warning) {
134
+ const { editorConsole } = await import('@volter/editor-core/editor-console');
135
+ editorConsole.warn(warning, 'adapter');
136
+ }
137
+ return mounted;
138
+ },
139
+ };
140
+ }
141
+
142
+ /**
143
+ * Resolve a `{ module }` adapter world: import the game folder's declared
144
+ * module through the same project-import route every other root entry takes
145
+ * (so it inherits the root `vite.config.ts`'s `resolve.dedupe: ['three',
146
+ * 'pixi.js']` collapse), guard it, and tag it with the manifest's declared
147
+ * surface.
148
+ *
149
+ * The module is imported through `projectEntryImportUrl` (`/@fs/`, mount-epoch
150
+ * cache-busted), like every other project module.
151
+ *
152
+ * Returns the module's own namespace beside the tagged adapter: for a
153
+ * `{ module }` root THAT namespace is the root's static surface — the project
154
+ * declared this module as the root's mechanism — and it becomes the
155
+ * declaration's `entry` (see `binding-resolver.ts`).
156
+ */
157
+ export async function resolveModuleAdapter(
158
+ world: ResolvedAdapterRoot,
159
+ projectRoot: string,
160
+ epoch: ProjectMountEpoch,
161
+ ): Promise<{ readonly resolved: SurfaceAdapter; readonly module: Record<string, unknown> }> {
162
+ const { adapter } = world;
163
+ if (adapter.type !== 'module') {
164
+ throw new Error(`resolveModuleAdapter: world "${world.id}" is not a { module } world.`);
165
+ }
166
+
167
+ const normalizedPath = guardModulePath(world, adapter.module);
168
+ // A `{ module }` root is project-owned code that can share modules with its
169
+ // sibling roots, so it takes the same mount epoch they do.
170
+ const modulePath = projectEntryImportUrl(projectRoot, normalizedPath, epoch);
171
+ const mod = (await import(/* @vite-ignore */ modulePath)) as Record<string, unknown>;
172
+ const candidate = mod['default'];
173
+
174
+ // The manifest's declared surface is the discriminant, so each branch names
175
+ // the module's adapter at ITS OWN surface — `guardModuleShape<K>` performs
176
+ // the identical runtime check in all three, and the result is paired with
177
+ // the tag rather than flattened through a three-shaped channel.
178
+ switch (world.surface) {
179
+ case 'three': {
180
+ guardModuleShape<'three'>(world, adapter.module, candidate);
181
+ return {
182
+ resolved: { surface: 'three', adapter: withCapabilityWarning(world, candidate) },
183
+ module: mod,
184
+ };
185
+ }
186
+ case 'canvas': {
187
+ guardModuleShape<'canvas'>(world, adapter.module, candidate);
188
+ return {
189
+ resolved: { surface: 'canvas', adapter: withCapabilityWarning(world, candidate) },
190
+ module: mod,
191
+ };
192
+ }
193
+ case 'dom': {
194
+ guardModuleShape<'dom'>(world, adapter.module, candidate);
195
+ return {
196
+ resolved: { surface: 'dom', adapter: withCapabilityWarning(world, candidate) },
197
+ module: mod,
198
+ };
199
+ }
200
+ default:
201
+ return assertNever(world.surface, 'resolveModuleAdapter');
202
+ }
203
+ }