@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,280 @@
1
+ /**
2
+ * Canvas implementation of an adapter-declared isolation scene document.
3
+ * Loaded only when a Canvas scene document is actually rendered; the generic
4
+ * scene table registry must not make a Three project evaluate Pixi authoring.
5
+ */
6
+
7
+ import { isolationImportUrl } from '@volter/editor-sdk/session/project-module-url';
8
+ import { themeVars } from '@volter/editor-sdk/widgets';
9
+ import { createPhysics2DRegistry } from '@volter/game-runtime/pixi/physics-registry';
10
+ import { createPhysicsAdapter2D } from '@volter/game-runtime/pixi/system-adapters';
11
+ import { useEffect, useRef, useState } from 'react';
12
+ import {
13
+ type IsolatedPixiScreenCtor,
14
+ type MountedIsolatedPixiScreen,
15
+ mountIsolatedPixiScreen,
16
+ } from '../authoring/mount-isolated-pixi-screen';
17
+ import { PixiAuthoringAdapter } from '../authoring/pixi-authoring-adapter';
18
+ import { createCreationSiteCanvasWriteTarget } from '../authoring/pixi-creation-site-write-target';
19
+ import { installCanvasSceneNavigation } from '@volter/editor-core/authoring/react-canvas-navigation';
20
+ import { createRootViewController } from '@volter/editor-core/authoring/world-pan-state';
21
+ import { capturePixiDisplayObjectThumbnail } from '@volter/editor-core/canvas-preview-frames';
22
+ import type { DocumentPreviewCaptureRequest } from '@volter/editor-core/document-preview-source';
23
+ import { useEditorStore } from '@volter/editor-core/editor-runtime';
24
+ import { authoringJournal } from '../history/json-history-resource';
25
+ import { setActiveScope } from '@volter/editor-core/hotkeys';
26
+ import { getCurrentProject } from '@volter/editor-core/project-manager';
27
+ import { takeNamedExport } from '../take-named-export';
28
+ import { registerWorkspaceDocumentSelection } from '@volter/editor-core/workspace-document-registry';
29
+ import {
30
+ CANVAS_SCENE_BACKGROUND,
31
+ CanvasSceneBackdrop,
32
+ CanvasSceneControls,
33
+ } from '@volter/editor-core/components/CanvasSceneViewport';
34
+ import { RootSelectionOverlay } from '@volter/editor-core/components/RootSelectionOverlay';
35
+
36
+ export interface PixiIsolationSceneContentProps {
37
+ readonly active: boolean;
38
+ readonly documentId: string;
39
+ readonly path: string;
40
+ readonly exportName: string | undefined;
41
+ readonly isolationSetup?: { readonly path: string; readonly export?: string };
42
+ }
43
+
44
+ async function loadSiblingAssetInitializer(
45
+ path: string,
46
+ ): Promise<{ initAssets?: () => Promise<void>; assetSourcePath?: string }> {
47
+ const assetsPath = path.replace(/\/screens\/[^/]+$/, '/assets.ts');
48
+ if (assetsPath === path) return {};
49
+ try {
50
+ const project = getCurrentProject();
51
+ if (!project) return {};
52
+ const assets = (await import(
53
+ /* @vite-ignore */ isolationImportUrl(project.rootPath, assetsPath)
54
+ )) as { initAssets?: () => Promise<void> };
55
+ return typeof assets.initAssets === 'function'
56
+ ? { initAssets: assets.initAssets, assetSourcePath: assetsPath }
57
+ : {};
58
+ } catch {
59
+ // A screen with no sibling assets.ts still mounts; missing textures surface
60
+ // as the constructor's own error.
61
+ return {};
62
+ }
63
+ }
64
+
65
+ export function PixiIsolationSceneContent({
66
+ active,
67
+ documentId,
68
+ path,
69
+ exportName,
70
+ isolationSetup: isolationSetupSpec,
71
+ }: PixiIsolationSceneContentProps) {
72
+ const store = useEditorStore();
73
+ const containerRef = useRef<HTMLDivElement>(null);
74
+ const viewRef = useRef(createRootViewController());
75
+ const view = viewRef.current;
76
+ const [error, setError] = useState<string | null>(null);
77
+ const [ready, setReady] = useState(false);
78
+ const [adapter, setAdapter] = useState<PixiAuthoringAdapter | null>(null);
79
+ const adapterRef = useRef<PixiAuthoringAdapter | null>(null);
80
+ const mountedRef = useRef<MountedIsolatedPixiScreen | null>(null);
81
+ const [visited, setVisited] = useState(active);
82
+ useEffect(() => {
83
+ if (active) setVisited(true);
84
+ }, [active]);
85
+
86
+ useEffect(() => {
87
+ const container = containerRef.current;
88
+ if (!container) return;
89
+ return installCanvasSceneNavigation(container, view);
90
+ }, [view]);
91
+
92
+ useEffect(() => {
93
+ if (!adapter) return;
94
+ return registerWorkspaceDocumentSelection(documentId, () => ({
95
+ adapter,
96
+ nodeId: [...store.selectedEntityIds].find((id) => adapter.hierarchy.node(id)) ?? null,
97
+ }));
98
+ }, [adapter, documentId, store]);
99
+
100
+ useEffect(() => {
101
+ if (!visited) return;
102
+ const layer = containerRef.current;
103
+ if (!layer) return;
104
+ let cancelled = false;
105
+ setError(null);
106
+ setReady(false);
107
+ setAdapter(null);
108
+ void loadIsolatedSceneComponent(path, exportName)
109
+ .then(async (Ctor) => {
110
+ const assets = await loadSiblingAssetInitializer(path);
111
+ const isolationSetup = isolationSetupSpec
112
+ ? await loadIsolationSetup(isolationSetupSpec.path, isolationSetupSpec.export)
113
+ : undefined;
114
+ return mountIsolatedPixiScreen(Ctor, {
115
+ ...assets,
116
+ ...(isolationSetup ? { isolationSetup } : {}),
117
+ view,
118
+ layer,
119
+ });
120
+ })
121
+ .then((mounted) => {
122
+ if (cancelled) {
123
+ mounted.dispose();
124
+ return;
125
+ }
126
+ mountedRef.current = mounted;
127
+ const next = new PixiAuthoringAdapter(mounted.screen, store, {
128
+ // The mount already chose the namespace the game's own class was
129
+ // constructed in; taking it back is what keeps ONE per surface.
130
+ pixi: mounted.pixi,
131
+ // Journal ids are project-relative paths — `scene:TitleScreen` is
132
+ // read as a URI scheme and refused (`resource-registry.ts`).
133
+ journal: authoringJournal(`isolation/${documentId.slice('scene:'.length)}`),
134
+ target: createCreationSiteCanvasWriteTarget({
135
+ physics: createPhysicsAdapter2D(createPhysics2DRegistry()),
136
+ history: store.projectHistory,
137
+ }),
138
+ surface: () => mounted.canvas,
139
+ capturePreview: (object, size) =>
140
+ capturePixiDisplayObjectThumbnail(object, { ...size, pixi: mounted.pixi }),
141
+ pointFromClient: (clientX, clientY, surfaceRect) => {
142
+ const pose = view.get();
143
+ return {
144
+ x: (clientX - surfaceRect.left - pose.x) / pose.zoom,
145
+ y: (clientY - surfaceRect.top - pose.y) / pose.zoom,
146
+ };
147
+ },
148
+ });
149
+ adapterRef.current = next;
150
+ setAdapter(next);
151
+ setReady(true);
152
+ })
153
+ .catch((reason: unknown) => {
154
+ if (cancelled) return;
155
+ setError(reason instanceof Error ? reason.message : String(reason));
156
+ });
157
+ return () => {
158
+ cancelled = true;
159
+ mountedRef.current?.dispose();
160
+ mountedRef.current = null;
161
+ adapterRef.current?.dispose();
162
+ adapterRef.current = null;
163
+ };
164
+ }, [documentId, store, view, visited, path, exportName, isolationSetupSpec]);
165
+
166
+ if (error) {
167
+ return (
168
+ <div
169
+ role="alert"
170
+ data-testid="scene-document-error"
171
+ style={{ padding: 12, fontSize: 12, color: themeVars.semantic.danger, maxWidth: 640 }}
172
+ >
173
+ <div style={{ fontWeight: 600, marginBottom: 4 }}>
174
+ {path}
175
+ {exportName ? ` — ${exportName}` : ''}
176
+ </div>
177
+ <div style={{ color: themeVars.content.muted, whiteSpace: 'pre-wrap' }}>{error}</div>
178
+ </div>
179
+ );
180
+ }
181
+
182
+ return (
183
+ <div
184
+ ref={containerRef}
185
+ data-testid="scene-document-pixi-isolation"
186
+ data-document-id={documentId}
187
+ data-vgai-backdrop-color={active ? CANVAS_SCENE_BACKGROUND : undefined}
188
+ data-vgai-backdrop-policy={active ? 'dark-frost' : undefined}
189
+ onPointerDown={() => setActiveScope('viewport')}
190
+ style={{
191
+ position: 'absolute',
192
+ inset: 0,
193
+ overflow: 'hidden',
194
+ pointerEvents: 'auto',
195
+ backgroundColor: CANVAS_SCENE_BACKGROUND,
196
+ touchAction: 'none',
197
+ }}
198
+ >
199
+ <CanvasSceneBackdrop view={view} />
200
+ {adapter ? <RootSelectionOverlay adapter={adapter} view={view} transformModeAware /> : null}
201
+ <CanvasSceneControls
202
+ active={active}
203
+ {...(adapter ? { adapter } : {})}
204
+ containerRef={containerRef}
205
+ view={view}
206
+ />
207
+ {!ready ? (
208
+ <div
209
+ data-testid="scene-document-loading"
210
+ style={{ padding: 12, fontSize: 12, color: themeVars.content.muted, fontStyle: 'italic' }}
211
+ >
212
+ mounting…
213
+ </div>
214
+ ) : null}
215
+ </div>
216
+ );
217
+ }
218
+
219
+ async function loadIsolatedSceneComponent(
220
+ path: string,
221
+ exportName: string | undefined,
222
+ ): Promise<IsolatedPixiScreenCtor> {
223
+ const project = getCurrentProject();
224
+ if (!project) throw new Error('No project is open, so its scene modules cannot be loaded.');
225
+ return takeNamedExport<IsolatedPixiScreenCtor>(
226
+ isolationImportUrl(project.rootPath, path),
227
+ path,
228
+ exportName,
229
+ );
230
+ }
231
+
232
+ /** Load the adapter-declared zero-argument prerequisite for one Edit piece. */
233
+ async function loadIsolationSetup(
234
+ path: string,
235
+ exportName: string | undefined,
236
+ ): Promise<() => void | Promise<void>> {
237
+ const project = getCurrentProject();
238
+ if (!project) throw new Error('No project is open, so its isolation setup cannot be loaded.');
239
+ return takeNamedExport<() => void | Promise<void>>(
240
+ isolationImportUrl(project.rootPath, path),
241
+ path,
242
+ exportName,
243
+ );
244
+ }
245
+
246
+ /** Headless full-composition capture for a Canvas scene Content tile. */
247
+ export async function capturePixiIsolationScenePreview(
248
+ props: Pick<PixiIsolationSceneContentProps, 'path' | 'exportName' | 'isolationSetup'>,
249
+ request: DocumentPreviewCaptureRequest,
250
+ ): Promise<string> {
251
+ const layer = document.createElement('div');
252
+ layer.style.cssText = `position:fixed;left:-20000px;top:0;width:${request.width}px;height:${request.height}px;overflow:hidden;`;
253
+ document.body.appendChild(layer);
254
+ const view = createRootViewController();
255
+ let mounted: MountedIsolatedPixiScreen | null = null;
256
+ try {
257
+ const Ctor = await loadIsolatedSceneComponent(props.path, props.exportName);
258
+ const assets = await loadSiblingAssetInitializer(props.path);
259
+ const isolationSetup = props.isolationSetup
260
+ ? await loadIsolationSetup(props.isolationSetup.path, props.isolationSetup.export)
261
+ : undefined;
262
+ mounted = await mountIsolatedPixiScreen(Ctor, {
263
+ ...assets,
264
+ ...(isolationSetup ? { isolationSetup } : {}),
265
+ view,
266
+ layer,
267
+ });
268
+ const image = await capturePixiDisplayObjectThumbnail(mounted.screen, {
269
+ width: request.width,
270
+ height: request.height,
271
+ budget: Number.POSITIVE_INFINITY,
272
+ pixi: mounted.pixi,
273
+ });
274
+ if (!image) throw new Error('The Canvas scene has no capturable composition.');
275
+ return image;
276
+ } finally {
277
+ mounted?.dispose();
278
+ layer.remove();
279
+ }
280
+ }
@@ -0,0 +1,89 @@
1
+ import { faDesktop } from '@fortawesome/free-solid-svg-icons';
2
+ import { AnchoredMenu, Button, EditorIcon, MenuItem, Text } from '@volter/editor-sdk/widgets';
3
+ import { useRef, useState } from 'react';
4
+
5
+ export interface Resolution {
6
+ label: string;
7
+ width: number | null;
8
+ height: number | null;
9
+ }
10
+
11
+ export const RESOLUTIONS: Resolution[] = [
12
+ { label: 'Fill', width: null, height: null },
13
+ { label: 'iPhone SE — 375×667', width: 375, height: 667 },
14
+ { label: 'iPhone 14 — 390×844', width: 390, height: 844 },
15
+ { label: 'iPhone 14 Pro Max — 430×932', width: 430, height: 932 },
16
+ { label: 'iPad Mini — 768×1024', width: 768, height: 1024 },
17
+ { label: 'iPad Pro 11″ — 834×1194', width: 834, height: 1194 },
18
+ { label: 'Laptop — 1366×768', width: 1366, height: 768 },
19
+ { label: 'Desktop — 1920×1080', width: 1920, height: 1080 },
20
+ ];
21
+
22
+ /** Shared preview-resolution picker for Game and isolated story documents. */
23
+ export function ResolutionPicker({
24
+ resolution,
25
+ onResolutionChange,
26
+ scale,
27
+ title = 'Change preview resolution',
28
+ options = RESOLUTIONS,
29
+ }: {
30
+ resolution: Resolution;
31
+ onResolutionChange: (resolution: Resolution) => void;
32
+ scale: number;
33
+ title?: string;
34
+ /** Menu entries. Callers with a project-declared resolution swap the list
35
+ * so the declared entry is offered and unreachable choices are not. */
36
+ options?: Resolution[];
37
+ }) {
38
+ const [open, setOpen] = useState(false);
39
+ const ref = useRef<HTMLButtonElement>(null);
40
+
41
+ const isFill = resolution.width === null;
42
+ const label = isFill ? 'Fill' : `${resolution.width}×${resolution.height}`;
43
+
44
+ return (
45
+ <div className="vgai-playbar-popover-anchor">
46
+ <Button
47
+ ref={ref}
48
+ variant="ghost"
49
+ size="comfortable"
50
+ aria-haspopup="menu"
51
+ aria-expanded={open}
52
+ aria-controls={open ? 'vgai-resolution-picker-menu' : undefined}
53
+ onClick={() => setOpen((value) => !value)}
54
+ title={title}
55
+ >
56
+ <EditorIcon icon={faDesktop} size="md" />
57
+ <Text variant="code">{label}</Text>
58
+ {!isFill && scale < 1 && (
59
+ <Text variant="caption" tone="dim">
60
+ {Math.round(scale * 100)}%
61
+ </Text>
62
+ )}
63
+ </Button>
64
+ {open && (
65
+ <AnchoredMenu
66
+ id="vgai-resolution-picker-menu"
67
+ anchorRef={ref}
68
+ className="vgai-resolution-menu"
69
+ gap={4}
70
+ onDismiss={() => setOpen(false)}
71
+ >
72
+ {options.map((candidate) => (
73
+ <MenuItem
74
+ key={candidate.label}
75
+ role="menuitemradio"
76
+ aria-checked={candidate.label === resolution.label}
77
+ onSelect={() => {
78
+ onResolutionChange(candidate);
79
+ setOpen(false);
80
+ }}
81
+ >
82
+ {candidate.label}
83
+ </MenuItem>
84
+ ))}
85
+ </AnchoredMenu>
86
+ )}
87
+ </div>
88
+ );
89
+ }
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Three implementation of an adapter-declared isolation scene document.
3
+ *
4
+ * The exact counterpart of `PixiIsolationSceneContent.tsx`, and extracted for
5
+ * the reason that module's own header already gives: "Loaded only when a
6
+ * Canvas scene document is actually rendered; the generic scene table registry
7
+ * must not make a Three project evaluate Pixi authoring." The three half sat
8
+ * INLINE in `scene-documents.tsx` instead, so the generic scene-table registry
9
+ * — which every project's tab row subscribes to at boot — carried the portable
10
+ * CSF mount with it: `stories/story-three-preview.ts`,
11
+ * `stories/mounted-story-viewport-source.ts`, `stories/story-mount-turn.ts`,
12
+ * `story-three-preview-runtime.ts`, `authoring/design-time-settle.ts` and
13
+ * `crash-null-boundary.ts` (measured 2026-09-18, phase 1 unit 9 of the
14
+ * open-source launch: six files, in a `models` build that declares no story).
15
+ *
16
+ * ## The mount, and who owns it
17
+ *
18
+ * Identical to the per-story 3D document's, deliberately and by reuse: the
19
+ * composition is mounted off-screen through `stories/story-three-preview.ts`
20
+ * and wrapped by `stories/mounted-story-viewport-source.ts`, whose
21
+ * per-activation disposer is EMPTY because a remount of this document (source
22
+ * identity change, not a tab flip) still calls `build()` again. This
23
+ * component's own effect cleanup is the ONE teardown path. See that module's
24
+ * header for the measured black-panel defect a second owner produces.
25
+ */
26
+
27
+ import { fsImportPath } from '@volter/editor-sdk/session/project-module-url';
28
+ import { themeVars } from '@volter/editor-sdk/widgets';
29
+ import { useEffect, useRef, useState } from 'react';
30
+ import type { DocumentPreviewCaptureRequest } from '@volter/editor-core/document-preview-source';
31
+ import { captureAuthoredThreeScenePreview } from '../document-preview-three';
32
+ import { getCurrentProject } from '@volter/editor-core/project-manager';
33
+ import {
34
+ type MountedStoryViewportSource,
35
+ mountedStoryViewportSource,
36
+ } from '../stories/mounted-story-viewport-source';
37
+ import {
38
+ disposeStoryObject3D,
39
+ mountStoryObject3D,
40
+ type StoryPreviewComponent,
41
+ } from '@volter/editor-core/stories/story-three-preview';
42
+ import { takeNamedExport } from '../take-named-export';
43
+ import type { WorkspaceDocumentContentProps } from '@volter/editor-core/workspace-document-registry';
44
+ import { Object3DDocumentViewport } from '@volter/editor-core/components/Object3DDocumentViewport';
45
+
46
+ export interface ThreeIsolationSceneState {
47
+ /** Project-relative module the composition lives in. */
48
+ readonly path: string;
49
+ readonly exportName: string | undefined;
50
+ readonly label: string;
51
+ }
52
+
53
+ export function ThreeIsolationSceneContent({
54
+ active,
55
+ documentId,
56
+ state,
57
+ }: WorkspaceDocumentContentProps & { state: ThreeIsolationSceneState | undefined }) {
58
+ const [source, setSource] = useState<MountedStoryViewportSource | null>(null);
59
+ const [error, setError] = useState<string | null>(null);
60
+ const sourceRef = useRef<MountedStoryViewportSource | null>(null);
61
+ // EVERY OPEN DOCUMENT IS MOUNTED, and this row is opened for EVERY
62
+ // scene a project declares — so mounting at open time would build every
63
+ // scene's fiber root at boot. Mount on first activation and retain it
64
+ // afterwards; a mount freed on deactivation is the black-panel defect
65
+ // `mountedStoryViewportSource` documents.
66
+ const [visited, setVisited] = useState(active);
67
+ useEffect(() => {
68
+ if (active) setVisited(true);
69
+ }, [active]);
70
+
71
+ const path = state?.path;
72
+ const exportName = state?.exportName;
73
+ useEffect(() => {
74
+ if (!visited || !path) return;
75
+ let cancelled = false;
76
+ setError(null);
77
+ setSource(null);
78
+ void loadSceneComponent(path, exportName)
79
+ .then((Component) => mountStoryObject3D(Component, {}))
80
+ .then((mounted) => {
81
+ if (cancelled) {
82
+ void disposeStoryObject3D(mounted);
83
+ return;
84
+ }
85
+ const next = mountedStoryViewportSource(mounted);
86
+ sourceRef.current = next;
87
+ setSource(next);
88
+ })
89
+ .catch((reason: unknown) => {
90
+ if (cancelled) return;
91
+ setError(reason instanceof Error ? reason.message : String(reason));
92
+ });
93
+ return () => {
94
+ cancelled = true;
95
+ // THIS is the mount's one owner — see the module header.
96
+ sourceRef.current?.dispose();
97
+ sourceRef.current = null;
98
+ setSource(null);
99
+ };
100
+ }, [visited, path, exportName]);
101
+
102
+ if (!state) {
103
+ return (
104
+ <div style={{ padding: 12, fontSize: 12, color: themeVars.content.muted }}>
105
+ This scene document is no longer declared by the project's adapter.
106
+ </div>
107
+ );
108
+ }
109
+
110
+ if (error) {
111
+ return (
112
+ <div
113
+ role="alert"
114
+ data-testid="scene-document-error"
115
+ style={{ padding: 12, fontSize: 12, color: themeVars.semantic.danger, maxWidth: 640 }}
116
+ >
117
+ <div style={{ fontWeight: 600, marginBottom: 4 }}>
118
+ {state.path}
119
+ {state.exportName ? ` — ${state.exportName}` : ''}
120
+ </div>
121
+ <div style={{ color: themeVars.content.muted, whiteSpace: 'pre-wrap' }}>{error}</div>
122
+ </div>
123
+ );
124
+ }
125
+
126
+ if (!source) {
127
+ return (
128
+ <div
129
+ data-testid="scene-document-loading"
130
+ style={{ padding: 12, fontSize: 12, color: themeVars.content.muted, fontStyle: 'italic' }}
131
+ >
132
+ mounting…
133
+ </div>
134
+ );
135
+ }
136
+
137
+ return (
138
+ <Object3DDocumentViewport
139
+ documentId={documentId}
140
+ sourcePath={state.path}
141
+ build={source.build}
142
+ displayName={state.label}
143
+ active={active}
144
+ />
145
+ );
146
+ }
147
+
148
+ /**
149
+ * Import the scene's own module through the project-module door and take the
150
+ * export the table named.
151
+ *
152
+ * Cache-busted per mount, the same always-fresh discipline design-time CSF
153
+ * loads use (`stories/story-discovery.ts`): a scene edited in the editor must
154
+ * be re-openable without a reload. The url is built by `fsImportPath`, the ONE
155
+ * owner of `/@fs/` urls for project code (PD-3).
156
+ */
157
+ async function loadSceneComponent(
158
+ path: string,
159
+ exportName: string | undefined,
160
+ ): Promise<StoryPreviewComponent> {
161
+ const project = getCurrentProject();
162
+ if (!project) throw new Error('No project is open, so its scene modules cannot be loaded.');
163
+ const url = `${fsImportPath(project.rootPath, path)}?t=${Date.now()}`;
164
+ return takeNamedExport<StoryPreviewComponent>(url, path, exportName);
165
+ }
166
+
167
+ /** Headless capture door for Content. It mounts the authored source without
168
+ * opening or activating its workspace document. */
169
+ export async function captureThreeIsolationScenePreview(
170
+ state: ThreeIsolationSceneState,
171
+ request: DocumentPreviewCaptureRequest,
172
+ ): Promise<string> {
173
+ const Component = await loadSceneComponent(state.path, state.exportName);
174
+ const mounted = await mountStoryObject3D(Component, {});
175
+ try {
176
+ return captureAuthoredThreeScenePreview(mounted.root, mounted.scene, request);
177
+ } finally {
178
+ await disposeStoryObject3D(mounted);
179
+ }
180
+ }