@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,224 @@
1
+ /**
2
+ * Teardown for whatever `{ ingest }` root is live — the one path out of the
3
+ * session slot (`active-ingest.ts`), and the three per-surface dispose bodies
4
+ * it dispatches to.
5
+ *
6
+ * Every mount entry opens with {@link exitActiveIngest}, so this is also the
7
+ * cross-surface switch: mounting a canvas root over a live three root disposes
8
+ * the three session first, whichever surface it was.
9
+ *
10
+ * All three teardown bodies end at `reclaimGameRealm('')`. An ingested game
11
+ * runs in the DEFAULT `gated-globals.ts` realm, and these bodies used to reset
12
+ * its input gate to always-true and stop there — which left the dead game's
13
+ * window/document listeners attached to the real `window` AND ungated, for the
14
+ * rest of the tab's life, with the next ingest mount inheriting the list.
15
+ * Ending the realm is what releases them; it also drops the gate entry, so
16
+ * resetting the gate is no longer a separate step.
17
+ */
18
+
19
+ import { setActiveAuthoring } from '@volter/editor-core/authoring/active-adapter';
20
+ import { setActiveSystems } from '@volter/editor-core/authoring/active-systems';
21
+ import { clearMountFailureReports } from '@volter/editor-core/authoring/mount-failure-report';
22
+ import { editorConsole } from '@volter/editor-core/editor-console';
23
+ import { reclaimGameRealm } from '../host/game-realm-reclaim';
24
+ import { setGameRealmReloadHandler } from '../host/gated-globals';
25
+ import {
26
+ activeIngest,
27
+ type IngestSession,
28
+ type IngestSession2D,
29
+ type IngestSessionReact,
30
+ setActiveIngest,
31
+ } from './active-ingest';
32
+ import { readIngestGameContract } from './game-contract-realm';
33
+ import { clearIngestHook } from './ingest-evidence-hook';
34
+ import { resetIngestPlaySurface } from './ingest-play-control';
35
+
36
+ /**
37
+ * D-V1 slot collapse: tears down WHATEVER kind is currently active,
38
+ * dispatching to that kind's own teardown body ({@link disposeThreejsSession}/
39
+ * {@link disposePixiSession}/{@link disposeReactSession}) — the exact same
40
+ * per-kind dispose semantics as before the collapse (resizeObserver disconnect,
41
+ * `exitPlayScene`, mount/embed dispose, undo-adapter unregister,
42
+ * authoring/systems/mount-failure clears, canvas removal, react `stop()`),
43
+ * just no longer gated behind "is THIS kind the one currently active" — every
44
+ * mount entry guard calls this unconditionally, closing the cross-kind leak
45
+ * (F26 residual): switching kinds always disposes the prior session, whichever
46
+ * kind it was. {@link unmountThreeIngestRoot}/{@link unmountCanvasIngestRoot}/
47
+ * {@link unmountDomIngestRoot} become thin wrappers around this that only act
48
+ * when the slot holds their own kind — so a same-kind caller sees the exact
49
+ * same observable behavior as before the collapse.
50
+ *
51
+ * D-V2 (composite): a composite session's `siblings` (mounted by
52
+ * `ingest-siblings.ts`'s `mountIngestSiblings`, attached via
53
+ * `attachIngestSiblings`) tear down FIRST, in REVERSE mount order,
54
+ * before the primary session's own per-kind dispose body runs — every kind
55
+ * shares this ONE teardown path, so a sibling never outlives the ingest
56
+ * session it was mounted beside. A throwing sibling `dispose()` is caught
57
+ * individually (never blocks the rest of teardown) and logged loudly, never
58
+ * silently swallowed.
59
+ */
60
+ export function exitActiveIngest(): void {
61
+ // The realm reload translation belongs to the session that just ended.
62
+ setGameRealmReloadHandler(null);
63
+ const active = activeIngest();
64
+ if (!active) return;
65
+ setActiveIngest(null);
66
+ resetIngestPlaySurface();
67
+ // THE GAME'S OWN WAY OUT RUNS FIRST. A contract-declared `lifecycle.dispose`
68
+ // is the one door that can end what the game's boot created — a self-booting
69
+ // R3F root's frame loop is not the host's to stop (see the contract slot's
70
+ // doc for the measured per-frame TypeError storm a dead mount's `useFrame`
71
+ // produced without it). Read before `reclaimGameRealm` ends the realm the
72
+ // contract lives in; declaration-gated, so a game that declares nothing
73
+ // (every canvas warm-remount game) is untouched.
74
+ try {
75
+ readIngestGameContract()?.lifecycle?.dispose?.();
76
+ } catch (err) {
77
+ editorConsole.error(`The game's own lifecycle.dispose threw at teardown: ${err}`, 'ingest');
78
+ }
79
+ for (let i = active.siblings.length - 1; i >= 0; i--) {
80
+ try {
81
+ active.siblings[i]!.dispose();
82
+ } catch (err) {
83
+ editorConsole.error(`Composite sibling dispose failed: ${err}`, 'ingest');
84
+ }
85
+ }
86
+ switch (active.kind) {
87
+ case 'three':
88
+ disposeThreejsSession(active.session);
89
+ return;
90
+ case 'canvas':
91
+ disposePixiSession(active.session);
92
+ return;
93
+ case 'dom':
94
+ disposeReactSession(active.session);
95
+ return;
96
+ }
97
+ }
98
+
99
+ /**
100
+ * D-V1: the three teardown body — byte-for-byte the pre-collapse
101
+ * `unmountThreeIngestRoot` logic below, extracted so {@link exitActiveIngest}
102
+ * can dispatch to it for ANY kind switch, not just a same-kind call.
103
+ * The slot and {@link resetIngestPlaySurface} are handled by the caller — this
104
+ * only does the threejs-specific teardown.
105
+ */
106
+ function disposeThreejsSession(s: IngestSession): void {
107
+ // The concrete ingest adapter owns a session history resource. Dispose it
108
+ // when present; minimal/custom authoring implementations need no teardown.
109
+ if (s.mount?.authoring) {
110
+ s.mount.authoring.dispose?.();
111
+ }
112
+ setActiveAuthoring(null);
113
+ setActiveSystems(null);
114
+ clearMountFailureReports(); // D-W3: no session -> no mount failure left to report either
115
+ s.resizeObserver?.disconnect();
116
+ s.inputGateUnsubscribe?.();
117
+ if (s.mount) {
118
+ try {
119
+ s.store.exitPlayScene();
120
+ } catch {
121
+ /* scene may not have been swapped if capture failed */
122
+ }
123
+ }
124
+ s.store.setPlayState('stopped');
125
+
126
+ // Tear down the mounted game (stops its loop, frees renderer, uninstalls trap).
127
+ try {
128
+ s.mount?.game.dispose();
129
+ } catch {
130
+ /* ignore */
131
+ }
132
+
133
+ reclaimGameRealm('', 'The ingested game');
134
+ clearIngestHook('__vgaiIngest');
135
+ editorConsole.log('Ingest root unmounted', 'ingest');
136
+ }
137
+
138
+ /**
139
+ * D-V1: the pixi teardown body — byte-for-byte the pre-collapse
140
+ * {@link unmountCanvasIngestRoot} logic, extracted so {@link exitActiveIngest}
141
+ * can dispatch to it for ANY kind switch. The slot and
142
+ * {@link resetIngestPlaySurface} are handled by the caller — this only does the
143
+ * pixi-specific teardown.
144
+ */
145
+ function disposePixiSession(s: IngestSession2D): void {
146
+ // v4 §7.1-13: the live canvas write target now self-registers with the undo
147
+ // timeline (mirrors `ThreeAuthoringAdapter` — see disposeThreejsSession's
148
+ // identical call above), so this session's entries must be dropped the same
149
+ // way on exit.
150
+ if (s.adapter) {
151
+ s.adapter.dispose();
152
+ }
153
+ setActiveAuthoring(null);
154
+ setActiveSystems(null);
155
+ clearMountFailureReports(); // D-W3: no session -> nothing left to report
156
+ s.store.setPlayState('stopped');
157
+ try {
158
+ s.dispose();
159
+ } catch {
160
+ /* ignore */
161
+ }
162
+ reclaimGameRealm('', 'The ingested canvas game');
163
+ clearIngestHook('__vgaiIngest2D');
164
+ editorConsole.log('canvas ingest root unmounted', 'ingest');
165
+ }
166
+
167
+ /**
168
+ * D-V1: the react teardown body — byte-for-byte the pre-collapse
169
+ * {@link unmountDomIngestRoot} logic, extracted so {@link exitActiveIngest} can
170
+ * dispatch to it for ANY kind switch. The slot and
171
+ * {@link resetIngestPlaySurface} are handled by the caller — this only does the
172
+ * react-specific teardown.
173
+ */
174
+ function disposeReactSession(s: IngestSessionReact): void {
175
+ // Disposal expires its session history and releases listeners.
176
+ s.adapter.dispose();
177
+ setActiveAuthoring(null);
178
+ setActiveSystems(null);
179
+ clearMountFailureReports(); // D-W3: no session -> nothing left to report (mirrors disposeThreejsSession's identical call)
180
+ s.store.setPlayState('stopped');
181
+ try {
182
+ s.stop();
183
+ } catch {
184
+ /* ignore */
185
+ }
186
+ reclaimGameRealm('', 'The ingested DOM game');
187
+ clearIngestHook('__vgaiIngestReact');
188
+ editorConsole.log('DOM ingest root unmounted', 'ingest');
189
+ }
190
+
191
+ /**
192
+ * Unmount a three ingest root: restore the editor scene and tear down the game.
193
+ * D-V1: a thin wrapper that only acts when a three session is the one currently
194
+ * in the slot — same observable behavior as before the collapse (a no-op if
195
+ * nothing, or a DIFFERENT kind, is active); the actual teardown is
196
+ * {@link disposeThreejsSession} via {@link exitActiveIngest}.
197
+ */
198
+ export function unmountThreeIngestRoot(): void {
199
+ if (activeIngest()?.kind !== 'three') return;
200
+ exitActiveIngest();
201
+ }
202
+
203
+ /**
204
+ * Unmount a canvas ingest root: tear down the captured game + adapter. D-V1: a
205
+ * thin wrapper that only acts when a canvas session is the one currently in the
206
+ * slot — same observable behavior as before the collapse; the actual teardown
207
+ * is {@link disposePixiSession} via {@link exitActiveIngest}.
208
+ */
209
+ export function unmountCanvasIngestRoot(): void {
210
+ if (activeIngest()?.kind !== 'canvas') return;
211
+ exitActiveIngest();
212
+ }
213
+
214
+ /**
215
+ * Unmount a DOM (native-React) ingest root: unmount the react world + tear down
216
+ * its Game/loop. D-V1: a thin wrapper that only acts when a dom session is the
217
+ * one currently in the slot — same observable behavior as before the collapse;
218
+ * the actual teardown is {@link disposeReactSession} via
219
+ * {@link exitActiveIngest}.
220
+ */
221
+ export function unmountDomIngestRoot(): void {
222
+ if (activeIngest()?.kind !== 'dom') return;
223
+ exitActiveIngest();
224
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * The window-event seam between the navmesh triggers (the Debug menu's
3
+ * Bake / Clear items, `contributions/navmesh.menu.ts`) and their listener
4
+ * (`navmesh-handler.ts`, started by `contributions/navmesh.service.ts`). Both
5
+ * sides import these names so a rename can never silently unwire the control —
6
+ * the handler would stop listening and the trigger would stop reaching it in
7
+ * the SAME edit.
8
+ *
9
+ * A window event (rather than a direct call) is deliberate: the handler follows
10
+ * the mounted `NavigationAdapter` and lives for the contribution pass, while
11
+ * the trigger is React menu chrome rendered on demand — the two never share a
12
+ * reference.
13
+ */
14
+
15
+ export const NAVMESH_BAKE_EVENT = 'editor:bake-navmesh';
16
+ export const NAVMESH_CLEAR_EVENT = 'editor:clear-navmesh';
17
+
18
+ /** Ask the mounted game's navigation adapter to bake from the walkable-tagged
19
+ * meshes in the authoring scene. */
20
+ export function dispatchNavMeshBake(): void {
21
+ window.dispatchEvent(new CustomEvent(NAVMESH_BAKE_EVENT));
22
+ }
23
+
24
+ /** Ask the mounted game's navigation adapter to clear its navmesh. */
25
+ export function dispatchNavMeshClear(): void {
26
+ window.dispatchEvent(new CustomEvent(NAVMESH_CLEAR_EVENT));
27
+ }
@@ -0,0 +1,237 @@
1
+ import { getActiveSystems } from '@volter/editor-core/authoring/active-systems';
2
+ import { editorHost } from '@volter/editor-sdk/host';
3
+ import type { NavigationAdapter } from '@volter/editor-project/adapter';
4
+ import { getUserData } from '@volter/threejs-runtime/ecs/user-data';
5
+ import * as THREE from 'three';
6
+ import { NAVMESH_BAKE_EVENT, NAVMESH_CLEAR_EVENT } from './navmesh-actions';
7
+ import {
8
+ beginNavBake,
9
+ endNavBake,
10
+ markNavMeshBaked,
11
+ navBakeBusy,
12
+ resetNavMeshBaked,
13
+ } from './navmesh-workflow-store';
14
+
15
+ /**
16
+ * The game skew's side of the Navigation verbs: bake/clear and the walkable
17
+ * overlay tint, shown through the viewport door (`host.viewport.setHelper`,
18
+ * kind `navmesh` — the Helpers menu's own toggle).
19
+ *
20
+ * Started by `contributions/navmesh.service.ts`, so its lifetime is the
21
+ * contribution pass; it used to be a mount effect in the editor's scene panel,
22
+ * which made a host panel the owner of a game-skew tool.
23
+ *
24
+ * It reads the authoring scene through `host.hierarchy.objects()` and the
25
+ * viewport through `host.viewport.setHelper` / `.rig()`, but keeps the
26
+ * `@editor/authoring/active-systems` import for the adapter itself: the bake
27
+ * collects the PRIMARY AUTHORING mount's walkable meshes, so the adapter it
28
+ * bakes into must be that same mount's — `getActiveSystems()`. The door's
29
+ * `systems.inspected()` follows the INSPECTED instance (the Inspect selector),
30
+ * which in a split session is a different mount, and baking the authoring
31
+ * scene's geometry into another instance's navmesh is the bug that would
32
+ * cause. `host.systems.subscribe` IS `subscribeActiveNavigation` (measured in
33
+ * `editor-host-door.ts`), so only the read needs the direct import.
34
+ *
35
+ * This handler speaks ONLY to the mounted game's `NavigationAdapter`
36
+ * (bake/debug/clear). It never imports Recast, constructs a navigation
37
+ * implementation, or disposes its handles. Returns a dispose function for
38
+ * editor-owned listeners and presentation only.
39
+ */
40
+ export function setupNavMeshHandlers(): () => void {
41
+ let boundNavigation: NavigationAdapter | null = null;
42
+
43
+ /** Unity-style blue walkable carpet (the helper's default is orange). */
44
+ const walkableMaterial = new THREE.MeshBasicMaterial({
45
+ color: 0x3b82f6,
46
+ transparent: true,
47
+ opacity: 0.5,
48
+ depthWrite: false,
49
+ });
50
+
51
+ function applyWalkableTint(helper: THREE.Object3D): void {
52
+ helper.traverse((child) => {
53
+ if ((child as THREE.Mesh).isMesh) (child as THREE.Mesh).material = walkableMaterial;
54
+ });
55
+ }
56
+
57
+ /**
58
+ * Nodes tagged for navmesh baking.
59
+ *
60
+ * The role is read from `userData['navRole']` — the format-neutral key
61
+ * `@volter/threejs-runtime/ecs/user-data` documents for exactly this ("navmesh role;
62
+ * collected at runtime"). An adapter-backed world tags its own objects.
63
+ */
64
+ function collectNavigationIds(role: 'walkable' | 'obstacle'): Set<string> {
65
+ const ids = new Set<string>();
66
+ for (const [id, obj] of editorHost().hierarchy.objects()) {
67
+ if (getUserData(obj, 'navRole') === role) ids.add(id);
68
+ }
69
+ return ids;
70
+ }
71
+
72
+ function clearPresentation(): void {
73
+ editorHost().viewport.setHelper('navmesh', null);
74
+ }
75
+
76
+ /** Follow late registration/replacement of the primary mounted adapter. A
77
+ * root may register navigation only after its async WASM setup completes. */
78
+ function syncMountedNavigation(): void {
79
+ const nav = getActiveSystems().navigation ?? null;
80
+ if (nav !== boundNavigation) {
81
+ boundNavigation = nav;
82
+ clearPresentation();
83
+ }
84
+ if (!nav) {
85
+ resetNavMeshBaked();
86
+ return;
87
+ }
88
+ let baked = false;
89
+ try {
90
+ baked = nav.hasNavMesh();
91
+ } catch (error) {
92
+ editorHost().console.error(
93
+ `Navigation adapter failed to report its state: ${error}`,
94
+ 'navigation',
95
+ );
96
+ }
97
+ if (!baked) {
98
+ resetNavMeshBaked();
99
+ return;
100
+ }
101
+ markNavMeshBaked();
102
+ // The adapter builds its debug mesh against the editor's scene; without
103
+ // a mounted viewport there is nothing to show it in.
104
+ const scene = editorHost().viewport.rig()?.scene;
105
+ if (!scene) return;
106
+ let debugMesh: THREE.Object3D | null = null;
107
+ try {
108
+ debugMesh = nav.debugMesh(scene);
109
+ } catch (error) {
110
+ editorHost().console.error(
111
+ `Navigation adapter failed to create its debug mesh: ${error}`,
112
+ 'navigation',
113
+ );
114
+ }
115
+ if (debugMesh) {
116
+ applyWalkableTint(debugMesh);
117
+ editorHost().viewport.setHelper('navmesh', debugMesh);
118
+ }
119
+ }
120
+
121
+ const onBake = async () => {
122
+ if (navBakeBusy()) return;
123
+ beginNavBake();
124
+ // Recast's solo bake is synchronous WASM — yield a frame so the Bake
125
+ // button visibly paints its busy state before the build blocks the thread.
126
+ await new Promise((r) => requestAnimationFrame(() => r(null)));
127
+ const nav = getActiveSystems().navigation;
128
+ if (!nav) {
129
+ const reason = 'The mounted game has no NavigationAdapter';
130
+ editorHost().console.warn(`${reason} — nothing can be baked`, 'navigation');
131
+ endNavBake(reason);
132
+ return;
133
+ }
134
+ if (!nav.bake) {
135
+ const reason = 'The mounted NavigationAdapter is inspect-only and does not support baking';
136
+ editorHost().console.warn(reason, 'navigation');
137
+ endNavBake(reason);
138
+ return;
139
+ }
140
+
141
+ const walkableIds = collectNavigationIds('walkable');
142
+ if (walkableIds.size === 0) {
143
+ editorHost().console.warn(
144
+ 'No walkable entities found — tag meshes as "walkable" in the Navigation section',
145
+ 'navigation',
146
+ );
147
+ endNavBake('No walkable-tagged entities in the scene');
148
+ return;
149
+ }
150
+
151
+ // Obstacle-role meshes go into the recast input TOO: recast's slope
152
+ // filter marks their steep faces unwalkable and erodes around them by
153
+ // walkableRadius — that is what carves holes the path routes around
154
+ // (Unity's Not-Walkable semantics). Only walkable-role presence gates
155
+ // the bake; obstacles alone can't produce a mesh.
156
+ const meshes: THREE.Mesh[] = [];
157
+ for (const id of [...walkableIds, ...collectNavigationIds('obstacle')]) {
158
+ const obj = editorHost().hierarchy.objects().get(id);
159
+ if (!obj) continue;
160
+ obj.traverse((child) => {
161
+ if ((child as THREE.Mesh).isMesh) meshes.push(child as THREE.Mesh);
162
+ });
163
+ }
164
+ if (meshes.length === 0) {
165
+ editorHost().console.error(
166
+ 'NavMesh bake failed: the walkable-tagged entities have no meshes',
167
+ 'navigation',
168
+ );
169
+ endNavBake('Walkable entities have no meshes');
170
+ return;
171
+ }
172
+
173
+ // Nothing authors bake parameters, so the bake uses recast's own defaults.
174
+ let success = false;
175
+ try {
176
+ success = nav.bake(meshes);
177
+ } catch (error) {
178
+ editorHost().console.error(`NavMesh bake failed: ${error}`, 'navigation');
179
+ endNavBake(`Bake failed: ${error}`);
180
+ return;
181
+ }
182
+ if (!success) {
183
+ editorHost().console.error('NavMesh bake failed', 'navigation');
184
+ endNavBake('Bake failed (recast rejected the input geometry)');
185
+ return;
186
+ }
187
+
188
+ boundNavigation = nav;
189
+ const scene = editorHost().viewport.rig()?.scene;
190
+ const debugMesh = scene ? nav.debugMesh(scene) : null;
191
+ if (debugMesh) {
192
+ applyWalkableTint(debugMesh);
193
+ editorHost().viewport.setHelper('navmesh', debugMesh);
194
+ }
195
+
196
+ endNavBake(null);
197
+
198
+ // The bake is SESSION-ONLY: no file is written and no loader reads one
199
+ // back, so the mesh lives only for as long as this editor session does.
200
+ };
201
+
202
+ const onClear = () => {
203
+ const nav = getActiveSystems().navigation;
204
+ if (!nav) {
205
+ editorHost().console.warn('The mounted game has no NavigationAdapter to clear', 'navigation');
206
+ return;
207
+ }
208
+ if (!nav.clear) {
209
+ editorHost().console.warn(
210
+ 'The mounted NavigationAdapter is inspect-only and does not support clearing',
211
+ 'navigation',
212
+ );
213
+ return;
214
+ }
215
+ try {
216
+ nav.clear(editorHost().viewport.rig()?.scene ?? new THREE.Scene());
217
+ } catch (error) {
218
+ editorHost().console.error(`NavMesh clear failed: ${error}`, 'navigation');
219
+ return;
220
+ }
221
+ clearPresentation();
222
+ resetNavMeshBaked();
223
+ };
224
+
225
+ window.addEventListener(NAVMESH_BAKE_EVENT, onBake);
226
+ window.addEventListener(NAVMESH_CLEAR_EVENT, onClear);
227
+ const unsubscribeNavigation = editorHost().systems.subscribe(syncMountedNavigation);
228
+ syncMountedNavigation();
229
+
230
+ return () => {
231
+ window.removeEventListener(NAVMESH_BAKE_EVENT, onBake);
232
+ window.removeEventListener(NAVMESH_CLEAR_EVENT, onClear);
233
+ unsubscribeNavigation();
234
+ clearPresentation();
235
+ walkableMaterial.dispose();
236
+ };
237
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * The navmesh tool's OWN state: whether a session navmesh is baked and
3
+ * whether a bake is in flight.
4
+ *
5
+ * This is one tool's workflow state, and it lived in `EditorShellStore`
6
+ * alongside selection, the adoption stack and play state — where every one of
7
+ * its writes notified every subscriber in the editor, and the format-neutral
8
+ * store named a navmesh probe. The store holds what the whole shell shares; a
9
+ * tool holds its own, beside itself (`navmesh-handler.ts` is the tool).
10
+ *
11
+ * Module state, `useSyncExternalStore` shape (`subscribeNavWorkflow` +
12
+ * `navWorkflowVersion`) — the same shape `authoring/active-adapter.ts` uses.
13
+ */
14
+
15
+ let _baked = false;
16
+ /** Bake busy state (the Debug-menu items' honest progress gate: recast's solo
17
+ * bake is synchronous WASM). */
18
+ let _busy = false;
19
+ const _listeners = new Set<() => void>();
20
+ let _version = 0;
21
+
22
+ function notify(): void {
23
+ _version++;
24
+ for (const listener of [..._listeners]) listener();
25
+ }
26
+
27
+ /** Subscribe to navmesh-workflow changes (`useSyncExternalStore` shape). */
28
+ export function subscribeNavWorkflow(listener: () => void): () => void {
29
+ _listeners.add(listener);
30
+ return () => {
31
+ _listeners.delete(listener);
32
+ };
33
+ }
34
+
35
+ /** Monotonic version (`useSyncExternalStore` snapshot). */
36
+ export function navWorkflowVersion(): number {
37
+ return _version;
38
+ }
39
+
40
+ /** `true` once a session navmesh has been baked (reset by Clear) — read by the
41
+ * Debug menu's NavMesh items to reflect baked / not-baked. */
42
+ export function navMeshBaked(): boolean {
43
+ return _baked;
44
+ }
45
+
46
+ export function markNavMeshBaked(): void {
47
+ if (_baked) return;
48
+ _baked = true;
49
+ notify();
50
+ }
51
+
52
+ /** Clear the baked flag — the Clear action removed the session mesh, so the
53
+ * menu items must stop reading as "Baked". */
54
+ export function resetNavMeshBaked(): void {
55
+ if (!_baked) return;
56
+ _baked = false;
57
+ notify();
58
+ }
59
+
60
+ export function navBakeBusy(): boolean {
61
+ return _busy;
62
+ }
63
+
64
+ /** Enter the baking busy state (the Debug-menu items disable on it). */
65
+ export function beginNavBake(): void {
66
+ _busy = true;
67
+ notify();
68
+ }
69
+
70
+ /** Leave the busy state; `error === null` marks the bake successful. The error
71
+ * TEXT is not stored — surfacing it loudly is the caller's job
72
+ * (`navmesh-handler.ts` emits every failure to the editor console), and a
73
+ * stored copy with no reader is what the schema-reader policy forbids. */
74
+ export function endNavBake(error: string | null): void {
75
+ _busy = false;
76
+ if (error === null) _baked = true;
77
+ notify();
78
+ }