@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,643 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edit-mode authoring (A1, docs/unified-world-editor/26-unified-editor-spec.md
|
|
3
|
+
* §2 A1) — project open loads `vgai.game.json` and installs a
|
|
4
|
+
* {@link CompositeAuthoringAdapter} over EVERY declared world, so a
|
|
5
|
+
* multi-world project's composition is disclosed in edit mode (#28) instead
|
|
6
|
+
* of only in play mode (`play-mode.ts`'s `installMultiWorldAuthoring`, the
|
|
7
|
+
* pattern this mirrors). Unlike play mode, worlds are NOT running here: only
|
|
8
|
+
* the FOCUSED threejs world is live (the existing `VgaiSceneAuthoringAdapter`
|
|
9
|
+
* over the store's already-loaded document — `loadInitialScene` is
|
|
10
|
+
* untouched, still the only thing that ever loads a scene into the store);
|
|
11
|
+
* every other declared world gets a read-only {@link BoundaryAuthoringAdapter}
|
|
12
|
+
* describing it from the manifest, upgraded to a live adapter only once its
|
|
13
|
+
* design-time layer actually mounts (B1).
|
|
14
|
+
*
|
|
15
|
+
* **Back-compat floor (exact, not "equivalent"):** a single-world project —
|
|
16
|
+
* whether there is no manifest at all (synthesized in memory, see
|
|
17
|
+
* `installEditModeAuthoringForProject` below) or a manifest that happens to
|
|
18
|
+
* declare exactly one world — has NOTHING new to disclose (#28 is a
|
|
19
|
+
* multi-world problem), so `installEditModeAuthoring` deliberately does NOT
|
|
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.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import type { AuthoringAdapter } from '@engine/adapter';
|
|
37
|
+
import { editorConsole } from '../editor-console';
|
|
38
|
+
import { getEditorMode } from '../editor-mode';
|
|
39
|
+
import type { EditorStore } from '../editor-store';
|
|
40
|
+
import { resolveInitialScenePathForManifestSynthesis } from '../initial-scene';
|
|
41
|
+
import { fetchRawGameManifest } from '../manifest-project';
|
|
42
|
+
import { getCurrentProject } from '../project-manager';
|
|
43
|
+
import { getStorageBackend } from '../storage';
|
|
44
|
+
import { UIEditStore } from '../ui-editor/ui-edit-store';
|
|
45
|
+
import { createHttpSourceWriteBackend } from '../ui-source/source-write-backend';
|
|
46
|
+
import { getAuthoringOverride, markEditModeOverride, setActiveAuthoring } from './active-adapter';
|
|
47
|
+
import { BoundaryAuthoringAdapter, type BoundaryWorldInfo } from './boundary-authoring-adapter';
|
|
48
|
+
import {
|
|
49
|
+
CompositeAuthoringAdapter,
|
|
50
|
+
type CompositeChild,
|
|
51
|
+
type GameRootManifestProvider,
|
|
52
|
+
} from './composite-authoring-adapter';
|
|
53
|
+
import { UIAuthoringAdapter } from './ui-authoring-adapter';
|
|
54
|
+
import { type UndoAdapter, unregisterUndoAdapter } from './undo-timeline';
|
|
55
|
+
import { VgaiSceneAuthoringAdapter } from './vgai-scene-authoring-adapter';
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* C3 (docs/unified-world-editor/26-unified-editor-spec.md §4 C3) — dispatched
|
|
59
|
+
* (on `window`) when the open scene's `ui` roots change in a way that
|
|
60
|
+
* requires REINSTALLING the edit-mode composite (today: only the "Add UI
|
|
61
|
+
* Canvas" hierarchy-menu action, `ui-hierarchy-menu.ts` — going from zero UI
|
|
62
|
+
* roots to one flips whether the scene-UI child exists at all, §3.1's
|
|
63
|
+
* install-gate). Mirrors `root-activation.ts`'s
|
|
64
|
+
* `ROOT_ACTIVATION_REQUEST_EVENT` window-CustomEvent decoupling pattern.
|
|
65
|
+
* `ViewportPanel.tsx` listens and reruns its `installAll()`.
|
|
66
|
+
*/
|
|
67
|
+
export const EDIT_MODE_REBUILD_EVENT = 'editor:edit-mode-rebuild';
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The display-relevant slice of a manifest world entry this module needs —
|
|
71
|
+
* deliberately LOOSE (a bare `string` `kind`, not the engine's validated
|
|
72
|
+
* `WorldEntry['kind']` union) so a world that fails schema validation
|
|
73
|
+
* (an unknown kind) can still be represented and surfaced as an error node
|
|
74
|
+
* (#18). Built by the LENIENT parse below (`parseEditModeManifest`), reachable
|
|
75
|
+
* from the production project-open path — NOT gated on strict Zod, which would
|
|
76
|
+
* reject the whole file for one bad world and make every declared world vanish
|
|
77
|
+
* (defect 2).
|
|
78
|
+
*/
|
|
79
|
+
export interface EditModeWorldSpec {
|
|
80
|
+
readonly id: string;
|
|
81
|
+
readonly kind: string;
|
|
82
|
+
readonly adapter?: string | undefined;
|
|
83
|
+
readonly scene?: string | undefined;
|
|
84
|
+
readonly entry?: string | undefined;
|
|
85
|
+
readonly zOrder?: number | undefined;
|
|
86
|
+
readonly pausable?: boolean | undefined;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface EditModeManifest {
|
|
90
|
+
readonly worlds: readonly EditModeWorldSpec[];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const KNOWN_WORLD_KINDS = new Set(['threejs', 'pixijs', 'react']);
|
|
94
|
+
|
|
95
|
+
function asString(v: unknown): string | undefined {
|
|
96
|
+
return typeof v === 'string' ? v : undefined;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Display string for a raw `adapter` field — informational only (the Boundary
|
|
100
|
+
* inspector's read-only `adapter` property). Read defensively: the raw JSON
|
|
101
|
+
* may be a string ('default'), a `{ module }`/`{ ingest }` object, or absent. */
|
|
102
|
+
function displayAdapter(adapter: unknown): string | undefined {
|
|
103
|
+
if (adapter === undefined || adapter === null) return undefined;
|
|
104
|
+
if (typeof adapter === 'string') return adapter;
|
|
105
|
+
if (typeof adapter === 'object') {
|
|
106
|
+
const obj = adapter as Record<string, unknown>;
|
|
107
|
+
if (typeof obj['module'] === 'string') return `module:${obj['module']}`;
|
|
108
|
+
const ingest = obj['ingest'];
|
|
109
|
+
if (ingest && typeof ingest === 'object') {
|
|
110
|
+
const strategy = asString((ingest as Record<string, unknown>)['strategy']);
|
|
111
|
+
return strategy ? `ingest:${strategy}` : 'ingest';
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* LENIENT parse (defect 2): read a raw `vgai.game.json` object's `worlds[]`
|
|
119
|
+
* defensively into `EditModeWorldSpec[]`, so a single bad world (unknown
|
|
120
|
+
* `kind`, missing `scene`+`entry`) becomes an error node (#18) while its VALID
|
|
121
|
+
* siblings still open — never the strict-Zod all-or-nothing of
|
|
122
|
+
* `loadGameManifest`. Returns `null` when there is no usable `worlds` array at
|
|
123
|
+
* all (a present-but-unusable manifest, distinct from an ABSENT one) so the
|
|
124
|
+
* caller can fall back to the synthesized single-world floor. A world missing
|
|
125
|
+
* an `id` gets a positional placeholder so it still surfaces rather than
|
|
126
|
+
* vanishing. `kind` is carried through verbatim (even a bogus string) —
|
|
127
|
+
* `resolveWorldFailureReason` decides resolvability downstream.
|
|
128
|
+
*/
|
|
129
|
+
export function parseEditModeManifest(raw: unknown): EditModeManifest | null {
|
|
130
|
+
if (!raw || typeof raw !== 'object') return null;
|
|
131
|
+
const worldsRaw = (raw as Record<string, unknown>)['worlds'];
|
|
132
|
+
if (!Array.isArray(worldsRaw) || worldsRaw.length === 0) return null;
|
|
133
|
+
const worlds: EditModeWorldSpec[] = worldsRaw.map((entry, i) => {
|
|
134
|
+
const w = (entry && typeof entry === 'object' ? entry : {}) as Record<string, unknown>;
|
|
135
|
+
const zOrderRaw = w['zOrder'];
|
|
136
|
+
const pausableRaw = w['pausable'];
|
|
137
|
+
return {
|
|
138
|
+
id: asString(w['id']) ?? `(world ${i})`,
|
|
139
|
+
// Missing/non-string kind is itself unresolvable — carry a sentinel so
|
|
140
|
+
// resolveWorldFailureReason names it, rather than crashing on undefined.
|
|
141
|
+
kind: asString(w['kind']) ?? '(missing kind)',
|
|
142
|
+
adapter: displayAdapter(w['adapter']),
|
|
143
|
+
scene: asString(w['scene']),
|
|
144
|
+
entry: asString(w['entry']),
|
|
145
|
+
zOrder: typeof zOrderRaw === 'number' ? zOrderRaw : undefined,
|
|
146
|
+
pausable: typeof pausableRaw === 'boolean' ? pausableRaw : undefined,
|
|
147
|
+
};
|
|
148
|
+
});
|
|
149
|
+
return { worlds };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Structural resolvability (#18) — NOT a filesystem check: "missing entry/scene
|
|
154
|
+
* file" here means the world declares NEITHER field (mirrors the manifest
|
|
155
|
+
* schema's own `checkAdapterSceneEntryRules`, `manifest/load.ts`). A live "does
|
|
156
|
+
* the file actually exist on disk" probe is B1 territory (the design-time mount
|
|
157
|
+
* attempt itself degrades to this same error-node floor when a layer throws) —
|
|
158
|
+
* out of scope here, where nothing but the focused world is ever fetched.
|
|
159
|
+
*/
|
|
160
|
+
export function resolveWorldFailureReason(world: EditModeWorldSpec): string | null {
|
|
161
|
+
if (!KNOWN_WORLD_KINDS.has(world.kind)) {
|
|
162
|
+
return `Unknown world kind "${world.kind}" — expected one of: threejs, pixijs, react.`;
|
|
163
|
+
}
|
|
164
|
+
if (!world.scene && !world.entry) {
|
|
165
|
+
return `World "${world.id}" declares neither \`scene\` nor \`entry\` — nothing to mount.`;
|
|
166
|
+
}
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function normalizeScenePath(path: string): string {
|
|
171
|
+
return path.startsWith('/') ? path.slice(1) : path;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Which threejs world is FOCUSED — its scene is the one already loaded into
|
|
176
|
+
* the live store by `loadInitialScene`. Mirrors that function's OWN
|
|
177
|
+
* resolution order (`initial-scene.ts`: `?scene=` URL param wins, else the
|
|
178
|
+
* project's `defaultScene`); if neither names one of the manifest's threejs
|
|
179
|
+
* worlds (or the manifest declares none), falls back to the first threejs
|
|
180
|
+
* world (matches the synthesized single-world manifest, which only ever has
|
|
181
|
+
* exactly one). Exported (pure — no `window`/store reads) so it's directly
|
|
182
|
+
* unit-testable; `installEditModeAuthoring` supplies the ambient
|
|
183
|
+
* `focusScenePath` from `window.location`/the current project.
|
|
184
|
+
*/
|
|
185
|
+
export function resolveFocusedThreejsWorldId(
|
|
186
|
+
worlds: readonly EditModeWorldSpec[],
|
|
187
|
+
focusScenePath: string | null | undefined,
|
|
188
|
+
): string | null {
|
|
189
|
+
const threejsWorlds = worlds.filter((w) => w.kind === 'threejs');
|
|
190
|
+
if (threejsWorlds.length === 0) return null;
|
|
191
|
+
if (focusScenePath) {
|
|
192
|
+
const normalized = normalizeScenePath(focusScenePath);
|
|
193
|
+
const match = threejsWorlds.find(
|
|
194
|
+
(w) => w.scene !== undefined && normalizeScenePath(w.scene) === normalized,
|
|
195
|
+
);
|
|
196
|
+
if (match) return match.id;
|
|
197
|
+
}
|
|
198
|
+
return threejsWorlds[0]!.id;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ---------------------------------------------------------------------------
|
|
202
|
+
// A4 (D8) — ManifestAuthoring: the game root's read-modify-write manifest surface
|
|
203
|
+
// ---------------------------------------------------------------------------
|
|
204
|
+
|
|
205
|
+
/** A raw `vgai.game.json` world entry, read/written defensively — same
|
|
206
|
+
* looseness as {@link EditModeWorldSpec}, but this is the RAW (unknown)
|
|
207
|
+
* object, never Zod-parsed, so unrelated fields survive a round trip. */
|
|
208
|
+
type RawWorldEntry = Record<string, unknown>;
|
|
209
|
+
|
|
210
|
+
function findRawWorld(raw: Record<string, unknown>, worldId: string): RawWorldEntry | undefined {
|
|
211
|
+
const worlds = raw['worlds'];
|
|
212
|
+
if (!Array.isArray(worlds)) return undefined;
|
|
213
|
+
return worlds.find(
|
|
214
|
+
(w): w is RawWorldEntry =>
|
|
215
|
+
!!w && typeof w === 'object' && (w as RawWorldEntry)['id'] === worldId,
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* ManifestAuthoring (A4, D8) — the game root's manifest-backed
|
|
221
|
+
* {@link GameRootManifestProvider}: holds the RAW parsed `vgai.game.json`
|
|
222
|
+
* object (read-modify-write — NEVER round-tripped through the strict Zod
|
|
223
|
+
* schema, so fields this module doesn't know about survive untouched) and
|
|
224
|
+
* writes it back via `POST /__editor/manifest` (the one manifest-write verb,
|
|
225
|
+
* `editor-server.ts`).
|
|
226
|
+
*
|
|
227
|
+
* Two write modes:
|
|
228
|
+
* - a REAL manifest (`synthesized: false`) writes through on every edit —
|
|
229
|
+
* the acceptance's "round-trips vgai.game.json on disk" needs no separate
|
|
230
|
+
* save step.
|
|
231
|
+
* - a SYNTHESIZED manifest (`synthesized: true`, no `vgai.game.json` on disk
|
|
232
|
+
* yet) BUFFERS edits in memory only; nothing is written until the
|
|
233
|
+
* explicit `materialize()` call (the generic inspector's "Create
|
|
234
|
+
* vgai.game.json" action). In production this branch is currently
|
|
235
|
+
* UNREACHABLE end-to-end: `installEditModeAuthoring` only ever installs a
|
|
236
|
+
* game root when 2+ worlds are declared, and `synthesizeSingleWorldManifest`
|
|
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.
|
|
243
|
+
*
|
|
244
|
+
* File-watcher landmine: `chokidar` only watches `public/` (`editor-server.ts`),
|
|
245
|
+
* so a manifest write at the PROJECT ROOT emits no SSE — `notifyIngestEdit()`
|
|
246
|
+
* after every successful write is what keeps the hierarchy panel's badges
|
|
247
|
+
* live instead of stale until reopen.
|
|
248
|
+
*/
|
|
249
|
+
export class ManifestAuthoring implements GameRootManifestProvider {
|
|
250
|
+
private readonly raw: Record<string, unknown>;
|
|
251
|
+
readonly synthesized: boolean;
|
|
252
|
+
private readonly store: EditorStore;
|
|
253
|
+
|
|
254
|
+
constructor(raw: Record<string, unknown>, synthesized: boolean, store: EditorStore) {
|
|
255
|
+
this.raw = raw;
|
|
256
|
+
this.synthesized = synthesized;
|
|
257
|
+
this.store = store;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
getGameName(): string {
|
|
261
|
+
const name = this.raw['name'];
|
|
262
|
+
return typeof name === 'string' ? name : '';
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
setGameName(name: string): void {
|
|
266
|
+
this.raw['name'] = name;
|
|
267
|
+
this.persist();
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
getWorldZOrder(worldId: string): number {
|
|
271
|
+
const z = findRawWorld(this.raw, worldId)?.['zOrder'];
|
|
272
|
+
return typeof z === 'number' ? z : 0;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
setWorldZOrder(worldId: string, value: number): void {
|
|
276
|
+
const world = findRawWorld(this.raw, worldId);
|
|
277
|
+
if (!world) return;
|
|
278
|
+
world['zOrder'] = value;
|
|
279
|
+
this.persist();
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
getWorldPausable(worldId: string): boolean {
|
|
283
|
+
const p = findRawWorld(this.raw, worldId)?.['pausable'];
|
|
284
|
+
return typeof p === 'boolean' ? p : true;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
setWorldPausable(worldId: string, value: boolean): void {
|
|
288
|
+
const world = findRawWorld(this.raw, worldId);
|
|
289
|
+
if (!world) return;
|
|
290
|
+
world['pausable'] = value;
|
|
291
|
+
this.persist();
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** The "Create vgai.game.json" action (generic inspector, while
|
|
295
|
+
* `synthesized`) — materializes the buffered edits to disk. Also safe to
|
|
296
|
+
* call on a non-synthesized instance (equivalent to an explicit re-save). */
|
|
297
|
+
async materialize(): Promise<boolean> {
|
|
298
|
+
return this.write();
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
private persist(): void {
|
|
302
|
+
if (this.synthesized) return; // buffered only — wait for materialize()
|
|
303
|
+
void this.write();
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
private async write(): Promise<boolean> {
|
|
307
|
+
const content = JSON.stringify(this.raw, null, 2);
|
|
308
|
+
try {
|
|
309
|
+
// Phase A cleanup: `vgai.game.json` is project-root-relative, the same
|
|
310
|
+
// namespace `authoring/world-overlay.ts`/`authoring/ingest-overlay.ts`
|
|
311
|
+
// already mode-branch on — browser mode has no `/__editor/*` server at
|
|
312
|
+
// all, so this used to silently fail every manifest edit there (caught
|
|
313
|
+
// below, logged, never surfaced beyond the console). Browser mode goes
|
|
314
|
+
// straight through `getStorageBackend()` (OPFS/FSA root at the TRUE
|
|
315
|
+
// project folder, no `public/` insertion); server mode is unchanged.
|
|
316
|
+
if (getEditorMode() === 'browser') {
|
|
317
|
+
await getStorageBackend().write('vgai.game.json', content);
|
|
318
|
+
} else {
|
|
319
|
+
const res = await fetch('/__editor/manifest', {
|
|
320
|
+
method: 'POST',
|
|
321
|
+
headers: { 'Content-Type': 'application/json' },
|
|
322
|
+
body: JSON.stringify({ path: 'vgai.game.json', content }),
|
|
323
|
+
});
|
|
324
|
+
if (!res.ok) return false;
|
|
325
|
+
}
|
|
326
|
+
// No SSE for a project-root write (file-watcher only covers `public/`)
|
|
327
|
+
// — notify directly so the hierarchy panel's badges refresh in place.
|
|
328
|
+
this.store.notifyIngestEdit();
|
|
329
|
+
return true;
|
|
330
|
+
} catch (err) {
|
|
331
|
+
editorConsole.error(`Failed to write vgai.game.json: ${err}`, 'project');
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/** Children built by the last `installEditModeAuthoring` call — tracked so
|
|
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
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Build the composite over every declared world and — ONLY when there is
|
|
409
|
+
* more than one world to disclose — install it as the active authoring
|
|
410
|
+
* override (see this module's header comment for why the single-world case
|
|
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.
|
|
414
|
+
*
|
|
415
|
+
* The focused threejs world is whichever one's scene `loadInitialScene`
|
|
416
|
+
* already loaded into the store (URL `?scene=` / `defaultScene` order); it
|
|
417
|
+
* gets the real `VgaiSceneAuthoringAdapter`. A1 deliberately ships NO
|
|
418
|
+
* focus-SWITCH (loading a DIFFERENT world's scene into the store) — that
|
|
419
|
+
* requires a dirty-check-before-switch prompt + the scene-load path and lands
|
|
420
|
+
* in A4 ({@link switchFocusedWorld} is the explicit, loud stub). Accepting a
|
|
421
|
+
* "focus this other world" override here would label the new world's identity
|
|
422
|
+
* onto the OLD world's still-loaded document — a silent misattribution
|
|
423
|
+
* (defect 5) — so that parameter was removed.
|
|
424
|
+
*
|
|
425
|
+
* `rawManifestInfo` (A4, D8, optional) — the RAW parsed `vgai.game.json` +
|
|
426
|
+
* whether it was synthesized (never written to disk); when given AND the
|
|
427
|
+
* composite ends up with 2+ children, a `ManifestAuthoring` is built over it
|
|
428
|
+
* and passed to `CompositeAuthoringAdapter` as its game-root manifest
|
|
429
|
+
* surface. Omitted (every existing unit-test call site) ⇒ no game root, the
|
|
430
|
+
* exact pre-A4 shape — see `CompositeAuthoringAdapter`'s own doc comment.
|
|
431
|
+
*/
|
|
432
|
+
export function installEditModeAuthoring(
|
|
433
|
+
store: EditorStore,
|
|
434
|
+
manifest: EditModeManifest,
|
|
435
|
+
rawManifestInfo?: { raw: Record<string, unknown>; synthesized: boolean },
|
|
436
|
+
): CompositeAuthoringAdapter {
|
|
437
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
438
|
+
const sceneParam = urlParams.get('scene');
|
|
439
|
+
const defaultScene = getCurrentProject()?.config.defaultScene ?? undefined;
|
|
440
|
+
const focusScenePath = sceneParam ?? defaultScene ?? null;
|
|
441
|
+
const focusedWorldId = resolveFocusedThreejsWorldId(manifest.worlds, focusScenePath);
|
|
442
|
+
|
|
443
|
+
const children: CompositeChild[] = manifest.worlds.map((world) => {
|
|
444
|
+
// D12 (B4) — install-time zOrder for every child, not just the Boundary's
|
|
445
|
+
// own inspector-displayed value: `childAdapters()`'s pick/paint-order
|
|
446
|
+
// consumers (the layered pick walk, the hierarchy zOrder badge) need it
|
|
447
|
+
// off ANY child, including the focused threejs world.
|
|
448
|
+
const zOrder = world.zOrder ?? 0;
|
|
449
|
+
if (world.id === focusedWorldId) {
|
|
450
|
+
return {
|
|
451
|
+
worldId: world.id,
|
|
452
|
+
kind: world.kind,
|
|
453
|
+
adapter: new VgaiSceneAuthoringAdapter(store),
|
|
454
|
+
zOrder,
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
const reason = resolveWorldFailureReason(world);
|
|
458
|
+
const info: BoundaryWorldInfo = {
|
|
459
|
+
id: world.id,
|
|
460
|
+
kind: world.kind,
|
|
461
|
+
adapter: world.adapter,
|
|
462
|
+
entryOrScenePath: world.scene ?? world.entry,
|
|
463
|
+
zOrder,
|
|
464
|
+
pausable: world.pausable ?? true,
|
|
465
|
+
};
|
|
466
|
+
return {
|
|
467
|
+
worldId: world.id,
|
|
468
|
+
kind: world.kind,
|
|
469
|
+
adapter: new BoundaryAuthoringAdapter(store, info, reason ?? undefined),
|
|
470
|
+
zOrder,
|
|
471
|
+
};
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
// C3 — the focused world's scene-UI child (§3.1): only when the open scene
|
|
475
|
+
// actually has UI roots to disclose, and only when there IS a focused
|
|
476
|
+
// threejs world to nest it under (a manifest declaring zero threejs worlds
|
|
477
|
+
// has nowhere to attach one — an edge case `resolveFocusedThreejsWorldId`
|
|
478
|
+
// already surfaces as `null`). Counts toward the `children.length > 1`
|
|
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
|
|
493
|
+
? new ManifestAuthoring(rawManifestInfo.raw, rawManifestInfo.synthesized, store)
|
|
494
|
+
: undefined;
|
|
495
|
+
const composite = new CompositeAuthoringAdapter(children, gameRoot);
|
|
496
|
+
_editModeChildren = children.map((c) => c.adapter);
|
|
497
|
+
|
|
498
|
+
// A1 delta (Wave-1 Phase-A close-out, spec:232-235, spec §3) — the ORIGINAL
|
|
499
|
+
// floor here was bare `children.length > 1`: a manifest whose only world is
|
|
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)) {
|
|
516
|
+
// Brand BEFORE installing so the store's save guard sees it the instant
|
|
517
|
+
// the override is live (defect 1: the focused first-party child must be
|
|
518
|
+
// able to Ctrl+S/autosave through the store, unlike a foreign override).
|
|
519
|
+
markEditModeOverride(composite);
|
|
520
|
+
setActiveAuthoring(composite);
|
|
521
|
+
store.notifyIngestEdit();
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
return composite;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/** Teardown symmetric to `installEditModeAuthoring` — clears the override
|
|
528
|
+
* ONLY if it is still exactly the composite this module last installed
|
|
529
|
+
* (never clobbers a DIFFERENT session, e.g. ingest/play, that took over
|
|
530
|
+
* afterward), and unregisters any child that self-registered with the undo
|
|
531
|
+
* timeline. */
|
|
532
|
+
export function exitEditModeAuthoring(installed?: CompositeAuthoringAdapter): void {
|
|
533
|
+
if (installed && getAuthoringOverride() === installed) {
|
|
534
|
+
setActiveAuthoring(null);
|
|
535
|
+
}
|
|
536
|
+
for (const adapter of _editModeChildren) {
|
|
537
|
+
if (adapter.undo && adapter.redo) {
|
|
538
|
+
unregisterUndoAdapter(adapter as AuthoringAdapter & UndoAdapter);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
_editModeChildren = [];
|
|
542
|
+
// R2 — see `_editModeUIStores`'s doc comment: the real undo-timeline
|
|
543
|
+
// registrant for a scene-UI child is its `UIEditStore`, not the
|
|
544
|
+
// `UIAuthoringAdapter` the loop above already (harmlessly, no-op) tried.
|
|
545
|
+
for (const uiStore of _editModeUIStores) {
|
|
546
|
+
unregisterUndoAdapter(uiStore);
|
|
547
|
+
}
|
|
548
|
+
_editModeUIStores = [];
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Focus-SWITCH (A4, defect 5): switching which threejs world is live requires
|
|
553
|
+
* (a) a dirty-check-before-switch prompt and (b) LOADING the target world's
|
|
554
|
+
* scene into the store — neither of which A1 ships. This explicit stub exists
|
|
555
|
+
* so a future caller gets a loud, named failure instead of A1 silently
|
|
556
|
+
* rebuilding a composite that labels the target world's identity onto the
|
|
557
|
+
* currently-loaded document. A1's composite always focuses the world
|
|
558
|
+
* `loadInitialScene` already loaded.
|
|
559
|
+
*/
|
|
560
|
+
export function switchFocusedWorld(): never {
|
|
561
|
+
throw new Error(
|
|
562
|
+
'Focus-switch (loading a different threejs world into the store, with a ' +
|
|
563
|
+
'dirty-check prompt) is not implemented in A1 — it lands in A4. A1 installs the ' +
|
|
564
|
+
'composite over the world loadInitialScene already loaded.',
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* Project-open wiring: fetch the RAW `vgai.game.json` and install the edit-mode
|
|
570
|
+
* composite via the LENIENT parse (defect 2) — a manifest with one bad world
|
|
571
|
+
* still opens its valid worlds, surfacing the bad one as an error node (#18).
|
|
572
|
+
* ONLY a genuinely ABSENT manifest (the route 404s → `fetchRawGameManifest`
|
|
573
|
+
* returns `null`), or one so malformed it has no usable `worlds` array,
|
|
574
|
+
* synthesizes a single `{ id: 'main', kind: 'threejs', scene: <resolved> }`
|
|
575
|
+
* game IN MEMORY (never written to disk), where `<resolved>` mirrors
|
|
576
|
+
* `loadInitialScene`'s OWN resolution order. This is the only call site that
|
|
577
|
+
* should call `installEditModeAuthoring` at ordinary project open
|
|
578
|
+
* (`ViewportPanel.tsx`).
|
|
579
|
+
*/
|
|
580
|
+
export async function installEditModeAuthoringForProject(
|
|
581
|
+
store: EditorStore,
|
|
582
|
+
): Promise<CompositeAuthoringAdapter> {
|
|
583
|
+
let manifest: EditModeManifest;
|
|
584
|
+
// A4 (D8) — the RAW object backing `ManifestAuthoring` (read-modify-write,
|
|
585
|
+
// never round-tripped through Zod). `synthesized: true` when there is no
|
|
586
|
+
// real `vgai.game.json` on disk (a 404, or one so malformed it has no
|
|
587
|
+
// usable `worlds` array) — see `ManifestAuthoring`'s doc comment for why
|
|
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 };
|
|
591
|
+
try {
|
|
592
|
+
const raw = await fetchRawGameManifest();
|
|
593
|
+
const parsed = raw !== null && parseEditModeManifest(raw);
|
|
594
|
+
if (parsed) {
|
|
595
|
+
manifest = parsed;
|
|
596
|
+
rawManifestInfo = { raw: raw as Record<string, unknown>, synthesized: false };
|
|
597
|
+
} else {
|
|
598
|
+
manifest = synthesizeSingleWorldManifest();
|
|
599
|
+
rawManifestInfo = { raw: synthesizeRawManifest(manifest), synthesized: true };
|
|
600
|
+
}
|
|
601
|
+
} catch (err) {
|
|
602
|
+
editorConsole.error(
|
|
603
|
+
`Failed to load vgai.game.json, falling back to a synthesized single-world game: ${err}`,
|
|
604
|
+
'project',
|
|
605
|
+
);
|
|
606
|
+
manifest = synthesizeSingleWorldManifest();
|
|
607
|
+
rawManifestInfo = { raw: synthesizeRawManifest(manifest), synthesized: true };
|
|
608
|
+
}
|
|
609
|
+
return installEditModeAuthoring(store, manifest, rawManifestInfo);
|
|
610
|
+
}
|
|
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
|
+
}
|