@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
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
* Edit-mode authoring (A1, docs/unified-world-editor/26-unified-editor-spec.md
|
|
3
3
|
* §2 A1) — project open loads `vgai.game.json` and installs a
|
|
4
4
|
* {@link CompositeAuthoringAdapter} over EVERY declared world, so a
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* pattern this mirrors). Unlike play mode, worlds are NOT running here: only
|
|
5
|
+
* project's composition is disclosed in edit mode (#28). Unlike play mode,
|
|
6
|
+
* roots are NOT running here: only
|
|
8
7
|
* the FOCUSED threejs world is live (the existing `VgaiSceneAuthoringAdapter`
|
|
9
8
|
* over the store's already-loaded document — `loadInitialScene` is
|
|
10
9
|
* untouched, still the only thing that ever loads a scene into the store);
|
|
@@ -12,47 +11,30 @@
|
|
|
12
11
|
* describing it from the manifest, upgraded to a live adapter only once its
|
|
13
12
|
* design-time layer actually mounts (B1).
|
|
14
13
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* touch the shared `setActiveAuthoring` slot in that case. `getActiveAuthoring`'s
|
|
21
|
-
* existing default (`new VgaiSceneAuthoringAdapter(store)`, `active-adapter.ts`)
|
|
22
|
-
* keeps serving every call site that reads it (the viewport's
|
|
23
|
-
* transforms/selection, `SaveStatus`, `DefaultEditorLayout`'s hierarchy/
|
|
24
|
-
* inspector panel choice, the autosave guard, undo dispatch) completely
|
|
25
|
-
* untouched — `hasAuthoringOverride()` stays `false`, exactly as today. Only
|
|
26
|
-
* a GENUINE multi-world manifest (2+ declared worlds) installs the composite
|
|
27
|
-
* as the active override, the same "a composite is active -> route through
|
|
28
|
-
* the contract-driven panels" pattern `installMultiWorldAuthoring` already
|
|
29
|
-
* established for play mode (verified precedent: `19-multi-world.spec.ts` /
|
|
30
|
-
* `22-tri-world.spec.ts` assert on `ingest-row`/`ingest-position-*` testids
|
|
31
|
-
* once a composite is installed) — A2's `GameHierarchy` replaces that
|
|
32
|
-
* temporary routing wholesale; until then this is the honest, already-proven
|
|
33
|
-
* mechanism for disclosing a composition #28 says must not stay invisible.
|
|
14
|
+
* D19 completes the migration: a project with one world and a project with
|
|
15
|
+
* many roots use the SAME game/world authoring tree. Cardinality may affect
|
|
16
|
+
* presentation density, never hierarchy semantics, routing, or persistence.
|
|
17
|
+
* Manifest-less projects still synthesize the conventional `main` world, so
|
|
18
|
+
* every project reaches this one path.
|
|
34
19
|
*/
|
|
35
20
|
|
|
36
|
-
import type { AuthoringAdapter } from '@engine/adapter';
|
|
37
21
|
import { editorConsole } from '../editor-console';
|
|
38
22
|
import { getEditorMode } from '../editor-mode';
|
|
39
23
|
import type { EditorStore } from '../editor-store';
|
|
40
|
-
import {
|
|
24
|
+
import { hierarchyProjectionFromProjectConfig } from '../hierarchy-projection';
|
|
25
|
+
import { getProjectFileHistory, type ProjectFileHistory } from '../history/project-file-history';
|
|
26
|
+
import { getManifestHistoryBackend } from '../history/project-root-history-backends';
|
|
41
27
|
import { fetchRawGameManifest } from '../manifest-project';
|
|
42
28
|
import { getCurrentProject } from '../project-manager';
|
|
43
29
|
import { getStorageBackend } from '../storage';
|
|
44
|
-
import { UIEditStore } from '../ui-editor/ui-edit-store';
|
|
45
|
-
import { createHttpSourceWriteBackend } from '../ui-source/source-write-backend';
|
|
46
30
|
import { getAuthoringOverride, markEditModeOverride, setActiveAuthoring } from './active-adapter';
|
|
47
31
|
import { BoundaryAuthoringAdapter, type BoundaryWorldInfo } from './boundary-authoring-adapter';
|
|
48
32
|
import {
|
|
49
33
|
CompositeAuthoringAdapter,
|
|
50
34
|
type CompositeChild,
|
|
51
|
-
type
|
|
35
|
+
type RootManifestProvider,
|
|
52
36
|
} from './composite-authoring-adapter';
|
|
53
|
-
import {
|
|
54
|
-
import { type UndoAdapter, unregisterUndoAdapter } from './undo-timeline';
|
|
55
|
-
import { VgaiSceneAuthoringAdapter } from './vgai-scene-authoring-adapter';
|
|
37
|
+
import { fetchSceneDoc, VgaiSceneAuthoringAdapter } from './vgai-scene-authoring-adapter';
|
|
56
38
|
|
|
57
39
|
/**
|
|
58
40
|
* C3 (docs/unified-world-editor/26-unified-editor-spec.md §4 C3) — dispatched
|
|
@@ -68,9 +50,9 @@ export const EDIT_MODE_REBUILD_EVENT = 'editor:edit-mode-rebuild';
|
|
|
68
50
|
|
|
69
51
|
/**
|
|
70
52
|
* The display-relevant slice of a manifest world entry this module needs —
|
|
71
|
-
* deliberately LOOSE (a bare `string` `
|
|
72
|
-
* `
|
|
73
|
-
* (an unknown
|
|
53
|
+
* deliberately LOOSE (a bare `string` `surface`, not the engine's validated
|
|
54
|
+
* `AdapterRoot['surface']` union) so a world that fails schema validation
|
|
55
|
+
* (an unknown surface) can still be represented and surfaced as an error node
|
|
74
56
|
* (#18). Built by the LENIENT parse below (`parseEditModeManifest`), reachable
|
|
75
57
|
* from the production project-open path — NOT gated on strict Zod, which would
|
|
76
58
|
* reject the whole file for one bad world and make every declared world vanish
|
|
@@ -78,7 +60,7 @@ export const EDIT_MODE_REBUILD_EVENT = 'editor:edit-mode-rebuild';
|
|
|
78
60
|
*/
|
|
79
61
|
export interface EditModeWorldSpec {
|
|
80
62
|
readonly id: string;
|
|
81
|
-
readonly
|
|
63
|
+
readonly surface: string;
|
|
82
64
|
readonly adapter?: string | undefined;
|
|
83
65
|
readonly scene?: string | undefined;
|
|
84
66
|
readonly entry?: string | undefined;
|
|
@@ -87,7 +69,7 @@ export interface EditModeWorldSpec {
|
|
|
87
69
|
}
|
|
88
70
|
|
|
89
71
|
export interface EditModeManifest {
|
|
90
|
-
readonly
|
|
72
|
+
readonly roots: readonly EditModeWorldSpec[];
|
|
91
73
|
}
|
|
92
74
|
|
|
93
75
|
const KNOWN_WORLD_KINDS = new Set(['threejs', 'pixijs', 'react']);
|
|
@@ -115,30 +97,38 @@ function displayAdapter(adapter: unknown): string | undefined {
|
|
|
115
97
|
}
|
|
116
98
|
|
|
117
99
|
/**
|
|
118
|
-
* LENIENT parse (defect 2): read a raw `vgai.game.json` object's `
|
|
100
|
+
* LENIENT parse (defect 2): read a raw `vgai.game.json` object's `roots[]`
|
|
119
101
|
* defensively into `EditModeWorldSpec[]`, so a single bad world (unknown
|
|
120
|
-
* `
|
|
102
|
+
* `surface`, missing `scene`+`entry`) becomes an error node (#18) while its VALID
|
|
121
103
|
* siblings still open — never the strict-Zod all-or-nothing of
|
|
122
|
-
* `loadGameManifest`. Returns `null` when there is no usable `
|
|
104
|
+
* `loadGameManifest`. Returns `null` when there is no usable `roots` array at
|
|
123
105
|
* all (a present-but-unusable manifest, distinct from an ABSENT one) so the
|
|
124
106
|
* caller can fall back to the synthesized single-world floor. A world missing
|
|
125
107
|
* an `id` gets a positional placeholder so it still surfaces rather than
|
|
126
|
-
* vanishing. `
|
|
108
|
+
* vanishing. `surface` is carried through verbatim (even a bogus string) —
|
|
127
109
|
* `resolveWorldFailureReason` decides resolvability downstream.
|
|
128
110
|
*/
|
|
129
111
|
export function parseEditModeManifest(raw: unknown): EditModeManifest | null {
|
|
130
112
|
if (!raw || typeof raw !== 'object') return null;
|
|
131
|
-
const worldsRaw = (raw as Record<string, unknown>)['
|
|
113
|
+
const worldsRaw = (raw as Record<string, unknown>)['roots'];
|
|
114
|
+
if (worldsRaw === undefined) return null;
|
|
132
115
|
if (!Array.isArray(worldsRaw) || worldsRaw.length === 0) return null;
|
|
133
|
-
const
|
|
116
|
+
const roots: EditModeWorldSpec[] = worldsRaw.map((entry, i) => {
|
|
134
117
|
const w = (entry && typeof entry === 'object' ? entry : {}) as Record<string, unknown>;
|
|
135
118
|
const zOrderRaw = w['zOrder'];
|
|
136
119
|
const pausableRaw = w['pausable'];
|
|
137
120
|
return {
|
|
138
121
|
id: asString(w['id']) ?? `(world ${i})`,
|
|
139
|
-
// Missing/non-string
|
|
122
|
+
// Missing/non-string surface is itself unresolvable — carry a sentinel so
|
|
140
123
|
// resolveWorldFailureReason names it, rather than crashing on undefined.
|
|
141
|
-
|
|
124
|
+
surface:
|
|
125
|
+
(typeof w['adapter'] === 'string' ? asString(w['adapter']) : undefined) ??
|
|
126
|
+
asString(
|
|
127
|
+
w['adapter'] && typeof w['adapter'] === 'object'
|
|
128
|
+
? (w['adapter'] as Record<string, unknown>)['surface']
|
|
129
|
+
: undefined,
|
|
130
|
+
) ??
|
|
131
|
+
'(missing adapter surface)',
|
|
142
132
|
adapter: displayAdapter(w['adapter']),
|
|
143
133
|
scene: asString(w['scene']),
|
|
144
134
|
entry: asString(w['entry']),
|
|
@@ -146,7 +136,7 @@ export function parseEditModeManifest(raw: unknown): EditModeManifest | null {
|
|
|
146
136
|
pausable: typeof pausableRaw === 'boolean' ? pausableRaw : undefined,
|
|
147
137
|
};
|
|
148
138
|
});
|
|
149
|
-
return {
|
|
139
|
+
return { roots };
|
|
150
140
|
}
|
|
151
141
|
|
|
152
142
|
/**
|
|
@@ -158,8 +148,8 @@ export function parseEditModeManifest(raw: unknown): EditModeManifest | null {
|
|
|
158
148
|
* out of scope here, where nothing but the focused world is ever fetched.
|
|
159
149
|
*/
|
|
160
150
|
export function resolveWorldFailureReason(world: EditModeWorldSpec): string | null {
|
|
161
|
-
if (!KNOWN_WORLD_KINDS.has(world.
|
|
162
|
-
return `Unknown world
|
|
151
|
+
if (!KNOWN_WORLD_KINDS.has(world.surface)) {
|
|
152
|
+
return `Unknown world surface "${world.surface}" — expected one of: threejs, pixijs, react.`;
|
|
163
153
|
}
|
|
164
154
|
if (!world.scene && !world.entry) {
|
|
165
155
|
return `World "${world.id}" declares neither \`scene\` nor \`entry\` — nothing to mount.`;
|
|
@@ -176,17 +166,21 @@ function normalizeScenePath(path: string): string {
|
|
|
176
166
|
* the live store by `loadInitialScene`. Mirrors that function's OWN
|
|
177
167
|
* resolution order (`initial-scene.ts`: `?scene=` URL param wins, else the
|
|
178
168
|
* project's `defaultScene`); if neither names one of the manifest's threejs
|
|
179
|
-
*
|
|
169
|
+
* roots (or the manifest declares none), falls back to the first threejs
|
|
180
170
|
* world (matches the synthesized single-world manifest, which only ever has
|
|
181
171
|
* exactly one). Exported (pure — no `window`/store reads) so it's directly
|
|
182
172
|
* unit-testable; `installEditModeAuthoring` supplies the ambient
|
|
183
173
|
* `focusScenePath` from `window.location`/the current project.
|
|
184
174
|
*/
|
|
185
175
|
export function resolveFocusedThreejsWorldId(
|
|
186
|
-
|
|
176
|
+
roots: readonly {
|
|
177
|
+
readonly id: string;
|
|
178
|
+
readonly surface: string;
|
|
179
|
+
readonly scene?: string | undefined;
|
|
180
|
+
}[],
|
|
187
181
|
focusScenePath: string | null | undefined,
|
|
188
182
|
): string | null {
|
|
189
|
-
const threejsWorlds =
|
|
183
|
+
const threejsWorlds = roots.filter((w) => w.surface === 'threejs');
|
|
190
184
|
if (threejsWorlds.length === 0) return null;
|
|
191
185
|
if (focusScenePath) {
|
|
192
186
|
const normalized = normalizeScenePath(focusScenePath);
|
|
@@ -199,26 +193,32 @@ export function resolveFocusedThreejsWorldId(
|
|
|
199
193
|
}
|
|
200
194
|
|
|
201
195
|
// ---------------------------------------------------------------------------
|
|
202
|
-
// A4 (D8) — ManifestAuthoring:
|
|
196
|
+
// A4 (D8) — ManifestAuthoring: read-modify-write root composition properties
|
|
203
197
|
// ---------------------------------------------------------------------------
|
|
204
198
|
|
|
205
199
|
/** A raw `vgai.game.json` world entry, read/written defensively — same
|
|
206
200
|
* looseness as {@link EditModeWorldSpec}, but this is the RAW (unknown)
|
|
207
201
|
* object, never Zod-parsed, so unrelated fields survive a round trip. */
|
|
208
|
-
type
|
|
209
|
-
|
|
210
|
-
function findRawWorld(raw: Record<string, unknown>, worldId: string):
|
|
211
|
-
const
|
|
212
|
-
if (!Array.isArray(
|
|
213
|
-
return
|
|
214
|
-
(w): w is
|
|
215
|
-
!!w && typeof w === 'object' && (w as
|
|
202
|
+
type RawAdapterRoot = Record<string, unknown>;
|
|
203
|
+
|
|
204
|
+
function findRawWorld(raw: Record<string, unknown>, worldId: string): RawAdapterRoot | undefined {
|
|
205
|
+
const roots = raw['roots'];
|
|
206
|
+
if (!Array.isArray(roots)) return undefined;
|
|
207
|
+
return roots.find(
|
|
208
|
+
(w): w is RawAdapterRoot =>
|
|
209
|
+
!!w && typeof w === 'object' && (w as RawAdapterRoot)['id'] === worldId,
|
|
216
210
|
);
|
|
217
211
|
}
|
|
218
212
|
|
|
213
|
+
/** Materialize D19's inferred world only when a user edits one of its
|
|
214
|
+
* manifest-backed properties. Until then the raw file remains minimal. */
|
|
215
|
+
function ensureRawWorld(raw: Record<string, unknown>, worldId: string): RawAdapterRoot | undefined {
|
|
216
|
+
return findRawWorld(raw, worldId);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
219
|
/**
|
|
220
|
-
* ManifestAuthoring (A4, D8) —
|
|
221
|
-
* {@link
|
|
220
|
+
* ManifestAuthoring (A4, D8) — authored roots' manifest-backed
|
|
221
|
+
* {@link RootManifestProvider}: holds the RAW parsed `vgai.game.json`
|
|
222
222
|
* object (read-modify-write — NEVER round-tripped through the strict Zod
|
|
223
223
|
* schema, so fields this module doesn't know about survive untouched) and
|
|
224
224
|
* writes it back via `POST /__editor/manifest` (the one manifest-write verb,
|
|
@@ -231,30 +231,44 @@ function findRawWorld(raw: Record<string, unknown>, worldId: string): RawWorldEn
|
|
|
231
231
|
* - a SYNTHESIZED manifest (`synthesized: true`, no `vgai.game.json` on disk
|
|
232
232
|
* yet) BUFFERS edits in memory only; nothing is written until the
|
|
233
233
|
* explicit `materialize()` call (the generic inspector's "Create
|
|
234
|
-
* vgai.game.json" action).
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
* (the only source of a `synthesized: true` manifest) always produces
|
|
238
|
-
* exactly ONE world — so a synthesized manifest never gets a game root
|
|
239
|
-
* through today's wiring. The buffering logic is still implemented and
|
|
240
|
-
* unit-tested for correctness/forward-compatibility (see this module's
|
|
241
|
-
* tests), and the limitation is called out explicitly rather than silently
|
|
242
|
-
* assumed away.
|
|
234
|
+
* vgai.game.json" action). D19 makes this branch reachable for a
|
|
235
|
+
* manifest-less conventional project because its inferred root is always
|
|
236
|
+
* present.
|
|
243
237
|
*
|
|
244
238
|
* File-watcher landmine: `chokidar` only watches `public/` (`editor-server.ts`),
|
|
245
239
|
* so a manifest write at the PROJECT ROOT emits no SSE — `notifyIngestEdit()`
|
|
246
240
|
* after every successful write is what keeps the hierarchy panel's badges
|
|
247
241
|
* live instead of stale until reopen.
|
|
248
242
|
*/
|
|
249
|
-
export class ManifestAuthoring implements
|
|
243
|
+
export class ManifestAuthoring implements RootManifestProvider {
|
|
250
244
|
private readonly raw: Record<string, unknown>;
|
|
251
|
-
|
|
245
|
+
private synthesizedValue: boolean;
|
|
252
246
|
private readonly store: EditorStore;
|
|
247
|
+
private readonly fileHistory: ProjectFileHistory | null;
|
|
248
|
+
private readonly unsubscribeHistory: (() => void) | null;
|
|
253
249
|
|
|
254
250
|
constructor(raw: Record<string, unknown>, synthesized: boolean, store: EditorStore) {
|
|
255
251
|
this.raw = raw;
|
|
256
|
-
this.
|
|
252
|
+
this.synthesizedValue = synthesized;
|
|
257
253
|
this.store = store;
|
|
254
|
+
const history = store.projectHistory;
|
|
255
|
+
this.fileHistory = history
|
|
256
|
+
? getProjectFileHistory(history, getManifestHistoryBackend(), 'project-manifest')
|
|
257
|
+
: null;
|
|
258
|
+
this.unsubscribeHistory = this.fileHistory
|
|
259
|
+
? this.fileHistory.subscribeAll((path) => {
|
|
260
|
+
if (path !== 'vgai.game.json') return;
|
|
261
|
+
void getManifestHistoryBackend()
|
|
262
|
+
.readBytes(path)
|
|
263
|
+
.then((bytes) => JSON.parse(new TextDecoder().decode(bytes)) as Record<string, unknown>)
|
|
264
|
+
.then((next) => {
|
|
265
|
+
for (const key of Object.keys(this.raw)) delete this.raw[key];
|
|
266
|
+
Object.assign(this.raw, next);
|
|
267
|
+
this.store.notifyIngestEdit();
|
|
268
|
+
})
|
|
269
|
+
.catch(() => {});
|
|
270
|
+
})
|
|
271
|
+
: null;
|
|
258
272
|
}
|
|
259
273
|
|
|
260
274
|
getGameName(): string {
|
|
@@ -262,9 +276,23 @@ export class ManifestAuthoring implements GameRootManifestProvider {
|
|
|
262
276
|
return typeof name === 'string' ? name : '';
|
|
263
277
|
}
|
|
264
278
|
|
|
279
|
+
get synthesized(): boolean {
|
|
280
|
+
return this.synthesizedValue;
|
|
281
|
+
}
|
|
282
|
+
|
|
265
283
|
setGameName(name: string): void {
|
|
266
284
|
this.raw['name'] = name;
|
|
267
|
-
this.persist();
|
|
285
|
+
this.persist('Rename Game');
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
getWorldContentSource(worldId: string): string | undefined {
|
|
289
|
+
const world = findRawWorld(this.raw, worldId);
|
|
290
|
+
if (!world) return undefined;
|
|
291
|
+
const scene = world['scene'];
|
|
292
|
+
if (typeof scene === 'string') return `Scene · ${scene}`;
|
|
293
|
+
const entry = world['entry'];
|
|
294
|
+
if (typeof entry === 'string') return `Entry · ${entry}`;
|
|
295
|
+
return 'Adapter-owned runtime content';
|
|
268
296
|
}
|
|
269
297
|
|
|
270
298
|
getWorldZOrder(worldId: string): number {
|
|
@@ -273,10 +301,10 @@ export class ManifestAuthoring implements GameRootManifestProvider {
|
|
|
273
301
|
}
|
|
274
302
|
|
|
275
303
|
setWorldZOrder(worldId: string, value: number): void {
|
|
276
|
-
const world =
|
|
304
|
+
const world = ensureRawWorld(this.raw, worldId);
|
|
277
305
|
if (!world) return;
|
|
278
306
|
world['zOrder'] = value;
|
|
279
|
-
this.persist();
|
|
307
|
+
this.persist('Change World Order');
|
|
280
308
|
}
|
|
281
309
|
|
|
282
310
|
getWorldPausable(worldId: string): boolean {
|
|
@@ -285,27 +313,42 @@ export class ManifestAuthoring implements GameRootManifestProvider {
|
|
|
285
313
|
}
|
|
286
314
|
|
|
287
315
|
setWorldPausable(worldId: string, value: boolean): void {
|
|
288
|
-
const world =
|
|
316
|
+
const world = ensureRawWorld(this.raw, worldId);
|
|
289
317
|
if (!world) return;
|
|
290
318
|
world['pausable'] = value;
|
|
291
|
-
this.persist();
|
|
319
|
+
this.persist('Change World Pausing');
|
|
292
320
|
}
|
|
293
321
|
|
|
294
322
|
/** The "Create vgai.game.json" action (generic inspector, while
|
|
295
323
|
* `synthesized`) — materializes the buffered edits to disk. Also safe to
|
|
296
324
|
* call on a non-synthesized instance (equivalent to an explicit re-save). */
|
|
297
325
|
async materialize(): Promise<boolean> {
|
|
298
|
-
|
|
326
|
+
const written = await this.write('Create Game Manifest');
|
|
327
|
+
if (written) this.synthesizedValue = false;
|
|
328
|
+
return written;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
dispose(): void {
|
|
332
|
+
this.unsubscribeHistory?.();
|
|
299
333
|
}
|
|
300
334
|
|
|
301
|
-
private persist(): void {
|
|
335
|
+
private persist(label: string): void {
|
|
302
336
|
if (this.synthesized) return; // buffered only — wait for materialize()
|
|
303
|
-
void this.write();
|
|
337
|
+
void this.write(label);
|
|
304
338
|
}
|
|
305
339
|
|
|
306
|
-
private async write(): Promise<boolean> {
|
|
340
|
+
private async write(label: string): Promise<boolean> {
|
|
307
341
|
const content = JSON.stringify(this.raw, null, 2);
|
|
308
342
|
try {
|
|
343
|
+
if (this.fileHistory) {
|
|
344
|
+
await this.fileHistory.write('vgai.game.json', content, {
|
|
345
|
+
label,
|
|
346
|
+
kind: 'manifest',
|
|
347
|
+
contentType: 'application/json',
|
|
348
|
+
});
|
|
349
|
+
this.store.notifyIngestEdit();
|
|
350
|
+
return true;
|
|
351
|
+
}
|
|
309
352
|
// Phase A cleanup: `vgai.game.json` is project-root-relative, the same
|
|
310
353
|
// namespace `authoring/world-overlay.ts`/`authoring/ingest-overlay.ts`
|
|
311
354
|
// already mode-branch on — browser mode has no `/__editor/*` server at
|
|
@@ -328,106 +371,44 @@ export class ManifestAuthoring implements GameRootManifestProvider {
|
|
|
328
371
|
this.store.notifyIngestEdit();
|
|
329
372
|
return true;
|
|
330
373
|
} catch (err) {
|
|
374
|
+
if (this.fileHistory) {
|
|
375
|
+
void getManifestHistoryBackend()
|
|
376
|
+
.readBytes('vgai.game.json')
|
|
377
|
+
.then((bytes) => JSON.parse(new TextDecoder().decode(bytes)) as Record<string, unknown>)
|
|
378
|
+
.then((next) => {
|
|
379
|
+
for (const key of Object.keys(this.raw)) delete this.raw[key];
|
|
380
|
+
Object.assign(this.raw, next);
|
|
381
|
+
this.store.notifyIngestEdit();
|
|
382
|
+
})
|
|
383
|
+
.catch(() => {});
|
|
384
|
+
}
|
|
331
385
|
editorConsole.error(`Failed to write vgai.game.json: ${err}`, 'project');
|
|
332
386
|
return false;
|
|
333
387
|
}
|
|
334
388
|
}
|
|
335
389
|
}
|
|
336
390
|
|
|
337
|
-
|
|
338
|
-
* `exitEditModeAuthoring` can unregister any that self-registered with the
|
|
339
|
-
* undo timeline (mirrors `play-mode.ts`'s `_multiWorldChildren`/
|
|
340
|
-
* `exitMultiWorldAuthoring`). Boundary adapters never register (no `undo`/
|
|
341
|
-
* `redo`); the focused `VgaiSceneAuthoringAdapter` delegates undo/redo to
|
|
342
|
-
* the store, which registers ITSELF once at construction
|
|
343
|
-
* (`editor-store.ts`) — so this is currently always a no-op in practice,
|
|
344
|
-
* kept for the same defensive reason `play-mode.ts`'s guard is: a future
|
|
345
|
-
* live child adapter (B1's `ReactWorldAuthoringAdapter`) DOES self-register. */
|
|
346
|
-
let _editModeChildren: AuthoringAdapter[] = [];
|
|
347
|
-
|
|
348
|
-
/**
|
|
349
|
-
* C3 / R2 — the `UIEditStore`(s) built for this session's scene-UI child(ren)
|
|
350
|
-
* (today: at most one, the focused world's). Tracked SEPARATELY from
|
|
351
|
-
* `_editModeChildren`: the undo timeline registers the `UIEditStore` itself
|
|
352
|
-
* (`ui-edit-store.ts`'s constructor, `registerUndoAdapter(this)`), NOT the
|
|
353
|
-
* `UIAuthoringAdapter` wrapping it — `exitEditModeAuthoring`'s generic
|
|
354
|
-
* `_editModeChildren` loop below unregisters the ADAPTER, which was never in
|
|
355
|
-
* the undo timeline's `live` set (a harmless no-op for it), so the real store
|
|
356
|
-
* needs its OWN explicit unregister here, exactly what the retired standalone
|
|
357
|
-
* UI-edit mode's own teardown used to do.
|
|
358
|
-
*/
|
|
359
|
-
let _editModeUIStores: UIEditStore[] = [];
|
|
360
|
-
|
|
361
|
-
/**
|
|
362
|
-
* C3 — build the scene-UI child for `focusedWorldId`'s composite when the
|
|
363
|
-
* open scene has authored UI roots (`store.getSceneUI().length > 0`,
|
|
364
|
-
* §3.1's install gate — a ui-less scene gets no child at all, keeping the
|
|
365
|
-
* A1 single-world back-compat floor byte-identical). Mirrors the retired
|
|
366
|
-
* standalone UI-edit mode's store/adapter construction, minus its
|
|
367
|
-
* demo-fixture fallback (Q3: "should never have existed" — dropped by
|
|
368
|
-
* design, not lost; see the harness/15/16 for the demo fixture's only
|
|
369
|
-
* remaining home) and its own `setActiveAuthoring` call (the shared
|
|
370
|
-
* composite install below already does that once, for every child).
|
|
371
|
-
*/
|
|
372
|
-
function buildUIChild(store: EditorStore, focusedWorldId: string, zOrder: number): CompositeChild {
|
|
373
|
-
const uiStore = new UIEditStore(store.getSceneUI(), {
|
|
374
|
-
// Persist every committed edit back into the open scene's `ui` + autosave
|
|
375
|
-
// (C9) — the exact same commit path the retired standalone UI-edit
|
|
376
|
-
// mode's `onCommit` used.
|
|
377
|
-
onCommit: (next) => store.setSceneUI(next),
|
|
378
|
-
});
|
|
379
|
-
_editModeUIStores.push(uiStore);
|
|
380
|
-
const adapter = new UIAuthoringAdapter(uiStore, {
|
|
381
|
-
...(import.meta.env.DEV ? { writeBackend: createHttpSourceWriteBackend() } : {}),
|
|
382
|
-
});
|
|
383
|
-
return {
|
|
384
|
-
worldId: `${focusedWorldId}:ui`,
|
|
385
|
-
kind: 'scene-ui',
|
|
386
|
-
adapter,
|
|
387
|
-
zOrder,
|
|
388
|
-
parentWorldId: focusedWorldId,
|
|
389
|
-
};
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
/**
|
|
393
|
-
* B1 — register a child adapter minted AFTER `installEditModeAuthoring`
|
|
394
|
-
* already ran (a design-time layer's successful mount, which upgrades a
|
|
395
|
-
* world's Boundary adapter to a live one via `CompositeAuthoringAdapter.
|
|
396
|
-
* replaceChild` — `design-time-layers.ts`). Mirrors the constructor-time
|
|
397
|
-
* seeding above (`_editModeChildren = children.map(...)`) so `exitEditModeAuthoring`
|
|
398
|
-
* unregisters this adapter from the undo timeline too, exactly like every
|
|
399
|
-
* child present at install time. A no-op call with an adapter that never
|
|
400
|
-
* self-registered (no `undo`/`redo`) is harmless — `exitEditModeAuthoring`'s
|
|
401
|
-
* loop already guards on that pair being present.
|
|
402
|
-
*/
|
|
403
|
-
export function registerEditModeChild(adapter: AuthoringAdapter): void {
|
|
404
|
-
_editModeChildren.push(adapter);
|
|
405
|
-
}
|
|
391
|
+
let _manifestAuthoring: ManifestAuthoring | null = null;
|
|
406
392
|
|
|
407
393
|
/**
|
|
408
|
-
* Build the composite over every declared world and
|
|
409
|
-
*
|
|
410
|
-
*
|
|
411
|
-
* deliberately leaves the shared slot untouched). Always returns the built
|
|
412
|
-
* composite so callers (tests) can inspect it regardless of whether it was
|
|
413
|
-
* installed as the override.
|
|
394
|
+
* Build the composite over every declared world and install it as the active
|
|
395
|
+
* authoring override for every non-empty composition
|
|
396
|
+
* (D19). Always returns the built composite so callers can inspect it.
|
|
414
397
|
*
|
|
415
|
-
* The focused threejs world is whichever one's scene
|
|
416
|
-
*
|
|
417
|
-
*
|
|
418
|
-
*
|
|
419
|
-
*
|
|
420
|
-
*
|
|
421
|
-
*
|
|
422
|
-
*
|
|
423
|
-
* (defect 5) — so that parameter was removed.
|
|
398
|
+
* The focused threejs world is whichever one's scene the store currently
|
|
399
|
+
* holds (URL `?scene=` / `defaultScene` order — `loadInitialScene` at open,
|
|
400
|
+
* {@link switchFocusedWorld} thereafter); it gets the real
|
|
401
|
+
* `VgaiSceneAuthoringAdapter`. This function still accepts NO "focus this
|
|
402
|
+
* other world" parameter — that would label the new world's identity onto
|
|
403
|
+
* the OLD world's still-loaded document (defect 5). A focus change happens
|
|
404
|
+
* ONLY through {@link switchFocusedWorld}, which changes the document and
|
|
405
|
+
* the `?scene=` truth together and then rebuilds through this function.
|
|
424
406
|
*
|
|
425
407
|
* `rawManifestInfo` (A4, D8, optional) — the RAW parsed `vgai.game.json` +
|
|
426
|
-
* whether it was synthesized (never written to disk); when given
|
|
427
|
-
* composite
|
|
428
|
-
* and passed to `CompositeAuthoringAdapter` as its
|
|
429
|
-
* surface.
|
|
430
|
-
* exact pre-A4 shape — see `CompositeAuthoringAdapter`'s own doc comment.
|
|
408
|
+
* whether it was synthesized (never written to disk); when given and the
|
|
409
|
+
* composite has a world, a `ManifestAuthoring` is built over it
|
|
410
|
+
* and passed to `CompositeAuthoringAdapter` as its root-property manifest
|
|
411
|
+
* surface. When omitted, composition properties are read-only.
|
|
431
412
|
*/
|
|
432
413
|
export function installEditModeAuthoring(
|
|
433
414
|
store: EditorStore,
|
|
@@ -438,9 +419,17 @@ export function installEditModeAuthoring(
|
|
|
438
419
|
const sceneParam = urlParams.get('scene');
|
|
439
420
|
const defaultScene = getCurrentProject()?.config.defaultScene ?? undefined;
|
|
440
421
|
const focusScenePath = sceneParam ?? defaultScene ?? null;
|
|
441
|
-
const focusedWorldId = resolveFocusedThreejsWorldId(manifest.
|
|
422
|
+
const focusedWorldId = resolveFocusedThreejsWorldId(manifest.roots, focusScenePath);
|
|
423
|
+
|
|
424
|
+
// A4 — rebuild the focus-candidate map WHOLE (install time is the recompute
|
|
425
|
+
// point): every unfocused threejs world with a loadable manifest scene.
|
|
426
|
+
_focusCandidates = new Map(
|
|
427
|
+
manifest.roots
|
|
428
|
+
.filter((w) => w.surface === 'threejs' && w.id !== focusedWorldId && w.scene !== undefined)
|
|
429
|
+
.map((w) => [w.id, w.scene as string]),
|
|
430
|
+
);
|
|
442
431
|
|
|
443
|
-
const children: CompositeChild[] = manifest.
|
|
432
|
+
const children: CompositeChild[] = manifest.roots.map((world) => {
|
|
444
433
|
// D12 (B4) — install-time zOrder for every child, not just the Boundary's
|
|
445
434
|
// own inspector-displayed value: `childAdapters()`'s pick/paint-order
|
|
446
435
|
// consumers (the layered pick walk, the hierarchy zOrder badge) need it
|
|
@@ -449,7 +438,7 @@ export function installEditModeAuthoring(
|
|
|
449
438
|
if (world.id === focusedWorldId) {
|
|
450
439
|
return {
|
|
451
440
|
worldId: world.id,
|
|
452
|
-
kind: world.
|
|
441
|
+
kind: world.surface,
|
|
453
442
|
adapter: new VgaiSceneAuthoringAdapter(store),
|
|
454
443
|
zOrder,
|
|
455
444
|
};
|
|
@@ -457,7 +446,7 @@ export function installEditModeAuthoring(
|
|
|
457
446
|
const reason = resolveWorldFailureReason(world);
|
|
458
447
|
const info: BoundaryWorldInfo = {
|
|
459
448
|
id: world.id,
|
|
460
|
-
kind: world.
|
|
449
|
+
kind: world.surface,
|
|
461
450
|
adapter: world.adapter,
|
|
462
451
|
entryOrScenePath: world.scene ?? world.entry,
|
|
463
452
|
zOrder,
|
|
@@ -465,54 +454,24 @@ export function installEditModeAuthoring(
|
|
|
465
454
|
};
|
|
466
455
|
return {
|
|
467
456
|
worldId: world.id,
|
|
468
|
-
kind: world.
|
|
457
|
+
kind: world.surface,
|
|
469
458
|
adapter: new BoundaryAuthoringAdapter(store, info, reason ?? undefined),
|
|
470
459
|
zOrder,
|
|
471
460
|
};
|
|
472
461
|
});
|
|
473
462
|
|
|
474
|
-
//
|
|
475
|
-
//
|
|
476
|
-
//
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
// install gate below BY DESIGN (R5): a single-world project that already
|
|
480
|
-
// authored UI content gets the composite + the nested "ui (scene-ui)" row;
|
|
481
|
-
// a ui-less single-world project's hierarchy shape is untouched.
|
|
482
|
-
if (focusedWorldId && store.getSceneUI().length > 0) {
|
|
483
|
-
const focusedChild = children.find((c) => c.worldId === focusedWorldId);
|
|
484
|
-
children.push(buildUIChild(store, focusedWorldId, focusedChild?.zOrder ?? 0));
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
// A4 (D8) — the game root only makes sense once there IS a composition to
|
|
488
|
-
// disclose (2+ worlds); a single-world project (synthesized or a real
|
|
489
|
-
// 1-world manifest) never installs the composite as the override at all
|
|
490
|
-
// (below), so it would never be reachable regardless.
|
|
491
|
-
const gameRoot =
|
|
492
|
-
rawManifestInfo && children.length > 1
|
|
463
|
+
// D19 — every composition, including one root, uses the same adapter projection.
|
|
464
|
+
// A synthesized manifest uses the same tree; its manifest-backed
|
|
465
|
+
// edits remain buffered until an explicit project change materializes it.
|
|
466
|
+
const manifestAuthoring =
|
|
467
|
+
rawManifestInfo && children.length >= 1
|
|
493
468
|
? new ManifestAuthoring(rawManifestInfo.raw, rawManifestInfo.synthesized, store)
|
|
494
469
|
: undefined;
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
// react (or any world-set with NO focused threejs world at all) never met
|
|
501
|
-
// it, so that world never entered the composite/hierarchy and
|
|
502
|
-
// `WorldSelectionOverlay` self-gated off, even though a real world exists
|
|
503
|
-
// to disclose. `focusedWorldId === null` (see `resolveFocusedThreejsWorldId`'s
|
|
504
|
-
// own doc comment: it returns `null` exactly when the manifest declares NO
|
|
505
|
-
// threejs world) with at least one child is that no-threejs case — install
|
|
506
|
-
// the composite there too. The single-threejs-world floor above (`children.
|
|
507
|
-
// length === 1` AND that one world IS the focused threejs world, so
|
|
508
|
-
// `focusedWorldId` is non-null) is UNCHANGED and must stay so — see the doc
|
|
509
|
-
// comment at this file's top (~:15-33) explaining why that back-compat
|
|
510
|
-
// floor is exact, not "equivalent". `mountDesignTimeLayers` already handles
|
|
511
|
-
// the layer mount for the newly-activated no-threejs case unchanged — no
|
|
512
|
-
// parallel react-only entry point is added here (spec-27 E3 all-worlds
|
|
513
|
-
// generalization).
|
|
514
|
-
const noFocusedThreejsWorld = focusedWorldId === null;
|
|
515
|
-
if (children.length > 1 || (children.length >= 1 && noFocusedThreejsWorld)) {
|
|
470
|
+
_manifestAuthoring = manifestAuthoring ?? null;
|
|
471
|
+
const projection = hierarchyProjectionFromProjectConfig(getCurrentProject()?.config);
|
|
472
|
+
const composite = new CompositeAuthoringAdapter(children, manifestAuthoring, projection);
|
|
473
|
+
|
|
474
|
+
if (children.length >= 1) {
|
|
516
475
|
// Brand BEFORE installing so the store's save guard sees it the instant
|
|
517
476
|
// the override is live (defect 1: the focused first-party child must be
|
|
518
477
|
// able to Ctrl+S/autosave through the store, unlike a foreign override).
|
|
@@ -527,51 +486,115 @@ export function installEditModeAuthoring(
|
|
|
527
486
|
/** Teardown symmetric to `installEditModeAuthoring` — clears the override
|
|
528
487
|
* ONLY if it is still exactly the composite this module last installed
|
|
529
488
|
* (never clobbers a DIFFERENT session, e.g. ingest/play, that took over
|
|
530
|
-
* afterward)
|
|
531
|
-
* timeline. */
|
|
489
|
+
* afterward). */
|
|
532
490
|
export function exitEditModeAuthoring(installed?: CompositeAuthoringAdapter): void {
|
|
533
491
|
if (installed && getAuthoringOverride() === installed) {
|
|
534
492
|
setActiveAuthoring(null);
|
|
535
493
|
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
494
|
+
_manifestAuthoring?.dispose();
|
|
495
|
+
_manifestAuthoring = null;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// ---------------------------------------------------------------------------
|
|
499
|
+
// A4 — focus-SWITCH: make a different threejs world the live/focused one
|
|
500
|
+
// ---------------------------------------------------------------------------
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* A4 — which UNFOCUSED threejs roots can become focused, captured by the
|
|
504
|
+
* last `installEditModeAuthoring` run (worldId → manifest scene path).
|
|
505
|
+
* Install time IS the recompute point — the map is rebuilt whole on every
|
|
506
|
+
* install/rebuild, never patched. A threejs world with no `scene` (entry-only)
|
|
507
|
+
* is genuinely unswitchable — there is nothing to load into the store — so it
|
|
508
|
+
* never enters the map and the shell shows no affordance for it (spec 28:
|
|
509
|
+
* hide when the subject is absent).
|
|
510
|
+
*/
|
|
511
|
+
let _focusCandidates = new Map<string, string>();
|
|
512
|
+
|
|
513
|
+
/** The manifest scene path `switchFocusedWorld` would load for `worldId`, or
|
|
514
|
+
* `null` when that world is not a switchable unfocused threejs world. The
|
|
515
|
+
* shell gates its focus-switch affordances on this (never advertise a switch
|
|
516
|
+
* that will refuse — spec 29 §3). */
|
|
517
|
+
export function worldFocusScenePath(worldId: string): string | null {
|
|
518
|
+
return _focusCandidates.get(worldId) ?? null;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/** Dispatched (on `window`) when a focus-switch needs the dirty-gate
|
|
522
|
+
* confirmation dialog — same decoupling pattern as `root-activation.ts`'s
|
|
523
|
+
* `ROOT_ACTIVATION_REQUEST_EVENT`; `DefaultEditorLayout` listens. */
|
|
524
|
+
export const WORLD_FOCUS_REQUEST_EVENT = 'editor:request-world-focus';
|
|
525
|
+
|
|
526
|
+
export interface WorldFocusRequest {
|
|
527
|
+
readonly worldId: string;
|
|
528
|
+
readonly scenePath: string;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Focus-SWITCH (A4, closing defect 5's deferral): make `worldId` the live
|
|
533
|
+
* threejs world by doing exactly what the initial load does, in its order —
|
|
534
|
+
* flush pending edits (`saveNow`, same as `rootGroups.activate`), load the
|
|
535
|
+
* target world's manifest scene into the store (this clears undo/redo and
|
|
536
|
+
* retargets autosave — `loadDocument`'s own proven semantics), point the
|
|
537
|
+
* ambient focus truth at it (`?scene=`, the SAME param `loadInitialScene`
|
|
538
|
+
* and `installEditModeAuthoring` read — so any later rebuild, and a browser
|
|
539
|
+
* refresh, re-derive THIS focus), then fire the C3 rebuild event so
|
|
540
|
+
* `ViewportPanel.installAll()` reinstalls the composite: the target world
|
|
541
|
+
* gets the real `VgaiSceneAuthoringAdapter`, the previously-focused world
|
|
542
|
+
* downgrades to a Boundary. No identity misattribution is possible because
|
|
543
|
+
* the document and the focus truth change together, before the rebuild.
|
|
544
|
+
*/
|
|
545
|
+
export async function switchFocusedWorld(
|
|
546
|
+
store: EditorStore,
|
|
547
|
+
worldId: string,
|
|
548
|
+
scenePath: string,
|
|
549
|
+
): Promise<void> {
|
|
550
|
+
if (store.isDirty) await store.saveNow();
|
|
551
|
+
const path = normalizeScenePath(scenePath);
|
|
552
|
+
let doc: Awaited<ReturnType<typeof fetchSceneDoc>>;
|
|
553
|
+
try {
|
|
554
|
+
doc = await fetchSceneDoc(path);
|
|
555
|
+
} catch (err) {
|
|
556
|
+
editorConsole.error(
|
|
557
|
+
`Focus-switch to world "${worldId}" failed — could not load its scene "${path}": ${err}`,
|
|
558
|
+
'project',
|
|
559
|
+
);
|
|
560
|
+
return;
|
|
547
561
|
}
|
|
548
|
-
|
|
562
|
+
store.loadDocument(doc, path);
|
|
563
|
+
const url = new URL(window.location.href);
|
|
564
|
+
url.searchParams.set('scene', path);
|
|
565
|
+
window.history.replaceState(null, '', url.toString());
|
|
566
|
+
window.dispatchEvent(new CustomEvent(EDIT_MODE_REBUILD_EVENT));
|
|
549
567
|
}
|
|
550
568
|
|
|
551
569
|
/**
|
|
552
|
-
*
|
|
553
|
-
*
|
|
554
|
-
*
|
|
555
|
-
*
|
|
556
|
-
*
|
|
557
|
-
*
|
|
558
|
-
* `loadInitialScene` already loaded.
|
|
570
|
+
* Request focusing `worldId` — the shell's ONE entry point (both the world
|
|
571
|
+
* row double-click and its context-menu item), mirroring
|
|
572
|
+
* `requestRootActivation`'s dirty-gate: a dirty store gets the shell-rendered
|
|
573
|
+
* confirmation dialog (`DefaultEditorLayout` listens for
|
|
574
|
+
* {@link WORLD_FOCUS_REQUEST_EVENT} and calls {@link switchFocusedWorld} on
|
|
575
|
+
* confirm); a clean store switches immediately.
|
|
559
576
|
*/
|
|
560
|
-
export function
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
577
|
+
export function requestWorldFocus(store: EditorStore, worldId: string): void {
|
|
578
|
+
const scenePath = worldFocusScenePath(worldId);
|
|
579
|
+
if (scenePath === null) return;
|
|
580
|
+
if (store.isDirty) {
|
|
581
|
+
window.dispatchEvent(
|
|
582
|
+
new CustomEvent(WORLD_FOCUS_REQUEST_EVENT, {
|
|
583
|
+
detail: { worldId, scenePath } satisfies WorldFocusRequest,
|
|
584
|
+
}),
|
|
585
|
+
);
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
void switchFocusedWorld(store, worldId, scenePath);
|
|
566
589
|
}
|
|
567
590
|
|
|
568
591
|
/**
|
|
569
592
|
* Project-open wiring: fetch the RAW `vgai.game.json` and install the edit-mode
|
|
570
593
|
* composite via the LENIENT parse (defect 2) — a manifest with one bad world
|
|
571
|
-
* still opens its valid
|
|
594
|
+
* still opens its valid roots, surfacing the bad one as an error node (#18).
|
|
572
595
|
* ONLY a genuinely ABSENT manifest (the route 404s → `fetchRawGameManifest`
|
|
573
|
-
* returns `null`), or one so malformed it has no usable `
|
|
574
|
-
* synthesizes a single `{ id: 'main',
|
|
596
|
+
* returns `null`), or one so malformed it has no usable `roots` array,
|
|
597
|
+
* synthesizes a single `{ id: 'main', surface: 'threejs', scene: <resolved> }`
|
|
575
598
|
* game IN MEMORY (never written to disk), where `<resolved>` mirrors
|
|
576
599
|
* `loadInitialScene`'s OWN resolution order. This is the only call site that
|
|
577
600
|
* should call `installEditModeAuthoring` at ordinary project open
|
|
@@ -581,13 +604,10 @@ export async function installEditModeAuthoringForProject(
|
|
|
581
604
|
store: EditorStore,
|
|
582
605
|
): Promise<CompositeAuthoringAdapter> {
|
|
583
606
|
let manifest: EditModeManifest;
|
|
584
|
-
//
|
|
585
|
-
//
|
|
586
|
-
//
|
|
587
|
-
|
|
588
|
-
// this branch is unreachable end-to-end today (synthesis always produces
|
|
589
|
-
// exactly one world; a game root only installs for 2+).
|
|
590
|
-
let rawManifestInfo: { raw: Record<string, unknown>; synthesized: boolean };
|
|
607
|
+
// The RAW object backing `ManifestAuthoring` (read-modify-write, never
|
|
608
|
+
// round-tripped through Zod). An omitted roots field is a real manifest
|
|
609
|
+
// using D19's convention, not a synthesized/unsaved manifest.
|
|
610
|
+
let rawManifestInfo: { raw: Record<string, unknown>; synthesized: boolean } | undefined;
|
|
591
611
|
try {
|
|
592
612
|
const raw = await fetchRawGameManifest();
|
|
593
613
|
const parsed = raw !== null && parseEditModeManifest(raw);
|
|
@@ -595,49 +615,14 @@ export async function installEditModeAuthoringForProject(
|
|
|
595
615
|
manifest = parsed;
|
|
596
616
|
rawManifestInfo = { raw: raw as Record<string, unknown>, synthesized: false };
|
|
597
617
|
} else {
|
|
598
|
-
manifest
|
|
599
|
-
rawManifestInfo = { raw: synthesizeRawManifest(manifest), synthesized: true };
|
|
618
|
+
throw new Error('manifest must declare a non-empty roots array');
|
|
600
619
|
}
|
|
601
620
|
} catch (err) {
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
rawManifestInfo = { raw: synthesizeRawManifest(manifest), synthesized: true };
|
|
621
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
622
|
+
editorConsole.error(`Failed to load vgai.game.json: ${detail}`, 'project');
|
|
623
|
+
manifest = {
|
|
624
|
+
roots: [{ id: 'manifest', surface: '(invalid manifest)', adapter: detail }],
|
|
625
|
+
};
|
|
608
626
|
}
|
|
609
627
|
return installEditModeAuthoring(store, manifest, rawManifestInfo);
|
|
610
628
|
}
|
|
611
|
-
|
|
612
|
-
function synthesizeSingleWorldManifest(): EditModeManifest {
|
|
613
|
-
return {
|
|
614
|
-
worlds: [
|
|
615
|
-
{
|
|
616
|
-
id: 'main',
|
|
617
|
-
kind: 'threejs',
|
|
618
|
-
scene: resolveInitialScenePathForManifestSynthesis(getCurrentProject()) ?? undefined,
|
|
619
|
-
},
|
|
620
|
-
],
|
|
621
|
-
};
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
/** A minimally-valid RAW manifest object matching a synthesized in-memory
|
|
625
|
-
* game (A4) — never written to disk on its own; only feeds `ManifestAuthoring`
|
|
626
|
-
* for the (currently unreachable, see that class's doc comment) 1-world
|
|
627
|
-
* "Create vgai.game.json" path. */
|
|
628
|
-
function synthesizeRawManifest(manifest: EditModeManifest): Record<string, unknown> {
|
|
629
|
-
return {
|
|
630
|
-
manifestVersion: 1,
|
|
631
|
-
name: getCurrentProject()?.config.name ?? 'My Game',
|
|
632
|
-
version: '0.1.0',
|
|
633
|
-
engine: { version: '0.0.0' },
|
|
634
|
-
worlds: manifest.worlds.map((w) => ({
|
|
635
|
-
id: w.id,
|
|
636
|
-
kind: w.kind,
|
|
637
|
-
...(w.scene !== undefined ? { scene: w.scene } : {}),
|
|
638
|
-
...(w.entry !== undefined ? { entry: w.entry } : {}),
|
|
639
|
-
zOrder: w.zOrder ?? 0,
|
|
640
|
-
pausable: w.pausable ?? true,
|
|
641
|
-
})),
|
|
642
|
-
};
|
|
643
|
-
}
|