@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,700 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Editor server SDK.
|
|
3
|
+
*
|
|
4
|
+
* All frontend code should use this module instead of calling fetch()/EventSource
|
|
5
|
+
* with raw endpoint URLs.
|
|
6
|
+
*
|
|
7
|
+
* The project's `public/` file operations (`listAssets`, `saveFile`) are routed
|
|
8
|
+
* through the backend-agnostic `StorageBackend` (see ./storage): locally that is
|
|
9
|
+
* `HttpStorage` over the same `/__editor/*` routes (behavior unchanged); in a
|
|
10
|
+
* server-less browser build it is `OpfsStorage`/`FsaStorage`, so the editor's
|
|
11
|
+
* file I/O works with no Node server. The remaining endpoints are NOT `public/`
|
|
12
|
+
* file ops, and split three ways in browser mode (Phase A cleanup — see
|
|
13
|
+
* `docs/FRONTEND-FIRST-ROADMAP.md` Phase A's "retire the remaining Node-only
|
|
14
|
+
* `/__editor/*` ops" bullet):
|
|
15
|
+
* - `loadEditorState`/`saveEditorState` route through `getStorageBackend()`
|
|
16
|
+
* directly (`.vgai/editor-state.json`, project-root-relative — same
|
|
17
|
+
* namespace `authoring/world-overlay.ts` already established), because
|
|
18
|
+
* remembering view/tool state is genuine authoring-loop parity.
|
|
19
|
+
* - Background, non-actionable reads with no independent browser-mode value
|
|
20
|
+
* degrade to an honest empty result with NO fetch attempted at all:
|
|
21
|
+
* `listRecentProjects`/`listExamples` (browser mode has no server-tracked
|
|
22
|
+
* project history), `saveThumbnail` (its only consumer is the Node-only
|
|
23
|
+
* recent-projects list), `startLogSession`/`endLogSession`/
|
|
24
|
+
* `flushLogEntries` (agent/CLI-tooling introspection — bucketed with the
|
|
25
|
+
* CLI control channel, which is already fully dropped in browser mode by
|
|
26
|
+
* `command-listener.ts`), and the read-only asset-library lookups.
|
|
27
|
+
* - User-triggered actions with no browser equivalent throw a clear, honest
|
|
28
|
+
* `Error` instead of doing a doomed fetch: `createProject`, `browseFolder`,
|
|
29
|
+
* `openProjectOnServer`, `revealInFinder`, `startExport`,
|
|
30
|
+
* `downloadOnlineAsset` — the browser-mode UI either hides the affordance
|
|
31
|
+
* (Build/Library tabs, "Show in Finder") or catches the throw into an
|
|
32
|
+
* existing error banner (`ProjectScreen.tsx`).
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import { resolveUrl } from '@engine/loader';
|
|
36
|
+
import { getEditorMode } from './editor-mode';
|
|
37
|
+
import { getStorageBackend } from './storage';
|
|
38
|
+
|
|
39
|
+
const BASE = '/__editor';
|
|
40
|
+
|
|
41
|
+
/** Browser mode has no Node dev server — every `/__editor/*` op below either
|
|
42
|
+
* routes through `getStorageBackend()` or degrades explicitly; this helper
|
|
43
|
+
* just names the check so each function reads the same way. */
|
|
44
|
+
function isBrowserMode(): boolean {
|
|
45
|
+
return getEditorMode() === 'browser';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Read a project text file, storage-backend first, then static HTTP fallback.
|
|
50
|
+
*
|
|
51
|
+
* A prefab/asset the user just saved lives in the storage backend (OPFS in the
|
|
52
|
+
* browser build); a prefab shipped with the project is a static file served over
|
|
53
|
+
* HTTP. Reading storage-first makes both work — critically, it lets a
|
|
54
|
+
* just-created prefab be resolved/unpacked in the server-less browser editor,
|
|
55
|
+
* where a plain `fetch()` of the OPFS path would 404. Returns null if neither has it.
|
|
56
|
+
*/
|
|
57
|
+
export async function readProjectTextFile(path: string): Promise<string | null> {
|
|
58
|
+
const key = path.replace(/^\//, '');
|
|
59
|
+
try {
|
|
60
|
+
const be = getStorageBackend();
|
|
61
|
+
if (await be.exists(key)) return await be.read(key);
|
|
62
|
+
} catch {
|
|
63
|
+
/* fall through to HTTP */
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
const res = await fetch(resolveUrl(path));
|
|
67
|
+
if (res.ok) return await res.text();
|
|
68
|
+
} catch {
|
|
69
|
+
/* ignore */
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface AssetEntry {
|
|
75
|
+
name: string;
|
|
76
|
+
type: 'file' | 'directory';
|
|
77
|
+
size: number;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** List files and folders in a directory under public/. */
|
|
81
|
+
export async function listAssets(root: string, dir: string): Promise<AssetEntry[]> {
|
|
82
|
+
void root; // backend is project-rooted; `root` kept for signature stability
|
|
83
|
+
try {
|
|
84
|
+
const entries = await getStorageBackend().list(dir);
|
|
85
|
+
return entries.map((e) => ({
|
|
86
|
+
name: e.name,
|
|
87
|
+
type: e.type === 'dir' ? 'directory' : 'file',
|
|
88
|
+
size: e.size ?? 0,
|
|
89
|
+
}));
|
|
90
|
+
} catch {
|
|
91
|
+
return [];
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Save a file to public/. Returns true on success. */
|
|
96
|
+
export async function saveFile(
|
|
97
|
+
path: string,
|
|
98
|
+
content: string,
|
|
99
|
+
encoding?: 'base64',
|
|
100
|
+
): Promise<boolean> {
|
|
101
|
+
try {
|
|
102
|
+
// base64 content → raw bytes; the backend re-encodes per its transport.
|
|
103
|
+
const data: string | Uint8Array =
|
|
104
|
+
encoding === 'base64' ? Uint8Array.from(atob(content), (c) => c.charCodeAt(0)) : content;
|
|
105
|
+
await getStorageBackend().write(path, data);
|
|
106
|
+
return true;
|
|
107
|
+
} catch {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Start a build/export. Returns the raw Response for SSE streaming. */
|
|
113
|
+
export async function startExport(target: string, signal?: AbortSignal | null): Promise<Response> {
|
|
114
|
+
if (isBrowserMode()) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
'Export is not available in browser mode — building a game requires the Node dev server ' +
|
|
117
|
+
'(`npm run dev`) or CLI (`vgai edit`).',
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
const init: RequestInit = {
|
|
121
|
+
method: 'POST',
|
|
122
|
+
headers: { 'Content-Type': 'application/json' },
|
|
123
|
+
body: JSON.stringify({ target }),
|
|
124
|
+
};
|
|
125
|
+
if (signal) init.signal = signal;
|
|
126
|
+
return fetch(`${BASE}/export`, init);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Build a download URL for a build artifact. */
|
|
130
|
+
export function getDownloadUrl(file: string): string {
|
|
131
|
+
return `${BASE}/download?file=${encodeURIComponent(file)}`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* The subset of `EventSource`'s surface every caller here actually uses.
|
|
136
|
+
* `close()` on the object `connectEvents()` returns detaches only the CALLING
|
|
137
|
+
* consumer (see below) — it is deliberately not a real `EventSource`, so
|
|
138
|
+
* nothing can reach in and close the shared socket out from under a sibling
|
|
139
|
+
* consumer.
|
|
140
|
+
*/
|
|
141
|
+
export interface EditorEventSource {
|
|
142
|
+
addEventListener(type: string, listener: (event: MessageEvent) => void): void;
|
|
143
|
+
removeEventListener(type: string, listener: (event: MessageEvent) => void): void;
|
|
144
|
+
close(): void;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
let sharedSource: EventSource | null = null;
|
|
148
|
+
let sharedRefCount = 0;
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Connect to the editor's SSE event stream.
|
|
152
|
+
*
|
|
153
|
+
* SHARED and ref-counted across every caller (W6a field note,
|
|
154
|
+
* docs/DATA-TOOLS-DESIGN.md §7): this used to hand back a brand-new
|
|
155
|
+
* `EventSource` per call, and by the time W6a added its 2 new push
|
|
156
|
+
* channels (tool/data-file add+unlink) this editor had **6** independent
|
|
157
|
+
* long-lived consumers (asset-moved + scene-changed in
|
|
158
|
+
* `DefaultEditorLayout`, the CLI command channel, console server-logs, and
|
|
159
|
+
* the 2 new ones) — landing EXACTLY on Chromium's hard 6-connections-
|
|
160
|
+
* per-origin cap for HTTP/1.1 and permanently starving every OTHER fetch
|
|
161
|
+
* to this origin (module chunks, `/__editor/tool-files`, everything) once
|
|
162
|
+
* all 6 slots were claimed forever. Verified live: with 6 real
|
|
163
|
+
* `EventSource`s already open, a 7th never left `readyState 0`
|
|
164
|
+
* (CONNECTING) — nor did ordinary `fetch()` calls issued after it.
|
|
165
|
+
*
|
|
166
|
+
* The fix is ONE real connection, reference-counted: `addEventListener`/
|
|
167
|
+
* `removeEventListener` proxy straight through to it, and `close()` only
|
|
168
|
+
* removes THIS caller's own listeners and decrements the count — the
|
|
169
|
+
* underlying socket closes for real only once the last caller releases it.
|
|
170
|
+
* However many editor features want server push, the origin only ever
|
|
171
|
+
* pays for one connection.
|
|
172
|
+
*/
|
|
173
|
+
export function connectEvents(): EditorEventSource {
|
|
174
|
+
if (!sharedSource) {
|
|
175
|
+
sharedSource = new EventSource(`${BASE}/events`);
|
|
176
|
+
}
|
|
177
|
+
const real = sharedSource;
|
|
178
|
+
sharedRefCount++;
|
|
179
|
+
|
|
180
|
+
const ownListeners: Array<{ type: string; listener: EventListener }> = [];
|
|
181
|
+
let released = false;
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
addEventListener(type, listener) {
|
|
185
|
+
const wrapped = listener as EventListener;
|
|
186
|
+
real.addEventListener(type, wrapped);
|
|
187
|
+
ownListeners.push({ type, listener: wrapped });
|
|
188
|
+
},
|
|
189
|
+
removeEventListener(type, listener) {
|
|
190
|
+
const wrapped = listener as EventListener;
|
|
191
|
+
real.removeEventListener(type, wrapped);
|
|
192
|
+
const idx = ownListeners.findIndex((l) => l.type === type && l.listener === wrapped);
|
|
193
|
+
if (idx >= 0) ownListeners.splice(idx, 1);
|
|
194
|
+
},
|
|
195
|
+
close() {
|
|
196
|
+
if (released) return;
|
|
197
|
+
released = true;
|
|
198
|
+
for (const { type, listener } of ownListeners) real.removeEventListener(type, listener);
|
|
199
|
+
ownListeners.length = 0;
|
|
200
|
+
sharedRefCount--;
|
|
201
|
+
if (sharedRefCount <= 0) {
|
|
202
|
+
real.close();
|
|
203
|
+
if (sharedSource === real) sharedSource = null;
|
|
204
|
+
sharedRefCount = 0;
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Report the current editor state to the server (called by command-listener). */
|
|
211
|
+
export async function reportEditorState(state: Record<string, unknown>): Promise<void> {
|
|
212
|
+
try {
|
|
213
|
+
await fetch(`${BASE}/state`, {
|
|
214
|
+
method: 'POST',
|
|
215
|
+
headers: { 'Content-Type': 'application/json' },
|
|
216
|
+
body: JSON.stringify(state),
|
|
217
|
+
});
|
|
218
|
+
} catch {
|
|
219
|
+
/* best-effort */
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** Report a command result back to the server so the SDK/CLI gets the response.
|
|
224
|
+
* `data` carries an optional payload for commands that answer with data
|
|
225
|
+
* (e.g. `capture-viewport`'s base64 PNG) — passed through verbatim to the
|
|
226
|
+
* SDK/CLI caller's response body. */
|
|
227
|
+
export async function reportCommandResult(
|
|
228
|
+
requestId: string,
|
|
229
|
+
ok: boolean,
|
|
230
|
+
error?: string,
|
|
231
|
+
data?: Record<string, unknown>,
|
|
232
|
+
): Promise<void> {
|
|
233
|
+
try {
|
|
234
|
+
await fetch(`${BASE}/command-result`, {
|
|
235
|
+
method: 'POST',
|
|
236
|
+
headers: { 'Content-Type': 'application/json' },
|
|
237
|
+
body: JSON.stringify({ _requestId: requestId, ok, error, data }),
|
|
238
|
+
});
|
|
239
|
+
} catch {
|
|
240
|
+
/* best-effort */
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export interface RecentProject {
|
|
245
|
+
name: string;
|
|
246
|
+
path: string;
|
|
247
|
+
lastOpened: string;
|
|
248
|
+
thumbnail?: string;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** List recently opened projects. Always empty in browser mode — there is no
|
|
252
|
+
* Node-tracked project history there (A1's persisted-root mechanism
|
|
253
|
+
* remembers exactly one folder, not a list); an empty result is the honest
|
|
254
|
+
* state, not a degraded one. */
|
|
255
|
+
export async function listRecentProjects(): Promise<RecentProject[]> {
|
|
256
|
+
if (isBrowserMode()) return [];
|
|
257
|
+
try {
|
|
258
|
+
const res = await fetch(`${BASE}/recent-projects`);
|
|
259
|
+
if (!res.ok) return [];
|
|
260
|
+
const data = (await res.json()) as { projects: RecentProject[] };
|
|
261
|
+
return data.projects;
|
|
262
|
+
} catch {
|
|
263
|
+
return [];
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export interface ExampleInfo {
|
|
268
|
+
id: string;
|
|
269
|
+
name: string;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** List `examples/<id>/` projects available to scaffold from (Track E3, §1.F).
|
|
273
|
+
* Always empty in browser mode — this is the on-disk scaffold-a-new-project
|
|
274
|
+
* flow; bundled examples are reached in browser mode via the separate
|
|
275
|
+
* `?project=<id>` hosted deep-link (`hosted-example.ts`) instead. */
|
|
276
|
+
export async function listExamples(): Promise<ExampleInfo[]> {
|
|
277
|
+
if (isBrowserMode()) return [];
|
|
278
|
+
try {
|
|
279
|
+
const res = await fetch(`${BASE}/examples`);
|
|
280
|
+
if (!res.ok) return [];
|
|
281
|
+
const data = (await res.json()) as { examples: ExampleInfo[] };
|
|
282
|
+
return data.examples;
|
|
283
|
+
} catch {
|
|
284
|
+
return [];
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** Create a new project from a template. */
|
|
289
|
+
export async function createProject(
|
|
290
|
+
name: string,
|
|
291
|
+
location: string,
|
|
292
|
+
template: 'starter' | 'empty' | 'example',
|
|
293
|
+
exampleId?: string,
|
|
294
|
+
): Promise<{ ok: boolean; path?: string; config?: { name: string }; error?: string }> {
|
|
295
|
+
if (isBrowserMode()) {
|
|
296
|
+
throw new Error(
|
|
297
|
+
'Creating a project on disk from a template needs the Node dev server. In browser mode, ' +
|
|
298
|
+
'use "Open Folder…" to author a real on-disk project via File System Access, or just start ' +
|
|
299
|
+
'editing — a Starter project is already seeded in your browser storage.',
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
const res = await fetch(`${BASE}/create-project`, {
|
|
303
|
+
method: 'POST',
|
|
304
|
+
headers: { 'Content-Type': 'application/json' },
|
|
305
|
+
body: JSON.stringify({ name, location, template, ...(exampleId ? { exampleId } : {}) }),
|
|
306
|
+
});
|
|
307
|
+
return (await res.json()) as {
|
|
308
|
+
ok: boolean;
|
|
309
|
+
path?: string;
|
|
310
|
+
config?: { name: string };
|
|
311
|
+
error?: string;
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** Open a native folder picker dialog. Returns the selected path, or null if cancelled. */
|
|
316
|
+
export async function browseFolder(title?: string): Promise<string | null> {
|
|
317
|
+
if (isBrowserMode()) {
|
|
318
|
+
throw new Error(
|
|
319
|
+
'The native folder picker needs the Node dev server. In browser mode, use "Open Folder…" ' +
|
|
320
|
+
'(above) instead — it picks a real on-disk folder via File System Access.',
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
try {
|
|
324
|
+
const res = await fetch(`${BASE}/browse-folder`, {
|
|
325
|
+
method: 'POST',
|
|
326
|
+
headers: { 'Content-Type': 'application/json' },
|
|
327
|
+
body: JSON.stringify({ title }),
|
|
328
|
+
});
|
|
329
|
+
if (!res.ok) return null;
|
|
330
|
+
const data = (await res.json()) as { path: string | null };
|
|
331
|
+
return data.path;
|
|
332
|
+
} catch {
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/** Reveal a file or folder in the OS file manager. */
|
|
338
|
+
export async function revealInFinder(targetPath: string): Promise<void> {
|
|
339
|
+
if (isBrowserMode()) {
|
|
340
|
+
throw new Error(
|
|
341
|
+
'Revealing a file in the OS file manager needs the Node dev server — a browser-mode project ' +
|
|
342
|
+
'has no OS path to reveal (File System Access does not expose one).',
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
await fetch(`${BASE}/reveal`, {
|
|
346
|
+
method: 'POST',
|
|
347
|
+
headers: { 'Content-Type': 'application/json' },
|
|
348
|
+
body: JSON.stringify({ path: targetPath }),
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/** Save a viewport thumbnail for the current project. Always a no-op in
|
|
353
|
+
* browser mode — its only consumer is the Node-only recent-projects list
|
|
354
|
+
* (`listRecentProjects`), which has no browser-mode equivalent to attach a
|
|
355
|
+
* thumbnail to; this is background bookkeeping, not a user-facing promise. */
|
|
356
|
+
export async function saveThumbnail(dataUrl: string): Promise<boolean> {
|
|
357
|
+
if (isBrowserMode()) return false;
|
|
358
|
+
try {
|
|
359
|
+
const res = await fetch(`${BASE}/save-thumbnail`, {
|
|
360
|
+
method: 'POST',
|
|
361
|
+
headers: { 'Content-Type': 'application/json' },
|
|
362
|
+
body: JSON.stringify({ dataUrl }),
|
|
363
|
+
});
|
|
364
|
+
return res.ok;
|
|
365
|
+
} catch {
|
|
366
|
+
return false;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/** Query the server for the currently active project (if any). */
|
|
371
|
+
export async function getCurrentServerProject(): Promise<{
|
|
372
|
+
path: string;
|
|
373
|
+
config: { name: string };
|
|
374
|
+
/** Whether the project ships src/ui/registry.ts (scene-UI project mode, D9). */
|
|
375
|
+
hasUiRegistry?: boolean;
|
|
376
|
+
/** Whether this server is the no-monorepo-checkout packaged runtime
|
|
377
|
+
* (`packages/editor/server/packaged.ts`) — see `adapter-resolver.ts`'s
|
|
378
|
+
* `isPackagedRuntime` for why a react world's mount needs this. */
|
|
379
|
+
packaged?: boolean;
|
|
380
|
+
} | null> {
|
|
381
|
+
// Browser mode has no Node dev server to ask — same honest no-fetch
|
|
382
|
+
// degrade as the other background reads in this file (Phase A).
|
|
383
|
+
if (isBrowserMode()) return null;
|
|
384
|
+
try {
|
|
385
|
+
const res = await fetch(`${BASE}/project`);
|
|
386
|
+
if (!res.ok) return null;
|
|
387
|
+
const data = (await res.json()) as {
|
|
388
|
+
project: {
|
|
389
|
+
path: string;
|
|
390
|
+
config: { name: string };
|
|
391
|
+
hasUiRegistry?: boolean;
|
|
392
|
+
packaged?: boolean;
|
|
393
|
+
} | null;
|
|
394
|
+
};
|
|
395
|
+
return data.project;
|
|
396
|
+
} catch {
|
|
397
|
+
return null;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/** Open/switch to a project by its filesystem path. */
|
|
402
|
+
export async function openProjectOnServer(
|
|
403
|
+
projectPath: string,
|
|
404
|
+
): Promise<{ ok: boolean; config?: { name: string } }> {
|
|
405
|
+
if (isBrowserMode()) {
|
|
406
|
+
throw new Error(
|
|
407
|
+
'Opening a project by filesystem path needs the Node dev server. In browser mode, use ' +
|
|
408
|
+
'"Open Folder…" instead — it picks a real on-disk folder via File System Access.',
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
const res = await fetch(`${BASE}/open-project`, {
|
|
412
|
+
method: 'POST',
|
|
413
|
+
headers: { 'Content-Type': 'application/json' },
|
|
414
|
+
body: JSON.stringify({ path: projectPath }),
|
|
415
|
+
});
|
|
416
|
+
return (await res.json()) as { ok: boolean; config?: { name: string } };
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// ---------------------------------------------------------------------------
|
|
420
|
+
// Persistent editor state (.vgai/editor-state.json)
|
|
421
|
+
// ---------------------------------------------------------------------------
|
|
422
|
+
|
|
423
|
+
/** Project-root-relative path — same `.vgai/` namespace as
|
|
424
|
+
* `authoring/world-overlay.ts`'s overlays, and the exact file the server's
|
|
425
|
+
* `/__editor/editor-state` route reads/writes via `node:fs`. */
|
|
426
|
+
const EDITOR_STATE_PATH = '.vgai/editor-state.json';
|
|
427
|
+
|
|
428
|
+
/** Load persisted editor state. Returns {} if none exists. Browser mode goes
|
|
429
|
+
* straight through `getStorageBackend()` (OPFS/FSA root at the true project
|
|
430
|
+
* folder, no `public/` insertion) instead of the Node-only route — this is
|
|
431
|
+
* genuine authoring-loop parity (remembered transform mode/snap/last
|
|
432
|
+
* scene/camera), not a background nicety, so it is ROUTED, not dropped. */
|
|
433
|
+
export async function loadEditorState(): Promise<Record<string, unknown>> {
|
|
434
|
+
try {
|
|
435
|
+
if (isBrowserMode()) {
|
|
436
|
+
const backend = getStorageBackend();
|
|
437
|
+
if (!(await backend.exists(EDITOR_STATE_PATH))) return {};
|
|
438
|
+
return JSON.parse(await backend.read(EDITOR_STATE_PATH)) as Record<string, unknown>;
|
|
439
|
+
}
|
|
440
|
+
const res = await fetch(`${BASE}/editor-state`);
|
|
441
|
+
if (!res.ok) return {};
|
|
442
|
+
return (await res.json()) as Record<string, unknown>;
|
|
443
|
+
} catch {
|
|
444
|
+
return {};
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/** Save editor state (persisted in .vgai/editor-state.json — see
|
|
449
|
+
* `loadEditorState`'s doc comment for the browser-mode routing). */
|
|
450
|
+
export async function saveEditorState(state: Record<string, unknown>): Promise<boolean> {
|
|
451
|
+
try {
|
|
452
|
+
if (isBrowserMode()) {
|
|
453
|
+
await getStorageBackend().write(EDITOR_STATE_PATH, JSON.stringify(state, null, 2));
|
|
454
|
+
return true;
|
|
455
|
+
}
|
|
456
|
+
const res = await fetch(`${BASE}/editor-state`, {
|
|
457
|
+
method: 'POST',
|
|
458
|
+
headers: { 'Content-Type': 'application/json' },
|
|
459
|
+
body: JSON.stringify(state),
|
|
460
|
+
});
|
|
461
|
+
return res.ok;
|
|
462
|
+
} catch {
|
|
463
|
+
return false;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// ---------------------------------------------------------------------------
|
|
468
|
+
// Play-mode console log persistence
|
|
469
|
+
// ---------------------------------------------------------------------------
|
|
470
|
+
|
|
471
|
+
export interface LogEntry {
|
|
472
|
+
t: number;
|
|
473
|
+
level: string;
|
|
474
|
+
source?: string;
|
|
475
|
+
sub?: string;
|
|
476
|
+
msg: string;
|
|
477
|
+
meta?: Record<string, unknown>;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/** Start a new log session. Returns the filename, or null on failure. Always
|
|
481
|
+
* `null` in browser mode with NO fetch attempted — play-mode console → JSONL
|
|
482
|
+
* persistence is `vgai apply-diff`/agent-tooling introspection, bucketed
|
|
483
|
+
* with the CLI control channel (already fully dropped in browser mode by
|
|
484
|
+
* `command-listener.ts`): no browser-mode consumer reads this file. */
|
|
485
|
+
export async function startLogSession(): Promise<string | null> {
|
|
486
|
+
if (isBrowserMode()) return null;
|
|
487
|
+
try {
|
|
488
|
+
const res = await fetch(`${BASE}/log-session`, {
|
|
489
|
+
method: 'POST',
|
|
490
|
+
headers: { 'Content-Type': 'application/json' },
|
|
491
|
+
body: JSON.stringify({ action: 'start' }),
|
|
492
|
+
});
|
|
493
|
+
if (!res.ok) return null;
|
|
494
|
+
const data = (await res.json()) as { ok: boolean; file: string };
|
|
495
|
+
return data.file;
|
|
496
|
+
} catch {
|
|
497
|
+
return null;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/** End the current log session. No-op in browser mode (see `startLogSession`). */
|
|
502
|
+
export async function endLogSession(): Promise<void> {
|
|
503
|
+
if (isBrowserMode()) return;
|
|
504
|
+
try {
|
|
505
|
+
await fetch(`${BASE}/log-session`, {
|
|
506
|
+
method: 'POST',
|
|
507
|
+
headers: { 'Content-Type': 'application/json' },
|
|
508
|
+
body: JSON.stringify({ action: 'end' }),
|
|
509
|
+
});
|
|
510
|
+
} catch {
|
|
511
|
+
/* best-effort */
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/** Flush a batch of log entries to the server. Returns true on success. No-op
|
|
516
|
+
* in browser mode (see `startLogSession`) — critically, this is called on a
|
|
517
|
+
* `setInterval` while Play is running, so without this guard it would hammer
|
|
518
|
+
* a dead endpoint every 200ms for the whole play session (the same
|
|
519
|
+
* retry-loop anti-pattern A2's SSE guards exist to avoid, just via
|
|
520
|
+
* `setInterval`+`fetch` instead of `EventSource`). */
|
|
521
|
+
export async function flushLogEntries(entries: LogEntry[]): Promise<boolean> {
|
|
522
|
+
if (entries.length === 0) return true;
|
|
523
|
+
if (isBrowserMode()) return true;
|
|
524
|
+
try {
|
|
525
|
+
const res = await fetch(`${BASE}/log-entries`, {
|
|
526
|
+
method: 'POST',
|
|
527
|
+
headers: { 'Content-Type': 'application/json' },
|
|
528
|
+
body: JSON.stringify({ entries }),
|
|
529
|
+
});
|
|
530
|
+
return res.ok;
|
|
531
|
+
} catch {
|
|
532
|
+
return false;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// ---------------------------------------------------------------------------
|
|
537
|
+
// Asset Library (online browsing + download)
|
|
538
|
+
//
|
|
539
|
+
// The whole feature is a Node-only proxy to Poly Haven/ambientcg (CORS +
|
|
540
|
+
// SSRF-allowlist reasons to stay server-side) that writes into
|
|
541
|
+
// `<project>/public/asset-library/...` via raw `node:fs`, not
|
|
542
|
+
// `StorageBackend` — there is no browser-mode equivalent. `BottomPanel.tsx`
|
|
543
|
+
// hides the whole "Library" tab in browser mode; the functions below are a
|
|
544
|
+
// defensive backstop for any other caller: read-only lookups degrade to an
|
|
545
|
+
// honest empty result (no fetch attempted), the one mutating call
|
|
546
|
+
// (`downloadOnlineAsset`) throws instead.
|
|
547
|
+
// ---------------------------------------------------------------------------
|
|
548
|
+
|
|
549
|
+
export interface OnlineAsset {
|
|
550
|
+
id: string;
|
|
551
|
+
source: 'polyhaven' | 'ambientcg' | 'local';
|
|
552
|
+
name: string;
|
|
553
|
+
type: 'model' | 'animation' | 'source' | 'hdri' | 'texture' | 'material';
|
|
554
|
+
thumbnailUrl: string;
|
|
555
|
+
categories: string[];
|
|
556
|
+
tags: string[];
|
|
557
|
+
downloadCount?: number;
|
|
558
|
+
license?: string;
|
|
559
|
+
author?: string;
|
|
560
|
+
sourceUrl?: string;
|
|
561
|
+
cloudHosted?: boolean;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
export interface AssetFileOption {
|
|
565
|
+
label: string;
|
|
566
|
+
format: string;
|
|
567
|
+
url: string;
|
|
568
|
+
sizeBytes?: number;
|
|
569
|
+
includes?: { relativePath: string; url: string; size: number }[];
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// ---------------------------------------------------------------------------
|
|
573
|
+
// Downloaded-asset cache (client-side, keyed by "source:slug")
|
|
574
|
+
// ---------------------------------------------------------------------------
|
|
575
|
+
|
|
576
|
+
/** Maps "source:slug" → local serving path (e.g. "/asset-library/polyhaven/arm-chair-01/..."). */
|
|
577
|
+
const downloadedAssetPaths = new Map<string, string>();
|
|
578
|
+
|
|
579
|
+
/** Listeners notified when the download cache changes. */
|
|
580
|
+
const downloadListeners = new Set<() => void>();
|
|
581
|
+
|
|
582
|
+
/** Same slug logic as the server's download endpoint. */
|
|
583
|
+
function toSlug(nameOrId: string): string {
|
|
584
|
+
return nameOrId
|
|
585
|
+
.toLowerCase()
|
|
586
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
587
|
+
.replace(/^-|-$/g, '');
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function assetCacheKey(source: string, nameOrId: string): string {
|
|
591
|
+
return `${source}:${toSlug(nameOrId)}`;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/** Check if an online asset has already been downloaded. Returns local path or null. */
|
|
595
|
+
export function getDownloadedAssetPath(source: string, nameOrId: string): string | null {
|
|
596
|
+
return downloadedAssetPaths.get(assetCacheKey(source, nameOrId)) ?? null;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/** Subscribe to download cache changes. Returns unsubscribe function. */
|
|
600
|
+
export function onAssetDownloaded(cb: () => void): () => void {
|
|
601
|
+
downloadListeners.add(cb);
|
|
602
|
+
return () => downloadListeners.delete(cb);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/** Fetch already-downloaded assets from the server and seed the cache.
|
|
606
|
+
* No-op in browser mode — see the Asset Library section header comment. */
|
|
607
|
+
export async function loadDownloadedAssets(): Promise<void> {
|
|
608
|
+
if (isBrowserMode()) return;
|
|
609
|
+
try {
|
|
610
|
+
const res = await fetch(`${BASE}/asset-library/downloaded`);
|
|
611
|
+
if (!res.ok) return;
|
|
612
|
+
const data = (await res.json()) as {
|
|
613
|
+
downloaded: { source: string; slug: string; path: string }[];
|
|
614
|
+
};
|
|
615
|
+
for (const entry of data.downloaded) {
|
|
616
|
+
downloadedAssetPaths.set(`${entry.source}:${entry.slug}`, `/${entry.path}`);
|
|
617
|
+
}
|
|
618
|
+
for (const cb of downloadListeners) cb();
|
|
619
|
+
} catch {
|
|
620
|
+
/* best-effort */
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/** Search for online assets via the editor server proxy. */
|
|
625
|
+
export async function searchOnlineAssets(params: {
|
|
626
|
+
source: string;
|
|
627
|
+
type: string;
|
|
628
|
+
q?: string;
|
|
629
|
+
category?: string;
|
|
630
|
+
offset?: number;
|
|
631
|
+
limit?: number;
|
|
632
|
+
}): Promise<{ assets: OnlineAsset[]; total: number }> {
|
|
633
|
+
if (isBrowserMode()) return { assets: [], total: 0 };
|
|
634
|
+
const qs = new URLSearchParams();
|
|
635
|
+
qs.set('source', params.source);
|
|
636
|
+
qs.set('type', params.type);
|
|
637
|
+
if (params.q) qs.set('q', params.q);
|
|
638
|
+
if (params.category) qs.set('category', params.category);
|
|
639
|
+
if (params.offset != null) qs.set('offset', String(params.offset));
|
|
640
|
+
if (params.limit != null) qs.set('limit', String(params.limit));
|
|
641
|
+
|
|
642
|
+
const res = await fetch(`${BASE}/asset-library/search?${qs}`);
|
|
643
|
+
if (!res.ok) return { assets: [], total: 0 };
|
|
644
|
+
return (await res.json()) as { assets: OnlineAsset[]; total: number };
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
/** Get available file options (resolutions/formats) for an online asset. */
|
|
648
|
+
export async function getOnlineAssetFiles(source: string, id: string): Promise<AssetFileOption[]> {
|
|
649
|
+
if (isBrowserMode()) return [];
|
|
650
|
+
const qs = new URLSearchParams({ source, id });
|
|
651
|
+
const res = await fetch(`${BASE}/asset-library/files?${qs}`);
|
|
652
|
+
if (!res.ok) return [];
|
|
653
|
+
const data = (await res.json()) as { files: AssetFileOption[] };
|
|
654
|
+
return data.files;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
/** Download an online asset into the project's public/asset-library/ folder. */
|
|
658
|
+
export async function downloadOnlineAsset(params: {
|
|
659
|
+
source: string;
|
|
660
|
+
id: string;
|
|
661
|
+
name: string;
|
|
662
|
+
url: string;
|
|
663
|
+
format: string;
|
|
664
|
+
includes?: { relativePath: string; url: string; size: number }[];
|
|
665
|
+
}): Promise<{ ok: boolean; path?: string; alreadyExists?: boolean; error?: string }> {
|
|
666
|
+
if (isBrowserMode()) {
|
|
667
|
+
throw new Error(
|
|
668
|
+
'Downloading online assets needs the Node dev server (Poly Haven/ambientcg access is proxied ' +
|
|
669
|
+
'server-side). Not available in browser mode.',
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
const res = await fetch(`${BASE}/asset-library/download`, {
|
|
673
|
+
method: 'POST',
|
|
674
|
+
headers: { 'Content-Type': 'application/json' },
|
|
675
|
+
body: JSON.stringify(params),
|
|
676
|
+
});
|
|
677
|
+
const result = (await res.json()) as {
|
|
678
|
+
ok: boolean;
|
|
679
|
+
path?: string;
|
|
680
|
+
alreadyExists?: boolean;
|
|
681
|
+
error?: string;
|
|
682
|
+
};
|
|
683
|
+
// Cache the local path on success
|
|
684
|
+
if (result.ok && result.path) {
|
|
685
|
+
downloadedAssetPaths.set(assetCacheKey(params.source, params.name), `/${result.path}`);
|
|
686
|
+
for (const cb of downloadListeners) cb();
|
|
687
|
+
}
|
|
688
|
+
return result;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
/** Get Poly Haven categories for a given asset type. */
|
|
692
|
+
export async function getAssetLibraryCategories(type: string, source?: string): Promise<string[]> {
|
|
693
|
+
if (isBrowserMode()) return [];
|
|
694
|
+
const params = new URLSearchParams({ type });
|
|
695
|
+
if (source) params.set('source', source);
|
|
696
|
+
const res = await fetch(`${BASE}/asset-library/categories?${params}`);
|
|
697
|
+
if (!res.ok) return [];
|
|
698
|
+
const data = (await res.json()) as { categories: string[] };
|
|
699
|
+
return data.categories;
|
|
700
|
+
}
|