@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,746 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VgaiSceneAuthoringAdapter — the FIRST-PARTY {@link AuthoringAdapter}.
|
|
3
|
+
*
|
|
4
|
+
* It implements the editor's authoring contract by delegating to the existing
|
|
5
|
+
* {@link EditorStore}. `SceneEntity` / `.vscn` is this adapter's PRIVATE model:
|
|
6
|
+
* this is the one editor module (besides the store/serializer internals) that is
|
|
7
|
+
* allowed to import `SceneEntity`. The editor UI talks to the `AuthoringAdapter`
|
|
8
|
+
* interface and never sees `SceneEntity` — that confinement is the inversion.
|
|
9
|
+
*
|
|
10
|
+
* An ingested external game's `IngestAuthoringAdapter` (Phase B) implements the
|
|
11
|
+
* SAME providers directly over its live `Object3D` tree, with no `SceneEntity`.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type {
|
|
15
|
+
AssetDropProvider,
|
|
16
|
+
AuthoringAdapter,
|
|
17
|
+
AuthoringCapabilities,
|
|
18
|
+
ComponentsProvider,
|
|
19
|
+
EditorNode,
|
|
20
|
+
FileMapProvider,
|
|
21
|
+
HierarchyProvider,
|
|
22
|
+
InspectorProvider,
|
|
23
|
+
PersistenceProvider,
|
|
24
|
+
PickProvider,
|
|
25
|
+
PropertyDescriptor,
|
|
26
|
+
RootGroup,
|
|
27
|
+
RootGroupsProvider,
|
|
28
|
+
SelectionProvider,
|
|
29
|
+
StructureProvider,
|
|
30
|
+
Transform,
|
|
31
|
+
TransformOwner,
|
|
32
|
+
TransformProvider,
|
|
33
|
+
} from '@engine/adapter';
|
|
34
|
+
import { resolveUrl } from '@engine/loader';
|
|
35
|
+
import { parseSceneFile } from '@engine/scene/parse';
|
|
36
|
+
import type { SceneEntity, SceneFile } from '@engine/scene/scene-types';
|
|
37
|
+
import { getUserData } from '@engine/scene/user-data';
|
|
38
|
+
import type { FieldKind } from '../components/inspectors/zod-fields';
|
|
39
|
+
import { introspectSchema } from '../components/inspectors/zod-fields';
|
|
40
|
+
import { getEditorMode } from '../editor-mode';
|
|
41
|
+
import type { EditorStore } from '../editor-store';
|
|
42
|
+
import { getLoadedComponents } from '../script-loader';
|
|
43
|
+
import { getStorageBackend } from '../storage';
|
|
44
|
+
import { getActiveNetworking, getActivePhysics } from './active-systems';
|
|
45
|
+
import { ensureProjectScenesLoaded, getCachedProjectScenes } from './project-scenes';
|
|
46
|
+
import { raycastPick } from './viewport-raycast';
|
|
47
|
+
|
|
48
|
+
type ComponentDataMap = NonNullable<SceneEntity['components']>;
|
|
49
|
+
|
|
50
|
+
/** `zod-fields.ts`'s `FieldKind` → `PropertyDescriptor.type` (D6 — zod stays
|
|
51
|
+
* behind `introspectSchema`; the contract only ever sees this format-neutral
|
|
52
|
+
* mapping, never a zod shape). `select` folds into `enum` — both render as a
|
|
53
|
+
* dropdown over `options`; every other kind name matches 1:1. */
|
|
54
|
+
function fieldKindToPropertyType(kind: FieldKind): PropertyDescriptor['type'] {
|
|
55
|
+
return kind === 'select' ? 'enum' : kind;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
// D8 — root groups (radio roots over the project's `*.vscn.json` files)
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
|
|
62
|
+
const SCENE_ROOT_PREFIX = 'sceneRoot:';
|
|
63
|
+
|
|
64
|
+
/** Member node id for a project-relative scene path — stable, opaque to the
|
|
65
|
+
* shell (GameHierarchy discovers membership only via `rootGroups.groups()`
|
|
66
|
+
* data, never by decoding this prefix itself). */
|
|
67
|
+
function sceneRootId(path: string): string {
|
|
68
|
+
return `${SCENE_ROOT_PREFIX}${path}`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Inverse of {@link sceneRootId} — `null` for any id this adapter didn't mint. */
|
|
72
|
+
function pathForSceneRootId(id: string): string | null {
|
|
73
|
+
return id.startsWith(SCENE_ROOT_PREFIX) ? id.slice(SCENE_ROOT_PREFIX.length) : null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function normalizeScenePath(path: string): string {
|
|
77
|
+
return path.startsWith('/') ? path.slice(1) : path;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function fetchSceneDoc(path: string): Promise<SceneFile> {
|
|
81
|
+
// Mirrors `initial-scene.ts`'s browser-vs-server branch: browser builds
|
|
82
|
+
// read scene files through the active `StorageBackend` (OPFS/File System
|
|
83
|
+
// Access), not `fetch()`; the server dev build serves them over HTTP.
|
|
84
|
+
if (getEditorMode() === 'browser') {
|
|
85
|
+
return parseSceneFile(JSON.parse(await getStorageBackend().read(path)));
|
|
86
|
+
}
|
|
87
|
+
const res = await fetch(resolveUrl(`/${path}`));
|
|
88
|
+
if (!res.ok) throw new Error(`HTTP ${res.status} fetching ${path}`);
|
|
89
|
+
return parseSceneFile(await res.json());
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function kindOf(e: SceneEntity): EditorNode['kind'] {
|
|
93
|
+
if (e.light) return 'light';
|
|
94
|
+
if (e.camera) return 'camera';
|
|
95
|
+
if (e.mesh) return 'mesh';
|
|
96
|
+
if (e.children && e.children.length > 0) return 'group';
|
|
97
|
+
return 'object';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function toNode(e: SceneEntity, parentId: string | null): EditorNode {
|
|
101
|
+
return {
|
|
102
|
+
id: e.id!,
|
|
103
|
+
label: e.name,
|
|
104
|
+
kind: kindOf(e),
|
|
105
|
+
parentId,
|
|
106
|
+
childIds: (e.children ?? []).map((c) => c.id!),
|
|
107
|
+
flags: { locked: e.locked === true },
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Locate an entity + its parent id within the store's entity tree. */
|
|
112
|
+
function findInTree(
|
|
113
|
+
roots: SceneEntity[],
|
|
114
|
+
id: string,
|
|
115
|
+
): { entity: SceneEntity; parentId: string | null } | null {
|
|
116
|
+
const walk = (
|
|
117
|
+
list: SceneEntity[],
|
|
118
|
+
parentId: string | null,
|
|
119
|
+
): { entity: SceneEntity; parentId: string | null } | null => {
|
|
120
|
+
for (const e of list) {
|
|
121
|
+
if (e.id === id) return { entity: e, parentId };
|
|
122
|
+
const hit = e.children ? walk(e.children, e.id!) : null;
|
|
123
|
+
if (hit) return hit;
|
|
124
|
+
}
|
|
125
|
+
return null;
|
|
126
|
+
};
|
|
127
|
+
return walk(roots, null);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* The 12-kind creation palette (A2 parity growth — ported verbatim from
|
|
132
|
+
* `SceneHierarchy.tsx`'s `addMenuItems`, formerly toolbar-only) plus the two
|
|
133
|
+
* legacy slugs ('box'/'mesh' both -> Box, 'group' -> an empty container) that
|
|
134
|
+
* pre-date this table and are pinned by `structure-provider.test.ts`. Shared
|
|
135
|
+
* by `structure.create` and `structure.creatableKinds` so the palette and the
|
|
136
|
+
* actual entity shape can never drift apart.
|
|
137
|
+
*/
|
|
138
|
+
const KIND_TEMPLATES: Record<string, { label: string; make: () => SceneEntity }> = {
|
|
139
|
+
empty: { label: 'Empty', make: () => ({ id: crypto.randomUUID(), name: 'Empty' }) },
|
|
140
|
+
box: {
|
|
141
|
+
label: 'Box',
|
|
142
|
+
make: () => ({
|
|
143
|
+
id: crypto.randomUUID(),
|
|
144
|
+
name: 'Box',
|
|
145
|
+
mesh: { type: 'box' },
|
|
146
|
+
material: { type: 'standard', color: '#888888' },
|
|
147
|
+
}),
|
|
148
|
+
},
|
|
149
|
+
mesh: {
|
|
150
|
+
label: 'Box',
|
|
151
|
+
make: () => ({
|
|
152
|
+
id: crypto.randomUUID(),
|
|
153
|
+
name: 'Box',
|
|
154
|
+
mesh: { type: 'box' },
|
|
155
|
+
material: { type: 'standard', color: '#888888' },
|
|
156
|
+
}),
|
|
157
|
+
},
|
|
158
|
+
sphere: {
|
|
159
|
+
label: 'Sphere',
|
|
160
|
+
make: () => ({
|
|
161
|
+
id: crypto.randomUUID(),
|
|
162
|
+
name: 'Sphere',
|
|
163
|
+
mesh: { type: 'sphere' },
|
|
164
|
+
material: { type: 'standard', color: '#888888' },
|
|
165
|
+
}),
|
|
166
|
+
},
|
|
167
|
+
plane: {
|
|
168
|
+
label: 'Plane',
|
|
169
|
+
make: () => ({
|
|
170
|
+
id: crypto.randomUUID(),
|
|
171
|
+
name: 'Plane',
|
|
172
|
+
mesh: { type: 'plane' },
|
|
173
|
+
material: { type: 'standard', color: '#888888' },
|
|
174
|
+
}),
|
|
175
|
+
},
|
|
176
|
+
cylinder: {
|
|
177
|
+
label: 'Cylinder',
|
|
178
|
+
make: () => ({
|
|
179
|
+
id: crypto.randomUUID(),
|
|
180
|
+
name: 'Cylinder',
|
|
181
|
+
mesh: { type: 'cylinder' },
|
|
182
|
+
material: { type: 'standard', color: '#888888' },
|
|
183
|
+
}),
|
|
184
|
+
},
|
|
185
|
+
capsule: {
|
|
186
|
+
label: 'Capsule',
|
|
187
|
+
make: () => ({
|
|
188
|
+
id: crypto.randomUUID(),
|
|
189
|
+
name: 'Capsule',
|
|
190
|
+
mesh: { type: 'capsule' },
|
|
191
|
+
material: { type: 'standard', color: '#888888' },
|
|
192
|
+
}),
|
|
193
|
+
},
|
|
194
|
+
group: { label: 'Group', make: () => ({ id: crypto.randomUUID(), name: 'Group', children: [] }) },
|
|
195
|
+
dirLight: {
|
|
196
|
+
label: 'Dir Light',
|
|
197
|
+
make: () => ({
|
|
198
|
+
id: crypto.randomUUID(),
|
|
199
|
+
name: 'DirectionalLight',
|
|
200
|
+
light: { type: 'directional', intensity: 1 },
|
|
201
|
+
transform: { position: [5, 10, 5] },
|
|
202
|
+
}),
|
|
203
|
+
},
|
|
204
|
+
pointLight: {
|
|
205
|
+
label: 'Point Light',
|
|
206
|
+
make: () => ({
|
|
207
|
+
id: crypto.randomUUID(),
|
|
208
|
+
name: 'PointLight',
|
|
209
|
+
light: { type: 'point', intensity: 1 },
|
|
210
|
+
}),
|
|
211
|
+
},
|
|
212
|
+
spotLight: {
|
|
213
|
+
label: 'Spot Light',
|
|
214
|
+
make: () => ({
|
|
215
|
+
id: crypto.randomUUID(),
|
|
216
|
+
name: 'SpotLight',
|
|
217
|
+
light: { type: 'spot', intensity: 1 },
|
|
218
|
+
}),
|
|
219
|
+
},
|
|
220
|
+
camera: {
|
|
221
|
+
label: 'Camera',
|
|
222
|
+
make: () => ({ id: crypto.randomUUID(), name: 'Camera', camera: { type: 'perspective' } }),
|
|
223
|
+
},
|
|
224
|
+
audioSource: {
|
|
225
|
+
label: 'Audio Source',
|
|
226
|
+
make: () => ({
|
|
227
|
+
id: crypto.randomUUID(),
|
|
228
|
+
name: 'AudioSource',
|
|
229
|
+
audio: { spatial: true, volume: 1 },
|
|
230
|
+
}),
|
|
231
|
+
},
|
|
232
|
+
spline: {
|
|
233
|
+
label: 'Spline',
|
|
234
|
+
make: () => ({
|
|
235
|
+
id: crypto.randomUUID(),
|
|
236
|
+
name: 'Spline',
|
|
237
|
+
spline: {
|
|
238
|
+
curveType: 'centripetal',
|
|
239
|
+
points: [
|
|
240
|
+
[0, 0, 0],
|
|
241
|
+
[1, 0, 1],
|
|
242
|
+
[2, 0, 0],
|
|
243
|
+
],
|
|
244
|
+
closed: false,
|
|
245
|
+
},
|
|
246
|
+
}),
|
|
247
|
+
},
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
/** The palette order shown in the creation menu — the 12 first-class kinds
|
|
251
|
+
* (legacy 'mesh'/'box' duplicate and internal-only 'group' are reachable by
|
|
252
|
+
* slug but not listed twice / not part of the user-facing palette). */
|
|
253
|
+
const PALETTE_ORDER = [
|
|
254
|
+
'empty',
|
|
255
|
+
'box',
|
|
256
|
+
'sphere',
|
|
257
|
+
'plane',
|
|
258
|
+
'cylinder',
|
|
259
|
+
'capsule',
|
|
260
|
+
'dirLight',
|
|
261
|
+
'pointLight',
|
|
262
|
+
'spotLight',
|
|
263
|
+
'camera',
|
|
264
|
+
'audioSource',
|
|
265
|
+
'spline',
|
|
266
|
+
] as const;
|
|
267
|
+
|
|
268
|
+
/** A minimal first-party `SceneEntity` for a structural `create(kind)`. */
|
|
269
|
+
function entityForKind(kind: string): SceneEntity {
|
|
270
|
+
return (KIND_TEMPLATES[kind] ?? KIND_TEMPLATES['empty']!).make();
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
type AssetDropKind = 'model' | 'image' | 'prefab' | 'audio';
|
|
274
|
+
|
|
275
|
+
/** Classify a dropped asset path by extension — the adapter's own private
|
|
276
|
+
* reading of the shell's asset path string (the shell never passes a `kind`;
|
|
277
|
+
* see `AssetDropProvider`'s contract). Mirrors `AssetBrowser.tsx`'s
|
|
278
|
+
* `getAssetKind`, trimmed to the kinds this adapter can turn into an entity. */
|
|
279
|
+
function kindFromAssetPath(path: string): AssetDropKind | null {
|
|
280
|
+
const lower = path.toLowerCase();
|
|
281
|
+
if (lower.endsWith('.glb') || lower.endsWith('.gltf')) return 'model';
|
|
282
|
+
if (/\.(png|jpe?g|webp|svg|gif)$/.test(lower)) return 'image';
|
|
283
|
+
if (lower.endsWith('.prefab.json')) return 'prefab';
|
|
284
|
+
if (/\.(mp3|ogg|wav|flac)$/.test(lower)) return 'audio';
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** Create entity data from a dropped asset path (mirrors the former
|
|
289
|
+
* `SceneHierarchy.tsx`'s `entityFromAssetDrop`, now keyed off the
|
|
290
|
+
* path-derived kind instead of a caller-supplied one). */
|
|
291
|
+
function entityFromAssetPath(path: string, kind: AssetDropKind): SceneEntity | null {
|
|
292
|
+
const name = path.split('/').pop() ?? path;
|
|
293
|
+
const baseName = name.replace(/\.[^.]+$/, '');
|
|
294
|
+
const id = crypto.randomUUID();
|
|
295
|
+
switch (kind) {
|
|
296
|
+
case 'model':
|
|
297
|
+
return { id, name: baseName, mesh: { type: 'gltf', src: path } };
|
|
298
|
+
case 'image':
|
|
299
|
+
return {
|
|
300
|
+
id,
|
|
301
|
+
name: baseName,
|
|
302
|
+
mesh: { type: 'plane', args: [2, 2] },
|
|
303
|
+
material: { type: 'standard', map: path },
|
|
304
|
+
};
|
|
305
|
+
case 'prefab':
|
|
306
|
+
return { id, name: baseName, prefab: path };
|
|
307
|
+
case 'audio':
|
|
308
|
+
return { id, name: baseName, audio: { src: path, spatial: true, volume: 1 } };
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export class VgaiSceneAuthoringAdapter implements AuthoringAdapter {
|
|
313
|
+
constructor(private readonly store: EditorStore) {}
|
|
314
|
+
|
|
315
|
+
readonly capabilities: AuthoringCapabilities = {
|
|
316
|
+
transform: true,
|
|
317
|
+
material: true,
|
|
318
|
+
inspectorFields: true,
|
|
319
|
+
create: true,
|
|
320
|
+
delete: true,
|
|
321
|
+
reparent: true,
|
|
322
|
+
persist: true,
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
subscribe(listener: () => void): () => void {
|
|
326
|
+
return this.store.subscribe(listener);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
readonly hierarchy: HierarchyProvider = {
|
|
330
|
+
// D8 — member (scene-file root) rows are APPENDED after the store's own
|
|
331
|
+
// entity roots (never interleaved between them): several existing e2e
|
|
332
|
+
// specs range-select or index-scan entity rows by relative position
|
|
333
|
+
// (`29-game-hierarchy.spec.ts`'s multi-select, drag-reorder) — appending
|
|
334
|
+
// keeps every entity's relative order/adjacency byte-identical to today,
|
|
335
|
+
// the only delta is N additional childless rows at the tail.
|
|
336
|
+
roots: () => [...this.store.entities.map((e) => toNode(e, null)), ...this.memberNodes()],
|
|
337
|
+
node: (id) => {
|
|
338
|
+
const memberPath = pathForSceneRootId(id);
|
|
339
|
+
if (memberPath !== null) return this.memberNode(memberPath);
|
|
340
|
+
const hit = findInTree(this.store.entities, id);
|
|
341
|
+
return hit ? toNode(hit.entity, hit.parentId) : null;
|
|
342
|
+
},
|
|
343
|
+
object3D: (id) => this.store.objectMap.get(id) ?? null,
|
|
344
|
+
idForObject3D: (o) => (getUserData(o, 'entityId') as string | undefined) ?? null,
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
/** The project's scene files (module-cached, D8) — [] until the async
|
|
348
|
+
* listing lands, at which point `notifyIngestEdit` re-renders the panel. */
|
|
349
|
+
private scenesList(): string[] {
|
|
350
|
+
ensureProjectScenesLoaded(() => this.store.notifyIngestEdit());
|
|
351
|
+
return getCachedProjectScenes();
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
private memberNodes(): EditorNode[] {
|
|
355
|
+
return this.scenesList()
|
|
356
|
+
.map((path) => this.memberNode(path))
|
|
357
|
+
.filter((n): n is EditorNode => n !== null);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/** `null` for a path that isn't (or is no longer) a known project scene —
|
|
361
|
+
* a stale/dangling `sceneRoot:` id resolves to nothing rather than a
|
|
362
|
+
* fabricated node. */
|
|
363
|
+
private memberNode(path: string): EditorNode | null {
|
|
364
|
+
if (!this.scenesList().includes(path)) return null;
|
|
365
|
+
const isActive =
|
|
366
|
+
this.store.savePath !== null && normalizeScenePath(this.store.savePath) === path;
|
|
367
|
+
return {
|
|
368
|
+
id: sceneRootId(path),
|
|
369
|
+
label: path,
|
|
370
|
+
kind: 'object',
|
|
371
|
+
parentId: null,
|
|
372
|
+
childIds: [],
|
|
373
|
+
// D8 — member of a RootGroup, not the active one (see `authoring.ts`'s
|
|
374
|
+
// `EditorNode.flags.inactiveRoot` doc). The active member gets NO flag
|
|
375
|
+
// so both radio states are plain childless rows (GameHierarchy renders
|
|
376
|
+
// the ● / ○ radio from `rootGroups` data, not from this flag alone).
|
|
377
|
+
flags: isActive ? {} : { inactiveRoot: true },
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* D8 — one mutually-exclusive "Scenes" group over every project
|
|
383
|
+
* `*.vscn.json`. `activate()` flushes a pending autosave first (belt: the
|
|
384
|
+
* 300ms debounce could otherwise lose an edit made just before switching),
|
|
385
|
+
* then loads the target scene into the live store exactly like opening it
|
|
386
|
+
* from the asset browser (`DefaultEditorLayout.tsx`'s `openSceneFromAsset`
|
|
387
|
+
* recipe) — `loadDocument` itself notifies subscribers.
|
|
388
|
+
*/
|
|
389
|
+
readonly rootGroups: RootGroupsProvider = {
|
|
390
|
+
groups: (): RootGroup[] => {
|
|
391
|
+
const scenes = this.scenesList();
|
|
392
|
+
const activeRootId = this.store.savePath
|
|
393
|
+
? sceneRootId(normalizeScenePath(this.store.savePath))
|
|
394
|
+
: null;
|
|
395
|
+
return [
|
|
396
|
+
{
|
|
397
|
+
id: 'scenes',
|
|
398
|
+
label: 'Scenes',
|
|
399
|
+
memberRootIds: scenes.map(sceneRootId),
|
|
400
|
+
activeRootId,
|
|
401
|
+
},
|
|
402
|
+
];
|
|
403
|
+
},
|
|
404
|
+
activate: async (groupId, rootId) => {
|
|
405
|
+
if (groupId !== 'scenes') return;
|
|
406
|
+
const path = pathForSceneRootId(rootId);
|
|
407
|
+
if (!path) return;
|
|
408
|
+
if (this.store.isDirty) await this.store.saveNow();
|
|
409
|
+
try {
|
|
410
|
+
const doc = await fetchSceneDoc(path);
|
|
411
|
+
this.store.loadDocument(doc, path);
|
|
412
|
+
} catch (err) {
|
|
413
|
+
// Plain `console` (not `editorConsole`, which touches `window` at
|
|
414
|
+
// module scope) — this adapter is exercised by node-environment unit
|
|
415
|
+
// tests with no DOM, same reasoning as `editor-store.ts`'s own
|
|
416
|
+
// `console.warn` in `_autoSave`.
|
|
417
|
+
console.error(`[VgaiSceneAuthoringAdapter] failed to activate scene root "${path}":`, err);
|
|
418
|
+
}
|
|
419
|
+
},
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
/** D8 — `*.vscn.json` files map to their scene-root id; anything else maps
|
|
423
|
+
* to nothing (this adapter owns no other file kind as a root). */
|
|
424
|
+
readonly files: FileMapProvider = {
|
|
425
|
+
rootForFile: (path) => {
|
|
426
|
+
const normalized = normalizeScenePath(path);
|
|
427
|
+
return normalized.endsWith('.vscn.json') ? sceneRootId(normalized) : null;
|
|
428
|
+
},
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
readonly selection: SelectionProvider = {
|
|
432
|
+
get: () => [...this.store.selectedEntityIds],
|
|
433
|
+
set: (ids) => this.store.selectMultiple(ids),
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* D12 (B4) — the threejs viewport raycast, delegating to the SHARED
|
|
438
|
+
* `raycastPick` helper (`viewport-raycast.ts`) that was extracted verbatim
|
|
439
|
+
* from `editor-viewport.ts`'s former `_raycastEntity` (behavior-identical
|
|
440
|
+
* checkpoint: `09-editor-interaction.spec.ts` must stay green). Shared so
|
|
441
|
+
* `IngestAuthoringAdapter` reuses the EXACT same raycast (restoring ingest/
|
|
442
|
+
* multi-world-play canvas click-select, which pre-B4 ran unconditionally) —
|
|
443
|
+
* see that helper's doc comment. Absent viewport context ⇒ `null`, an honest
|
|
444
|
+
* headless degrade.
|
|
445
|
+
*/
|
|
446
|
+
readonly pickable: PickProvider = {
|
|
447
|
+
pick: (clientX, clientY) => raycastPick(this.store, clientX, clientY),
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
readonly transforms: TransformProvider = {
|
|
451
|
+
get: (id): Transform => {
|
|
452
|
+
const obj = this.store.objectMap.get(id);
|
|
453
|
+
if (!obj) return { position: [0, 0, 0], rotation: [0, 0, 0, 1], scale: [1, 1, 1] };
|
|
454
|
+
return {
|
|
455
|
+
position: obj.position.toArray() as [number, number, number],
|
|
456
|
+
rotation: obj.quaternion.toArray() as [number, number, number, number],
|
|
457
|
+
scale: obj.scale.toArray() as [number, number, number],
|
|
458
|
+
};
|
|
459
|
+
},
|
|
460
|
+
// Transform owner reflects the active system adapters: a Rapier-driven body
|
|
461
|
+
// reports 'physics'; a replicated remote/server object reports 'network'.
|
|
462
|
+
owner: (id): TransformOwner => {
|
|
463
|
+
const obj = this.store.objectMap.get(id);
|
|
464
|
+
if (!obj) return 'editor';
|
|
465
|
+
const net = getActiveNetworking();
|
|
466
|
+
if (net?.networkId(obj) && net.authority(obj) !== 'local') return 'network';
|
|
467
|
+
return getActivePhysics()?.ownerOf(obj) ?? 'editor';
|
|
468
|
+
},
|
|
469
|
+
// beginEdit snapshots for undo and, for a physics-owned object, FREEZES the
|
|
470
|
+
// body (kinematic) so the gizmo edit isn't overwritten mid-drag. Remote/server
|
|
471
|
+
// network-authoritative objects are inspect-only → the edit is a no-op.
|
|
472
|
+
beginEdit: (id) => {
|
|
473
|
+
const obj = this.store.objectMap.get(id);
|
|
474
|
+
if (obj && !this.editable(obj)) return; // inspect-only (network-owned)
|
|
475
|
+
this.store.pushUndoSnapshot();
|
|
476
|
+
if (obj) getActivePhysics()?.freeze(obj);
|
|
477
|
+
},
|
|
478
|
+
// apply is the LIVE path: move the object + sync the descriptor without
|
|
479
|
+
// pushing undo (beginEdit already snapshotted). The store's ECS-sync callback
|
|
480
|
+
// commits the pose to the owning body (PhysicsAdapter.commit) so it tracks.
|
|
481
|
+
apply: (id, t) => {
|
|
482
|
+
const obj = this.store.objectMap.get(id);
|
|
483
|
+
if (obj && !this.editable(obj)) return; // inspect-only (network-owned)
|
|
484
|
+
if (obj) {
|
|
485
|
+
obj.position.set(t.position[0], t.position[1], t.position[2]);
|
|
486
|
+
obj.quaternion.set(t.rotation[0], t.rotation[1], t.rotation[2], t.rotation[3]);
|
|
487
|
+
obj.scale.set(t.scale[0], t.scale[1], t.scale[2]);
|
|
488
|
+
}
|
|
489
|
+
this.store.updateTransformLive(id, t.position, t.rotation, t.scale);
|
|
490
|
+
},
|
|
491
|
+
endEdit: (id) => {
|
|
492
|
+
const obj = this.store.objectMap.get(id);
|
|
493
|
+
if (obj) getActivePhysics()?.unfreeze(obj);
|
|
494
|
+
this.store.commitLiveTransform();
|
|
495
|
+
},
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
/** False when the active networking adapter marks the object inspect-only. */
|
|
499
|
+
private editable(obj: import('three').Object3D): boolean {
|
|
500
|
+
const net = getActiveNetworking();
|
|
501
|
+
return net ? net.editable(obj) : true;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
readonly inspector: InspectorProvider = {
|
|
505
|
+
properties: (id): PropertyDescriptor[] => {
|
|
506
|
+
const e = this.store.getEntityById(id);
|
|
507
|
+
if (!e) return [];
|
|
508
|
+
const props: PropertyDescriptor[] = [
|
|
509
|
+
{ path: 'name', label: 'Name', type: 'string' },
|
|
510
|
+
{ path: 'visible', label: 'Visible', type: 'boolean' },
|
|
511
|
+
// A2 parity growth — surfaced so the shell's reserved-path lock
|
|
512
|
+
// affordance (hierarchy panel) sees it via `properties()`, exactly
|
|
513
|
+
// like `visible`. Own flag only (NOT `store.isEntityLocked`'s
|
|
514
|
+
// ancestor-merged read) — matches the pre-A2 hierarchy row, whose
|
|
515
|
+
// lock icon/toggle always reflected/toggled the entity's OWN flag.
|
|
516
|
+
{ path: 'locked', label: 'Locked', type: 'boolean' },
|
|
517
|
+
];
|
|
518
|
+
if (e.material) props.push({ path: 'material.color', label: 'Color', type: 'color' });
|
|
519
|
+
// C1 — component CONFIG fields, one PropertyDescriptor per schema field,
|
|
520
|
+
// grouped by component name (mirrors `ComponentsSection.tsx`'s
|
|
521
|
+
// per-component field rows). Only components whose class has a `static
|
|
522
|
+
// schema` get descriptors — schema-less components' raw data isn't
|
|
523
|
+
// format-neutral, so this contract surface stays silent on them (same
|
|
524
|
+
// gate `ComponentsSection` uses for `addable`/`renderSchemaFields`).
|
|
525
|
+
const classes = getLoadedComponents();
|
|
526
|
+
for (const compName of Object.keys(e.components ?? {})) {
|
|
527
|
+
const schema = classes[compName]?.schema;
|
|
528
|
+
if (!schema) continue;
|
|
529
|
+
for (const f of introspectSchema(schema)) {
|
|
530
|
+
props.push({
|
|
531
|
+
path: `components.${compName}.${f.name}`,
|
|
532
|
+
label: f.name,
|
|
533
|
+
type: fieldKindToPropertyType(f.kind),
|
|
534
|
+
group: compName,
|
|
535
|
+
...(f.options ? { options: f.options } : {}),
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
return props;
|
|
540
|
+
},
|
|
541
|
+
get: (id, path) => {
|
|
542
|
+
const e = this.store.getEntityById(id);
|
|
543
|
+
if (!e) return undefined;
|
|
544
|
+
if (path === 'name') return e.name;
|
|
545
|
+
if (path === 'visible') return e.visible !== false;
|
|
546
|
+
if (path === 'locked') return e.locked === true;
|
|
547
|
+
if (path === 'material.color') return e.material?.color;
|
|
548
|
+
const compField = /^components\.([^.]+)\.(.+)$/.exec(path);
|
|
549
|
+
const compName = compField?.[1];
|
|
550
|
+
const fieldName = compField?.[2];
|
|
551
|
+
if (compName !== undefined && fieldName !== undefined) {
|
|
552
|
+
const comp = e.components?.[compName] as Record<string, unknown> | undefined;
|
|
553
|
+
return comp?.[fieldName];
|
|
554
|
+
}
|
|
555
|
+
return undefined;
|
|
556
|
+
},
|
|
557
|
+
set: (id, path, value) => {
|
|
558
|
+
const compField = /^components\.([^.]+)\.(.+)$/.exec(path);
|
|
559
|
+
const compName = compField?.[1];
|
|
560
|
+
const fieldName = compField?.[2];
|
|
561
|
+
if (compName !== undefined && fieldName !== undefined) {
|
|
562
|
+
const e = this.store.getEntityById(id);
|
|
563
|
+
if (!e) return;
|
|
564
|
+
const components = (e.components ?? {}) as Record<string, Record<string, unknown>>;
|
|
565
|
+
const next: Record<string, Record<string, unknown>> = {
|
|
566
|
+
...components,
|
|
567
|
+
[compName]: { ...components[compName], [fieldName]: value },
|
|
568
|
+
};
|
|
569
|
+
this.store.updateEntity(id, { components: next as unknown as ComponentDataMap });
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
if (path === 'name') this.store.updateEntity(id, { name: String(value) });
|
|
573
|
+
else if (path === 'visible') this.store.updateEntity(id, { visible: Boolean(value) });
|
|
574
|
+
else if (path === 'locked') {
|
|
575
|
+
const e = this.store.getEntityById(id);
|
|
576
|
+
if (e && (e.locked === true) !== Boolean(value)) this.store.toggleLocked(id);
|
|
577
|
+
} else if (path === 'material.color') {
|
|
578
|
+
this.store.batchMutate([id], (e) => {
|
|
579
|
+
if (e.material) e.material.color = String(value);
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
},
|
|
583
|
+
};
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* C1 (D6, `docs/unified-world-editor/26-unified-editor-spec.md` §C1) —
|
|
587
|
+
* GameComponent management. Mirrors `ComponentsSection.tsx:44-75` byte for
|
|
588
|
+
* byte: there are no dedicated store verbs, add/remove both go through
|
|
589
|
+
* `store.updateEntity(id, { components })` — undo is FREE (`updateEntity`
|
|
590
|
+
* calls `_pushUndo()` before mutating), so this provider needs no `undo()`
|
|
591
|
+
* of its own and must never push its own snapshot. `getLoadedComponents()`
|
|
592
|
+
* is empty until a project's scripts have been loaded at least once (play
|
|
593
|
+
* mode, or `setLoadedComponents` in tests) — that is a real design-time
|
|
594
|
+
* limitation this provider mirrors rather than works around (out of scope
|
|
595
|
+
* here — see the plan's eager-load exclusion).
|
|
596
|
+
*/
|
|
597
|
+
readonly components: ComponentsProvider = {
|
|
598
|
+
available: (nodeId) => {
|
|
599
|
+
const e = this.store.getEntityById(nodeId);
|
|
600
|
+
if (!e) return [];
|
|
601
|
+
const components = (e.components ?? {}) as Record<string, Record<string, unknown>>;
|
|
602
|
+
const classes = getLoadedComponents();
|
|
603
|
+
return Object.keys(classes)
|
|
604
|
+
.filter((name) => classes[name]?.schema && !(name in components))
|
|
605
|
+
.sort();
|
|
606
|
+
},
|
|
607
|
+
list: (nodeId) => {
|
|
608
|
+
const e = this.store.getEntityById(nodeId);
|
|
609
|
+
if (!e) return [];
|
|
610
|
+
return Object.keys(e.components ?? {}).map((type) => ({ type }));
|
|
611
|
+
},
|
|
612
|
+
config: (nodeId, type) => {
|
|
613
|
+
const e = this.store.getEntityById(nodeId);
|
|
614
|
+
const comp = (e?.components as Record<string, Record<string, unknown>> | undefined)?.[type];
|
|
615
|
+
return comp ? { ...comp } : null;
|
|
616
|
+
},
|
|
617
|
+
add: (nodeId, type) => {
|
|
618
|
+
const e = this.store.getEntityById(nodeId);
|
|
619
|
+
if (!e) return;
|
|
620
|
+
const components = (e.components ?? {}) as Record<string, Record<string, unknown>>;
|
|
621
|
+
const schema = getLoadedComponents()[type]?.schema;
|
|
622
|
+
const data: Record<string, unknown> = {};
|
|
623
|
+
if (schema) {
|
|
624
|
+
for (const f of introspectSchema(schema)) {
|
|
625
|
+
if (f.default !== undefined) data[f.name] = f.default;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
const next: Record<string, Record<string, unknown>> = { ...components, [type]: data };
|
|
629
|
+
this.store.updateEntity(nodeId, { components: next as unknown as ComponentDataMap });
|
|
630
|
+
},
|
|
631
|
+
remove: (nodeId, type) => {
|
|
632
|
+
const e = this.store.getEntityById(nodeId);
|
|
633
|
+
if (!e) return;
|
|
634
|
+
const next = { ...(e.components ?? {}) } as Record<string, Record<string, unknown>>;
|
|
635
|
+
delete next[type];
|
|
636
|
+
this.store.updateEntity(nodeId, { components: next as unknown as ComponentDataMap });
|
|
637
|
+
},
|
|
638
|
+
};
|
|
639
|
+
|
|
640
|
+
readonly structure: StructureProvider = {
|
|
641
|
+
create: (kind, parentId) => {
|
|
642
|
+
const entity = entityForKind(kind);
|
|
643
|
+
this.store.addEntity(parentId ?? null, entity);
|
|
644
|
+
return entity.id!;
|
|
645
|
+
},
|
|
646
|
+
remove: (id) => this.store.removeEntity(id),
|
|
647
|
+
duplicate: (id) => {
|
|
648
|
+
this.store.duplicateEntity(id);
|
|
649
|
+
// duplicateEntity selects the clone → read its id back.
|
|
650
|
+
return [...this.store.selectedEntityIds][0] ?? id;
|
|
651
|
+
},
|
|
652
|
+
reparent: (id, newParentId) => {
|
|
653
|
+
// Append under the new parent (large index → splice appends).
|
|
654
|
+
this.store.moveEntity([id], newParentId, Number.MAX_SAFE_INTEGER);
|
|
655
|
+
},
|
|
656
|
+
// A2 parity growth — sibling reorder, wrapping `store.moveEntity` (the
|
|
657
|
+
// same verb `reparent` above already wraps) with an index computed from
|
|
658
|
+
// the CURRENT sibling list minus the moving id itself (moveEntity always
|
|
659
|
+
// removes the dragged objects from their old parent before re-reading
|
|
660
|
+
// the target's children, so the index must already exclude them).
|
|
661
|
+
reorder: (id, beforeSiblingId) => {
|
|
662
|
+
const hit = findInTree(this.store.entities, id);
|
|
663
|
+
if (!hit) return;
|
|
664
|
+
const parentId = hit.parentId;
|
|
665
|
+
const parentEntity = parentId ? findInTree(this.store.entities, parentId)?.entity : null;
|
|
666
|
+
const siblings = (parentId ? (parentEntity?.children ?? []) : this.store.entities).filter(
|
|
667
|
+
(e) => e.id !== id,
|
|
668
|
+
);
|
|
669
|
+
const index =
|
|
670
|
+
beforeSiblingId === null
|
|
671
|
+
? siblings.length
|
|
672
|
+
: (() => {
|
|
673
|
+
const idx = siblings.findIndex((e) => e.id === beforeSiblingId);
|
|
674
|
+
return idx < 0 ? siblings.length : idx;
|
|
675
|
+
})();
|
|
676
|
+
this.store.moveEntity([id], parentId, index);
|
|
677
|
+
},
|
|
678
|
+
// A2 parity growth — the former toolbar-only `addMenuItems` list (12
|
|
679
|
+
// kinds), now driving the shell's generic Create▸ palette for ANY
|
|
680
|
+
// parent (root via the toolbar `+`, or a specific row via the context
|
|
681
|
+
// menu) — a capability the old toolbar-only Add menu never had.
|
|
682
|
+
creatableKinds: () =>
|
|
683
|
+
PALETTE_ORDER.map((kind) => ({ kind, label: KIND_TEMPLATES[kind]!.label })),
|
|
684
|
+
};
|
|
685
|
+
|
|
686
|
+
/**
|
|
687
|
+
* A2 parity growth — port of the former `SceneHierarchy.tsx`'s
|
|
688
|
+
* `entityFromAssetDrop` + its animation-attach special case, now living
|
|
689
|
+
* behind the format-neutral `AssetDropProvider` seam. The shell passes only
|
|
690
|
+
* a node id + asset path (never a `kind` — that would leak `SceneEntity`
|
|
691
|
+
* vocabulary across Rule Zero), so this adapter re-derives the kind from
|
|
692
|
+
* the path extension itself (`kindFromAssetPath`).
|
|
693
|
+
*
|
|
694
|
+
* Root-drop sentinel: `nodeId === ''` means "no target row" (the shell's
|
|
695
|
+
* tail/background drop) — there being no `AssetDropProvider` parameter for
|
|
696
|
+
* "no parent" (unlike `StructureProvider.create`'s optional `parentId`),
|
|
697
|
+
* this adapter and its callers (the shell, and
|
|
698
|
+
* `CompositeAuthoringAdapter`'s group-node translation) share this one
|
|
699
|
+
* local convention. Any OTHER nodeId is treated as "drop into/onto this
|
|
700
|
+
* entity" (becomes its child). (The former 'animation' asset kind — drop a
|
|
701
|
+
* `.animgraph.json` onto an entity to attach it directly — was removed by
|
|
702
|
+
* E5; there is no drop-an-asset equivalent for XState-driven animation, it
|
|
703
|
+
* is authored in component code.)
|
|
704
|
+
*/
|
|
705
|
+
readonly assetDrop: AssetDropProvider = {
|
|
706
|
+
accepts: (_nodeId, assetPath) => kindFromAssetPath(assetPath) !== null,
|
|
707
|
+
drop: (nodeId, assetPath) => {
|
|
708
|
+
const kind = kindFromAssetPath(assetPath);
|
|
709
|
+
if (!kind) return;
|
|
710
|
+
const parentId = nodeId === '' ? null : nodeId;
|
|
711
|
+
const entity = entityFromAssetPath(assetPath, kind);
|
|
712
|
+
if (entity) this.store.addEntity(parentId, entity);
|
|
713
|
+
},
|
|
714
|
+
};
|
|
715
|
+
|
|
716
|
+
// A class GETTER (not a field initializer) — a field initializer runs before
|
|
717
|
+
// the constructor body assigns `this.store` (parameter properties assign in
|
|
718
|
+
// the constructor body, same as any other constructor statement), so a plain
|
|
719
|
+
// object-literal field would capture `this.store` as `undefined`. A getter
|
|
720
|
+
// defers construction of the provider object until first access, by which
|
|
721
|
+
// point the instance is fully built.
|
|
722
|
+
get persistence(): PersistenceProvider {
|
|
723
|
+
const store = this.store;
|
|
724
|
+
return {
|
|
725
|
+
isDirty: () => store.isDirty,
|
|
726
|
+
save: async () => {
|
|
727
|
+
await store.saveNow();
|
|
728
|
+
},
|
|
729
|
+
serialize: () => store.getDocument(),
|
|
730
|
+
captureToVscn: (ids) =>
|
|
731
|
+
ids.map((id) => store.getEntityById(id)).filter((e): e is SceneEntity => e !== null),
|
|
732
|
+
// Live — reflects `store.savePath`, which changes on loadDocument/loadPrefab.
|
|
733
|
+
get destination(): string {
|
|
734
|
+
return store.savePath ?? '(no scene loaded)';
|
|
735
|
+
},
|
|
736
|
+
// The reload contract (design §5): the existing `applyExternalUpdate`
|
|
737
|
+
// diff-apply path, exposed through the provider instead of the SSE handler
|
|
738
|
+
// reaching into the store directly (`rawContent` forwarded so own-echo
|
|
739
|
+
// detection — `EditorStore`'s private epoch/cooldown discipline — still
|
|
740
|
+
// works exactly as it did before this was routed through the provider).
|
|
741
|
+
applyExternal: (content, rawContent) => {
|
|
742
|
+
store.applyExternalUpdate(content as SceneFile, rawContent);
|
|
743
|
+
},
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
}
|