@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,221 @@
|
|
|
1
|
+
import type { SceneEntity } from '@engine/scene/scene-types';
|
|
2
|
+
import * as THREE from 'three';
|
|
3
|
+
import { EDITOR_LAYER } from './editor-layers';
|
|
4
|
+
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Handle definition
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
export interface HandleDef {
|
|
10
|
+
/** Unique handle ID within the entity, e.g. 'light-distance' */
|
|
11
|
+
id: string;
|
|
12
|
+
/** The entity ID this handle belongs to */
|
|
13
|
+
entityId: string;
|
|
14
|
+
/** Compute the handle's world position from the current entity state */
|
|
15
|
+
getWorldPosition: (entityObj: THREE.Object3D, entity: SceneEntity) => THREE.Vector3;
|
|
16
|
+
/** Called during drag — mutates the entity descriptor directly. */
|
|
17
|
+
onDrag: (newWorldPos: THREE.Vector3, entityObj: THREE.Object3D, entity: SceneEntity) => void;
|
|
18
|
+
/** Handle color (default yellow) */
|
|
19
|
+
color?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface HandleMesh extends THREE.Mesh {
|
|
23
|
+
userData: {
|
|
24
|
+
editorHandle: true;
|
|
25
|
+
handleDef: HandleDef;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const HANDLE_COLOR = 0xffaa00;
|
|
30
|
+
const HANDLE_HOVER_COLOR = 0xffffff;
|
|
31
|
+
|
|
32
|
+
const handleGeo = new THREE.SphereGeometry(1, 10, 10);
|
|
33
|
+
|
|
34
|
+
/** Create a handle mesh for a given definition. */
|
|
35
|
+
export function createHandleMesh(def: HandleDef): HandleMesh {
|
|
36
|
+
const mat = new THREE.MeshBasicMaterial({
|
|
37
|
+
color: def.color ?? HANDLE_COLOR,
|
|
38
|
+
depthTest: false,
|
|
39
|
+
transparent: true,
|
|
40
|
+
opacity: 0.85,
|
|
41
|
+
});
|
|
42
|
+
const mesh = new THREE.Mesh(handleGeo, mat) as unknown as HandleMesh;
|
|
43
|
+
mesh.renderOrder = 998;
|
|
44
|
+
mesh.layers.set(EDITOR_LAYER);
|
|
45
|
+
mesh.userData = { editorHandle: true, handleDef: def };
|
|
46
|
+
return mesh;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Set a handle mesh to its hovered state. */
|
|
50
|
+
export function setHandleHovered(mesh: HandleMesh, hovered: boolean): void {
|
|
51
|
+
const mat = mesh.material as THREE.MeshBasicMaterial;
|
|
52
|
+
mat.color.setHex(hovered ? HANDLE_HOVER_COLOR : (mesh.userData.handleDef.color ?? HANDLE_COLOR));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Scale a handle to maintain constant screen size. */
|
|
56
|
+
export function scaleHandle(mesh: THREE.Mesh, camera: THREE.Camera): void {
|
|
57
|
+
const dist = mesh.position.distanceTo(camera.position);
|
|
58
|
+
const s = dist * 0.012;
|
|
59
|
+
mesh.scale.setScalar(s);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
// Handle definitions per entity type
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
|
|
66
|
+
/** Create handle definitions for an entity. Returns empty array if no handles apply. */
|
|
67
|
+
export function createHandleDefs(entity: SceneEntity): HandleDef[] {
|
|
68
|
+
const defs: HandleDef[] = [];
|
|
69
|
+
const eid = entity.id!;
|
|
70
|
+
|
|
71
|
+
// -- Point light distance --
|
|
72
|
+
if (entity.light?.type === 'point' && entity.light.distance) {
|
|
73
|
+
defs.push({
|
|
74
|
+
id: 'light-distance',
|
|
75
|
+
entityId: eid,
|
|
76
|
+
getWorldPosition: (obj) => {
|
|
77
|
+
const local = new THREE.Vector3(entity.light!.distance ?? 5, 0, 0);
|
|
78
|
+
return local.applyMatrix4(obj.matrixWorld);
|
|
79
|
+
},
|
|
80
|
+
onDrag: (newWorld, obj, ent) => {
|
|
81
|
+
const inv = obj.matrixWorld.clone().invert();
|
|
82
|
+
const local = newWorld.clone().applyMatrix4(inv);
|
|
83
|
+
const dist = Math.max(0.1, local.length());
|
|
84
|
+
ent.light!.distance = Math.round(dist * 100) / 100;
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// -- Spot light distance + angle --
|
|
90
|
+
if (entity.light?.type === 'spot') {
|
|
91
|
+
const dist = entity.light.distance ?? 10;
|
|
92
|
+
if (dist > 0) {
|
|
93
|
+
defs.push({
|
|
94
|
+
id: 'spot-distance',
|
|
95
|
+
entityId: eid,
|
|
96
|
+
getWorldPosition: (obj) => {
|
|
97
|
+
const local = new THREE.Vector3(0, 0, -(entity.light!.distance ?? 10));
|
|
98
|
+
return local.applyMatrix4(obj.matrixWorld);
|
|
99
|
+
},
|
|
100
|
+
onDrag: (newWorld, obj, ent) => {
|
|
101
|
+
const inv = obj.matrixWorld.clone().invert();
|
|
102
|
+
const local = newWorld.clone().applyMatrix4(inv);
|
|
103
|
+
ent.light!.distance = Math.max(0.1, Math.round(Math.abs(local.z) * 100) / 100);
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
defs.push({
|
|
108
|
+
id: 'spot-angle',
|
|
109
|
+
entityId: eid,
|
|
110
|
+
color: 0xff8844,
|
|
111
|
+
getWorldPosition: (obj) => {
|
|
112
|
+
const angle = entity.light!.angle ?? Math.PI / 6;
|
|
113
|
+
const d = entity.light!.distance ?? 10;
|
|
114
|
+
const r = Math.tan(angle) * d;
|
|
115
|
+
const local = new THREE.Vector3(r, 0, -d);
|
|
116
|
+
return local.applyMatrix4(obj.matrixWorld);
|
|
117
|
+
},
|
|
118
|
+
onDrag: (newWorld, obj, ent) => {
|
|
119
|
+
const inv = obj.matrixWorld.clone().invert();
|
|
120
|
+
const local = newWorld.clone().applyMatrix4(inv);
|
|
121
|
+
const d = Math.abs(local.z) || 0.1;
|
|
122
|
+
const r = Math.abs(local.x);
|
|
123
|
+
const angle = Math.atan2(r, d);
|
|
124
|
+
ent.light!.angle = Math.max(
|
|
125
|
+
0.01,
|
|
126
|
+
Math.min(Math.PI / 2 - 0.01, Math.round(angle * 1000) / 1000),
|
|
127
|
+
);
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// -- Audio source ref/max distance --
|
|
133
|
+
if (entity.audio?.spatial !== false && entity.audio) {
|
|
134
|
+
defs.push({
|
|
135
|
+
id: 'audio-refDistance',
|
|
136
|
+
entityId: eid,
|
|
137
|
+
color: 0x44bbff,
|
|
138
|
+
getWorldPosition: (obj) => {
|
|
139
|
+
const local = new THREE.Vector3(entity.audio!.refDistance ?? 1, 0, 0);
|
|
140
|
+
return local.applyMatrix4(obj.matrixWorld);
|
|
141
|
+
},
|
|
142
|
+
onDrag: (newWorld, obj, ent) => {
|
|
143
|
+
const inv = obj.matrixWorld.clone().invert();
|
|
144
|
+
const local = newWorld.clone().applyMatrix4(inv);
|
|
145
|
+
ent.audio!.refDistance = Math.max(0.01, Math.round(local.length() * 100) / 100);
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
if ((entity.audio.maxDistance ?? 10000) < 10000) {
|
|
149
|
+
defs.push({
|
|
150
|
+
id: 'audio-maxDistance',
|
|
151
|
+
entityId: eid,
|
|
152
|
+
color: 0x2277aa,
|
|
153
|
+
getWorldPosition: (obj) => {
|
|
154
|
+
const local = new THREE.Vector3(entity.audio!.maxDistance ?? 100, 0, 0);
|
|
155
|
+
return local.applyMatrix4(obj.matrixWorld);
|
|
156
|
+
},
|
|
157
|
+
onDrag: (newWorld, obj, ent) => {
|
|
158
|
+
const inv = obj.matrixWorld.clone().invert();
|
|
159
|
+
const local = newWorld.clone().applyMatrix4(inv);
|
|
160
|
+
ent.audio!.maxDistance = Math.max(
|
|
161
|
+
(ent.audio!.refDistance ?? 1) + 0.1,
|
|
162
|
+
Math.round(local.length() * 100) / 100,
|
|
163
|
+
);
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// -- Camera FOV --
|
|
170
|
+
if (entity.camera?.type === 'perspective') {
|
|
171
|
+
defs.push({
|
|
172
|
+
id: 'camera-fov',
|
|
173
|
+
entityId: eid,
|
|
174
|
+
color: 0xcccc44,
|
|
175
|
+
getWorldPosition: (obj) => {
|
|
176
|
+
const fov = entity.camera!.fov ?? 60;
|
|
177
|
+
const halfRad = THREE.MathUtils.degToRad(fov / 2);
|
|
178
|
+
const d = 3; // fixed forward distance for the handle
|
|
179
|
+
const y = Math.tan(halfRad) * d;
|
|
180
|
+
const local = new THREE.Vector3(0, y, -d);
|
|
181
|
+
return local.applyMatrix4(obj.matrixWorld);
|
|
182
|
+
},
|
|
183
|
+
onDrag: (newWorld, obj, ent) => {
|
|
184
|
+
const inv = obj.matrixWorld.clone().invert();
|
|
185
|
+
const local = newWorld.clone().applyMatrix4(inv);
|
|
186
|
+
const d = Math.abs(local.z) || 0.1;
|
|
187
|
+
const y = Math.abs(local.y);
|
|
188
|
+
const halfRad = Math.atan2(y, d);
|
|
189
|
+
const fov = THREE.MathUtils.radToDeg(halfRad) * 2;
|
|
190
|
+
ent.camera!.fov = Math.max(5, Math.min(170, Math.round(fov * 10) / 10));
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// -- Spline control points --
|
|
196
|
+
if (entity.spline && entity.spline.points.length >= 2) {
|
|
197
|
+
for (let i = 0; i < entity.spline.points.length; i++) {
|
|
198
|
+
defs.push({
|
|
199
|
+
id: `spline-point-${i}`,
|
|
200
|
+
entityId: eid,
|
|
201
|
+
color: 0x00ccff,
|
|
202
|
+
getWorldPosition: (obj) => {
|
|
203
|
+
const p = entity.spline!.points[i]!;
|
|
204
|
+
const local = new THREE.Vector3(p[0], p[1], p[2]);
|
|
205
|
+
return local.applyMatrix4(obj.matrixWorld);
|
|
206
|
+
},
|
|
207
|
+
onDrag: (newWorld, obj, ent) => {
|
|
208
|
+
const inv = obj.matrixWorld.clone().invert();
|
|
209
|
+
const local = newWorld.clone().applyMatrix4(inv);
|
|
210
|
+
ent.spline!.points[i] = [
|
|
211
|
+
Math.round(local.x * 1000) / 1000,
|
|
212
|
+
Math.round(local.y * 1000) / 1000,
|
|
213
|
+
Math.round(local.z * 1000) / 1000,
|
|
214
|
+
];
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return defs;
|
|
221
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hierarchy context-menu contribution registry (A2,
|
|
3
|
+
* docs/unified-world-editor/26-unified-editor-spec.md §2 A2) — the escape
|
|
4
|
+
* hatch for first-party-only row actions that are NOT YET contract-expressible
|
|
5
|
+
* through `StructureProvider`/`InspectorProvider` (prefab unpack/open/save-as
|
|
6
|
+
* today; a future tag-search or similar joins the same seam). `GameHierarchy`
|
|
7
|
+
* (the contract-only shell) calls {@link getHierarchyMenuItems} and appends
|
|
8
|
+
* whatever comes back to its own Rename/Duplicate/Show-Hide/Lock/Delete items
|
|
9
|
+
* — it never branches on WHICH registration fired, keeping Rule Zero intact
|
|
10
|
+
* (the shell still speaks only the `AuthoringAdapter` contract; only the
|
|
11
|
+
* REGISTRATION module, e.g. `authoring/first-party-hierarchy-menu.ts`, is
|
|
12
|
+
* allowed to reach into store internals).
|
|
13
|
+
*
|
|
14
|
+
* PERMANENT adapter-module contribution seam (blessed by C2, docs/unified-
|
|
15
|
+
* world-editor/26-unified-editor-spec.md §4 C2 — the same status as C2's own
|
|
16
|
+
* `inspector-section-registry.ts`): first-party affordances that aren't (yet,
|
|
17
|
+
* or ever) contract-expressible register through here instead of the
|
|
18
|
+
* contract-only shell branching on adapter identity. If a given affordance
|
|
19
|
+
* later becomes contract-expressible, its OWN registration is removed at
|
|
20
|
+
* that point — the registry mechanism itself is not temporary.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import type { EditorStore } from './editor-store';
|
|
24
|
+
|
|
25
|
+
export interface HierarchyMenuContext {
|
|
26
|
+
/** The row's node id (an `EditorNode.id` — format-neutral, not a `SceneEntity` id). */
|
|
27
|
+
readonly nodeId: string;
|
|
28
|
+
readonly store: EditorStore;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface HierarchyMenuItem {
|
|
32
|
+
readonly label: string;
|
|
33
|
+
readonly action: () => void;
|
|
34
|
+
readonly color?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
type Matcher = (ctx: HierarchyMenuContext) => boolean;
|
|
38
|
+
type ItemsFactory = (ctx: HierarchyMenuContext) => HierarchyMenuItem[];
|
|
39
|
+
|
|
40
|
+
interface Registration {
|
|
41
|
+
readonly match: Matcher;
|
|
42
|
+
readonly items: ItemsFactory;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const registrations: Registration[] = [];
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Register a set of first-party (or other non-contract) hierarchy context-menu
|
|
49
|
+
* items. `match` decides whether this registration applies to a given row;
|
|
50
|
+
* `items` builds the actual entries when it does (called only when `match`
|
|
51
|
+
* returns true). Returns an unregister function.
|
|
52
|
+
*/
|
|
53
|
+
export function registerHierarchyMenuItems(match: Matcher, items: ItemsFactory): () => void {
|
|
54
|
+
const reg: Registration = { match, items };
|
|
55
|
+
registrations.push(reg);
|
|
56
|
+
return () => {
|
|
57
|
+
const idx = registrations.indexOf(reg);
|
|
58
|
+
if (idx >= 0) registrations.splice(idx, 1);
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Every registered item that applies to `ctx`, in registration order. */
|
|
63
|
+
export function getHierarchyMenuItems(ctx: HierarchyMenuContext): HierarchyMenuItem[] {
|
|
64
|
+
return registrations.filter((r) => r.match(ctx)).flatMap((r) => r.items(ctx));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Test-only reset (mirrors `undo-timeline.ts`'s `__resetUndoTimelineForTest`). */
|
|
68
|
+
export function __resetHierarchyMenuRegistryForTest(): void {
|
|
69
|
+
registrations.length = 0;
|
|
70
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure row-flattening for the hierarchy tree (no React) — extracted so the
|
|
3
|
+
* child-cap logic is unit-testable without rendering the panel.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { SceneEntity } from '@engine/scene/scene-types';
|
|
7
|
+
|
|
8
|
+
/** Max children shown per parent before a "… N more" row (when capping is on). */
|
|
9
|
+
export const CHILD_CAP = 20;
|
|
10
|
+
export const MORE_ID_PREFIX = '__more__:';
|
|
11
|
+
|
|
12
|
+
export interface TreeRow {
|
|
13
|
+
entity: SceneEntity;
|
|
14
|
+
depth: number;
|
|
15
|
+
parentId: string | null;
|
|
16
|
+
childIndex: number;
|
|
17
|
+
hasChildren: boolean;
|
|
18
|
+
isOpen: boolean;
|
|
19
|
+
/** Present on a synthetic "… N more" row that reveals capped siblings. */
|
|
20
|
+
more?: { parentKey: string; hidden: number };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Stable key for a parent's child-limit bookkeeping (root uses a sentinel). */
|
|
24
|
+
export function parentKeyOf(parentId: string | null): string {
|
|
25
|
+
return parentId ?? '__root__';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Flatten a `SceneEntity` tree into display rows, honoring collapsed nodes and
|
|
30
|
+
* (when `capEnabled`) a per-parent child cap with a synthetic "… N more" row.
|
|
31
|
+
* `expanded` maps a parent key → how many children it has been asked to reveal.
|
|
32
|
+
*/
|
|
33
|
+
export function flattenVisibleRows(
|
|
34
|
+
entities: SceneEntity[],
|
|
35
|
+
closedIds: Set<string>,
|
|
36
|
+
expanded: Map<string, number>,
|
|
37
|
+
capEnabled: boolean,
|
|
38
|
+
parentId: string | null = null,
|
|
39
|
+
depth = 0,
|
|
40
|
+
): TreeRow[] {
|
|
41
|
+
const rows: TreeRow[] = [];
|
|
42
|
+
const parentKey = parentKeyOf(parentId);
|
|
43
|
+
// Cap each sibling list so a huge live scene graph (play mode) doesn't dump
|
|
44
|
+
// thousands of rows at once. A "… N more" row reveals the next page on click.
|
|
45
|
+
const limit = capEnabled
|
|
46
|
+
? Math.max(CHILD_CAP, expanded.get(parentKey) ?? CHILD_CAP)
|
|
47
|
+
: Number.POSITIVE_INFINITY;
|
|
48
|
+
const shown = Math.min(entities.length, limit);
|
|
49
|
+
for (let i = 0; i < shown; i++) {
|
|
50
|
+
const entity = entities[i]!;
|
|
51
|
+
const id = entity.id!;
|
|
52
|
+
const hasChildren = (entity.children?.length ?? 0) > 0;
|
|
53
|
+
const isOpen = hasChildren && !closedIds.has(id);
|
|
54
|
+
rows.push({ entity, depth, parentId, childIndex: i, hasChildren, isOpen });
|
|
55
|
+
if (isOpen && entity.children) {
|
|
56
|
+
rows.push(
|
|
57
|
+
...flattenVisibleRows(entity.children, closedIds, expanded, capEnabled, id, depth + 1),
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (entities.length > shown) {
|
|
62
|
+
rows.push({
|
|
63
|
+
entity: { id: `${MORE_ID_PREFIX}${parentKey}`, name: '' } as SceneEntity,
|
|
64
|
+
depth,
|
|
65
|
+
parentId,
|
|
66
|
+
childIndex: shown,
|
|
67
|
+
hasChildren: false,
|
|
68
|
+
isOpen: false,
|
|
69
|
+
more: { parentKey, hidden: entities.length - shown },
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return rows;
|
|
73
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hosted-example routing — the static/browser build's `?project=<exampleId>`
|
|
3
|
+
* deep-link scheme (docs/EXAMPLES-AS-PROJECTS-DESIGN.md §H, Track H).
|
|
4
|
+
*
|
|
5
|
+
* In the hosted editor every `examples/<id>/` project is staged at
|
|
6
|
+
* `/examples/<id>/` by the web build (`vite-plugin-example-assets.ts` copies
|
|
7
|
+
* `public/*` plus the `vgai.game.json` manifest) and its code ships inside the
|
|
8
|
+
* pre-bundled project scripts, dispatched by `EditorPreview.exampleId`
|
|
9
|
+
* (`browser-play-entry.ts`). This module supplies the routing glue between
|
|
10
|
+
* those two existing seams:
|
|
11
|
+
*
|
|
12
|
+
* - `openExampleProject(id)` — resolve `?project=<id>` to an ActiveProject by
|
|
13
|
+
* fetching the staged manifest; namespaces ALL relative asset resolution
|
|
14
|
+
* (editor-side prefab/material/scene fetches included) via `setAssetPrefix`.
|
|
15
|
+
* - `exampleDispatchFor(...)` — the play-mode decision: dispatch the bundled
|
|
16
|
+
* example setup, or play the OPEN scene generically but keep the example's
|
|
17
|
+
* asset prefix (`?scene=` override within the project).
|
|
18
|
+
* - `legacyPoolSearch(...)` — rewrite pre-Track-H `?scene=/examples/scenes/…`
|
|
19
|
+
* pool links (the deleted flat scene pool) onto the project scheme, so
|
|
20
|
+
* published lesson URLs and bookmarks keep working.
|
|
21
|
+
*
|
|
22
|
+
* Example projects open READ-ONLY: their scenes load with no save path, which
|
|
23
|
+
* makes autosave an inert no-op (see `editor-store.ts`) — edits are in-memory
|
|
24
|
+
* for the session. To keep changes, scaffold the example into a real project
|
|
25
|
+
* (`create-vgai-project <name> --example <id>`).
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { setAssetPrefix } from '@engine/loader';
|
|
29
|
+
import { GameManifestSchema } from '@engine/manifest/schema';
|
|
30
|
+
import type { ActiveProject } from './project-manager';
|
|
31
|
+
|
|
32
|
+
/** Folder-name shape shared by every `examples/<id>/` — also a path-safety gate. */
|
|
33
|
+
const EXAMPLE_ID_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
34
|
+
|
|
35
|
+
/** Fetch + validate the staged manifest for a bundled example and derive the
|
|
36
|
+
* ActiveProject the rest of the editor consumes. Throws on unknown id /
|
|
37
|
+
* missing staging / invalid manifest — the caller decides the fallback. */
|
|
38
|
+
export async function openExampleProject(exampleId: string): Promise<ActiveProject> {
|
|
39
|
+
if (!EXAMPLE_ID_RE.test(exampleId)) {
|
|
40
|
+
throw new Error(`Invalid example id "${exampleId}"`);
|
|
41
|
+
}
|
|
42
|
+
const manifestUrl = `/examples/${exampleId}/vgai.game.json`;
|
|
43
|
+
const res = await fetch(manifestUrl);
|
|
44
|
+
if (!res.ok) {
|
|
45
|
+
throw new Error(`HTTP ${res.status} fetching ${manifestUrl} — not a bundled example?`);
|
|
46
|
+
}
|
|
47
|
+
const manifest = GameManifestSchema.parse(await res.json());
|
|
48
|
+
|
|
49
|
+
// Namespace relative asset resolution to this example's staged public/ —
|
|
50
|
+
// the same prefix `browser-play-entry.ts` sets at play dispatch, applied
|
|
51
|
+
// here so EDITOR-side fetches (scenes, prefabs, .mat files via
|
|
52
|
+
// `resolveUrl`) resolve against the project too.
|
|
53
|
+
setAssetPrefix(`/examples/${exampleId}/`);
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
rootPath: `/examples/${exampleId}`,
|
|
57
|
+
config: {
|
|
58
|
+
name: manifest.name,
|
|
59
|
+
defaultScene: manifest.worlds[0]?.scene ?? null,
|
|
60
|
+
...(manifest.resolution ? { resolution: manifest.resolution } : {}),
|
|
61
|
+
},
|
|
62
|
+
exampleId,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The play-mode routing decision for a hosted example project — pure, so it is
|
|
68
|
+
* unit-testable without booting play mode. Returns the EditorPreview fields to
|
|
69
|
+
* spread into the preview object:
|
|
70
|
+
*
|
|
71
|
+
* - `{ exampleId }` — the project's shipped game: dispatch the bundled setup
|
|
72
|
+
* (its own registry + gameplay systems; it loads its own manifest scene).
|
|
73
|
+
* - `{ assetPrefix }` — a `?scene=` override inside the project (e.g.
|
|
74
|
+
* feature-scenes' unmounted extras): play the editor's OPEN scene through
|
|
75
|
+
* the generic loader, but keep asset refs resolving into the example.
|
|
76
|
+
* - `{}` — not an example project, or the user opened a VFS scene of their
|
|
77
|
+
* own (savePath set): today's generic behavior, root prefix.
|
|
78
|
+
*/
|
|
79
|
+
export function exampleDispatchFor(
|
|
80
|
+
exampleId: string | undefined,
|
|
81
|
+
savePath: string | null,
|
|
82
|
+
hasSceneOverride: boolean,
|
|
83
|
+
): { exampleId?: string; assetPrefix?: string } {
|
|
84
|
+
if (!exampleId || savePath !== null) return {};
|
|
85
|
+
if (hasSceneOverride) return { assetPrefix: `/examples/${exampleId}/` };
|
|
86
|
+
return { exampleId };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
// Legacy flat-pool links (`?scene=/examples/scenes/<name>.vscn.json`)
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
|
|
93
|
+
/** Pool scenes that live in the scene-only `feature-scenes` project now. */
|
|
94
|
+
const FEATURE_SCENES_POOL = new Set([
|
|
95
|
+
'instanced-grid', // feature-scenes' manifest default — no scene override needed
|
|
96
|
+
'learn-skybox',
|
|
97
|
+
'ocean',
|
|
98
|
+
'particles',
|
|
99
|
+
'terrain',
|
|
100
|
+
'water-shader',
|
|
101
|
+
]);
|
|
102
|
+
|
|
103
|
+
/** Pool scenes whose example folder name differs from the scene file name. */
|
|
104
|
+
const RENAMED_POOL: Record<string, string> = {
|
|
105
|
+
arena: 'third-person-arena',
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/** Pool scenes whose name IS their example id (verified on disk by
|
|
109
|
+
* `hosted-example-routing.test.ts` — every target's manifest + scene exist). */
|
|
110
|
+
const IDENTITY_POOL = new Set([
|
|
111
|
+
'editor-tutorial',
|
|
112
|
+
'fps',
|
|
113
|
+
'ik',
|
|
114
|
+
'learn-joints',
|
|
115
|
+
'learn-matfiles',
|
|
116
|
+
'learn-splines',
|
|
117
|
+
'learn-triggers',
|
|
118
|
+
'littlest-tokyo',
|
|
119
|
+
'rts',
|
|
120
|
+
'third-person',
|
|
121
|
+
'vehicle',
|
|
122
|
+
]);
|
|
123
|
+
|
|
124
|
+
/** Map a legacy pool scene name to its `?project=` route, or null if unknown. */
|
|
125
|
+
export function legacyPoolRoute(name: string): { project: string; scene?: string } | null {
|
|
126
|
+
if (FEATURE_SCENES_POOL.has(name)) {
|
|
127
|
+
return name === 'instanced-grid'
|
|
128
|
+
? { project: 'feature-scenes' }
|
|
129
|
+
: { project: 'feature-scenes', scene: `scenes/${name}.vscn.json` };
|
|
130
|
+
}
|
|
131
|
+
const project = RENAMED_POOL[name] ?? (IDENTITY_POOL.has(name) ? name : null);
|
|
132
|
+
return project ? { project } : null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Rewrite a legacy pool deep link's search string onto the project scheme.
|
|
137
|
+
* Returns the new search string (other params preserved), or null when the
|
|
138
|
+
* URL is not a known legacy pool link (leave it alone).
|
|
139
|
+
*/
|
|
140
|
+
export function legacyPoolSearch(search: string): string | null {
|
|
141
|
+
const params = new URLSearchParams(search);
|
|
142
|
+
const scene = params.get('scene');
|
|
143
|
+
if (!scene || params.get('project')) return null;
|
|
144
|
+
const m = /^\/examples\/scenes\/([a-z0-9-]+)\.vscn\.json$/.exec(scene);
|
|
145
|
+
if (!m || m[1] === undefined) return null;
|
|
146
|
+
const route = legacyPoolRoute(m[1]);
|
|
147
|
+
if (!route) return null;
|
|
148
|
+
params.delete('scene');
|
|
149
|
+
params.set('project', route.project);
|
|
150
|
+
if (route.scene) params.set('scene', route.scene);
|
|
151
|
+
return `?${params.toString()}`;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Apply the legacy rewrite to the live URL (history.replaceState, no reload). */
|
|
155
|
+
export function applyLegacyPoolRedirect(): void {
|
|
156
|
+
const rewritten = legacyPoolSearch(window.location.search);
|
|
157
|
+
if (rewritten !== null) {
|
|
158
|
+
window.history.replaceState(null, '', `${window.location.pathname}${rewritten}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
package/src/hotkeys.ts
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// Types
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
|
|
7
|
+
export type HotkeyScope = 'viewport' | 'hierarchy' | 'inspector' | 'asset-browser' | 'global';
|
|
8
|
+
|
|
9
|
+
export interface HotkeyBinding {
|
|
10
|
+
/** The key to match (`e.key`, lowercase). Ignored when `code` is set. */
|
|
11
|
+
key: string;
|
|
12
|
+
/** Match on `e.code` instead of `e.key` (for numpad keys). */
|
|
13
|
+
code?: string;
|
|
14
|
+
/** Require Ctrl (Win/Linux) or Cmd (Mac). */
|
|
15
|
+
mod?: boolean;
|
|
16
|
+
/** Require Shift. */
|
|
17
|
+
shift?: boolean;
|
|
18
|
+
/** Require Alt/Option (D4 — `wrap`'s `Ctrl+Alt+G`). */
|
|
19
|
+
alt?: boolean;
|
|
20
|
+
/** Scope this binding belongs to. `'global'` bindings fire regardless of panel focus. */
|
|
21
|
+
scope: HotkeyScope;
|
|
22
|
+
/** Action to execute when the binding matches. */
|
|
23
|
+
action: (e: KeyboardEvent) => void;
|
|
24
|
+
/** Human-readable label for menus / tooltips. */
|
|
25
|
+
label?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// Active scope (which panel was last interacted with)
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
let _activeScope: HotkeyScope = 'viewport';
|
|
33
|
+
|
|
34
|
+
export function getActiveScope(): HotkeyScope {
|
|
35
|
+
return _activeScope;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function setActiveScope(scope: HotkeyScope): void {
|
|
39
|
+
_activeScope = scope;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
// Binding registry
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
const _bindings: HotkeyBinding[] = [];
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Register hotkey bindings. Returns a dispose function that removes them.
|
|
50
|
+
*/
|
|
51
|
+
export function registerHotkeys(bindings: HotkeyBinding[]): () => void {
|
|
52
|
+
_bindings.push(...bindings);
|
|
53
|
+
return () => {
|
|
54
|
+
for (const b of bindings) {
|
|
55
|
+
const idx = _bindings.indexOf(b);
|
|
56
|
+
if (idx >= 0) _bindings.splice(idx, 1);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
// Dispatch
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
function handleKeyDown(e: KeyboardEvent): void {
|
|
66
|
+
// Skip when typing in form inputs — D4 (spec27 §6 D4 "each shortcut fires
|
|
67
|
+
// only when not typing") widens this to `[contenteditable]` too (no
|
|
68
|
+
// in-repo editor uses one today — `WorldTextEditor`/`GameHierarchy`'s
|
|
69
|
+
// rename both use a real `<textarea>`/`<input>`, already covered above —
|
|
70
|
+
// but the guard should hold for a future contentEditable surface without
|
|
71
|
+
// needing another fix here).
|
|
72
|
+
if (
|
|
73
|
+
e.target instanceof HTMLInputElement ||
|
|
74
|
+
e.target instanceof HTMLTextAreaElement ||
|
|
75
|
+
e.target instanceof HTMLSelectElement ||
|
|
76
|
+
(e.target instanceof HTMLElement && e.target.isContentEditable)
|
|
77
|
+
)
|
|
78
|
+
return;
|
|
79
|
+
|
|
80
|
+
const mod = e.metaKey || e.ctrlKey;
|
|
81
|
+
const key = e.key.toLowerCase();
|
|
82
|
+
const code = e.code;
|
|
83
|
+
const scope = _activeScope;
|
|
84
|
+
|
|
85
|
+
// Check scoped bindings first, then global
|
|
86
|
+
const passes: HotkeyScope[] = scope === 'global' ? ['global'] : [scope, 'global'];
|
|
87
|
+
|
|
88
|
+
for (const pass of passes) {
|
|
89
|
+
for (const b of _bindings) {
|
|
90
|
+
if (b.scope !== pass) continue;
|
|
91
|
+
|
|
92
|
+
const keyMatch = b.code ? b.code === code : b.key === key;
|
|
93
|
+
if (!keyMatch) continue;
|
|
94
|
+
if ((b.mod ?? false) !== mod) continue;
|
|
95
|
+
if ((b.shift ?? false) !== e.shiftKey) continue;
|
|
96
|
+
if ((b.alt ?? false) !== e.altKey) continue;
|
|
97
|
+
|
|
98
|
+
e.preventDefault();
|
|
99
|
+
b.action(e);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
let _installed = false;
|
|
106
|
+
|
|
107
|
+
/** Attach the global keydown listener. Call once at startup. */
|
|
108
|
+
export function installHotkeys(): void {
|
|
109
|
+
if (_installed) return;
|
|
110
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
111
|
+
_installed = true;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Detach the global keydown listener. */
|
|
115
|
+
export function uninstallHotkeys(): void {
|
|
116
|
+
window.removeEventListener('keydown', handleKeyDown);
|
|
117
|
+
_installed = false;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* True once this shared hotkey system is installed (the real editor's
|
|
122
|
+
* `registerEditorHotkeys`/`installHotkeys`, called when the viewport mounts).
|
|
123
|
+
* `false` in a standalone surface that never mounts a viewport (e.g. the UI-editor
|
|
124
|
+
* harness) — used so a panel that has its OWN local keydown handling (T3.2 slice
|
|
125
|
+
* 2: `UIEditor.tsx`'s undo/redo) can tell whether THIS system will already
|
|
126
|
+
* dispatch the key, and skip acting itself rather than double-dispatching.
|
|
127
|
+
*/
|
|
128
|
+
export function isHotkeysInstalled(): boolean {
|
|
129
|
+
return _installed;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
// React hook for panel-local shortcuts
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Register hotkey bindings for the lifetime of a component.
|
|
138
|
+
* Pass a stable array (defined outside render or wrapped in useMemo).
|
|
139
|
+
*/
|
|
140
|
+
export function useHotkeys(bindings: HotkeyBinding[]): void {
|
|
141
|
+
useEffect(() => registerHotkeys(bindings), [bindings]);
|
|
142
|
+
}
|