@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,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"properties": {
|
|
5
|
+
"boxSpinSpeed": {
|
|
6
|
+
"default": 0.8,
|
|
7
|
+
"description": "Demo box spin speed, radians/sec — edit tuning.data.json while playing to see live tuning",
|
|
8
|
+
"type": "number",
|
|
9
|
+
"minimum": -5,
|
|
10
|
+
"maximum": 5
|
|
11
|
+
},
|
|
12
|
+
"boxColor": {
|
|
13
|
+
"default": "#4488ff",
|
|
14
|
+
"description": "Demo box color (hex)",
|
|
15
|
+
"type": "string"
|
|
16
|
+
},
|
|
17
|
+
"$schema": {
|
|
18
|
+
"type": "string"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tuning schema — the engineer-owned half of a data asset
|
|
3
|
+
* (docs/DATA-TOOLS-DESIGN.md §2.1).
|
|
4
|
+
*
|
|
5
|
+
* Every data asset is a trio in this folder:
|
|
6
|
+
*
|
|
7
|
+
* tuning.schema.ts ← this file: the Zod schema. Code owns the shape.
|
|
8
|
+
* tuning.schema.json ← EMITTED artifact (`npm run emit-schemas`) — gives
|
|
9
|
+
* VS Code validation/autocomplete on the data file.
|
|
10
|
+
* tuning.data.json ← the VALUES. Designer/editor/agent-owned; its first
|
|
11
|
+
* line `"$schema": "./tuning.schema.json"` wires the
|
|
12
|
+
* two together with no dev server running.
|
|
13
|
+
*
|
|
14
|
+
* Annotate every field with `.min/.max/.default/.describe` — the same
|
|
15
|
+
* annotations `GameComponent.static schema` uses. They document the file,
|
|
16
|
+
* constrain edits, and (W2+) become editor widgets.
|
|
17
|
+
*
|
|
18
|
+
* Rule of thumb (project CLAUDE.md carries the doctrine): if a designer
|
|
19
|
+
* should be able to change it, it belongs here — NOT as a constant in a
|
|
20
|
+
* component. Per-entity placement config stays on component schemas.
|
|
21
|
+
*/
|
|
22
|
+
import { z } from 'zod';
|
|
23
|
+
|
|
24
|
+
export const TuningSchema = z.object({
|
|
25
|
+
boxSpinSpeed: z
|
|
26
|
+
.number()
|
|
27
|
+
.min(-5)
|
|
28
|
+
.max(5)
|
|
29
|
+
.default(0.8)
|
|
30
|
+
.describe('Demo box spin speed, radians/sec — edit tuning.data.json while playing to see live tuning'),
|
|
31
|
+
boxColor: z.string().default('#4488ff').describe('Demo box color (hex)'),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
export type Tuning = z.infer<typeof TuningSchema>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The live handle for the `tuning` data asset — the blessed consumption shape
|
|
3
|
+
* (docs/DATA-TOOLS-DESIGN.md §2.3, engine `@engine/data/data-asset`).
|
|
4
|
+
*
|
|
5
|
+
* Three lines is the whole wiring:
|
|
6
|
+
* 1. statically import the JSON (bundler-friendly, works in dev and build),
|
|
7
|
+
* 2. `defineData` — Zod parse-on-load (fails loud naming file + field),
|
|
8
|
+
* returning `get()`/`subscribe()`,
|
|
9
|
+
* 3. the `import.meta.hot` line — Vite HMR needs the literal dep path in
|
|
10
|
+
* THIS module, so an edit to tuning.data.json (VS Code, an agent, the
|
|
11
|
+
* future editor Data tab) hot-swaps values into the RUNNING game, no
|
|
12
|
+
* remount, no reload.
|
|
13
|
+
*
|
|
14
|
+
* Consume with `tuning.get()` INSIDE your update loop every frame (never
|
|
15
|
+
* cache fields across frames, or live tuning silently stops working for that
|
|
16
|
+
* field) — see `src/scripts/components/data-spinner.ts` for the worked use.
|
|
17
|
+
*/
|
|
18
|
+
import { defineData } from '@engine/data/data-asset';
|
|
19
|
+
import raw from './tuning.data.json';
|
|
20
|
+
import { TuningSchema } from './tuning.schema';
|
|
21
|
+
|
|
22
|
+
export const tuning = defineData(TuningSchema, raw, 'src/data/tuning.data.json');
|
|
23
|
+
|
|
24
|
+
import.meta.hot?.accept('./tuning.data.json', (m) => tuning.hotSwap(m?.['default']));
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standalone entry point.
|
|
3
|
+
*
|
|
4
|
+
* One `mountGameFromManifest` call (E4, docs/unified-world-editor/
|
|
5
|
+
* 27-visual-react-editing.md §7 E4 — the S-D composer): mounts every world
|
|
6
|
+
* `vgai.game.json` declares — just the one `default`-adapter threejs world
|
|
7
|
+
* for a plain scaffold — through the engine's `registerWorldKind` registry,
|
|
8
|
+
* no example switcher, no HUD dropdown, no discovery glob (that in-app
|
|
9
|
+
* gallery machinery has been deleted), and no hand-built `WorldMountSpec[]`.
|
|
10
|
+
* The default scene is the simple box from `src/scripts/main.ts`.
|
|
11
|
+
*
|
|
12
|
+
* `registerWorldKind('threejs', defaultThreeKindFactory)` opts into the
|
|
13
|
+
* engine's stock threejs resolution (entry module's `adapter`/`setup` export,
|
|
14
|
+
* or a scene-driven world) — see `mount-game.ts`'s doc comment for why this
|
|
15
|
+
* is a plain function the CALLER registers rather than something the engine
|
|
16
|
+
* wires up for you (pixijs/react worlds need the SAME one-line registration
|
|
17
|
+
* with your own factory; see `examples/tri-world/src/main.ts`).
|
|
18
|
+
*
|
|
19
|
+
* `host.loadEntryModule` resolves a manifest-declared `entry` path to an
|
|
20
|
+
* ALREADY-IMPORTED module — a static lookup table keyed by that exact path
|
|
21
|
+
* string, not a runtime `import(path)` (a static bundler can't resolve a
|
|
22
|
+
* computed specifier for a real `dist/` build; see `mount-game.ts`'s
|
|
23
|
+
* `ManifestHost.loadEntryModule` doc comment).
|
|
24
|
+
*
|
|
25
|
+
* No Scene UI setup here (C1, docs/AI-NATIVE-AUTHORING-IMPLEMENTATION-SPEC.md
|
|
26
|
+
* §9): a plain scaffold's default game UI is ordinary React, mounted via
|
|
27
|
+
* `mountUI(ctx.uiContainer, ...)` from `./scripts/main.ts`'s `setup()` (see
|
|
28
|
+
* `./ui/mount-default-hud.tsx`) — no `@vgai/scene-ui` import, no `.vscn`
|
|
29
|
+
* `ui` field. A project that opts INTO the (still-supported, being retired
|
|
30
|
+
* under C5) Scene UI data-language authoring path can wire
|
|
31
|
+
* `registerSceneUIRenderer()` back in itself; the template no longer does
|
|
32
|
+
* it by default.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import type { ManifestHost } from '@engine/runtime/mount-game';
|
|
36
|
+
import {
|
|
37
|
+
defaultThreeKindFactory,
|
|
38
|
+
isWorldKindRegistered,
|
|
39
|
+
mountGameFromManifest,
|
|
40
|
+
registerWorldKind,
|
|
41
|
+
} from '@engine/runtime/mount-game';
|
|
42
|
+
import manifest from '../vgai.game.json';
|
|
43
|
+
import * as mainScript from './scripts/main';
|
|
44
|
+
|
|
45
|
+
const DEFAULT_SIGNALING_URL = 'https://vgai-p2p-colyseus.aaron-0ed.workers.dev';
|
|
46
|
+
|
|
47
|
+
// The manifest's `entry`-declaring worlds' modules, statically imported and
|
|
48
|
+
// keyed by the EXACT `entry` path each world declares in `vgai.game.json` —
|
|
49
|
+
// see this file's header comment for why this is a static table, not a
|
|
50
|
+
// dynamic `import(path)`. Add an entry here if you add another entry-driven
|
|
51
|
+
// world to the manifest.
|
|
52
|
+
const ENTRY_MODULES: Readonly<Record<string, unknown>> = {
|
|
53
|
+
'src/scripts/main.ts': mainScript,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// Reopen fix (E4.R1): `registerWorldKind` throws on a double-registration of
|
|
57
|
+
// the same kind (an accidental duplicate is far more likely than an
|
|
58
|
+
// intentional swap — see its own doc comment) — but this module's top level
|
|
59
|
+
// re-runs whenever a hot-accept boundary re-executes it WITHOUT a full page
|
|
60
|
+
// reload, which would otherwise turn a live-reload edit into a hard crash.
|
|
61
|
+
// Guard so a re-execution is a no-op instead of bricking the reload.
|
|
62
|
+
if (!isWorldKindRegistered('threejs')) {
|
|
63
|
+
registerWorldKind('threejs', defaultThreeKindFactory);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function main() {
|
|
67
|
+
try {
|
|
68
|
+
const res = await fetch('project.json');
|
|
69
|
+
if (res.ok) {
|
|
70
|
+
const cfg = await res.json();
|
|
71
|
+
if (cfg.name) document.title = cfg.name;
|
|
72
|
+
}
|
|
73
|
+
} catch {
|
|
74
|
+
/* keep default title */
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
await configureNetworkModeFromUrl();
|
|
78
|
+
|
|
79
|
+
const container = document.getElementById('game-canvas') as HTMLElement;
|
|
80
|
+
const host: ManifestHost = {
|
|
81
|
+
container,
|
|
82
|
+
async loadEntryModule(path) {
|
|
83
|
+
const mod = ENTRY_MODULES[path];
|
|
84
|
+
if (!mod) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
`main.ts: unknown entry module "${path}" declared by vgai.game.json — add it to ` +
|
|
87
|
+
'ENTRY_MODULES above.',
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
return mod;
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
const session = await mountGameFromManifest(manifest, host);
|
|
94
|
+
session.resize(window.innerWidth, window.innerHeight);
|
|
95
|
+
|
|
96
|
+
window.addEventListener('resize', () => {
|
|
97
|
+
session.resize(window.innerWidth, window.innerHeight);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
main().catch(console.error);
|
|
102
|
+
|
|
103
|
+
async function configureNetworkModeFromUrl(): Promise<void> {
|
|
104
|
+
const params = new URLSearchParams(window.location.search);
|
|
105
|
+
const mode = params.get('net');
|
|
106
|
+
if (mode !== 'p2p-host' && mode !== 'p2p-join') return;
|
|
107
|
+
|
|
108
|
+
const sdk = (await import('@colyseus/sdk')) as unknown as {
|
|
109
|
+
configureP2PColyseusClient?: (options: {
|
|
110
|
+
mode:
|
|
111
|
+
| {
|
|
112
|
+
kind: 'p2p-host';
|
|
113
|
+
signalingUrl: string;
|
|
114
|
+
iceServers: RTCIceServer[];
|
|
115
|
+
forceRelay?: boolean;
|
|
116
|
+
}
|
|
117
|
+
| {
|
|
118
|
+
kind: 'p2p-join';
|
|
119
|
+
signalingUrl: string;
|
|
120
|
+
roomId?: string;
|
|
121
|
+
iceServers: RTCIceServer[];
|
|
122
|
+
forceRelay?: boolean;
|
|
123
|
+
};
|
|
124
|
+
rooms?: Record<string, unknown>;
|
|
125
|
+
maxLocalClientsPerRoom?: number;
|
|
126
|
+
}) => void;
|
|
127
|
+
};
|
|
128
|
+
if (!sdk.configureP2PColyseusClient) return;
|
|
129
|
+
|
|
130
|
+
const signalingUrl = params.get('signal') ?? DEFAULT_SIGNALING_URL;
|
|
131
|
+
const forceRelay = params.get('relay') === '1';
|
|
132
|
+
const iceServers: RTCIceServer[] = [{ urls: 'stun:stun.l.google.com:19302' }];
|
|
133
|
+
|
|
134
|
+
if (mode === 'p2p-host') {
|
|
135
|
+
const { rooms } = (await import('../server/rooms')) as {
|
|
136
|
+
rooms: Array<{ name: string; handler: unknown }>;
|
|
137
|
+
};
|
|
138
|
+
sdk.configureP2PColyseusClient({
|
|
139
|
+
mode: { kind: 'p2p-host', signalingUrl, iceServers, forceRelay },
|
|
140
|
+
rooms: Object.fromEntries(rooms.map((room) => [room.name, room.handler])),
|
|
141
|
+
...(forceRelay ? { maxLocalClientsPerRoom: 1 } : {}),
|
|
142
|
+
});
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const roomId = params.get('roomId') ?? params.get('room');
|
|
147
|
+
sdk.configureP2PColyseusClient({
|
|
148
|
+
mode: {
|
|
149
|
+
kind: 'p2p-join',
|
|
150
|
+
signalingUrl,
|
|
151
|
+
...(roomId !== null ? { roomId } : {}),
|
|
152
|
+
iceServers,
|
|
153
|
+
forceRelay,
|
|
154
|
+
},
|
|
155
|
+
maxLocalClientsPerRoom: 1,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { GameComponent } from '@engine/ecs/game-component';
|
|
2
|
+
import type * as THREE from 'three';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { tuning } from '../../data/tuning';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The template's worked data-asset consumer (docs/DATA-TOOLS-DESIGN.md §6.2
|
|
8
|
+
* — the pattern ships working at birth): spins the default scene's Box at
|
|
9
|
+
* `boxSpinSpeed` and tints it `boxColor`, both read from
|
|
10
|
+
* `src/data/tuning.data.json` through the `tuning` handle.
|
|
11
|
+
*
|
|
12
|
+
* Try it: press ▶, then edit `src/data/tuning.data.json` and save — the box
|
|
13
|
+
* changes speed/color LIVE (Vite HMR, no reload; see src/data/tuning.ts).
|
|
14
|
+
*
|
|
15
|
+
* This component demonstrates BOTH halves of the data-vs-config split
|
|
16
|
+
* (§3.3/§6.2): `boxSpinSpeed`/`boxColor` are PROJECT-WIDE feel, so they live
|
|
17
|
+
* in the data asset and read through `tuning.get()` (every frame — cache a
|
|
18
|
+
* field across frames and live tuning silently stops reaching it). `spinScale`
|
|
19
|
+
* is THIS entity's own knob — per-entity config authored on the component
|
|
20
|
+
* itself via `static schema` (the same shape SceneCamera uses), read live by
|
|
21
|
+
* an inspector tool through `useSelection()` (see `spin.tool.tsx`).
|
|
22
|
+
*/
|
|
23
|
+
export class DataSpinner extends GameComponent {
|
|
24
|
+
static schema = z.object({
|
|
25
|
+
spinScale: z
|
|
26
|
+
.number()
|
|
27
|
+
.default(1)
|
|
28
|
+
.describe(
|
|
29
|
+
'Per-entity multiplier on the shared boxSpinSpeed — the project-wide feel stays in ' +
|
|
30
|
+
"src/data/tuning.data.json; this is the one knob that is THIS entity's own.",
|
|
31
|
+
),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
spinScale = 1;
|
|
35
|
+
|
|
36
|
+
update(dt: number) {
|
|
37
|
+
const t = tuning.get();
|
|
38
|
+
this.object3D.rotation.y += t.boxSpinSpeed * this.spinScale * dt;
|
|
39
|
+
|
|
40
|
+
const material = (this.object3D as THREE.Mesh).material as
|
|
41
|
+
| THREE.MeshStandardMaterial
|
|
42
|
+
| undefined;
|
|
43
|
+
if (material?.color) material.color.set(t.boxColor);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { GameComponent } from '@engine/ecs/game-component';
|
|
3
|
+
import type { GameContext } from '@engine/runtime/types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Positions the runtime camera on init.
|
|
7
|
+
*
|
|
8
|
+
* Configure x/y/z (camera position) and lookAtX/lookAtY/lookAtZ (look-at
|
|
9
|
+
* target) in the scene JSON. Runs in preRender phase so it can adjust the
|
|
10
|
+
* camera before rendering. One-time framing in init() only — this is the
|
|
11
|
+
* blessed "static scene setup" camera-via-component shape (not a per-frame
|
|
12
|
+
* follow), same category as the one-time `ctx.camera.position.set(...)` calls
|
|
13
|
+
* example `setup()` functions make directly.
|
|
14
|
+
*/
|
|
15
|
+
export class SceneCamera extends GameComponent {
|
|
16
|
+
static phase = 'preRender' as const;
|
|
17
|
+
|
|
18
|
+
static schema = z.object({
|
|
19
|
+
x: z.number().default(3).describe('Camera position X'),
|
|
20
|
+
y: z.number().default(3).describe('Camera position Y'),
|
|
21
|
+
z: z.number().default(3).describe('Camera position Z'),
|
|
22
|
+
lookAtX: z.number().default(0).describe('Look-at target X'),
|
|
23
|
+
lookAtY: z.number().default(0.5).describe('Look-at target Y'),
|
|
24
|
+
lookAtZ: z.number().default(0).describe('Look-at target Z'),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
x = 3;
|
|
28
|
+
y = 3;
|
|
29
|
+
z = 3;
|
|
30
|
+
lookAtX = 0;
|
|
31
|
+
lookAtY = 0.5;
|
|
32
|
+
lookAtZ = 0;
|
|
33
|
+
|
|
34
|
+
init(ctx: GameContext) {
|
|
35
|
+
ctx.camera.position.set(this.x, this.y, this.z);
|
|
36
|
+
ctx.camera.lookAt(this.lookAtX, this.lookAtY, this.lookAtZ);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
update() {}
|
|
40
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default game script.
|
|
3
|
+
*
|
|
4
|
+
* When launched from the editor, loads the scene the user is editing.
|
|
5
|
+
* When running standalone, loads the default scene from disk.
|
|
6
|
+
*
|
|
7
|
+
* If the scene matches a known example, delegates to that example's setup
|
|
8
|
+
* function so gameplay systems (movement, camera, AI, etc.) are registered.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { GameAdapter } from '@engine/adapter';
|
|
12
|
+
import { fromSetup } from '@engine/adapter';
|
|
13
|
+
import type { EditorPreview, GameCleanup, GameContext } from '@engine/runtime/types';
|
|
14
|
+
import { loadScene, loadSceneFromData } from '@engine/scene/scene-loader';
|
|
15
|
+
import { applyScenePostProcessing } from '@engine/setup/setup-renderer';
|
|
16
|
+
import { mountDefaultHud } from '../ui/mount-default-hud';
|
|
17
|
+
import { componentRegistry, gameComponents } from './registry';
|
|
18
|
+
|
|
19
|
+
const DEFAULT_SCENE = 'scenes/default.vscn.json';
|
|
20
|
+
|
|
21
|
+
/** Exported for true HMR — play-mode swaps prototypes on live instances. */
|
|
22
|
+
export const components = gameComponents;
|
|
23
|
+
|
|
24
|
+
// HMR boundary — play-mode.ts handles script hot-reloading via custom events.
|
|
25
|
+
// This prevents Vite from triggering full page reloads when component files change.
|
|
26
|
+
if (import.meta.hot) {
|
|
27
|
+
import.meta.hot.accept();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function setup(ctx: GameContext, editor?: EditorPreview): Promise<GameCleanup> {
|
|
31
|
+
// Generic fallback: load the scene with the full component registry
|
|
32
|
+
// so ECS components are applied, but no gameplay systems are registered.
|
|
33
|
+
const loadOpts = {
|
|
34
|
+
scene: ctx.scene,
|
|
35
|
+
rapierWorld: ctx.rapierWorld,
|
|
36
|
+
rapier: ctx.rapier,
|
|
37
|
+
physics: ctx.physics,
|
|
38
|
+
audioListener: ctx.audio.listener,
|
|
39
|
+
componentRegistry,
|
|
40
|
+
componentManager: ctx.components,
|
|
41
|
+
// Required for runtime fidelity (P1.4): particles need the batched renderer,
|
|
42
|
+
// skybox/IBL needs the WebGLRenderer for PMREM.
|
|
43
|
+
particleRenderer: ctx.particles.batchedRenderer,
|
|
44
|
+
renderer: ctx.composer.getRenderer(),
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Set the INITIAL camera viewpoint BEFORE loading the scene, so that any
|
|
48
|
+
// scene-defined camera or SceneCamera component (which run during loadScene)
|
|
49
|
+
// takes precedence over the editor viewport camera. Generic scenes with no
|
|
50
|
+
// camera component keep this viewpoint.
|
|
51
|
+
if (editor?.viewportCamera) {
|
|
52
|
+
ctx.camera.position.fromArray(editor.viewportCamera.position);
|
|
53
|
+
ctx.camera.quaternion.fromArray(editor.viewportCamera.quaternion);
|
|
54
|
+
} else {
|
|
55
|
+
ctx.camera.position.set(3, 3, 3);
|
|
56
|
+
ctx.camera.lookAt(0, 0.5, 0);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const sceneInstance = editor
|
|
60
|
+
? await loadSceneFromData(editor.sceneData, loadOpts)
|
|
61
|
+
: await loadScene(DEFAULT_SCENE, loadOpts);
|
|
62
|
+
|
|
63
|
+
// Apply scene-defined post-processing (bloom, vignette, DoF, …) if present.
|
|
64
|
+
if (sceneInstance.environment?.postProcessing) {
|
|
65
|
+
applyScenePostProcessing(
|
|
66
|
+
ctx.composer,
|
|
67
|
+
ctx.composer.getRenderer(),
|
|
68
|
+
ctx.scene,
|
|
69
|
+
ctx.camera,
|
|
70
|
+
sceneInstance.environment,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Tick scene animations (GLTF mixers, etc.)
|
|
75
|
+
ctx.systems.add('postPhysics', (dt) => sceneInstance.update(dt));
|
|
76
|
+
|
|
77
|
+
// Default game HUD (C1, docs/AI-NATIVE-AUTHORING-IMPLEMENTATION-SPEC.md §9):
|
|
78
|
+
// ordinary React mounted through the existing `mountUI(ctx.uiContainer, ...)`
|
|
79
|
+
// overlay seam — see `../ui/DefaultHud.tsx`. Delete this line (and the HUD
|
|
80
|
+
// files under `../ui/`) if your game doesn't want it; it is not required by
|
|
81
|
+
// anything else in the template.
|
|
82
|
+
const unmountHud = mountDefaultHud(ctx.uiContainer);
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
dispose() {
|
|
86
|
+
unmountHud();
|
|
87
|
+
sceneInstance.dispose();
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** This example as a {@link GameAdapter} — first-party is one implementer of
|
|
93
|
+
* the same interface an external game implements (no change to setup's body). */
|
|
94
|
+
export const adapter: GameAdapter = fromSetup('default', setup);
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project component registry.
|
|
3
|
+
*
|
|
4
|
+
* Every component used in .vscn.json files must be registered here. The scene
|
|
5
|
+
* loader and validator use this to map component names to GameComponent classes.
|
|
6
|
+
*
|
|
7
|
+
* This file is copied VERBATIM into every scaffolded project (it lives inside
|
|
8
|
+
* `packages/editor/template/`) — it must never import anything outside the
|
|
9
|
+
* template's own tree (no reaching into repo-root `examples/<id>/`, which
|
|
10
|
+
* doesn't exist once copied elsewhere; a real scaffold's own
|
|
11
|
+
* `scaffold-typecheck` gate catches this).
|
|
12
|
+
*
|
|
13
|
+
* When you delete an example, remove its imports and entries below.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { GameComponentClass } from '@engine/ecs/game-component';
|
|
17
|
+
import type { ComponentRegistry } from '@engine/scene/component-registry';
|
|
18
|
+
import { DataSpinner } from './components/data-spinner';
|
|
19
|
+
import { SceneCamera } from './components/scene-camera';
|
|
20
|
+
|
|
21
|
+
export const componentRegistry = {
|
|
22
|
+
// GameComponent classes (OOP components with true HMR)
|
|
23
|
+
DataSpinner,
|
|
24
|
+
SceneCamera,
|
|
25
|
+
} satisfies ComponentRegistry;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Union of all registered component names — discoverable + type-checked rather
|
|
29
|
+
* than stringly-typed. Derived from the registry so it can never drift.
|
|
30
|
+
*/
|
|
31
|
+
export type ComponentName = keyof typeof componentRegistry;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Typed accessor for a registered component class. Prefer this over indexing
|
|
35
|
+
* `componentRegistry` directly: `name` is constrained to `ComponentName`, so
|
|
36
|
+
* typos are caught at compile time and editors can autocomplete the options.
|
|
37
|
+
*/
|
|
38
|
+
export function getComponent(name: ComponentName): GameComponentClass {
|
|
39
|
+
return componentRegistry[name];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** GameComponent classes for true HMR. Keyed by class name. */
|
|
43
|
+
export const gameComponents: Record<string, GameComponentClass> = {
|
|
44
|
+
DataSpinner,
|
|
45
|
+
SceneCamera,
|
|
46
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# src/tools — project tools
|
|
2
|
+
|
|
3
|
+
Editor-docked dev/tuning panels live here: `<name>.tool.tsx` exporting
|
|
4
|
+
`tool = defineTool({...})` plus a default React component — the editor
|
|
5
|
+
discovers them automatically. Two placements: `'dock'` (⚙ tab after the
|
|
6
|
+
built-in bottom tabs — `example.tool.tsx`, the shipped worked example) and
|
|
7
|
+
`'inspector'` (⚙ section in the Inspector when its `match(node, adapter)`
|
|
8
|
+
accepts the selection, e.g. `adapter.components?.list(node.id)` —
|
|
9
|
+
`spin.tool.tsx`). Inside an inspector tool, `useSelection()` (from
|
|
10
|
+
`@engine/react/use-selection`) returns the selected node, its attached
|
|
11
|
+
components, and each component's own authored config, live. Build panels
|
|
12
|
+
from `@editor/widgets` (the editor's own widget kit + tokens) so they look
|
|
13
|
+
native. Editor-only: `npm run build` never bundles this folder — see
|
|
14
|
+
`docs/DATA-TOOLS-DESIGN.md` §3–§4 in the engine checkout.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example dock tool — the worked project-tool pattern
|
|
3
|
+
* (docs/DATA-TOOLS-DESIGN.md §3, in the engine checkout). Slightly
|
|
4
|
+
* over-commented on purpose: this file is read far more than it runs.
|
|
5
|
+
*
|
|
6
|
+
* A tool is ONE file, `src/tools/<name>.tool.tsx`, exporting exactly two
|
|
7
|
+
* things:
|
|
8
|
+
* 1. `tool` — metadata via `defineTool` (id, tab title, placement),
|
|
9
|
+
* 2. a default-exported plain React component.
|
|
10
|
+
* The editor discovers it automatically (folder scan — no registration
|
|
11
|
+
* anywhere; delete the file and the tab is gone) and docks it as a ⚙ tab
|
|
12
|
+
* after the built-in bottom tabs. It stays interactive during play mode, so
|
|
13
|
+
* you tune while the game runs.
|
|
14
|
+
*
|
|
15
|
+
* Tools are EDITOR-ONLY dev surfaces: nothing in game code imports
|
|
16
|
+
* `src/tools/`, so `npm run build` never bundles this file — cheats and
|
|
17
|
+
* tuning panels can't leak into a shipped game. Keep game logic out of here.
|
|
18
|
+
*
|
|
19
|
+
* This example closes the full live-tuning loop on the `tuning` data asset
|
|
20
|
+
* (`src/data/` — the trio this project ships working):
|
|
21
|
+
*
|
|
22
|
+
* slider → writeData() → src/data/tuning.data.json written on disk
|
|
23
|
+
* → Vite HMR → tuning.hotSwap() → useData re-renders this panel
|
|
24
|
+
* → and the RUNNING game (DataSpinner reads tuning.get() per frame)
|
|
25
|
+
* picks the value up the same instant, no reload.
|
|
26
|
+
*
|
|
27
|
+
* That's the whole architecture: the file is the document; the editor, this
|
|
28
|
+
* panel, VS Code, and the game are all just views of it.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { useData, writeData } from '@engine/react/use-data';
|
|
32
|
+
import { defineTool } from '@engine/tools/define-tool';
|
|
33
|
+
// A tool imports the project's OWN data handles — the same ones the game
|
|
34
|
+
// reads. One handle, every surface agrees on the values.
|
|
35
|
+
import { tuning } from '../data/tuning';
|
|
36
|
+
|
|
37
|
+
/** The editor reads this export to place the tool (docs/DATA-TOOLS-DESIGN.md
|
|
38
|
+
* §3.1). `id` keys the tab (test id `bottom-tab-tool-tuning`); `title` is
|
|
39
|
+
* the label; `'dock'` is the project-global bottom-tab placement (for the
|
|
40
|
+
* selection-scoped `'inspector'` placement, see `spin.tool.tsx`). */
|
|
41
|
+
export const tool = defineTool({
|
|
42
|
+
id: 'tuning',
|
|
43
|
+
title: 'Tuning',
|
|
44
|
+
placement: 'dock',
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
/** The panel itself: plain React, inline styles (editor chrome around it is
|
|
48
|
+
* editor-styled; what's inside is yours). No providers needed. */
|
|
49
|
+
export default function TuningTool() {
|
|
50
|
+
// useData = live values: re-renders once per accepted edit of the data
|
|
51
|
+
// file, whoever made it (this slider, the editor's Data tab, VS Code, an
|
|
52
|
+
// agent). Read it every render — never copy fields into component state.
|
|
53
|
+
const values = useData(tuning);
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<div style={{ padding: 12, fontSize: 12, fontFamily: 'ui-monospace, monospace' }}>
|
|
57
|
+
{/* Live readout — proves the round trip: drag the slider (or edit the
|
|
58
|
+
JSON anywhere else) and watch these values follow the file. */}
|
|
59
|
+
<div style={{ marginBottom: 10, color: '#8c8c8c' }}>
|
|
60
|
+
src/data/tuning.data.json (live):
|
|
61
|
+
<pre style={{ margin: '4px 0 0', color: 'inherit' }}>{JSON.stringify(values, null, 2)}</pre>
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<label style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
65
|
+
boxSpinSpeed
|
|
66
|
+
<input
|
|
67
|
+
type="range"
|
|
68
|
+
min={-5}
|
|
69
|
+
max={5}
|
|
70
|
+
step={0.1}
|
|
71
|
+
data-testid="tool-slider-boxSpinSpeed"
|
|
72
|
+
// Uncontrolled (defaultValue) on purpose: the authoritative value
|
|
73
|
+
// round-trips through the FILE (write → HMR → useData), which can
|
|
74
|
+
// lag a beat behind the pointer — controlling the thumb from that
|
|
75
|
+
// would make it fight the drag. The readout above is the live view.
|
|
76
|
+
defaultValue={values.boxSpinSpeed}
|
|
77
|
+
// writeData writes the WHOLE file through the editor dev server
|
|
78
|
+
// (only src/data/**/*.data.json is accepted): spread the current
|
|
79
|
+
// values, replace one field. It's async — fire and forget here.
|
|
80
|
+
onChange={(e) =>
|
|
81
|
+
void writeData('src/data/tuning.data.json', {
|
|
82
|
+
...values,
|
|
83
|
+
boxSpinSpeed: Number(e.target.value),
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
/>
|
|
87
|
+
<span>{values.boxSpinSpeed}</span>
|
|
88
|
+
</label>
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example INSPECTOR tool — the selection-scoped project-tool pattern
|
|
3
|
+
* (docs/DATA-TOOLS-DESIGN.md §3, W3/W3.1, in the engine checkout). Slightly
|
|
4
|
+
* over-commented on purpose: this file is read far more than it runs.
|
|
5
|
+
*
|
|
6
|
+
* Same one-file physics as the dock example (`example.tool.tsx`), different
|
|
7
|
+
* placement: `placement: 'inspector'` + a `match` predicate. Instead of a
|
|
8
|
+
* bottom-dock tab, the editor renders this as a ⚙-marked SECTION in the
|
|
9
|
+
* Inspector rail whenever the current selection satisfies `match` — select
|
|
10
|
+
* the Box (it has a `DataSpinner`) and it appears under the built-in
|
|
11
|
+
* sections; select the Ground (a mesh, but no `DataSpinner`) or a light and
|
|
12
|
+
* it's gone. It stays live during play mode, so you tune while the game runs.
|
|
13
|
+
*
|
|
14
|
+
* Three things this file demonstrates beyond the dock example:
|
|
15
|
+
*
|
|
16
|
+
* 1. `match(node, adapter)` — the selection predicate, now COMPONENT-based
|
|
17
|
+
* rather than shape-based: it asks `adapter.components?.list(node.id)`
|
|
18
|
+
* for the attached component types instead of guessing from `node.kind`.
|
|
19
|
+
* Keep it cheap and pure, it runs on every inspector render.
|
|
20
|
+
* 2. `useSelection()` (`@engine/react/use-selection`, W3.1) — inside the
|
|
21
|
+
* component, this returns the selected node PLUS its attached components
|
|
22
|
+
* and each one's own authored config (`ComponentsProvider.config`). Here
|
|
23
|
+
* it reads `DataSpinner`'s per-entity `spinScale` — read-only in v1;
|
|
24
|
+
* per-entity WRITE-from-tool is future `useTunable`/inspector-set
|
|
25
|
+
* territory. `useSelection` only works inside an inspector tool (it
|
|
26
|
+
* throws a teaching error from a dock tool — there is no selection there).
|
|
27
|
+
* 3. `@editor/widgets` — the editor's OWN widget kit + design tokens,
|
|
28
|
+
* public for project tools (§3.4 "tools are editor citizens"): panels
|
|
29
|
+
* built from it sit flush with the built-in inspector sections and
|
|
30
|
+
* re-skin with the editor. `NumberInput` here is the exact
|
|
31
|
+
* scrub-or-type field the built-in Transform section uses.
|
|
32
|
+
*
|
|
33
|
+
* The component receives `{ node, nodeId }` as props (see
|
|
34
|
+
* `InspectorToolProps` in `@engine/tools/define-tool`) — unchanged by W3.1;
|
|
35
|
+
* richer data arrives through `useSelection()`, not more props. Like every
|
|
36
|
+
* tool, it's editor-only: `npm run build` never bundles this file.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
import { NumberInput, THEME } from '@editor/widgets';
|
|
40
|
+
import { useData, writeData } from '@engine/react/use-data';
|
|
41
|
+
import { useSelection } from '@engine/react/use-selection';
|
|
42
|
+
import { defineTool, type InspectorToolProps } from '@engine/tools/define-tool';
|
|
43
|
+
// The same project data handle the game (DataSpinner) and the dock example
|
|
44
|
+
// read — one handle, every surface agrees on the values.
|
|
45
|
+
import { tuning } from '../data/tuning';
|
|
46
|
+
|
|
47
|
+
export const tool = defineTool({
|
|
48
|
+
id: 'spin',
|
|
49
|
+
title: 'Spin tuning',
|
|
50
|
+
placement: 'inspector',
|
|
51
|
+
// Show this section only on an entity with a DataSpinner attached — the
|
|
52
|
+
// component-based idiom (rather than guessing from `node.kind`). `node` is
|
|
53
|
+
// null in the no-selection state — return false to stay out of the panel.
|
|
54
|
+
match: (node, adapter) =>
|
|
55
|
+
node !== null &&
|
|
56
|
+
(adapter.components?.list(node.id) ?? []).some((c) => c.type === 'DataSpinner'),
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
export default function SpinInspector({ node }: InspectorToolProps) {
|
|
60
|
+
// Live values: re-renders on every accepted edit of the data file,
|
|
61
|
+
// whoever made it (this field, the dock tool, the DATA tab, VS Code).
|
|
62
|
+
const values = useData(tuning);
|
|
63
|
+
|
|
64
|
+
// The selected entity's own components + their authored config, live
|
|
65
|
+
// (W3.1) — distinct from `values` above: `tuning` is project-wide feel,
|
|
66
|
+
// `spinScale` here is THIS entity's own per-entity knob.
|
|
67
|
+
const { components } = useSelection();
|
|
68
|
+
const spinner = components.find((c) => c.type === 'DataSpinner');
|
|
69
|
+
const spinScale =
|
|
70
|
+
typeof spinner?.config?.['spinScale'] === 'number'
|
|
71
|
+
? (spinner.config['spinScale'] as number)
|
|
72
|
+
: 1;
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 6, fontSize: 11 }}>
|
|
76
|
+
<div style={{ color: THEME.textMuted }}>
|
|
77
|
+
{node ? `Selected: ${node.label}` : 'Nothing selected'} — boxSpinSpeed drives the Box's
|
|
78
|
+
DataSpinner (src/data/tuning.data.json, live)
|
|
79
|
+
</div>
|
|
80
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
|
81
|
+
<span style={{ color: THEME.textMuted, width: 90 }}>boxSpinSpeed</span>
|
|
82
|
+
{/* The editor's own scrub-or-type number field (@editor/widgets):
|
|
83
|
+
drag horizontally to scrub, click to type. writeData writes the
|
|
84
|
+
whole data file through the dev server; HMR flows it back into
|
|
85
|
+
useData above AND the running game. */}
|
|
86
|
+
<NumberInput
|
|
87
|
+
value={values.boxSpinSpeed}
|
|
88
|
+
step={0.1}
|
|
89
|
+
testId="spin-tool-speed"
|
|
90
|
+
style={{ flex: 1 }}
|
|
91
|
+
onChange={(v) =>
|
|
92
|
+
void writeData('src/data/tuning.data.json', { ...values, boxSpinSpeed: v })
|
|
93
|
+
}
|
|
94
|
+
/>
|
|
95
|
+
</div>
|
|
96
|
+
<div style={{ color: THEME.textMuted }} data-testid="spin-tool-entity-scale">
|
|
97
|
+
this entity's spinScale: {spinScale} ×
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
);
|
|
101
|
+
}
|