@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,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-browser TypeScript transpile + module resolution.
|
|
3
|
+
*
|
|
4
|
+
* This is the client-side replacement for Vite's on-the-fly transpile of
|
|
5
|
+
* project scripts. It lets the editor hot-reload edited component source
|
|
6
|
+
* **without any server round-trip** — no Node, no Vite dev server, no file
|
|
7
|
+
* write. The flow is:
|
|
8
|
+
*
|
|
9
|
+
* TS source string
|
|
10
|
+
* → esbuild-wasm transform (TS/TSX → ESM JS, in a Web Worker)
|
|
11
|
+
* → rewrite bare imports (`three`, `@engine/...`) to blob-URL shims that
|
|
12
|
+
* re-export the **live, already-running** singletons
|
|
13
|
+
* → Blob → URL.createObjectURL → dynamic import()
|
|
14
|
+
*
|
|
15
|
+
* Sharing singletons is the load-bearing detail: a hot-swapped component must
|
|
16
|
+
* extend the SAME `GameComponent` class and use the SAME `THREE` instance as
|
|
17
|
+
* the running runtime, or `instanceof` checks and prototype chains break. We
|
|
18
|
+
* therefore never pull `three`/engine code from a CDN — we re-export the
|
|
19
|
+
* modules the editor already imported, via `registerRuntimeModules()`.
|
|
20
|
+
*
|
|
21
|
+
* Idiomatic: esbuild-wasm is the same engine Vite uses, just run client-side;
|
|
22
|
+
* we call its documented API directly (no wrapper abstraction).
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import * as esbuild from 'esbuild-wasm';
|
|
26
|
+
// Vite serves the wasm binary as a static asset URL — fully local, no CDN.
|
|
27
|
+
import esbuildWasmUrl from 'esbuild-wasm/esbuild.wasm?url';
|
|
28
|
+
import { GAME_GLOBALS_PRELUDE } from './gated-globals';
|
|
29
|
+
|
|
30
|
+
/** Live runtime modules, keyed by the bare specifier used to import them. */
|
|
31
|
+
type ModuleNamespace = Record<string, unknown>;
|
|
32
|
+
const runtimeModules = new Map<string, ModuleNamespace>();
|
|
33
|
+
|
|
34
|
+
/** Per-specifier shim blob URLs, memoized (the live module identity is stable). */
|
|
35
|
+
const shimUrlCache = new Map<string, string>();
|
|
36
|
+
|
|
37
|
+
let initPromise: Promise<void> | null = null;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Initialize esbuild-wasm exactly once. Safe to call repeatedly — concurrent
|
|
41
|
+
* callers share the same promise.
|
|
42
|
+
*/
|
|
43
|
+
export function initBrowserTranspile(): Promise<void> {
|
|
44
|
+
if (!initPromise) {
|
|
45
|
+
initPromise = esbuild.initialize({ wasmURL: esbuildWasmUrl }).catch((err) => {
|
|
46
|
+
// Allow a retry on failure rather than caching a rejected promise.
|
|
47
|
+
initPromise = null;
|
|
48
|
+
throw err;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
return initPromise;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Register the live runtime module namespaces that transpiled user code is
|
|
56
|
+
* allowed to import. Call once at editor/runtime init with the modules the
|
|
57
|
+
* host already imported statically (so identities are shared).
|
|
58
|
+
*
|
|
59
|
+
* registerRuntimeModules({
|
|
60
|
+
* three: THREE,
|
|
61
|
+
* '@engine/ecs/game-component': gameComponentModule,
|
|
62
|
+
* });
|
|
63
|
+
*/
|
|
64
|
+
export function registerRuntimeModules(modules: Record<string, ModuleNamespace>): void {
|
|
65
|
+
for (const [spec, ns] of Object.entries(modules)) {
|
|
66
|
+
runtimeModules.set(spec, ns);
|
|
67
|
+
shimUrlCache.delete(spec); // invalidate any stale shim
|
|
68
|
+
}
|
|
69
|
+
// Exposed on globalThis so shim modules (which run in their own module scope)
|
|
70
|
+
// can reach the live namespaces by specifier.
|
|
71
|
+
(globalThis as Record<string, unknown>)['__vgaiModules'] = Object.fromEntries(runtimeModules);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const IDENT_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
75
|
+
|
|
76
|
+
// Reserved words can't be `export const <name>` binding identifiers. Some real
|
|
77
|
+
// modules expose them as convenience named exports (e.g. zod exports `catch`,
|
|
78
|
+
// `enum`, `function`, `void`) — re-exporting those verbatim would make the shim
|
|
79
|
+
// itself a SyntaxError ("Unexpected token 'catch'"). They're never imported by
|
|
80
|
+
// those names, so we skip them.
|
|
81
|
+
const RESERVED_WORDS = new Set([
|
|
82
|
+
'break',
|
|
83
|
+
'case',
|
|
84
|
+
'catch',
|
|
85
|
+
'class',
|
|
86
|
+
'const',
|
|
87
|
+
'continue',
|
|
88
|
+
'debugger',
|
|
89
|
+
'default',
|
|
90
|
+
'delete',
|
|
91
|
+
'do',
|
|
92
|
+
'else',
|
|
93
|
+
'enum',
|
|
94
|
+
'export',
|
|
95
|
+
'extends',
|
|
96
|
+
'false',
|
|
97
|
+
'finally',
|
|
98
|
+
'for',
|
|
99
|
+
'function',
|
|
100
|
+
'if',
|
|
101
|
+
'import',
|
|
102
|
+
'in',
|
|
103
|
+
'instanceof',
|
|
104
|
+
'new',
|
|
105
|
+
'null',
|
|
106
|
+
'return',
|
|
107
|
+
'super',
|
|
108
|
+
'switch',
|
|
109
|
+
'this',
|
|
110
|
+
'throw',
|
|
111
|
+
'true',
|
|
112
|
+
'try',
|
|
113
|
+
'typeof',
|
|
114
|
+
'var',
|
|
115
|
+
'void',
|
|
116
|
+
'while',
|
|
117
|
+
'with',
|
|
118
|
+
'yield',
|
|
119
|
+
'let',
|
|
120
|
+
'static',
|
|
121
|
+
'await',
|
|
122
|
+
'implements',
|
|
123
|
+
'interface',
|
|
124
|
+
'package',
|
|
125
|
+
'private',
|
|
126
|
+
'protected',
|
|
127
|
+
'public',
|
|
128
|
+
]);
|
|
129
|
+
|
|
130
|
+
/** Build (or reuse) a blob-URL ESM module that re-exports a live namespace. */
|
|
131
|
+
function shimUrlFor(spec: string): string {
|
|
132
|
+
const cached = shimUrlCache.get(spec);
|
|
133
|
+
if (cached) return cached;
|
|
134
|
+
|
|
135
|
+
const ns = runtimeModules.get(spec);
|
|
136
|
+
if (!ns) {
|
|
137
|
+
throw new Error(
|
|
138
|
+
`Cannot resolve import '${spec}' in browser HMR — not a registered runtime module. ` +
|
|
139
|
+
`Register it via registerRuntimeModules({ '${spec}': <module> }).`,
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const lines = [`const __m = globalThis.__vgaiModules[${JSON.stringify(spec)}];`];
|
|
144
|
+
for (const key of Object.keys(ns)) {
|
|
145
|
+
if (key === 'default' || !IDENT_RE.test(key) || RESERVED_WORDS.has(key)) continue;
|
|
146
|
+
lines.push(`export const ${key} = __m[${JSON.stringify(key)}];`);
|
|
147
|
+
}
|
|
148
|
+
if ('default' in ns) lines.push(`export default __m[${JSON.stringify('default')}];`);
|
|
149
|
+
|
|
150
|
+
const url = URL.createObjectURL(new Blob([lines.join('\n')], { type: 'text/javascript' }));
|
|
151
|
+
shimUrlCache.set(spec, url);
|
|
152
|
+
return url;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Matches `from '...'` / `from "..."` and bare `import '...'`, but NOT dynamic
|
|
156
|
+
// `import('...')` (a `(` follows `import`, not a quote) — those stay untouched.
|
|
157
|
+
const IMPORT_RE = /(\bfrom\s*|\bimport\s+)(['"])([^'"]+)\2/g;
|
|
158
|
+
|
|
159
|
+
/** True for specifiers that are already directly importable in the browser. */
|
|
160
|
+
function isResolvable(spec: string): boolean {
|
|
161
|
+
return (
|
|
162
|
+
spec.startsWith('.') ||
|
|
163
|
+
spec.startsWith('/') ||
|
|
164
|
+
spec.startsWith('blob:') ||
|
|
165
|
+
spec.startsWith('http:') ||
|
|
166
|
+
spec.startsWith('https:')
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Rewrite bare imports in transpiled JS to blob-URL shims over live modules. */
|
|
171
|
+
function rewriteImports(code: string): string {
|
|
172
|
+
return code.replace(IMPORT_RE, (match, lead: string, quote: string, spec: string) => {
|
|
173
|
+
if (isResolvable(spec)) return match;
|
|
174
|
+
return `${lead}${quote}${shimUrlFor(spec)}${quote}`;
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Transpile TS/TSX source to an importable ESM module URL (blob:). Bare imports
|
|
180
|
+
* are rewritten to live-singleton shims.
|
|
181
|
+
*
|
|
182
|
+
* Low-level primitive: it does NOT apply the game-globals shadow. Project /
|
|
183
|
+
* game code must go through {@link importFromSource} (which wraps this) so its
|
|
184
|
+
* `window`/`document` input listeners stay focus-gated; only shadow-exempt
|
|
185
|
+
* editor-internal source should call this directly.
|
|
186
|
+
*/
|
|
187
|
+
export async function transpileToModuleUrl(
|
|
188
|
+
source: string,
|
|
189
|
+
filename = 'in-browser-edit.tsx',
|
|
190
|
+
): Promise<string> {
|
|
191
|
+
await initBrowserTranspile();
|
|
192
|
+
const { code } = await esbuild.transform(source, {
|
|
193
|
+
loader: filename.endsWith('.tsx') ? 'tsx' : 'ts',
|
|
194
|
+
format: 'esm',
|
|
195
|
+
target: 'es2022',
|
|
196
|
+
sourcefile: filename,
|
|
197
|
+
// Match the editor's tsconfig: decorators + classic field semantics.
|
|
198
|
+
tsconfigRaw: {
|
|
199
|
+
compilerOptions: { experimentalDecorators: true, useDefineForClassFields: false },
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
const rewritten = rewriteImports(code);
|
|
203
|
+
return URL.createObjectURL(new Blob([rewritten], { type: 'text/javascript' }));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Prepend the game-globals lexical shadow to project source — the browser-side
|
|
208
|
+
* analogue of the dev server's `vgai-game-globals` Vite plugin
|
|
209
|
+
* (`server/dev.ts` + `shouldShadowGameGlobals`). The server shadows per-module
|
|
210
|
+
* because Vite transforms EVERY module indiscriminately and must be told which
|
|
211
|
+
* ones are game code; the browser has no such firehose — {@link importFromSource}
|
|
212
|
+
* is the single client-transpile chokepoint and only ever receives project
|
|
213
|
+
* code, so "scoping" here is by construction (route project code through this
|
|
214
|
+
* seam; never through the raw {@link transpileToModuleUrl}). Applying the shadow
|
|
215
|
+
* to EVERY module that reaches this seam is what keeps game `window`/`document`
|
|
216
|
+
* input focus-gated on the whole browser path — the bundled project scripts
|
|
217
|
+
* (`browser-project-scripts.ts`) AND every single-file hot-swap alike, not just
|
|
218
|
+
* hot-swaps. Kept as its own named, exported function so that invariant is
|
|
219
|
+
* unit-testable and can't be silently dropped (`browser-globals-shadow.test.ts`).
|
|
220
|
+
*/
|
|
221
|
+
export function applyGameGlobalsShadow(source: string): string {
|
|
222
|
+
return GAME_GLOBALS_PRELUDE + source;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Transpile TS/TSX source in-browser and dynamically import it, returning the
|
|
227
|
+
* module namespace. The blob URL is revoked once the import settles.
|
|
228
|
+
*
|
|
229
|
+
* This is the ONLY path project code should take: it applies the game-globals
|
|
230
|
+
* shadow ({@link applyGameGlobalsShadow}) so a component's `window`/`document`
|
|
231
|
+
* resolve to the editor's gated proxies (input stays focus-gated on the HMR
|
|
232
|
+
* path too). The raw {@link transpileToModuleUrl} deliberately does NOT shadow —
|
|
233
|
+
* it is the low-level primitive; feeding project code to it directly would leak
|
|
234
|
+
* ungated input listeners.
|
|
235
|
+
*/
|
|
236
|
+
export async function importFromSource(
|
|
237
|
+
source: string,
|
|
238
|
+
filename = 'in-browser-edit.tsx',
|
|
239
|
+
): Promise<Record<string, unknown>> {
|
|
240
|
+
const url = await transpileToModuleUrl(applyGameGlobalsShadow(source), filename);
|
|
241
|
+
try {
|
|
242
|
+
return (await import(/* @vite-ignore */ url)) as Record<string, unknown>;
|
|
243
|
+
} finally {
|
|
244
|
+
URL.revokeObjectURL(url);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-mode live-reload (Phase A2).
|
|
3
|
+
*
|
|
4
|
+
* A server-backed editor learns about file changes two ways: chokidar → SSE
|
|
5
|
+
* (`scene-changed`, consumed by `connectSceneEvents`) and the Vite dev server's
|
|
6
|
+
* `vgai:script-update` HMR event (consumed in `play-mode.ts`). A server-less
|
|
7
|
+
* browser editor has neither. What it DOES have is `StorageBackend.watch()`:
|
|
8
|
+
* because the browser backend (OPFS / File System Access) is the sole writer,
|
|
9
|
+
* its self-derived write events ARE the change signal — exactly what HMR needs.
|
|
10
|
+
*
|
|
11
|
+
* This module is the browser counterpart of those two consumers. `asset-events`
|
|
12
|
+
* and `play-mode` keep the server (SSE / `import.meta.hot`) paths and route
|
|
13
|
+
* through here only when `getEditorMode() === 'browser'`.
|
|
14
|
+
*
|
|
15
|
+
* A `WatchEvent` carries only `{ type, path }` — no bytes — so each helper reads
|
|
16
|
+
* the file back from the backend before dispatching:
|
|
17
|
+
* - Scene changes: today this is the editor's own autosave echo, suppressed
|
|
18
|
+
* downstream by the store's `_lastSavedContent` guard (a self-write reads
|
|
19
|
+
* back byte-identical). It becomes a genuine external-change signal once A4
|
|
20
|
+
* adds File System Access out-of-band change detection — this wiring needs
|
|
21
|
+
* no further change for that.
|
|
22
|
+
* - Component sources: fires whenever anything writes a project `.ts`/`.tsx`
|
|
23
|
+
* through the backend (a future code panel, an agent apply-diff, the dev
|
|
24
|
+
* `storage-hmr-demo`), driving the same in-browser hot-swap the server's
|
|
25
|
+
* `vgai:script-update` handler performs.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import type { StorageBackend, WatchEvent } from './storage';
|
|
29
|
+
import { getStorageBackend } from './storage';
|
|
30
|
+
|
|
31
|
+
/** A scene document the editor can reload (`.vscn.json`). */
|
|
32
|
+
export function isSceneFile(path: string): boolean {
|
|
33
|
+
return path.endsWith('.vscn.json');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* A project source file that hot-swaps in place (a GameComponent module).
|
|
38
|
+
*
|
|
39
|
+
* `.ts`/`.tsx` only, excluding the entry (`main.ts`) and component registry
|
|
40
|
+
* (`registry.ts`) — editing those needs a warm restart (server mode) or a
|
|
41
|
+
* client-side re-bundle (browser mode, not yet available), never a live
|
|
42
|
+
* prototype swap. Mirrors the server `vgai:script-update` handler's split.
|
|
43
|
+
*/
|
|
44
|
+
export function isHotSwapScriptFile(path: string): boolean {
|
|
45
|
+
if (!path.endsWith('.ts') && !path.endsWith('.tsx')) return false;
|
|
46
|
+
const base = path.slice(path.lastIndexOf('/') + 1);
|
|
47
|
+
return base !== 'main.ts' && base !== 'registry.ts';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** create/update carry new content; remove does not (nothing to reload). */
|
|
51
|
+
function isWriteEvent(ev: WatchEvent): boolean {
|
|
52
|
+
return ev.type === 'create' || ev.type === 'update';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Subscribe to backend writes matching `match`, read the new content, and hand
|
|
57
|
+
* it to `deliver`. Returns an unsubscribe function. Reads that fail (a file
|
|
58
|
+
* removed between the event and the read) are ignored.
|
|
59
|
+
*/
|
|
60
|
+
function watchFileWrites(
|
|
61
|
+
backend: StorageBackend,
|
|
62
|
+
match: (path: string) => boolean,
|
|
63
|
+
deliver: (path: string, content: string) => void,
|
|
64
|
+
): () => void {
|
|
65
|
+
return backend.watch((ev) => {
|
|
66
|
+
if (!isWriteEvent(ev) || !match(ev.path)) return;
|
|
67
|
+
void backend
|
|
68
|
+
.read(ev.path)
|
|
69
|
+
.then((content) => deliver(ev.path, content))
|
|
70
|
+
.catch(() => {
|
|
71
|
+
/* file vanished between the write event and the read — ignore */
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Browser analogue of `connectSceneEvents`: fire `onChange` with the reloaded
|
|
78
|
+
* `.vscn.json` content whenever the active scene file is written through the
|
|
79
|
+
* backend. Returns an unsubscribe function.
|
|
80
|
+
*/
|
|
81
|
+
export function watchSceneChanges(
|
|
82
|
+
onChange: (event: { path: string; content: string }) => void,
|
|
83
|
+
backend: StorageBackend = getStorageBackend(),
|
|
84
|
+
): () => void {
|
|
85
|
+
return watchFileWrites(backend, isSceneFile, (path, content) => onChange({ path, content }));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Browser analogue of the `vgai:script-update` HMR handler: fire `onChange` with
|
|
90
|
+
* a hot-swappable component source whenever one is written through the backend.
|
|
91
|
+
* Returns an unsubscribe function.
|
|
92
|
+
*/
|
|
93
|
+
export function watchScriptChanges(
|
|
94
|
+
onChange: (event: { path: string; source: string }) => void,
|
|
95
|
+
backend: StorageBackend = getStorageBackend(),
|
|
96
|
+
): () => void {
|
|
97
|
+
return watchFileWrites(backend, isHotSwapScriptFile, (path, source) =>
|
|
98
|
+
onChange({ path, source }),
|
|
99
|
+
);
|
|
100
|
+
}
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command listener — receives commands from the editor server via SSE
|
|
3
|
+
* and dispatches them to the EditorStore and play-mode functions.
|
|
4
|
+
*
|
|
5
|
+
* The server broadcasts `editor-command` events sent by the SDK/CLI.
|
|
6
|
+
* This module translates them into store method calls.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { parseSceneFile, SceneParseError } from '@engine/scene/parse';
|
|
10
|
+
import { connectEvents, reportCommandResult, reportEditorState } from './editor-api';
|
|
11
|
+
import { editorConsole } from './editor-console';
|
|
12
|
+
import { getEditorMode } from './editor-mode';
|
|
13
|
+
import type { AssetKind, EditorStore, HelperVisibility } from './editor-store';
|
|
14
|
+
import {
|
|
15
|
+
enterPlayMode,
|
|
16
|
+
exitPlayMode,
|
|
17
|
+
pausePlayMode,
|
|
18
|
+
resumePlayMode,
|
|
19
|
+
stepPlayMode,
|
|
20
|
+
} from './play-mode';
|
|
21
|
+
|
|
22
|
+
export interface EditorCommand {
|
|
23
|
+
type: string;
|
|
24
|
+
_requestId?: string;
|
|
25
|
+
[key: string]: unknown;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface CommandResult {
|
|
29
|
+
ok: boolean;
|
|
30
|
+
error?: string;
|
|
31
|
+
/** Optional payload for commands that answer with data (e.g. `capture-viewport`'s base64 PNG). */
|
|
32
|
+
data?: Record<string, unknown>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Live viewport camera pose — undefined when nothing has bound a camera yet
|
|
36
|
+
* (see `EditorStore.cameraPose`'s doc comment). */
|
|
37
|
+
function collectCamera(store: EditorStore):
|
|
38
|
+
| {
|
|
39
|
+
position: { x: number; y: number; z: number };
|
|
40
|
+
target: { x: number; y: number; z: number };
|
|
41
|
+
fov: number;
|
|
42
|
+
}
|
|
43
|
+
| undefined {
|
|
44
|
+
const pose = store.cameraPose;
|
|
45
|
+
if (!pose) return undefined;
|
|
46
|
+
return {
|
|
47
|
+
position: { x: pose.position.x, y: pose.position.y, z: pose.position.z },
|
|
48
|
+
target: { x: pose.target.x, y: pose.target.y, z: pose.target.z },
|
|
49
|
+
fov: pose.fov,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function collectState(store: EditorStore): Record<string, unknown> {
|
|
54
|
+
return {
|
|
55
|
+
playState: store.playState,
|
|
56
|
+
selectedEntityId: store.selectedEntityId,
|
|
57
|
+
selectedEntityIds: [...store.selectedEntityIds],
|
|
58
|
+
activeViewportTab: store.activeViewportTab,
|
|
59
|
+
activeTabKey: store.activeTabKey,
|
|
60
|
+
showGrid: store.showGrid,
|
|
61
|
+
showHelpers: store.showHelpers,
|
|
62
|
+
showStats: store.showStats,
|
|
63
|
+
shadingMode: store.shadingMode,
|
|
64
|
+
helperVisibility: { ...store.helperVisibility },
|
|
65
|
+
transformMode: store.transformMode,
|
|
66
|
+
transformSpace: store.transformSpace,
|
|
67
|
+
snapEnabled: store.snapEnabled,
|
|
68
|
+
entityCount: store.allEntities().length,
|
|
69
|
+
savePath: store.savePath,
|
|
70
|
+
// Camera facet (B3-followup): real pose when a viewport is bound, absent
|
|
71
|
+
// otherwise — never a fabricated value (see EditorStore.cameraPose).
|
|
72
|
+
camera: collectCamera(store),
|
|
73
|
+
// Hierarchy facet (B3-followup): the real entity tree, flattened to
|
|
74
|
+
// id/name/childIds rows (same shape editor.hierarchy.inspect declares).
|
|
75
|
+
// `hierarchyRows()` lives on EditorStore (inside the SceneEntity
|
|
76
|
+
// allowlist — see authoring-inversion-guard.test.ts) so this file never
|
|
77
|
+
// needs to import SceneEntity itself.
|
|
78
|
+
entities: store.hierarchyRows(),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Validate the current scene document against the Zod schema. */
|
|
83
|
+
function validateCurrentScene(store: EditorStore): CommandResult {
|
|
84
|
+
const doc = store.getDocument();
|
|
85
|
+
try {
|
|
86
|
+
parseSceneFile(doc);
|
|
87
|
+
return { ok: true };
|
|
88
|
+
} catch (err) {
|
|
89
|
+
if (err instanceof SceneParseError) {
|
|
90
|
+
return { ok: false, error: err.message };
|
|
91
|
+
}
|
|
92
|
+
return { ok: false, error: `Scene validation failed: ${err}` };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Exported (alongside `collectState` above) so unit tests can dispatch
|
|
98
|
+
* commands directly against a headless `EditorStore`, with no SSE/server
|
|
99
|
+
* round-trip — see `packages/editor/test/command-listener.test.ts`, notably
|
|
100
|
+
* the false-ack regression test: an unrecognized command must return
|
|
101
|
+
* `{ok:false}`, never fall through to a fabricated `{ok:true}`.
|
|
102
|
+
*/
|
|
103
|
+
export async function handleCommand(
|
|
104
|
+
store: EditorStore,
|
|
105
|
+
cmd: EditorCommand,
|
|
106
|
+
): Promise<CommandResult> {
|
|
107
|
+
switch (cmd['type']) {
|
|
108
|
+
// Play control
|
|
109
|
+
case 'play': {
|
|
110
|
+
// Validate before play — enterPlayMode() also validates, but we need
|
|
111
|
+
// the error message to flow back to the SDK/CLI.
|
|
112
|
+
const validation = validateCurrentScene(store);
|
|
113
|
+
if (!validation.ok) {
|
|
114
|
+
return validation;
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
await enterPlayMode();
|
|
118
|
+
} catch (err) {
|
|
119
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
120
|
+
}
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
case 'stop':
|
|
124
|
+
exitPlayMode();
|
|
125
|
+
break;
|
|
126
|
+
case 'pause':
|
|
127
|
+
pausePlayMode();
|
|
128
|
+
break;
|
|
129
|
+
case 'resume':
|
|
130
|
+
resumePlayMode();
|
|
131
|
+
break;
|
|
132
|
+
case 'step':
|
|
133
|
+
stepPlayMode();
|
|
134
|
+
break;
|
|
135
|
+
|
|
136
|
+
// Selection
|
|
137
|
+
case 'select':
|
|
138
|
+
store.select((cmd['id'] as string | null) ?? null);
|
|
139
|
+
break;
|
|
140
|
+
case 'select-multiple':
|
|
141
|
+
store.selectMultiple(cmd['ids'] as string[]);
|
|
142
|
+
break;
|
|
143
|
+
case 'select-all':
|
|
144
|
+
store.selectAll();
|
|
145
|
+
break;
|
|
146
|
+
|
|
147
|
+
// Viewport
|
|
148
|
+
case 'focus-entity':
|
|
149
|
+
store.focusOnEntity(cmd['id'] as string);
|
|
150
|
+
break;
|
|
151
|
+
case 'focus-selection':
|
|
152
|
+
store.focusOnSelection();
|
|
153
|
+
break;
|
|
154
|
+
case 'view-preset':
|
|
155
|
+
store.setViewPreset(cmd['preset'] as 'top' | 'front' | 'right' | 'perspective');
|
|
156
|
+
break;
|
|
157
|
+
case 'set-camera': {
|
|
158
|
+
const position = cmd['position'] as { x: number; y: number; z: number } | undefined;
|
|
159
|
+
const target = cmd['target'] as { x: number; y: number; z: number } | undefined;
|
|
160
|
+
const fov = cmd['fov'] as number | undefined;
|
|
161
|
+
if (
|
|
162
|
+
!position ||
|
|
163
|
+
!target ||
|
|
164
|
+
typeof position.x !== 'number' ||
|
|
165
|
+
typeof position.y !== 'number' ||
|
|
166
|
+
typeof position.z !== 'number' ||
|
|
167
|
+
typeof target.x !== 'number' ||
|
|
168
|
+
typeof target.y !== 'number' ||
|
|
169
|
+
typeof target.z !== 'number'
|
|
170
|
+
) {
|
|
171
|
+
return { ok: false, error: 'set-camera requires numeric {x,y,z} position and target.' };
|
|
172
|
+
}
|
|
173
|
+
store.setCameraPose(position, target, fov);
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Panels
|
|
178
|
+
case 'viewport-tab':
|
|
179
|
+
store.setActiveViewportTab(cmd['tab'] as 'scene' | 'game');
|
|
180
|
+
break;
|
|
181
|
+
case 'active-tab':
|
|
182
|
+
store.setActiveTab(cmd['key'] as string);
|
|
183
|
+
break;
|
|
184
|
+
case 'open-asset-tab':
|
|
185
|
+
store.openAssetTab(cmd['path'] as string, cmd['kind'] as AssetKind);
|
|
186
|
+
break;
|
|
187
|
+
case 'close-asset-tab':
|
|
188
|
+
store.closeAssetTab(cmd['key'] as string);
|
|
189
|
+
break;
|
|
190
|
+
case 'toggle-command-palette':
|
|
191
|
+
store.toggleCommandPalette();
|
|
192
|
+
break;
|
|
193
|
+
case 'toggle-console':
|
|
194
|
+
window.dispatchEvent(new CustomEvent('editor:toggle-console'));
|
|
195
|
+
break;
|
|
196
|
+
case 'show-build':
|
|
197
|
+
window.dispatchEvent(new CustomEvent('editor:show-build-tab'));
|
|
198
|
+
break;
|
|
199
|
+
case 'capture-viewport': {
|
|
200
|
+
// Fresh, unthrottled on-demand capture (see EditorStore.captureViewportImage's
|
|
201
|
+
// doc comment) — distinct from the periodic autosave thumbnail the
|
|
202
|
+
// project-thumbnail snapshot path serves. `size` is optional; defaults
|
|
203
|
+
// to the store's standard thumbnail size.
|
|
204
|
+
const size = cmd['size'] as number | undefined;
|
|
205
|
+
const dataUrl = store.captureViewportImage(size);
|
|
206
|
+
if (!dataUrl) {
|
|
207
|
+
return {
|
|
208
|
+
ok: false,
|
|
209
|
+
error: 'Viewport is not bound yet (no renderer/scene/camera) — cannot capture.',
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
const comma = dataUrl.indexOf(',');
|
|
213
|
+
const base64 = comma >= 0 ? dataUrl.slice(comma + 1) : dataUrl;
|
|
214
|
+
return { ok: true, data: { base64, mimeType: 'image/png' } };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Scene loading
|
|
218
|
+
case 'open-scene': {
|
|
219
|
+
const scenePath = cmd['path'] as string;
|
|
220
|
+
const url = scenePath.startsWith('/') ? scenePath : `/${scenePath}`;
|
|
221
|
+
const res = await fetch(url);
|
|
222
|
+
if (!res.ok) {
|
|
223
|
+
return { ok: false, error: `Failed to fetch scene: ${res.status} ${res.statusText}` };
|
|
224
|
+
}
|
|
225
|
+
try {
|
|
226
|
+
const doc = parseSceneFile(await res.json());
|
|
227
|
+
const savePath = scenePath.replace(/^\//, '');
|
|
228
|
+
store.loadDocument(doc, savePath);
|
|
229
|
+
} catch (err) {
|
|
230
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
231
|
+
editorConsole.error(`Failed to open scene: ${msg}`, 'scene');
|
|
232
|
+
return { ok: false, error: msg };
|
|
233
|
+
}
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Display — set semantics (only toggle when value differs)
|
|
238
|
+
case 'set-grid':
|
|
239
|
+
if (store.showGrid !== (cmd['enabled'] as boolean)) store.toggleGrid();
|
|
240
|
+
break;
|
|
241
|
+
case 'set-helpers':
|
|
242
|
+
if (store.showHelpers !== (cmd['enabled'] as boolean)) store.toggleHelpers();
|
|
243
|
+
break;
|
|
244
|
+
case 'set-stats':
|
|
245
|
+
if (store.showStats !== (cmd['enabled'] as boolean)) store.toggleStats();
|
|
246
|
+
break;
|
|
247
|
+
case 'set-shading-mode':
|
|
248
|
+
store.setShadingMode(cmd['mode'] as 'solid' | 'wireframe' | 'unlit');
|
|
249
|
+
break;
|
|
250
|
+
case 'set-helper-type': {
|
|
251
|
+
const helperType = cmd['helperType'] as keyof HelperVisibility;
|
|
252
|
+
if (store.helperVisibility[helperType] !== (cmd['enabled'] as boolean)) {
|
|
253
|
+
store.toggleHelperType(helperType);
|
|
254
|
+
}
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Transform tools — set semantics
|
|
259
|
+
case 'set-transform-mode':
|
|
260
|
+
store.setTransformMode(cmd['mode'] as 'translate' | 'rotate' | 'scale');
|
|
261
|
+
break;
|
|
262
|
+
case 'set-transform-space':
|
|
263
|
+
store.setTransformSpace(cmd['space'] as 'world' | 'local');
|
|
264
|
+
break;
|
|
265
|
+
case 'set-snap':
|
|
266
|
+
if (store.snapEnabled !== (cmd['enabled'] as boolean)) store.toggleSnap();
|
|
267
|
+
break;
|
|
268
|
+
|
|
269
|
+
default:
|
|
270
|
+
// Every unrecognized command type used to fall through to `return { ok:
|
|
271
|
+
// true }` below — a silent, false ack: the browser did NOTHING, but the
|
|
272
|
+
// SDK/CLI caller was told it succeeded. Every "honest gap" op in
|
|
273
|
+
// packages/vgai-sdk/src/editor/*-operations.ts (camera.get,
|
|
274
|
+
// hierarchy.inspect pre-wiring, story.open) exists specifically to avoid
|
|
275
|
+
// ever sending a command that would land here. Reporting a structured
|
|
276
|
+
// failure — never a fabricated success — is the fix, and it must hold for
|
|
277
|
+
// ANY unrecognized `type`, not just the ones known about today.
|
|
278
|
+
return { ok: false, error: `Unknown editor command: ${String(cmd['type'])}` };
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return { ok: true };
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Connect the command listener to the editor server's SSE stream.
|
|
286
|
+
* Dispatches incoming commands to the store and play-mode functions.
|
|
287
|
+
* Reports state after each command and on initial connect.
|
|
288
|
+
*/
|
|
289
|
+
export function connectCommandListener(store: EditorStore): () => void {
|
|
290
|
+
// Browser mode (Phase A2): the `vgai` CLI control channel is server-only (it
|
|
291
|
+
// is an SSE command stream + state POSTs to `/__editor/*`, which do not exist
|
|
292
|
+
// without a Node server). Skip it — otherwise the EventSource retry-loops
|
|
293
|
+
// against a 404 and every `reportEditorState` POSTs into the void.
|
|
294
|
+
if (getEditorMode() === 'browser') {
|
|
295
|
+
void store;
|
|
296
|
+
return () => {};
|
|
297
|
+
}
|
|
298
|
+
const source = connectEvents();
|
|
299
|
+
|
|
300
|
+
// Report initial state
|
|
301
|
+
reportEditorState(collectState(store));
|
|
302
|
+
|
|
303
|
+
source.addEventListener('editor-command', (e: MessageEvent) => {
|
|
304
|
+
try {
|
|
305
|
+
const cmd = JSON.parse(e.data as string) as EditorCommand;
|
|
306
|
+
const requestId = cmd._requestId;
|
|
307
|
+
handleCommand(store, cmd).then((result) => {
|
|
308
|
+
// Report command result back to server (so SDK/CLI gets the response)
|
|
309
|
+
if (requestId) {
|
|
310
|
+
reportCommandResult(requestId, result.ok, result.error, result.data);
|
|
311
|
+
}
|
|
312
|
+
// Report updated state after command execution
|
|
313
|
+
reportEditorState(collectState(store));
|
|
314
|
+
});
|
|
315
|
+
} catch {
|
|
316
|
+
/* ignore malformed events */
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
// When the server switches projects, reload to pick up the new project's files
|
|
321
|
+
source.addEventListener('project-changed', () => {
|
|
322
|
+
window.location.reload();
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
return () => source.close();
|
|
326
|
+
}
|