@vgai/editor 0.2.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/LICENSE +661 -0
- package/README.md +35 -0
- package/dist/a/b/c/test-nested.json +1 -0
- package/dist/assets/CCDIKSolver-Dz-ysxZF.js +1 -0
- package/dist/assets/SelectionBox-BXsv_6aH.js +1 -0
- package/dist/assets/browser-project-scripts-DOru4L1x.js +2 -0
- package/dist/assets/browserAll-CEkWYlFk.js +1 -0
- package/dist/assets/collision-world-DniMB0qq.glb +0 -0
- package/dist/assets/create-runtime-DYIWr3va.js +1 -0
- package/dist/assets/data-asset-Bb9sIM7i.js +5 -0
- package/dist/assets/esbuild-BHljloGq.wasm +0 -0
- package/dist/assets/fake-three-BMTQ9rl_.js +1 -0
- package/dist/assets/game-8SDZPPgH.js +1 -0
- package/dist/assets/game-BI111o5f.js +10 -0
- package/dist/assets/game-CevorxyV.js +1 -0
- package/dist/assets/game-Di1H0gFj.js +1 -0
- package/dist/assets/game-Di7XlywD.js +1 -0
- package/dist/assets/game-DjnCaw8R.js +1 -0
- package/dist/assets/game-Q9A0aXSV.js +1 -0
- package/dist/assets/game-pt_1lQLv.js +1 -0
- package/dist/assets/game-source-CvidN7AB.js +1 -0
- package/dist/assets/game-xsOho_wg.js +83 -0
- package/dist/assets/index-Bp5cecD6.js +12 -0
- package/dist/assets/index-CPVV6pjw.js +1 -0
- package/dist/assets/index-CpOnYyeD.js +213 -0
- package/dist/assets/index-Cs8xwv3e.js +1 -0
- package/dist/assets/index-DL7RcQ0n.js +27 -0
- package/dist/assets/index-Dr8H_6Rl.js +1 -0
- package/dist/assets/index-_bCaipUk.js +13650 -0
- package/dist/assets/ingest-siblings-Dz8eEqrV.js +1 -0
- package/dist/assets/mount-manifest-Bhk0ohsl.js +1 -0
- package/dist/assets/recast-navigation.wasm-compat-CvP4UGK6.js +1 -0
- package/dist/assets/scene-query-BivAHhJu.js +1 -0
- package/dist/assets/stats.module--VATS4Kh.js +1 -0
- package/dist/assets/webworkerAll-BLrH0F6F.js +1 -0
- package/dist/assets/xstate-animation-binding-1NGLmw6y.js +1 -0
- package/dist/assets/xstate.esm-BgUD33nl.js +3 -0
- package/dist/index.html +37 -0
- package/dist/ingest/racing-game/UPSTREAM.md +68 -0
- package/dist/ingest/racing-game/a/Inter-Black.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-Black.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-BlackItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-BlackItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-Bold.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-Bold.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-BoldItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-BoldItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraBold.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraBold.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraBoldItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraBoldItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraLight.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraLight.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraLightItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraLightItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-Italic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-Italic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-Light.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-Light.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-LightItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-LightItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-Medium.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-Medium.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-MediumItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-MediumItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-Regular.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-Regular.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-SemiBold.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-SemiBold.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-SemiBoldItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-SemiBoldItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-Thin.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-Thin.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-ThinItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-ThinItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-italic.var.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-roman.var.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter.var.woff2 +0 -0
- package/dist/ingest/racing-game/a/index.css +1 -0
- package/dist/ingest/racing-game/images/bronze.png +0 -0
- package/dist/ingest/racing-game/images/gold.png +0 -0
- package/dist/ingest/racing-game/images/silver.png +0 -0
- package/dist/ingest/racing-game/index.html +15 -0
- package/dist/ingest/racing-game/models/chassis-draco.glb +0 -0
- package/dist/ingest/racing-game/models/track-draco.glb +0 -0
- package/dist/ingest/racing-game/models/wheel-draco.glb +0 -0
- package/dist/ingest/racing-game/rg-entry.js +1 -0
- package/dist/ingest/racing-game/sounds/accelerate.mp3 +0 -0
- package/dist/ingest/racing-game/sounds/boost.mp3 +0 -0
- package/dist/ingest/racing-game/sounds/crash.mp3 +0 -0
- package/dist/ingest/racing-game/sounds/engine.mp3 +0 -0
- package/dist/ingest/racing-game/sounds/honk.mp3 +0 -0
- package/dist/ingest/racing-game/sounds/tire-brake.mp3 +0 -0
- package/dist/ingest/racing-game/sounds/train.mp3 +0 -0
- package/dist/ingest/racing-game/sounds/water.mp3 +0 -0
- package/dist/ingest/racing-game/textures/cursor.svg +3 -0
- package/dist/ingest/racing-game/textures/dikhololo_night_1k.hdr +0 -0
- package/dist/ingest/racing-game/textures/heightmap_1024.png +0 -0
- package/dist/ingest/racing-game/textures/mask.svg +11 -0
- package/dist/ingest/racing-game/textures/no_sound.png +0 -0
- package/dist/ingest/racing-game/three-r139.module.js +50617 -0
- package/dist/ingest/racing-game/vendor.js +179 -0
- package/dist/ingest/racing-game/vgai.game.json +20 -0
- package/dist/ingest/simcity/UPSTREAM.md +65 -0
- package/dist/ingest/simcity/favicon.ico +0 -0
- package/dist/ingest/simcity/fonts/Bebas-Regular.otf +0 -0
- package/dist/ingest/simcity/fonts/Digitalt.otf +0 -0
- package/dist/ingest/simcity/fonts/Stanberry.ttf +0 -0
- package/dist/ingest/simcity/icons/bulldozer-color.png +0 -0
- package/dist/ingest/simcity/icons/bulldozer.png +0 -0
- package/dist/ingest/simcity/icons/calendar.png +0 -0
- package/dist/ingest/simcity/icons/factory-color.png +0 -0
- package/dist/ingest/simcity/icons/factory.png +0 -0
- package/dist/ingest/simcity/icons/house-color.png +0 -0
- package/dist/ingest/simcity/icons/house.png +0 -0
- package/dist/ingest/simcity/icons/job.png +0 -0
- package/dist/ingest/simcity/icons/office.png +0 -0
- package/dist/ingest/simcity/icons/pause-color.png +0 -0
- package/dist/ingest/simcity/icons/pause.png +0 -0
- package/dist/ingest/simcity/icons/person.png +0 -0
- package/dist/ingest/simcity/icons/play-color.png +0 -0
- package/dist/ingest/simcity/icons/play.png +0 -0
- package/dist/ingest/simcity/icons/power-color.png +0 -0
- package/dist/ingest/simcity/icons/power-line-color.png +0 -0
- package/dist/ingest/simcity/icons/power.png +0 -0
- package/dist/ingest/simcity/icons/road-color.png +0 -0
- package/dist/ingest/simcity/icons/road.png +0 -0
- package/dist/ingest/simcity/icons/select-color.png +0 -0
- package/dist/ingest/simcity/icons/select.png +0 -0
- package/dist/ingest/simcity/icons/store-color.png +0 -0
- package/dist/ingest/simcity/index.html +94 -0
- package/dist/ingest/simcity/index.js +58 -0
- package/dist/ingest/simcity/main.css +233 -0
- package/dist/ingest/simcity/models/armored-truck.glb +0 -0
- package/dist/ingest/simcity/models/building-block-4floor-corner.glb +0 -0
- package/dist/ingest/simcity/models/building-block-4floor-short.glb +0 -0
- package/dist/ingest/simcity/models/building-block-5floor.glb +0 -0
- package/dist/ingest/simcity/models/building-burger-joint.glb +0 -0
- package/dist/ingest/simcity/models/building-cafe.glb +0 -0
- package/dist/ingest/simcity/models/building-casino.glb +0 -0
- package/dist/ingest/simcity/models/building-cinema.glb +0 -0
- package/dist/ingest/simcity/models/building-house-block-big.glb +0 -0
- package/dist/ingest/simcity/models/building-house-family-large.glb +0 -0
- package/dist/ingest/simcity/models/building-house-family-small.glb +0 -0
- package/dist/ingest/simcity/models/building-office-balcony.glb +0 -0
- package/dist/ingest/simcity/models/building-office-big.glb +0 -0
- package/dist/ingest/simcity/models/building-office-pyramid.glb +0 -0
- package/dist/ingest/simcity/models/building-office-tall.glb +0 -0
- package/dist/ingest/simcity/models/building-office.glb +0 -0
- package/dist/ingest/simcity/models/building-restaurant.glb +0 -0
- package/dist/ingest/simcity/models/building-skyscraper.glb +0 -0
- package/dist/ingest/simcity/models/car-ambulance-pickup.glb +0 -0
- package/dist/ingest/simcity/models/car-baywatch.glb +0 -0
- package/dist/ingest/simcity/models/car-hippie-van.glb +0 -0
- package/dist/ingest/simcity/models/car-passenger-race.glb +0 -0
- package/dist/ingest/simcity/models/car-passenger.glb +0 -0
- package/dist/ingest/simcity/models/car-police.glb +0 -0
- package/dist/ingest/simcity/models/car-taxi.glb +0 -0
- package/dist/ingest/simcity/models/car-tow-truck.glb +0 -0
- package/dist/ingest/simcity/models/car-truck-dump.glb +0 -0
- package/dist/ingest/simcity/models/car-veteran.glb +0 -0
- package/dist/ingest/simcity/models/construction-small.glb +0 -0
- package/dist/ingest/simcity/models/data-center.glb +0 -0
- package/dist/ingest/simcity/models/industry-factory-old.glb +0 -0
- package/dist/ingest/simcity/models/industry-factory.glb +0 -0
- package/dist/ingest/simcity/models/industry-refinery.glb +0 -0
- package/dist/ingest/simcity/models/industry-warehouse.glb +0 -0
- package/dist/ingest/simcity/models/power_line_pole_modified.glb +0 -0
- package/dist/ingest/simcity/models/tile-plain_grass.glb +0 -0
- package/dist/ingest/simcity/models/tile-road-curve.glb +0 -0
- package/dist/ingest/simcity/models/tile-road-end.glb +0 -0
- package/dist/ingest/simcity/models/tile-road-intersection-t.glb +0 -0
- package/dist/ingest/simcity/models/tile-road-intersection.glb +0 -0
- package/dist/ingest/simcity/models/tile-road-straight.glb +0 -0
- package/dist/ingest/simcity/models/truck.glb +0 -0
- package/dist/ingest/simcity/statusIcons/no-power.png +0 -0
- package/dist/ingest/simcity/statusIcons/no-road-access.png +0 -0
- package/dist/ingest/simcity/textures/base.png +0 -0
- package/dist/ingest/simcity/textures/grid.png +0 -0
- package/dist/ingest/simcity/textures/specular.png +0 -0
- package/dist/ingest/simcity/three-r155.module.js +51466 -0
- package/dist/ingest/simcity/vgai.game.json +20 -0
- package/dist/ingest/tanks/UPSTREAM.md +69 -0
- package/dist/ingest/tanks/index.html +214 -0
- package/dist/ingest/tanks/models/pixelTank.fbx +0 -0
- package/dist/ingest/tanks/tanks-entry.js +92 -0
- package/dist/ingest/tanks/three-r170.module.js +54571 -0
- package/dist/ingest/tanks/vgai.game.json +20 -0
- package/dist/inputmaps/default.inputmap.json +41 -0
- package/dist/jsm/libs/draco/gltf/draco_decoder.js +33 -0
- package/dist/jsm/libs/draco/gltf/draco_decoder.wasm +0 -0
- package/dist/jsm/libs/draco/gltf/draco_wasm_wrapper.js +116 -0
- package/dist/scenes/untitled.vscn.json +29 -0
- package/dist/textures/waternormals.jpg +0 -0
- package/dist-server/packaged.mjs +18155 -0
- package/package.json +36 -0
- package/server/asset-library-routes.ts +833 -0
- package/server/canonical-path.ts +50 -0
- package/server/cloud-asset-catalog.ts +101 -0
- package/server/dev.ts +350 -0
- package/server/editor-server.ts +1537 -0
- package/server/editor-sse.ts +73 -0
- package/server/game-globals-shadow.ts +58 -0
- package/server/local-asset-catalog.ts +163 -0
- package/server/packaged.ts +571 -0
- package/server/prod.ts +75 -0
- package/server/project-optimize-deps-entries.ts +149 -0
- package/server/project-view.ts +65 -0
- package/server/server-utils.ts +430 -0
- package/server/session-registry.ts +93 -0
- package/src/EditorContext.tsx +87 -0
- package/src/achieved-tier.ts +126 -0
- package/src/action-registry.ts +268 -0
- package/src/adapter-resolver.ts +1858 -0
- package/src/asset-events.ts +100 -0
- package/src/authoring/active-adapter.ts +98 -0
- package/src/authoring/active-systems.ts +50 -0
- package/src/authoring/boundary-authoring-adapter.ts +178 -0
- package/src/authoring/composite-authoring-adapter.ts +891 -0
- package/src/authoring/design-time-layers.ts +776 -0
- package/src/authoring/edit-mode-authoring.ts +643 -0
- package/src/authoring/embed-only-adapter.ts +57 -0
- package/src/authoring/ephemeral-persistence.ts +26 -0
- package/src/authoring/eyedropper-session.ts +69 -0
- package/src/authoring/first-party-hierarchy-menu.ts +74 -0
- package/src/authoring/first-party-inspector/AddSectionMenu.tsx +118 -0
- package/src/authoring/first-party-inspector/EnvironmentSection.tsx +550 -0
- package/src/authoring/first-party-inspector/HeaderSection.tsx +329 -0
- package/src/authoring/first-party-inspector/is-first-party-selection.ts +44 -0
- package/src/authoring/first-party-inspector/register.tsx +108 -0
- package/src/authoring/ingest-authoring-adapter.ts +475 -0
- package/src/authoring/ingest-capture.ts +94 -0
- package/src/authoring/ingest-game-adapter.ts +291 -0
- package/src/authoring/ingest-iframe-adapter.ts +38 -0
- package/src/authoring/ingest-iframe-reachable-adapter.ts +401 -0
- package/src/authoring/ingest-overlay.ts +193 -0
- package/src/authoring/layered-pick.ts +87 -0
- package/src/authoring/mount-failure-report.ts +112 -0
- package/src/authoring/overlay-report.ts +52 -0
- package/src/authoring/project-scenes.ts +111 -0
- package/src/authoring/react-dom-authoring-adapter.ts +795 -0
- package/src/authoring/react-inspector-section.tsx +1041 -0
- package/src/authoring/react-world-authoring-adapter.ts +2400 -0
- package/src/authoring/root-activation.ts +61 -0
- package/src/authoring/scene-ui-layer.ts +485 -0
- package/src/authoring/story-overlay.ts +48 -0
- package/src/authoring/ui-authoring-adapter.ts +325 -0
- package/src/authoring/ui-hierarchy-menu.ts +40 -0
- package/src/authoring/ui-inspector-sections.tsx +156 -0
- package/src/authoring/undo-timeline.ts +145 -0
- package/src/authoring/vgai-scene-authoring-adapter.ts +746 -0
- package/src/authoring/viewport-pick-context.ts +31 -0
- package/src/authoring/viewport-raycast.ts +80 -0
- package/src/authoring/world-overlay.ts +76 -0
- package/src/authoring/world-pan-state.ts +115 -0
- package/src/authoring/world-session-state.ts +102 -0
- package/src/authoring/world2d-authoring-adapter.ts +313 -0
- package/src/browser-external-watch.ts +98 -0
- package/src/browser-play-entry.ts +112 -0
- package/src/browser-project-scripts.ts +112 -0
- package/src/browser-transpile.ts +246 -0
- package/src/browser-watch-reload.ts +100 -0
- package/src/command-listener.ts +326 -0
- package/src/components/AlignToolbar.tsx +189 -0
- package/src/components/AppRoot.tsx +73 -0
- package/src/components/AssetBrowser.tsx +502 -0
- package/src/components/BottomPanel.tsx +326 -0
- package/src/components/BuildPanel.tsx +455 -0
- package/src/components/CameraInfo.tsx +56 -0
- package/src/components/CommandPalette.tsx +265 -0
- package/src/components/ConsolePanel.tsx +272 -0
- package/src/components/DataPanel.tsx +1268 -0
- package/src/components/DefaultEditorLayout.tsx +509 -0
- package/src/components/FolderTree.tsx +179 -0
- package/src/components/GameHierarchy.tsx +1349 -0
- package/src/components/Inspector.tsx +671 -0
- package/src/components/InspectorToolSection.tsx +74 -0
- package/src/components/MountFailureBanner.tsx +110 -0
- package/src/components/NetworkSection.tsx +109 -0
- package/src/components/OnlineAssetBrowser.tsx +448 -0
- package/src/components/OverlayReportBanner.tsx +122 -0
- package/src/components/PlayBar.tsx +356 -0
- package/src/components/PrefabSaveDialog.tsx +201 -0
- package/src/components/ProjectHeader.tsx +174 -0
- package/src/components/ProjectScreen.tsx +1004 -0
- package/src/components/RightPanel.tsx +166 -0
- package/src/components/RootActivateDialog.tsx +105 -0
- package/src/components/SaveDialog.tsx +197 -0
- package/src/components/SaveStatus.tsx +69 -0
- package/src/components/StatsOverlay.tsx +35 -0
- package/src/components/ToolHost.tsx +80 -0
- package/src/components/Toolbar.tsx +307 -0
- package/src/components/ViewportOverlay.tsx +234 -0
- package/src/components/ViewportPanel.tsx +369 -0
- package/src/components/WorldContextMenu.tsx +257 -0
- package/src/components/WorldSelectionOverlay.tsx +2003 -0
- package/src/components/WorldTextEditor.tsx +86 -0
- package/src/components/asset-viewers/AssetViewer.tsx +38 -0
- package/src/components/asset-viewers/AudioViewer.tsx +149 -0
- package/src/components/asset-viewers/GenericJsonViewer.tsx +77 -0
- package/src/components/asset-viewers/ImageViewer.tsx +65 -0
- package/src/components/asset-viewers/InputMapViewer.tsx +206 -0
- package/src/components/asset-viewers/ModelViewer.tsx +180 -0
- package/src/components/asset-viewers/OnlineAssetDetail.tsx +236 -0
- package/src/components/data-csv.ts +301 -0
- package/src/components/entity-name-commit.ts +18 -0
- package/src/components/inspector-property-grouping.ts +65 -0
- package/src/components/inspector-widgets/AlignmentGrid.tsx +188 -0
- package/src/components/inspector-widgets/BorderEditor.tsx +305 -0
- package/src/components/inspector-widgets/ColorPicker.tsx +548 -0
- package/src/components/inspector-widgets/FilterEditor.tsx +108 -0
- package/src/components/inspector-widgets/FontPicker.tsx +196 -0
- package/src/components/inspector-widgets/GradientEditor.tsx +277 -0
- package/src/components/inspector-widgets/ScrubbableInput.tsx +152 -0
- package/src/components/inspector-widgets/ShadowEditor.tsx +332 -0
- package/src/components/inspector-widgets/color-utils.ts +202 -0
- package/src/components/inspector-widgets/gallery-main.tsx +167 -0
- package/src/components/inspector-widgets/index.ts +22 -0
- package/src/components/inspector-widgets/shared.tsx +135 -0
- package/src/components/inspectors/AnimationSection.tsx +143 -0
- package/src/components/inspectors/AudioSection.tsx +215 -0
- package/src/components/inspectors/BoneAttachmentSection.tsx +82 -0
- package/src/components/inspectors/CameraSection.tsx +105 -0
- package/src/components/inspectors/ComponentsSection.tsx +358 -0
- package/src/components/inspectors/JointsSection.tsx +255 -0
- package/src/components/inspectors/LightSection.tsx +116 -0
- package/src/components/inspectors/MaterialSection.tsx +1189 -0
- package/src/components/inspectors/MeshSection.tsx +99 -0
- package/src/components/inspectors/NavigationSection.tsx +58 -0
- package/src/components/inspectors/ParticleSection.tsx +681 -0
- package/src/components/inspectors/PhysicsSection.tsx +326 -0
- package/src/components/inspectors/PostProcessingSection.tsx +1290 -0
- package/src/components/inspectors/RenderingSection.tsx +109 -0
- package/src/components/inspectors/ShadowSection.tsx +84 -0
- package/src/components/inspectors/SplineSection.tsx +191 -0
- package/src/components/inspectors/TransformSection.tsx +191 -0
- package/src/components/inspectors/json-schema-fields.ts +281 -0
- package/src/components/inspectors/multi-edit-utils.ts +49 -0
- package/src/components/inspectors/zod-fields.ts +184 -0
- package/src/components/primitives/ColorInput.tsx +109 -0
- package/src/components/primitives/JsonInput.tsx +98 -0
- package/src/components/primitives/NumberInput.tsx +164 -0
- package/src/components/primitives/Panel.tsx +68 -0
- package/src/components/primitives/SectionHeader.tsx +45 -0
- package/src/components/primitives/Tooltip.tsx +82 -0
- package/src/components/primitives/Vec3Input.tsx +62 -0
- package/src/components/world-overlay-gestures.ts +1438 -0
- package/src/doctor/data-checks.ts +285 -0
- package/src/doctor/report.ts +386 -0
- package/src/editor-api.ts +700 -0
- package/src/editor-console.ts +194 -0
- package/src/editor-hotkeys.ts +441 -0
- package/src/editor-layers.ts +2 -0
- package/src/editor-mode.ts +28 -0
- package/src/editor-store.ts +2225 -0
- package/src/editor-viewport.ts +1924 -0
- package/src/entity-factory.ts +221 -0
- package/src/game-globals-prelude.ts +27 -0
- package/src/gated-globals.ts +131 -0
- package/src/generated/setup-by-example.generated.ts +53 -0
- package/src/gizmo-registry.ts +608 -0
- package/src/handle-system.ts +221 -0
- package/src/hierarchy-menu-registry.ts +70 -0
- package/src/hierarchy-rows.ts +73 -0
- package/src/hosted-example.ts +160 -0
- package/src/hotkeys.ts +142 -0
- package/src/ingest/discovery-react.ts +132 -0
- package/src/ingest/discovery.ts +205 -0
- package/src/ingest/discovery2d.ts +204 -0
- package/src/ingest/esm-entry-resolution.ts +102 -0
- package/src/ingest/games/bloom-composer/game.js +99 -0
- package/src/ingest/games/bloom-composer/vgai.game.json +21 -0
- package/src/ingest/games/embed-bundle/embed.html +10 -0
- package/src/ingest/games/embed-bundle/vgai.game.json +21 -0
- package/src/ingest/games/games-fps/assets/collision-world.glb +0 -0
- package/src/ingest/games/games-fps/game.js +479 -0
- package/src/ingest/games/games-fps/vgai.game.json +24 -0
- package/src/ingest/games/iframe-reachable/game-source.js +58 -0
- package/src/ingest/games/iframe-reachable/vgai.game.json +21 -0
- package/src/ingest/games/interactive-cubes/game.js +139 -0
- package/src/ingest/games/interactive-cubes/vgai.game.json +21 -0
- package/src/ingest/games/mismatched-three/fake-three.js +38 -0
- package/src/ingest/games/mismatched-three/game.js +10 -0
- package/src/ingest/games/mismatched-three/vgai.game.json +22 -0
- package/src/ingest/games/own-three-bundled/game.js +43 -0
- package/src/ingest/games/own-three-bundled/vgai.game.json +21 -0
- package/src/ingest/games/pointerlock-maze/game.js +288 -0
- package/src/ingest/games/pointerlock-maze/vgai.game.json +22 -0
- package/src/ingest/games-2d/_fixtures/first-party-scene.scn2d.json +124 -0
- package/src/ingest/games-2d/bubbo-bubbo/vgai.game.json +25 -0
- package/src/ingest/games-2d/bunnymark/game.ts +88 -0
- package/src/ingest/games-2d/bunnymark/vgai.game.json +21 -0
- package/src/ingest/games-2d/flappy/game.ts +148 -0
- package/src/ingest/games-2d/flappy/vgai.game.json +21 -0
- package/src/ingest/games-2d/flappy-pixi/vgai.game.json +25 -0
- package/src/ingest/games-2d/puzzling-potions/vgai.game.json +25 -0
- package/src/ingest/games-2d/tilemap-camera/game.ts +115 -0
- package/src/ingest/games-2d/tilemap-camera/vgai.game.json +21 -0
- package/src/ingest/games-react/react-rpg/entry.tsx +185 -0
- package/src/ingest/games-react/react-rpg/shims.d.ts +26 -0
- package/src/ingest/games-react/react-rpg/src/ui/game-state.tsx +129 -0
- package/src/ingest/games-react/react-rpg/vgai.game.json +21 -0
- package/src/ingest/host-namespace-registry.ts +196 -0
- package/src/ingest/types.ts +127 -0
- package/src/ingest-hierarchy-rows.ts +207 -0
- package/src/ingest-mode.ts +2092 -0
- package/src/ingest-siblings.ts +466 -0
- package/src/initial-scene.ts +194 -0
- package/src/inspector-section-registry.ts +117 -0
- package/src/interactive-edit-scope.ts +34 -0
- package/src/main.tsx +23 -0
- package/src/manifest-project.ts +43 -0
- package/src/model-thumbnail.ts +156 -0
- package/src/module-mode.ts +513 -0
- package/src/navmesh-handler.ts +94 -0
- package/src/packaged-runtime.ts +49 -0
- package/src/play-mode.ts +1148 -0
- package/src/playground/main.ts +206 -0
- package/src/project-manager.ts +173 -0
- package/src/scene-diff.ts +112 -0
- package/src/scene-serializer.ts +144 -0
- package/src/scene-sync.ts +587 -0
- package/src/scene-ui-mode/active-scene-ui.ts +58 -0
- package/src/scene-ui-mode/scene-ui-palette-panel.tsx +152 -0
- package/src/scene-ui-mode/scene-ui-timeline-tab.tsx +29 -0
- package/src/scene-ui-mode/ui-prefab-dialog-host.tsx +74 -0
- package/src/scene-ui-mode/ui-prefab-hierarchy-menu.ts +49 -0
- package/src/scene-ui-mode/ui-prefab-io.ts +76 -0
- package/src/script-loader.ts +143 -0
- package/src/storage/fsa-storage.ts +55 -0
- package/src/storage/handle-storage.ts +150 -0
- package/src/storage/handle-store.ts +116 -0
- package/src/storage/http-storage.ts +137 -0
- package/src/storage/index.ts +147 -0
- package/src/storage/mem-storage.ts +135 -0
- package/src/storage/opfs-storage.ts +25 -0
- package/src/storage/paths.ts +26 -0
- package/src/storage/seed.ts +106 -0
- package/src/storage/types.ts +110 -0
- package/src/tool-loader.ts +291 -0
- package/src/ui-editor/inspector.tsx +563 -0
- package/src/ui-editor/node-ops.ts +266 -0
- package/src/ui-editor/overlay.tsx +380 -0
- package/src/ui-editor/palette.tsx +114 -0
- package/src/ui-editor/react-store.ts +78 -0
- package/src/ui-editor/timeline-panel.tsx +443 -0
- package/src/ui-editor/ui-edit-store.ts +552 -0
- package/src/ui-editor/ui-prefab.ts +95 -0
- package/src/ui-source/inspect.ts +544 -0
- package/src/ui-source/oid-transform.ts +133 -0
- package/src/ui-source/source-write-backend.ts +203 -0
- package/src/ui-source/tailwind-map.ts +174 -0
- package/src/ui-source/writer.ts +1175 -0
- package/src/widgets/index.ts +73 -0
- package/template/.claude/skills/editor/SKILL.md +252 -0
- package/template/CLAUDE.md +512 -0
- package/template/LICENSE +30 -0
- package/template/emit-schemas.ts +42 -0
- package/template/index.html +17 -0
- package/template/package.json +53 -0
- package/template/project.json +9 -0
- package/template/public/audio/ambient-arena.wav +0 -0
- package/template/public/audio/ambient-music.wav +0 -0
- package/template/public/audio/ambient-nature.wav +0 -0
- package/template/public/audio/ambient-wind.wav +0 -0
- package/template/public/inputmaps/default.inputmap.json +41 -0
- package/template/public/jsm/libs/draco/gltf/draco_decoder.js +33 -0
- package/template/public/jsm/libs/draco/gltf/draco_decoder.wasm +0 -0
- package/template/public/jsm/libs/draco/gltf/draco_encoder.js +33 -0
- package/template/public/jsm/libs/draco/gltf/draco_wasm_wrapper.js +116 -0
- package/template/public/scenes/default.vscn.json +43 -0
- package/template/public/scenes/untitled.vscn.json +29 -0
- package/template/public/textures/waternormals.jpg +0 -0
- package/template/server/colyseus-setup.ts +85 -0
- package/template/server/main.ts +34 -0
- package/template/server/rooms/arena-room.ts +420 -0
- package/template/server/rooms/game-room.ts +162 -0
- package/template/server/rooms.ts +15 -0
- package/template/server/tsconfig.json +19 -0
- package/template/src/data/README.md +40 -0
- package/template/src/data/assets.ts +31 -0
- package/template/src/data/tuning.data.json +5 -0
- package/template/src/data/tuning.schema.json +21 -0
- package/template/src/data/tuning.schema.ts +34 -0
- package/template/src/data/tuning.ts +24 -0
- package/template/src/main.ts +157 -0
- package/template/src/scripts/components/data-spinner.ts +45 -0
- package/template/src/scripts/components/scene-camera.ts +40 -0
- package/template/src/scripts/main.ts +94 -0
- package/template/src/scripts/registry.ts +46 -0
- package/template/src/tools/README.md +14 -0
- package/template/src/tools/example.tool.tsx +91 -0
- package/template/src/tools/spin.tool.tsx +101 -0
- package/template/src/ui/Button.tsx +27 -0
- package/template/src/ui/DefaultHud.tsx +40 -0
- package/template/src/ui/KeyHint.tsx +25 -0
- package/template/src/ui/Panel.tsx +52 -0
- package/template/src/ui/RebindPanel.tsx +129 -0
- package/template/src/ui/Slider.tsx +28 -0
- package/template/src/ui/StatusDot.tsx +22 -0
- package/template/src/ui/Toggle.tsx +19 -0
- package/template/src/ui/game-state.tsx +37 -0
- package/template/src/ui/index.ts +7 -0
- package/template/src/ui/mount-default-hud.tsx +19 -0
- package/template/src/ui/mount-ui.ts +15 -0
- package/template/tsconfig.json +36 -0
- package/template/validate-asset-content.ts +78 -0
- package/template/validate-manifest.ts +55 -0
- package/template/validate-scenes.ts +211 -0
- package/template/vgai.game.json +21 -0
- package/template/vite.config.ts +128 -0
- package/vite-plugin-example-assets.ts +60 -0
- package/vite-plugin-game-globals.ts +46 -0
- package/vite-plugin-game-static.ts +71 -0
- package/vite-plugin-project-game-static.ts +84 -0
- package/vite-plugin-react-game-static.ts +98 -0
- package/vite-plugin-react-world-provider.ts +81 -0
- package/vite-plugin-react-world-runtime.ts +76 -0
- package/vite-plugin-ui-oid.ts +561 -0
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Composite sibling mounts — D-V2/D-V3/D-V4/D-V7
|
|
3
|
+
* (docs/WAVE5-MULTIWORLD-INGEST-DESIGN.md, F24 residual), extended by D-B1/
|
|
4
|
+
* D-B3 (docs/WAVE6-DOCTOR-DESIGN.md Slice B, wave-5 residual closure): when a
|
|
5
|
+
* manifest declares exactly one `{ ingest }` world alongside OTHER worlds
|
|
6
|
+
* ("siblings"), `ingest-mode.ts`'s composite routes mount the ingest world
|
|
7
|
+
* through their existing enter path and then call {@link mountIngestSiblings}
|
|
8
|
+
* here to mount every OTHER world in manifest order (already z-sorted by the
|
|
9
|
+
* loader), degrading loudly per world rather than silently ignoring them
|
|
10
|
+
* (the F24/F9 anti-pattern this closes):
|
|
11
|
+
*
|
|
12
|
+
* - `default-react` — a bare (no `<GameProvider>`) DOM layer, D-V3. There is
|
|
13
|
+
* no native `Game` beside an ingest world, so wrapping one would fabricate
|
|
14
|
+
* first-party data (anti-shim) — the sibling's own `useGameState`/
|
|
15
|
+
* `useGame` calls are simply unavailable, and this says so once per world.
|
|
16
|
+
* Has no loop at all, so its `SiblingMount.setPaused` stays `undefined`
|
|
17
|
+
* (D-B1) — never a fabricated no-op.
|
|
18
|
+
* - `{ module }` (any kind) OR a native `default-three`/`default-pixi` world
|
|
19
|
+
* (D-B3 — the wave-5 residual this closes: a second `{ ingest }` world is
|
|
20
|
+
* structurally unreachable here, D-V2 already fails the whole route
|
|
21
|
+
* before any sibling loop runs, so `resolveAllWorlds`'s one-element call
|
|
22
|
+
* below never hits its `MULTI_WORLD_ALLOWED_IDENTITIES` guard, which only
|
|
23
|
+
* applies for `worlds.length > 1`) — a SCOPED single-world runtime, D-V4:
|
|
24
|
+
* the exact resolve+mount `module-mode.ts`'s `enterModuleModeWorldsPath`
|
|
25
|
+
* proves out, reused via `mountModuleWorldRuntime`, but with NONE of that
|
|
26
|
+
* route's global side effects (no `setActiveAuthoring`/`setActiveSystems`,
|
|
27
|
+
* no viewport-tab switch, no `__vgaiModule` hook, no play-surface writes).
|
|
28
|
+
* Its `SiblingMount.setPaused` (D-B1) is wired straight from the scoped
|
|
29
|
+
* `GameSession.pause()`/`.resume()` `mountModuleWorldRuntime` hands back —
|
|
30
|
+
* the SAME honest, already-existing capability `play-mode.ts`'s own
|
|
31
|
+
* `pausePlayMode`/`resumePlayMode` call on the PRIMARY route's session
|
|
32
|
+
* (`create-runtime.ts`'s `Game.play.pause()`/`.resume()`, gated per
|
|
33
|
+
* `pausable` world) — never a fabricated pause.
|
|
34
|
+
* - anything else — `rejectUnmountableSibling`'s named, loud non-goal for a
|
|
35
|
+
* genuinely unroutable shape (there is none known today; every currently
|
|
36
|
+
* resolvable identity is handled above): a per-world mount-failure entry,
|
|
37
|
+
* never silence.
|
|
38
|
+
*
|
|
39
|
+
* A failing sibling NEVER unmounts the ingest world: each failure is caught
|
|
40
|
+
* individually and appended to the (D-V5) multi-entry mount-failure report;
|
|
41
|
+
* the loop continues to the next sibling. Every successfully-mounted
|
|
42
|
+
* sibling's handle is returned so `ingest-mode.ts` can store it on the
|
|
43
|
+
* unified `_active` slot, dispose it (reverse order) on exit, and fan pause/
|
|
44
|
+
* resume out to it (D-B2, `getIngestPlayControl()`).
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
import type { AuthoringAdapter } from '@engine/adapter';
|
|
48
|
+
import type { ResolvedGameManifest, ResolvedWorldEntry } from '@engine/manifest/load';
|
|
49
|
+
import type { Overlay2D } from '@engine/world2d/authoring-2d';
|
|
50
|
+
import { createPhysics2DRegistry } from '@engine/world2d/physics2d-registry';
|
|
51
|
+
import { createPhysicsAdapter2D } from '@engine/world2d/system-adapters-2d';
|
|
52
|
+
import { Component, createElement, type ReactNode } from 'react';
|
|
53
|
+
import { flushSync } from 'react-dom';
|
|
54
|
+
import { createRoot } from 'react-dom/client';
|
|
55
|
+
import { resolveReactWorldEntryComponent } from './adapter-resolver';
|
|
56
|
+
import { makeEmbedOnlyAdapter } from './authoring/embed-only-adapter';
|
|
57
|
+
import { IngestAuthoringAdapter } from './authoring/ingest-authoring-adapter';
|
|
58
|
+
import type { IngestOverlay } from './authoring/ingest-overlay';
|
|
59
|
+
import { addMountFailureReport, formatMountFailureMessage } from './authoring/mount-failure-report';
|
|
60
|
+
import {
|
|
61
|
+
ReactDomAuthoringAdapter,
|
|
62
|
+
type ReactDomOverlay,
|
|
63
|
+
} from './authoring/react-dom-authoring-adapter';
|
|
64
|
+
import { ReactWorldAuthoringAdapter, walkOidTree } from './authoring/react-world-authoring-adapter';
|
|
65
|
+
import { unregisterUndoAdapter } from './authoring/undo-timeline';
|
|
66
|
+
import { loadWorldOverlay, saveWorldOverlay, worldOverlayPath } from './authoring/world-overlay';
|
|
67
|
+
import { World2DAuthoringAdapter } from './authoring/world2d-authoring-adapter';
|
|
68
|
+
import { editorConsole } from './editor-console';
|
|
69
|
+
import type { EditorStore } from './editor-store';
|
|
70
|
+
import { mountModuleWorldRuntime, withStoreSelection } from './module-mode';
|
|
71
|
+
import { createHttpSourceWriteBackend } from './ui-source/source-write-backend';
|
|
72
|
+
|
|
73
|
+
/** One mounted sibling world's handle — stored on `ingest-mode.ts`'s unified
|
|
74
|
+
* `_active` slot and disposed (reverse order) by `exitActiveIngest()`. */
|
|
75
|
+
export interface SiblingMount {
|
|
76
|
+
readonly worldId: string;
|
|
77
|
+
/**
|
|
78
|
+
* D-B1 (docs/WAVE6-DOCTOR-DESIGN.md Slice B): an OPTIONAL, honest pause
|
|
79
|
+
* capability — present only when this sibling's own mounted session
|
|
80
|
+
* exposes a REAL pause (today: every `mountScopedRuntimeSibling` kind, via
|
|
81
|
+
* its scoped `GameSession.pause()`/`.resume()`). Absent (never a fake
|
|
82
|
+
* no-op) for a kind with no loop to gate at all — the bare `default-react`
|
|
83
|
+
* sibling. `getIngestPlayControl()` (`ingest-mode.ts`, D-B2) calls this
|
|
84
|
+
* `?.`-optionally after its own primary-surface call, mirroring the
|
|
85
|
+
* reverse-order dispose fan-out below.
|
|
86
|
+
*/
|
|
87
|
+
setPaused?(paused: boolean): void;
|
|
88
|
+
/**
|
|
89
|
+
* D-E1 (docs/WAVE8-SIBLING-AUTHORING-DESIGN.md): an OPTIONAL, HONEST
|
|
90
|
+
* authoring surface for this sibling — present iff `store` was supplied to
|
|
91
|
+
* {@link mountIngestSiblings} (every production caller supplies one; the
|
|
92
|
+
* parameter stays optional only so the one pre-existing test that calls
|
|
93
|
+
* this module directly without a store keeps compiling/running unchanged).
|
|
94
|
+
* When present, it is NEVER a fabricated stand-in:
|
|
95
|
+
* - a scoped-runtime sibling (`{ module }`, or a native
|
|
96
|
+
* `default-three`/`default-pixi` world) gets its OWN mounted
|
|
97
|
+
* `AuthoringAdapter` (`mounted.authoring`), store-selection-wrapped
|
|
98
|
+
* (`withStoreSelection`) — or, when it exposes none, the SAME named
|
|
99
|
+
* embed-only floor `enterModuleModeWorldsPath` uses
|
|
100
|
+
* (`makeEmbedOnlyAdapter`), so the world still appears in the hierarchy
|
|
101
|
+
* as a group with its honest label, never silently omitted.
|
|
102
|
+
* - a bare `default-react` sibling gets a {@link ReactWorldAuthoringAdapter}
|
|
103
|
+
* over its own layer DOM root — an un-instrumented entry (no
|
|
104
|
+
* `data-oid`) yields an EMPTY subtree under its group node, never
|
|
105
|
+
* fabricated nodes.
|
|
106
|
+
* `ingest-mode.ts`'s composite routes promote to a `CompositeAuthoringAdapter`
|
|
107
|
+
* over the primary + every sibling's `authoring` once any sibling mounts
|
|
108
|
+
* (D-E2); a `SiblingMount` with no `authoring` (store omitted) simply
|
|
109
|
+
* cannot participate in that promotion.
|
|
110
|
+
*/
|
|
111
|
+
readonly authoring?: AuthoringAdapter;
|
|
112
|
+
dispose(): void;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Minimal class-component error boundary (D-V3): a react sibling mounts with
|
|
117
|
+
* NO `<GameProvider>` (there is no native `Game` beside an ingest world), so
|
|
118
|
+
* a sibling component that calls `useGame`/`useGameState` anyway throws
|
|
119
|
+
* synchronously at first render — an error boundary is the only way React
|
|
120
|
+
* lets a HOST catch that (there is no hook equivalent), and it lets ONE
|
|
121
|
+
* misbehaving sibling fail loudly (via `onCaught`) without ever throwing out
|
|
122
|
+
* of `root.render()` uncaught or crashing any OTHER sibling / the ingest
|
|
123
|
+
* world itself.
|
|
124
|
+
*/
|
|
125
|
+
class SiblingErrorBoundary extends Component<
|
|
126
|
+
{ onCaught: (error: unknown) => void; children: ReactNode },
|
|
127
|
+
{ crashed: boolean }
|
|
128
|
+
> {
|
|
129
|
+
override state = { crashed: false };
|
|
130
|
+
static getDerivedStateFromError(): { crashed: boolean } {
|
|
131
|
+
return { crashed: true };
|
|
132
|
+
}
|
|
133
|
+
override componentDidCatch(error: unknown): void {
|
|
134
|
+
this.props.onCaught(error);
|
|
135
|
+
}
|
|
136
|
+
override render(): ReactNode {
|
|
137
|
+
return this.state.crashed ? null : this.props.children;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Position/size/z-order every sibling layer shares — same absolute-fill
|
|
142
|
+
* convention `create-runtime.ts`'s own react-world DOM layer uses
|
|
143
|
+
* (`mountOneReactWorld`), so a sibling stacks exactly like a native world
|
|
144
|
+
* would have. `pointerEvents` differs per sibling kind below (module worlds
|
|
145
|
+
* own real canvases that need real hit-testing; the react sibling's layer
|
|
146
|
+
* stays `none` by the same HUD-overlay convention `mountOneReactWorld` uses,
|
|
147
|
+
* its rendered content opts back in per-element). */
|
|
148
|
+
function styleSiblingLayer(
|
|
149
|
+
layer: HTMLElement,
|
|
150
|
+
world: ResolvedWorldEntry,
|
|
151
|
+
pointerEvents: string,
|
|
152
|
+
): void {
|
|
153
|
+
layer.style.position = 'absolute';
|
|
154
|
+
layer.style.top = '0';
|
|
155
|
+
layer.style.left = '0';
|
|
156
|
+
layer.style.width = '100%';
|
|
157
|
+
layer.style.height = '100%';
|
|
158
|
+
layer.style.zIndex = String(world.zOrder ?? 0);
|
|
159
|
+
layer.style.pointerEvents = pointerEvents;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* D-V3: mount a `default-react` sibling as a bare DOM layer — no
|
|
164
|
+
* `<GameProvider>` wrap (anti-shim: there is no native `Game` to hand it).
|
|
165
|
+
* Reuses `adapter-resolver.ts`'s exact entry-loading step
|
|
166
|
+
* (`resolveReactWorldEntryComponent` — the same D4 `scene`-forbidden/
|
|
167
|
+
* `entry`-required checks and default-export validation
|
|
168
|
+
* `resolveDefaultReactAdapter` itself uses) rather than duplicating it.
|
|
169
|
+
*
|
|
170
|
+
* If the entry component throws at mount/first render (most commonly: it
|
|
171
|
+
* calls `useGame`/`useGameState` with no provider in the tree), the error
|
|
172
|
+
* boundary above catches it, this function removes the layer it just
|
|
173
|
+
* created, and RETHROWS so the caller's uniform per-sibling catch
|
|
174
|
+
* (`mountOneIngestSibling`'s caller, {@link mountIngestSiblings}) can turn it
|
|
175
|
+
* into a named mount-failure-report entry for THIS world, exactly like any
|
|
176
|
+
* other sibling failure — the ingest world stays mounted regardless.
|
|
177
|
+
*/
|
|
178
|
+
async function mountDefaultReactSibling(
|
|
179
|
+
world: ResolvedWorldEntry,
|
|
180
|
+
gameContainer: HTMLElement,
|
|
181
|
+
projectRoot: string,
|
|
182
|
+
store: EditorStore | undefined,
|
|
183
|
+
): Promise<SiblingMount> {
|
|
184
|
+
const Entry = await resolveReactWorldEntryComponent(world, projectRoot);
|
|
185
|
+
|
|
186
|
+
const layer = document.createElement('div');
|
|
187
|
+
styleSiblingLayer(layer, world, 'none');
|
|
188
|
+
gameContainer.appendChild(layer);
|
|
189
|
+
|
|
190
|
+
// D-V3: one structured note per world naming the degrade — never silent
|
|
191
|
+
// about the missing bridge.
|
|
192
|
+
editorConsole.log(
|
|
193
|
+
`react sibling "${world.id}": no native Game beside an ingest world — useGameState unavailable`,
|
|
194
|
+
'ingest',
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
const root = createRoot(layer);
|
|
198
|
+
let caught: unknown = null;
|
|
199
|
+
// `flushSync` forces the initial mount (render + commit + error-boundary
|
|
200
|
+
// recovery, if the entry throws) to complete SYNCHRONOUSLY, so `caught` is
|
|
201
|
+
// reliably populated by the time this call returns — react-dom's default
|
|
202
|
+
// scheduler does not guarantee that for a bare `root.render()` call (a
|
|
203
|
+
// production API, not a test-only affordance: this host needs the throw/
|
|
204
|
+
// no-throw outcome immediately, to decide whether to keep the layer or
|
|
205
|
+
// tear it down and report a named failure).
|
|
206
|
+
flushSync(() => {
|
|
207
|
+
root.render(
|
|
208
|
+
createElement(SiblingErrorBoundary, {
|
|
209
|
+
onCaught: (error: unknown) => {
|
|
210
|
+
caught = error;
|
|
211
|
+
},
|
|
212
|
+
children: createElement(Entry),
|
|
213
|
+
}),
|
|
214
|
+
);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
if (caught !== null) {
|
|
218
|
+
root.unmount();
|
|
219
|
+
layer.remove();
|
|
220
|
+
throw caught instanceof Error ? caught : new Error(String(caught));
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// D-M1 (docs/WAVE11-REACT-SIBLING-DOM-FALLBACK-DESIGN.md): once mounted,
|
|
224
|
+
// probe the REAL committed layer with `walkOidTree` — a partially-stamped
|
|
225
|
+
// tree counts as stamped (the walk's transparent-skip contract already
|
|
226
|
+
// handles instrumentation gaps), so this is a single "any node at all?"
|
|
227
|
+
// check, not a completeness one. A non-empty tree keeps the exact D-E1
|
|
228
|
+
// construction below (a STAMPED first-party entry — the sanctioned JSX
|
|
229
|
+
// write-back seam). A genuinely EMPTY tree (no `data-oid` reachable at
|
|
230
|
+
// all — e.g. an ingest-REACT sibling swept into the D-Y4 foreign-game
|
|
231
|
+
// exclusion, or an entry that rendered nothing yet) falls back to
|
|
232
|
+
// `ReactDomAuthoringAdapter`: an overlay-only surface over the live DOM,
|
|
233
|
+
// no `SourceWriteBackend` of any kind — never a write seam over source the
|
|
234
|
+
// stamping pipeline declined to instrument. Only computed when a store was
|
|
235
|
+
// supplied (see `SiblingMount.authoring`'s doc comment) — never fabricated.
|
|
236
|
+
let authoring: ReactWorldAuthoringAdapter | ReactDomAuthoringAdapter | undefined;
|
|
237
|
+
if (store) {
|
|
238
|
+
const stamped = walkOidTree(layer).nodes.size > 0;
|
|
239
|
+
authoring = stamped
|
|
240
|
+
? // D-E1: the exact play-mode react-branch construction (`play-mode.ts`
|
|
241
|
+
// `installMultiWorldAuthoring` :341-347) over THIS sibling's own layer
|
|
242
|
+
// DOM root — no `<GameProvider>`/`ReactWorldHost` required.
|
|
243
|
+
new ReactWorldAuthoringAdapter(layer, store, {
|
|
244
|
+
...(import.meta.env.DEV ? { writeBackend: createHttpSourceWriteBackend() } : {}),
|
|
245
|
+
})
|
|
246
|
+
: new ReactDomAuthoringAdapter(layer, store, {
|
|
247
|
+
overlay: await loadWorldOverlay<ReactDomOverlay>(world.id),
|
|
248
|
+
persist: (o) => saveWorldOverlay(world.id, o),
|
|
249
|
+
destination: worldOverlayPath(world.id),
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
worldId: world.id,
|
|
255
|
+
...(authoring ? { authoring } : {}),
|
|
256
|
+
dispose(): void {
|
|
257
|
+
// D-M3: both branches above self-register with the global undo
|
|
258
|
+
// timeline at construction (`registerUndoAdapter`, in each adapter's
|
|
259
|
+
// own constructor) — drop it here so any of its entries still on the
|
|
260
|
+
// global stack are dropped (with a warn) rather than misapplied to a
|
|
261
|
+
// torn-down layer, mirroring every other undo-registering adapter's
|
|
262
|
+
// teardown in this codebase (`disposeReactSession`/`disposeThreejsSession`
|
|
263
|
+
// in `ingest-mode.ts`, the scene-UI child's `UIEditStore` unregister
|
|
264
|
+
// in `edit-mode-authoring.ts`'s `exitEditModeAuthoring`).
|
|
265
|
+
if (authoring) unregisterUndoAdapter(authoring);
|
|
266
|
+
root.unmount();
|
|
267
|
+
layer.remove();
|
|
268
|
+
},
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* D-V4/D-B3: mount a `{ module }` sibling (any kind) OR a native
|
|
274
|
+
* `default-three`/`default-pixi` sibling through the SCOPED single-world
|
|
275
|
+
* runtime `module-mode.ts`'s `mountModuleWorldRuntime` provides — the exact
|
|
276
|
+
* resolve+mount `enterModuleModeWorldsPath` performs, reused here with NONE
|
|
277
|
+
* of that route's global side effects: no `setActiveAuthoring`/
|
|
278
|
+
* `setActiveSystems`, no viewport-tab switch, no `__vgaiModule` hook, no
|
|
279
|
+
* mount-failure-report clear. Mounts into its OWN layer div (never straight
|
|
280
|
+
* into the shared `gameContainer` the way the primary/single-world routes
|
|
281
|
+
* do) so its internal canvas/DOM stacking never collides with the ingest
|
|
282
|
+
* world's own canvas/DOM layer already in that container.
|
|
283
|
+
*
|
|
284
|
+
* `mountModuleWorldRuntime` is kind-agnostic BY CONSTRUCTION here, not just
|
|
285
|
+
* for `{ module }` adapters: it calls `resolveAllWorlds` with a ONE-element
|
|
286
|
+
* `worlds` array, and `resolveAllWorlds`'s `MULTI_WORLD_ALLOWED_IDENTITIES`
|
|
287
|
+
* scope guard only fires for `worlds.length > 1` — so `resolveWorldAdapter`'s
|
|
288
|
+
* full identity switch (including `default-three`/`default-pixi`) is reached
|
|
289
|
+
* directly, unmodified, with the guard never consulted (D-B3's ground-truth
|
|
290
|
+
* finding; `MULTI_WORLD_ALLOWED_IDENTITIES` itself stays byte-identical).
|
|
291
|
+
*
|
|
292
|
+
* D-B1: the returned `SiblingMount.setPaused` calls the scoped
|
|
293
|
+
* `GameSession.pause()`/`.resume()` `mountModuleWorldRuntime` hands back —
|
|
294
|
+
* the SAME honest, already-existing capability `play-mode.ts`'s own
|
|
295
|
+
* `pausePlayMode`/`resumePlayMode` call on the PRIMARY route's session
|
|
296
|
+
* (`create-runtime.ts`'s `Game.play.pause()`/`.resume()`, gated per
|
|
297
|
+
* `pausable` world within this scoped, single-world `Game`) — never a
|
|
298
|
+
* fabricated no-op.
|
|
299
|
+
*
|
|
300
|
+
* D-G1 (docs/WAVE9-SIBLING-PERSISTENCE-DOCTOR-DESIGN.md §1, closing the
|
|
301
|
+
* wave-8 residual this doc comment used to name): the mounted world's
|
|
302
|
+
* authoring surface converges with play-mode's own multi-world construction
|
|
303
|
+
* (`play-mode.ts`'s `installMultiWorldAuthoring`, verbatim per branch):
|
|
304
|
+
* 1. `mounted.authoring` present (a `{ module }` adapter that ships its own
|
|
305
|
+
* surface, with its own persistence if any) — kept byte-for-byte,
|
|
306
|
+
* store-selection-wrapped, exactly as before this slice. NEVER wrapped
|
|
307
|
+
* in an editor overlay: the module's own choice wins.
|
|
308
|
+
* 2. Else `worldInstance.kind === 'threejs'` (a native `default-three`
|
|
309
|
+
* sibling, or a `{ module }` sibling that mounted a threejs world with no
|
|
310
|
+
* authoring of its own) — a real `IngestAuthoringAdapter` over
|
|
311
|
+
* `worldInstance.threeRoot()`, with REAL `.vgai/overlays/<world-id>.json`
|
|
312
|
+
* persistence (D-G2: the SAME overlay identity play-mode would use for
|
|
313
|
+
* this same world id — one persisted document either route converges
|
|
314
|
+
* on). D-G4: `loadWorldOverlay` already returns `{}` on miss/error — no
|
|
315
|
+
* new swallowing added here.
|
|
316
|
+
* 3. Else `kind === 'pixijs'` — the pixijs analog through
|
|
317
|
+
* `World2DAuthoringAdapter`, physics wired via
|
|
318
|
+
* `createPhysicsAdapter2D(worldInstance.physics2d ??
|
|
319
|
+
* createPhysics2DRegistry())` exactly like play-mode's own branch.
|
|
320
|
+
* 4. Else (no mounted.authoring, not a first-party-authorable kind) — the
|
|
321
|
+
* named `makeEmbedOnlyAdapter` floor, unchanged.
|
|
322
|
+
* D-G3: `composite-authoring-adapter.ts`'s persistence routing (OR-dirty,
|
|
323
|
+
* per-child save, `' + '`-joined destinations) needs NO changes — once this
|
|
324
|
+
* function hands back an adapter with `capabilities.persist` +
|
|
325
|
+
* `persistence`, the composite's existing routing picks it up automatically.
|
|
326
|
+
*/
|
|
327
|
+
async function mountScopedRuntimeSibling(
|
|
328
|
+
manifest: ResolvedGameManifest,
|
|
329
|
+
world: ResolvedWorldEntry,
|
|
330
|
+
gameContainer: HTMLElement,
|
|
331
|
+
projectRoot: string,
|
|
332
|
+
store: EditorStore | undefined,
|
|
333
|
+
): Promise<SiblingMount> {
|
|
334
|
+
const layer = document.createElement('div');
|
|
335
|
+
styleSiblingLayer(layer, world, 'auto');
|
|
336
|
+
gameContainer.appendChild(layer);
|
|
337
|
+
|
|
338
|
+
try {
|
|
339
|
+
const w = gameContainer.clientWidth || 800;
|
|
340
|
+
const h = gameContainer.clientHeight || 600;
|
|
341
|
+
const { session } = await mountModuleWorldRuntime(manifest, world, projectRoot, layer, w, h);
|
|
342
|
+
// D-G1: `session.game.worlds[0]` is the SAME `WorldInstance` shape
|
|
343
|
+
// play-mode iterates (`kind`, `threeRoot()`, `pixiRoot()`, `physics2d`) —
|
|
344
|
+
// a single-element `resolveAllWorlds` call always registers exactly one.
|
|
345
|
+
const worldInstance = session.game.worlds[0]!;
|
|
346
|
+
const mountedAuthoring = worldInstance.mounted.authoring;
|
|
347
|
+
let authoring: AuthoringAdapter | undefined;
|
|
348
|
+
if (store) {
|
|
349
|
+
if (mountedAuthoring) {
|
|
350
|
+
// D-E1 (byte-stable, docs/WAVE8-SIBLING-AUTHORING-DESIGN.md): the
|
|
351
|
+
// module's own surface (and its own persistence, if any) wins —
|
|
352
|
+
// never wrap an editor overlay around it.
|
|
353
|
+
authoring = withStoreSelection(mountedAuthoring, store);
|
|
354
|
+
} else if (worldInstance.kind === 'threejs') {
|
|
355
|
+
authoring = new IngestAuthoringAdapter(store, worldInstance.threeRoot(), {
|
|
356
|
+
overlay: await loadWorldOverlay<IngestOverlay>(worldInstance.id),
|
|
357
|
+
persist: (o) => saveWorldOverlay(worldInstance.id, o),
|
|
358
|
+
destination: worldOverlayPath(worldInstance.id),
|
|
359
|
+
});
|
|
360
|
+
} else if (worldInstance.kind === 'pixijs') {
|
|
361
|
+
const physics = createPhysicsAdapter2D(
|
|
362
|
+
worldInstance.physics2d ?? createPhysics2DRegistry(),
|
|
363
|
+
);
|
|
364
|
+
authoring = new World2DAuthoringAdapter(worldInstance.pixiRoot(), physics, store, {
|
|
365
|
+
overlay: await loadWorldOverlay<Overlay2D>(worldInstance.id),
|
|
366
|
+
persist: (o) => saveWorldOverlay(worldInstance.id, o),
|
|
367
|
+
destination: worldOverlayPath(worldInstance.id),
|
|
368
|
+
});
|
|
369
|
+
} else {
|
|
370
|
+
authoring = makeEmbedOnlyAdapter(store, `${world.id} (no authoring surface)`);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
return {
|
|
374
|
+
worldId: world.id,
|
|
375
|
+
...(authoring ? { authoring } : {}),
|
|
376
|
+
setPaused(paused: boolean): void {
|
|
377
|
+
if (paused) session.pause();
|
|
378
|
+
else session.resume();
|
|
379
|
+
},
|
|
380
|
+
dispose(): void {
|
|
381
|
+
session.stop();
|
|
382
|
+
layer.remove();
|
|
383
|
+
},
|
|
384
|
+
};
|
|
385
|
+
} catch (err) {
|
|
386
|
+
layer.remove();
|
|
387
|
+
throw err;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* D-V7 (updated by D-B3): a sibling reaching here is neither `default-react`
|
|
393
|
+
* nor routable through the scoped runtime above (`{ module }` of any kind,
|
|
394
|
+
* or a native `default-three`/`default-pixi` world) — a genuinely unroutable
|
|
395
|
+
* shape. No such shape is known to exist today (every currently-resolvable
|
|
396
|
+
* adapter identity is handled above); this is a named, loud non-goal for a
|
|
397
|
+
* hypothetical future one, never silently ignored (the F24 anti-pattern) and
|
|
398
|
+
* never half-mounted. (A second `{ ingest }` world is structurally
|
|
399
|
+
* unreachable here — D-V2's route-level guard already fails the whole
|
|
400
|
+
* composite before any sibling loop runs.)
|
|
401
|
+
*/
|
|
402
|
+
function rejectUnmountableSibling(world: ResolvedWorldEntry): never {
|
|
403
|
+
throw new Error(
|
|
404
|
+
`sibling world "${world.id}" (kind: ${world.kind}, adapter identity: ` +
|
|
405
|
+
`"${world.adapter.identity}") has no known composite-sibling mount path — every ` +
|
|
406
|
+
'currently-resolvable identity (default-three/default-pixi/default-react/module) is ' +
|
|
407
|
+
'routed above; this is reached only for a genuinely unroutable shape ' +
|
|
408
|
+
'(docs/WAVE5-MULTIWORLD-INGEST-DESIGN.md D-V7, updated by docs/WAVE6-DOCTOR-DESIGN.md D-B3).',
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
async function mountOneIngestSibling(
|
|
413
|
+
manifest: ResolvedGameManifest,
|
|
414
|
+
world: ResolvedWorldEntry,
|
|
415
|
+
gameContainer: HTMLElement,
|
|
416
|
+
projectRoot: string,
|
|
417
|
+
store: EditorStore | undefined,
|
|
418
|
+
): Promise<SiblingMount> {
|
|
419
|
+
if (
|
|
420
|
+
world.adapter.type === 'module' ||
|
|
421
|
+
world.adapter.identity === 'default-three' ||
|
|
422
|
+
world.adapter.identity === 'default-pixi'
|
|
423
|
+
) {
|
|
424
|
+
return mountScopedRuntimeSibling(manifest, world, gameContainer, projectRoot, store);
|
|
425
|
+
}
|
|
426
|
+
if (world.adapter.identity === 'default-react') {
|
|
427
|
+
return mountDefaultReactSibling(world, gameContainer, projectRoot, store);
|
|
428
|
+
}
|
|
429
|
+
rejectUnmountableSibling(world);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* D-V2: mount every world in `manifest.worlds` OTHER than the primary ingest
|
|
434
|
+
* world (`ingestWorldId`), in manifest order (already z-sorted by the
|
|
435
|
+
* loader). A failing sibling never unmounts the ingest world or stops the
|
|
436
|
+
* loop — it is caught, reported (D-V5's multi-entry mount-failure report,
|
|
437
|
+
* deduped by worldId), and the loop continues to the next sibling.
|
|
438
|
+
*/
|
|
439
|
+
export async function mountIngestSiblings(
|
|
440
|
+
manifest: ResolvedGameManifest,
|
|
441
|
+
ingestWorldId: string,
|
|
442
|
+
gameContainer: HTMLElement,
|
|
443
|
+
projectRoot: string,
|
|
444
|
+
// D-E1: optional ONLY so the one pre-existing direct caller
|
|
445
|
+
// (`ingest-composite-sibling-pause.test.ts`, which never inspects
|
|
446
|
+
// `SiblingMount.authoring`) keeps compiling/running unchanged — every
|
|
447
|
+
// production caller (`ingest-mode.ts`'s composite routes) supplies one.
|
|
448
|
+
store?: EditorStore,
|
|
449
|
+
): Promise<SiblingMount[]> {
|
|
450
|
+
const mounted: SiblingMount[] = [];
|
|
451
|
+
for (const world of manifest.worlds) {
|
|
452
|
+
if (world.id === ingestWorldId) continue;
|
|
453
|
+
try {
|
|
454
|
+
mounted.push(await mountOneIngestSibling(manifest, world, gameContainer, projectRoot, store));
|
|
455
|
+
} catch (err) {
|
|
456
|
+
editorConsole.error(`Composite sibling "${world.id}" failed to mount: ${err}`, 'ingest');
|
|
457
|
+
addMountFailureReport({
|
|
458
|
+
worldId: world.id,
|
|
459
|
+
kind: world.kind,
|
|
460
|
+
identity: world.adapter.identity,
|
|
461
|
+
message: formatMountFailureMessage(err),
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
return mounted;
|
|
466
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { resolveUrl } from '@engine/loader';
|
|
2
|
+
import { parseSceneFile } from '@engine/scene/parse';
|
|
3
|
+
import type { SceneFile } from '@engine/scene/scene-types';
|
|
4
|
+
import { saveFile } from './editor-api';
|
|
5
|
+
import { editorConsole } from './editor-console';
|
|
6
|
+
import { getEditorMode } from './editor-mode';
|
|
7
|
+
import type { EditorStore } from './editor-store';
|
|
8
|
+
import {
|
|
9
|
+
type ActiveProject,
|
|
10
|
+
detectServerProject,
|
|
11
|
+
getCurrentProject,
|
|
12
|
+
getProjectPathFromURL,
|
|
13
|
+
openProject,
|
|
14
|
+
} from './project-manager';
|
|
15
|
+
import { getStorageBackend } from './storage';
|
|
16
|
+
|
|
17
|
+
/** Detect the active project (from URL or server) and log the result. */
|
|
18
|
+
export async function initProject(): Promise<void> {
|
|
19
|
+
// If the project manager already has a project (set by AppRoot/ProjectScreen),
|
|
20
|
+
// just log it and return — no need to re-detect.
|
|
21
|
+
const existing = getCurrentProject();
|
|
22
|
+
if (existing) {
|
|
23
|
+
editorConsole.log(`Opened project: ${existing.config.name}`, 'project');
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const projectPath = getProjectPathFromURL();
|
|
28
|
+
if (projectPath) {
|
|
29
|
+
try {
|
|
30
|
+
const project = await openProject(projectPath);
|
|
31
|
+
editorConsole.log(`Opened project: ${project.config.name}`, 'project');
|
|
32
|
+
} catch (err) {
|
|
33
|
+
editorConsole.error(`Failed to open project: ${err}`, 'project');
|
|
34
|
+
}
|
|
35
|
+
} else {
|
|
36
|
+
try {
|
|
37
|
+
const project = await detectServerProject();
|
|
38
|
+
if (project) {
|
|
39
|
+
editorConsole.log(`Opened project: ${project.config.name}`, 'project');
|
|
40
|
+
}
|
|
41
|
+
} catch {
|
|
42
|
+
// No server project — run without a project
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** A minimal empty scene — camera + a box to look at. Used both for the
|
|
48
|
+
* "no scene declared" in-memory fallback and the "declared scene failed to
|
|
49
|
+
* load" on-disk recovery fallback below. */
|
|
50
|
+
function buildFallbackSceneDoc(): SceneFile {
|
|
51
|
+
return {
|
|
52
|
+
version: 1,
|
|
53
|
+
name: 'Untitled',
|
|
54
|
+
entities: [
|
|
55
|
+
{
|
|
56
|
+
name: 'Camera',
|
|
57
|
+
camera: { type: 'perspective' as const },
|
|
58
|
+
transform: { position: [0, 2, 5] as [number, number, number] },
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: 'Box',
|
|
62
|
+
mesh: { type: 'box' as const },
|
|
63
|
+
material: { type: 'standard' as const, color: '#888888' },
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* A1 (`authoring/edit-mode-authoring.ts`): the scene path `loadInitialScene`
|
|
71
|
+
* WOULD resolve to, computed with NO I/O — used only to NAME the `scene`
|
|
72
|
+
* field of the single-world manifest synthesized for a manifest-less project,
|
|
73
|
+
* so that synthesized world is a faithful (if inert) description of what's
|
|
74
|
+
* actually loaded. Mirrors `loadInitialScene`'s resolution order exactly
|
|
75
|
+
* (`?scene=` URL param -> `ProjectConfig.defaultScene` -> legacy
|
|
76
|
+
* `'scenes/default.vscn.json'` fallback) but never fetches/loads/writes
|
|
77
|
+
* anything — `loadInitialScene` itself is UNTOUCHED by this addition and
|
|
78
|
+
* remains the only function that actually loads a scene into the store, so
|
|
79
|
+
* this can never drift into a second, competing load path.
|
|
80
|
+
*/
|
|
81
|
+
export function resolveInitialScenePathForManifestSynthesis(
|
|
82
|
+
project: ActiveProject | null,
|
|
83
|
+
): string | null {
|
|
84
|
+
const sceneParam = new URLSearchParams(window.location.search).get('scene');
|
|
85
|
+
if (sceneParam) return sceneParam;
|
|
86
|
+
// A legal "no scene declared" project (§7.1-12, see `loadInitialScene`
|
|
87
|
+
// below) — an entry-only first world has no scene path to name.
|
|
88
|
+
if (project && !project.config.defaultScene) return null;
|
|
89
|
+
return project?.config.defaultScene ?? 'scenes/default.vscn.json';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Load the initial scene from URL param, project default, or create a fallback. */
|
|
93
|
+
export async function loadInitialScene(store: EditorStore): Promise<void> {
|
|
94
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
95
|
+
const sceneUrl = urlParams.get('scene');
|
|
96
|
+
const project = getCurrentProject();
|
|
97
|
+
|
|
98
|
+
if (sceneUrl) {
|
|
99
|
+
try {
|
|
100
|
+
// Hosted example project (Track H): `?scene=` is PROJECT-RELATIVE —
|
|
101
|
+
// `resolveUrl` carries the `/examples/<id>/` prefix set at open — and
|
|
102
|
+
// loads READ-ONLY (no save path ⇒ autosave inert), since the staged
|
|
103
|
+
// example is not writable storage.
|
|
104
|
+
if (project?.exampleId && !sceneUrl.startsWith('/')) {
|
|
105
|
+
const res = await fetch(resolveUrl(sceneUrl));
|
|
106
|
+
if (!res.ok) {
|
|
107
|
+
throw new Error(`HTTP ${res.status} fetching ${sceneUrl}`);
|
|
108
|
+
}
|
|
109
|
+
store.loadDocument(parseSceneFile(await res.json()));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const res = await fetch(resolveUrl(sceneUrl));
|
|
114
|
+
if (!res.ok) {
|
|
115
|
+
throw new Error(`HTTP ${res.status} fetching ${sceneUrl}`);
|
|
116
|
+
}
|
|
117
|
+
const doc = parseSceneFile(await res.json());
|
|
118
|
+
const savePath = sceneUrl.startsWith('/') ? sceneUrl.slice(1) : sceneUrl;
|
|
119
|
+
store.loadDocument(doc, savePath);
|
|
120
|
+
return;
|
|
121
|
+
} catch (err) {
|
|
122
|
+
editorConsole.error(`Failed to load scene from URL: ${err}`, 'scene');
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// A KNOWN project (readProjectView resolved it — see project-view.ts) that
|
|
127
|
+
// declares NO scene at all (its `defaultScene` is explicit `null`, or —
|
|
128
|
+
// legacy `project.json` verbatim pass-through — the key is simply absent)
|
|
129
|
+
// is a legal shape (an entry-only first world). NEVER guess a path or probe
|
|
130
|
+
// the network for one here: that is exactly what silently WROTE a
|
|
131
|
+
// placeholder 'scenes/untitled.vscn.json' for a project the user never
|
|
132
|
+
// asked to persist anything into (§7.1-12). Build the fallback purely in
|
|
133
|
+
// memory and hand it to the store WITHOUT a save path — `loadDocument`
|
|
134
|
+
// sets `savePath` to `null` in that case, which makes `saveNow()`/autosave
|
|
135
|
+
// an inert no-op (`editor-store.ts`'s `if (!this._savePath || ...) return
|
|
136
|
+
// false`) until an explicit user save gives it a real, user-visible path.
|
|
137
|
+
if (project && !project.config.defaultScene) {
|
|
138
|
+
store.loadDocument(buildFallbackSceneDoc());
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// No project resolved at all (e.g. no project open) — preserve the
|
|
143
|
+
// pre-existing guess as the last resort for finding SOMETHING to load.
|
|
144
|
+
const defaultScenePath = project?.config.defaultScene ?? 'scenes/default.vscn.json';
|
|
145
|
+
|
|
146
|
+
// Browser-first build: scene files live in the in-browser VFS, not on an HTTP
|
|
147
|
+
// server, so read through the storage backend instead of fetch(). Bundled
|
|
148
|
+
// EXAMPLE projects are the exception — their scenes are static files staged
|
|
149
|
+
// under `/examples/<id>/`, fetched through the asset prefix and loaded
|
|
150
|
+
// READ-ONLY (no save path), same as the `?scene=` branch above.
|
|
151
|
+
if (getEditorMode() === 'browser') {
|
|
152
|
+
if (project?.exampleId) {
|
|
153
|
+
try {
|
|
154
|
+
const res = await fetch(resolveUrl(defaultScenePath));
|
|
155
|
+
if (!res.ok) {
|
|
156
|
+
throw new Error(`HTTP ${res.status} fetching ${defaultScenePath}`);
|
|
157
|
+
}
|
|
158
|
+
store.loadDocument(parseSceneFile(await res.json()));
|
|
159
|
+
return;
|
|
160
|
+
} catch (err) {
|
|
161
|
+
editorConsole.error(`Failed to load example scene: ${err}`, 'scene');
|
|
162
|
+
store.loadDocument(buildFallbackSceneDoc());
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
try {
|
|
167
|
+
const doc = parseSceneFile(JSON.parse(await getStorageBackend().read(defaultScenePath)));
|
|
168
|
+
store.loadDocument(doc, defaultScenePath);
|
|
169
|
+
return;
|
|
170
|
+
} catch (err) {
|
|
171
|
+
editorConsole.error(`Failed to load scene from storage: ${err}`, 'scene');
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
const res = await fetch(resolveUrl(`/${defaultScenePath}`));
|
|
177
|
+
if (res.ok) {
|
|
178
|
+
const doc = parseSceneFile(await res.json());
|
|
179
|
+
store.loadDocument(doc, defaultScenePath);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
} catch {
|
|
183
|
+
// Default scene not found — fall through to fallback
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Fallback: create a minimal scene. Reached only when a scene path WAS
|
|
187
|
+
// resolved (declared, or guessed because no project was known at all) but
|
|
188
|
+
// failed to load — pre-existing recovery behavior, unchanged: persist a
|
|
189
|
+
// fresh untitled scene so there is something to edit and save.
|
|
190
|
+
const fallbackDoc = buildFallbackSceneDoc();
|
|
191
|
+
const fallbackPath = 'scenes/untitled.vscn.json';
|
|
192
|
+
await saveFile(fallbackPath, JSON.stringify(fallbackDoc, null, 2));
|
|
193
|
+
store.loadDocument(fallbackDoc, fallbackPath);
|
|
194
|
+
}
|