@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,313 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* World2DAuthoringAdapter — the editor's {@link AuthoringAdapter} implemented over a
|
|
3
|
+
* live PixiJS world2d tree (first-party scene OR an ingested unmodified game). It is
|
|
4
|
+
* the world2d peer of {@link IngestAuthoringAdapter}: the editor's REAL adapter-driven
|
|
5
|
+
* panels (GameHierarchy / Inspector) render world2d entities and edits apply
|
|
6
|
+
* live, with NO SceneEntity fabricated (anti-shim rule).
|
|
7
|
+
*
|
|
8
|
+
* It wraps the engine's backend-neutral {@link AuthoringAdapter2D} (tree + structural
|
|
9
|
+
* ids + overlay) and {@link PhysicsAdapter2D} (freeze→commit→unfreeze), and translates
|
|
10
|
+
* the engine's 2D transform (x, y, scalar rotation) to/from the editor's neutral
|
|
11
|
+
* {@link Transform} tuple (position[3] / quaternion-about-Z / scale[3]).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type {
|
|
15
|
+
AuthoringAdapter,
|
|
16
|
+
AuthoringCapabilities,
|
|
17
|
+
EditorNode,
|
|
18
|
+
HierarchyProvider,
|
|
19
|
+
InspectorProvider,
|
|
20
|
+
PersistenceProvider,
|
|
21
|
+
PropertyDescriptor,
|
|
22
|
+
SelectionProvider,
|
|
23
|
+
Transform,
|
|
24
|
+
TransformOwner,
|
|
25
|
+
TransformProvider,
|
|
26
|
+
} from '@engine/adapter';
|
|
27
|
+
import {
|
|
28
|
+
AuthoringAdapter2D,
|
|
29
|
+
type Overlay2D,
|
|
30
|
+
type Transform2DValue,
|
|
31
|
+
} from '@engine/world2d/authoring-2d';
|
|
32
|
+
import type { PhysicsAdapter2D } from '@engine/world2d/system-adapters-2d';
|
|
33
|
+
import type { Container } from 'pixi.js';
|
|
34
|
+
import type { EditorStore } from '../editor-store';
|
|
35
|
+
import { notifyUndoableOp, registerUndoAdapter } from './undo-timeline';
|
|
36
|
+
|
|
37
|
+
function numToHex(n: number): string {
|
|
38
|
+
return `#${(n & 0xffffff).toString(16).padStart(6, '0')}`;
|
|
39
|
+
}
|
|
40
|
+
function hexToNum(hex: string): number {
|
|
41
|
+
return Number.parseInt(hex.replace('#', ''), 16);
|
|
42
|
+
}
|
|
43
|
+
/** Quaternion (xyzw) about Z ⇄ scalar radians. */
|
|
44
|
+
function angleToQuat(a: number): [number, number, number, number] {
|
|
45
|
+
return [0, 0, Math.sin(a / 2), Math.cos(a / 2)];
|
|
46
|
+
}
|
|
47
|
+
function quatToAngle(q: [number, number, number, number]): number {
|
|
48
|
+
return 2 * Math.atan2(q[2], q[3]);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Save callback + human-readable destination for a world2d session's persistence
|
|
53
|
+
* (design §2/§4). Mirrors `IngestAuthoringOptions` (`ingest-authoring-adapter.ts`):
|
|
54
|
+
* absence of `persist` means runtime-only (`capabilities.persist === false`).
|
|
55
|
+
*/
|
|
56
|
+
export interface World2DAuthoringOptions {
|
|
57
|
+
/** Overlay to reapply once the tree is walked (edits from a previous session). */
|
|
58
|
+
overlay?: Overlay2D | undefined;
|
|
59
|
+
/** Save callback for `persistence.save()`. Absence ⇒ runtime-only (persist:false). */
|
|
60
|
+
persist?: ((overlay: Overlay2D) => Promise<void>) | undefined;
|
|
61
|
+
/** Human-readable destination (design §2), e.g. `.vgai/overlays/2d-<id>.json`. */
|
|
62
|
+
destination?: string | undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export class World2DAuthoringAdapter implements AuthoringAdapter {
|
|
66
|
+
readonly capabilities: AuthoringCapabilities;
|
|
67
|
+
private readonly a2d: AuthoringAdapter2D;
|
|
68
|
+
private readonly persistFn: ((overlay: Overlay2D) => Promise<void>) | undefined;
|
|
69
|
+
/** Human-readable overlay destination (design §2) — see {@link World2DAuthoringOptions.destination}. */
|
|
70
|
+
private readonly destination: string;
|
|
71
|
+
private listeners = new Set<() => void>();
|
|
72
|
+
|
|
73
|
+
constructor(
|
|
74
|
+
root: Container,
|
|
75
|
+
private readonly physics: PhysicsAdapter2D,
|
|
76
|
+
private readonly store: EditorStore,
|
|
77
|
+
opts: World2DAuthoringOptions = {},
|
|
78
|
+
) {
|
|
79
|
+
this.a2d = new AuthoringAdapter2D(root);
|
|
80
|
+
if (opts.overlay) this.a2d.applyOverlay(opts.overlay);
|
|
81
|
+
this.persistFn = opts.persist;
|
|
82
|
+
this.destination =
|
|
83
|
+
opts.destination ?? (opts.persist ? '(world2d overlay)' : '(runtime-only — not persisted)');
|
|
84
|
+
this.capabilities = {
|
|
85
|
+
transform: true,
|
|
86
|
+
material: true, // tint
|
|
87
|
+
inspectorFields: true,
|
|
88
|
+
// Structural authoring is not supported over a world2d tree today (mirrors
|
|
89
|
+
// the 3D ingest ceiling): create/delete/reparent would need to survive the
|
|
90
|
+
// next refresh()/remount, which neither surface implements.
|
|
91
|
+
create: false,
|
|
92
|
+
delete: false,
|
|
93
|
+
reparent: false,
|
|
94
|
+
persist: !!this.persistFn, // runtime-only unless a persist callback is wired
|
|
95
|
+
};
|
|
96
|
+
// v4 architecture-review §7.1-13 (was T3.2 slice 2 for the structural twin,
|
|
97
|
+
// `IngestAuthoringAdapter`, only): this adapter IS the undo/redo target the
|
|
98
|
+
// editor-global timeline dispatches to for entries it records below
|
|
99
|
+
// (`pushUndo`). The caller (`play-mode.ts`'s `exitMultiWorldAuthoring`,
|
|
100
|
+
// `ingest-mode.ts`'s `exitIngestMode2D`) unregisters it on teardown — see
|
|
101
|
+
// those call sites' comments for why an unregistered-but-still-recorded
|
|
102
|
+
// entry is dropped (with a warn) rather than undone against a torn-down tree.
|
|
103
|
+
registerUndoAdapter(this);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
refresh(): { count: number; sprites: number } {
|
|
107
|
+
return this.a2d.refresh();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Reapply a saved/external overlay onto the live tree (see `AuthoringAdapter2D.applyOverlay`). */
|
|
111
|
+
applyOverlay(overlay: Overlay2D): { applied: number; orphaned: number } {
|
|
112
|
+
return this.a2d.applyOverlay(overlay);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// A class GETTER, not a field initializer — a field initializer runs before the
|
|
116
|
+
// constructor body assigns `this.persistFn`/`this.destination` (the slice-1
|
|
117
|
+
// ordering bug; see `vgai-scene-authoring-adapter.ts` for the pattern).
|
|
118
|
+
get persistence(): PersistenceProvider {
|
|
119
|
+
const a2d = this.a2d;
|
|
120
|
+
const persistFn = this.persistFn;
|
|
121
|
+
const destination = this.destination;
|
|
122
|
+
return {
|
|
123
|
+
isDirty: () => a2d.isDirty(),
|
|
124
|
+
save: async () => {
|
|
125
|
+
if (!persistFn) return;
|
|
126
|
+
await persistFn(a2d.serializeOverlay());
|
|
127
|
+
a2d.markClean();
|
|
128
|
+
},
|
|
129
|
+
serialize: () => a2d.serializeOverlay(),
|
|
130
|
+
destination,
|
|
131
|
+
// The reload contract (design §5): re-apply a changed overlay onto the live
|
|
132
|
+
// tree without a full remount — `AuthoringAdapter2D.applyOverlay` already IS
|
|
133
|
+
// that remount-free logic (used at construction too).
|
|
134
|
+
applyExternal: (content) => {
|
|
135
|
+
a2d.applyOverlay(content as Overlay2D);
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private notify(): void {
|
|
141
|
+
for (const l of this.listeners) l();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
readonly hierarchy: HierarchyProvider = {
|
|
145
|
+
roots: () =>
|
|
146
|
+
this.a2d
|
|
147
|
+
.roots()
|
|
148
|
+
.map((n) => this.toEditorNode(n.id))
|
|
149
|
+
.filter((n): n is EditorNode => n !== null),
|
|
150
|
+
node: (id) => this.toEditorNode(id),
|
|
151
|
+
object3D: () => null, // world2d entities are PIXI display objects, not THREE — no 3D gizmo binding
|
|
152
|
+
idForObject3D: () => null,
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
private toEditorNode(id: string): EditorNode | null {
|
|
156
|
+
const n = this.a2d.node(id);
|
|
157
|
+
if (!n) return null;
|
|
158
|
+
const disp = this.a2d.displayObject(id);
|
|
159
|
+
const owner: TransformOwner =
|
|
160
|
+
disp && this.physics.ownerOf(disp) === 'physics' ? 'physics' : 'editor';
|
|
161
|
+
return {
|
|
162
|
+
id: n.id,
|
|
163
|
+
label: n.label,
|
|
164
|
+
kind: n.kind,
|
|
165
|
+
parentId: n.parentId,
|
|
166
|
+
childIds: n.childIds,
|
|
167
|
+
flags: { runtimeOnly: true, transformOwner: owner },
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
readonly selection: SelectionProvider = {
|
|
172
|
+
get: () => [...this.store.selectedEntityIds],
|
|
173
|
+
set: (ids) => {
|
|
174
|
+
this.store.selectMultiple(ids);
|
|
175
|
+
this.notify();
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
readonly transforms: TransformProvider = {
|
|
180
|
+
get: (id): Transform => {
|
|
181
|
+
const t = this.a2d.getTransform(id);
|
|
182
|
+
if (!t) return { position: [0, 0, 0], rotation: [0, 0, 0, 1], scale: [1, 1, 1] };
|
|
183
|
+
return {
|
|
184
|
+
position: [t.position[0], t.position[1], 0],
|
|
185
|
+
rotation: angleToQuat(t.rotation),
|
|
186
|
+
scale: [t.scale[0], t.scale[1], 1],
|
|
187
|
+
};
|
|
188
|
+
},
|
|
189
|
+
owner: (id) => {
|
|
190
|
+
const disp = this.a2d.displayObject(id);
|
|
191
|
+
return disp && this.physics.ownerOf(disp) === 'physics' ? 'physics' : 'editor';
|
|
192
|
+
},
|
|
193
|
+
// Snapshot the PRE-drag transform for undo (mirrors IngestAuthoringAdapter's
|
|
194
|
+
// `editStartTransform`) — in the engine's own [x,y]/scalar-radians shape, not
|
|
195
|
+
// the editor's quaternion `Transform`, so undo/redo round-trip through the
|
|
196
|
+
// exact same primitives `setTransform`/`getTransform` already use (no lossy
|
|
197
|
+
// angle<->quaternion re-derivation on the undo path).
|
|
198
|
+
beginEdit: (id) => {
|
|
199
|
+
const disp = this.a2d.displayObject(id);
|
|
200
|
+
if (disp && this.physics.ownerOf(disp) === 'physics') this.physics.freeze(disp);
|
|
201
|
+
this.editStartTransform = this.a2d.getTransform(id) ?? undefined;
|
|
202
|
+
},
|
|
203
|
+
apply: (id, t) => {
|
|
204
|
+
const pos: [number, number] = [t.position[0], t.position[1]];
|
|
205
|
+
const angle = quatToAngle(t.rotation);
|
|
206
|
+
this.writeTransform(id, { position: pos, rotation: angle, scale: [t.scale[0], t.scale[1]] });
|
|
207
|
+
},
|
|
208
|
+
// Pushes exactly ONE undo entry per gesture: `apply` fires many times during
|
|
209
|
+
// a live drag (no undo push there — see `writeTransform`), `endEdit` fires
|
|
210
|
+
// once on commit, which is where the before/after pair is recorded.
|
|
211
|
+
endEdit: (id) => {
|
|
212
|
+
const disp = this.a2d.displayObject(id);
|
|
213
|
+
if (disp && this.physics.ownerOf(disp) === 'physics') this.physics.unfreeze(disp);
|
|
214
|
+
const prev = this.editStartTransform;
|
|
215
|
+
this.editStartTransform = undefined;
|
|
216
|
+
if (!prev) return;
|
|
217
|
+
const next = this.a2d.getTransform(id);
|
|
218
|
+
if (!next) return;
|
|
219
|
+
this.pushUndo({
|
|
220
|
+
undo: () => this.writeTransform(id, prev),
|
|
221
|
+
redo: () => this.writeTransform(id, next),
|
|
222
|
+
});
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
/** Write a 2D transform to the live tree (+ physics body, if owned) + the
|
|
227
|
+
* overlay (via `AuthoringAdapter2D.setTransform`) — no undo push; shared by
|
|
228
|
+
* `transforms.apply` (live-drag, no push) and the undo/redo thunks
|
|
229
|
+
* `endEdit` records (which DO push, exactly once, on commit). */
|
|
230
|
+
private writeTransform(id: string, t: Transform2DValue): void {
|
|
231
|
+
const disp = this.a2d.displayObject(id);
|
|
232
|
+
if (disp && this.physics.ownerOf(disp) === 'physics') {
|
|
233
|
+
this.physics.commit(disp, t.position, t.rotation);
|
|
234
|
+
}
|
|
235
|
+
this.a2d.setTransform(id, t);
|
|
236
|
+
this.notify();
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
readonly inspector: InspectorProvider = {
|
|
240
|
+
properties: (id): PropertyDescriptor[] => {
|
|
241
|
+
const props: PropertyDescriptor[] = [
|
|
242
|
+
{ path: 'name', label: 'Name', type: 'string' },
|
|
243
|
+
{ path: 'visible', label: 'Visible', type: 'boolean' },
|
|
244
|
+
{ path: 'alpha', label: 'Alpha', type: 'number' },
|
|
245
|
+
];
|
|
246
|
+
const disp = this.a2d.displayObject(id);
|
|
247
|
+
if (disp && 'tint' in disp) props.push({ path: 'tint', label: 'Tint', type: 'color' });
|
|
248
|
+
return props;
|
|
249
|
+
},
|
|
250
|
+
get: (id, path) => {
|
|
251
|
+
const disp = this.a2d.displayObject(id) as (Container & Record<string, unknown>) | null;
|
|
252
|
+
if (!disp) return undefined;
|
|
253
|
+
if (path === 'name') return disp.label;
|
|
254
|
+
if (path === 'tint') return numToHex((disp as { tint?: number }).tint ?? 0xffffff);
|
|
255
|
+
return disp[path];
|
|
256
|
+
},
|
|
257
|
+
set: (id, path, value) => {
|
|
258
|
+
const prev = this.inspector.get(id, path); // snapshot for undo
|
|
259
|
+
this.writeProp(id, path, value);
|
|
260
|
+
this.pushUndo({
|
|
261
|
+
undo: () => this.writeProp(id, path, prev),
|
|
262
|
+
redo: () => this.writeProp(id, path, value),
|
|
263
|
+
});
|
|
264
|
+
},
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
/** Mutate one reflectable field on the live display object (+ overlay for
|
|
268
|
+
* tint/visible, via `AuthoringAdapter2D.set`) — no undo push. */
|
|
269
|
+
private writeProp(id: string, path: string, value: unknown): void {
|
|
270
|
+
const disp = this.a2d.displayObject(id) as (Container & Record<string, unknown>) | null;
|
|
271
|
+
if (!disp) return;
|
|
272
|
+
if (path === 'name') disp.label = value as string;
|
|
273
|
+
else if (path === 'tint') this.a2d.set(id, 'tint', hexToNum(value as string));
|
|
274
|
+
else if (path === 'visible') this.a2d.set(id, 'visible', value as boolean);
|
|
275
|
+
else disp[path] = value;
|
|
276
|
+
this.notify();
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// --- Overlay-backed undo/redo (v4 §7.1-13 — the world2d port of
|
|
280
|
+
// IngestAuthoringAdapter's "Overlay-backed undo/redo" section). ---
|
|
281
|
+
private readonly undoStack: Array<{ undo: () => void; redo: () => void }> = [];
|
|
282
|
+
private readonly redoStack: Array<{ undo: () => void; redo: () => void }> = [];
|
|
283
|
+
private editStartTransform: Transform2DValue | undefined;
|
|
284
|
+
|
|
285
|
+
private pushUndo(entry: { undo: () => void; redo: () => void }): void {
|
|
286
|
+
this.undoStack.push(entry);
|
|
287
|
+
this.redoStack.length = 0;
|
|
288
|
+
notifyUndoableOp(this); // tag this op on the global timeline
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
undo(): boolean {
|
|
292
|
+
// `false` tells the undo timeline "couldn't act — put the entry back" (see
|
|
293
|
+
// `UndoAdapter` in `./undo-timeline.ts`).
|
|
294
|
+
const e = this.undoStack.pop();
|
|
295
|
+
if (!e) return false;
|
|
296
|
+
e.undo();
|
|
297
|
+
this.redoStack.push(e);
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
redo(): boolean {
|
|
302
|
+
const e = this.redoStack.pop();
|
|
303
|
+
if (!e) return false;
|
|
304
|
+
e.redo();
|
|
305
|
+
this.undoStack.push(e);
|
|
306
|
+
return true;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
subscribe(listener: () => void): () => void {
|
|
310
|
+
this.listeners.add(listener);
|
|
311
|
+
return () => this.listeners.delete(listener);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-mode EXTERNAL-change detection for the open scene (Phase A4).
|
|
3
|
+
*
|
|
4
|
+
* A2 (`browser-watch-reload.ts`) derives change events from the storage
|
|
5
|
+
* backend's OWN writes — perfect for the editor's autosave echo, but blind to
|
|
6
|
+
* edits made out-of-band: an agent's `apply-diff` writing the `.vscn.json`
|
|
7
|
+
* straight to disk, another editor, a `git checkout`. The File System Access
|
|
8
|
+
* backend points at a real on-disk folder that other tools can write, and the
|
|
9
|
+
* browser gets no OS file-change notification for it — so the only way to learn
|
|
10
|
+
* about an external edit is to poll.
|
|
11
|
+
*
|
|
12
|
+
* This polls the CURRENTLY-OPEN scene file's content on an interval and, when it
|
|
13
|
+
* differs from the last content we observed, delivers it through the SAME
|
|
14
|
+
* `onSceneChanged` consumer A2 feeds (`connectSceneEvents` →
|
|
15
|
+
* `applyExternalUpdate`). That reuse is deliberate: the store's own-echo guard
|
|
16
|
+
* (`applyExternalUpdate` skips when `rawContent === _lastSavedContent`) already
|
|
17
|
+
* drops the editor's own saves, so a self-write that the poll happens to catch
|
|
18
|
+
* is harmlessly ignored downstream, and only a genuine external edit reloads.
|
|
19
|
+
*
|
|
20
|
+
* Scope: the open scene only (the roadmap's A4). Component-source external edits
|
|
21
|
+
* (→ hot-swap) are a later extension; this is the piece the gate's "genuine
|
|
22
|
+
* external edits arrive once A4 lands" note refers to. Gated to the FSA backend
|
|
23
|
+
* by the caller — OPFS is a private-origin sandbox with no external writer.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import type { StorageBackend } from './storage';
|
|
27
|
+
import { getStorageBackend } from './storage';
|
|
28
|
+
|
|
29
|
+
export interface SceneWatchState {
|
|
30
|
+
/** Last scene content this watcher observed (null until the first poll). */
|
|
31
|
+
lastContent: string | null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* One poll step (exported for unit tests — the interval driver just repeats it).
|
|
36
|
+
*
|
|
37
|
+
* The FIRST observation only establishes a baseline and never delivers: on watch
|
|
38
|
+
* start the on-disk content equals what the editor already has loaded, so firing
|
|
39
|
+
* would be a spurious reload. Every later change from that baseline delivers.
|
|
40
|
+
*/
|
|
41
|
+
export async function pollExternalSceneOnce(
|
|
42
|
+
state: SceneWatchState,
|
|
43
|
+
getScenePath: () => string | null,
|
|
44
|
+
backend: StorageBackend,
|
|
45
|
+
deliver: (event: { path: string; content: string }) => void,
|
|
46
|
+
): Promise<void> {
|
|
47
|
+
const path = getScenePath();
|
|
48
|
+
if (!path) return;
|
|
49
|
+
let content: string;
|
|
50
|
+
try {
|
|
51
|
+
content = await backend.read(path);
|
|
52
|
+
} catch {
|
|
53
|
+
// File missing/unreadable this tick (mid-write, removed) — try again next.
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (content === state.lastContent) return;
|
|
57
|
+
const isBaseline = state.lastContent === null;
|
|
58
|
+
state.lastContent = content;
|
|
59
|
+
if (isBaseline) return; // establish baseline silently
|
|
60
|
+
deliver({ path, content });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Poll the open scene for external edits. `getScenePath` is read live each tick
|
|
65
|
+
* (so switching the open scene re-baselines automatically). Returns an
|
|
66
|
+
* unsubscribe that stops the interval. Overlapping ticks are coalesced — a slow
|
|
67
|
+
* read never stacks up.
|
|
68
|
+
*/
|
|
69
|
+
export function watchExternalSceneChanges(
|
|
70
|
+
getScenePath: () => string | null,
|
|
71
|
+
deliver: (event: { path: string; content: string }) => void,
|
|
72
|
+
opts: { backend?: StorageBackend; intervalMs?: number } = {},
|
|
73
|
+
): () => void {
|
|
74
|
+
const backend = opts.backend ?? getStorageBackend();
|
|
75
|
+
const intervalMs = opts.intervalMs ?? 1500;
|
|
76
|
+
const state: SceneWatchState = { lastContent: null };
|
|
77
|
+
let stopped = false;
|
|
78
|
+
let inFlight = false;
|
|
79
|
+
|
|
80
|
+
const tick = async (): Promise<void> => {
|
|
81
|
+
if (inFlight) return;
|
|
82
|
+
inFlight = true;
|
|
83
|
+
try {
|
|
84
|
+
await pollExternalSceneOnce(state, getScenePath, backend, deliver);
|
|
85
|
+
} finally {
|
|
86
|
+
inFlight = false;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const timer = setInterval(() => {
|
|
91
|
+
if (!stopped) void tick();
|
|
92
|
+
}, intervalMs);
|
|
93
|
+
|
|
94
|
+
return () => {
|
|
95
|
+
stopped = true;
|
|
96
|
+
clearInterval(timer);
|
|
97
|
+
};
|
|
98
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser play entry — a STATIC, dynamic-import-free version of the template's
|
|
3
|
+
* `src/scripts/main.ts` for the static/browser editor build.
|
|
4
|
+
*
|
|
5
|
+
* The static host has no dev server, so play mode can't `import('/@fs/.../main.ts')`.
|
|
6
|
+
* Instead this file is pre-bundled (see `scripts/bundle-project-scripts.mjs`) into a
|
|
7
|
+
* single self-contained ESM (`public/project-scripts.bundle.js`) with all engine +
|
|
8
|
+
* vendor modules kept EXTERNAL, then loaded in-browser via esbuild-wasm transpile
|
|
9
|
+
* (`browser-project-scripts.ts` → `importFromSource`), which shims each external
|
|
10
|
+
* import to the editor's LIVE singleton.
|
|
11
|
+
*
|
|
12
|
+
* Dispatch is over `SETUP_BY_EXAMPLE_ID`, a plain exampleId → setup map of STATIC
|
|
13
|
+
* imports spanning `examples/<id>/src/...` (repo-root sibling packages — see
|
|
14
|
+
* docs/EXAMPLES-AS-PROJECTS-DESIGN.md §4), so the bundle has zero dynamic imports.
|
|
15
|
+
* That map is not hand-maintained: `generated/setup-by-example.generated.ts` is
|
|
16
|
+
* GENERATED from each example folder's `vgai.game.json` manifest by
|
|
17
|
+
* `scripts/bundle-project-scripts.mjs` — regenerate with
|
|
18
|
+
* `node scripts/bundle-project-scripts.mjs` after editing an example manifest.
|
|
19
|
+
*
|
|
20
|
+
* Design note (Adjudication H's plumbing point, closed by Track H): which
|
|
21
|
+
* example to run is threaded through `EditorPreview.exampleId`
|
|
22
|
+
* (`@engine/runtime/types`) — the ONLY channel a caller has into this bundle's
|
|
23
|
+
* exported `setup(ctx, editor)`, since `GameSetupFn`'s signature is fixed and
|
|
24
|
+
* used broadly. The caller is play-mode.ts's legacy browser branch, fed by the
|
|
25
|
+
* hosted `?project=<id>` routing (`hosted-example.ts`); undefined still means
|
|
26
|
+
* the generic scene-loader fallback. `loadBrowserProjectScripts()` itself
|
|
27
|
+
* stays a plain no-arg loader — it just returns whatever `setup` the bundle
|
|
28
|
+
* exports, unmodified.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { setAssetPrefix } from '@engine/loader';
|
|
32
|
+
import type { EditorPreview, GameCleanup, GameContext } from '@engine/runtime/types';
|
|
33
|
+
import { loadScene, loadSceneFromData } from '@engine/scene/scene-loader';
|
|
34
|
+
import { applyScenePostProcessing } from '@engine/setup/setup-renderer';
|
|
35
|
+
// The generic fallback's component registry is still the template's (soon-to-be
|
|
36
|
+
// minimal) `src/scripts/registry.ts` — unrelated to example dispatch, and this
|
|
37
|
+
// migration wave does not touch the template layout (a later wave does).
|
|
38
|
+
import { componentRegistry, gameComponents } from '../template/src/scripts/registry';
|
|
39
|
+
import { SETUP_BY_EXAMPLE_ID } from './generated/setup-by-example.generated';
|
|
40
|
+
|
|
41
|
+
const DEFAULT_SCENE = 'scenes/default.vscn.json';
|
|
42
|
+
|
|
43
|
+
/** Exported so the editor inspector can read component `static schema`s. */
|
|
44
|
+
export const components = gameComponents;
|
|
45
|
+
|
|
46
|
+
export async function setup(ctx: GameContext, editor?: EditorPreview): Promise<GameCleanup> {
|
|
47
|
+
// If the caller (Track H's `?project=<id>` routing, or a future picker)
|
|
48
|
+
// asked for a specific bundled example, delegate to its setup (registers the
|
|
49
|
+
// correct gameplay systems + component registries) after namespacing
|
|
50
|
+
// relative asset fetches to that example's own copied public/ output (see
|
|
51
|
+
// vite-plugin-example-assets.ts). An exampleId with NO dispatch entry (e.g.
|
|
52
|
+
// a server-room example the static host can't run — see the generated
|
|
53
|
+
// file's header) falls through to the generic loader below, KEEPING the
|
|
54
|
+
// example prefix so the open scene's relative asset refs still resolve.
|
|
55
|
+
if (editor?.exampleId) {
|
|
56
|
+
setAssetPrefix(`/examples/${editor.exampleId}/`);
|
|
57
|
+
const exampleSetup = SETUP_BY_EXAMPLE_ID[editor.exampleId];
|
|
58
|
+
if (exampleSetup) {
|
|
59
|
+
return exampleSetup(ctx);
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
// Generic fallback prefix: `editor.assetPrefix` when the editor is playing
|
|
63
|
+
// a bundled example's non-default scene (Track H `?scene=` override),
|
|
64
|
+
// otherwise the deploy root — always reset here so a previously mounted
|
|
65
|
+
// example's namespaced prefix never leaks into this path.
|
|
66
|
+
setAssetPrefix(editor?.assetPrefix ?? '/');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Generic fallback: load the scene with the full component registry so ECS
|
|
70
|
+
// components apply, but no example-specific gameplay systems are registered.
|
|
71
|
+
const loadOpts = {
|
|
72
|
+
scene: ctx.scene,
|
|
73
|
+
rapierWorld: ctx.rapierWorld,
|
|
74
|
+
rapier: ctx.rapier,
|
|
75
|
+
physics: ctx.physics,
|
|
76
|
+
audioListener: ctx.audio.listener,
|
|
77
|
+
componentRegistry,
|
|
78
|
+
componentManager: ctx.components,
|
|
79
|
+
particleRenderer: ctx.particles.batchedRenderer,
|
|
80
|
+
renderer: ctx.composer.getRenderer(),
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
if (editor?.viewportCamera) {
|
|
84
|
+
ctx.camera.position.fromArray(editor.viewportCamera.position);
|
|
85
|
+
ctx.camera.quaternion.fromArray(editor.viewportCamera.quaternion);
|
|
86
|
+
} else {
|
|
87
|
+
ctx.camera.position.set(3, 3, 3);
|
|
88
|
+
ctx.camera.lookAt(0, 0.5, 0);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const sceneInstance = editor
|
|
92
|
+
? await loadSceneFromData(editor.sceneData, loadOpts)
|
|
93
|
+
: await loadScene(DEFAULT_SCENE, loadOpts);
|
|
94
|
+
|
|
95
|
+
if (sceneInstance.environment?.postProcessing) {
|
|
96
|
+
applyScenePostProcessing(
|
|
97
|
+
ctx.composer,
|
|
98
|
+
ctx.composer.getRenderer(),
|
|
99
|
+
ctx.scene,
|
|
100
|
+
ctx.camera,
|
|
101
|
+
sceneInstance.environment,
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
ctx.systems.add('postPhysics', (dt) => sceneInstance.update(dt));
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
dispose() {
|
|
109
|
+
sceneInstance.dispose();
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-build play-mode script loader.
|
|
3
|
+
*
|
|
4
|
+
* The static/browser build has no dev server, so play mode can't
|
|
5
|
+
* `import('/@fs/.../src/scripts/main.ts')`. Instead we ship the project scripts
|
|
6
|
+
* pre-bundled to a single self-contained ESM (`public/project-scripts.bundle.js`,
|
|
7
|
+
* built by `scripts/bundle-project-scripts.mjs`) with all engine + vendor modules
|
|
8
|
+
* kept EXTERNAL, and run it through the same in-browser transpile path the
|
|
9
|
+
* Playground uses (`importFromSource`). Each external bare import is shimmed to
|
|
10
|
+
* the editor's LIVE singleton via `registerRuntimeModules`, so the bundled
|
|
11
|
+
* examples share the editor's THREE / GameComponent / physics world.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { GameComponentClass } from '@engine/ecs/game-component';
|
|
15
|
+
import type { GameSetupFn } from '@engine/runtime/types';
|
|
16
|
+
import { importFromSource, registerRuntimeModules } from './browser-transpile';
|
|
17
|
+
import { type ProjectScripts, setLoadedComponents } from './script-loader';
|
|
18
|
+
|
|
19
|
+
let _modulesRegistered = false;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* One lazy loader per external specifier the bundle imports — the keys ARE the
|
|
23
|
+
* registered-specifier list (exported below for the drift-guard test). Lazy
|
|
24
|
+
* (only awaited on first Play) so these heavy modules aren't pulled into the
|
|
25
|
+
* editor's startup chunk. The list must mirror the externals actually present
|
|
26
|
+
* in `project-scripts.bundle.js` — a new external missing here makes
|
|
27
|
+
* `importFromSource` throw "not a registered runtime module" AT PLAY TIME in
|
|
28
|
+
* the hosted build only, which is exactly how the Track E2 starfighter
|
|
29
|
+
* relocation silently broke hosted Play (caught live in Track H's H3
|
|
30
|
+
* verification: RGBELoader/BufferGeometryUtils were bundled but unregistered).
|
|
31
|
+
* `browser-bundle-runtime-modules.test.ts` now fails CI on that drift.
|
|
32
|
+
*/
|
|
33
|
+
const BUNDLE_RUNTIME_MODULE_LOADERS: Record<string, () => Promise<unknown>> = {
|
|
34
|
+
three: () => import('three'),
|
|
35
|
+
'three/addons/controls/OrbitControls.js': () => import('three/addons/controls/OrbitControls.js'),
|
|
36
|
+
'three/addons/interactive/SelectionBox.js': () =>
|
|
37
|
+
import('three/addons/interactive/SelectionBox.js'),
|
|
38
|
+
'three/addons/loaders/RGBELoader.js': () => import('three/addons/loaders/RGBELoader.js'),
|
|
39
|
+
'three/addons/utils/BufferGeometryUtils.js': () =>
|
|
40
|
+
import('three/addons/utils/BufferGeometryUtils.js'),
|
|
41
|
+
'three/addons/utils/SkeletonUtils.js': () => import('three/addons/utils/SkeletonUtils.js'),
|
|
42
|
+
'three/examples/jsm/animation/CCDIKSolver.js': () =>
|
|
43
|
+
import('three/examples/jsm/animation/CCDIKSolver.js'),
|
|
44
|
+
'three.quarks': () => import('three.quarks'),
|
|
45
|
+
'quarks.core': () => import('quarks.core'),
|
|
46
|
+
postprocessing: () => import('postprocessing'),
|
|
47
|
+
gsap: () => import('gsap'),
|
|
48
|
+
zod: () => import('zod'),
|
|
49
|
+
react: () => import('react'),
|
|
50
|
+
'react/jsx-runtime': () => import('react/jsx-runtime'),
|
|
51
|
+
'react-dom/client': () => import('react-dom/client'),
|
|
52
|
+
'@engine/adapter': () => import('@engine/adapter'),
|
|
53
|
+
'@engine/ai/navigation': () => import('@engine/ai/navigation'),
|
|
54
|
+
'@engine/animation/xstate-animation-binding': () =>
|
|
55
|
+
import('@engine/animation/xstate-animation-binding'),
|
|
56
|
+
'@engine/audio': () => import('@engine/audio'),
|
|
57
|
+
'@engine/data/data-asset': () => import('@engine/data/data-asset'),
|
|
58
|
+
'@engine/ecs/game-component': () => import('@engine/ecs/game-component'),
|
|
59
|
+
'@engine/scene/scene-loader': () => import('@engine/scene/scene-loader'),
|
|
60
|
+
'@engine/scene/scene-query': () => import('@engine/scene/scene-query'),
|
|
61
|
+
'@engine/scene/user-data': () => import('@engine/scene/user-data'),
|
|
62
|
+
'@engine/setup/setup-renderer': () => import('@engine/setup/setup-renderer'),
|
|
63
|
+
// littlest-tokyo (GLTF quick-start) extras:
|
|
64
|
+
'@engine/loader': () => import('@engine/loader'),
|
|
65
|
+
'@engine/scene/parse': () => import('@engine/scene/parse'),
|
|
66
|
+
// E5 — XState-driven character animation (third-person/rts/third-person-arena):
|
|
67
|
+
xstate: () => import('xstate'),
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/** The bundle-external specifiers the browser build can shim at Play time —
|
|
71
|
+
* exported for `browser-bundle-runtime-modules.test.ts` (bundle-drift guard). */
|
|
72
|
+
export const REGISTERED_BUNDLE_SPECIFIERS: readonly string[] = Object.keys(
|
|
73
|
+
BUNDLE_RUNTIME_MODULE_LOADERS,
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
async function ensureRuntimeModulesRegistered(): Promise<void> {
|
|
77
|
+
if (_modulesRegistered) return;
|
|
78
|
+
const entries = await Promise.all(
|
|
79
|
+
Object.entries(BUNDLE_RUNTIME_MODULE_LOADERS).map(
|
|
80
|
+
async ([specifier, load]) => [specifier, (await load()) as Record<string, unknown>] as const,
|
|
81
|
+
),
|
|
82
|
+
);
|
|
83
|
+
const mods: Record<string, Record<string, unknown>> = Object.fromEntries(entries);
|
|
84
|
+
registerRuntimeModules(mods);
|
|
85
|
+
_modulesRegistered = true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Load the pre-bundled project scripts in the browser build: register live
|
|
90
|
+
* runtime modules, fetch the bundle, transpile+import it in-browser, and return
|
|
91
|
+
* its `setup` / `components`. Mirrors `loadProjectScripts`'s return shape.
|
|
92
|
+
*/
|
|
93
|
+
export async function loadBrowserProjectScripts(): Promise<ProjectScripts> {
|
|
94
|
+
await ensureRuntimeModulesRegistered();
|
|
95
|
+
|
|
96
|
+
const url = `${import.meta.env.BASE_URL}project-scripts.bundle.js`;
|
|
97
|
+
const res = await fetch(url);
|
|
98
|
+
if (!res.ok) {
|
|
99
|
+
throw new Error(
|
|
100
|
+
`Failed to fetch ${url} (${res.status}) — is the project-scripts bundle built?`,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
const source = await res.text();
|
|
104
|
+
|
|
105
|
+
const mod = await importFromSource(source, 'project-scripts.bundle.js');
|
|
106
|
+
if (typeof mod['setup'] !== 'function') {
|
|
107
|
+
throw new Error('project-scripts bundle does not export a setup() function');
|
|
108
|
+
}
|
|
109
|
+
const components = mod['components'] as Record<string, GameComponentClass> | undefined;
|
|
110
|
+
setLoadedComponents(components ?? {});
|
|
111
|
+
return { setup: mod['setup'] as GameSetupFn, components };
|
|
112
|
+
}
|