@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,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON Schema → `FieldDescriptor` conversion for DATA ASSETS (W2,
|
|
3
|
+
* docs/DATA-TOOLS-DESIGN.md §2).
|
|
4
|
+
*
|
|
5
|
+
* ARCHITECTURE RULE (spec §0/§2.1): the editor consumes the EMITTED JSON
|
|
6
|
+
* Schema (`*.schema.json`, the interchange artifact produced by
|
|
7
|
+
* `toDataJsonSchema`/`z.toJSONSchema`) — it never imports or executes the
|
|
8
|
+
* project's `*.schema.ts`. That keeps the Data panel a pure consumer of the
|
|
9
|
+
* contract format ("the contract is the format, not the library") and means
|
|
10
|
+
* anything that can emit JSON Schema gets an editor for free.
|
|
11
|
+
*
|
|
12
|
+
* This is the JSON Schema sibling of `zod-fields.ts`'s `introspectSchema`
|
|
13
|
+
* (which walks live Zod internals for component schemas). Both funnel into the
|
|
14
|
+
* same `FieldDescriptor` IR so the Data panel renders with the exact widgets
|
|
15
|
+
* the component inspector uses. Like `zod-fields.ts`, this module is pure (no
|
|
16
|
+
* React, no DOM) so it can be unit-tested in Node.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type { FieldDescriptor, FieldKind } from './zod-fields';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A `FieldDescriptor` extended with the numeric bounds JSON Schema carries
|
|
23
|
+
* (`minimum`/`maximum`). The Zod introspection path never surfaced these, but
|
|
24
|
+
* emitted data-asset schemas do (e.g. `z.number().min(0).max(10)` emits
|
|
25
|
+
* `"minimum": 0, "maximum": 10`) and the Data panel clamps number edits to
|
|
26
|
+
* them.
|
|
27
|
+
*
|
|
28
|
+
* Widened locally (W2 hardening, spec §2.1 field-depth policy) with three
|
|
29
|
+
* kinds `zod-fields.ts`'s component-inspector `FieldKind` has no use for, so
|
|
30
|
+
* they're added here rather than on the shared type:
|
|
31
|
+
* - `'group'` — an object-typed node whose `properties` the panel can recurse
|
|
32
|
+
* into as a nested sub-form (`children` holds the nested descriptors).
|
|
33
|
+
* Singleton data assets only; a depth cap degrades deep nesting to `'json'`.
|
|
34
|
+
* - `'json'` — anything the panel can't type (arrays, unions, a group past
|
|
35
|
+
* the depth cap): a raw-JSON cell that parses on commit and REFUSES an
|
|
36
|
+
* invalid edit rather than ever writing a string in place of the typed
|
|
37
|
+
* value (the invariant this hardening exists to hold — see `JsonCell` in
|
|
38
|
+
* `DataPanel.tsx`).
|
|
39
|
+
* - `'ref'` — a declared `dataRef(target)` field (`@engine/data/data-ref`):
|
|
40
|
+
* the emitted schema carries `"x-vgai-ref": target` (a Zod `.meta()` that
|
|
41
|
+
* survives `toDataJsonSchema`), so the panel renders a `stem#key` PICKER
|
|
42
|
+
* sourced from the target file's own keys instead of a free-text cell.
|
|
43
|
+
*/
|
|
44
|
+
export interface DataFieldDescriptor extends Omit<FieldDescriptor, 'kind'> {
|
|
45
|
+
kind: FieldKind | 'group' | 'json' | 'ref';
|
|
46
|
+
/** Nested field descriptors, present only when `kind === 'group'`. */
|
|
47
|
+
children?: DataFieldDescriptor[];
|
|
48
|
+
min?: number;
|
|
49
|
+
max?: number;
|
|
50
|
+
/** Target asset stem for `kind === 'ref'` — the `dataRef` call's argument. */
|
|
51
|
+
refTarget?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Defensive recursion cap on nested `group` descriptors (spec §2.1: "1-2
|
|
56
|
+
* levels" is the blessed shape; this is the hard backstop, not the taught
|
|
57
|
+
* limit). Past this depth a would-be group degrades to `'json'` — safe,
|
|
58
|
+
* never a corruption vector, because `JsonCell` never widens a typed value
|
|
59
|
+
* into a string on commit.
|
|
60
|
+
*/
|
|
61
|
+
const MAX_GROUP_DEPTH = 4;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The two blessed data-asset shapes (spec §2.1), detected from the emitted
|
|
65
|
+
* schema's top level:
|
|
66
|
+
* - **singleton** — one config object; `properties` describes its fields.
|
|
67
|
+
* Renders as a form.
|
|
68
|
+
* - **table** — `z.record(rowSchema)`; the emitted schema carries the row
|
|
69
|
+
* schema as an object-valued `additionalProperties`. Renders as a grid,
|
|
70
|
+
* one row per key in the data file.
|
|
71
|
+
*/
|
|
72
|
+
export type DataAssetShape =
|
|
73
|
+
| { shape: 'singleton'; fields: DataFieldDescriptor[] }
|
|
74
|
+
| { shape: 'table'; rowFields: DataFieldDescriptor[] };
|
|
75
|
+
|
|
76
|
+
/** Loosely-typed view of a JSON Schema node (we only read what we render). */
|
|
77
|
+
interface SchemaNode {
|
|
78
|
+
type?: string | string[];
|
|
79
|
+
properties?: Record<string, unknown>;
|
|
80
|
+
additionalProperties?: unknown;
|
|
81
|
+
enum?: unknown[];
|
|
82
|
+
default?: unknown;
|
|
83
|
+
description?: string;
|
|
84
|
+
minimum?: number;
|
|
85
|
+
maximum?: number;
|
|
86
|
+
anyOf?: unknown[];
|
|
87
|
+
/** `dataRef(target)`'s marker (`@engine/data/data-ref`) — present iff this is a declared ref field. */
|
|
88
|
+
'x-vgai-ref'?: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function asNode(value: unknown): SchemaNode | null {
|
|
92
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? (value as SchemaNode) : null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Heuristic (same idea as `zod-fields.ts`'s `looksLikeColor`): a string field
|
|
97
|
+
* edits as a color swatch when its name says color, its default is a
|
|
98
|
+
* `#rrggbb`-style value, or its description mentions color.
|
|
99
|
+
*/
|
|
100
|
+
function looksLikeColor(name: string, def: unknown, description: string | undefined): boolean {
|
|
101
|
+
if (/colou?r/i.test(name)) return true;
|
|
102
|
+
if (typeof def === 'string' && /^#/.test(def)) return true;
|
|
103
|
+
if (typeof description === 'string' && /\bcolou?r\b/i.test(description)) return true;
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** First declared `type` string, unwrapping the `["number","null"]` array form. */
|
|
108
|
+
function nodeType(node: SchemaNode): string | undefined {
|
|
109
|
+
if (typeof node.type === 'string') return node.type;
|
|
110
|
+
if (Array.isArray(node.type)) return node.type.find((t) => t !== 'null');
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Optional/nullable Zod fields can emit as `anyOf` branches; adopt the first
|
|
116
|
+
* branch that declares a type. Defensive, like zod-fields' fallbacks —
|
|
117
|
+
* anything unrecognized degrades to a raw text field, never a crash.
|
|
118
|
+
*/
|
|
119
|
+
function unwrapAnyOf(node: SchemaNode): SchemaNode {
|
|
120
|
+
if (nodeType(node) || node.enum || !Array.isArray(node.anyOf)) return node;
|
|
121
|
+
const branch = node.anyOf.map(asNode).find((b) => b && (nodeType(b) || b.enum));
|
|
122
|
+
if (!branch) return node;
|
|
123
|
+
// Branch supplies the type/enum; the outer node's default/description/
|
|
124
|
+
// bounds (where present) win, since Zod hoists those to the wrapper.
|
|
125
|
+
return {
|
|
126
|
+
...branch,
|
|
127
|
+
...node,
|
|
128
|
+
...(branch.type ? { type: branch.type } : {}),
|
|
129
|
+
...(branch.enum ? { enum: branch.enum } : {}),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Map one (unwrapped) schema node to the widget kind + enum options.
|
|
135
|
+
*
|
|
136
|
+
* `depth` is the nesting depth of THIS node (0 = a direct field of the
|
|
137
|
+
* top-level singleton/row schema) — it gates the `'group'` case so recursion
|
|
138
|
+
* can't run away on a pathological schema; see `MAX_GROUP_DEPTH`.
|
|
139
|
+
*/
|
|
140
|
+
function detectKind(
|
|
141
|
+
node: SchemaNode,
|
|
142
|
+
name: string,
|
|
143
|
+
depth: number,
|
|
144
|
+
): { kind: DataFieldDescriptor['kind']; options?: string[]; refTarget?: string } {
|
|
145
|
+
// Ref first: a declared `dataRef(target)` field is a plain `{ type:
|
|
146
|
+
// "string", pattern: ..., "x-vgai-ref": target }` — it must win over both
|
|
147
|
+
// the enum and plain-string branches below, which would otherwise render
|
|
148
|
+
// it as ordinary free text.
|
|
149
|
+
if (typeof node['x-vgai-ref'] === 'string') {
|
|
150
|
+
return { kind: 'ref', refTarget: node['x-vgai-ref'] };
|
|
151
|
+
}
|
|
152
|
+
// Enum first: emitted enums are `{ type: "string", enum: [...] }`, so the
|
|
153
|
+
// enum check must win over the plain-string branch.
|
|
154
|
+
if (Array.isArray(node.enum) && node.enum.length > 0) {
|
|
155
|
+
return { kind: 'enum', options: node.enum.map(String) };
|
|
156
|
+
}
|
|
157
|
+
switch (nodeType(node)) {
|
|
158
|
+
case 'number':
|
|
159
|
+
case 'integer':
|
|
160
|
+
return { kind: 'number' };
|
|
161
|
+
case 'boolean':
|
|
162
|
+
return { kind: 'boolean' };
|
|
163
|
+
case 'string':
|
|
164
|
+
return looksLikeColor(name, node.default, node.description)
|
|
165
|
+
? { kind: 'color' }
|
|
166
|
+
: { kind: 'string' };
|
|
167
|
+
default:
|
|
168
|
+
// `type: 'object'` (or absent — Zod can omit `type` on some wrapper
|
|
169
|
+
// shapes) with an object-valued `properties`: a nested group the panel
|
|
170
|
+
// can recurse into as a sub-form, UNLESS the depth cap is hit. Arrays,
|
|
171
|
+
// unions, and anything else with no `properties` to walk (including a
|
|
172
|
+
// capped-out group) fall through to `'json'` — a raw-JSON cell that
|
|
173
|
+
// parses on commit and refuses invalid input, never a widened string
|
|
174
|
+
// (W2 hardening: the prior fallback here was `'string'`, which is
|
|
175
|
+
// exactly the corruption this module exists to prevent — see
|
|
176
|
+
// `DataFieldDescriptor`'s doc comment).
|
|
177
|
+
if (asNode(node.properties) && depth < MAX_GROUP_DEPTH) {
|
|
178
|
+
return { kind: 'group' };
|
|
179
|
+
}
|
|
180
|
+
return { kind: 'json' };
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** Convert ONE JSON Schema property node into a field descriptor. */
|
|
185
|
+
function toDescriptor(name: string, raw: unknown, depth: number): DataFieldDescriptor {
|
|
186
|
+
const node = unwrapAnyOf(asNode(raw) ?? {});
|
|
187
|
+
const { kind, options, refTarget } = detectKind(node, name, depth);
|
|
188
|
+
|
|
189
|
+
const fd: DataFieldDescriptor = { name, kind };
|
|
190
|
+
if (options !== undefined) fd.options = options;
|
|
191
|
+
if (refTarget !== undefined) fd.refTarget = refTarget;
|
|
192
|
+
if (node.default !== undefined) fd.default = node.default;
|
|
193
|
+
if (node.description !== undefined) fd.description = node.description;
|
|
194
|
+
if (typeof node.minimum === 'number') fd.min = node.minimum;
|
|
195
|
+
if (typeof node.maximum === 'number') fd.max = node.maximum;
|
|
196
|
+
if (kind === 'group') fd.children = jsonSchemaToFieldDescriptors(node, depth + 1);
|
|
197
|
+
return fd;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Convert a JSON Schema OBJECT node's `properties` into field descriptors.
|
|
202
|
+
*
|
|
203
|
+
* The `"$schema"` property is skipped: `toDataJsonSchema` deliberately allows
|
|
204
|
+
* a `"$schema"` string on emitted object schemas so VS Code doesn't flag the
|
|
205
|
+
* declaration line in data files (spec §9.1) — it is plumbing, not a field.
|
|
206
|
+
*
|
|
207
|
+
* `depth` is the nesting depth of the fields THIS call produces (0 at the
|
|
208
|
+
* top-level call) — threaded through so a nested `group`'s own children are
|
|
209
|
+
* classified one level deeper; see `MAX_GROUP_DEPTH`.
|
|
210
|
+
*/
|
|
211
|
+
export function jsonSchemaToFieldDescriptors(schema: unknown, depth = 0): DataFieldDescriptor[] {
|
|
212
|
+
const node = asNode(schema);
|
|
213
|
+
const props = node ? asNode(node.properties) : null;
|
|
214
|
+
if (!props) return [];
|
|
215
|
+
|
|
216
|
+
const fields: DataFieldDescriptor[] = [];
|
|
217
|
+
for (const [name, propSchema] of Object.entries(props)) {
|
|
218
|
+
if (name === '$schema') continue;
|
|
219
|
+
fields.push(toDescriptor(name, propSchema, depth));
|
|
220
|
+
}
|
|
221
|
+
return fields;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Classify an emitted data-asset schema as one of the two blessed shapes
|
|
226
|
+
* (spec §2.1) and return the descriptors the Data panel renders.
|
|
227
|
+
*
|
|
228
|
+
* Table detection MUST run first: a table's emitted schema also carries a
|
|
229
|
+
* top-level `properties: { "$schema": … }` (the §9.1 allowance), so
|
|
230
|
+
* "has properties" alone would misread every table as a singleton. The
|
|
231
|
+
* discriminator is an OBJECT-VALUED `additionalProperties` — the row schema
|
|
232
|
+
* `z.record(rowSchema)` emits (a bare boolean `additionalProperties` is just
|
|
233
|
+
* an open/closed-object flag, not a row schema).
|
|
234
|
+
*
|
|
235
|
+
* Returns `null` for anything that is neither (the panel shows the file as
|
|
236
|
+
* unsupported rather than guessing).
|
|
237
|
+
*/
|
|
238
|
+
export function describeDataSchema(schema: unknown): DataAssetShape | null {
|
|
239
|
+
const node = asNode(schema);
|
|
240
|
+
if (!node) return null;
|
|
241
|
+
|
|
242
|
+
const rowSchema = asNode(node.additionalProperties);
|
|
243
|
+
if (rowSchema && asNode(rowSchema.properties)) {
|
|
244
|
+
return { shape: 'table', rowFields: jsonSchemaToFieldDescriptors(rowSchema) };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const fields = jsonSchemaToFieldDescriptors(node);
|
|
248
|
+
if (fields.length > 0) return { shape: 'singleton', fields };
|
|
249
|
+
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* A fresh value for one row-schema field (W6b, docs/DATA-TOOLS-DESIGN.md §7):
|
|
255
|
+
* the schema's own declared `default` when present (cloned — never the same
|
|
256
|
+
* reference, so two new rows never share a mutable nested object), else the
|
|
257
|
+
* kind's zero value. Used to build a brand-new table row (add-row) and to
|
|
258
|
+
* fill in a cell a CSV import left blank/missing (W6c).
|
|
259
|
+
*/
|
|
260
|
+
export function defaultValueForDescriptor(f: DataFieldDescriptor): unknown {
|
|
261
|
+
if (f.default !== undefined) return structuredClone(f.default);
|
|
262
|
+
switch (f.kind) {
|
|
263
|
+
case 'number':
|
|
264
|
+
return f.min ?? 0;
|
|
265
|
+
case 'boolean':
|
|
266
|
+
return false;
|
|
267
|
+
case 'color':
|
|
268
|
+
return '#ffffff';
|
|
269
|
+
case 'enum':
|
|
270
|
+
return f.options?.[0] ?? '';
|
|
271
|
+
case 'string':
|
|
272
|
+
case 'ref':
|
|
273
|
+
return '';
|
|
274
|
+
case 'group':
|
|
275
|
+
return Object.fromEntries(
|
|
276
|
+
(f.children ?? []).map((c) => [c.name, defaultValueForDescriptor(c)]),
|
|
277
|
+
);
|
|
278
|
+
default:
|
|
279
|
+
return null; // 'json' — JsonCell renders/edits it safely
|
|
280
|
+
}
|
|
281
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { SceneEntity } from '@engine/scene/scene-types';
|
|
2
|
+
|
|
3
|
+
/** Shared props for all inspector sections. */
|
|
4
|
+
export interface SectionProps {
|
|
5
|
+
entities: SceneEntity[];
|
|
6
|
+
ids: string[];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Return the common numeric value across entities, or null if mixed. */
|
|
10
|
+
export function commonNum(
|
|
11
|
+
entities: SceneEntity[],
|
|
12
|
+
getter: (e: SceneEntity) => number,
|
|
13
|
+
): number | null {
|
|
14
|
+
if (entities.length === 0) return null;
|
|
15
|
+
const first = getter(entities[0]!);
|
|
16
|
+
return entities.every((e) => Math.abs(getter(e) - first) < 0.0005) ? first : null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Return the common string value across entities, or null if mixed. */
|
|
20
|
+
export function commonStr(
|
|
21
|
+
entities: SceneEntity[],
|
|
22
|
+
getter: (e: SceneEntity) => string | undefined,
|
|
23
|
+
): string | null {
|
|
24
|
+
if (entities.length === 0) return null;
|
|
25
|
+
const first = getter(entities[0]!);
|
|
26
|
+
return entities.every((e) => getter(e) === first) ? (first ?? null) : null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Return the common boolean value across entities, or null if mixed. */
|
|
30
|
+
export function commonBool(
|
|
31
|
+
entities: SceneEntity[],
|
|
32
|
+
getter: (e: SceneEntity) => boolean,
|
|
33
|
+
): boolean | null {
|
|
34
|
+
if (entities.length === 0) return null;
|
|
35
|
+
const first = getter(entities[0]!);
|
|
36
|
+
return entities.every((e) => getter(e) === first) ? first : null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Return the common Vec3 across entities, with per-axis null for mixed. */
|
|
40
|
+
export function commonVec3(
|
|
41
|
+
entities: SceneEntity[],
|
|
42
|
+
getter: (e: SceneEntity) => [number, number, number],
|
|
43
|
+
): [number | null, number | null, number | null] {
|
|
44
|
+
return [
|
|
45
|
+
commonNum(entities, (e) => getter(e)[0]),
|
|
46
|
+
commonNum(entities, (e) => getter(e)[1]),
|
|
47
|
+
commonNum(entities, (e) => getter(e)[2]),
|
|
48
|
+
];
|
|
49
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema introspection for the component inspector.
|
|
3
|
+
*
|
|
4
|
+
* `introspectSchema` walks a Zod object schema (zod 4, the version the engine
|
|
5
|
+
* uses via `z.toJSONSchema`) and produces a flat list of `FieldDescriptor`s that
|
|
6
|
+
* the inspector renders as typed widgets. It reads the zod internal `_def`
|
|
7
|
+
* directly because zod offers no public reflection API. The walk is defensive:
|
|
8
|
+
* it also understands the zod 3 `_def.typeName` shape and falls back to a plain
|
|
9
|
+
* string/text field for anything it does not recognise.
|
|
10
|
+
*
|
|
11
|
+
* This module is pure (no React, no DOM) so it can be unit-tested in Node.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { z } from 'zod';
|
|
15
|
+
|
|
16
|
+
/** Widget kind the inspector should render for a field. */
|
|
17
|
+
export type FieldKind = 'number' | 'boolean' | 'string' | 'color' | 'vec3' | 'enum' | 'select';
|
|
18
|
+
|
|
19
|
+
/** One editable field discovered on a component schema. */
|
|
20
|
+
export interface FieldDescriptor {
|
|
21
|
+
/** Property name on the component data object. */
|
|
22
|
+
name: string;
|
|
23
|
+
/** Which widget to render. */
|
|
24
|
+
kind: FieldKind;
|
|
25
|
+
/** Default value from `.default(...)`, if any. */
|
|
26
|
+
default?: unknown;
|
|
27
|
+
/** Human description from `.describe(...)`, if any. */
|
|
28
|
+
description?: string;
|
|
29
|
+
/** Allowed string values for `enum`/`select` kinds. */
|
|
30
|
+
options?: string[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Loosely-typed view of a zod node's internal definition. */
|
|
34
|
+
interface ZodDef {
|
|
35
|
+
type?: string; // zod 4 discriminator: 'number' | 'default' | 'enum' | ...
|
|
36
|
+
typeName?: string; // zod 3 discriminator: 'ZodNumber' | 'ZodDefault' | ...
|
|
37
|
+
innerType?: AnyZod; // wrapper (default/optional/nullable/readonly) payload
|
|
38
|
+
defaultValue?: unknown | (() => unknown); // value (zod 4) or thunk (zod 3)
|
|
39
|
+
description?: string;
|
|
40
|
+
entries?: Record<string, string>; // zod 4 enum
|
|
41
|
+
values?: unknown[]; // zod 3 enum
|
|
42
|
+
items?: AnyZod[]; // tuple element schemas
|
|
43
|
+
element?: AnyZod; // array element schema
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface AnyZod {
|
|
47
|
+
_def?: ZodDef;
|
|
48
|
+
description?: string;
|
|
49
|
+
options?: unknown[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Wrapper node types that hold the real schema in `_def.innerType`. */
|
|
53
|
+
const WRAPPERS = new Set(['default', 'optional', 'nullable', 'readonly', 'nonoptional']);
|
|
54
|
+
|
|
55
|
+
/** Normalised node type string (zod 4 `type` or de-prefixed zod 3 `typeName`). */
|
|
56
|
+
function defType(node: AnyZod | undefined): string {
|
|
57
|
+
const d = node?._def;
|
|
58
|
+
if (!d) return 'unknown';
|
|
59
|
+
if (typeof d.type === 'string') return d.type;
|
|
60
|
+
if (typeof d.typeName === 'string') return d.typeName.replace(/^Zod/, '').toLowerCase();
|
|
61
|
+
return 'unknown';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
interface Unwrapped {
|
|
65
|
+
inner: AnyZod;
|
|
66
|
+
default: unknown;
|
|
67
|
+
description: string | undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Peel wrapper nodes, collecting the default value and description along the way. */
|
|
71
|
+
function unwrap(field: AnyZod): Unwrapped {
|
|
72
|
+
let cur: AnyZod = field;
|
|
73
|
+
let def: unknown;
|
|
74
|
+
let description: string | undefined = field.description ?? field._def?.description;
|
|
75
|
+
|
|
76
|
+
while (cur && WRAPPERS.has(defType(cur))) {
|
|
77
|
+
if (defType(cur) === 'default') {
|
|
78
|
+
const dv = cur._def?.defaultValue;
|
|
79
|
+
def = typeof dv === 'function' ? (dv as () => unknown)() : dv;
|
|
80
|
+
}
|
|
81
|
+
if (!description) description = cur._def?.description;
|
|
82
|
+
const inner = cur._def?.innerType;
|
|
83
|
+
if (!inner) break;
|
|
84
|
+
cur = inner;
|
|
85
|
+
}
|
|
86
|
+
if (!description) description = cur?.description ?? cur?._def?.description;
|
|
87
|
+
return { inner: cur, default: def, description };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Extract the allowed string values of a zod enum (zod 3 or 4 shape). */
|
|
91
|
+
function enumOptions(inner: AnyZod): string[] {
|
|
92
|
+
const d = inner._def;
|
|
93
|
+
if (d?.entries) return Object.values(d.entries).map(String);
|
|
94
|
+
if (Array.isArray(d?.values)) return d.values.map(String);
|
|
95
|
+
if (Array.isArray(inner.options)) return inner.options.map(String);
|
|
96
|
+
return [];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Heuristic: should this string field be edited as a color swatch? */
|
|
100
|
+
function looksLikeColor(name: string, def: unknown, description: string | undefined): boolean {
|
|
101
|
+
if (/colou?r/i.test(name)) return true;
|
|
102
|
+
if (typeof def === 'string' && /^#/.test(def)) return true;
|
|
103
|
+
if (typeof description === 'string' && /\bcolou?r\b/i.test(description)) return true;
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
interface KindResult {
|
|
108
|
+
kind: FieldKind;
|
|
109
|
+
options?: string[];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function detectKind(
|
|
113
|
+
inner: AnyZod,
|
|
114
|
+
name: string,
|
|
115
|
+
def: unknown,
|
|
116
|
+
description: string | undefined,
|
|
117
|
+
): KindResult {
|
|
118
|
+
switch (defType(inner)) {
|
|
119
|
+
case 'number':
|
|
120
|
+
case 'int':
|
|
121
|
+
return { kind: 'number' };
|
|
122
|
+
case 'boolean':
|
|
123
|
+
return { kind: 'boolean' };
|
|
124
|
+
case 'enum':
|
|
125
|
+
case 'nativeenum': {
|
|
126
|
+
const options = enumOptions(inner);
|
|
127
|
+
return options.length ? { kind: 'enum', options } : { kind: 'string' };
|
|
128
|
+
}
|
|
129
|
+
case 'string':
|
|
130
|
+
return looksLikeColor(name, def, description) ? { kind: 'color' } : { kind: 'string' };
|
|
131
|
+
case 'tuple': {
|
|
132
|
+
const items = inner._def?.items ?? [];
|
|
133
|
+
if (items.length === 3 && items.every((it) => defType(it) === 'number')) {
|
|
134
|
+
return { kind: 'vec3' };
|
|
135
|
+
}
|
|
136
|
+
return { kind: 'string' }; // unsupported tuple shape -> text/JSON fallback
|
|
137
|
+
}
|
|
138
|
+
case 'array': {
|
|
139
|
+
const el = inner._def?.element;
|
|
140
|
+
// Arrays have no fixed length in the schema; only treat a 3-number array
|
|
141
|
+
// with a length-3 default as a vec3, otherwise fall back to text.
|
|
142
|
+
if (el && defType(el) === 'number' && Array.isArray(def) && def.length === 3) {
|
|
143
|
+
return { kind: 'vec3' };
|
|
144
|
+
}
|
|
145
|
+
return { kind: 'string' };
|
|
146
|
+
}
|
|
147
|
+
default:
|
|
148
|
+
// Unknown / unsupported zod types: edit as raw text (JSON for non-strings).
|
|
149
|
+
return { kind: 'string' };
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Read the object shape from a zod object schema (zod 3 or 4). */
|
|
154
|
+
function getShape(schema: z.ZodObject<z.ZodRawShape>): Record<string, AnyZod> | undefined {
|
|
155
|
+
const anySchema = schema as unknown as {
|
|
156
|
+
shape?: Record<string, AnyZod> | (() => Record<string, AnyZod>);
|
|
157
|
+
_def?: { shape?: Record<string, AnyZod> | (() => Record<string, AnyZod>) };
|
|
158
|
+
};
|
|
159
|
+
const raw = anySchema.shape ?? anySchema._def?.shape;
|
|
160
|
+
const resolved = typeof raw === 'function' ? raw() : raw;
|
|
161
|
+
return resolved && typeof resolved === 'object' ? resolved : undefined;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Introspect a Zod object schema into a flat list of editable field descriptors.
|
|
166
|
+
* Returns `[]` for anything that is not a usable object schema.
|
|
167
|
+
*/
|
|
168
|
+
export function introspectSchema(schema: z.ZodObject<z.ZodRawShape>): FieldDescriptor[] {
|
|
169
|
+
const shape = getShape(schema);
|
|
170
|
+
if (!shape) return [];
|
|
171
|
+
|
|
172
|
+
const fields: FieldDescriptor[] = [];
|
|
173
|
+
for (const [name, field] of Object.entries(shape)) {
|
|
174
|
+
if (!field || typeof field !== 'object') continue;
|
|
175
|
+
const { inner, default: def, description } = unwrap(field);
|
|
176
|
+
const { kind, options } = detectKind(inner, name, def, description);
|
|
177
|
+
const fd: FieldDescriptor = { name, kind };
|
|
178
|
+
if (def !== undefined) fd.default = def;
|
|
179
|
+
if (description) fd.description = description;
|
|
180
|
+
if (options?.length) fd.options = options;
|
|
181
|
+
fields.push(fd);
|
|
182
|
+
}
|
|
183
|
+
return fields;
|
|
184
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { useInteractiveEditScope } from '../../interactive-edit-scope';
|
|
3
|
+
|
|
4
|
+
interface ColorInputProps {
|
|
5
|
+
value: string | null;
|
|
6
|
+
onChange: (value: string) => void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function ColorInput({ value, onChange }: ColorInputProps) {
|
|
10
|
+
// Local draft state for the text input — only commits on blur or Enter.
|
|
11
|
+
const [draft, setDraft] = useState(value ?? '');
|
|
12
|
+
const editScope = useInteractiveEditScope();
|
|
13
|
+
|
|
14
|
+
// Sync draft when the external value changes (e.g. undo, selection change).
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
setDraft(value ?? '');
|
|
17
|
+
}, [value]);
|
|
18
|
+
|
|
19
|
+
const commit = () => {
|
|
20
|
+
const trimmed = draft.trim();
|
|
21
|
+
if (trimmed && trimmed !== value) {
|
|
22
|
+
onChange(trimmed);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
if (value === null) {
|
|
27
|
+
return (
|
|
28
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
|
|
29
|
+
<div
|
|
30
|
+
style={{
|
|
31
|
+
width: 28,
|
|
32
|
+
height: 22,
|
|
33
|
+
border: '1px solid #333',
|
|
34
|
+
borderRadius: 2,
|
|
35
|
+
background:
|
|
36
|
+
'linear-gradient(45deg, #666 25%, transparent 25%, transparent 75%, #666 75%), linear-gradient(45deg, #666 25%, transparent 25%, transparent 75%, #666 75%)',
|
|
37
|
+
backgroundSize: '8px 8px',
|
|
38
|
+
backgroundPosition: '0 0, 4px 4px',
|
|
39
|
+
}}
|
|
40
|
+
/>
|
|
41
|
+
<input
|
|
42
|
+
placeholder="mixed"
|
|
43
|
+
onChange={(e) => {
|
|
44
|
+
if (e.target.value) onChange(e.target.value);
|
|
45
|
+
}}
|
|
46
|
+
style={{
|
|
47
|
+
flex: 1,
|
|
48
|
+
background: '#1e1e1e',
|
|
49
|
+
color: '#666',
|
|
50
|
+
fontStyle: 'italic',
|
|
51
|
+
border: '1px solid #333',
|
|
52
|
+
borderRadius: 2,
|
|
53
|
+
padding: '2px 4px',
|
|
54
|
+
fontSize: 12,
|
|
55
|
+
}}
|
|
56
|
+
/>
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
|
|
63
|
+
<input
|
|
64
|
+
type="color"
|
|
65
|
+
value={value}
|
|
66
|
+
// The native picker fires onChange continuously while dragging. Coalesce
|
|
67
|
+
// the whole drag into one undo step (beginInteractiveEdit is idempotent);
|
|
68
|
+
// the gesture closes on blur when the picker is dismissed.
|
|
69
|
+
onChange={(e) => {
|
|
70
|
+
editScope?.begin();
|
|
71
|
+
onChange(e.target.value);
|
|
72
|
+
}}
|
|
73
|
+
onBlur={() => editScope?.end()}
|
|
74
|
+
style={{
|
|
75
|
+
width: 28,
|
|
76
|
+
height: 22,
|
|
77
|
+
border: '1px solid #333',
|
|
78
|
+
borderRadius: 2,
|
|
79
|
+
padding: 0,
|
|
80
|
+
cursor: 'pointer',
|
|
81
|
+
background: 'transparent',
|
|
82
|
+
}}
|
|
83
|
+
/>
|
|
84
|
+
<input
|
|
85
|
+
value={draft}
|
|
86
|
+
onChange={(e) => setDraft(e.target.value)}
|
|
87
|
+
onBlur={commit}
|
|
88
|
+
onKeyDown={(e) => {
|
|
89
|
+
if (e.key === 'Enter') {
|
|
90
|
+
commit();
|
|
91
|
+
(e.target as HTMLInputElement).blur();
|
|
92
|
+
} else if (e.key === 'Escape') {
|
|
93
|
+
setDraft(value);
|
|
94
|
+
(e.target as HTMLInputElement).blur();
|
|
95
|
+
}
|
|
96
|
+
}}
|
|
97
|
+
style={{
|
|
98
|
+
flex: 1,
|
|
99
|
+
background: '#1e1e1e',
|
|
100
|
+
color: '#c5c8ce',
|
|
101
|
+
border: '1px solid #333',
|
|
102
|
+
borderRadius: 2,
|
|
103
|
+
padding: '2px 4px',
|
|
104
|
+
fontSize: 12,
|
|
105
|
+
}}
|
|
106
|
+
/>
|
|
107
|
+
</div>
|
|
108
|
+
);
|
|
109
|
+
}
|