@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.
- package/README.md +24 -0
- package/dist/assets/axe-C7YBtOuM.js +30 -0
- package/dist/assets/browserAll-DDtGGE48.js +1 -0
- package/dist/assets/create-runtime-DbTlBlKG.js +1 -0
- package/dist/assets/{game-BI111o5f.js → game-BE0r7DMI.js} +3 -3
- package/dist/assets/game-BdTHR2_X.js +1 -0
- package/dist/assets/{game-pt_1lQLv.js → game-C3iQGSqo.js} +1 -1
- package/dist/assets/game-CcHfYxjP.js +1 -0
- package/dist/assets/game-Cqzj_lpD.js +1 -0
- package/dist/assets/game-DSZdeCHu.js +1 -0
- package/dist/assets/game-Dch9sAPc.js +1 -0
- package/dist/assets/{game-xsOho_wg.js → game-SAFn90pA.js} +3 -3
- package/dist/assets/game-loop-DMIgSMV-.js +1 -0
- package/dist/assets/{game-source-CvidN7AB.js → game-source-Dk83w26t.js} +1 -1
- package/dist/assets/{index-Bp5cecD6.js → index-4idIsHHJ.js} +2 -2
- package/dist/assets/{index-DL7RcQ0n.js → index-C-LhOTBN.js} +1 -1
- package/dist/assets/{index-Cs8xwv3e.js → index-CDkxceqE.js} +1 -1
- package/dist/assets/index-CQEfi217.js +15412 -0
- package/dist/assets/index-Dvi-V0NE.css +1 -0
- package/dist/assets/ingest-siblings-s0P5I1Ub.js +1 -0
- package/dist/assets/mount-manifest-DXMOlF79.js +1 -0
- package/dist/assets/react-18-C9eHn8x1.js +1 -0
- package/dist/assets/webworkerAll-Fn0e-ouF.js +1 -0
- package/dist/assets/world-documents-GM0a9Kv7.js +1 -0
- package/dist/index.html +15 -13
- package/dist/ingest/racing-game/vgai.game.json +5 -4
- package/dist/ingest/simcity/vgai.game.json +5 -4
- package/dist/ingest/tanks/vgai.game.json +5 -4
- package/dist/project-scripts.bundle.js +3917 -0
- package/dist/scenes/untitled.vscn.json +2 -0
- package/dist-server/packaged.mjs +7697 -3435
- package/package.json +31 -4
- package/scripts/probe-reporter.ts +349 -0
- package/scripts/run-probe.ts +2260 -0
- package/server/asset-catalog-audit.ts +268 -0
- package/server/asset-catalog-v2.ts +146 -0
- package/server/asset-library-routes.ts +276 -58
- package/server/canonical-path.ts +1 -1
- package/server/dev.ts +148 -44
- package/server/editor-server.ts +888 -61
- package/server/editor-sse.ts +115 -4
- package/server/harness-chat-service.ts +796 -0
- package/server/local-asset-catalog.ts +157 -22
- package/server/packaged.ts +51 -21
- package/server/process-shutdown.ts +136 -0
- package/server/prod.ts +9 -6
- package/server/project-hmr-files.ts +41 -0
- package/server/project-operations.ts +343 -0
- package/server/project-optimize-deps-entries.ts +4 -4
- package/server/project-output-writer.ts +156 -0
- package/server/project-validation.ts +140 -0
- package/server/project-view.ts +8 -50
- package/server/server-utils.ts +75 -10
- package/server/session-registry.ts +12 -0
- package/src/EditorContext.tsx +107 -3
- package/src/action-registry.ts +115 -17
- package/src/adapter-resolver.ts +213 -478
- package/src/asset-events.ts +64 -1
- package/src/asset-preview.ts +906 -0
- package/src/asset-selection.ts +121 -0
- package/src/asset-workflow/asset-capabilities.ts +259 -0
- package/src/asset-workflow/asset-editor-errors.ts +46 -0
- package/src/asset-workflow/asset-import-pipeline.ts +176 -0
- package/src/asset-workflow/asset-library-query.ts +107 -0
- package/src/asset-workflow/asset-types.ts +192 -0
- package/src/asset-workflow/asset-workflow-quality.ts +65 -0
- package/src/asset-workflow/import-contract.ts +113 -0
- package/src/asset-workflow/model-conversion.ts +126 -0
- package/src/asset-workflow/model-inspection.ts +185 -0
- package/src/asset-workflow/project-asset-commands.ts +23 -0
- package/src/asset-workflow/project-asset-health.ts +153 -0
- package/src/asset-workflow/project-asset-operations.ts +119 -0
- package/src/asset-workflow/thumbnail-system.ts +172 -0
- package/src/authoring/active-systems.ts +15 -0
- package/src/authoring/boundary-authoring-adapter.ts +16 -5
- package/src/authoring/composite-authoring-adapter.ts +305 -124
- package/src/authoring/design-time-layers.ts +300 -117
- package/src/authoring/edit-mode-authoring.ts +278 -293
- package/src/authoring/embed-only-adapter.ts +4 -2
- package/src/authoring/first-party-hierarchy-menu.ts +7 -2
- package/src/authoring/first-party-inspector/AddSectionMenu.tsx +5 -12
- package/src/authoring/first-party-inspector/EnvironmentSection.tsx +53 -126
- package/src/authoring/first-party-inspector/HeaderSection.tsx +54 -65
- package/src/authoring/first-party-inspector/is-first-party-selection.ts +30 -11
- package/src/authoring/first-party-inspector/register.tsx +13 -2
- package/src/authoring/ingest-authoring-adapter.ts +142 -45
- package/src/authoring/mount-failure-report.ts +5 -5
- package/src/authoring/provenance.ts +41 -0
- package/src/authoring/react-dom-authoring-adapter.ts +92 -92
- package/src/authoring/react-inspector-section.tsx +489 -51
- package/src/authoring/react-world-authoring-adapter.ts +541 -306
- package/src/authoring/story-overlay.ts +1 -1
- package/src/authoring/story-types.ts +11 -0
- package/src/authoring/vgai-scene-authoring-adapter.ts +54 -25
- package/src/authoring/viewport-tool-context.ts +78 -0
- package/src/authoring/world-canvas-viewport-state.ts +35 -0
- package/src/authoring/world-hidden-viewport.ts +186 -0
- package/src/authoring/world-pan-state.ts +33 -17
- package/src/authoring/world-session-state.ts +1 -1
- package/src/authoring/world2d-authoring-adapter.ts +109 -52
- package/src/availability-tick.ts +56 -0
- package/src/browser-play-entry.ts +5 -5
- package/src/browser-project-scripts.ts +1 -0
- package/src/build-identity.ts +15 -0
- package/src/command-listener.ts +893 -19
- package/src/components/AlignToolbar.tsx +74 -63
- package/src/components/AppRoot.tsx +193 -26
- package/src/components/ApplicationMenus.tsx +272 -0
- package/src/components/AssetBrowser.tsx +1450 -293
- package/src/components/AssetEditorShell.tsx +141 -0
- package/src/components/AssetImportDetails.tsx +218 -0
- package/src/components/AssetLibraryPanel.tsx +14 -0
- package/src/components/CameraInfo.tsx +11 -20
- package/src/components/CenterDocuments.tsx +585 -0
- package/src/components/CommandPalette.tsx +75 -30
- package/src/components/ConsolePanel.css +55 -0
- package/src/components/ConsolePanel.tsx +57 -112
- package/src/components/DebugConsolePanel.tsx +461 -0
- package/src/components/DebuggerPanel.tsx +165 -0
- package/src/components/DefaultEditorLayout.tsx +167 -231
- package/src/components/GameHierarchy.tsx +873 -290
- package/src/components/GamePerformanceOverlay.tsx +46 -0
- package/src/components/HarnessChatPanel.tsx +2352 -0
- package/src/components/HistoryPanel.tsx +226 -0
- package/src/components/Inspector.tsx +153 -99
- package/src/components/InspectorToolSection.tsx +6 -4
- package/src/components/MountFailureBanner.tsx +31 -48
- package/src/components/NetworkSection.tsx +25 -14
- package/src/components/OnlineAssetBrowser.tsx +794 -343
- package/src/components/OverlayReportBanner.tsx +38 -64
- package/src/components/PerformancePanel.tsx +206 -0
- package/src/components/PlayBar.tsx +93 -263
- package/src/components/PrefabSaveDialog.tsx +55 -127
- package/src/components/ProjectHeader.css +60 -0
- package/src/components/ProjectHeader.tsx +170 -105
- package/src/components/ProjectScreen.tsx +246 -184
- package/src/components/ReactCanvasControls.tsx +185 -0
- package/src/components/ResolutionPicker.tsx +85 -0
- package/src/components/RootActivateDialog.tsx +39 -68
- package/src/components/SaveDialog.tsx +55 -127
- package/src/components/SaveStatus.tsx +11 -29
- package/src/components/StartupLoadingScreen.tsx +96 -0
- package/src/components/StateWatchPanel.tsx +302 -0
- package/src/components/StatsOverlay.tsx +12 -5
- package/src/components/StatusBar.tsx +64 -0
- package/src/components/ThemeManagerDialog.css +89 -0
- package/src/components/ThemeManagerDialog.tsx +371 -0
- package/src/components/ToolHost.tsx +26 -15
- package/src/components/Toolbar.tsx +117 -214
- package/src/components/VgaiVoxelLogo.tsx +89 -0
- package/src/components/ViewportOverlay.tsx +97 -172
- package/src/components/ViewportPanel.tsx +130 -31
- package/src/components/WorkspaceDock.tsx +891 -0
- package/src/components/WorldSelectionOverlay.tsx +51 -63
- package/src/components/WorldTextEditor.tsx +9 -5
- package/src/components/asset-documents.tsx +390 -0
- package/src/components/asset-editor-persistence.ts +44 -0
- package/src/components/asset-selection-section.tsx +338 -0
- package/src/components/asset-viewers/AudioViewer.tsx +73 -21
- package/src/components/asset-viewers/GenericJsonViewer.tsx +26 -11
- package/src/components/asset-viewers/ImageViewer.tsx +43 -11
- package/src/components/asset-viewers/InputMapViewer.tsx +512 -134
- package/src/components/asset-viewers/MaterialAssetEditor.tsx +540 -0
- package/src/components/asset-viewers/ModelViewer.tsx +805 -121
- package/src/components/asset-viewers/OnlineAssetDetail.tsx +177 -60
- package/src/components/asset-viewers/SourceAssetViewer.tsx +121 -0
- package/src/components/asset-workflow.css +506 -0
- package/src/components/build-documents.tsx +696 -0
- package/src/components/core-utilities.tsx +75 -0
- package/src/components/data-documents.tsx +364 -0
- package/src/components/{DataPanel.tsx → data-editor.tsx} +138 -288
- package/src/components/debugger-panel-state.ts +57 -0
- package/src/components/inspector-widgets/AlignmentGrid.tsx +8 -14
- package/src/components/inspector-widgets/BorderEditor.tsx +41 -33
- package/src/components/inspector-widgets/ColorPicker.tsx +39 -39
- package/src/components/inspector-widgets/FontPicker.tsx +13 -21
- package/src/components/inspector-widgets/GradientEditor.tsx +11 -19
- package/src/components/inspector-widgets/ScrubbableInput.tsx +6 -3
- package/src/components/inspector-widgets/ShadowEditor.tsx +20 -33
- package/src/components/inspector-widgets/shared.tsx +90 -49
- package/src/components/inspectors/AnimationSection.tsx +39 -40
- package/src/components/inspectors/AudioSection.tsx +28 -39
- package/src/components/inspectors/BoneAttachmentSection.tsx +19 -24
- package/src/components/inspectors/CameraSection.tsx +14 -23
- package/src/components/inspectors/ComponentsSection.tsx +39 -43
- package/src/components/inspectors/JointsSection.tsx +34 -43
- package/src/components/inspectors/LightSection.tsx +90 -35
- package/src/components/inspectors/MaterialSection.tsx +137 -174
- package/src/components/inspectors/MeshSection.tsx +9 -17
- package/src/components/inspectors/NavigationSection.tsx +8 -15
- package/src/components/inspectors/ParticleSection.tsx +91 -76
- package/src/components/inspectors/PhysicsSection.tsx +42 -46
- package/src/components/inspectors/PostProcessingSection.tsx +28 -36
- package/src/components/inspectors/RenderingSection.tsx +12 -20
- package/src/components/inspectors/ShadowSection.tsx +7 -8
- package/src/components/inspectors/SplineSection.tsx +31 -42
- package/src/components/inspectors/TransformSection.tsx +14 -22
- package/src/components/patterns/Dialog.tsx +95 -0
- package/src/components/patterns/Fields.tsx +44 -0
- package/src/components/patterns/List.tsx +25 -0
- package/src/components/patterns/StateSurface.tsx +40 -0
- package/src/components/patterns/Surfaces.tsx +115 -0
- package/src/components/patterns/Tabs.tsx +80 -0
- package/src/components/patterns/Toolbar.tsx +51 -0
- package/src/components/patterns/Tree.tsx +72 -0
- package/src/components/primitives/Button.tsx +46 -0
- package/src/components/primitives/ColorInput.tsx +11 -47
- package/src/components/primitives/EditorIcon.tsx +99 -0
- package/src/components/primitives/FormControls.tsx +71 -0
- package/src/components/primitives/JsonInput.tsx +15 -2
- package/src/components/primitives/Layout.tsx +100 -0
- package/src/components/primitives/Menu.tsx +105 -0
- package/src/components/primitives/NumberInput.tsx +8 -35
- package/src/components/primitives/Panel.tsx +49 -44
- package/src/components/primitives/SectionHeader.tsx +10 -28
- package/src/components/primitives/Text.tsx +46 -0
- package/src/components/primitives/Tooltip.tsx +20 -12
- package/src/components/primitives/Vec3Input.tsx +3 -0
- package/src/components/primitives/banner-tones.ts +33 -0
- package/src/components/primitives/editor-icons.ts +102 -0
- package/src/components/primitives/panel-header-styles.ts +30 -0
- package/src/components/project-operation-documents.tsx +226 -0
- package/src/components/status-contributions.tsx +175 -0
- package/src/components/story-documents-register.ts +15 -0
- package/src/components/story-documents.tsx +1185 -0
- package/src/components/tool-documents.tsx +110 -0
- package/src/components/workspace-dock.css +293 -0
- package/src/components/workspace-history.ts +33 -0
- package/src/components/world-documents.tsx +145 -0
- package/src/components/xstate-documents.tsx +209 -0
- package/src/composite-screenshot.ts +248 -0
- package/src/design-system/index.ts +70 -0
- package/src/design-system-stories/Foundation.stories.tsx +183 -0
- package/src/design-system-stories/Patterns.stories.tsx +251 -0
- package/src/design-system-stories/Workspace.stories.tsx +538 -0
- package/src/doctor/report.ts +57 -13
- package/src/editor-api.ts +295 -31
- package/src/editor-compatibility.ts +200 -0
- package/src/editor-console.ts +35 -17
- package/src/editor-hotkeys.ts +85 -33
- package/src/editor-store.ts +538 -233
- package/src/editor-viewport.ts +160 -34
- package/src/entity-factory.ts +14 -3
- package/src/gizmo-registry.ts +21 -5
- package/src/harness-chat-editor-context.ts +37 -0
- package/src/harness-chat-indicator.ts +68 -0
- package/src/harness-chat-layout.ts +53 -0
- package/src/harness-chat-lifecycle.ts +170 -0
- package/src/harness-chat-reconnect.ts +85 -0
- package/src/harness-chat-types.ts +409 -0
- package/src/hierarchy-drop.ts +50 -0
- package/src/hierarchy-expansion-state.ts +100 -0
- package/src/hierarchy-menu-registry.ts +1 -1
- package/src/hierarchy-projection.ts +76 -0
- package/src/history/editor-session.ts +21 -0
- package/src/history/history-commands.ts +147 -0
- package/src/history/history-service.ts +904 -0
- package/src/history/json-history-resource.ts +165 -0
- package/src/history/persistence-coordinator.ts +35 -0
- package/src/history/project-file-history.ts +266 -0
- package/src/history/project-root-history-backends.ts +53 -0
- package/src/history/resource-registry.ts +209 -0
- package/src/history/snapshot-store.ts +108 -0
- package/src/history/source-history-backend.ts +374 -0
- package/src/history/types.ts +100 -0
- package/src/hmr-registration-group.ts +67 -0
- package/src/hmr-stable-react-context.ts +23 -0
- package/src/hosted-example.ts +12 -2
- package/src/hotkeys.ts +89 -18
- package/src/ingest/discovery-react.ts +9 -10
- package/src/ingest/discovery.ts +4 -4
- package/src/ingest/discovery2d.ts +5 -5
- package/src/ingest/games/bloom-composer/vgai.game.json +3 -3
- package/src/ingest/games/embed-bundle/vgai.game.json +3 -3
- package/src/ingest/games/games-fps/vgai.game.json +3 -3
- package/src/ingest/games/iframe-reachable/vgai.game.json +3 -3
- package/src/ingest/games/interactive-cubes/vgai.game.json +3 -3
- package/src/ingest/games/mismatched-three/vgai.game.json +3 -3
- package/src/ingest/games/own-three-bundled/vgai.game.json +3 -3
- package/src/ingest/games/pointerlock-maze/vgai.game.json +7 -4
- package/src/ingest/games-2d/bubbo-bubbo/vgai.game.json +3 -3
- package/src/ingest/games-2d/bunnymark/vgai.game.json +3 -3
- package/src/ingest/games-2d/flappy/vgai.game.json +3 -3
- package/src/ingest/games-2d/flappy-pixi/vgai.game.json +3 -3
- package/src/ingest/games-2d/puzzling-potions/vgai.game.json +3 -3
- package/src/ingest/games-2d/tilemap-camera/vgai.game.json +3 -3
- package/src/ingest/games-react/react-rpg/vgai.game.json +3 -3
- package/src/ingest/types.ts +1 -1
- package/src/ingest-mode.ts +47 -52
- package/src/ingest-siblings.ts +23 -33
- package/src/initial-scene.ts +1 -2
- package/src/main.tsx +16 -8
- package/src/manifest-project.ts +10 -12
- package/src/model-thumbnail.ts +261 -26
- package/src/module-mode.ts +44 -50
- package/src/play-log-events.ts +26 -0
- package/src/play-mode.ts +495 -401
- package/src/play-scene-adoption.ts +23 -0
- package/src/playground/main.ts +68 -24
- package/src/playwright-shim.ts +473 -0
- package/src/project-manager.ts +90 -10
- package/src/project-operations.ts +112 -0
- package/src/project-story-discovery.ts +25 -0
- package/src/project-tool-discovery.ts +43 -0
- package/src/scene-serializer.ts +0 -6
- package/src/scene-sync.ts +7 -2
- package/src/script-loader.ts +3 -3
- package/src/skeleton-helper.ts +57 -0
- package/src/storage/seed.ts +68 -8
- package/src/stories/StoryPreviewMount.tsx +265 -0
- package/src/stories/action-stories.fixture.tsx +44 -0
- package/src/stories/button-stories.fixture.tsx +50 -0
- package/src/stories/compose-project-stories.ts +182 -0
- package/src/stories/loader-stories.fixture.tsx +53 -0
- package/src/stories/story-discovery.ts +130 -0
- package/src/stories/story-registry.ts +146 -0
- package/src/theme-css.ts +1049 -0
- package/src/theme-library.ts +238 -0
- package/src/theme-preference.ts +130 -0
- package/src/theme.ts +527 -0
- package/src/tool-loader.ts +80 -24
- package/src/ui-source/inspect.ts +27 -4
- package/src/ui-source/source-write-backend.ts +83 -22
- package/src/widgets/index.ts +9 -18
- package/src/workspace-aux-commands.ts +11 -0
- package/src/workspace-dock-controller.ts +64 -0
- package/src/workspace-document-ids.ts +5 -0
- package/src/workspace-document-registry.ts +367 -0
- package/src/workspace-state-persistence.ts +455 -0
- package/src/workspace-status-registry.ts +110 -0
- package/src/workspace-utility-commands.ts +49 -0
- package/src/workspace-utility-registry.ts +161 -0
- package/src/world-document-routing.ts +16 -0
- package/src/xstate-inspect/XStateMachineInspector.tsx +544 -0
- package/src/xstate-inspect/character-animation-machine.fixture.ts +74 -0
- package/src/xstate-inspect/use-live-actor-state.ts +39 -0
- package/src/xstate-inspect/xstate-graph.ts +265 -0
- package/src/xstate-inspect/xstate-layout.ts +76 -0
- package/src/z-index.ts +26 -0
- package/template/.claude/skills/editor/SKILL.md +129 -90
- package/template/AGENTS.md +1110 -0
- package/template/CLAUDE.md +1 -512
- package/template/IDIOMS.md +50 -0
- package/template/check-idioms.ts +706 -0
- package/template/manifest-entry-modules-plugin.ts +53 -0
- package/template/package.json +13 -4
- package/template/public/scenes/default.scn2d.json +6 -0
- package/template/public/scenes/default.vscn.json +7 -1
- package/template/public/scenes/untitled.vscn.json +10 -0
- package/template/scripts/playtest.ts +40 -0
- package/template/server/loopback.ts +75 -0
- package/template/server/rooms/arena-room.ts +69 -1
- package/template/server/rooms/game-room.ts +68 -1
- package/template/src/data/README.md +9 -4
- package/template/src/data/data-ref.ts +35 -0
- package/template/src/data/tuning.schema.ts +1 -1
- package/template/src/main.ts +41 -62
- package/template/src/manifest-entry-modules.d.ts +3 -0
- package/template/src/operations/README.md +88 -0
- package/template/src/runtime/pixi-adapter.ts +24 -0
- package/template/src/scripts/components/data-spinner.ts +43 -2
- package/template/src/scripts/components/spin-lap-math.ts +27 -0
- package/template/src/tools/README.md +5 -3
- package/template/src/tools/example.tool.tsx +13 -7
- package/template/src/ui/Button.stories.tsx +66 -0
- package/template/src/ui/RebindPanel.tsx +2 -6
- package/template/src/ui/game.tsx +17 -0
- package/template/tests/acceptance/example.spec.ts +64 -0
- package/template/tests/logic/example.test.ts +31 -0
- package/template/tests/logic/room.test.ts +100 -0
- package/template/tests/tsconfig.json +30 -0
- package/template/validate-manifest.ts +5 -5
- package/template/validate-scenes.ts +50 -18
- package/template/vgai.game.json +20 -13
- package/template/vite.config.ts +77 -16
- package/vite-plugin-ui-oid.ts +261 -17
- package/dist/assets/CCDIKSolver-Dz-ysxZF.js +0 -1
- package/dist/assets/SelectionBox-BXsv_6aH.js +0 -1
- package/dist/assets/browser-project-scripts-DOru4L1x.js +0 -2
- package/dist/assets/browserAll-CEkWYlFk.js +0 -1
- package/dist/assets/create-runtime-DYIWr3va.js +0 -1
- package/dist/assets/data-asset-Bb9sIM7i.js +0 -5
- package/dist/assets/game-8SDZPPgH.js +0 -1
- package/dist/assets/game-CevorxyV.js +0 -1
- package/dist/assets/game-Di7XlywD.js +0 -1
- package/dist/assets/game-DjnCaw8R.js +0 -1
- package/dist/assets/game-Q9A0aXSV.js +0 -1
- package/dist/assets/index-CPVV6pjw.js +0 -1
- package/dist/assets/index-CpOnYyeD.js +0 -213
- package/dist/assets/index-Dr8H_6Rl.js +0 -1
- package/dist/assets/index-_bCaipUk.js +0 -13650
- package/dist/assets/ingest-siblings-Dz8eEqrV.js +0 -1
- package/dist/assets/mount-manifest-Bhk0ohsl.js +0 -1
- package/dist/assets/scene-query-BivAHhJu.js +0 -1
- package/dist/assets/webworkerAll-BLrH0F6F.js +0 -1
- package/dist/assets/xstate-animation-binding-1NGLmw6y.js +0 -1
- package/dist/assets/xstate.esm-BgUD33nl.js +0 -3
- package/src/authoring/scene-ui-layer.ts +0 -485
- package/src/authoring/ui-authoring-adapter.ts +0 -325
- package/src/authoring/ui-hierarchy-menu.ts +0 -40
- package/src/authoring/ui-inspector-sections.tsx +0 -156
- package/src/authoring/undo-timeline.ts +0 -145
- package/src/components/BottomPanel.tsx +0 -326
- package/src/components/BuildPanel.tsx +0 -455
- package/src/components/FolderTree.tsx +0 -179
- package/src/components/RightPanel.tsx +0 -166
- package/src/components/WorldContextMenu.tsx +0 -257
- package/src/components/asset-viewers/AssetViewer.tsx +0 -38
- package/src/ingest/games-react/react-rpg/src/ui/game-state.tsx +0 -129
- package/src/scene-ui-mode/active-scene-ui.ts +0 -58
- package/src/scene-ui-mode/scene-ui-palette-panel.tsx +0 -152
- package/src/scene-ui-mode/scene-ui-timeline-tab.tsx +0 -29
- package/src/scene-ui-mode/ui-prefab-dialog-host.tsx +0 -74
- package/src/scene-ui-mode/ui-prefab-hierarchy-menu.ts +0 -49
- package/src/scene-ui-mode/ui-prefab-io.ts +0 -76
- package/src/ui-editor/inspector.tsx +0 -563
- package/src/ui-editor/node-ops.ts +0 -266
- package/src/ui-editor/overlay.tsx +0 -380
- package/src/ui-editor/palette.tsx +0 -114
- package/src/ui-editor/react-store.ts +0 -78
- package/src/ui-editor/timeline-panel.tsx +0 -443
- package/src/ui-editor/ui-edit-store.ts +0 -552
- package/src/ui-editor/ui-prefab.ts +0 -95
- package/template/project.json +0 -9
- package/template/src/scripts/main.ts +0 -94
- package/template/src/ui/DefaultHud.tsx +0 -40
- package/template/src/ui/game-state.tsx +0 -37
- package/template/src/ui/mount-default-hud.tsx +0 -19
- package/template/src/ui/mount-ui.ts +0 -15
- package/vite-plugin-react-world-provider.ts +0 -81
|
@@ -0,0 +1,585 @@
|
|
|
1
|
+
import { Button, Menu, MenuItem, themeVars } from '../design-system';
|
|
2
|
+
/**
|
|
3
|
+
* W1 (docs/EDITOR-WORKSPACE-SHELL-TODO.md §9/W1) — the first two workspace
|
|
4
|
+
* DOCUMENT implementers (§7.1) and the bridge that keeps the document
|
|
5
|
+
* registry and the editor store's `activeViewportTab` agreeing:
|
|
6
|
+
*
|
|
7
|
+
* - the SCENE document (`workspace:scene`): the active Three scene, titled
|
|
8
|
+
* with its source-owned name (§8 — `Default.vscn`, never `Viewport`),
|
|
9
|
+
* non-closeable (there is always a center document), content =
|
|
10
|
+
* `ViewportPanel` (which keeps owning the renderer/RAF/hotkeys — its
|
|
11
|
+
* overlays ToolStrip/ViewportOverlay/CameraInfo/WorldSelectionOverlay
|
|
12
|
+
* move WITH it into the document, per inventory rows V5–V7/V11);
|
|
13
|
+
* - the GAME document (`workspace:game`): the live player-facing composite,
|
|
14
|
+
* titled `Game` (§8), pinned, always open, and always the leftmost anchor.
|
|
15
|
+
* While stopped it shows an explicit empty state rather than stale runtime
|
|
16
|
+
* pixels. Dockview renders it with
|
|
17
|
+
* `renderer: "always"`, so its canvas container exists before Play's
|
|
18
|
+
* asynchronous setup and survives tab/group changes.
|
|
19
|
+
*
|
|
20
|
+
* THE INPUT-GATE CONTRACT (CLAUDE.md T6.3): `store.activeViewportTab`
|
|
21
|
+
* remains the single source of truth that `play-mode.ts`/`ingest-mode.ts`
|
|
22
|
+
* read (`playState === 'playing' && activeViewportTab === 'game'`) — this
|
|
23
|
+
* module NEVER replaces that predicate, it mirrors registry activation into
|
|
24
|
+
* it (descriptor `onActivate` → `setActiveViewportTab`) and store changes
|
|
25
|
+
* back into registry activation (`syncCenterDocuments`, run on every store
|
|
26
|
+
* notify). Play auto-switch (enter play → Game tab, stop → Scene tab) stays
|
|
27
|
+
* store-driven (`setPlayState`), so `vgai select-tab`-style control commands
|
|
28
|
+
* (`command-listener.ts`'s `set-viewport-tab`) and every ingest/module-mode
|
|
29
|
+
* `setActiveViewportTab` call keep working unchanged.
|
|
30
|
+
*
|
|
31
|
+
* Lifecycle: `enterPlayMode` requires the game container to exist BEFORE
|
|
32
|
+
* play starts (`setGameContainer`). The pinned document plus Dockview's
|
|
33
|
+
* always-rendered panel mode is the one continuous React instance that
|
|
34
|
+
* satisfies that requirement.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
import {
|
|
38
|
+
Component,
|
|
39
|
+
type ErrorInfo,
|
|
40
|
+
type ReactNode,
|
|
41
|
+
useCallback,
|
|
42
|
+
useEffect,
|
|
43
|
+
useRef,
|
|
44
|
+
useState,
|
|
45
|
+
useSyncExternalStore,
|
|
46
|
+
} from 'react';
|
|
47
|
+
import { DisclosureIcon } from '../design-system';
|
|
48
|
+
import { useEditorStore } from '../EditorContext';
|
|
49
|
+
import { editorConsole } from '../editor-console';
|
|
50
|
+
import { getGameProfiler, resizeGame, setGameContainer } from '../play-mode';
|
|
51
|
+
import { GAME_DOCUMENT_ID, SCENE_DOCUMENT_ID } from '../workspace-document-ids';
|
|
52
|
+
import {
|
|
53
|
+
activateWorkspaceDocument,
|
|
54
|
+
activeWorkspaceDocumentId,
|
|
55
|
+
closeAllWorkspaceDocuments,
|
|
56
|
+
openWorkspaceDocument,
|
|
57
|
+
openWorkspaceDocuments,
|
|
58
|
+
setWorkspaceDocumentDirty,
|
|
59
|
+
setWorkspaceDocumentTitle,
|
|
60
|
+
type WorkspaceDocumentContentProps,
|
|
61
|
+
type WorkspaceDocumentDescriptor,
|
|
62
|
+
} from '../workspace-document-registry';
|
|
63
|
+
import { openDebuggerPanel } from './debugger-panel-state';
|
|
64
|
+
import { GamePerformanceOverlay } from './GamePerformanceOverlay';
|
|
65
|
+
import { MuteButton, type Resolution, ResolutionPicker } from './PlayBar';
|
|
66
|
+
import { ViewportPanel } from './ViewportPanel';
|
|
67
|
+
|
|
68
|
+
/** Stable identity (§7.1) of the single active-scene center document. The
|
|
69
|
+
* SLOT is stable while the scene it shows can change (root activation,
|
|
70
|
+
* prefab edit) — the tab's title tracks `store.savePath` live via
|
|
71
|
+
* `setWorkspaceDocumentTitle`, keeping "there is always a center document"
|
|
72
|
+
* true without close/reopen churn. */
|
|
73
|
+
export { GAME_DOCUMENT_ID, SCENE_DOCUMENT_ID } from '../workspace-document-ids';
|
|
74
|
+
/** Stable identity (§7.1) of the Game preview document. */
|
|
75
|
+
|
|
76
|
+
/** §8 source-owned tab title for the active scene document: the file's own
|
|
77
|
+
* basename with the serialization suffix dropped (`scenes/Default.vscn.json`
|
|
78
|
+
* → `Default.vscn`, `x/Crate.prefab.json` → `Crate.prefab`) — matching the
|
|
79
|
+
* TODO's §4.1 mock. `Untitled` only when no source file exists yet. */
|
|
80
|
+
export function sceneDocumentTitle(savePath: string | null): string {
|
|
81
|
+
if (!savePath) return 'Untitled';
|
|
82
|
+
const base = savePath.split('/').pop() ?? savePath;
|
|
83
|
+
return base.endsWith('.json') ? base.slice(0, -'.json'.length) : base;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** The narrow store surface this module reads/writes — kept minimal so the
|
|
87
|
+
* bridge is unit-testable against a plain fake (no full EditorStore). */
|
|
88
|
+
export interface CenterDocumentsStore {
|
|
89
|
+
subscribe(listener: () => void): () => void;
|
|
90
|
+
readonly playState: 'stopped' | 'playing' | 'paused';
|
|
91
|
+
readonly activeViewportTab: 'scene' | 'game';
|
|
92
|
+
readonly savePath: string | null;
|
|
93
|
+
readonly isDirty: boolean;
|
|
94
|
+
setActiveViewportTab(tab: 'scene' | 'game'): void;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
interface ViewportErrorBoundaryState {
|
|
98
|
+
error: Error | null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** A renderer failure must degrade the Scene document, not unmount the whole
|
|
102
|
+
* Dockview workspace. The surrounding hierarchy, Inspector, Console, menus,
|
|
103
|
+
* and project resources remain usable, with an explicit retry after the old
|
|
104
|
+
* renderer/context has been torn down. */
|
|
105
|
+
export class ViewportErrorBoundary extends Component<
|
|
106
|
+
{ children: ReactNode },
|
|
107
|
+
ViewportErrorBoundaryState
|
|
108
|
+
> {
|
|
109
|
+
override state: ViewportErrorBoundaryState = { error: null };
|
|
110
|
+
|
|
111
|
+
static getDerivedStateFromError(error: Error): ViewportErrorBoundaryState {
|
|
112
|
+
return { error };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
override componentDidCatch(error: Error, info: ErrorInfo): void {
|
|
116
|
+
editorConsole.error(`Viewport failed to start: ${error.message}`, 'editor');
|
|
117
|
+
// Keep the component stack in the browser console for developer diagnosis.
|
|
118
|
+
// biome-ignore lint/suspicious/noConsole: renderer startup failures need their React component stack in developer tools as well as the user-facing editor Console entry
|
|
119
|
+
console.error('[vgai] Viewport failed to start', error, info.componentStack);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
override render() {
|
|
123
|
+
if (!this.state.error) return this.props.children;
|
|
124
|
+
return (
|
|
125
|
+
<div
|
|
126
|
+
data-testid="viewport-startup-error"
|
|
127
|
+
role="alert"
|
|
128
|
+
style={{
|
|
129
|
+
position: 'absolute',
|
|
130
|
+
inset: 0,
|
|
131
|
+
display: 'flex',
|
|
132
|
+
flexDirection: 'column',
|
|
133
|
+
alignItems: 'center',
|
|
134
|
+
justifyContent: 'center',
|
|
135
|
+
gap: 8,
|
|
136
|
+
padding: 24,
|
|
137
|
+
background: themeVars.surface.shell,
|
|
138
|
+
color: themeVars.content.muted,
|
|
139
|
+
textAlign: 'center',
|
|
140
|
+
}}
|
|
141
|
+
>
|
|
142
|
+
<div style={{ color: themeVars.content.primary, fontSize: 14 }}>Viewport unavailable</div>
|
|
143
|
+
<div style={{ maxWidth: 520, color: themeVars.content.dim, fontSize: 11 }}>
|
|
144
|
+
{this.state.error.message}
|
|
145
|
+
</div>
|
|
146
|
+
<Button
|
|
147
|
+
type="button"
|
|
148
|
+
className="vgai-btn"
|
|
149
|
+
data-testid="retry-viewport"
|
|
150
|
+
onClick={() => this.setState({ error: null })}
|
|
151
|
+
>
|
|
152
|
+
Retry Viewport
|
|
153
|
+
</Button>
|
|
154
|
+
</div>
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function SceneDocumentContent(_props: WorkspaceDocumentContentProps) {
|
|
160
|
+
return (
|
|
161
|
+
<ViewportErrorBoundary>
|
|
162
|
+
<ViewportPanel />
|
|
163
|
+
</ViewportErrorBoundary>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// --- Game document view state (W2, inventory row V4) -----------------------
|
|
168
|
+
// Resolution/scale are shared between the Game document's CONTENT (which
|
|
169
|
+
// owns the canvas resize/scale math) and its document-local TOOLBAR (which
|
|
170
|
+
// hosts the picker + mute, §6.3 "Game mute/resolution controls appear only
|
|
171
|
+
// in the Game document") — a tiny module-scope store in the house
|
|
172
|
+
// `useSyncExternalStore` shape, session-scoped like the old GamePanel state.
|
|
173
|
+
|
|
174
|
+
let _gameResolution: Resolution = { label: 'Fill', width: null, height: null };
|
|
175
|
+
let _gameScale = 1;
|
|
176
|
+
let _gameStatsVisible = false;
|
|
177
|
+
let _gameViewVersion = 0;
|
|
178
|
+
const _gameViewListeners = new Set<() => void>();
|
|
179
|
+
|
|
180
|
+
function notifyGameView(): void {
|
|
181
|
+
_gameViewVersion++;
|
|
182
|
+
for (const fn of _gameViewListeners) fn();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function subscribeGameView(fn: () => void): () => void {
|
|
186
|
+
_gameViewListeners.add(fn);
|
|
187
|
+
return () => {
|
|
188
|
+
_gameViewListeners.delete(fn);
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const gameViewVersion = () => _gameViewVersion;
|
|
193
|
+
|
|
194
|
+
/** The Game document's current resolution selection (exported for tests). */
|
|
195
|
+
export function gameDocumentResolution(): Resolution {
|
|
196
|
+
return _gameResolution;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** Set the Game document's resolution (exported for tests). */
|
|
200
|
+
export function setGameDocumentResolution(resolution: Resolution): void {
|
|
201
|
+
_gameResolution = resolution;
|
|
202
|
+
notifyGameView();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function gameStatsVisible(): boolean {
|
|
206
|
+
return _gameStatsVisible;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function setGameStatsVisible(visible: boolean): void {
|
|
210
|
+
if (_gameStatsVisible === visible) return;
|
|
211
|
+
_gameStatsVisible = visible;
|
|
212
|
+
if (visible) {
|
|
213
|
+
const profiler = getGameProfiler();
|
|
214
|
+
if (profiler) profiler.enabled = true;
|
|
215
|
+
}
|
|
216
|
+
notifyGameView();
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function setGameScale(scale: number): void {
|
|
220
|
+
if (scale === _gameScale) return;
|
|
221
|
+
_gameScale = scale;
|
|
222
|
+
notifyGameView();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function GameStatsMenu() {
|
|
226
|
+
const [open, setOpen] = useState(false);
|
|
227
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
228
|
+
useEffect(() => {
|
|
229
|
+
if (!open) return;
|
|
230
|
+
const onPointerDown = (event: PointerEvent) => {
|
|
231
|
+
if (!ref.current?.contains(event.target as Node)) setOpen(false);
|
|
232
|
+
};
|
|
233
|
+
document.addEventListener('pointerdown', onPointerDown);
|
|
234
|
+
return () => document.removeEventListener('pointerdown', onPointerDown);
|
|
235
|
+
}, [open]);
|
|
236
|
+
return (
|
|
237
|
+
<div ref={ref} style={{ position: 'relative' }}>
|
|
238
|
+
<Button
|
|
239
|
+
type="button"
|
|
240
|
+
variant="outline"
|
|
241
|
+
size="compact"
|
|
242
|
+
data-testid="game-stats-menu"
|
|
243
|
+
aria-expanded={open}
|
|
244
|
+
onClick={() => setOpen((value) => !value)}
|
|
245
|
+
>
|
|
246
|
+
<span>Stats</span>
|
|
247
|
+
<DisclosureIcon direction="down" />
|
|
248
|
+
</Button>
|
|
249
|
+
{open && (
|
|
250
|
+
<Menu
|
|
251
|
+
style={{
|
|
252
|
+
position: 'absolute',
|
|
253
|
+
right: 0,
|
|
254
|
+
top: 'calc(100% + 3px)',
|
|
255
|
+
zIndex: 20,
|
|
256
|
+
minWidth: 190,
|
|
257
|
+
}}
|
|
258
|
+
>
|
|
259
|
+
<MenuItem onClick={() => setGameStatsVisible(!_gameStatsVisible)}>
|
|
260
|
+
{_gameStatsVisible ? '✓ ' : ' '}Show live overlay
|
|
261
|
+
</MenuItem>
|
|
262
|
+
<MenuItem
|
|
263
|
+
onClick={() => {
|
|
264
|
+
const profiler = getGameProfiler();
|
|
265
|
+
if (profiler) {
|
|
266
|
+
const recording = profiler.getSnapshot().recording;
|
|
267
|
+
recording ? profiler.stopRecording() : profiler.startRecording();
|
|
268
|
+
notifyGameView();
|
|
269
|
+
}
|
|
270
|
+
}}
|
|
271
|
+
>
|
|
272
|
+
{getGameProfiler()?.getSnapshot().recording ? 'Stop capture' : 'Start capture'}
|
|
273
|
+
</MenuItem>
|
|
274
|
+
<MenuItem onClick={() => openDebuggerPanel('profiler')}>Open Profiler</MenuItem>
|
|
275
|
+
<MenuItem onClick={() => openDebuggerPanel('network')}>Open Network Diagnostics</MenuItem>
|
|
276
|
+
</Menu>
|
|
277
|
+
)}
|
|
278
|
+
</div>
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* The Game document's LOCAL toolbar (§7.1 toolbar contribution, rendered by
|
|
284
|
+
* `WorkspaceDock` inside the Game document only while that panel is
|
|
285
|
+
* active): resolution picker + mute — the V4 remainder deferred from W1.
|
|
286
|
+
* Transport (play/pause/stop) deliberately stays in the global `PlayBar`.
|
|
287
|
+
*/
|
|
288
|
+
export function GameDocumentToolbar(_props: WorkspaceDocumentContentProps) {
|
|
289
|
+
useSyncExternalStore(subscribeGameView, gameViewVersion);
|
|
290
|
+
return (
|
|
291
|
+
<div
|
|
292
|
+
data-testid="game-doc-toolbar"
|
|
293
|
+
style={{
|
|
294
|
+
display: 'flex',
|
|
295
|
+
alignItems: 'center',
|
|
296
|
+
justifyContent: 'flex-end',
|
|
297
|
+
gap: 6,
|
|
298
|
+
padding: '2px 8px',
|
|
299
|
+
}}
|
|
300
|
+
>
|
|
301
|
+
<ResolutionPicker
|
|
302
|
+
resolution={_gameResolution}
|
|
303
|
+
onResolutionChange={setGameDocumentResolution}
|
|
304
|
+
scale={_gameScale}
|
|
305
|
+
title="Change game resolution"
|
|
306
|
+
/>
|
|
307
|
+
<MuteButton />
|
|
308
|
+
<GameStatsMenu />
|
|
309
|
+
</div>
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/** Game panel — a container div that play-mode.ts fills with a canvas.
|
|
314
|
+
* (Moved verbatim from `DefaultEditorLayout.tsx` in W1 — inventory row V3.) */
|
|
315
|
+
function GamePanel({ style }: { style?: React.CSSProperties }) {
|
|
316
|
+
const store = useEditorStore();
|
|
317
|
+
const isRunning = store.playState !== 'stopped';
|
|
318
|
+
const outerRef = useRef<HTMLDivElement>(null);
|
|
319
|
+
// W2 (V4): resolution comes from the shared game-view store above — the
|
|
320
|
+
// document-local toolbar writes it, this content applies it.
|
|
321
|
+
useSyncExternalStore(subscribeGameView, gameViewVersion);
|
|
322
|
+
const resolution = _gameResolution;
|
|
323
|
+
const [containerSize, setContainerSize] = useState({ width: 0, height: 0 });
|
|
324
|
+
|
|
325
|
+
// Callback ref so setGameContainer is called even when the element swaps (fill ↔ device)
|
|
326
|
+
const prevRef = useRef<HTMLDivElement | null>(null);
|
|
327
|
+
const canvasRef = useCallback((el: HTMLDivElement | null) => {
|
|
328
|
+
if (el === prevRef.current) return;
|
|
329
|
+
// Move game canvas to new container
|
|
330
|
+
if (prevRef.current && el) {
|
|
331
|
+
while (prevRef.current.firstChild) el.appendChild(prevRef.current.firstChild);
|
|
332
|
+
}
|
|
333
|
+
prevRef.current = el;
|
|
334
|
+
setGameContainer(el);
|
|
335
|
+
}, []);
|
|
336
|
+
|
|
337
|
+
// Track outer container size for scale calculation
|
|
338
|
+
useEffect(() => {
|
|
339
|
+
const el = outerRef.current;
|
|
340
|
+
if (!el) return;
|
|
341
|
+
const observer = new ResizeObserver(([entry]) => {
|
|
342
|
+
if (!entry) return;
|
|
343
|
+
setContainerSize({ width: entry.contentRect.width, height: entry.contentRect.height });
|
|
344
|
+
});
|
|
345
|
+
observer.observe(el);
|
|
346
|
+
return () => observer.disconnect();
|
|
347
|
+
}, []);
|
|
348
|
+
|
|
349
|
+
// Resize the game renderer when resolution or container size changes
|
|
350
|
+
const isFill = resolution.width === null;
|
|
351
|
+
useEffect(() => {
|
|
352
|
+
if (isFill) {
|
|
353
|
+
// Fill mode: resize to match available container space
|
|
354
|
+
if (containerSize.width > 0 && containerSize.height > 0) {
|
|
355
|
+
resizeGame(containerSize.width, containerSize.height);
|
|
356
|
+
}
|
|
357
|
+
} else {
|
|
358
|
+
resizeGame(resolution.width!, resolution.height!);
|
|
359
|
+
}
|
|
360
|
+
}, [isFill, resolution.width, resolution.height, containerSize.width, containerSize.height]);
|
|
361
|
+
|
|
362
|
+
// Scale: fit the device resolution into the available space, never upscale
|
|
363
|
+
let scale = 1;
|
|
364
|
+
if (!isFill && containerSize.width > 0 && containerSize.height > 0) {
|
|
365
|
+
const scaleX = containerSize.width / resolution.width!;
|
|
366
|
+
const scaleY = containerSize.height / resolution.height!;
|
|
367
|
+
scale = Math.min(scaleX, scaleY, 1);
|
|
368
|
+
}
|
|
369
|
+
// Publish for the toolbar's percentage readout (effect: setState-in-render
|
|
370
|
+
// on ANOTHER component's store is a React error; post-commit is correct).
|
|
371
|
+
useEffect(() => {
|
|
372
|
+
setGameScale(scale);
|
|
373
|
+
}, [scale]);
|
|
374
|
+
|
|
375
|
+
return (
|
|
376
|
+
<div style={{ overflow: 'hidden', position: 'relative', flexDirection: 'column', ...style }}>
|
|
377
|
+
<div
|
|
378
|
+
ref={outerRef}
|
|
379
|
+
style={{
|
|
380
|
+
flex: 1,
|
|
381
|
+
overflow: 'hidden',
|
|
382
|
+
display: 'flex',
|
|
383
|
+
alignItems: 'center',
|
|
384
|
+
justifyContent: 'center',
|
|
385
|
+
position: 'relative',
|
|
386
|
+
}}
|
|
387
|
+
>
|
|
388
|
+
{isFill ? (
|
|
389
|
+
<div
|
|
390
|
+
ref={canvasRef}
|
|
391
|
+
data-testid="game-runtime-container"
|
|
392
|
+
aria-hidden="true"
|
|
393
|
+
style={{ position: 'absolute', inset: 0, visibility: isRunning ? 'visible' : 'hidden' }}
|
|
394
|
+
/>
|
|
395
|
+
) : (
|
|
396
|
+
/* Wrapper sized to the scaled dimensions so flexbox centering works.
|
|
397
|
+
The inner canvasRef is full device resolution, CSS-scaled down. */
|
|
398
|
+
<div
|
|
399
|
+
style={{
|
|
400
|
+
width: resolution.width! * scale,
|
|
401
|
+
height: resolution.height! * scale,
|
|
402
|
+
overflow: 'hidden',
|
|
403
|
+
visibility: isRunning ? 'visible' : 'hidden',
|
|
404
|
+
}}
|
|
405
|
+
>
|
|
406
|
+
<div
|
|
407
|
+
ref={canvasRef}
|
|
408
|
+
data-testid="game-runtime-container"
|
|
409
|
+
aria-hidden="true"
|
|
410
|
+
style={{
|
|
411
|
+
width: resolution.width!,
|
|
412
|
+
height: resolution.height!,
|
|
413
|
+
transformOrigin: 'top left',
|
|
414
|
+
transform: `scale(${scale})`,
|
|
415
|
+
}}
|
|
416
|
+
/>
|
|
417
|
+
</div>
|
|
418
|
+
)}
|
|
419
|
+
{!isRunning && (
|
|
420
|
+
<div
|
|
421
|
+
data-testid="game-stopped-placeholder"
|
|
422
|
+
style={{
|
|
423
|
+
position: 'absolute',
|
|
424
|
+
inset: 0,
|
|
425
|
+
display: 'flex',
|
|
426
|
+
flexDirection: 'column',
|
|
427
|
+
alignItems: 'center',
|
|
428
|
+
justifyContent: 'center',
|
|
429
|
+
gap: 6,
|
|
430
|
+
background: themeVars.surface.shell,
|
|
431
|
+
color: themeVars.content.dim,
|
|
432
|
+
fontSize: 11,
|
|
433
|
+
pointerEvents: 'none',
|
|
434
|
+
}}
|
|
435
|
+
>
|
|
436
|
+
<span style={{ color: themeVars.content.primary, fontSize: 13 }}>Game is stopped</span>
|
|
437
|
+
<span>Press Play to start the composed runtime.</span>
|
|
438
|
+
</div>
|
|
439
|
+
)}
|
|
440
|
+
{/* Same overlay ViewportPanel renders on the Scene tab (T-R1a #6) — the
|
|
441
|
+
Game tab is where play auto-switches to, so it must render here too,
|
|
442
|
+
not only on the Scene-tab viewport it would otherwise be hidden behind. */}
|
|
443
|
+
{store.playState !== 'stopped' && _gameStatsVisible && <GamePerformanceOverlay />}
|
|
444
|
+
</div>
|
|
445
|
+
{/* W4 (V4): the PlayBar copy that rendered here moved to the global
|
|
446
|
+
command header (`ProjectHeader.tsx`) — transport is now
|
|
447
|
+
document-independent; resolution/mute stay in this document's
|
|
448
|
+
local toolbar (`GameDocumentToolbar`). */}
|
|
449
|
+
</div>
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/** The Game document's content host. Rendered warm (hidden) even while the
|
|
454
|
+
* document is closed — see this file's header. */
|
|
455
|
+
function GameDocumentContent(_props: WorkspaceDocumentContentProps) {
|
|
456
|
+
const store = useEditorStore();
|
|
457
|
+
useSyncExternalStore(store.subscribe, store.getSnapshot);
|
|
458
|
+
const isPlaying = store.playState !== 'stopped';
|
|
459
|
+
return (
|
|
460
|
+
<GamePanel
|
|
461
|
+
style={{
|
|
462
|
+
flex: 1,
|
|
463
|
+
position: 'relative',
|
|
464
|
+
display: 'flex',
|
|
465
|
+
flexDirection: 'column',
|
|
466
|
+
pointerEvents: 'auto',
|
|
467
|
+
// L-20 — playing/paused edge treatment from the shared themeVars.semantic.success/themeVars.semantic.warning
|
|
468
|
+
// tokens (same "healthy"/"attention" semantic SaveStatus uses).
|
|
469
|
+
...(store.playState === 'playing'
|
|
470
|
+
? { outline: `2px solid ${themeVars.semantic.success}`, outlineOffset: -2 }
|
|
471
|
+
: isPlaying
|
|
472
|
+
? { outline: `2px solid ${themeVars.semantic.warning}`, outlineOffset: -2 }
|
|
473
|
+
: {}),
|
|
474
|
+
}}
|
|
475
|
+
/>
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/** Build the W1 document descriptors bound to a store. `onActivate` is the
|
|
480
|
+
* registry→store half of the bridge: activating a tab (click, open,
|
|
481
|
+
* close-neighbor fallback) writes the store's `activeViewportTab`, which is
|
|
482
|
+
* what the T6.3 input gate reads. */
|
|
483
|
+
export function createCenterDocumentDescriptors(store: CenterDocumentsStore): {
|
|
484
|
+
scene: WorkspaceDocumentDescriptor;
|
|
485
|
+
game: WorkspaceDocumentDescriptor;
|
|
486
|
+
} {
|
|
487
|
+
const scene: WorkspaceDocumentDescriptor = {
|
|
488
|
+
id: SCENE_DOCUMENT_ID,
|
|
489
|
+
title: sceneDocumentTitle(store.savePath),
|
|
490
|
+
kind: 'scene',
|
|
491
|
+
...(store.savePath ? { provenance: { sourcePath: store.savePath } } : {}),
|
|
492
|
+
Content: SceneDocumentContent,
|
|
493
|
+
closeable: false,
|
|
494
|
+
onActivate: () => store.setActiveViewportTab('scene'),
|
|
495
|
+
};
|
|
496
|
+
const game: WorkspaceDocumentDescriptor = {
|
|
497
|
+
id: GAME_DOCUMENT_ID,
|
|
498
|
+
title: 'Game',
|
|
499
|
+
kind: 'game',
|
|
500
|
+
Content: GameDocumentContent,
|
|
501
|
+
// W2 (V4): the document-local toolbar — resolution + mute appear only
|
|
502
|
+
// while the Game document is active (§6.3).
|
|
503
|
+
Toolbar: GameDocumentToolbar,
|
|
504
|
+
closeable: false,
|
|
505
|
+
onActivate: () => store.setActiveViewportTab('game'),
|
|
506
|
+
};
|
|
507
|
+
return { scene, game };
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* The store→registry half of the bridge. Idempotent — safe (and designed) to
|
|
512
|
+
* run on EVERY store notify; re-entrant calls triggered by its own
|
|
513
|
+
* `onActivate` → `setActiveViewportTab` → notify chain settle as no-ops.
|
|
514
|
+
*
|
|
515
|
+
* 1. the scene document is always open (opens it on first run);
|
|
516
|
+
* 2. its tab title/dirty dot track `savePath`/`isDirty` live;
|
|
517
|
+
* 3. the Game document is always open. Dockview's `renderer: "always"`
|
|
518
|
+
* preserves its canvas before play begins and across tab switches;
|
|
519
|
+
* 4. the active document mirrors `activeViewportTab` (auto-switch on
|
|
520
|
+
* play/stop, `set-viewport-tab` control commands, ingest-mode's direct
|
|
521
|
+
* writes). A `game` tab value with no Game document open (transient
|
|
522
|
+
* store states) falls back to the scene document — there is always an
|
|
523
|
+
* active center document.
|
|
524
|
+
*/
|
|
525
|
+
export function syncCenterDocuments(
|
|
526
|
+
store: CenterDocumentsStore,
|
|
527
|
+
docs: { scene: WorkspaceDocumentDescriptor; game: WorkspaceDocumentDescriptor },
|
|
528
|
+
): void {
|
|
529
|
+
const isOpen = (id: string) => openWorkspaceDocuments().some((d) => d.descriptor.id === id);
|
|
530
|
+
// Opening a registry document activates it. Capture the caller's requested
|
|
531
|
+
// viewport before ensuring the permanently mounted Game tab, then restore
|
|
532
|
+
// that intent below; merely making Game discoverable must not switch a
|
|
533
|
+
// stopped editor away from Scene.
|
|
534
|
+
const requestedViewportTab = store.activeViewportTab;
|
|
535
|
+
|
|
536
|
+
// Registry order is the canonical tab order for a fresh layout. Game is
|
|
537
|
+
// permanently first—like the player preview in established game editors—
|
|
538
|
+
// while Scene remains the initial active authoring document when stopped.
|
|
539
|
+
if (!isOpen(GAME_DOCUMENT_ID)) openWorkspaceDocument(docs.game);
|
|
540
|
+
if (!isOpen(SCENE_DOCUMENT_ID)) openWorkspaceDocument(docs.scene);
|
|
541
|
+
setWorkspaceDocumentTitle(SCENE_DOCUMENT_ID, sceneDocumentTitle(store.savePath));
|
|
542
|
+
setWorkspaceDocumentDirty(SCENE_DOCUMENT_ID, store.isDirty);
|
|
543
|
+
|
|
544
|
+
// Mirror the store tab into registry activation — W2-aware: since asset
|
|
545
|
+
// and story documents joined the center, a THIRD-party document being
|
|
546
|
+
// active is a legitimate state (its own onActivate already asserted the
|
|
547
|
+
// store's 'scene' tab, so the T6.3 gate is off). The mirror therefore only
|
|
548
|
+
// corrects genuine disagreements about the SCENE/GAME pair:
|
|
549
|
+
// - tab says 'game' (play auto-switch, `set-viewport-tab game`) → the
|
|
550
|
+
// Game document must be active;
|
|
551
|
+
// - tab says 'scene' while the GAME document is active (external
|
|
552
|
+
// `set-viewport-tab scene` during play) → back to the scene document;
|
|
553
|
+
// - no active document at all (first run) → the scene document.
|
|
554
|
+
// A 'scene' tab with an asset/story document active is CONSISTENT and must
|
|
555
|
+
// not be "corrected" — forcing the scene document here (the W1 behavior)
|
|
556
|
+
// would instantly steal the tab back from every non-Game document on the
|
|
557
|
+
// next store notify.
|
|
558
|
+
const activeId = activeWorkspaceDocumentId();
|
|
559
|
+
if (requestedViewportTab === 'game' && isOpen(GAME_DOCUMENT_ID)) {
|
|
560
|
+
if (activeId !== GAME_DOCUMENT_ID) activateWorkspaceDocument(GAME_DOCUMENT_ID);
|
|
561
|
+
} else if (activeId === GAME_DOCUMENT_ID || activeId === null) {
|
|
562
|
+
// Covers both the external scene-tab write during play AND the
|
|
563
|
+
// transient "tab says game but no Game document is open" fallback —
|
|
564
|
+
// there is always an active center document.
|
|
565
|
+
activateWorkspaceDocument(SCENE_DOCUMENT_ID);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Layout hook: installs the W1 center documents for this editor session and
|
|
571
|
+
* keeps them synced to the store. Teardown closes every workspace document
|
|
572
|
+
* (project switch / return to project screen), firing their dispose hooks.
|
|
573
|
+
*/
|
|
574
|
+
export function useCenterDocuments(store: CenterDocumentsStore): void {
|
|
575
|
+
useEffect(() => {
|
|
576
|
+
const docs = createCenterDocumentDescriptors(store);
|
|
577
|
+
const run = () => syncCenterDocuments(store, docs);
|
|
578
|
+
run();
|
|
579
|
+
const unsubscribe = store.subscribe(run);
|
|
580
|
+
return () => {
|
|
581
|
+
unsubscribe();
|
|
582
|
+
closeAllWorkspaceDocuments();
|
|
583
|
+
};
|
|
584
|
+
}, [store]);
|
|
585
|
+
}
|