@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,107 @@
1
+ /**
2
+ * Manifest-backed project detection for the play path — T3.3 slice 2 part C.
3
+ *
4
+ * The dev server serves the project's RAW `vgai.project.json` at `/vgai.project.json`
5
+ * (editor-server.ts's project-file-serving middleware, mirroring how it
6
+ * with no view synthesis — the browser parses it itself through the PURE half
7
+ * of the manifest loader (`loadGameManifest`,
8
+ * `@volter/editor-project/manifest/load`), never `load-file.ts` (that needs `node:fs`, which
9
+ * has no place in a browser bundle).
10
+ */
11
+
12
+ import { MANIFEST_FILENAME } from '@volter/editor-project/manifest/filename';
13
+ import { loadGameManifest, type ResolvedGameManifest } from '@volter/editor-project/manifest/load';
14
+ import { resolveUrl } from '@volter/editor-threejs/loader';
15
+
16
+ /**
17
+ * "This project has no manifest" — a DIFFERENT answer from "this project's
18
+ * manifest could not be read", and the reason {@link fetchGameManifest} needs
19
+ * two rejections rather than one.
20
+ *
21
+ * A caller that cannot tell them apart has only one safe move: swallow both.
22
+ * `ingest/mount-ingest-root.ts`'s three manifest routes did exactly that
23
+ * (`.catch(() => null)` each), so a project declaring a perfectly good
24
+ * `{ ingest }` root in a manifest the strict loader REJECTED — one
25
+ * unrecognized key is enough, the schema is `.strict()` — was declined by
26
+ * every route and mounted NOTHING, with not one line anywhere saying why.
27
+ *
28
+ * Recognized by `name`, not `instanceof`: this classification must survive a
29
+ * caller holding a different module instance of this file, and a wrong answer
30
+ * there is a silent mount, not a loud one.
31
+ */
32
+ export const MANIFEST_ABSENT_ERROR_NAME = 'ManifestAbsentError';
33
+
34
+ /** True for the rejection {@link fetchGameManifest} uses to say "no manifest
35
+ * here" — see {@link MANIFEST_ABSENT_ERROR_NAME}. */
36
+ export function isManifestAbsence(error: unknown): boolean {
37
+ return error instanceof Error && error.name === MANIFEST_ABSENT_ERROR_NAME;
38
+ }
39
+
40
+ function manifestAbsent(where: string): Error {
41
+ const error = new Error(`${where} declares no ${MANIFEST_FILENAME}`);
42
+ error.name = MANIFEST_ABSENT_ERROR_NAME;
43
+ return error;
44
+ }
45
+
46
+ /**
47
+ * Fetch + parse the current project's sole v2 config from the session that
48
+ * serves it. Both failures are loud; ABSENCE is the one that is also
49
+ * classifiable ({@link isManifestAbsence}).
50
+ */
51
+ export async function fetchGameManifest(): Promise<ResolvedGameManifest> {
52
+ const url = resolveUrl('/vgai.project.json');
53
+ const res = await fetch(url);
54
+ if (res.status === 404) throw manifestAbsent(url);
55
+ if (!res.ok) throw new Error(`Failed to load ${url}: ${res.status} ${res.statusText}`);
56
+ // A 404 is not the only way this route says "no manifest": a dev server with
57
+ // an SPA fallback answers an unknown path with index.html and a 200, which
58
+ // `res.json()` reports as `Unexpected token '<'` — an unreadable manifest,
59
+ // when the truth is that there is none. `fetchRawGameManifest` below has
60
+ // made the same distinction the same way since it was written.
61
+ const body = await res.text();
62
+ if (/^\s*</.test(body)) throw manifestAbsent(url);
63
+ try {
64
+ return loadGameManifest(JSON.parse(body), { configurationKinds: 'defer' });
65
+ } catch (error) {
66
+ throw new Error(
67
+ `Failed to load ${url}: ${error instanceof Error ? error.message : String(error)}`,
68
+ );
69
+ }
70
+ }
71
+
72
+ /**
73
+ * A1 (edit mode, defect 2): fetch the RAW, UN-validated `vgai.project.json` JSON
74
+ * — or `null` when the project genuinely has none (the route 404s). Unlike
75
+ * {@link fetchGameManifest}, this NEVER throws on a schema violation: edit mode
76
+ * must tolerate a manifest with ONE bad world (e.g. an unknown `kind`) by
77
+ * surfacing that world as an error node (#18) while the VALID roots declared
78
+ * alongside it still open — strict `loadGameManifest` would reject the whole
79
+ * file and make every declared world vanish. The caller (`edit-mode-authoring
80
+ * .ts`) reads `roots[]` leniently; a genuinely absent manifest (this returns
81
+ * `null`) is the only case that synthesizes a single-world game.
82
+ */
83
+ export async function fetchRawGameManifest(): Promise<unknown | null> {
84
+ const url = resolveUrl('/vgai.project.json');
85
+ const res = await fetch(url);
86
+ if (!res.ok) return null;
87
+ // A 404 is not the only way this route says "no manifest". A dev server with
88
+ // an SPA fallback answers an unknown path with index.html and a **200**, so
89
+ // `res.ok` passes and `res.json()` throws `Unexpected token '<', "<!DOCTYPE
90
+ // "... is not valid JSON` — from a function whose whole contract is to return
91
+ // `null` when there is no manifest. That throw escapes into callers that
92
+ // only ever guarded against `null`: it aborted `tryManifestIngestRoute2D`
93
+ // before it could look at the roots at all, so a perfectly well-formed
94
+ // `{ ingest }` world silently fell through to the default-pixi path and
95
+ // failed to mount, reporting the JSON error as the mount's cause with no
96
+ // hint of which fetch produced it.
97
+ const body = await res.text();
98
+ if (/^\s*</.test(body)) return null; // HTML fallback === no manifest here
99
+ try {
100
+ return JSON.parse(body) as unknown;
101
+ } catch (error) {
102
+ // A genuinely malformed manifest is NOT absence — say so, and say where.
103
+ throw new Error(
104
+ `${url} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,
105
+ );
106
+ }
107
+ }
@@ -0,0 +1,523 @@
1
+ /**
2
+ * Offscreen Three.js renderer that generates thumbnail images for supported 3D source formats.
3
+ *
4
+ * Usage:
5
+ * const renderer = getModelThumbnailRenderer();
6
+ * const dataUrl = await renderer.render('/assets/models/Robot.glb');
7
+ * // dataUrl is a PNG data-URL string suitable for <img src="...">
8
+ *
9
+ * - Singleton: one renderer is shared across all callers.
10
+ * - Deduplicating: concurrent requests for the same URL share one render pass.
11
+ * - Cached: once rendered, the data URL is returned immediately.
12
+ * - Self-cleaning: each loaded object is disposed after the thumbnail is captured.
13
+ */
14
+
15
+ import type { SparkRenderer, SplatMesh } from '@sparkjsdev/spark';
16
+ import { markHostRenderer } from '@volter/editor-threejs/viewport/renderer-ownership';
17
+ import { loadSplat } from '@volter/editor-threejs/asset-loaders';
18
+ import { hasUserData } from '@volter/editor-threejs/ecs/user-data';
19
+ import { gltfLoader } from '@volter/editor-threejs/loader';
20
+ import { disposeSparkRendererWhenIdle } from '@volter/editor-threejs/render/spark-renderer-lifecycle';
21
+ import * as THREE from 'three';
22
+ import {
23
+ applyStudioEnvironment,
24
+ STUDIO_AMBIENT_WITH_ENVIRONMENT,
25
+ } from './three-viewport/studio-environment';
26
+
27
+ const SIZE = 128;
28
+
29
+ let instance: ModelThumbnailRenderer | null = null;
30
+
31
+ export type ModelThumbnailFormat =
32
+ | 'glb'
33
+ | 'gltf'
34
+ | 'fbx'
35
+ | 'obj'
36
+ | 'dae'
37
+ | 'stl'
38
+ | 'ply'
39
+ | 'spz'
40
+ | 'bvh'
41
+ | '3ds';
42
+
43
+ export interface ModelThumbnailSpec {
44
+ url: string;
45
+ format?: ModelThumbnailFormat;
46
+ materialUrl?: string;
47
+ background?: 'transparent' | 'neutral';
48
+ output?: 'png' | 'webp';
49
+ }
50
+
51
+ const MODEL_THUMBNAIL_FORMATS = new Set<ModelThumbnailFormat>([
52
+ 'glb',
53
+ 'gltf',
54
+ 'fbx',
55
+ 'obj',
56
+ 'dae',
57
+ 'stl',
58
+ 'ply',
59
+ 'spz',
60
+ 'bvh',
61
+ '3ds',
62
+ ]);
63
+
64
+ export function modelThumbnailFormat(url: string): ModelThumbnailFormat | undefined {
65
+ const pathname = url.split(/[?#]/, 1)[0]!.toLowerCase();
66
+ const extension = pathname.slice(pathname.lastIndexOf('.') + 1) as ModelThumbnailFormat;
67
+ return MODEL_THUMBNAIL_FORMATS.has(extension) ? extension : undefined;
68
+ }
69
+
70
+ export class ModelThumbnailRenderer {
71
+ private renderer: THREE.WebGLRenderer;
72
+ private scene: THREE.Scene;
73
+ private camera: THREE.PerspectiveCamera;
74
+ private cache = new Map<string, string>();
75
+ private pending = new Map<string, Promise<string>>();
76
+ private sparkRenderer: SparkRenderer | null = null;
77
+ constructor() {
78
+ const canvas = document.createElement('canvas');
79
+ canvas.width = SIZE;
80
+ canvas.height = SIZE;
81
+
82
+ this.renderer = markHostRenderer(
83
+ new THREE.WebGLRenderer({
84
+ canvas,
85
+ alpha: true,
86
+ antialias: true,
87
+ preserveDrawingBuffer: true,
88
+ }),
89
+ );
90
+ this.renderer.setSize(SIZE, SIZE);
91
+ this.renderer.setPixelRatio(1);
92
+ this.renderer.outputColorSpace = THREE.SRGBColorSpace;
93
+ this.renderer.toneMapping = THREE.ACESFilmicToneMapping;
94
+ this.renderer.toneMappingExposure = 1.0;
95
+
96
+ this.scene = new THREE.Scene();
97
+
98
+ // The neutral studio IBL, so a PBR metal has something to reflect — a
99
+ // `metalness: 1` material has no diffuse term and thumbnails it black
100
+ // otherwise. `scene.background` is untouched: the thumbnail canvas stays
101
+ // alpha, as every caller composites it. The bake lives as long as this
102
+ // renderer does (there is no teardown path for either — one instance,
103
+ // held for the editor's lifetime by `getModelThumbnailRenderer`).
104
+ applyStudioEnvironment(this.scene, this.renderer);
105
+
106
+ // Soft ambient fill, scaled back now that the environment carries the
107
+ // ambient term properly.
108
+ this.scene.add(new THREE.AmbientLight(0xffffff, 0.8 * STUDIO_AMBIENT_WITH_ENVIRONMENT));
109
+
110
+ // Key light (upper-right-front)
111
+ const key = new THREE.DirectionalLight(0xffffff, 1.5);
112
+ key.position.set(3, 5, 4);
113
+ this.scene.add(key);
114
+
115
+ // Fill light (lower-left, dimmer, warm)
116
+ const fill = new THREE.DirectionalLight(0xffeedd, 0.4);
117
+ fill.position.set(-3, -1, 2);
118
+ this.scene.add(fill);
119
+
120
+ this.camera = new THREE.PerspectiveCamera(35, 1, 0.01, 1000);
121
+ }
122
+
123
+ /** Render a thumbnail for the given model URL. Returns a PNG data-URL. */
124
+ async render(input: string | ModelThumbnailSpec): Promise<string> {
125
+ const spec: ModelThumbnailSpec = typeof input === 'string' ? { url: input } : input;
126
+ const format = spec.format ?? modelThumbnailFormat(spec.url);
127
+ if (!format) throw new Error(`No thumbnail loader for ${spec.url}`);
128
+ const resolved: ModelThumbnailSpec & { format: ModelThumbnailFormat } = { ...spec, format };
129
+ const key = JSON.stringify(resolved);
130
+ const cached = this.cache.get(key);
131
+ if (cached) return cached;
132
+
133
+ // Deduplicate concurrent requests for the same URL
134
+ const inflight = this.pending.get(key);
135
+ if (inflight) return inflight;
136
+
137
+ const promise = this._render(resolved);
138
+ this.pending.set(key, promise);
139
+ try {
140
+ const dataUrl = await promise;
141
+ this.cache.set(key, dataUrl);
142
+ return dataUrl;
143
+ } finally {
144
+ this.pending.delete(key);
145
+ }
146
+ }
147
+
148
+ private async _render(
149
+ spec: ModelThumbnailSpec & { format: ModelThumbnailFormat },
150
+ ): Promise<string> {
151
+ const group = createModelPreviewSource(await loadModelThumbnailObject(spec));
152
+
153
+ this.scene.add(group);
154
+ if (spec.format === 'spz' && !this.sparkRenderer) {
155
+ const { SparkRenderer } = await import('@sparkjsdev/spark');
156
+ this.sparkRenderer = new SparkRenderer({ renderer: this.renderer, enableLod: false });
157
+ this.scene.add(this.sparkRenderer);
158
+ }
159
+
160
+ // Auto-frame the camera around the model
161
+ const box = modelPreviewBounds(group);
162
+ if (box.isEmpty()) {
163
+ this.scene.remove(group);
164
+ throw new Error('Model contains no previewable geometry.');
165
+ }
166
+ const center = box.getCenter(new THREE.Vector3());
167
+ const sphere = box.getBoundingSphere(new THREE.Sphere());
168
+ const fovRad = (this.camera.fov * Math.PI) / 180;
169
+ const dist = (Math.max(sphere.radius, 0.0001) / Math.sin(fovRad / 2)) * 1.18;
170
+ const direction = new THREE.Vector3(0.65, 0.45, 0.65).normalize();
171
+
172
+ // Position camera at a 3/4 angle (slightly above, rotated 30° from front)
173
+ this.camera.position.set(
174
+ center.x + dist * direction.x,
175
+ center.y + dist * direction.y,
176
+ center.z + dist * direction.z,
177
+ );
178
+ this.camera.lookAt(center);
179
+ this.camera.near = dist * 0.01;
180
+ this.camera.far = dist * 20;
181
+ this.camera.updateProjectionMatrix();
182
+
183
+ // Render
184
+ if (spec.background === 'neutral') this.renderer.setClearColor(0x20242a, 1);
185
+ else this.renderer.setClearColor(0x000000, 0);
186
+ this.renderer.clear();
187
+ this.renderer.render(this.scene, this.camera);
188
+ const outputCanvas = strengthenThinThumbnailSilhouette(
189
+ this.renderer.domElement,
190
+ spec.background === 'neutral',
191
+ );
192
+ const dataUrl =
193
+ spec.output === 'webp'
194
+ ? outputCanvas.toDataURL('image/webp', 0.86)
195
+ : outputCanvas.toDataURL('image/png');
196
+
197
+ // Clean up the loaded GLTF to free GPU memory
198
+ this.scene.remove(group);
199
+ // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Disposal enumerates the texture slots used by the supported Three.js materials.
200
+ group.traverse((obj) => {
201
+ if (hasUserData(obj, 'gaussianSplat')) (obj as SplatMesh).dispose();
202
+ const renderable = obj as THREE.Mesh | THREE.Line | THREE.Points;
203
+ if (renderable.geometry) renderable.geometry.dispose();
204
+ if (renderable.material) {
205
+ const materials = Array.isArray(renderable.material)
206
+ ? renderable.material
207
+ : [renderable.material];
208
+ for (const mat of materials) {
209
+ // Dispose any textures attached to the material
210
+ if ('map' in mat && mat.map instanceof THREE.Texture) mat.map.dispose();
211
+ if ('normalMap' in mat && mat.normalMap instanceof THREE.Texture) mat.normalMap.dispose();
212
+ if ('emissiveMap' in mat && mat.emissiveMap instanceof THREE.Texture)
213
+ mat.emissiveMap.dispose();
214
+ if ('aoMap' in mat && mat.aoMap instanceof THREE.Texture) mat.aoMap.dispose();
215
+ if ('roughnessMap' in mat && mat.roughnessMap instanceof THREE.Texture)
216
+ mat.roughnessMap.dispose();
217
+ if ('metalnessMap' in mat && mat.metalnessMap instanceof THREE.Texture)
218
+ mat.metalnessMap.dispose();
219
+ mat.dispose();
220
+ }
221
+ }
222
+ });
223
+
224
+ return dataUrl;
225
+ }
226
+
227
+ dispose() {
228
+ if (this.sparkRenderer) {
229
+ this.scene.remove(this.sparkRenderer);
230
+ disposeSparkRendererWhenIdle(this.sparkRenderer);
231
+ this.sparkRenderer = null;
232
+ }
233
+ this.renderer.dispose();
234
+ this.renderer.forceContextLoss();
235
+ this.cache.clear();
236
+ instance = null;
237
+ }
238
+ }
239
+
240
+ function strengthenThinThumbnailSilhouette(
241
+ source: HTMLCanvasElement,
242
+ neutralBackground: boolean,
243
+ ): HTMLCanvasElement {
244
+ const canvas = document.createElement('canvas');
245
+ canvas.width = source.width;
246
+ canvas.height = source.height;
247
+ const context = canvas.getContext('2d', { willReadFrequently: true });
248
+ if (!context) return source;
249
+ context.drawImage(source, 0, 0);
250
+ const image = context.getImageData(0, 0, canvas.width, canvas.height);
251
+ const visible = new Uint8Array(canvas.width * canvas.height);
252
+ let visibleCount = 0;
253
+ for (let pixel = 0; pixel < visible.length; pixel++) {
254
+ const offset = pixel * 4;
255
+ const isVisible = neutralBackground
256
+ ? Math.abs(image.data[offset]! - 32) > 3 ||
257
+ Math.abs(image.data[offset + 1]! - 36) > 3 ||
258
+ Math.abs(image.data[offset + 2]! - 42) > 3
259
+ : image.data[offset + 3]! > 8;
260
+ if (isVisible) {
261
+ visible[pixel] = 1;
262
+ visibleCount++;
263
+ }
264
+ }
265
+ const ratio = visibleCount / visible.length;
266
+ if (ratio < 0.0001) return canvas;
267
+ const original = new Uint8ClampedArray(image.data);
268
+ if (neutralBackground) {
269
+ for (let pixel = 0; pixel < visible.length; pixel++) {
270
+ if (!visible[pixel]) continue;
271
+ const offset = pixel * 4;
272
+ const luminance =
273
+ original[offset]! * 0.2126 +
274
+ original[offset + 1]! * 0.7152 +
275
+ original[offset + 2]! * 0.0722;
276
+ if (luminance >= 88) continue;
277
+ const lift = 88 - luminance;
278
+ image.data[offset] = Math.min(255, original[offset]! + lift);
279
+ image.data[offset + 1] = Math.min(255, original[offset + 1]! + lift);
280
+ image.data[offset + 2] = Math.min(255, original[offset + 2]! + lift);
281
+ }
282
+ }
283
+ const dilationPasses = ratio < 0.004 ? 2 : ratio < 0.02 ? 1 : 0;
284
+ let currentVisible = visible;
285
+ for (let pass = 0; pass < dilationPasses; pass++) {
286
+ const nextVisible = currentVisible.slice();
287
+ const passPixels = new Uint8ClampedArray(image.data);
288
+ for (let y = 1; y < canvas.height - 1; y++) {
289
+ for (let x = 1; x < canvas.width - 1; x++) {
290
+ const pixel = y * canvas.width + x;
291
+ if (currentVisible[pixel]) continue;
292
+ const neighbor = [pixel - 1, pixel + 1, pixel - canvas.width, pixel + canvas.width].find(
293
+ (candidate) => currentVisible[candidate],
294
+ );
295
+ if (neighbor === undefined) continue;
296
+ const targetOffset = pixel * 4;
297
+ const sourceOffset = neighbor * 4;
298
+ image.data.set(passPixels.subarray(sourceOffset, sourceOffset + 4), targetOffset);
299
+ nextVisible[pixel] = 1;
300
+ }
301
+ }
302
+ currentVisible = nextVisible;
303
+ }
304
+ context.putImageData(image, 0, 0);
305
+ return canvas;
306
+ }
307
+
308
+ /** Bind-pose geometry bounds avoid malformed/outlier skin transforms shrinking a preview to a dot. */
309
+ export function modelPreviewBounds(model: THREE.Object3D): THREE.Box3 {
310
+ model.updateWorldMatrix(true, true);
311
+ const bounds = new THREE.Box3();
312
+ const candidate = new THREE.Box3();
313
+ model.traverseVisible((object) => {
314
+ if (hasUserData(object, 'gaussianSplat')) {
315
+ candidate.copy((object as SplatMesh).getBoundingBox()).applyMatrix4(object.matrixWorld);
316
+ bounds.union(candidate);
317
+ return;
318
+ }
319
+ const renderable = object as THREE.Mesh | THREE.Line | THREE.Points;
320
+ if (!renderable.geometry) return;
321
+ if (!renderable.geometry.boundingBox) renderable.geometry.computeBoundingBox();
322
+ if (!renderable.geometry.boundingBox) return;
323
+ candidate.copy(renderable.geometry.boundingBox).applyMatrix4(object.matrixWorld);
324
+ bounds.union(candidate);
325
+ });
326
+ return bounds;
327
+ }
328
+
329
+ // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: This format dispatcher keeps each Three.js loader path visible in one place.
330
+ export async function loadModelThumbnailObject(
331
+ spec: ModelThumbnailSpec & { format: ModelThumbnailFormat },
332
+ ): Promise<THREE.Object3D> {
333
+ switch (spec.format) {
334
+ case 'glb':
335
+ case 'gltf': {
336
+ const gltf = await gltfLoader.loadAsync(spec.url);
337
+ gltf.scene.animations = [...gltf.animations];
338
+ return gltf.scene;
339
+ }
340
+ case 'fbx': {
341
+ const { FBXLoader } = await import('three/addons/loaders/FBXLoader.js');
342
+ return new FBXLoader().loadAsync(spec.url);
343
+ }
344
+ case 'obj': {
345
+ const { OBJLoader } = await import('three/addons/loaders/OBJLoader.js');
346
+ const materialUrl = spec.materialUrl ?? (await discoverObjMaterial(spec.url));
347
+ const loader = new OBJLoader();
348
+ if (materialUrl) {
349
+ const { MTLLoader } = await import('three/addons/loaders/MTLLoader.js');
350
+ const materials = await new MTLLoader().loadAsync(materialUrl);
351
+ materials.preload();
352
+ loader.setMaterials(materials);
353
+ }
354
+ return loader.loadAsync(spec.url);
355
+ }
356
+ case 'dae': {
357
+ const { ColladaLoader } = await import('three/addons/loaders/ColladaLoader.js');
358
+ const collada = await new ColladaLoader().loadAsync(spec.url);
359
+ const animations = (collada as unknown as { animations?: THREE.AnimationClip[] }).animations;
360
+ if (animations) collada.scene.animations = [...animations];
361
+ return collada.scene;
362
+ }
363
+ case 'stl': {
364
+ const { STLLoader } = await import('three/addons/loaders/STLLoader.js');
365
+ const geometry = await new STLLoader().loadAsync(spec.url);
366
+ return new THREE.Mesh(
367
+ geometry,
368
+ new THREE.MeshStandardMaterial({ color: 0x75a7d8, roughness: 0.75 }),
369
+ );
370
+ }
371
+ case 'ply': {
372
+ const { PLYLoader } = await import('three/addons/loaders/PLYLoader.js');
373
+ const geometry = await new PLYLoader().loadAsync(spec.url);
374
+ if (geometry.index || geometry.hasAttribute('normal')) {
375
+ if (!geometry.hasAttribute('normal')) geometry.computeVertexNormals();
376
+ return new THREE.Mesh(
377
+ geometry,
378
+ new THREE.MeshStandardMaterial({
379
+ color: geometry.hasAttribute('color') ? 0xffffff : 0xb8d09b,
380
+ vertexColors: geometry.hasAttribute('color'),
381
+ roughness: 0.8,
382
+ side: THREE.DoubleSide,
383
+ }),
384
+ );
385
+ }
386
+ geometry.computeBoundingBox();
387
+ const diagonal = geometry.boundingBox?.getSize(new THREE.Vector3()).length() ?? 1;
388
+ return new THREE.Points(
389
+ geometry,
390
+ new THREE.PointsMaterial({
391
+ color: geometry.hasAttribute('color') ? 0xffffff : 0xb8d09b,
392
+ vertexColors: geometry.hasAttribute('color'),
393
+ size: Math.max(diagonal / 220, 0.0001),
394
+ sizeAttenuation: true,
395
+ }),
396
+ );
397
+ }
398
+ case 'spz':
399
+ return loadSplat(spec.url);
400
+ case 'bvh': {
401
+ const { BVHLoader } = await import('three/addons/loaders/BVHLoader.js');
402
+ const result = await new BVHLoader().loadAsync(spec.url);
403
+ const root = result.skeleton.bones[0];
404
+ if (!root) throw new Error('BVH contains no skeleton root.');
405
+ const group = new THREE.Group();
406
+ group.animations = [result.clip];
407
+ group.add(root);
408
+ return group;
409
+ }
410
+ case '3ds': {
411
+ const { TDSLoader } = await import('three/addons/loaders/TDSLoader.js');
412
+ return new TDSLoader().loadAsync(spec.url);
413
+ }
414
+ }
415
+ }
416
+
417
+ /**
418
+ * Give animation-only model files an honest renderable representation.
419
+ * FBX/glTF animation exports commonly contain a complete bone hierarchy and
420
+ * clips but no mesh. They are valid assets, so Asset Editor visualizes their
421
+ * current skeleton instead of misreporting them as an empty source.
422
+ */
423
+ export function createModelPreviewSource(model: THREE.Object3D): THREE.Object3D {
424
+ if (hasRenderableModelContent(model)) return model;
425
+
426
+ model.updateWorldMatrix(true, true);
427
+ const helper = new THREE.SkeletonHelper(model);
428
+ const positions = helper.geometry.getAttribute('position');
429
+ if (!positions || positions.count === 0) {
430
+ helper.dispose();
431
+ return model;
432
+ }
433
+
434
+ helper.name = 'Asset Editor skeleton preview';
435
+ helper.frustumCulled = false;
436
+ const previewRoot = new THREE.Group();
437
+ previewRoot.name = model.name;
438
+ previewRoot.animations = [...model.animations];
439
+ previewRoot.add(model, helper);
440
+ previewRoot.updateWorldMatrix(true, true);
441
+ // SkeletonHelper updates its dynamic line vertices from updateMatrixWorld,
442
+ // while the read-only preview measurer intentionally calls updateWorldMatrix.
443
+ // Materialize the current pose once so framing sees the real skeleton span.
444
+ helper.updateMatrixWorld(true);
445
+ return previewRoot;
446
+ }
447
+
448
+ function hasRenderableModelContent(model: THREE.Object3D): boolean {
449
+ let renderable = false;
450
+ model.traverseVisible((object) => {
451
+ if (!renderable && objectHasRenderableModelContent(object)) renderable = true;
452
+ });
453
+ return renderable;
454
+ }
455
+
456
+ function objectHasRenderableModelContent(object: THREE.Object3D): boolean {
457
+ const sprite = object as THREE.Sprite;
458
+ if (sprite.isSprite) return sprite.material.visible;
459
+ const mesh = object as THREE.Mesh;
460
+ const line = object as THREE.Line;
461
+ const points = object as THREE.Points;
462
+ if (!mesh.isMesh && !line.isLine && !points.isPoints) return false;
463
+ const drawable = object as THREE.Mesh;
464
+ const instanced = object as THREE.InstancedMesh;
465
+ if (instanced.isInstancedMesh && instanced.count === 0) return false;
466
+ const materials = Array.isArray(drawable.material) ? drawable.material : [drawable.material];
467
+ if (materials.length === 0 || materials.every((material) => !material?.visible)) return false;
468
+ const positions = drawable.geometry?.getAttribute('position');
469
+ return Boolean(positions && positions.count > 0 && drawable.geometry.drawRange.count !== 0);
470
+ }
471
+
472
+ async function discoverObjMaterial(url: string): Promise<string | undefined> {
473
+ const pathname = url.split(/[?#]/, 1)[0]!;
474
+ const materialUrl = `${pathname.slice(0, pathname.lastIndexOf('.'))}.mtl`;
475
+ try {
476
+ const response = await fetch(materialUrl, { method: 'HEAD' });
477
+ return response.ok ? materialUrl : undefined;
478
+ } catch {
479
+ return undefined;
480
+ }
481
+ }
482
+
483
+ /** Normalize an upstream/source preview image through the same thumbnail canvas. */
484
+ export async function renderImageThumbnail(
485
+ url: string,
486
+ options: { output?: 'png' | 'webp'; background?: 'transparent' | 'neutral' } = {},
487
+ ): Promise<string> {
488
+ const image = await new Promise<HTMLImageElement>((resolve, reject) => {
489
+ const element = new Image();
490
+ element.onload = () => resolve(element);
491
+ element.onerror = () => reject(new Error(`Failed to load preview image: ${url}`));
492
+ element.src = url;
493
+ });
494
+ const canvas = document.createElement('canvas');
495
+ canvas.width = SIZE;
496
+ canvas.height = SIZE;
497
+ const context = canvas.getContext('2d');
498
+ if (!context) throw new Error('2D thumbnail canvas is unavailable.');
499
+ if (options.background === 'neutral') {
500
+ context.fillStyle = '#20242a';
501
+ context.fillRect(0, 0, SIZE, SIZE);
502
+ }
503
+ const scale = Math.min(SIZE / image.naturalWidth, SIZE / image.naturalHeight);
504
+ const width = image.naturalWidth * scale;
505
+ const height = image.naturalHeight * scale;
506
+ context.drawImage(image, (SIZE - width) / 2, (SIZE - height) / 2, width, height);
507
+ return options.output === 'webp'
508
+ ? canvas.toDataURL('image/webp', 0.86)
509
+ : canvas.toDataURL('image/png');
510
+ }
511
+
512
+ /** Get (or create) the shared thumbnail renderer. */
513
+ export function getModelThumbnailRenderer(): ModelThumbnailRenderer {
514
+ if (!instance) instance = new ModelThumbnailRenderer();
515
+ return instance;
516
+ }
517
+
518
+ // A Fast Refresh invalidation creates a fresh module singleton. Release the
519
+ // previous module's offscreen WebGL context first instead of silently leaking
520
+ // one context per AssetBrowser edit.
521
+ if (import.meta.hot) {
522
+ import.meta.hot.dispose(() => instance?.dispose());
523
+ }