@vgai/editor 0.2.0 → 0.4.0-canary.20260715.0

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 (430) hide show
  1. package/README.md +24 -0
  2. package/dist/assets/axe-C7YBtOuM.js +30 -0
  3. package/dist/assets/browserAll-DDtGGE48.js +1 -0
  4. package/dist/assets/create-runtime-DbTlBlKG.js +1 -0
  5. package/dist/assets/{game-BI111o5f.js → game-BE0r7DMI.js} +3 -3
  6. package/dist/assets/game-BdTHR2_X.js +1 -0
  7. package/dist/assets/{game-pt_1lQLv.js → game-C3iQGSqo.js} +1 -1
  8. package/dist/assets/game-CcHfYxjP.js +1 -0
  9. package/dist/assets/game-Cqzj_lpD.js +1 -0
  10. package/dist/assets/game-DSZdeCHu.js +1 -0
  11. package/dist/assets/game-Dch9sAPc.js +1 -0
  12. package/dist/assets/{game-xsOho_wg.js → game-SAFn90pA.js} +3 -3
  13. package/dist/assets/game-loop-DMIgSMV-.js +1 -0
  14. package/dist/assets/{game-source-CvidN7AB.js → game-source-Dk83w26t.js} +1 -1
  15. package/dist/assets/{index-Bp5cecD6.js → index-4idIsHHJ.js} +2 -2
  16. package/dist/assets/{index-DL7RcQ0n.js → index-C-LhOTBN.js} +1 -1
  17. package/dist/assets/{index-Cs8xwv3e.js → index-CDkxceqE.js} +1 -1
  18. package/dist/assets/index-CQEfi217.js +15412 -0
  19. package/dist/assets/index-Dvi-V0NE.css +1 -0
  20. package/dist/assets/ingest-siblings-s0P5I1Ub.js +1 -0
  21. package/dist/assets/mount-manifest-DXMOlF79.js +1 -0
  22. package/dist/assets/react-18-C9eHn8x1.js +1 -0
  23. package/dist/assets/webworkerAll-Fn0e-ouF.js +1 -0
  24. package/dist/assets/world-documents-GM0a9Kv7.js +1 -0
  25. package/dist/index.html +15 -13
  26. package/dist/ingest/racing-game/vgai.game.json +5 -4
  27. package/dist/ingest/simcity/vgai.game.json +5 -4
  28. package/dist/ingest/tanks/vgai.game.json +5 -4
  29. package/dist/project-scripts.bundle.js +3917 -0
  30. package/dist/scenes/untitled.vscn.json +2 -0
  31. package/dist-server/packaged.mjs +7697 -3435
  32. package/package.json +31 -4
  33. package/scripts/probe-reporter.ts +349 -0
  34. package/scripts/run-probe.ts +2260 -0
  35. package/server/asset-catalog-audit.ts +268 -0
  36. package/server/asset-catalog-v2.ts +146 -0
  37. package/server/asset-library-routes.ts +276 -58
  38. package/server/canonical-path.ts +1 -1
  39. package/server/dev.ts +148 -44
  40. package/server/editor-server.ts +888 -61
  41. package/server/editor-sse.ts +115 -4
  42. package/server/harness-chat-service.ts +796 -0
  43. package/server/local-asset-catalog.ts +157 -22
  44. package/server/packaged.ts +51 -21
  45. package/server/process-shutdown.ts +136 -0
  46. package/server/prod.ts +9 -6
  47. package/server/project-hmr-files.ts +41 -0
  48. package/server/project-operations.ts +343 -0
  49. package/server/project-optimize-deps-entries.ts +4 -4
  50. package/server/project-output-writer.ts +156 -0
  51. package/server/project-validation.ts +140 -0
  52. package/server/project-view.ts +8 -50
  53. package/server/server-utils.ts +75 -10
  54. package/server/session-registry.ts +12 -0
  55. package/src/EditorContext.tsx +107 -3
  56. package/src/action-registry.ts +115 -17
  57. package/src/adapter-resolver.ts +213 -478
  58. package/src/asset-events.ts +64 -1
  59. package/src/asset-preview.ts +906 -0
  60. package/src/asset-selection.ts +121 -0
  61. package/src/asset-workflow/asset-capabilities.ts +259 -0
  62. package/src/asset-workflow/asset-editor-errors.ts +46 -0
  63. package/src/asset-workflow/asset-import-pipeline.ts +176 -0
  64. package/src/asset-workflow/asset-library-query.ts +107 -0
  65. package/src/asset-workflow/asset-types.ts +192 -0
  66. package/src/asset-workflow/asset-workflow-quality.ts +65 -0
  67. package/src/asset-workflow/import-contract.ts +113 -0
  68. package/src/asset-workflow/model-conversion.ts +126 -0
  69. package/src/asset-workflow/model-inspection.ts +185 -0
  70. package/src/asset-workflow/project-asset-commands.ts +23 -0
  71. package/src/asset-workflow/project-asset-health.ts +153 -0
  72. package/src/asset-workflow/project-asset-operations.ts +119 -0
  73. package/src/asset-workflow/thumbnail-system.ts +172 -0
  74. package/src/authoring/active-systems.ts +15 -0
  75. package/src/authoring/boundary-authoring-adapter.ts +16 -5
  76. package/src/authoring/composite-authoring-adapter.ts +305 -124
  77. package/src/authoring/design-time-layers.ts +300 -117
  78. package/src/authoring/edit-mode-authoring.ts +278 -293
  79. package/src/authoring/embed-only-adapter.ts +4 -2
  80. package/src/authoring/first-party-hierarchy-menu.ts +7 -2
  81. package/src/authoring/first-party-inspector/AddSectionMenu.tsx +5 -12
  82. package/src/authoring/first-party-inspector/EnvironmentSection.tsx +53 -126
  83. package/src/authoring/first-party-inspector/HeaderSection.tsx +54 -65
  84. package/src/authoring/first-party-inspector/is-first-party-selection.ts +30 -11
  85. package/src/authoring/first-party-inspector/register.tsx +13 -2
  86. package/src/authoring/ingest-authoring-adapter.ts +142 -45
  87. package/src/authoring/mount-failure-report.ts +5 -5
  88. package/src/authoring/provenance.ts +41 -0
  89. package/src/authoring/react-dom-authoring-adapter.ts +92 -92
  90. package/src/authoring/react-inspector-section.tsx +489 -51
  91. package/src/authoring/react-world-authoring-adapter.ts +541 -306
  92. package/src/authoring/story-overlay.ts +1 -1
  93. package/src/authoring/story-types.ts +11 -0
  94. package/src/authoring/vgai-scene-authoring-adapter.ts +54 -25
  95. package/src/authoring/viewport-tool-context.ts +78 -0
  96. package/src/authoring/world-canvas-viewport-state.ts +35 -0
  97. package/src/authoring/world-hidden-viewport.ts +186 -0
  98. package/src/authoring/world-pan-state.ts +33 -17
  99. package/src/authoring/world-session-state.ts +1 -1
  100. package/src/authoring/world2d-authoring-adapter.ts +109 -52
  101. package/src/availability-tick.ts +56 -0
  102. package/src/browser-play-entry.ts +5 -5
  103. package/src/browser-project-scripts.ts +1 -0
  104. package/src/build-identity.ts +15 -0
  105. package/src/command-listener.ts +893 -19
  106. package/src/components/AlignToolbar.tsx +74 -63
  107. package/src/components/AppRoot.tsx +193 -26
  108. package/src/components/ApplicationMenus.tsx +272 -0
  109. package/src/components/AssetBrowser.tsx +1450 -293
  110. package/src/components/AssetEditorShell.tsx +141 -0
  111. package/src/components/AssetImportDetails.tsx +218 -0
  112. package/src/components/AssetLibraryPanel.tsx +14 -0
  113. package/src/components/CameraInfo.tsx +11 -20
  114. package/src/components/CenterDocuments.tsx +585 -0
  115. package/src/components/CommandPalette.tsx +75 -30
  116. package/src/components/ConsolePanel.css +55 -0
  117. package/src/components/ConsolePanel.tsx +57 -112
  118. package/src/components/DebugConsolePanel.tsx +461 -0
  119. package/src/components/DebuggerPanel.tsx +165 -0
  120. package/src/components/DefaultEditorLayout.tsx +167 -231
  121. package/src/components/GameHierarchy.tsx +873 -290
  122. package/src/components/GamePerformanceOverlay.tsx +46 -0
  123. package/src/components/HarnessChatPanel.tsx +2352 -0
  124. package/src/components/HistoryPanel.tsx +226 -0
  125. package/src/components/Inspector.tsx +153 -99
  126. package/src/components/InspectorToolSection.tsx +6 -4
  127. package/src/components/MountFailureBanner.tsx +31 -48
  128. package/src/components/NetworkSection.tsx +25 -14
  129. package/src/components/OnlineAssetBrowser.tsx +794 -343
  130. package/src/components/OverlayReportBanner.tsx +38 -64
  131. package/src/components/PerformancePanel.tsx +206 -0
  132. package/src/components/PlayBar.tsx +93 -263
  133. package/src/components/PrefabSaveDialog.tsx +55 -127
  134. package/src/components/ProjectHeader.css +60 -0
  135. package/src/components/ProjectHeader.tsx +170 -105
  136. package/src/components/ProjectScreen.tsx +246 -184
  137. package/src/components/ReactCanvasControls.tsx +185 -0
  138. package/src/components/ResolutionPicker.tsx +85 -0
  139. package/src/components/RootActivateDialog.tsx +39 -68
  140. package/src/components/SaveDialog.tsx +55 -127
  141. package/src/components/SaveStatus.tsx +11 -29
  142. package/src/components/StartupLoadingScreen.tsx +96 -0
  143. package/src/components/StateWatchPanel.tsx +302 -0
  144. package/src/components/StatsOverlay.tsx +12 -5
  145. package/src/components/StatusBar.tsx +64 -0
  146. package/src/components/ThemeManagerDialog.css +89 -0
  147. package/src/components/ThemeManagerDialog.tsx +371 -0
  148. package/src/components/ToolHost.tsx +26 -15
  149. package/src/components/Toolbar.tsx +117 -214
  150. package/src/components/VgaiVoxelLogo.tsx +89 -0
  151. package/src/components/ViewportOverlay.tsx +97 -172
  152. package/src/components/ViewportPanel.tsx +130 -31
  153. package/src/components/WorkspaceDock.tsx +891 -0
  154. package/src/components/WorldSelectionOverlay.tsx +51 -63
  155. package/src/components/WorldTextEditor.tsx +9 -5
  156. package/src/components/asset-documents.tsx +390 -0
  157. package/src/components/asset-editor-persistence.ts +44 -0
  158. package/src/components/asset-selection-section.tsx +338 -0
  159. package/src/components/asset-viewers/AudioViewer.tsx +73 -21
  160. package/src/components/asset-viewers/GenericJsonViewer.tsx +26 -11
  161. package/src/components/asset-viewers/ImageViewer.tsx +43 -11
  162. package/src/components/asset-viewers/InputMapViewer.tsx +512 -134
  163. package/src/components/asset-viewers/MaterialAssetEditor.tsx +540 -0
  164. package/src/components/asset-viewers/ModelViewer.tsx +805 -121
  165. package/src/components/asset-viewers/OnlineAssetDetail.tsx +177 -60
  166. package/src/components/asset-viewers/SourceAssetViewer.tsx +121 -0
  167. package/src/components/asset-workflow.css +506 -0
  168. package/src/components/build-documents.tsx +696 -0
  169. package/src/components/core-utilities.tsx +75 -0
  170. package/src/components/data-documents.tsx +364 -0
  171. package/src/components/{DataPanel.tsx → data-editor.tsx} +138 -288
  172. package/src/components/debugger-panel-state.ts +57 -0
  173. package/src/components/inspector-widgets/AlignmentGrid.tsx +8 -14
  174. package/src/components/inspector-widgets/BorderEditor.tsx +41 -33
  175. package/src/components/inspector-widgets/ColorPicker.tsx +39 -39
  176. package/src/components/inspector-widgets/FontPicker.tsx +13 -21
  177. package/src/components/inspector-widgets/GradientEditor.tsx +11 -19
  178. package/src/components/inspector-widgets/ScrubbableInput.tsx +6 -3
  179. package/src/components/inspector-widgets/ShadowEditor.tsx +20 -33
  180. package/src/components/inspector-widgets/shared.tsx +90 -49
  181. package/src/components/inspectors/AnimationSection.tsx +39 -40
  182. package/src/components/inspectors/AudioSection.tsx +28 -39
  183. package/src/components/inspectors/BoneAttachmentSection.tsx +19 -24
  184. package/src/components/inspectors/CameraSection.tsx +14 -23
  185. package/src/components/inspectors/ComponentsSection.tsx +39 -43
  186. package/src/components/inspectors/JointsSection.tsx +34 -43
  187. package/src/components/inspectors/LightSection.tsx +90 -35
  188. package/src/components/inspectors/MaterialSection.tsx +137 -174
  189. package/src/components/inspectors/MeshSection.tsx +9 -17
  190. package/src/components/inspectors/NavigationSection.tsx +8 -15
  191. package/src/components/inspectors/ParticleSection.tsx +91 -76
  192. package/src/components/inspectors/PhysicsSection.tsx +42 -46
  193. package/src/components/inspectors/PostProcessingSection.tsx +28 -36
  194. package/src/components/inspectors/RenderingSection.tsx +12 -20
  195. package/src/components/inspectors/ShadowSection.tsx +7 -8
  196. package/src/components/inspectors/SplineSection.tsx +31 -42
  197. package/src/components/inspectors/TransformSection.tsx +14 -22
  198. package/src/components/patterns/Dialog.tsx +95 -0
  199. package/src/components/patterns/Fields.tsx +44 -0
  200. package/src/components/patterns/List.tsx +25 -0
  201. package/src/components/patterns/StateSurface.tsx +40 -0
  202. package/src/components/patterns/Surfaces.tsx +115 -0
  203. package/src/components/patterns/Tabs.tsx +80 -0
  204. package/src/components/patterns/Toolbar.tsx +51 -0
  205. package/src/components/patterns/Tree.tsx +72 -0
  206. package/src/components/primitives/Button.tsx +46 -0
  207. package/src/components/primitives/ColorInput.tsx +11 -47
  208. package/src/components/primitives/EditorIcon.tsx +99 -0
  209. package/src/components/primitives/FormControls.tsx +71 -0
  210. package/src/components/primitives/JsonInput.tsx +15 -2
  211. package/src/components/primitives/Layout.tsx +100 -0
  212. package/src/components/primitives/Menu.tsx +105 -0
  213. package/src/components/primitives/NumberInput.tsx +8 -35
  214. package/src/components/primitives/Panel.tsx +49 -44
  215. package/src/components/primitives/SectionHeader.tsx +10 -28
  216. package/src/components/primitives/Text.tsx +46 -0
  217. package/src/components/primitives/Tooltip.tsx +20 -12
  218. package/src/components/primitives/Vec3Input.tsx +3 -0
  219. package/src/components/primitives/banner-tones.ts +33 -0
  220. package/src/components/primitives/editor-icons.ts +102 -0
  221. package/src/components/primitives/panel-header-styles.ts +30 -0
  222. package/src/components/project-operation-documents.tsx +226 -0
  223. package/src/components/status-contributions.tsx +175 -0
  224. package/src/components/story-documents-register.ts +15 -0
  225. package/src/components/story-documents.tsx +1185 -0
  226. package/src/components/tool-documents.tsx +110 -0
  227. package/src/components/workspace-dock.css +293 -0
  228. package/src/components/workspace-history.ts +33 -0
  229. package/src/components/world-documents.tsx +145 -0
  230. package/src/components/xstate-documents.tsx +209 -0
  231. package/src/composite-screenshot.ts +248 -0
  232. package/src/design-system/index.ts +70 -0
  233. package/src/design-system-stories/Foundation.stories.tsx +183 -0
  234. package/src/design-system-stories/Patterns.stories.tsx +251 -0
  235. package/src/design-system-stories/Workspace.stories.tsx +538 -0
  236. package/src/doctor/report.ts +57 -13
  237. package/src/editor-api.ts +295 -31
  238. package/src/editor-compatibility.ts +200 -0
  239. package/src/editor-console.ts +35 -17
  240. package/src/editor-hotkeys.ts +85 -33
  241. package/src/editor-store.ts +538 -233
  242. package/src/editor-viewport.ts +160 -34
  243. package/src/entity-factory.ts +14 -3
  244. package/src/gizmo-registry.ts +21 -5
  245. package/src/harness-chat-editor-context.ts +37 -0
  246. package/src/harness-chat-indicator.ts +68 -0
  247. package/src/harness-chat-layout.ts +53 -0
  248. package/src/harness-chat-lifecycle.ts +170 -0
  249. package/src/harness-chat-reconnect.ts +85 -0
  250. package/src/harness-chat-types.ts +409 -0
  251. package/src/hierarchy-drop.ts +50 -0
  252. package/src/hierarchy-expansion-state.ts +100 -0
  253. package/src/hierarchy-menu-registry.ts +1 -1
  254. package/src/hierarchy-projection.ts +76 -0
  255. package/src/history/editor-session.ts +21 -0
  256. package/src/history/history-commands.ts +147 -0
  257. package/src/history/history-service.ts +904 -0
  258. package/src/history/json-history-resource.ts +165 -0
  259. package/src/history/persistence-coordinator.ts +35 -0
  260. package/src/history/project-file-history.ts +266 -0
  261. package/src/history/project-root-history-backends.ts +53 -0
  262. package/src/history/resource-registry.ts +209 -0
  263. package/src/history/snapshot-store.ts +108 -0
  264. package/src/history/source-history-backend.ts +374 -0
  265. package/src/history/types.ts +100 -0
  266. package/src/hmr-registration-group.ts +67 -0
  267. package/src/hmr-stable-react-context.ts +23 -0
  268. package/src/hosted-example.ts +12 -2
  269. package/src/hotkeys.ts +89 -18
  270. package/src/ingest/discovery-react.ts +9 -10
  271. package/src/ingest/discovery.ts +4 -4
  272. package/src/ingest/discovery2d.ts +5 -5
  273. package/src/ingest/games/bloom-composer/vgai.game.json +3 -3
  274. package/src/ingest/games/embed-bundle/vgai.game.json +3 -3
  275. package/src/ingest/games/games-fps/vgai.game.json +3 -3
  276. package/src/ingest/games/iframe-reachable/vgai.game.json +3 -3
  277. package/src/ingest/games/interactive-cubes/vgai.game.json +3 -3
  278. package/src/ingest/games/mismatched-three/vgai.game.json +3 -3
  279. package/src/ingest/games/own-three-bundled/vgai.game.json +3 -3
  280. package/src/ingest/games/pointerlock-maze/vgai.game.json +7 -4
  281. package/src/ingest/games-2d/bubbo-bubbo/vgai.game.json +3 -3
  282. package/src/ingest/games-2d/bunnymark/vgai.game.json +3 -3
  283. package/src/ingest/games-2d/flappy/vgai.game.json +3 -3
  284. package/src/ingest/games-2d/flappy-pixi/vgai.game.json +3 -3
  285. package/src/ingest/games-2d/puzzling-potions/vgai.game.json +3 -3
  286. package/src/ingest/games-2d/tilemap-camera/vgai.game.json +3 -3
  287. package/src/ingest/games-react/react-rpg/vgai.game.json +3 -3
  288. package/src/ingest/types.ts +1 -1
  289. package/src/ingest-mode.ts +47 -52
  290. package/src/ingest-siblings.ts +23 -33
  291. package/src/initial-scene.ts +1 -2
  292. package/src/main.tsx +16 -8
  293. package/src/manifest-project.ts +10 -12
  294. package/src/model-thumbnail.ts +261 -26
  295. package/src/module-mode.ts +44 -50
  296. package/src/play-log-events.ts +26 -0
  297. package/src/play-mode.ts +495 -401
  298. package/src/play-scene-adoption.ts +23 -0
  299. package/src/playground/main.ts +68 -24
  300. package/src/playwright-shim.ts +473 -0
  301. package/src/project-manager.ts +90 -10
  302. package/src/project-operations.ts +112 -0
  303. package/src/project-story-discovery.ts +25 -0
  304. package/src/project-tool-discovery.ts +43 -0
  305. package/src/scene-serializer.ts +0 -6
  306. package/src/scene-sync.ts +7 -2
  307. package/src/script-loader.ts +3 -3
  308. package/src/skeleton-helper.ts +57 -0
  309. package/src/storage/seed.ts +68 -8
  310. package/src/stories/StoryPreviewMount.tsx +265 -0
  311. package/src/stories/action-stories.fixture.tsx +44 -0
  312. package/src/stories/button-stories.fixture.tsx +50 -0
  313. package/src/stories/compose-project-stories.ts +182 -0
  314. package/src/stories/loader-stories.fixture.tsx +53 -0
  315. package/src/stories/story-discovery.ts +130 -0
  316. package/src/stories/story-registry.ts +146 -0
  317. package/src/theme-css.ts +1049 -0
  318. package/src/theme-library.ts +238 -0
  319. package/src/theme-preference.ts +130 -0
  320. package/src/theme.ts +527 -0
  321. package/src/tool-loader.ts +80 -24
  322. package/src/ui-source/inspect.ts +27 -4
  323. package/src/ui-source/source-write-backend.ts +83 -22
  324. package/src/widgets/index.ts +9 -18
  325. package/src/workspace-aux-commands.ts +11 -0
  326. package/src/workspace-dock-controller.ts +64 -0
  327. package/src/workspace-document-ids.ts +5 -0
  328. package/src/workspace-document-registry.ts +367 -0
  329. package/src/workspace-state-persistence.ts +455 -0
  330. package/src/workspace-status-registry.ts +110 -0
  331. package/src/workspace-utility-commands.ts +49 -0
  332. package/src/workspace-utility-registry.ts +161 -0
  333. package/src/world-document-routing.ts +16 -0
  334. package/src/xstate-inspect/XStateMachineInspector.tsx +544 -0
  335. package/src/xstate-inspect/character-animation-machine.fixture.ts +74 -0
  336. package/src/xstate-inspect/use-live-actor-state.ts +39 -0
  337. package/src/xstate-inspect/xstate-graph.ts +265 -0
  338. package/src/xstate-inspect/xstate-layout.ts +76 -0
  339. package/src/z-index.ts +26 -0
  340. package/template/.claude/skills/editor/SKILL.md +129 -90
  341. package/template/AGENTS.md +1110 -0
  342. package/template/CLAUDE.md +1 -512
  343. package/template/IDIOMS.md +50 -0
  344. package/template/check-idioms.ts +706 -0
  345. package/template/manifest-entry-modules-plugin.ts +53 -0
  346. package/template/package.json +13 -4
  347. package/template/public/scenes/default.scn2d.json +6 -0
  348. package/template/public/scenes/default.vscn.json +7 -1
  349. package/template/public/scenes/untitled.vscn.json +10 -0
  350. package/template/scripts/playtest.ts +40 -0
  351. package/template/server/loopback.ts +75 -0
  352. package/template/server/rooms/arena-room.ts +69 -1
  353. package/template/server/rooms/game-room.ts +68 -1
  354. package/template/src/data/README.md +9 -4
  355. package/template/src/data/data-ref.ts +35 -0
  356. package/template/src/data/tuning.schema.ts +1 -1
  357. package/template/src/main.ts +41 -62
  358. package/template/src/manifest-entry-modules.d.ts +3 -0
  359. package/template/src/operations/README.md +88 -0
  360. package/template/src/runtime/pixi-adapter.ts +24 -0
  361. package/template/src/scripts/components/data-spinner.ts +43 -2
  362. package/template/src/scripts/components/spin-lap-math.ts +27 -0
  363. package/template/src/tools/README.md +5 -3
  364. package/template/src/tools/example.tool.tsx +13 -7
  365. package/template/src/ui/Button.stories.tsx +66 -0
  366. package/template/src/ui/RebindPanel.tsx +2 -6
  367. package/template/src/ui/game.tsx +17 -0
  368. package/template/tests/acceptance/example.spec.ts +64 -0
  369. package/template/tests/logic/example.test.ts +31 -0
  370. package/template/tests/logic/room.test.ts +100 -0
  371. package/template/tests/tsconfig.json +30 -0
  372. package/template/validate-manifest.ts +5 -5
  373. package/template/validate-scenes.ts +50 -18
  374. package/template/vgai.game.json +20 -13
  375. package/template/vite.config.ts +77 -16
  376. package/vite-plugin-ui-oid.ts +261 -17
  377. package/dist/assets/CCDIKSolver-Dz-ysxZF.js +0 -1
  378. package/dist/assets/SelectionBox-BXsv_6aH.js +0 -1
  379. package/dist/assets/browser-project-scripts-DOru4L1x.js +0 -2
  380. package/dist/assets/browserAll-CEkWYlFk.js +0 -1
  381. package/dist/assets/create-runtime-DYIWr3va.js +0 -1
  382. package/dist/assets/data-asset-Bb9sIM7i.js +0 -5
  383. package/dist/assets/game-8SDZPPgH.js +0 -1
  384. package/dist/assets/game-CevorxyV.js +0 -1
  385. package/dist/assets/game-Di7XlywD.js +0 -1
  386. package/dist/assets/game-DjnCaw8R.js +0 -1
  387. package/dist/assets/game-Q9A0aXSV.js +0 -1
  388. package/dist/assets/index-CPVV6pjw.js +0 -1
  389. package/dist/assets/index-CpOnYyeD.js +0 -213
  390. package/dist/assets/index-Dr8H_6Rl.js +0 -1
  391. package/dist/assets/index-_bCaipUk.js +0 -13650
  392. package/dist/assets/ingest-siblings-Dz8eEqrV.js +0 -1
  393. package/dist/assets/mount-manifest-Bhk0ohsl.js +0 -1
  394. package/dist/assets/scene-query-BivAHhJu.js +0 -1
  395. package/dist/assets/webworkerAll-BLrH0F6F.js +0 -1
  396. package/dist/assets/xstate-animation-binding-1NGLmw6y.js +0 -1
  397. package/dist/assets/xstate.esm-BgUD33nl.js +0 -3
  398. package/src/authoring/scene-ui-layer.ts +0 -485
  399. package/src/authoring/ui-authoring-adapter.ts +0 -325
  400. package/src/authoring/ui-hierarchy-menu.ts +0 -40
  401. package/src/authoring/ui-inspector-sections.tsx +0 -156
  402. package/src/authoring/undo-timeline.ts +0 -145
  403. package/src/components/BottomPanel.tsx +0 -326
  404. package/src/components/BuildPanel.tsx +0 -455
  405. package/src/components/FolderTree.tsx +0 -179
  406. package/src/components/RightPanel.tsx +0 -166
  407. package/src/components/WorldContextMenu.tsx +0 -257
  408. package/src/components/asset-viewers/AssetViewer.tsx +0 -38
  409. package/src/ingest/games-react/react-rpg/src/ui/game-state.tsx +0 -129
  410. package/src/scene-ui-mode/active-scene-ui.ts +0 -58
  411. package/src/scene-ui-mode/scene-ui-palette-panel.tsx +0 -152
  412. package/src/scene-ui-mode/scene-ui-timeline-tab.tsx +0 -29
  413. package/src/scene-ui-mode/ui-prefab-dialog-host.tsx +0 -74
  414. package/src/scene-ui-mode/ui-prefab-hierarchy-menu.ts +0 -49
  415. package/src/scene-ui-mode/ui-prefab-io.ts +0 -76
  416. package/src/ui-editor/inspector.tsx +0 -563
  417. package/src/ui-editor/node-ops.ts +0 -266
  418. package/src/ui-editor/overlay.tsx +0 -380
  419. package/src/ui-editor/palette.tsx +0 -114
  420. package/src/ui-editor/react-store.ts +0 -78
  421. package/src/ui-editor/timeline-panel.tsx +0 -443
  422. package/src/ui-editor/ui-edit-store.ts +0 -552
  423. package/src/ui-editor/ui-prefab.ts +0 -95
  424. package/template/project.json +0 -9
  425. package/template/src/scripts/main.ts +0 -94
  426. package/template/src/ui/DefaultHud.tsx +0 -40
  427. package/template/src/ui/game-state.tsx +0 -37
  428. package/template/src/ui/mount-default-hud.tsx +0 -19
  429. package/template/src/ui/mount-ui.ts +0 -15
  430. package/vite-plugin-react-world-provider.ts +0 -81
@@ -0,0 +1,185 @@
1
+ import * as THREE from 'three';
2
+
3
+ export interface ModelInspection {
4
+ nodes: number;
5
+ meshes: number;
6
+ primitives: number;
7
+ vertices: number;
8
+ triangles: number;
9
+ materials: number;
10
+ textures: number;
11
+ bones: number;
12
+ clips: number;
13
+ morphTargets: number;
14
+ memoryBytes: number;
15
+ bounds: { x: number; y: number; z: number } | null;
16
+ hierarchy: Array<{ id: string; name: string; type: string; depth: number }>;
17
+ materialNames: string[];
18
+ textureNames: string[];
19
+ }
20
+
21
+ const textureKeys = [
22
+ 'map',
23
+ 'alphaMap',
24
+ 'aoMap',
25
+ 'bumpMap',
26
+ 'displacementMap',
27
+ 'emissiveMap',
28
+ 'envMap',
29
+ 'lightMap',
30
+ 'metalnessMap',
31
+ 'normalMap',
32
+ 'roughnessMap',
33
+ 'specularMap',
34
+ ] as const;
35
+
36
+ /** Exact, side-effect-free statistics used by the Asset Editor and import validation. */
37
+ export function inspectModel(root: THREE.Object3D): ModelInspection {
38
+ const materials = new Set<THREE.Material>();
39
+ const textures = new Set<THREE.Texture>();
40
+ const hierarchy: ModelInspection['hierarchy'] = [];
41
+ let nodes = 0;
42
+ let meshes = 0;
43
+ let primitives = 0;
44
+ let vertices = 0;
45
+ let triangles = 0;
46
+ let bones = 0;
47
+ let morphTargets = 0;
48
+ let memoryBytes = 0;
49
+
50
+ // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: one traversal intentionally reconciles geometry, material, texture, animation, and hierarchy statistics.
51
+ root.traverse((object) => {
52
+ nodes++;
53
+ let depth = 0;
54
+ for (let parent = object.parent; parent && parent !== root.parent; parent = parent.parent)
55
+ depth++;
56
+ hierarchy.push({ id: object.uuid, name: object.name || object.type, type: object.type, depth });
57
+ if ((object as THREE.Bone).isBone) bones++;
58
+ const renderable = object as THREE.Mesh;
59
+ const drawable = object as THREE.Object3D & {
60
+ isMesh?: boolean;
61
+ isLine?: boolean;
62
+ isPoints?: boolean;
63
+ };
64
+ if (!drawable.isMesh && !drawable.isLine && !drawable.isPoints) return;
65
+ meshes++;
66
+ const geometry = renderable.geometry;
67
+ if (geometry) {
68
+ const position = geometry.getAttribute('position');
69
+ vertices += position?.count ?? 0;
70
+ triangles += geometry.index
71
+ ? Math.floor(geometry.index.count / 3)
72
+ : Math.floor((position?.count ?? 0) / 3);
73
+ primitives += Math.max(geometry.groups.length, 1);
74
+ morphTargets += geometry.morphAttributes['position']?.length ?? 0;
75
+ if (geometry.index) memoryBytes += geometry.index.array.byteLength;
76
+ for (const attribute of Object.values(geometry.attributes))
77
+ memoryBytes += attribute.array.byteLength;
78
+ }
79
+ const objectMaterials = Array.isArray(renderable.material)
80
+ ? renderable.material
81
+ : renderable.material
82
+ ? [renderable.material]
83
+ : [];
84
+ for (const material of objectMaterials) {
85
+ materials.add(material);
86
+ for (const key of textureKeys) {
87
+ const texture = (material as THREE.Material & Record<string, unknown>)[key];
88
+ if (texture instanceof THREE.Texture) textures.add(texture);
89
+ }
90
+ }
91
+ });
92
+ for (const texture of textures) {
93
+ const image = texture.image as { width?: number; height?: number } | undefined;
94
+ memoryBytes += (image?.width ?? 0) * (image?.height ?? 0) * 4;
95
+ }
96
+ const box = new THREE.Box3().setFromObject(root);
97
+ const size = box.isEmpty() ? null : box.getSize(new THREE.Vector3());
98
+ return {
99
+ nodes,
100
+ meshes,
101
+ primitives,
102
+ vertices,
103
+ triangles,
104
+ materials: materials.size,
105
+ textures: textures.size,
106
+ bones,
107
+ clips: root.animations.length,
108
+ morphTargets,
109
+ memoryBytes,
110
+ bounds: size ? { x: size.x, y: size.y, z: size.z } : null,
111
+ hierarchy,
112
+ materialNames: [...materials].map((material) => material.name || material.type),
113
+ textureNames: [...textures].map(
114
+ (texture) => texture.name || texture.source.data?.src || 'Texture',
115
+ ),
116
+ };
117
+ }
118
+
119
+ export type ModelDiagnosticMode =
120
+ | 'lit'
121
+ | 'unlit'
122
+ | 'wireframe'
123
+ | 'normals'
124
+ | 'uv'
125
+ | 'vertex-colors'
126
+ | 'bounds'
127
+ | 'skeleton'
128
+ | 'collision'
129
+ | 'lod';
130
+
131
+ export type ModelCameraPreset =
132
+ | 'front'
133
+ | 'back'
134
+ | 'left'
135
+ | 'right'
136
+ | 'top'
137
+ | 'bottom'
138
+ | 'isometric';
139
+
140
+ export function cameraPresetDirection(preset: ModelCameraPreset): THREE.Vector3 {
141
+ const directions: Record<ModelCameraPreset, [number, number, number]> = {
142
+ front: [0, 0, 1],
143
+ back: [0, 0, -1],
144
+ left: [-1, 0, 0],
145
+ right: [1, 0, 0],
146
+ top: [0, 1, 0],
147
+ bottom: [0, -1, 0],
148
+ isometric: [1, 0.72, 1],
149
+ };
150
+ return new THREE.Vector3(...directions[preset]).normalize();
151
+ }
152
+
153
+ export function supportedModelDiagnosticModes(root: THREE.Object3D): Array<{
154
+ mode: ModelDiagnosticMode;
155
+ available: boolean;
156
+ reason?: string;
157
+ }> {
158
+ let uv = false;
159
+ let colors = false;
160
+ let bones = false;
161
+ let lod = false;
162
+ root.traverse((object) => {
163
+ const mesh = object as THREE.Mesh;
164
+ uv ||= Boolean(mesh.geometry?.getAttribute('uv'));
165
+ colors ||= Boolean(mesh.geometry?.getAttribute('color'));
166
+ bones ||= Boolean((object as THREE.Bone).isBone || (mesh as THREE.SkinnedMesh).isSkinnedMesh);
167
+ lod ||= (object as THREE.LOD).isLOD === true;
168
+ });
169
+ return [
170
+ { mode: 'lit', available: true },
171
+ { mode: 'unlit', available: true },
172
+ { mode: 'wireframe', available: true },
173
+ { mode: 'normals', available: true },
174
+ { mode: 'uv', available: uv, ...(!uv ? { reason: 'No UV channel' } : {}) },
175
+ {
176
+ mode: 'vertex-colors',
177
+ available: colors,
178
+ ...(!colors ? { reason: 'No vertex colors' } : {}),
179
+ },
180
+ { mode: 'bounds', available: true },
181
+ { mode: 'skeleton', available: bones, ...(!bones ? { reason: 'No skeleton' } : {}) },
182
+ { mode: 'collision', available: false, reason: 'No collision metadata' },
183
+ { mode: 'lod', available: lod, ...(!lod ? { reason: 'No LOD groups' } : {}) },
184
+ ];
185
+ }
@@ -0,0 +1,23 @@
1
+ export const PROJECT_ASSET_COMMANDS = {
2
+ open: { id: 'project-asset.open', shortcut: 'Enter' },
3
+ rename: { id: 'project-asset.rename', shortcut: 'F2' },
4
+ duplicate: { id: 'project-asset.duplicate', shortcut: 'Mod+D' },
5
+ delete: { id: 'project-asset.delete', shortcut: 'Delete' },
6
+ copyPath: { id: 'project-asset.copy-path', shortcut: 'Mod+Shift+C' },
7
+ reveal: { id: 'project-asset.reveal', shortcut: null },
8
+ refresh: { id: 'project-asset.refresh', shortcut: 'Mod+R' },
9
+ import: { id: 'project-asset.import', shortcut: null },
10
+ createFolder: { id: 'project-asset.create-folder', shortcut: 'Mod+Shift+N' },
11
+ reimport: { id: 'project-asset.reimport', shortcut: null },
12
+ } as const;
13
+
14
+ export type ProjectAssetCommandId =
15
+ (typeof PROJECT_ASSET_COMMANDS)[keyof typeof PROJECT_ASSET_COMMANDS]['id'];
16
+
17
+ export function projectAssetCommandIds(): readonly ProjectAssetCommandId[] {
18
+ return Object.values(PROJECT_ASSET_COMMANDS).map((command) => command.id);
19
+ }
20
+
21
+ export function projectAssetBrowserStateKey(projectId: string): string {
22
+ return `vgai:project-assets:v1:${projectId}`;
23
+ }
@@ -0,0 +1,153 @@
1
+ import type { StorageBackend } from '../storage/types';
2
+ import { assetCapabilities } from './asset-capabilities';
3
+ import type { AssetHealth, AssetHealthCode } from './asset-types';
4
+
5
+ export interface ProjectAssetFileSnapshot {
6
+ readonly path: string;
7
+ readonly text?: string;
8
+ }
9
+
10
+ export interface ProjectAssetRecipeSnapshot {
11
+ readonly sourcePath: string;
12
+ readonly recordedSourceHash: string;
13
+ readonly currentSourceHash: string;
14
+ }
15
+
16
+ export interface ProjectAssetReference {
17
+ readonly ownerPath: string;
18
+ readonly jsonPath: string;
19
+ readonly assetPath: string;
20
+ }
21
+
22
+ export interface ProjectAssetHealthResult {
23
+ readonly health: AssetHealth;
24
+ readonly dependencies: readonly string[];
25
+ readonly references: readonly ProjectAssetReference[];
26
+ }
27
+
28
+ function collectStringPaths(value: unknown, path = '$'): { path: string; value: string }[] {
29
+ if (typeof value === 'string') return [{ path, value }];
30
+ if (Array.isArray(value)) {
31
+ return value.flatMap((entry, index) => collectStringPaths(entry, `${path}[${index}]`));
32
+ }
33
+ if (!value || typeof value !== 'object') return [];
34
+ return Object.entries(value).flatMap(([key, entry]) =>
35
+ collectStringPaths(entry, `${path}.${key}`),
36
+ );
37
+ }
38
+
39
+ function externalDependencies(assetPath: string, text: string | undefined): string[] {
40
+ if (!text || !assetPath.toLowerCase().endsWith('.gltf')) return [];
41
+ try {
42
+ const json = JSON.parse(text) as { buffers?: { uri?: string }[]; images?: { uri?: string }[] };
43
+ const base = assetPath.includes('/') ? assetPath.slice(0, assetPath.lastIndexOf('/') + 1) : '';
44
+ return [...(json.buffers ?? []), ...(json.images ?? [])].flatMap((entry) =>
45
+ entry.uri && !entry.uri.startsWith('data:') ? [`${base}${entry.uri}`] : [],
46
+ );
47
+ } catch {
48
+ return [];
49
+ }
50
+ }
51
+
52
+ export function inspectProjectAssetHealth(input: {
53
+ readonly assetPath: string;
54
+ readonly files: readonly ProjectAssetFileSnapshot[];
55
+ readonly recipe?: ProjectAssetRecipeSnapshot;
56
+ }): ProjectAssetHealthResult {
57
+ const byPath = new Map(input.files.map((file) => [file.path.replace(/^\//, ''), file]));
58
+ const assetPath = input.assetPath.replace(/^\//, '');
59
+ const asset = byPath.get(assetPath);
60
+ const codes = new Set<AssetHealthCode>();
61
+ const capability = assetCapabilities(assetPath);
62
+ if (!asset) codes.add('missing-file');
63
+ if (!capability.importable) codes.add('unsupported-format');
64
+ else if (!capability.runtimeReady) codes.add('source-only');
65
+
66
+ if (asset?.text && capability.kind === 'json') {
67
+ try {
68
+ JSON.parse(asset.text);
69
+ } catch {
70
+ codes.add('invalid-metadata');
71
+ }
72
+ }
73
+
74
+ const dependencies = externalDependencies(assetPath, asset?.text);
75
+ if (dependencies.some((path) => !byPath.has(path))) codes.add('missing-dependency');
76
+ if (
77
+ assetPath.startsWith('asset-library/local/') &&
78
+ !byPath.has(`${assetPath.slice(0, assetPath.lastIndexOf('/') + 1)}_VGAI_ASSET.json`)
79
+ ) {
80
+ codes.add('missing-attribution');
81
+ }
82
+ if (input.recipe && input.recipe.recordedSourceHash !== input.recipe.currentSourceHash) {
83
+ codes.add('stale-import');
84
+ }
85
+
86
+ const references: ProjectAssetReference[] = [];
87
+ for (const file of input.files) {
88
+ if (!file.text || !/\.(?:vscn|prefab|mat)\.json$/i.test(file.path)) continue;
89
+ try {
90
+ for (const candidate of collectStringPaths(JSON.parse(file.text))) {
91
+ const normalized = candidate.value.replace(/^\//, '');
92
+ if (normalized === assetPath) {
93
+ references.push({ ownerPath: file.path, jsonPath: candidate.path, assetPath });
94
+ } else if (normalized.startsWith('assets/') && !byPath.has(normalized)) {
95
+ codes.add('broken-scene-reference');
96
+ }
97
+ }
98
+ } catch {
99
+ codes.add('invalid-metadata');
100
+ }
101
+ }
102
+
103
+ if (codes.size === 0) codes.add('healthy');
104
+ const errorCodes = new Set<AssetHealthCode>([
105
+ 'missing-file',
106
+ 'missing-dependency',
107
+ 'invalid-metadata',
108
+ 'broken-scene-reference',
109
+ ]);
110
+ return {
111
+ health: {
112
+ status: [...codes].some((code) => errorCodes.has(code))
113
+ ? 'error'
114
+ : codes.has('healthy')
115
+ ? 'healthy'
116
+ : 'warning',
117
+ codes: [...codes],
118
+ },
119
+ dependencies,
120
+ references,
121
+ };
122
+ }
123
+
124
+ async function snapshotProjectFiles(
125
+ backend: StorageBackend,
126
+ dir = '',
127
+ ): Promise<ProjectAssetFileSnapshot[]> {
128
+ const entries = await backend.list(dir);
129
+ const snapshots: ProjectAssetFileSnapshot[] = [];
130
+ for (const entry of entries) {
131
+ const path = dir ? `${dir}/${entry.name}` : entry.name;
132
+ if (entry.type === 'dir') {
133
+ snapshots.push(...(await snapshotProjectFiles(backend, path)));
134
+ } else if (/\.json$|\.gltf$/i.test(path)) {
135
+ snapshots.push({ path, text: await backend.read(path) });
136
+ } else {
137
+ snapshots.push({ path });
138
+ }
139
+ }
140
+ return snapshots;
141
+ }
142
+
143
+ export async function inspectProjectAssetHealthFromStorage(
144
+ backend: StorageBackend,
145
+ assetPath: string,
146
+ recipe?: ProjectAssetRecipeSnapshot,
147
+ ): Promise<ProjectAssetHealthResult> {
148
+ return inspectProjectAssetHealth({
149
+ assetPath,
150
+ files: await snapshotProjectFiles(backend),
151
+ ...(recipe ? { recipe } : {}),
152
+ });
153
+ }
@@ -0,0 +1,119 @@
1
+ import type { HistoryService } from '../history/history-service';
2
+ import { getProjectFileHistory } from '../history/project-file-history';
3
+ import type { StorageBackend } from '../storage/types';
4
+
5
+ export type ProjectAssetOperation =
6
+ | { readonly type: 'create-folder'; readonly path: string }
7
+ | { readonly type: 'duplicate'; readonly source: string; readonly destination: string }
8
+ | { readonly type: 'move'; readonly source: string; readonly destination: string }
9
+ | { readonly type: 'delete'; readonly paths: readonly string[] }
10
+ | { readonly type: 'import'; readonly files: readonly { path: string; bytes: Uint8Array }[] }
11
+ | { readonly type: 'reimport'; readonly files: readonly { path: string; bytes: Uint8Array }[] };
12
+
13
+ function emitAssetMutation(detail: {
14
+ type: 'move' | 'delete' | 'change';
15
+ oldPath?: string;
16
+ newPath?: string;
17
+ paths?: readonly string[];
18
+ }): void {
19
+ if (typeof window === 'undefined') return;
20
+ window.dispatchEvent(new CustomEvent('editor:asset-mutation', { detail }));
21
+ window.dispatchEvent(new CustomEvent('editor:assets-changed'));
22
+ }
23
+
24
+ function validPath(path: string): string {
25
+ const normalized = path
26
+ .replace(/^\/+/, '')
27
+ .replace(/\\/g, '/')
28
+ .replace(/\/{2,}/g, '/');
29
+ if (
30
+ !normalized ||
31
+ normalized === '.' ||
32
+ normalized.split('/').some((part) => !part || part === '..')
33
+ ) {
34
+ throw new Error(`Invalid project asset path: ${path}`);
35
+ }
36
+ return normalized;
37
+ }
38
+
39
+ async function assertDestinationsFree(backend: StorageBackend, paths: readonly string[]) {
40
+ for (const path of paths) {
41
+ if (await backend.exists(path)) throw new Error(`An asset already exists at ${path}.`);
42
+ }
43
+ }
44
+
45
+ /** Executes all writes through exact-byte project history; no UI owns filesystem mutation. */
46
+ // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: the tagged operation union is intentionally centralized so every project mutation shares one atomic history boundary.
47
+ export async function executeProjectAssetOperation(
48
+ operation: ProjectAssetOperation,
49
+ dependencies: { readonly backend: StorageBackend; readonly history: HistoryService },
50
+ ): Promise<boolean> {
51
+ const files = getProjectFileHistory(dependencies.history, dependencies.backend);
52
+ switch (operation.type) {
53
+ case 'create-folder': {
54
+ const folder = validPath(operation.path);
55
+ const marker = `${folder}/.vgai-folder`;
56
+ if (await dependencies.backend.exists(folder))
57
+ throw new Error(`Folder already exists: ${folder}.`);
58
+ return files.write(marker, '', { label: `Create folder ${folder}` });
59
+ }
60
+ case 'duplicate': {
61
+ const source = validPath(operation.source);
62
+ const destination = validPath(operation.destination);
63
+ await assertDestinationsFree(dependencies.backend, [destination]);
64
+ const bytes = await dependencies.backend.readBytes(source);
65
+ return files.write(destination, bytes, { label: `Duplicate ${source}` });
66
+ }
67
+ case 'move': {
68
+ const source = validPath(operation.source);
69
+ const destination = validPath(operation.destination);
70
+ await assertDestinationsFree(dependencies.backend, [destination]);
71
+ const bytes = await dependencies.backend.readBytes(source);
72
+ const changed = await files.mutate(
73
+ [
74
+ { path: destination, data: bytes },
75
+ { path: source, data: null },
76
+ ],
77
+ { label: `Move ${source}` },
78
+ );
79
+ if (changed) emitAssetMutation({ type: 'move', oldPath: source, newPath: destination });
80
+ return changed;
81
+ }
82
+ case 'delete': {
83
+ const paths = [...new Set(operation.paths.map(validPath))];
84
+ const changed = await files.mutate(
85
+ paths.map((path) => ({ path, data: null })),
86
+ { label: `Delete ${paths.length} asset${paths.length === 1 ? '' : 's'}` },
87
+ );
88
+ if (changed) emitAssetMutation({ type: 'delete', paths });
89
+ return changed;
90
+ }
91
+ case 'import': {
92
+ const imported = operation.files.map((file) => ({
93
+ path: validPath(file.path),
94
+ data: file.bytes,
95
+ }));
96
+ await assertDestinationsFree(
97
+ dependencies.backend,
98
+ imported.map((file) => file.path),
99
+ );
100
+ const changed = await files.mutate(imported, {
101
+ label: `Import ${imported.length} asset${imported.length === 1 ? '' : 's'}`,
102
+ });
103
+ if (changed) emitAssetMutation({ type: 'change', paths: imported.map((file) => file.path) });
104
+ return changed;
105
+ }
106
+ case 'reimport': {
107
+ const reimported = operation.files.map((file) => ({
108
+ path: validPath(file.path),
109
+ data: file.bytes,
110
+ }));
111
+ const changed = await files.mutate(reimported, {
112
+ label: `Reimport ${reimported.length} asset${reimported.length === 1 ? '' : 's'}`,
113
+ });
114
+ if (changed)
115
+ emitAssetMutation({ type: 'change', paths: reimported.map((file) => file.path) });
116
+ return changed;
117
+ }
118
+ }
119
+ }
@@ -0,0 +1,172 @@
1
+ import { z } from 'zod';
2
+ import { getStorageBackend } from '../storage';
3
+ import type { AssetCatalog } from './asset-types';
4
+
5
+ export const THUMBNAIL_PROFILE = {
6
+ version: 'vgai-thumbnail-v2',
7
+ width: 256,
8
+ height: 256,
9
+ background: 'neutral',
10
+ framing: 'bounds-isometric-1.3',
11
+ lighting: 'studio-key-fill-v1',
12
+ colorManagement: 'srgb-aces-filmic',
13
+ animationPose: 'representative-35-percent',
14
+ } as const;
15
+
16
+ export const thumbnailManifestSchema = z.object({
17
+ version: z.literal(1),
18
+ profile: z.literal(THUMBNAIL_PROFILE.version),
19
+ generatedAt: z.string(),
20
+ entries: z.record(
21
+ z.string(),
22
+ z.object({
23
+ state: z.enum(['generated', 'upstream', 'fallback', 'unsupported', 'failed']),
24
+ path: z.string().optional(),
25
+ profileVersion: z.string(),
26
+ sourceHash: z.string().optional(),
27
+ resultHash: z.string().optional(),
28
+ issueCode: z.string().optional(),
29
+ animation: z.object({ clip: z.string(), time: z.number().nonnegative() }).optional(),
30
+ framing: z
31
+ .object({
32
+ position: z.tuple([z.number(), z.number(), z.number()]),
33
+ target: z.tuple([z.number(), z.number(), z.number()]),
34
+ })
35
+ .optional(),
36
+ }),
37
+ ),
38
+ });
39
+ export type ThumbnailManifest = z.infer<typeof thumbnailManifestSchema>;
40
+
41
+ let manifestPromise: Promise<ThumbnailManifest | null> | null = null;
42
+ export function loadProjectThumbnailManifest(): Promise<ThumbnailManifest | null> {
43
+ if (!manifestPromise) {
44
+ const backend = getStorageBackend() as ReturnType<typeof getStorageBackend> | undefined;
45
+ manifestPromise = backend
46
+ ? backend.read('.vgai/thumbnails.json').then(
47
+ (content) => thumbnailManifestSchema.parse(JSON.parse(content)),
48
+ () => null,
49
+ )
50
+ : Promise.resolve(null);
51
+ }
52
+ return manifestPromise;
53
+ }
54
+ export function resetProjectThumbnailManifestCache(): void {
55
+ manifestPromise = null;
56
+ }
57
+
58
+ interface QueueJob<T> {
59
+ key: string;
60
+ priority: number;
61
+ consumers: number;
62
+ started: boolean;
63
+ resolve(value: T): void;
64
+ reject(error: unknown): void;
65
+ promise: Promise<T>;
66
+ }
67
+
68
+ /** Bounded, priority-aware, deduplicating fallback queue shared by all visible cards. */
69
+ export class ThumbnailJobQueue<T> {
70
+ private active = 0;
71
+ private readonly jobs = new Map<string, QueueJob<T>>();
72
+ private pending: QueueJob<T>[] = [];
73
+ constructor(
74
+ private readonly execute: (key: string) => Promise<T>,
75
+ readonly concurrency = 2,
76
+ ) {}
77
+ request(key: string, priority = 0): { promise: Promise<T>; cancel(): void } {
78
+ let job = this.jobs.get(key);
79
+ if (!job) {
80
+ let resolve!: (value: T) => void;
81
+ let reject!: (error: unknown) => void;
82
+ const promise = new Promise<T>((ok, fail) => {
83
+ resolve = ok;
84
+ reject = fail;
85
+ });
86
+ job = { key, priority, consumers: 0, started: false, resolve, reject, promise };
87
+ this.jobs.set(key, job);
88
+ this.pending.push(job);
89
+ }
90
+ job.consumers++;
91
+ job.priority = Math.max(job.priority, priority);
92
+ this.pending.sort((a, b) => b.priority - a.priority);
93
+ this.pump();
94
+ let cancelled = false;
95
+ return {
96
+ promise: job.promise,
97
+ cancel: () => {
98
+ if (cancelled) return;
99
+ cancelled = true;
100
+ job!.consumers--;
101
+ if (!job!.started && job!.consumers === 0) {
102
+ this.pending = this.pending.filter((item) => item !== job);
103
+ this.jobs.delete(job!.key);
104
+ job!.reject(new DOMException('Thumbnail request cancelled', 'AbortError'));
105
+ }
106
+ },
107
+ };
108
+ }
109
+ snapshot(): { active: number; pending: number; keys: number } {
110
+ return { active: this.active, pending: this.pending.length, keys: this.jobs.size };
111
+ }
112
+ private pump(): void {
113
+ while (this.active < this.concurrency) {
114
+ const job = this.pending.shift();
115
+ if (!job) return;
116
+ if (job.consumers === 0) continue;
117
+ job.started = true;
118
+ this.active++;
119
+ void this.execute(job.key)
120
+ .then(job.resolve, job.reject)
121
+ .finally(() => {
122
+ this.active--;
123
+ this.jobs.delete(job.key);
124
+ this.pump();
125
+ });
126
+ }
127
+ }
128
+ }
129
+
130
+ export interface ThumbnailQualitySample {
131
+ alphaCoverage: number;
132
+ luminanceVariance: number;
133
+ edgeCoverage: number;
134
+ }
135
+ export function auditThumbnailQuality(sample: ThumbnailQualitySample): string[] {
136
+ const issues: string[] = [];
137
+ if (sample.alphaCoverage < 0.01) issues.push('thumbnail.blank-transparent');
138
+ if (sample.luminanceVariance < 0.0005 && sample.edgeCoverage < 0.01)
139
+ issues.push('thumbnail.near-uniform');
140
+ if (sample.edgeCoverage > 0.45) issues.push('thumbnail.possibly-clipped');
141
+ return issues;
142
+ }
143
+
144
+ export function catalogThumbnailCoverage(catalog: AssetCatalog): {
145
+ total: number;
146
+ generated: number;
147
+ upstream: number;
148
+ fallback: number;
149
+ unsupported: number;
150
+ failed: number;
151
+ } {
152
+ const result = { total: 0, generated: 0, upstream: 0, fallback: 0, unsupported: 0, failed: 0 };
153
+ for (const family of catalog.families) {
154
+ for (const variant of family.variants) {
155
+ result.total++;
156
+ result[variant.thumbnail.state]++;
157
+ }
158
+ }
159
+ return result;
160
+ }
161
+
162
+ export function representativeAnimationPose(
163
+ clips: readonly { name: string; duration: number }[],
164
+ ): { clip: string; time: number } | null {
165
+ if (!clips.length) return null;
166
+ const ranked = [...clips].sort((a, b) => {
167
+ const score = (name: string) => (/walk|run|idle|action/i.test(name) ? 1 : 0);
168
+ return score(b.name) - score(a.name) || b.duration - a.duration || a.name.localeCompare(b.name);
169
+ });
170
+ const clip = ranked[0]!;
171
+ return { clip: clip.name, time: Number((clip.duration * 0.35).toFixed(6)) };
172
+ }
@@ -48,3 +48,18 @@ export function setActiveNetworking(n: SystemAdapters['networking'] | null): voi
48
48
  export function getActiveNetworking(): SystemAdapters['networking'] | null {
49
49
  return _systems.networking ?? null;
50
50
  }
51
+
52
+ export function setActiveDebug(d: SystemAdapters['debug'] | null): void {
53
+ const next = { ..._systems };
54
+ if (d) next.debug = d;
55
+ else delete next.debug;
56
+ _systems = next;
57
+ }
58
+ /** The active game's debug seam (`DebugAdapter`, SYNTHETIC-PLAYER-SPEC §3.1) —
59
+ * installed with the whole bag by play-mode's `setActiveSystems`, read by
60
+ * the Debug Console / State Watch panels and the CLI command relay. Null
61
+ * outside play (or for a mount exposing no debug adapter) — the panels'
62
+ * hide signal. */
63
+ export function getActiveDebug(): SystemAdapters['debug'] | null {
64
+ return _systems.debug ?? null;
65
+ }