@volter/editor-core 0.5.57

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 (719) hide show
  1. package/BUNDLED_NOTICES +1527 -0
  2. package/LICENSE +686 -0
  3. package/LICENSE-APACHE +202 -0
  4. package/NOTICE +18 -0
  5. package/README.md +24 -0
  6. package/dist/build/chunk-JS4D4XZ3.js +175 -0
  7. package/dist/build/chunk-JS4D4XZ3.js.map +7 -0
  8. package/dist/build/vite-plugin-product-contributions.js +100 -0
  9. package/dist/build/vite-plugin-product-contributions.js.map +7 -0
  10. package/dist/build/vite-plugin-shared-react.js +25 -0
  11. package/dist/build/vite-plugin-shared-react.js.map +7 -0
  12. package/dist/build/vite-plugin-shared-three.js +86 -0
  13. package/dist/build/vite-plugin-shared-three.js.map +7 -0
  14. package/dist/server/chunk-KMJC7AGO.js +98 -0
  15. package/dist/server/chunk-KMJC7AGO.js.map +7 -0
  16. package/dist/server/chunk-PSILUMGS.js +46 -0
  17. package/dist/server/chunk-PSILUMGS.js.map +7 -0
  18. package/dist/server/frame-proxy.js +255 -0
  19. package/dist/server/frame-proxy.js.map +7 -0
  20. package/dist/server/open-browser.js +53 -0
  21. package/dist/server/open-browser.js.map +7 -0
  22. package/dist/server/process-shutdown.js +127 -0
  23. package/dist/server/process-shutdown.js.map +7 -0
  24. package/dist/server/session-registry.js +141 -0
  25. package/dist/server/session-registry.js.map +7 -0
  26. package/dist/server/spawn-opener.js +11 -0
  27. package/dist/server/spawn-opener.js.map +7 -0
  28. package/dist/server/worktree-identity.js +11 -0
  29. package/dist/server/worktree-identity.js.map +7 -0
  30. package/dist-server/packaged.mjs +88816 -0
  31. package/package.json +118 -0
  32. package/scripts/build-plugins.mjs +14 -0
  33. package/scripts/build-server.mjs +26 -0
  34. package/scripts/build-session.mjs +9 -0
  35. package/server/account-credentials.ts +60 -0
  36. package/server/account-service.ts +1434 -0
  37. package/server/adapter-region-includes.ts +47 -0
  38. package/server/asset-catalog-v2.ts +175 -0
  39. package/server/asset-history-snapshots.ts +196 -0
  40. package/server/asset-ledger-store.ts +195 -0
  41. package/server/asset-library-routes.ts +1595 -0
  42. package/server/blender-wasm-artifact.ts +430 -0
  43. package/server/boot-timings.ts +106 -0
  44. package/server/canonical-path.ts +50 -0
  45. package/server/checkout-workspace-preflight.ts +349 -0
  46. package/server/cloud-asset-catalog.ts +158 -0
  47. package/server/coding-inference-launch.ts +237 -0
  48. package/server/collaboration-account-client.ts +202 -0
  49. package/server/collaboration-attribution.ts +122 -0
  50. package/server/collaboration-session.ts +934 -0
  51. package/server/comfyui-bridge.ts +248 -0
  52. package/server/console-ledger.ts +499 -0
  53. package/server/creation-site-transform.ts +304 -0
  54. package/server/creation-site-write.ts +648 -0
  55. package/server/data-file-serialize.ts +158 -0
  56. package/server/editor-brand-html.ts +72 -0
  57. package/server/editor-control-socket.ts +366 -0
  58. package/server/editor-server-options.ts +190 -0
  59. package/server/editor-server.ts +1483 -0
  60. package/server/editor-sse.ts +352 -0
  61. package/server/engine-provenance.ts +169 -0
  62. package/server/engine-source-restart.ts +117 -0
  63. package/server/frame-bridge.ts +115 -0
  64. package/server/frame-proxy.ts +490 -0
  65. package/server/frame-workbench.ts +259 -0
  66. package/server/frontend-handoff.ts +229 -0
  67. package/server/game-globals-shadow.ts +113 -0
  68. package/server/gameplay-session-retention.ts +149 -0
  69. package/server/gameplay-sessions.ts +123 -0
  70. package/server/generation-jobs.ts +201 -0
  71. package/server/generation-reconciler.ts +137 -0
  72. package/server/generative-execution-context.ts +59 -0
  73. package/server/git-workflow.ts +612 -0
  74. package/server/harness-chat-caller.ts +36 -0
  75. package/server/harness-chat-service.ts +1681 -0
  76. package/server/idle-shutdown.ts +170 -0
  77. package/server/js-profiling-policy.ts +52 -0
  78. package/server/launcher-settings.ts +84 -0
  79. package/server/local-asset-catalog.ts +410 -0
  80. package/server/managed-account-defaults.ts +22 -0
  81. package/server/mock-control-plane.ts +540 -0
  82. package/server/model-import-conversion.ts +345 -0
  83. package/server/native-credential-store.ts +162 -0
  84. package/server/open-browser.ts +118 -0
  85. package/server/packaged.ts +1192 -0
  86. package/server/play-stall.ts +126 -0
  87. package/server/process-shutdown.ts +218 -0
  88. package/server/product-presets.ts +55 -0
  89. package/server/project-build-artifact.ts +95 -0
  90. package/server/project-components.ts +100 -0
  91. package/server/project-dependency-invalidation.ts +114 -0
  92. package/server/project-file-scan.ts +235 -0
  93. package/server/project-hmr-files.ts +215 -0
  94. package/server/project-install-roots.ts +210 -0
  95. package/server/project-kinds.ts +71 -0
  96. package/server/project-mcp-servers.ts +82 -0
  97. package/server/project-module-freshness.ts +109 -0
  98. package/server/project-module-instance.ts +375 -0
  99. package/server/project-optimize-deps-entries.ts +827 -0
  100. package/server/project-output-writer.ts +679 -0
  101. package/server/project-package-origin.ts +63 -0
  102. package/server/project-root-surface.ts +831 -0
  103. package/server/project-scratch-path.ts +47 -0
  104. package/server/project-script-hmr.ts +619 -0
  105. package/server/project-serving-plugins.ts +236 -0
  106. package/server/project-tools.ts +653 -0
  107. package/server/project-validation.ts +250 -0
  108. package/server/project-verbs.ts +127 -0
  109. package/server/project-view.ts +52 -0
  110. package/server/project-watch.ts +1134 -0
  111. package/server/project-work-coordinator.ts +288 -0
  112. package/server/provider-credentials.ts +331 -0
  113. package/server/recent-projects-store.ts +98 -0
  114. package/server/redact-secrets.ts +71 -0
  115. package/server/repository-presence.ts +267 -0
  116. package/server/routes/account.ts +357 -0
  117. package/server/routes/agents.ts +60 -0
  118. package/server/routes/assets.ts +154 -0
  119. package/server/routes/build.ts +13 -0
  120. package/server/routes/collaboration.ts +595 -0
  121. package/server/routes/configurations.ts +419 -0
  122. package/server/routes/context.ts +264 -0
  123. package/server/routes/control-plane.ts +1811 -0
  124. package/server/routes/logs.ts +272 -0
  125. package/server/routes/project-identity.ts +302 -0
  126. package/server/routes/project-open.ts +429 -0
  127. package/server/routes/project-source.ts +686 -0
  128. package/server/routes/project-state.ts +383 -0
  129. package/server/routes/relay.ts +1029 -0
  130. package/server/routes/route-helpers.ts +29 -0
  131. package/server/routes/served-modules.ts +138 -0
  132. package/server/routes/session-tabs.ts +196 -0
  133. package/server/routes/settings.ts +121 -0
  134. package/server/routes/share-control.ts +486 -0
  135. package/server/routes/themes.ts +116 -0
  136. package/server/routes/tools.ts +230 -0
  137. package/server/routes/worktrees.ts +595 -0
  138. package/server/scoped-game-css.ts +461 -0
  139. package/server/server-utils.ts +1194 -0
  140. package/server/session-product.ts +112 -0
  141. package/server/session-registry.ts +230 -0
  142. package/server/share-claims.ts +138 -0
  143. package/server/share-host.ts +526 -0
  144. package/server/share-session-gateway.ts +1362 -0
  145. package/server/share-tunnel.ts +123 -0
  146. package/server/spawn-opener.ts +74 -0
  147. package/server/support/play/log-format.ts +76 -0
  148. package/server/support/play/session-record.ts +199 -0
  149. package/server/support/project/build-discipline.ts +718 -0
  150. package/server/support/project/inspection-node.ts +119 -0
  151. package/server/support/project/log-naming.ts +54 -0
  152. package/server/support/project/provenance.ts +290 -0
  153. package/server/support/project/run-name.ts +18 -0
  154. package/server/support/project/session-journal.ts +830 -0
  155. package/server/support/project/shared.ts +327 -0
  156. package/server/tab-bootstrap.ts +62 -0
  157. package/server/tab-heartbeat.ts +477 -0
  158. package/server/tab-lifecycle.ts +550 -0
  159. package/server/tab-presence.ts +1508 -0
  160. package/server/team-agent-mirror.ts +95 -0
  161. package/server/twin-auth.ts +138 -0
  162. package/server/vendored-lock-recorder.ts +968 -0
  163. package/server/worktree-identity.ts +121 -0
  164. package/server/worktree-management.ts +398 -0
  165. package/server/worktree-retention.d.mts +13 -0
  166. package/server/worktree-retention.mjs +205 -0
  167. package/src/EditorContext.tsx +169 -0
  168. package/src/account.ts +333 -0
  169. package/src/action-registry.ts +532 -0
  170. package/src/active-product.ts +74 -0
  171. package/src/active-project.ts +155 -0
  172. package/src/adapter-editor-config.ts +25 -0
  173. package/src/adapter-observation.ts +49 -0
  174. package/src/animation/stage-transport.ts +379 -0
  175. package/src/animation/three-clips-subject.ts +172 -0
  176. package/src/api/asset-library-wire.ts +45 -0
  177. package/src/api/assets.ts +365 -0
  178. package/src/api/base.ts +10 -0
  179. package/src/api/build.ts +99 -0
  180. package/src/api/git-wire.ts +56 -0
  181. package/src/api/logs.ts +92 -0
  182. package/src/api/project-identity.ts +74 -0
  183. package/src/api/project-open.ts +355 -0
  184. package/src/api/project-source.ts +162 -0
  185. package/src/api/project-state.ts +107 -0
  186. package/src/api/relay.ts +268 -0
  187. package/src/api/settings.ts +36 -0
  188. package/src/api/themes.ts +45 -0
  189. package/src/api/worktrees.ts +257 -0
  190. package/src/asset-compare-core.ts +171 -0
  191. package/src/asset-compare.ts +294 -0
  192. package/src/asset-editor-context.tsx +100 -0
  193. package/src/asset-events.ts +97 -0
  194. package/src/asset-inspector-actions.ts +87 -0
  195. package/src/asset-preview-framing.ts +357 -0
  196. package/src/asset-preview.ts +2802 -0
  197. package/src/asset-selection-viewer-registry.ts +113 -0
  198. package/src/asset-selection.ts +133 -0
  199. package/src/asset-workflow/asset-capabilities.ts +344 -0
  200. package/src/asset-workflow/asset-import-jobs.ts +106 -0
  201. package/src/asset-workflow/asset-ledger-backend.ts +126 -0
  202. package/src/asset-workflow/asset-ledger.ts +156 -0
  203. package/src/asset-workflow/asset-materialization-report.ts +140 -0
  204. package/src/asset-workflow/asset-pack-manifest.ts +320 -0
  205. package/src/asset-workflow/asset-types.ts +142 -0
  206. package/src/asset-workflow/asset-workflow-quality.ts +61 -0
  207. package/src/asset-workflow/audio-preview-player.ts +193 -0
  208. package/src/asset-workflow/audio-waveform.ts +22 -0
  209. package/src/asset-workflow/cloud-asset-client.ts +263 -0
  210. package/src/asset-workflow/folder-preview.ts +345 -0
  211. package/src/asset-workflow/hosted-asset-materialization.ts +236 -0
  212. package/src/asset-workflow/image-view-scale.ts +32 -0
  213. package/src/asset-workflow/import-contract.ts +124 -0
  214. package/src/asset-workflow/ledger-write-lock.ts +244 -0
  215. package/src/asset-workflow/model-inspection.ts +818 -0
  216. package/src/asset-workflow/pixi-spritesheet.ts +197 -0
  217. package/src/asset-workflow/preview-resource-lifetime.ts +44 -0
  218. package/src/asset-workflow/project-asset-commands.ts +23 -0
  219. package/src/asset-workflow/project-asset-health.ts +139 -0
  220. package/src/asset-workflow/project-asset-operations.ts +154 -0
  221. package/src/asset-workflow/project-asset-roots.ts +68 -0
  222. package/src/asset-workflow/project-content.ts +288 -0
  223. package/src/asset-workflow/project-source-index.ts +550 -0
  224. package/src/asset-workflow/thumbnail-system.ts +256 -0
  225. package/src/authoring/active-adapter.ts +199 -0
  226. package/src/authoring/active-systems.ts +422 -0
  227. package/src/authoring/adapter-key.ts +18 -0
  228. package/src/authoring/authoring-asset-url.ts +27 -0
  229. package/src/authoring/bootstrap-state.ts +49 -0
  230. package/src/authoring/boundary-authoring-adapter.ts +184 -0
  231. package/src/authoring/breakpoint-state.ts +43 -0
  232. package/src/authoring/canvas-scene-guides.ts +76 -0
  233. package/src/authoring/component-instance-root.ts +171 -0
  234. package/src/authoring/composite-authoring-adapter.ts +2110 -0
  235. package/src/authoring/consumer-actions.ts +520 -0
  236. package/src/authoring/css-numeric-style.ts +97 -0
  237. package/src/authoring/design-time-layers.ts +849 -0
  238. package/src/authoring/design-time-mount-registry.ts +235 -0
  239. package/src/authoring/design-time-settle.ts +343 -0
  240. package/src/authoring/edit-mode-authoring.ts +619 -0
  241. package/src/authoring/eyedropper-session.ts +60 -0
  242. package/src/authoring/instance-source-menu-register.ts +13 -0
  243. package/src/authoring/instance-source-menu.ts +135 -0
  244. package/src/authoring/layered-pick.ts +183 -0
  245. package/src/authoring/live-gesture-lock.ts +50 -0
  246. package/src/authoring/live-object-transform.ts +62 -0
  247. package/src/authoring/mount-failure-report.ts +154 -0
  248. package/src/authoring/mounted-root-subjects.ts +144 -0
  249. package/src/authoring/no-authoring-adapter.ts +55 -0
  250. package/src/authoring/null-inspection-subjects.tsx +75 -0
  251. package/src/authoring/object3d-document-persistence.ts +106 -0
  252. package/src/authoring/object3d-document-session-registry.ts +120 -0
  253. package/src/authoring/object3d-document-session.ts +1326 -0
  254. package/src/authoring/object3d-gesture-controller.ts +113 -0
  255. package/src/authoring/panel-authoring.ts +121 -0
  256. package/src/authoring/prefab-instance-inspector-section.tsx +237 -0
  257. package/src/authoring/prefab-instance-section-model.ts +55 -0
  258. package/src/authoring/project-authoring-session.ts +105 -0
  259. package/src/authoring/provenance.ts +99 -0
  260. package/src/authoring/quarks-particle-systems.ts +19 -0
  261. package/src/authoring/react-canvas-navigation.ts +255 -0
  262. package/src/authoring/react-design-canvas-style.ts +20 -0
  263. package/src/authoring/react-story-board.ts +937 -0
  264. package/src/authoring/selection-scope.ts +195 -0
  265. package/src/authoring/shell-document-ops.ts +169 -0
  266. package/src/authoring/shell-object3d-document-write-policy.ts +108 -0
  267. package/src/authoring/shell-viewport-policy.ts +48 -0
  268. package/src/authoring/source-object3d-authoring-adapter.ts +526 -0
  269. package/src/authoring/stories-scope.ts +35 -0
  270. package/src/authoring/story-board-chrome-fit.ts +107 -0
  271. package/src/authoring/story-board-presentation.ts +111 -0
  272. package/src/authoring/three-projection-core.ts +226 -0
  273. package/src/authoring/viewport-pick-context.ts +40 -0
  274. package/src/authoring/viewport-raycast.ts +240 -0
  275. package/src/authoring/viewport-tool-context.ts +73 -0
  276. package/src/authoring/world-canvas-viewport-state.ts +35 -0
  277. package/src/authoring/world-hidden-viewport.ts +152 -0
  278. package/src/authoring/world-pan-state.ts +198 -0
  279. package/src/authoring/world-session-state.ts +101 -0
  280. package/src/authoring/write-pipe.ts +173 -0
  281. package/src/availability-tick.ts +66 -0
  282. package/src/bitmap-label.ts +120 -0
  283. package/src/blender-tab-metrics.ts +161 -0
  284. package/src/board-open-actions.ts +20 -0
  285. package/src/boot-routing.ts +391 -0
  286. package/src/build-identity.ts +15 -0
  287. package/src/bytes-codec.ts +62 -0
  288. package/src/camera-authoring.ts +175 -0
  289. package/src/cancellation-reason.ts +58 -0
  290. package/src/canvas-preview-frames.ts +482 -0
  291. package/src/capture-camera-pose.ts +77 -0
  292. package/src/chrome-registry.ts +159 -0
  293. package/src/chrome-slot-registry.ts +91 -0
  294. package/src/collaboration-client.ts +264 -0
  295. package/src/collaboration-presence.ts +41 -0
  296. package/src/command-dispatch.ts +19 -0
  297. package/src/command-listener.ts +2743 -0
  298. package/src/command-registry.ts +70 -0
  299. package/src/component-board-registry.ts +205 -0
  300. package/src/component-states-registry.ts +200 -0
  301. package/src/components/AgentPresentationNotice.tsx +75 -0
  302. package/src/components/AlignToolbar.tsx +204 -0
  303. package/src/components/AppRoot.tsx +394 -0
  304. package/src/components/ApplicationMenus.tsx +415 -0
  305. package/src/components/AssetBrowser.tsx +2843 -0
  306. package/src/components/AssetEditorShell.tsx +216 -0
  307. package/src/components/AssetInspectorToolSection.tsx +124 -0
  308. package/src/components/BoardRulers.tsx +354 -0
  309. package/src/components/CameraInfo.tsx +81 -0
  310. package/src/components/CanvasSceneViewport.tsx +556 -0
  311. package/src/components/CapabilityCoverageSection.tsx +185 -0
  312. package/src/components/CenterDocuments.tsx +22 -0
  313. package/src/components/ChromeSlot.tsx +20 -0
  314. package/src/components/CodeView.tsx +470 -0
  315. package/src/components/CollaborationHeaderControl.css +74 -0
  316. package/src/components/CompactInspectorCard.tsx +190 -0
  317. package/src/components/CompactInspectorShell.tsx +39 -0
  318. package/src/components/ConsolePanel.css +55 -0
  319. package/src/components/ConsolePanel.tsx +270 -0
  320. package/src/components/DefaultEditorLayout.tsx +106 -0
  321. package/src/components/DocumentHeaderStrip.tsx +96 -0
  322. package/src/components/DocumentShelfRail.tsx +45 -0
  323. package/src/components/DocumentThumbnail.tsx +345 -0
  324. package/src/components/EditorLeaseGuard.tsx +372 -0
  325. package/src/components/ErrorBanner.tsx +123 -0
  326. package/src/components/FolderPreviewTile.tsx +159 -0
  327. package/src/components/GameHierarchy.tsx +3842 -0
  328. package/src/components/GameplaySessionTimeline.tsx +295 -0
  329. package/src/components/InspectionProjection.tsx +968 -0
  330. package/src/components/Inspector.tsx +233 -0
  331. package/src/components/InspectorCanvasPreview.tsx +35 -0
  332. package/src/components/InspectorFieldsSection.tsx +290 -0
  333. package/src/components/InspectorObjectPreview.tsx +57 -0
  334. package/src/components/InspectorStoriesSection.tsx +92 -0
  335. package/src/components/InspectorToolSection.tsx +96 -0
  336. package/src/components/InspectorTransformSection.tsx +245 -0
  337. package/src/components/LightExplorerPanel.tsx +433 -0
  338. package/src/components/MediaProperties.tsx +145 -0
  339. package/src/components/NonThreeAuthoringBootstrap.tsx +19 -0
  340. package/src/components/Object3DDocumentToolbar.css +100 -0
  341. package/src/components/Object3DDocumentToolbar.tsx +454 -0
  342. package/src/components/Object3DDocumentViewport.tsx +58 -0
  343. package/src/components/ProjectHeader.css +617 -0
  344. package/src/components/ProjectHeader.tsx +328 -0
  345. package/src/components/ProjectLayout.tsx +249 -0
  346. package/src/components/ReactCanvasControls.tsx +284 -0
  347. package/src/components/RootSelectionOverlay.tsx +3162 -0
  348. package/src/components/RootTextEditor.tsx +79 -0
  349. package/src/components/SaveStatus.tsx +70 -0
  350. package/src/components/StageHost.tsx +1954 -0
  351. package/src/components/StageOverlays.tsx +21 -0
  352. package/src/components/StartupErrorScreen.tsx +76 -0
  353. package/src/components/StartupLoadingScreen.tsx +65 -0
  354. package/src/components/StatsOverlay.tsx +78 -0
  355. package/src/components/SurfaceStateOverlay.tsx +24 -0
  356. package/src/components/ToolContributionSurfaces.tsx +65 -0
  357. package/src/components/ToolHost.tsx +370 -0
  358. package/src/components/ToolObject3DPreview.tsx +39 -0
  359. package/src/components/Toolbar.tsx +595 -0
  360. package/src/components/TransientHint.tsx +44 -0
  361. package/src/components/TransportStrip.tsx +174 -0
  362. package/src/components/VersionControlSection.tsx +470 -0
  363. package/src/components/VgaiLogo.css +83 -0
  364. package/src/components/VgaiLogo.tsx +35 -0
  365. package/src/components/ViewportControlsHint.tsx +60 -0
  366. package/src/components/ViewportFurniture.tsx +401 -0
  367. package/src/components/ViewportOverlay.tsx +145 -0
  368. package/src/components/ViewportOverlaysMenu.tsx +153 -0
  369. package/src/components/ViewportShadingMenu.tsx +300 -0
  370. package/src/components/ViewportViewMenu.tsx +101 -0
  371. package/src/components/WorkspaceDocumentSurface.tsx +204 -0
  372. package/src/components/WorkspaceUtilitySurface.tsx +22 -0
  373. package/src/components/WorktreeSwitcher.css +239 -0
  374. package/src/components/WorktreeSwitcher.tsx +890 -0
  375. package/src/components/account-documents.tsx +1162 -0
  376. package/src/components/asset-documents.tsx +812 -0
  377. package/src/components/asset-editor-persistence.ts +216 -0
  378. package/src/components/asset-selection-section.tsx +535 -0
  379. package/src/components/asset-thumbnails.tsx +312 -0
  380. package/src/components/asset-viewers/AudioViewer.tsx +201 -0
  381. package/src/components/asset-viewers/EntityModelDocument.tsx +122 -0
  382. package/src/components/asset-viewers/EnvironmentAssetDocument.tsx +440 -0
  383. package/src/components/asset-viewers/GenericJsonViewer.tsx +102 -0
  384. package/src/components/asset-viewers/ImageViewer.tsx +300 -0
  385. package/src/components/asset-viewers/JsonAssetDocument.tsx +93 -0
  386. package/src/components/asset-viewers/LiveModuleDocument.tsx +417 -0
  387. package/src/components/asset-viewers/LutAssetDocument.tsx +444 -0
  388. package/src/components/asset-viewers/ModelAssetDocument.tsx +105 -0
  389. package/src/components/asset-viewers/Object3DPreview.tsx +359 -0
  390. package/src/components/asset-viewers/OnlineAssetDetail.tsx +421 -0
  391. package/src/components/asset-viewers/PasteboardModuleDocument.tsx +67 -0
  392. package/src/components/asset-viewers/QuarksAssetDocument.tsx +526 -0
  393. package/src/components/asset-viewers/ShaderAssetDocument.tsx +743 -0
  394. package/src/components/asset-viewers/SourceAssetViewer.tsx +281 -0
  395. package/src/components/asset-viewers/SpritesheetSpriteView.tsx +102 -0
  396. package/src/components/asset-viewers/VideoViewer.tsx +101 -0
  397. package/src/components/asset-viewers/shader-source.ts +144 -0
  398. package/src/components/asset-workflow.css +731 -0
  399. package/src/components/board-guides.ts +150 -0
  400. package/src/components/compact-inspector.css +539 -0
  401. package/src/components/core-utilities.tsx +90 -0
  402. package/src/components/editor-notifications.css +40 -0
  403. package/src/components/engine-workspace.css +274 -0
  404. package/src/components/inspector-preview-section.tsx +238 -0
  405. package/src/components/inspector-property-grouping.ts +64 -0
  406. package/src/components/inspector-revert-label.ts +20 -0
  407. package/src/components/inspector-selection.ts +42 -0
  408. package/src/components/inspector-stories-gating.ts +171 -0
  409. package/src/components/inspector-transform-subject.ts +11 -0
  410. package/src/components/inspector-transform.ts +75 -0
  411. package/src/components/kind-documents.tsx +500 -0
  412. package/src/components/palette-action-publisher.tsx +140 -0
  413. package/src/components/primitives/DraftColorInput.tsx +74 -0
  414. package/src/components/product-shell.css +1829 -0
  415. package/src/components/project-tool-documents.tsx +410 -0
  416. package/src/components/scene-documents.tsx +223 -0
  417. package/src/components/stage-keyboard.tsx +37 -0
  418. package/src/components/stage-overlay-set.tsx +107 -0
  419. package/src/components/stage-presence-markers.ts +478 -0
  420. package/src/components/standard-viewport-dressing.ts +417 -0
  421. package/src/components/status-contributions.tsx +403 -0
  422. package/src/components/tool-documents.tsx +309 -0
  423. package/src/components/tool-schema-form.tsx +262 -0
  424. package/src/components/use-after-paint.ts +41 -0
  425. package/src/components/use-project-image-assets.ts +86 -0
  426. package/src/components/viewport-header-controls.css +90 -0
  427. package/src/components/viewport-surface-status.tsx +55 -0
  428. package/src/components/workspace-history.ts +32 -0
  429. package/src/components/workspace-static-panel-registry.tsx +152 -0
  430. package/src/components/workspace-surfaces.css +621 -0
  431. package/src/components/world-documents.tsx +586 -0
  432. package/src/components/world-overlay-gestures.ts +1694 -0
  433. package/src/composite-screenshot.ts +1641 -0
  434. package/src/console-sync.ts +131 -0
  435. package/src/constraint-helper.ts +338 -0
  436. package/src/content-entry-source-registry.ts +184 -0
  437. package/src/coverage/authoring-seam-evidence.ts +300 -0
  438. package/src/coverage/canvas-reveal.ts +192 -0
  439. package/src/coverage/design-time-surfaces.ts +101 -0
  440. package/src/coverage/live-seam-evidence.ts +11 -0
  441. package/src/coverage/ontology-invariants.ts +466 -0
  442. package/src/coverage/session-vitals.ts +501 -0
  443. package/src/coverage/system-seam-evidence.ts +72 -0
  444. package/src/crash-null-boundary.ts +36 -0
  445. package/src/creation-site-edit.ts +1479 -0
  446. package/src/creation-site-registry.ts +160 -0
  447. package/src/delegate-harness-registry.ts +143 -0
  448. package/src/document-context-registry.ts +177 -0
  449. package/src/document-open-registry.ts +200 -0
  450. package/src/document-preview-source.ts +20 -0
  451. package/src/document-renderer-session.ts +138 -0
  452. package/src/editor-api.ts +46 -0
  453. package/src/editor-chrome-capture.ts +139 -0
  454. package/src/editor-commands.ts +164 -0
  455. package/src/editor-console.ts +563 -0
  456. package/src/editor-current-view.ts +82 -0
  457. package/src/editor-document-probe.ts +881 -0
  458. package/src/editor-git-client.ts +115 -0
  459. package/src/editor-host-door.ts +462 -0
  460. package/src/editor-hotkeys.ts +842 -0
  461. package/src/editor-lease-view.ts +39 -0
  462. package/src/editor-lease.ts +415 -0
  463. package/src/editor-mode.ts +19 -0
  464. package/src/editor-notifications.ts +140 -0
  465. package/src/editor-presence.ts +563 -0
  466. package/src/editor-presentation-activity.ts +50 -0
  467. package/src/editor-presentation-notice.ts +42 -0
  468. package/src/editor-runtime.tsx +145 -0
  469. package/src/editor-server-response.ts +86 -0
  470. package/src/editor-session-attribution.ts +75 -0
  471. package/src/editor-session-mode.ts +54 -0
  472. package/src/editor-shell-store.ts +1304 -0
  473. package/src/editor-state-facets.ts +74 -0
  474. package/src/editor-styles.css +33 -0
  475. package/src/editor-view-presentation.ts +810 -0
  476. package/src/editor-viewport.ts +5302 -0
  477. package/src/entity-lod.ts +31 -0
  478. package/src/entity-object.ts +91 -0
  479. package/src/files/file-provider.ts +62 -0
  480. package/src/files/project-files.ts +264 -0
  481. package/src/finders/index.ts +136 -0
  482. package/src/finders/scenes-from-entrypoint-selection.ts +387 -0
  483. package/src/frame/bridge.tsx +1408 -0
  484. package/src/frame/product.ts +77 -0
  485. package/src/gameplay-dom-recording.ts +319 -0
  486. package/src/gameplay-export-state.ts +14 -0
  487. package/src/gameplay-replay.ts +417 -0
  488. package/src/gameplay-session-time.ts +9 -0
  489. package/src/gameplay-sessions.ts +204 -0
  490. package/src/harness-chat-types.ts +253 -0
  491. package/src/hierarchy-component-marks.ts +298 -0
  492. package/src/hierarchy-drop.ts +91 -0
  493. package/src/hierarchy-expansion-state.ts +80 -0
  494. package/src/hierarchy-header-slot.ts +52 -0
  495. package/src/hierarchy-internals.ts +197 -0
  496. package/src/hierarchy-kind-icon.ts +217 -0
  497. package/src/hierarchy-mark-reader.ts +73 -0
  498. package/src/hierarchy-menu-registry.ts +67 -0
  499. package/src/hierarchy-node-rows.ts +307 -0
  500. package/src/hierarchy-panel-view.ts +280 -0
  501. package/src/hierarchy-projection.ts +76 -0
  502. package/src/hierarchy-row-cache.ts +243 -0
  503. package/src/hierarchy-row-model.ts +308 -0
  504. package/src/hierarchy-rows.ts +11 -0
  505. package/src/hierarchy-walk.ts +86 -0
  506. package/src/history/editor-session.ts +25 -0
  507. package/src/history/history-commands.ts +147 -0
  508. package/src/history/history-delegate.ts +163 -0
  509. package/src/history/history-limit-notices.ts +43 -0
  510. package/src/history/history-service.ts +1173 -0
  511. package/src/history/persistence-coordinator.ts +35 -0
  512. package/src/history/project-file-history.ts +386 -0
  513. package/src/history/project-root-history-backends.ts +139 -0
  514. package/src/history/resource-registry.ts +209 -0
  515. package/src/history/snapshot-store.ts +103 -0
  516. package/src/history/source-history-backend.ts +546 -0
  517. package/src/history/types.ts +124 -0
  518. package/src/hmr-registration-group.ts +67 -0
  519. package/src/hmr-stable-react-context.ts +23 -0
  520. package/src/hotkeys.ts +188 -0
  521. package/src/inference-diagnostics.ts +69 -0
  522. package/src/initial-project.ts +80 -0
  523. package/src/inspection/active-subject.ts +578 -0
  524. package/src/inspection/active-surface.ts +142 -0
  525. package/src/inspection/compose.ts +1064 -0
  526. package/src/inspection/display.ts +170 -0
  527. package/src/inspection/document-subject.ts +109 -0
  528. package/src/inspection/game-subject.ts +85 -0
  529. package/src/inspection/model.ts +542 -0
  530. package/src/inspection/null-subject.ts +115 -0
  531. package/src/inspection/serialize.ts +357 -0
  532. package/src/inspection/use-active-inspection.ts +180 -0
  533. package/src/inspector-presentation.ts +201 -0
  534. package/src/inspector-section-registry.ts +221 -0
  535. package/src/instance-source-actions.ts +163 -0
  536. package/src/instanced-presentation.ts +164 -0
  537. package/src/js-heap.ts +71 -0
  538. package/src/key-actions.ts +91 -0
  539. package/src/keymap-presets.ts +401 -0
  540. package/src/layout-policy.ts +31 -0
  541. package/src/learn-links.ts +73 -0
  542. package/src/light-explorer-model.ts +134 -0
  543. package/src/live-canvas-frame.ts +55 -0
  544. package/src/live-document.ts +279 -0
  545. package/src/live-module-source.ts +230 -0
  546. package/src/live-session-registry.ts +220 -0
  547. package/src/live-transition.ts +633 -0
  548. package/src/manifest-project.ts +107 -0
  549. package/src/model-thumbnail.ts +523 -0
  550. package/src/native-selection-style.ts +202 -0
  551. package/src/object3d-document-write-policy.ts +137 -0
  552. package/src/packaged-runtime.ts +108 -0
  553. package/src/palettes/maya.palette.json +57 -0
  554. package/src/palettes/substance.palette.json +57 -0
  555. package/src/pasteboard-module.ts +81 -0
  556. package/src/performance-profiler.ts +367 -0
  557. package/src/performance-sources.ts +69 -0
  558. package/src/play-boot-phase.ts +145 -0
  559. package/src/presentation-surface.ts +248 -0
  560. package/src/project-adapter.ts +1143 -0
  561. package/src/project-asset-refresh.ts +26 -0
  562. package/src/project-declaration-refresh.ts +31 -0
  563. package/src/project-local-state.ts +118 -0
  564. package/src/project-manager.ts +243 -0
  565. package/src/project-module-changes.ts +163 -0
  566. package/src/project-module-split.ts +266 -0
  567. package/src/project-provenance.ts +115 -0
  568. package/src/project-ready.ts +42 -0
  569. package/src/project-session-reset.ts +67 -0
  570. package/src/project-shape.ts +68 -0
  571. package/src/project-tool-discovery.ts +66 -0
  572. package/src/project-tools.ts +107 -0
  573. package/src/project-work-types.ts +149 -0
  574. package/src/projection/three.ts +898 -0
  575. package/src/projection/types.ts +44 -0
  576. package/src/readiness.ts +113 -0
  577. package/src/reflection-probe-helper.ts +142 -0
  578. package/src/reported-play-state.ts +90 -0
  579. package/src/resolve-relative-specifier.ts +33 -0
  580. package/src/scene-document-plan.ts +315 -0
  581. package/src/scene-framing.ts +315 -0
  582. package/src/scene-live-open.ts +210 -0
  583. package/src/scene-view-fog.ts +89 -0
  584. package/src/scoped-game-css.ts +152 -0
  585. package/src/session-orphan-record.ts +193 -0
  586. package/src/session-tombstone.ts +126 -0
  587. package/src/settings/settings-provider.ts +82 -0
  588. package/src/settings-store.ts +345 -0
  589. package/src/shared-view-restore.ts +42 -0
  590. package/src/shell-store-door.ts +45 -0
  591. package/src/source-conflict.ts +122 -0
  592. package/src/spatial-handle-visuals.ts +332 -0
  593. package/src/stage-context.ts +341 -0
  594. package/src/stage-store-registry.ts +63 -0
  595. package/src/stale-chunk-recovery.ts +34 -0
  596. package/src/state-report-deferral.ts +73 -0
  597. package/src/storage/host-files-storage.ts +97 -0
  598. package/src/storage/http-storage.ts +174 -0
  599. package/src/storage/index.ts +75 -0
  600. package/src/storage/mem-storage.ts +149 -0
  601. package/src/storage/path-lock.ts +44 -0
  602. package/src/storage/paths.ts +26 -0
  603. package/src/storage/types.ts +116 -0
  604. package/src/stories/StoryComponentThumbnail.tsx +184 -0
  605. package/src/stories/StoryPreviewMount.tsx +306 -0
  606. package/src/stories/component-content-source.tsx +106 -0
  607. package/src/stories/component-states-source.ts +78 -0
  608. package/src/stories/compose-project-stories.ts +255 -0
  609. package/src/stories/pixi-story-model.ts +30 -0
  610. package/src/stories/prefabs-finder.ts +54 -0
  611. package/src/stories/prefabs-from-stories.ts +182 -0
  612. package/src/stories/project-story-discovery.ts +78 -0
  613. package/src/stories/project-story-regions.ts +24 -0
  614. package/src/stories/story-actions.ts +54 -0
  615. package/src/stories/story-capture-command.ts +124 -0
  616. package/src/stories/story-capture.ts +584 -0
  617. package/src/stories/story-declared-medium.ts +126 -0
  618. package/src/stories/story-discovery.ts +176 -0
  619. package/src/stories/story-document-openers.ts +36 -0
  620. package/src/stories/story-dom-runtime.ts +78 -0
  621. package/src/stories/story-grouping.ts +111 -0
  622. package/src/stories/story-lane.ts +88 -0
  623. package/src/stories/story-mount-turn.ts +27 -0
  624. package/src/stories/story-opener.ts +260 -0
  625. package/src/stories/story-pixi-preview.ts +408 -0
  626. package/src/stories/story-presentation.ts +215 -0
  627. package/src/stories/story-registry.ts +517 -0
  628. package/src/stories/story-three-preview.ts +807 -0
  629. package/src/stories/three-story-model.ts +96 -0
  630. package/src/story-three-preview-runtime.ts +56 -0
  631. package/src/surface-keyboard.ts +112 -0
  632. package/src/surface-state.ts +124 -0
  633. package/src/tab-bootstrap.js +628 -0
  634. package/src/tab-census.ts +198 -0
  635. package/src/tab-lifecycle-client.ts +196 -0
  636. package/src/theme-library.ts +844 -0
  637. package/src/theme-preference.ts +378 -0
  638. package/src/theme.css +3437 -0
  639. package/src/three-viewport/bone-selection-highlight.ts +119 -0
  640. package/src/three-viewport/camera-fit.ts +41 -0
  641. package/src/three-viewport/interactive-renderer.ts +132 -0
  642. package/src/three-viewport/selection-brackets.ts +310 -0
  643. package/src/three-viewport/selection-outline.ts +183 -0
  644. package/src/three-viewport/skeleton-helper.ts +61 -0
  645. package/src/three-viewport/source-color.ts +167 -0
  646. package/src/three-viewport/studio-environment.ts +96 -0
  647. package/src/three-viewport-presentation.ts +23 -0
  648. package/src/tool-contribution-play.ts +74 -0
  649. package/src/tool-loader.ts +1798 -0
  650. package/src/transform-mode-request.ts +57 -0
  651. package/src/transient-hint.ts +78 -0
  652. package/src/trigger-volume-helper.ts +116 -0
  653. package/src/ui-source/adapter-region-includes.ts +241 -0
  654. package/src/ui-source/ensure-import.ts +132 -0
  655. package/src/ui-source/file-region-resolver.ts +302 -0
  656. package/src/ui-source/inspect.ts +775 -0
  657. package/src/ui-source/oid-transform.ts +1845 -0
  658. package/src/ui-source/plan-csf-story.ts +196 -0
  659. package/src/ui-source/plan-extract-component.ts +421 -0
  660. package/src/ui-source/plan-fork-component.ts +621 -0
  661. package/src/ui-source/plan-named-style.ts +95 -0
  662. package/src/ui-source/plan-source-edit.ts +353 -0
  663. package/src/ui-source/r3f-contract-resolver.ts +327 -0
  664. package/src/ui-source/r3f-diagnostic-index.ts +165 -0
  665. package/src/ui-source/r3f-environment-binding.ts +133 -0
  666. package/src/ui-source/r3f-joint-binding.ts +251 -0
  667. package/src/ui-source/r3f-lod-binding.ts +91 -0
  668. package/src/ui-source/r3f-particle-binding.ts +235 -0
  669. package/src/ui-source/r3f-physics-binding.ts +483 -0
  670. package/src/ui-source/r3f-project-contracts.ts +444 -0
  671. package/src/ui-source/relative-import-specifier.ts +34 -0
  672. package/src/ui-source/reparent-guard.ts +544 -0
  673. package/src/ui-source/source-edit-request.ts +97 -0
  674. package/src/ui-source/source-write-backend.ts +618 -0
  675. package/src/ui-source/syntactic-prop-specs.ts +296 -0
  676. package/src/ui-source/tier-source-write-backend.ts +279 -0
  677. package/src/ui-source/ts-ast.ts +171 -0
  678. package/src/ui-source/utility-class-support.ts +169 -0
  679. package/src/ui-source/write-component-default.ts +133 -0
  680. package/src/ui-source/writer.ts +1989 -0
  681. package/src/viewport-activation-timings.ts +840 -0
  682. package/src/viewport-authoring-policy.ts +186 -0
  683. package/src/viewport-controls-hint.ts +57 -0
  684. package/src/viewport-door.ts +237 -0
  685. package/src/viewport-shading-boundary.ts +12 -0
  686. package/src/vite-error-surface.ts +141 -0
  687. package/src/wait-until.ts +37 -0
  688. package/src/workspace-areas.ts +156 -0
  689. package/src/workspace-aux-commands.ts +11 -0
  690. package/src/workspace-available-documents.ts +127 -0
  691. package/src/workspace-core-utilities.ts +31 -0
  692. package/src/workspace-document-ids.ts +59 -0
  693. package/src/workspace-document-registry.ts +612 -0
  694. package/src/workspace-document-restore.ts +145 -0
  695. package/src/workspace-host-commands.ts +141 -0
  696. package/src/workspace-persistence-gate.ts +40 -0
  697. package/src/workspace-play-utilities.ts +44 -0
  698. package/src/workspace-presets.ts +425 -0
  699. package/src/workspace-regions.ts +294 -0
  700. package/src/workspace-state-persistence.ts +536 -0
  701. package/src/workspace-static-panels.ts +73 -0
  702. package/src/workspace-status-registry.ts +121 -0
  703. package/src/workspace-style.ts +239 -0
  704. package/src/workspace-utility-commands.ts +74 -0
  705. package/src/workspace-utility-registry.ts +263 -0
  706. package/src/workspace-viewport-rect.ts +97 -0
  707. package/src/world-adoption.ts +115 -0
  708. package/src/world-document-routing.ts +104 -0
  709. package/vite-plugin-creation-site-write.ts +184 -0
  710. package/vite-plugin-creation-site.ts +80 -0
  711. package/vite-plugin-game-static.ts +303 -0
  712. package/vite-plugin-module-doorways.ts +336 -0
  713. package/vite-plugin-product-contributions.ts +197 -0
  714. package/vite-plugin-project-game-static.ts +125 -0
  715. package/vite-plugin-project-jsx-js.ts +65 -0
  716. package/vite-plugin-project-root-absolute-assets.ts +359 -0
  717. package/vite-plugin-shared-react.ts +420 -0
  718. package/vite-plugin-shared-three.ts +203 -0
  719. package/vite-plugin-ui-oid.ts +2106 -0
@@ -0,0 +1,807 @@
1
+ /**
2
+ * Off-screen `three`/R3F story preview — render a composed CSF story that
3
+ * produces an R3F world OFF-SCREEN and hand back its root `THREE.Object3D`.
4
+ * Portable CSF is a prefab's explicit declaration and its sole preview
5
+ * authority, whether or not instances happen to exist in a scene.
6
+ *
7
+ * ## Why this module exists
8
+ *
9
+ * A discoverable CSF story (`*.stories.tsx`, found by `story-discovery.ts` and
10
+ * composed by `compose-project-stories.ts`) is the author's portable "here is
11
+ * how this prefab renders." This module mounts that story's R3F content in an
12
+ * isolated fiber root and returns the resulting scene as an ordinary
13
+ * `THREE.Object3D`, which callers feed to `captureObjectAssetPreview`.
14
+ * Scene placement, authoring snapshots, and model literals are deliberately
15
+ * outside this path: they describe instances or dependencies, not the prefab's
16
+ * authored representative state.
17
+ *
18
+ * ## How the off-screen mount works (and why no real WebGL is needed to build
19
+ * the graph)
20
+ *
21
+ * This mirrors `@vgai/game-runtime/world3d-react`'s `r3f-adapter.tsx` bridge, minus the
22
+ * engine runtime: `createRoot(canvas)` + `configure({ frameloop: 'never' })`,
23
+ * waiting for fiber's `onCreated` for the real `THREE.Scene` (React 19 gives no
24
+ * synchronous first commit). Two deliberate choices:
25
+ *
26
+ * - `extend(THREE)` first — fiber v9 tree-shakes the THREE catalogue, so a
27
+ * bare `createRoot` (no `<Canvas>`) throws "X is not part of the THREE
28
+ * namespace" on the first intrinsic (`<object3D>`, `<mesh>`, …) without it.
29
+ * This is the exact reason `r3f-adapter.tsx` calls `extend(host.three)`.
30
+ * - The renderer is a STUB (`createStubRenderer`). Under `frameloop: 'never'`
31
+ * fiber never calls `gl.render`, and the pixels are produced later by
32
+ * `captureObjectAssetPreview`, which builds its OWN `WebGLRenderer`. So the
33
+ * reconciler builds the real object graph with no WebGL context at all —
34
+ * which is what lets the mount (the load-bearing logic) run headless under
35
+ * jsdom while the snapshot stays environment-bound.
36
+ *
37
+ * `.load()` (the story's `loaders`) is awaited before the first render, the
38
+ * same contract `StoryPreviewMount.tsx` documents: Storybook 9 only populates a
39
+ * story's loaded data once `.load()` itself has run. A `three` prefab story's
40
+ * loader is where it builds the runtime context its component needs (e.g. the
41
+ * squash `Mob` story loads its `.glb` and inits Rapier there).
42
+ *
43
+ * ## What the mount photographs: the FIRST COMMIT
44
+ *
45
+ * Because the subject is whatever fiber commits first, a story that wraps its
46
+ * three content in `<Suspense>` hands back the FALLBACK — the boundary commits
47
+ * immediately with its (non-three) fallback subtree, so the story is skipped as
48
+ * "no three content" while the real children arrive on a later commit nobody is
49
+ * waiting for. `useGLTF` and drei's other suspending loaders WITHOUT a boundary
50
+ * are the shape that works: with nothing to catch it the whole root suspends,
51
+ * and the first commit is the one that already carries the loaded content.
52
+ *
53
+ * That shape has a price, and it is the other half of the same fact: an
54
+ * unboundaried suspend delays the FIRST COMMIT until the content is ready, so a
55
+ * story whose load outruns {@link ONCREATED_TIMEOUT_MS} rejects at the ceiling —
56
+ * a big asset either loads fast here or the ceiling is its limit.
57
+ *
58
+ * ## Physics: the context a naive story of a `RigidBody` component never has
59
+ *
60
+ * `@react-three/rapier`'s hooks read a React context that only `<Physics>`
61
+ * provides, so a colocated story of an ordinary gameplay prefab — the common
62
+ * case, since template/example prefabs wrap themselves in `<RigidBody>` — dies
63
+ * on `react-three-rapier: useRapier must be used within <Physics />!` the
64
+ * moment a preview surface mounts it. That is a collision between two shipped
65
+ * rules (every reusable gameplay component gets a colocated story; gameplay
66
+ * components use rapier), not a story-authoring mistake, so the mount resolves
67
+ * it: {@link mountStoryObject3D} RETRIES the mount once inside a real
68
+ * `<Physics paused>` when — and only when — the first attempt failed with
69
+ * exactly that error (see {@link isMissingPhysicsContext}).
70
+ *
71
+ * Retry rather than always-wrap, deliberately:
72
+ * - a story that needs no physics never imports rapier, never initializes its
73
+ * WASM, and mounts byte-identically to before;
74
+ * - a story that provides its OWN `<Physics>` never throws, so it never
75
+ * reaches the retry and is never double-wrapped. (Nesting is harmless
76
+ * anyway — measured: an inner `<Physics>` simply overrides the outer
77
+ * context and the tree mounts identically — but not relying on that is
78
+ * cheaper than relying on it.)
79
+ * The retry's `<Physics>` is NOT paused, and that is the whole point: every
80
+ * story mount runs the ONE bounded design-time settle below before it is handed
81
+ * to anybody, so what a surface photographs is the component's REST pose — a
82
+ * body on its suspension, a wheel the physics worker has actually placed — and
83
+ * then content time is held forever. A paused provider would freeze the world
84
+ * at a spawn pose whose wheels no worker ever positioned (measured: the
85
+ * racing-game `Vehicle` exhibit rendered wheelless).
86
+ *
87
+ * The context is only real if the `<Physics>` provider and the story's hooks
88
+ * come from the SAME `@react-three/rapier` module instance — a scaffolded
89
+ * project resolves its own copy from its own `node_modules`, which would make
90
+ * two contexts and leave the error exactly where it was. Repo-root
91
+ * `vite.config.ts`'s `resolve.dedupe` carries the package for that reason, the
92
+ * same identity contract already spelled out there for `@react-three/fiber`.
93
+ * The packaged runtime resolves both the preview renderer and this lazy
94
+ * provider through the project-rooted Vite graph, so the context identity is
95
+ * the same there too; `story-three-preview-runtime.ts` owns that seam.
96
+ */
97
+
98
+ import type { RootState } from '@react-three/fiber';
99
+ import { Component, type ReactNode } from 'react';
100
+ import * as THREE from 'three';
101
+ import { captureObjectAssetPreview } from '../asset-preview';
102
+ import { settleDesignWorld } from '../authoring/design-time-settle';
103
+ import { CrashNullBoundary } from '../crash-null-boundary';
104
+ import {
105
+ resolveStoryThreePreviewRuntime,
106
+ type StoryThreePreviewRuntime,
107
+ } from '../story-three-preview-runtime';
108
+ import { viewportTimingsEnabled } from '../viewport-activation-timings';
109
+ import { runInStoryMountTurn } from './story-mount-turn';
110
+
111
+ /** Last mount's phase split — only written when timings are on. The board
112
+ * reads this after each attempt so a per-story row can name load vs fiber
113
+ * vs settle without threading a callback through the mount turn. */
114
+ export interface StoryMountPhaseTiming {
115
+ readonly runtimeMs: number;
116
+ readonly loadMs: number;
117
+ readonly fiberMs: number;
118
+ readonly settleMs: number;
119
+ }
120
+
121
+ let lastMountPhases: StoryMountPhaseTiming | null = null;
122
+
123
+ export function lastStoryMountPhaseTiming(): StoryMountPhaseTiming | null {
124
+ return lastMountPhases;
125
+ }
126
+
127
+ /** A composed portable story is a callable React component; Storybook attaches
128
+ * an optional `.load()` when the CSF export declares `loaders`. A plain React
129
+ * component (no `.load`) is accepted too — the load step is simply skipped. */
130
+ export type StoryPreviewComponent = React.ComponentType<Record<string, unknown>> & {
131
+ load?: () => Promise<unknown>;
132
+ };
133
+
134
+ export interface MountedStoryObject3D {
135
+ /** The named wrapper group the story rendered INTO — a single `THREE.Object3D`
136
+ * holding exactly the story's content (see {@link PREVIEW_ROOT_NAME}). Hand
137
+ * it straight to `captureObjectAssetPreview`; framing a precise wrapper
138
+ * rather than the whole `THREE.Scene` keeps default lights/helpers fiber may
139
+ * add out of the shot. */
140
+ readonly root: THREE.Object3D;
141
+ /** The authored R3F scene around `root`, including its background,
142
+ * environment, fog, and scene-level camera declarations. */
143
+ readonly scene: THREE.Scene;
144
+ /** Advance only through an owning Asset Lab transport (animation/physics
145
+ * preview). Merely mounting a story never calls this, so Edit remains held. */
146
+ advance(deltaSeconds: number): void;
147
+ /** Tear the isolated fiber root down. Runs the story's own effect cleanups
148
+ * (a translated scene's `detachNodes`, releasing its Rapier handles). */
149
+ dispose(): void;
150
+ }
151
+
152
+ /** The name of the wrapper `<group>` every story is rendered into, so its root
153
+ * `Object3D` can be extracted unambiguously from fiber's scene. */
154
+ export const PREVIEW_ROOT_NAME = 'vgai:story-preview-root';
155
+
156
+ /** How long to wait for fiber's first commit before giving up. The LAST-RESORT
157
+ * ceiling only: a reconcile-time crash is now caught by
158
+ * {@link CrashNullBoundary} and rejects immediately (see its doc comment), so
159
+ * reaching this timeout means the story neither committed nor threw. TWO causes
160
+ * reach it, and the common one is not a bug: a story still SUSPENDED on a slow
161
+ * load (the unboundaried shape the module doc recommends holds the first commit
162
+ * until its content is ready), or a genuinely hung render. Matches the 10s the
163
+ * engine's own `r3f-adapter.tsx` uses. */
164
+ const ONCREATED_TIMEOUT_MS = 10_000;
165
+
166
+ /**
167
+ * A React fiber, just enough to walk the committed tree for a `<Physics>`
168
+ * provider. Both `@react-three/rapier` and `@react-three/cannon` export that
169
+ * name; the settle must not import either library to ask.
170
+ */
171
+ interface ReactFiberLike {
172
+ readonly type?: unknown;
173
+ readonly elementType?: unknown;
174
+ readonly memoizedProps?: unknown;
175
+ readonly child?: ReactFiberLike | null;
176
+ readonly sibling?: ReactFiberLike | null;
177
+ }
178
+
179
+ function reactComponentName(type: unknown): string {
180
+ if (typeof type === 'function') {
181
+ const named = type as { name?: string; displayName?: string };
182
+ return named.displayName || named.name || '';
183
+ }
184
+ if (type && typeof type === 'object') {
185
+ const wrapped = type as {
186
+ displayName?: string;
187
+ name?: string;
188
+ type?: unknown;
189
+ render?: unknown;
190
+ };
191
+ if (typeof wrapped.displayName === 'string' && wrapped.displayName.length > 0) {
192
+ return wrapped.displayName;
193
+ }
194
+ if (typeof wrapped.name === 'string' && wrapped.name.length > 0) return wrapped.name;
195
+ return reactComponentName(wrapped.type ?? wrapped.render);
196
+ }
197
+ return '';
198
+ }
199
+
200
+ function reactFiberOf(instance: object): ReactFiberLike | undefined {
201
+ const record = instance as Record<string, unknown>;
202
+ const fiber = record['_reactInternals'] ?? record['_reactInternalFiber'];
203
+ return fiber && typeof fiber === 'object' ? (fiber as ReactFiberLike) : undefined;
204
+ }
205
+
206
+ function physicsNodeFacts(node: ReactFiberLike): {
207
+ readonly provider: boolean;
208
+ readonly body: boolean;
209
+ readonly movingBody: boolean;
210
+ } {
211
+ const names = [reactComponentName(node.type), reactComponentName(node.elementType)];
212
+ const body = names.includes('RigidBody') || names.includes('InstancedRigidBodies');
213
+ if (!body) return { provider: names.includes('Physics'), body: false, movingBody: false };
214
+ const props =
215
+ node.memoizedProps && typeof node.memoizedProps === 'object'
216
+ ? (node.memoizedProps as Record<string, unknown>)
217
+ : {};
218
+ // Rapier's native default is dynamic. Kinematic bodies also own their pose
219
+ // and must settle; only `fixed` is inert.
220
+ return { provider: names.includes('Physics'), body: true, movingBody: props['type'] !== 'fixed' };
221
+ }
222
+
223
+ /**
224
+ * Whether this committed tree has physics that can change its design pose.
225
+ *
226
+ * A provider containing only fixed Rapier bodies has a physics subscriber,
227
+ * but there is nothing to settle: stepping it for the full "never observed
228
+ * motion" budget cost third-person's static tower/tree stories ~1.3 seconds
229
+ * apiece. Read the library's own `RigidBody type` declaration from the
230
+ * committed React tree. Unknown physics remains conservative and settles;
231
+ * only a tree whose identified bodies are ALL explicitly fixed skips.
232
+ */
233
+ function reactSubtreeNeedsPhysicsSettle(fiber: ReactFiberLike | undefined): boolean {
234
+ const stack: ReactFiberLike[] = fiber ? [fiber] : [];
235
+ const seen = new Set<ReactFiberLike>();
236
+ let hasPhysics = false;
237
+ let identifiedBody = false;
238
+ let movingBody = false;
239
+ while (stack.length > 0) {
240
+ const node = stack.pop()!;
241
+ if (seen.has(node)) continue;
242
+ seen.add(node);
243
+ const facts = physicsNodeFacts(node);
244
+ hasPhysics ||= facts.provider;
245
+ identifiedBody ||= facts.body;
246
+ movingBody ||= facts.movingBody;
247
+ if (node.child) stack.push(node.child);
248
+ if (node.sibling) stack.push(node.sibling);
249
+ }
250
+ if (!hasPhysics) return false;
251
+ return !identifiedBody || movingBody;
252
+ }
253
+
254
+ /**
255
+ * Walks the committed story tree after mount and reports whether physics can
256
+ * change its pose. Fixed-only worlds are already at their final pose.
257
+ */
258
+ class PhysicsSubscriberProbe extends Component<{
259
+ readonly onReport: (hasPhysics: boolean) => void;
260
+ readonly children?: ReactNode;
261
+ }> {
262
+ override componentDidMount(): void {
263
+ this.props.onReport(reactSubtreeNeedsPhysicsSettle(reactFiberOf(this)));
264
+ }
265
+
266
+ override render(): ReactNode {
267
+ return this.props.children ?? null;
268
+ }
269
+ }
270
+
271
+ /**
272
+ * The R3F reconciler refusing a non-`three` intrinsic — the QUALIFICATION
273
+ * REJECTION class, and the ONLY error class this module silences.
274
+ *
275
+ * A `<div>` in a story means "this is a DOM story", which is a question both 3D
276
+ * surfaces ask on purpose and answer by skipping. It is matched on the
277
+ * reconciler's own message because that message is what names the class: a
278
+ * story that DOES render three content and then throws for its own reason
279
+ * (a failed load, a bad prop) produces a different error and must still be
280
+ * reported like any other.
281
+ */
282
+ function isQualificationRejection(error: unknown): boolean {
283
+ return error instanceof Error && error.message.includes('is not part of the THREE namespace');
284
+ }
285
+
286
+ /**
287
+ * Errors the qualify / off-screen classify path produces for ITS OWN
288
+ * scaffolding — missing engine context, R3F hooks outside a Canvas — that
289
+ * must not reach the user's console as if the game threw them.
290
+ *
291
+ * "Hooks can only be used within the Canvas component" and "useGame: no Game
292
+ * in context" both leaked past {@link isQualificationRejection}: a `<Physics>`
293
+ * without a Canvas, or a story component reading the non-optional Game handle,
294
+ * fails the classify mount and Fiber's `reportError` files it as a session
295
+ * error. Both are classification-internal, the same class as the reconciler
296
+ * refusal.
297
+ */
298
+ function isQualifyInternalError(error: unknown): boolean {
299
+ if (isQualificationRejection(error)) return true;
300
+ if (!(error instanceof Error)) return false;
301
+ return (
302
+ error.message.includes('Hooks can only be used within the Canvas component') ||
303
+ error.message.includes('useGame: no Game in context')
304
+ );
305
+ }
306
+
307
+ /**
308
+ * The refusals the story CAPTURE lane may fall through to its DOM leg silently —
309
+ * "this is a DOM story", asked and answered by classification. Anything outside
310
+ * this class is a THREE story that genuinely failed, and per the contract above
311
+ * it must be NAMED where it falls through, never swallowed: an entire project's
312
+ * prefab sheet once photographed blank with the real error (`Asset preview
313
+ * source has no renderable bounds`) invisible behind a bare `catch`.
314
+ */
315
+ export function isStoryThreeClassificationRefusal(error: unknown): boolean {
316
+ return isQualifyInternalError(error);
317
+ }
318
+
319
+ /**
320
+ * `@react-three/rapier` refusing to hand out its context — the PHYSICS-CONTEXT
321
+ * class, and the one failure this module answers by mounting a second time (see
322
+ * the module doc's "Physics" section).
323
+ *
324
+ * Matched on the library's own message (`useRapier must be used within
325
+ * <Physics />`), which every rapier hook and `<RigidBody>` funnels through, for
326
+ * the same reason {@link isQualificationRejection} matches the reconciler's: the
327
+ * message is what names the class. A story that DOES have physics context and
328
+ * then fails for its own reason produces a different error and must still be
329
+ * reported.
330
+ *
331
+ * The rejection {@link mountStoryObject3DOnce} raises wraps the original
332
+ * message, so this reads the whole string rather than the `cause`.
333
+ */
334
+ function isMissingPhysicsContext(error: unknown): boolean {
335
+ return error instanceof Error && error.message.includes('useRapier must be used within <Physics');
336
+ }
337
+
338
+ /**
339
+ * The REAL `@react-three/rapier` `<Physics>` component, imported only once a
340
+ * story has proven it needs one (see {@link isMissingPhysicsContext}) — the same
341
+ * dynamic-import discipline the networking path uses, so a project with no
342
+ * physics never pulls rapier or its WASM into the editor session at all.
343
+ *
344
+ * `null` when the package cannot be resolved: nothing to retry with, so the
345
+ * caller re-throws the story's own error rather than inventing a second one.
346
+ */
347
+ async function loadPhysicsProvider(runtime: StoryThreePreviewRuntime): Promise<React.ComponentType<{
348
+ paused?: boolean;
349
+ children?: ReactNode;
350
+ }> | null> {
351
+ try {
352
+ const projectRapierPath = '/@id/@react-three/rapier';
353
+ const rapier = runtime.packaged
354
+ ? await import(/* @vite-ignore */ projectRapierPath)
355
+ : await import('@react-three/rapier');
356
+ return rapier.Physics as React.ComponentType<{ paused?: boolean; children?: ReactNode }>;
357
+ } catch {
358
+ return null;
359
+ }
360
+ }
361
+
362
+ interface ErrorReportingGlobal {
363
+ reportError?: ((error: unknown) => void) | undefined;
364
+ }
365
+
366
+ /**
367
+ * `createRoot`, with the qualification-rejection class filtered out of the
368
+ * root's error reporting.
369
+ *
370
+ * {@link CrashNullBoundary} already HANDLES that error, but handling it is not
371
+ * enough to keep it off the page: fiber pins React 19's
372
+ * `onUncaughtError`/`onCaughtError`/`onRecoverableError` to one
373
+ * `logRecoverableError` and exposes no override, and that reporter is
374
+ * `reportError` — which dispatches a real uncaught-`error` event. `the world root's stage`
375
+ * listens for exactly that and files it in `editorConsole`, so scanning a
376
+ * project's DOM stories used to leave the editor showing a red `N errors` badge
377
+ * for entirely by-design behaviour.
378
+ *
379
+ * Fiber captures that reporter SYNCHRONOUSLY inside `createRoot` (as
380
+ * `typeof reportError === 'function' ? reportError : console.error`), so the
381
+ * captured value is the one seam available — and the patch therefore lives
382
+ * exactly as long as the `createRoot` call, while the root itself keeps the
383
+ * filtering reporter for its whole life. Everything that is not a qualification
384
+ * rejection is forwarded to the reporter fiber would otherwise have captured,
385
+ * so no other failure is quietened anywhere.
386
+ *
387
+ * `retryable` extends the same containment to the physics-context class on the
388
+ * attempt that HAS a retry behind it: an unwrapped mount of a physics story is
389
+ * expected to fail and is immediately re-run inside `<Physics paused>`, so
390
+ * surfacing its first failure would put a red error badge on the editor for a
391
+ * preview that then renders perfectly — the flooded-console half of the same
392
+ * defect. The retry itself is NOT retryable, so a story that still fails under
393
+ * real physics reports exactly as loudly as any other broken story.
394
+ */
395
+ function createFilteredStoryRoot(
396
+ canvas: HTMLCanvasElement,
397
+ retryable: boolean,
398
+ createRoot: StoryThreePreviewRuntime['createR3FRoot'],
399
+ ): ReturnType<StoryThreePreviewRuntime['createR3FRoot']> {
400
+ const globals = globalThis as ErrorReportingGlobal;
401
+ const previous = globals.reportError;
402
+ const forward: (error: unknown) => void =
403
+ typeof previous === 'function'
404
+ ? previous.bind(globalThis)
405
+ : (error) => {
406
+ // biome-ignore lint/suspicious/noConsole: fiber's own fallback when the environment has no `reportError`; anything that is not a qualification rejection must still surface exactly as it would have.
407
+ console.error(error);
408
+ };
409
+ globals.reportError = (error: unknown) => {
410
+ if (isQualifyInternalError(error)) return;
411
+ if (retryable && isMissingPhysicsContext(error)) return;
412
+ forward(error);
413
+ };
414
+ try {
415
+ return createRoot(canvas);
416
+ } finally {
417
+ globals.reportError = previous;
418
+ }
419
+ }
420
+
421
+ /**
422
+ * A do-nothing stand-in for `THREE.WebGLRenderer`, enough for fiber's
423
+ * `configure()` to accept it while `frameloop: 'never'` guarantees `render()`
424
+ * is never called. Only the members fiber touches during configure/teardown
425
+ * are present.
426
+ */
427
+ function createStubRenderer(canvas: HTMLCanvasElement): Record<string, unknown> {
428
+ return {
429
+ domElement: canvas,
430
+ xr: {
431
+ enabled: false,
432
+ addEventListener() {},
433
+ removeEventListener() {},
434
+ setAnimationLoop() {},
435
+ getSession() {
436
+ return null;
437
+ },
438
+ },
439
+ shadowMap: { enabled: false, type: THREE.PCFSoftShadowMap },
440
+ outputColorSpace: THREE.SRGBColorSpace,
441
+ toneMapping: THREE.ACESFilmicToneMapping,
442
+ setPixelRatio() {},
443
+ setSize() {},
444
+ setClearColor() {},
445
+ setClearAlpha() {},
446
+ setViewport() {},
447
+ setScissor() {},
448
+ setScissorTest() {},
449
+ setAnimationLoop() {},
450
+ clear() {},
451
+ render() {},
452
+ compile() {},
453
+ dispose() {},
454
+ getPixelRatio() {
455
+ return 1;
456
+ },
457
+ getContext() {
458
+ // This non-rendering mount owns no GPU context. An empty object lies
459
+ // to components checking for one before constructing postprocessing.
460
+ return null;
461
+ },
462
+ };
463
+ }
464
+
465
+ let catalogueExtendedWith: StoryThreePreviewRuntime['extendThree'] | null = null;
466
+
467
+ /** A mount function valid inside an already-acquired story turn. Compound
468
+ * callers receive it so they can mount repeatedly without recursively joining
469
+ * the queue and deadlocking themselves. */
470
+ export type StoryMountInTurn = (
471
+ Component: StoryPreviewComponent,
472
+ props?: Record<string, unknown>,
473
+ ) => Promise<MountedStoryObject3D>;
474
+
475
+ /**
476
+ * Run one compound story-mount operation without any other mount interleaving.
477
+ *
478
+ * The queue itself is `story-mount-turn.ts` — shared with the canvas surface's
479
+ * own preview mount, because the process-global loader state two mounts collide
480
+ * over is not per-medium.
481
+ */
482
+ export function withStoryMountTurn<T>(task: (mount: StoryMountInTurn) => Promise<T>): Promise<T> {
483
+ return runInStoryMountTurn(() => task(mountStoryObject3DInTurn));
484
+ }
485
+
486
+ /** `extend(THREE)` once per session — it merges into a module-global catalogue,
487
+ * so repeating it per mount is wasted work, and it must run before ANY three
488
+ * intrinsic reconciles (see the module doc). */
489
+ function ensureThreeCatalogue(runtime: StoryThreePreviewRuntime): void {
490
+ if (catalogueExtendedWith === runtime.extendThree) return;
491
+ catalogueExtendedWith = runtime.extendThree;
492
+ runtime.extendThree(runtime.three as unknown as Parameters<typeof runtime.extendThree>[0]);
493
+ }
494
+
495
+ /**
496
+ * Mount one composed `three`/R3F story off-screen and return the named wrapper
497
+ * group it rendered into (see {@link PREVIEW_ROOT_NAME}) plus a `dispose()`. The
498
+ * group's children are exactly what the story rendered —
499
+ * `captureObjectAssetPreview` traverses it and frames the renderable geometry.
500
+ *
501
+ * `props` are passed straight to the composed story, which is Storybook's own
502
+ * portable-story contract for overriding args at render time (partial props
503
+ * over the composed args — the same mechanism the story documents use for
504
+ * its Inspector arg edits). Omitted, the story renders with its authored args.
505
+ *
506
+ * A story whose component uses `@react-three/rapier` is mounted a second time
507
+ * inside a real `<Physics>` — see the module doc's "Physics" section for
508
+ * why that retry is the shape, and why a story carrying its own `<Physics>`
509
+ * never takes it.
510
+ *
511
+ * The returned mount is ALREADY SETTLED and its content time is held: every
512
+ * mount runs one bounded {@link settleDesignWorld} before returning. Its
513
+ * explicit `advance(dt)` handle is inert until a document transport chooses to
514
+ * call it; mounting a story never starts content time by itself.
515
+ */
516
+ export async function mountStoryObject3D(
517
+ Component: StoryPreviewComponent,
518
+ props: Record<string, unknown> = {},
519
+ ): Promise<MountedStoryObject3D> {
520
+ return withStoryMountTurn((mount) => mount(Component, props));
521
+ }
522
+
523
+ /** Dispose a standalone story mount in the same shared turn domain. Compound
524
+ * callers already holding a turn (the board) dispose their raw mounts inside
525
+ * that turn instead. */
526
+ export function disposeStoryObject3D(mounted: MountedStoryObject3D): Promise<void> {
527
+ return withStoryMountTurn(async () => mounted.dispose());
528
+ }
529
+
530
+ /** The mount body for callers that already own the process-wide turn. */
531
+ async function mountStoryObject3DInTurn(
532
+ Component: StoryPreviewComponent,
533
+ props: Record<string, unknown> = {},
534
+ ): Promise<MountedStoryObject3D> {
535
+ const timed = viewportTimingsEnabled();
536
+ lastMountPhases = null;
537
+ const t0 = timed ? Date.now() : 0;
538
+ const runtime = await resolveStoryThreePreviewRuntime();
539
+ const runtimeMs = timed ? Date.now() - t0 : 0;
540
+ // Loaders (Storybook 9 portable-story contract): must run before the first
541
+ // render or the story's `loaded` data is empty. See the module doc and
542
+ // `StoryPreviewMount.tsx`. Hoisted ABOVE the mount so the physics retry
543
+ // below re-renders the story without re-running its loaders — `.load()` is
544
+ // the story's own side-effecting setup (a `.glb` fetch, a Rapier init), and
545
+ // running it twice for one preview is work nobody asked for.
546
+ const tLoad = timed ? Date.now() : 0;
547
+ if (typeof Component.load === 'function') await Component.load();
548
+ const loadMs = timed ? Date.now() - tLoad : 0;
549
+
550
+ try {
551
+ return await mountStoryObject3DOnce(runtime, Component, props, undefined, {
552
+ timed,
553
+ runtimeMs,
554
+ loadMs,
555
+ });
556
+ } catch (error) {
557
+ if (!isMissingPhysicsContext(error)) throw error;
558
+ const Physics = await loadPhysicsProvider(runtime);
559
+ if (!Physics) throw error;
560
+ return await mountStoryObject3DOnce(
561
+ runtime,
562
+ Component,
563
+ props,
564
+ (story) => runtime.createElement(Physics, { paused: false }, story),
565
+ { timed, runtimeMs, loadMs },
566
+ );
567
+ }
568
+ }
569
+
570
+ /**
571
+ * One mount attempt. Everything {@link mountStoryObject3D} documents happens
572
+ * here; it exists separately only so the physics retry can re-run it with a
573
+ * `wrap` around the story (and without re-running the story's loaders).
574
+ */
575
+ async function mountStoryObject3DOnce(
576
+ runtime: StoryThreePreviewRuntime,
577
+ Component: StoryPreviewComponent,
578
+ props: Record<string, unknown>,
579
+ wrap?: (story: ReactNode) => ReactNode,
580
+ phases?: { timed: boolean; runtimeMs: number; loadMs: number },
581
+ ): Promise<MountedStoryObject3D> {
582
+ ensureThreeCatalogue(runtime);
583
+
584
+ // A detached canvas — never added to the document. Fiber needs a canvas
585
+ // handle for `createRoot`; nothing ever draws to it (stub renderer +
586
+ // `frameloop: 'never'`).
587
+ const canvas = document.createElement('canvas');
588
+ // An UNWRAPPED attempt always has the physics retry behind it (see
589
+ // `mountStoryObject3D`), which is exactly what makes its physics-context
590
+ // failure containable rather than reportable.
591
+ const root = createFilteredStoryRoot(canvas, wrap === undefined, runtime.createR3FRoot);
592
+
593
+ let resolveState!: (state: RootState) => void;
594
+ const statePromise = new Promise<RootState>((resolve) => {
595
+ resolveState = resolve;
596
+ });
597
+
598
+ await root.configure({
599
+ gl: (() => createStubRenderer(canvas)) as never,
600
+ frameloop: 'never',
601
+ size: { width: 128, height: 96, top: 0, left: 0 },
602
+ onCreated: (state) => resolveState(state),
603
+ });
604
+
605
+ // A reconcile crash is reported by the boundary, not by a throw fiber lets
606
+ // through (see {@link CrashNullBoundary}) — turn it into an immediate
607
+ // rejection so a non-`three` story answers in milliseconds.
608
+ let reconcileError: Error | null = null;
609
+ let rejectOnReconcileError!: (reason: Error) => void;
610
+ const reconcileFailure = new Promise<never>((_, reject) => {
611
+ rejectOnReconcileError = reject;
612
+ });
613
+ const reportReconcileError = (error: unknown): void => {
614
+ reconcileError ??= new Error(
615
+ 'mountStoryObject3D: the story threw while reconciling into an R3F root — it does not ' +
616
+ 'render `three` content (a DOM/React story), or its component crashed. Original error: ' +
617
+ String(error instanceof Error ? error.message : error),
618
+ { cause: error },
619
+ );
620
+ rejectOnReconcileError(reconcileError);
621
+ };
622
+
623
+ // Render the story INTO a named wrapper group so its root `Object3D` is
624
+ // extractable by name (point: a stable capture subject, not the whole scene).
625
+ const story = runtime.createElement(Component, props);
626
+ // The probe reports after commit whether physics can change the authored
627
+ // pose. A mesh with no provider, and a fixed-only physics world, must not pay
628
+ // the worker quiet-wait and "never observed motion" budget.
629
+ let hasPhysicsSubscriber = false;
630
+ const notePhysicsSubscriber = (hasPhysics: boolean): void => {
631
+ hasPhysicsSubscriber = hasPhysics;
632
+ };
633
+ root.render(
634
+ runtime.createElement(
635
+ 'group',
636
+ { name: PREVIEW_ROOT_NAME },
637
+ runtime.createElement(
638
+ CrashNullBoundary,
639
+ { onCaught: reportReconcileError },
640
+ runtime.createElement(
641
+ PhysicsSubscriberProbe,
642
+ { onReport: notePhysicsSubscriber },
643
+ // The physics retry's `<Physics paused>` goes INSIDE the boundary, so a
644
+ // story that still fails under it reports through the same path.
645
+ wrap ? wrap(story) : story,
646
+ ),
647
+ ),
648
+ ),
649
+ );
650
+
651
+ // Neither committing nor throwing — a story still suspended on a slow load,
652
+ // or a hung render. The last-resort ceiling (the same guard
653
+ // `r3f-adapter.tsx` uses); see {@link ONCREATED_TIMEOUT_MS}.
654
+ const tFiber = phases?.timed ? Date.now() : 0;
655
+ let timer: ReturnType<typeof setTimeout> | undefined;
656
+ const state = await Promise.race([
657
+ statePromise,
658
+ reconcileFailure,
659
+ new Promise<never>((_, reject) => {
660
+ timer = setTimeout(
661
+ () =>
662
+ reject(
663
+ new Error(
664
+ 'mountStoryObject3D: onCreated did not fire within 10s — the story never reached ' +
665
+ 'its first R3F commit and never threw. Either it is still SUSPENDED on a slow ' +
666
+ 'load (a suspending loader with no <Suspense> boundary holds the first commit ' +
667
+ 'until its content is ready), or its render is hung.',
668
+ ),
669
+ ),
670
+ ONCREATED_TIMEOUT_MS,
671
+ );
672
+ }),
673
+ ])
674
+ .catch((reason: unknown) => {
675
+ if (phases?.timed) {
676
+ lastMountPhases = {
677
+ runtimeMs: phases.runtimeMs,
678
+ loadMs: phases.loadMs,
679
+ fiberMs: Date.now() - tFiber,
680
+ settleMs: 0,
681
+ };
682
+ }
683
+ root.unmount();
684
+ throw reason;
685
+ })
686
+ .finally(() => clearTimeout(timer));
687
+
688
+ // `componentDidCatch` and `onCreated` can both fire for the SAME commit (the
689
+ // boundary's fallback commits successfully), so a resolved race does not by
690
+ // itself mean the story rendered. The captured error is the authority.
691
+ if (reconcileError) {
692
+ root.unmount();
693
+ throw reconcileError;
694
+ }
695
+
696
+ // Fiber's clock leaks wall time into `useFrame` deltas via `getDelta()` unless
697
+ // stopped. The Asset Lab host may advance this root later, but only with its
698
+ // own deterministic document delta through the `advance` handle below.
699
+ state.clock.autoStart = false;
700
+ state.clock.stop();
701
+
702
+ const previewRoot = state.scene.getObjectByName(PREVIEW_ROOT_NAME);
703
+ if (!previewRoot) {
704
+ root.unmount();
705
+ throw new Error(
706
+ `mountStoryObject3D: the story's wrapper group ("${PREVIEW_ROOT_NAME}") is not in the ` +
707
+ 'scene after its first commit — the story rendered nothing mountable.',
708
+ );
709
+ }
710
+
711
+ // THE ONE BOUNDED SETTLE (`authoring/design-time-settle.ts`) — the same
712
+ // mechanism, constants and rest test the Scene view's design session runs, on
713
+ // the same seam (the world's own tick). Without it a physics-owned story shows
714
+ // a spawn pose the game never has, and for a raycast vehicle the wheels are
715
+ // never placed at all. After it returns, nothing advances this mount again.
716
+ const fiberMs = phases?.timed ? Date.now() - tFiber : 0;
717
+ const tSettle = phases?.timed ? Date.now() : 0;
718
+ await settleDesignWorld({
719
+ scene: previewRoot,
720
+ update: (deltaSeconds) => {
721
+ state.advance(state.clock.elapsedTime + Math.min(deltaSeconds, 0.1), false);
722
+ },
723
+ hasPhysicsSubscriber,
724
+ });
725
+ if (phases?.timed) {
726
+ lastMountPhases = {
727
+ runtimeMs: phases.runtimeMs,
728
+ loadMs: phases.loadMs,
729
+ fiberMs,
730
+ settleMs: Date.now() - tSettle,
731
+ };
732
+ }
733
+
734
+ let explicitContentTime = state.clock.elapsedTime;
735
+
736
+ return {
737
+ root: previewRoot,
738
+ scene: state.scene,
739
+ advance(deltaSeconds): void {
740
+ if (!(deltaSeconds > 0) || !Number.isFinite(deltaSeconds)) return;
741
+ explicitContentTime += Math.min(deltaSeconds, 0.1);
742
+ state.advance(explicitContentTime, false);
743
+ },
744
+ dispose(): void {
745
+ root.unmount();
746
+ },
747
+ };
748
+ }
749
+
750
+ /** Options for {@link captureStoryComponentThumbnail}, mirroring the asset
751
+ * browser's own live-instance capture dimensions. */
752
+ export interface StoryThumbnailOptions {
753
+ readonly width?: number;
754
+ readonly height?: number;
755
+ /** Arg overrides for the composed story — see {@link mountStoryObject3D}'s
756
+ * `props`. A card photographed with edited args must photograph THOSE args. */
757
+ readonly props?: Record<string, unknown>;
758
+ /** Free capture camera, forwarded to `captureObjectAssetPreview` — one view
759
+ * from the chosen angle instead of the default perspective view. */
760
+ readonly camera?: import('@volter/editor-sdk').AssetPreviewCameraChoice;
761
+ /** Clip pose, forwarded to `captureObjectAssetPreview` — sample a named
762
+ * clip at a time on the capture snapshot before framing. */
763
+ readonly pose?: import('@volter/editor-sdk').AssetPreviewPose;
764
+ }
765
+
766
+ /**
767
+ * The thumbnail entry point: mount a `three` prefab's story off-screen, capture a
768
+ * perspective thumbnail from the resulting root `THREE.Object3D` via
769
+ * `captureObjectAssetPreview`, then tear the
770
+ * off-screen root down. Returns a `data:` URL.
771
+ *
772
+ * Requires a real WebGL context (`captureObjectAssetPreview` builds a
773
+ * `WebGLRenderer`), so it throws under jsdom — the caller gates on
774
+ * {@link supportsThreeStoryCapture} exactly as the asset browser gates its
775
+ * live capture, and honestly keeps the glyph when capture is unavailable.
776
+ */
777
+ export async function captureStoryComponentThumbnail(
778
+ Component: StoryPreviewComponent,
779
+ options: StoryThumbnailOptions = {},
780
+ ): Promise<string> {
781
+ const mounted = await mountStoryObject3D(Component, options.props ?? {});
782
+ try {
783
+ const capture = captureObjectAssetPreview(mounted.root, {
784
+ width: options.width ?? 128,
785
+ height: options.height ?? 96,
786
+ ...(options.camera === undefined ? {} : { camera: options.camera }),
787
+ ...(options.pose === undefined ? {} : { pose: options.pose }),
788
+ });
789
+ const image = capture.views.find((view) => view.view === 'perspective') ?? capture.views[0];
790
+ if (!image) throw new Error('Story preview capture returned no views.');
791
+ return `data:${image.mimeType};base64,${image.base64}`;
792
+ } finally {
793
+ await disposeStoryObject3D(mounted);
794
+ }
795
+ }
796
+
797
+ /** Whether a WebGL snapshot can be taken here — the same environment gate the
798
+ * asset browser applies to its live-instance capture (jsdom has no real WebGL, so
799
+ * the off-screen render still builds the graph but a snapshot cannot be
800
+ * rasterized). */
801
+ export function supportsThreeStoryCapture(): boolean {
802
+ return (
803
+ typeof WebGLRenderingContext !== 'undefined' &&
804
+ typeof navigator !== 'undefined' &&
805
+ !navigator.userAgent.toLowerCase().includes('jsdom')
806
+ );
807
+ }