@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,776 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Design-time layer stack (B1, docs/unified-world-editor/26-unified-editor-spec.md
|
|
3
|
+
* §3 B1 · D3/D9) — mounts a DESIGN-TIME layer for every non-focused react/pixi
|
|
4
|
+
* world so edit mode composites the viewport exactly like the runtime does
|
|
5
|
+
* (`@engine/runtime/create-runtime.ts`'s `createWorldsGameRuntime`, mirrored
|
|
6
|
+
* below): a z-ordered DOM stack, the threejs canvas keeping its existing,
|
|
7
|
+
* UNTOUCHED slot, each react/pixi world getting an absolutely-positioned
|
|
8
|
+
* layer above it. Layers default `pointer-events:none`; the eye
|
|
9
|
+
* (`world-session-state.ts`'s `hidden`) hides a layer; `interactive` forwards
|
|
10
|
+
* real pointer events for design-time hover/press testing; `pickLock` is
|
|
11
|
+
* stored only (B4 consumes it in the pick walk). A layer that throws on
|
|
12
|
+
* mount degrades to its A1 `BoundaryAuthoringAdapter` error node (#18) via
|
|
13
|
+
* `mount-failure-report.ts` — never a blank editor; every OTHER world (and
|
|
14
|
+
* the focused threejs world in particular) keeps working regardless.
|
|
15
|
+
*
|
|
16
|
+
* On a successful mount, the world's read-only Boundary adapter is REPLACED
|
|
17
|
+
* in the composite by a live one (`CompositeAuthoringAdapter.replaceChild`,
|
|
18
|
+
* B1) — react: the EXISTING, fully-featured `ReactWorldAuthoringAdapter`
|
|
19
|
+
* (spec-27's work; this file only produces the DOM it reflects, it never
|
|
20
|
+
* implements a second react adapter). Pixi stays Boundary-grade for the
|
|
21
|
+
* WHOLE S-B effort — display layer + (B4) stage hit-test only, no
|
|
22
|
+
* hierarchy/stories — see `mountPixiLayer`'s doc comment.
|
|
23
|
+
*
|
|
24
|
+
* Only NON-focused `react`/`pixijs` children are candidates for a layer: the
|
|
25
|
+
* focused threejs world already renders through `ViewportPanel`'s own
|
|
26
|
+
* `<canvas>` (its existing slot, deliberately untouched here — a plain,
|
|
27
|
+
* un-z-indexed, `position:static` element always paints below every
|
|
28
|
+
* POSITIONED layer this module adds, "canvas keeps its slot / accept
|
|
29
|
+
* canvas-bottom" per this task's own allowance). A non-focused THREEJS world
|
|
30
|
+
* (there is no live scene to show it, and switching focus is out of B1's
|
|
31
|
+
* scope — see `edit-mode-authoring.ts`'s `switchFocusedWorld` stub) simply
|
|
32
|
+
* keeps its existing Boundary node, untouched by this module.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import type { AuthoringAdapter, PickProvider } from '@engine/adapter';
|
|
36
|
+
import { createAssetCache } from '@engine/assets';
|
|
37
|
+
import { createGameLoop } from '@engine/core/game-loop';
|
|
38
|
+
import type { ResolvedWorldEntry } from '@engine/manifest/load';
|
|
39
|
+
import { createGame, type Game } from '@engine/runtime/game';
|
|
40
|
+
import { stackOrder } from '@engine/runtime/input-router';
|
|
41
|
+
import { PixiSceneGameAdapter } from '@engine/world2d/pixi-game-adapter';
|
|
42
|
+
import { createDataSource } from '@vgai/scene-ui/data-source';
|
|
43
|
+
import { storiesFromRegistry, type UIStory } from '@vgai/scene-ui/registry';
|
|
44
|
+
import { UIDataProvider } from '@vgai/scene-ui/ui-data-context';
|
|
45
|
+
import { Component, type ReactNode } from 'react';
|
|
46
|
+
import {
|
|
47
|
+
fetchScene2DFile,
|
|
48
|
+
loadProjectUIRegistry,
|
|
49
|
+
resolveGameProviderForProject,
|
|
50
|
+
resolveReactWorldEntryComponent,
|
|
51
|
+
resolveReactWorldMountRuntime,
|
|
52
|
+
} from '../adapter-resolver';
|
|
53
|
+
import { editorConsole } from '../editor-console';
|
|
54
|
+
import type { EditorStore } from '../editor-store';
|
|
55
|
+
import { getCurrentProject } from '../project-manager';
|
|
56
|
+
import { createHttpSourceWriteBackend } from '../ui-source/source-write-backend';
|
|
57
|
+
import { BoundaryAuthoringAdapter, type BoundaryWorldInfo } from './boundary-authoring-adapter';
|
|
58
|
+
import type { CompositeAuthoringAdapter } from './composite-authoring-adapter';
|
|
59
|
+
import { registerEditModeChild } from './edit-mode-authoring';
|
|
60
|
+
import { addMountFailureReport, formatMountFailureMessage } from './mount-failure-report';
|
|
61
|
+
import { ReactWorldAuthoringAdapter } from './react-world-authoring-adapter';
|
|
62
|
+
import { mountSceneUILayer } from './scene-ui-layer';
|
|
63
|
+
import { getStoryOverlay } from './story-overlay';
|
|
64
|
+
import { UIAuthoringAdapter } from './ui-authoring-adapter';
|
|
65
|
+
import {
|
|
66
|
+
getWorldPan,
|
|
67
|
+
panTransformValue,
|
|
68
|
+
resetWorldPan,
|
|
69
|
+
subscribeWorldPan,
|
|
70
|
+
} from './world-pan-state';
|
|
71
|
+
import { isWorldHidden, isWorldInteractive } from './world-session-state';
|
|
72
|
+
|
|
73
|
+
/** Bottom-to-top layer z-index base — strictly below `PlayBar`'s z100/
|
|
74
|
+
* `ToolStrip`+`ViewportOverlay`'s z1000, so gizmo/orbit-controls/marquee
|
|
75
|
+
* chrome is never occluded by a design-time layer. */
|
|
76
|
+
const BASE_Z_INDEX = 1;
|
|
77
|
+
|
|
78
|
+
/** One candidate world for a design-time layer — read entirely off its
|
|
79
|
+
* existing `BoundaryAuthoringAdapter` (kind/path/zOrder/pausable are already
|
|
80
|
+
* exposed there, independent of whether the composite has a game-root
|
|
81
|
+
* manifest surface — see that adapter's own inspector). */
|
|
82
|
+
interface LayerCandidate {
|
|
83
|
+
readonly worldId: string;
|
|
84
|
+
readonly kind: 'react' | 'pixijs';
|
|
85
|
+
readonly path: string | undefined;
|
|
86
|
+
readonly zOrder: number;
|
|
87
|
+
readonly pausable: boolean;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function readCandidates(composite: CompositeAuthoringAdapter): LayerCandidate[] {
|
|
91
|
+
const out: LayerCandidate[] = [];
|
|
92
|
+
for (const { worldId, adapter } of composite.childAdapters()) {
|
|
93
|
+
if (!(adapter instanceof BoundaryAuthoringAdapter)) continue; // the focused (live) world
|
|
94
|
+
const kind = adapter.inspector.get(worldId, 'kind') as string;
|
|
95
|
+
if (kind !== 'react' && kind !== 'pixijs') continue; // B1 scope: react/pixi layers only
|
|
96
|
+
const reason = adapter.inspector.get(worldId, 'reason');
|
|
97
|
+
if (reason !== undefined) continue; // already an error node — nothing new to attempt
|
|
98
|
+
out.push({
|
|
99
|
+
worldId,
|
|
100
|
+
kind,
|
|
101
|
+
path: adapter.inspector.get(worldId, 'path') as string | undefined,
|
|
102
|
+
zOrder: Number(adapter.inspector.get(worldId, 'zOrder') ?? 0),
|
|
103
|
+
pausable: adapter.inspector.get(worldId, 'pausable') !== false,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
return out;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* The inert design-time GameContext stand-in (B1's one genuine design
|
|
111
|
+
* decision). A REAL `Game` — never a hand-typed duck-typed literal, mirroring
|
|
112
|
+
* this repo's own test convention (`packages/editor/test/adapter-conformance-kit.ts`'s
|
|
113
|
+
* `headlessGame()`) — built with a loop that is constructed but NEVER
|
|
114
|
+
* started/ticked, and with NO worlds ever registered. This satisfies
|
|
115
|
+
* `useGame`/`useGameState` (`@engine/react/game-state`, and any project's own
|
|
116
|
+
* re-export of it) without throwing, genuinely inertly:
|
|
117
|
+
* - `state.subscribe` never fires — nothing ever calls
|
|
118
|
+
* `GameStateBridgeInternal.bump()` (only `GameInternal.runFrame` does, and
|
|
119
|
+
* this Game's loop never runs a frame);
|
|
120
|
+
* - `state.frameVersion` stays `0` forever;
|
|
121
|
+
* - `queryByComponent`/`world()`/`worlds` are empty/`null` because `worlds`
|
|
122
|
+
* stays `[]` (nothing ever calls `registerWorld`).
|
|
123
|
+
* `defaultWorld`/`components`/`input`/`audio` throw descriptively if touched
|
|
124
|
+
* (same as any fresh `createGame()` before a world registers) — a HUD that
|
|
125
|
+
* reaches for those is out of scope for a design-time stand-in and SHOULD
|
|
126
|
+
* throw loudly, caught by `DesignTimeErrorBoundary` below, degrading to the
|
|
127
|
+
* #18 error node rather than silently faking a value.
|
|
128
|
+
*
|
|
129
|
+
* B2 swaps this stand-in's DATA for story-sourced data (a `data`/services
|
|
130
|
+
* seam) — this function is the seam B2 replaces/extends, not a permanent
|
|
131
|
+
* fixture; it never constructs a live Game/GameComponents/sockets.
|
|
132
|
+
*/
|
|
133
|
+
function createDesignTimeGame(): Game {
|
|
134
|
+
const loop = createGameLoop({ fixedTimestep: 1 / 60, maxSubSteps: 8, update: () => {} });
|
|
135
|
+
return createGame({ loop, assets: createAssetCache() });
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Minimal class-component error boundary — the only way React lets a host
|
|
139
|
+
* catch a synchronous render-time throw (there is no hook equivalent).
|
|
140
|
+
* Mirrors `ingest-siblings.ts`'s `SiblingErrorBoundary` (not exported from
|
|
141
|
+
* that file, so re-declared here rather than reached into across an
|
|
142
|
+
* unrelated module). */
|
|
143
|
+
class DesignTimeErrorBoundary extends Component<
|
|
144
|
+
{ onCaught: (error: unknown) => void; children: ReactNode },
|
|
145
|
+
{ crashed: boolean }
|
|
146
|
+
> {
|
|
147
|
+
override state = { crashed: false };
|
|
148
|
+
static getDerivedStateFromError(): { crashed: boolean } {
|
|
149
|
+
return { crashed: true };
|
|
150
|
+
}
|
|
151
|
+
override componentDidCatch(error: unknown): void {
|
|
152
|
+
this.props.onCaught(error);
|
|
153
|
+
}
|
|
154
|
+
override render(): ReactNode {
|
|
155
|
+
return this.state.crashed ? null : this.props.children;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* A minimal `ResolvedWorldEntry` satisfying `resolveReactWorldEntryComponent`
|
|
161
|
+
* (`../adapter-resolver.ts`, which reads only `id`/`kind`/`scene`/`entry` off
|
|
162
|
+
* it) — the fields below it (`adapter`/`capabilities`/`loop`/`description`)
|
|
163
|
+
* are unused by that function but required by the type; filled with the
|
|
164
|
+
* honest "this is a native default-react world" values.
|
|
165
|
+
*/
|
|
166
|
+
function buildResolvedWorldEntryStub(worldId: string, entry: string): ResolvedWorldEntry {
|
|
167
|
+
return {
|
|
168
|
+
id: worldId,
|
|
169
|
+
kind: 'react',
|
|
170
|
+
description: undefined,
|
|
171
|
+
adapter: { type: 'default', identity: 'default-react' },
|
|
172
|
+
scene: undefined,
|
|
173
|
+
entry,
|
|
174
|
+
zOrder: 0,
|
|
175
|
+
pausable: true,
|
|
176
|
+
loop: 'gated',
|
|
177
|
+
capabilities: { local: 'first-party', hosted: 'first-party' },
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Re-applies session-local layer CSS (eye + interactive) — cheap, called on
|
|
182
|
+
* every store notification, never triggers a remount. */
|
|
183
|
+
function applySessionStyle(layer: HTMLElement, worldId: string): void {
|
|
184
|
+
layer.style.display = isWorldHidden(worldId) ? 'none' : 'block';
|
|
185
|
+
layer.style.pointerEvents = isWorldInteractive(worldId) ? 'auto' : 'none';
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function createLayerElement(container: HTMLElement, worldId: string, zIndex: number): HTMLElement {
|
|
189
|
+
const layer = document.createElement('div');
|
|
190
|
+
layer.dataset['worldId'] = worldId;
|
|
191
|
+
layer.style.position = 'absolute';
|
|
192
|
+
layer.style.top = '0';
|
|
193
|
+
layer.style.left = '0';
|
|
194
|
+
layer.style.width = '100%';
|
|
195
|
+
layer.style.height = '100%';
|
|
196
|
+
layer.style.zIndex = String(zIndex);
|
|
197
|
+
applySessionStyle(layer, worldId);
|
|
198
|
+
// D4 (spec27 §8 "space-pan" row) — seed this layer with whatever pan is
|
|
199
|
+
// currently in effect (normally none — `mountDesignTimeLayers` resets pan
|
|
200
|
+
// on every fresh install, see its own doc comment — but re-applying here
|
|
201
|
+
// too keeps this function correct standalone). See `world-pan-state.ts`'s
|
|
202
|
+
// doc comment for the full lockstep-translate soundness argument: THIS
|
|
203
|
+
// element is exactly `this.root`/`hostRect()` in
|
|
204
|
+
// `react-world-authoring-adapter.ts`, i.e. the host `RectProvider.rect(id)`
|
|
205
|
+
// subtracts — panning it is what keeps `rect(id)` pan-invariant.
|
|
206
|
+
applyPanTransform(layer);
|
|
207
|
+
container.appendChild(layer);
|
|
208
|
+
return layer;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** Re-applies the current shared world-pan transform to one layer element —
|
|
212
|
+
* called at creation and by `mountDesignTimeLayers`' pan subscription on
|
|
213
|
+
* every subsequent pan change (see `world-pan-state.ts`). */
|
|
214
|
+
function applyPanTransform(layer: HTMLElement): void {
|
|
215
|
+
layer.style.transform = panTransformValue(getWorldPan()) ?? '';
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
interface LayerMountResult {
|
|
219
|
+
readonly adapter?: AuthoringAdapter;
|
|
220
|
+
/**
|
|
221
|
+
* D12 (B4) — a stage hit-test over this layer's mounted content, in CLIENT
|
|
222
|
+
* (browser) px, `PickProvider['pick']`-shaped. Only `mountPixiLayer`
|
|
223
|
+
* produces one (react layers get their pick via the FULL
|
|
224
|
+
* `ReactWorldAuthoringAdapter.pickable` the `adapter` field above already
|
|
225
|
+
* carries) — see the success handler below for how a pixi layer's `pick`
|
|
226
|
+
* (with no `adapter` of its own) gets wired onto its still-Boundary-grade
|
|
227
|
+
* authoring adapter.
|
|
228
|
+
*/
|
|
229
|
+
readonly pick?: PickProvider['pick'];
|
|
230
|
+
dispose(): void;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* B3 — the layer's current render target: EITHER the world's `entry`
|
|
235
|
+
* (the world's own entry component, against whatever story `stories.apply`
|
|
236
|
+
* last applied — B1/B2's original render), OR a single catalog component
|
|
237
|
+
* `isolate`d against a story (the storybook canvas, §0/§3 B3). Exactly one
|
|
238
|
+
* of these is "current" at a time; `apply`/`isolate` write into their OWN
|
|
239
|
+
* slot independently (`entryStory`/`isolation` below) so applying an
|
|
240
|
+
* entry-level story WHILE isolated updates the entry's story in the
|
|
241
|
+
* background WITHOUT restoring the entry render (RISKS: "apply-while-isolated
|
|
242
|
+
* must NOT restore entry") — the render picks `isolation` over `entryStory`
|
|
243
|
+
* whenever `isolation` is set.
|
|
244
|
+
*/
|
|
245
|
+
interface LayerRenderState {
|
|
246
|
+
entryStory: (UIStory & { id: string }) | null;
|
|
247
|
+
isolation: { componentKey: string; story: (UIStory & { id: string }) | null } | null;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* React design-time mount (B1, story wiring B2 · D4, catalog B3) — the entry
|
|
252
|
+
* via the EXISTING #31 loader (`resolveReactWorldEntryComponent`, reused
|
|
253
|
+
* verbatim), wrapped in the project's own `GameProvider`
|
|
254
|
+
* (`resolveGameProviderForProject` — reused verbatim so a project shipping
|
|
255
|
+
* its own `src/ui/game-state.tsx` is wrapped by ITS OWN provider, never
|
|
256
|
+
* split-context — see that function's doc comment) around the inert
|
|
257
|
+
* `createDesignTimeGame()` stand-in (UNTOUCHED by B2/B3 — see that function's
|
|
258
|
+
* own doc comment), inside a `UIDataProvider` (B2 — seeded from the active
|
|
259
|
+
* story's `data`, or `{}` with none applied) and a React error boundary.
|
|
260
|
+
* `flushSync` forces the initial render/commit/error-boundary-recovery to
|
|
261
|
+
* finish before this call returns (mirrors `ingest-siblings.ts`'s
|
|
262
|
+
* `mountDefaultReactSibling`), so a throwing entry is caught HERE —
|
|
263
|
+
* synchronously — rather than discovered later on react's own schedule.
|
|
264
|
+
*
|
|
265
|
+
* B2 — stories: `storySources` is `[storiesFromRegistry(registry)`, the
|
|
266
|
+
* project's `src/ui/registry.ts` (`states` folded in as an alias) — `,
|
|
267
|
+
* getStoryOverlay(worldId) ?? {}]`, an editor-side overlay (Q2 decoupling,
|
|
268
|
+
* `story-overlay.ts`) — the overlay LAST so it wins on an id collision. No
|
|
269
|
+
* story is applied at mount (`render(…)`, matching B1's original empty-data
|
|
270
|
+
* render byte-for-byte when no story exists yet); `stories.apply` (routed
|
|
271
|
+
* here from the story picker via `onStoryApplied`) RE-RENDERS this SAME root
|
|
272
|
+
* in place against the applied story's `data`/`props` — never a remount,
|
|
273
|
+
* never a second `createDesignTimeGame()`/`GameProvider` (the inert Game
|
|
274
|
+
* stand-in stays the SAME instance across every story switch AND every
|
|
275
|
+
* isolate/exit, exactly as B1 left it — story data rides `UIDataProvider`/
|
|
276
|
+
* entry-or-component props ONLY, never `game.state`/`queryByComponent`, see
|
|
277
|
+
* this module's own crux doc comment — the `30-composition-render` "Ticks: 0"
|
|
278
|
+
* tripwire is the proof).
|
|
279
|
+
*
|
|
280
|
+
* B3 — `catalogComponents: registry?.components` gates
|
|
281
|
+
* `ReactWorldAuthoringAdapter`'s catalog hierarchy ON for this world (§0's
|
|
282
|
+
* reconciliation: THIS is the one site that turns it on — `play-mode.ts`'s
|
|
283
|
+
* `installMultiWorldAuthoring` never passes it, so play mode's hierarchy is
|
|
284
|
+
* untouched by construction). `onIsolate` re-renders the SAME root against
|
|
285
|
+
* the isolated component (alone) instead of the entry — `render()` below is
|
|
286
|
+
* `renderStory`'s generalization: it picks `isolation` over `entryStory`
|
|
287
|
+
* whenever an isolation is active (see {@link LayerRenderState}'s doc
|
|
288
|
+
* comment).
|
|
289
|
+
*
|
|
290
|
+
* On success, mints the world's live `ReactWorldAuthoringAdapter` (the SAME
|
|
291
|
+
* construction recipe as `play-mode.ts`'s `installMultiWorldAuthoring`, plus
|
|
292
|
+
* the new `storySources`/`onStoryApplied`/`catalogComponents`/`onIsolate`/
|
|
293
|
+
* `worldId` options) over this layer's now-mounted DOM — the adapter reflects
|
|
294
|
+
* an already-mounted root, it never mounts one itself; this function is the
|
|
295
|
+
* design-time MOUNT B1/B2/B3 add, the adapter class itself is wholesale
|
|
296
|
+
* reused.
|
|
297
|
+
*/
|
|
298
|
+
async function mountReactLayer(
|
|
299
|
+
candidate: LayerCandidate,
|
|
300
|
+
layer: HTMLElement,
|
|
301
|
+
projectRoot: string,
|
|
302
|
+
store: EditorStore,
|
|
303
|
+
): Promise<LayerMountResult> {
|
|
304
|
+
if (!candidate.path) {
|
|
305
|
+
throw new Error(
|
|
306
|
+
`design-time react layer "${candidate.worldId}": no \`entry\` declared — nothing to mount.`,
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
const worldStub = buildResolvedWorldEntryStub(candidate.worldId, candidate.path);
|
|
310
|
+
const Entry = await resolveReactWorldEntryComponent(worldStub, projectRoot);
|
|
311
|
+
const GameProvider = await resolveGameProviderForProject(
|
|
312
|
+
projectRoot,
|
|
313
|
+
candidate.worldId,
|
|
314
|
+
'default-react',
|
|
315
|
+
);
|
|
316
|
+
// C1 (phase-b/c1-react-mount): this layer's own react-world mount is a
|
|
317
|
+
// SECOND call site with the identical packaged-runtime dual-React-instance
|
|
318
|
+
// exposure `adapter-resolver.ts`'s `ReactWorldAdapter.mount()` has — under
|
|
319
|
+
// the packaged runtime, `createElement`/`createRoot`/`flushSync` must come
|
|
320
|
+
// from the PROJECT's own react, not this module's (removed) static
|
|
321
|
+
// imports. See `resolveReactWorldMountRuntime`'s doc comment.
|
|
322
|
+
const { createElement, createRoot, flushSync } = await resolveReactWorldMountRuntime();
|
|
323
|
+
const game = createDesignTimeGame();
|
|
324
|
+
|
|
325
|
+
// D4 (B2) — story sources: the project's registry (states folded in as
|
|
326
|
+
// stories) first, an editor-side overlay last (wins on an id collision).
|
|
327
|
+
// `loadProjectUIRegistry` stays silent for the common no-registry case
|
|
328
|
+
// (its own `hasUiRegistry` probe) — awaited HERE (not the async cache slot
|
|
329
|
+
// `scene-ui-layer.ts`'s own registry load populates, which can still be
|
|
330
|
+
// settling when this layer mounts, since the two mounts race).
|
|
331
|
+
const registry = await loadProjectUIRegistry(projectRoot);
|
|
332
|
+
const storySources: ReadonlyArray<Record<string, UIStory>> = [
|
|
333
|
+
storiesFromRegistry(registry ?? {}),
|
|
334
|
+
getStoryOverlay(candidate.worldId) ?? {},
|
|
335
|
+
];
|
|
336
|
+
// B3 — the catalog gate: absent/empty `components` (the common project,
|
|
337
|
+
// and every pre-B3 fixture) keeps `ReactWorldAuthoringAdapter`'s hierarchy
|
|
338
|
+
// the plain DOM walk, byte-identical (§1's gate — "components NON-EMPTY
|
|
339
|
+
// specifically").
|
|
340
|
+
const catalogComponents = registry?.components;
|
|
341
|
+
|
|
342
|
+
const root = createRoot(layer);
|
|
343
|
+
const renderState: LayerRenderState = { entryStory: null, isolation: null };
|
|
344
|
+
|
|
345
|
+
/** Render the CURRENT layer state (entry or isolated — see
|
|
346
|
+
* {@link LayerRenderState}) against the mounted root. Shared by the
|
|
347
|
+
* initial mount and every subsequent `stories.apply`/`stories.isolate` —
|
|
348
|
+
* React reconciles in place, never a remount (the inert `game`/
|
|
349
|
+
* `GameProvider` stay the SAME instances — the inert-Game invariant). */
|
|
350
|
+
const render = (onCaught: (error: unknown) => void): void => {
|
|
351
|
+
const { entryStory, isolation } = renderState;
|
|
352
|
+
const story = isolation ? isolation.story : entryStory;
|
|
353
|
+
// An isolated key the catalog doesn't actually have is defensive-only —
|
|
354
|
+
// the adapter itself refuses to `isolate` an unregistered key — so
|
|
355
|
+
// `Component` falling through to `null` here should never observably
|
|
356
|
+
// happen; it's a safe degrade rather than a crash if it somehow did.
|
|
357
|
+
const Component = isolation ? catalogComponents?.[isolation.componentKey] : undefined;
|
|
358
|
+
const view = isolation
|
|
359
|
+
? Component
|
|
360
|
+
? createElement(Component, { ...(story?.props ?? {}) })
|
|
361
|
+
: null
|
|
362
|
+
: createElement(Entry, { ...(story?.props ?? {}) });
|
|
363
|
+
root.render(
|
|
364
|
+
createElement(DesignTimeErrorBoundary, {
|
|
365
|
+
// Key by the current view + applied story so switching stories, OR
|
|
366
|
+
// switching entry<->isolated, REMOUNTS the boundary (React unmounts a
|
|
367
|
+
// differently-keyed element at the same position), resetting its
|
|
368
|
+
// `crashed` latch — otherwise a story/isolation whose data makes the
|
|
369
|
+
// render throw would stick `crashed` and blank the world for the rest
|
|
370
|
+
// of the session even after picking a good story/exiting again.
|
|
371
|
+
key: isolation
|
|
372
|
+
? `isolate:${isolation.componentKey}:${isolation.story?.id ?? ''}`
|
|
373
|
+
: story
|
|
374
|
+
? `story:${story.id}`
|
|
375
|
+
: '__none__',
|
|
376
|
+
onCaught,
|
|
377
|
+
children: createElement(
|
|
378
|
+
UIDataProvider,
|
|
379
|
+
{ data: createDataSource(story?.data ?? {}) },
|
|
380
|
+
createElement(GameProvider, { game }, view),
|
|
381
|
+
),
|
|
382
|
+
}),
|
|
383
|
+
);
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
let caught: unknown = null;
|
|
387
|
+
flushSync(() => {
|
|
388
|
+
render((error: unknown) => {
|
|
389
|
+
caught = error;
|
|
390
|
+
});
|
|
391
|
+
});
|
|
392
|
+
if (caught !== null) {
|
|
393
|
+
root.unmount();
|
|
394
|
+
throw caught instanceof Error ? caught : new Error(String(caught));
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const adapter = new ReactWorldAuthoringAdapter(layer, store, {
|
|
398
|
+
...(import.meta.env.DEV ? { writeBackend: createHttpSourceWriteBackend() } : {}),
|
|
399
|
+
storySources,
|
|
400
|
+
worldId: candidate.worldId,
|
|
401
|
+
catalogComponents,
|
|
402
|
+
onStoryApplied: (story) => {
|
|
403
|
+
renderState.entryStory = story;
|
|
404
|
+
render((error: unknown) => {
|
|
405
|
+
editorConsole.error(
|
|
406
|
+
`[design-time-layers] world "${candidate.worldId}" crashed applying story ` +
|
|
407
|
+
`"${story?.id ?? '(cleared)'}": ${formatMountFailureMessage(error)}`,
|
|
408
|
+
'authoring',
|
|
409
|
+
);
|
|
410
|
+
});
|
|
411
|
+
},
|
|
412
|
+
onIsolate: (v) => {
|
|
413
|
+
renderState.isolation = v;
|
|
414
|
+
render((error: unknown) => {
|
|
415
|
+
editorConsole.error(
|
|
416
|
+
`[design-time-layers] world "${candidate.worldId}" crashed isolating ` +
|
|
417
|
+
`"${v?.componentKey ?? '(cleared)'}": ${formatMountFailureMessage(error)}`,
|
|
418
|
+
'authoring',
|
|
419
|
+
);
|
|
420
|
+
});
|
|
421
|
+
},
|
|
422
|
+
});
|
|
423
|
+
return {
|
|
424
|
+
adapter,
|
|
425
|
+
dispose: () => {
|
|
426
|
+
// A4 (spec 27 §3) — release the adapter's `vite:afterUpdate` echo-reconcile
|
|
427
|
+
// subscription before dropping its DOM, so listeners don't accumulate across
|
|
428
|
+
// layer remounts (a leaked one is harmless — it early-returns on an empty echo —
|
|
429
|
+
// but this keeps teardown clean).
|
|
430
|
+
adapter.disposeReactWorldAdapter();
|
|
431
|
+
root.unmount();
|
|
432
|
+
},
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/** Best-effort layer pixel size — the container should already be laid out
|
|
437
|
+
* by the time this runs (mounted after `loadInitialScene` + the manifest
|
|
438
|
+
* fetch both resolve), but falls back to a fixed floor rather than handing
|
|
439
|
+
* Pixi/WebGL a degenerate 0×0 surface if it somehow isn't yet. */
|
|
440
|
+
function measure(container: HTMLElement): { width: number; height: number } {
|
|
441
|
+
const rect = container.getBoundingClientRect();
|
|
442
|
+
return {
|
|
443
|
+
width: Math.max(1, Math.round(rect.width) || container.clientWidth || 300),
|
|
444
|
+
height: Math.max(1, Math.round(rect.height) || container.clientHeight || 200),
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Pixi display-only design-time mount (B1) — `.scn2d` load
|
|
450
|
+
* (`fetchScene2DFile`, reused verbatim) + a ONE-SHOT render (no ongoing
|
|
451
|
+
* simulation/ticking at design time: this is a static disclosure, not a live
|
|
452
|
+
* preview). Reuses the exact production recipe (`PixiSceneGameAdapter.mount`,
|
|
453
|
+
* `transparent:true`, `autoStart:false` — the adapter's own default) rather
|
|
454
|
+
* than hand-rolling a leaner physics-free path: `loadScene2DFromData` still
|
|
455
|
+
* requires a full, real `World2DContext` (a real Rapier `World` +
|
|
456
|
+
* `ComponentManager` + `SystemRunner` — `packages/engine/src/world2d/types.ts`
|
|
457
|
+
* has no optional/physics-free fields), so there is no genuinely cheaper
|
|
458
|
+
* primitive available — this pays the one-time Rapier-wasm-init +
|
|
459
|
+
* component-attach cost once per pixi world, at design-time-layer-mount time.
|
|
460
|
+
* This is the explicitly chosen display-only depth for B1.
|
|
461
|
+
*
|
|
462
|
+
* Pixi is NOT upgraded to a live authoring adapter here: it stays
|
|
463
|
+
* Boundary-grade for the WHOLE S-B effort (display layer + B4's stage
|
|
464
|
+
* hit-test only, no hierarchy/stories) — an explicit, out-of-scope-by-design
|
|
465
|
+
* non-goal, not an oversight.
|
|
466
|
+
*
|
|
467
|
+
* B1 supports `scene`-declared pixi worlds only (the `.scn2d` display-only
|
|
468
|
+
* case named in the spec); an `entry`-based (imperative-setup) pixi world has
|
|
469
|
+
* no scene file to fetch and fails loudly here rather than being silently
|
|
470
|
+
* skipped — degrading to the same #18 error node as any other mount failure.
|
|
471
|
+
*/
|
|
472
|
+
async function mountPixiLayer(
|
|
473
|
+
candidate: LayerCandidate,
|
|
474
|
+
layer: HTMLElement,
|
|
475
|
+
container: HTMLElement,
|
|
476
|
+
): Promise<LayerMountResult> {
|
|
477
|
+
if (!candidate.path) {
|
|
478
|
+
throw new Error(
|
|
479
|
+
`design-time pixi layer "${candidate.worldId}": no \`scene\` declared — nothing to mount ` +
|
|
480
|
+
'(B1 display-only mount supports scene-based pixi worlds only, not `entry`).',
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
const sceneData = await fetchScene2DFile(candidate.path);
|
|
484
|
+
const canvas = document.createElement('canvas');
|
|
485
|
+
canvas.style.width = '100%';
|
|
486
|
+
canvas.style.height = '100%';
|
|
487
|
+
canvas.style.display = 'block';
|
|
488
|
+
layer.appendChild(canvas);
|
|
489
|
+
|
|
490
|
+
const { width, height } = measure(container);
|
|
491
|
+
const adapter = new PixiSceneGameAdapter({ id: candidate.worldId, sceneData });
|
|
492
|
+
const mounted = await adapter.mount({ canvas, width, height, transparent: true });
|
|
493
|
+
mounted.update?.(0); // one-shot render — see doc comment above
|
|
494
|
+
|
|
495
|
+
// D12 (B4) — stage hit-test: client (browser) px -> the stage's own
|
|
496
|
+
// coordinate space (pixi's logical/CSS-pixel `renderer.screen`, NOT the
|
|
497
|
+
// resolution-scaled device-pixel framebuffer size) via the canvas's OWN
|
|
498
|
+
// rect, so a post-mount CSS resize (this layer's canvas is styled
|
|
499
|
+
// 100%/100%, `measure()` only runs once at mount time) is scaled correctly
|
|
500
|
+
// rather than assuming a 1:1 client-px-to-stage-unit mapping forever. A
|
|
501
|
+
// world with no live rect (e.g. torn down between the event and this call)
|
|
502
|
+
// degrades to `null` — never a stale/garbage hit.
|
|
503
|
+
const pick: PickProvider['pick'] = (clientX, clientY) => {
|
|
504
|
+
const rect = canvas.getBoundingClientRect();
|
|
505
|
+
if (rect.width === 0 || rect.height === 0) return null;
|
|
506
|
+
const screen = mounted.ctx.app.renderer.screen;
|
|
507
|
+
const localX = ((clientX - rect.left) / rect.width) * screen.width;
|
|
508
|
+
const localY = ((clientY - rect.top) / rect.height) * screen.height;
|
|
509
|
+
for (const child of mounted.stage.children) {
|
|
510
|
+
if (child.getBounds().containsPoint(localX, localY)) return candidate.worldId;
|
|
511
|
+
}
|
|
512
|
+
return null;
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
return { dispose: () => mounted.dispose(), pick };
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Mount a design-time layer for every non-focused react/pixi world declared
|
|
520
|
+
* by `composite` (already installed via `installEditModeAuthoringForProject`)
|
|
521
|
+
* into `container` (`ViewportPanel`'s own containerRef div — the SAME element
|
|
522
|
+
* the editor's threejs `<canvas>` lives in). Returns a disposer; call it from
|
|
523
|
+
* the SAME effect cleanup that tears down the composite
|
|
524
|
+
* (`exitEditModeAuthoring`) — see `ViewportPanel.tsx`'s wiring.
|
|
525
|
+
*
|
|
526
|
+
* A no-op (returns a no-op disposer) when there is nothing to layer: a
|
|
527
|
+
* single-world project (back-compat floor, matches `installEditModeAuthoring`'s
|
|
528
|
+
* own single-world floor) or no resolvable current project.
|
|
529
|
+
*
|
|
530
|
+
* Play-mode handoff: the editor's Scene and Game tabs are BOTH always
|
|
531
|
+
* mounted (only one is `display`-visible at a time — the Game tab hosts
|
|
532
|
+
* `play-mode.ts`'s OWN, entirely separate composite/mount over a REAL,
|
|
533
|
+
* ticking `Game`). The instant play starts, this module tears its
|
|
534
|
+
* design-time layers down for the rest of THIS edit session: two
|
|
535
|
+
* independent DOM trees for the SAME react world (this inert stand-in,
|
|
536
|
+
* play-mode's live one) would otherwise carry the SAME `data-testid`s
|
|
537
|
+
* (whatever the entry's own JSX declares, e.g. tri-world's `hud-count`)
|
|
538
|
+
* simultaneously in one document — not just visually confusing, but a
|
|
539
|
+
* genuine test/automation hazard (two elements answering to one testid).
|
|
540
|
+
* Design-time layers do not remount when play stops again — reload picks
|
|
541
|
+
* them back up, exactly like any other edit-mode-only state (undo history,
|
|
542
|
+
* panel layout) that a play session doesn't attempt to preserve. This never
|
|
543
|
+
* touches the FOCUSED threejs world or the rest of the editor, which stay
|
|
544
|
+
* fully functional regardless of play state.
|
|
545
|
+
*/
|
|
546
|
+
export function mountDesignTimeLayers(
|
|
547
|
+
container: HTMLElement,
|
|
548
|
+
composite: CompositeAuthoringAdapter,
|
|
549
|
+
store: EditorStore,
|
|
550
|
+
): () => void {
|
|
551
|
+
// D4 (spec27 §8 "space-pan" row) — a fresh layer stack starts un-panned:
|
|
552
|
+
// every call here is a "world switch" (initial edit-mode install, or an
|
|
553
|
+
// `'editor:edit-mode-rebuild'` re-install — see `ViewportPanel.tsx`'s
|
|
554
|
+
// `installAll`), so a stale pan offset from a PREVIOUS layer stack must
|
|
555
|
+
// never carry over onto a different one.
|
|
556
|
+
resetWorldPan();
|
|
557
|
+
const projectRoot = getCurrentProject()?.rootPath;
|
|
558
|
+
const candidates = readCandidates(composite);
|
|
559
|
+
// C3 — the scene-UI child (at most one, the focused world's): already a
|
|
560
|
+
// LIVE `UIAuthoringAdapter` at composite-install time (`edit-mode-
|
|
561
|
+
// authoring.ts` — no Boundary-to-live upgrade dance needed here, unlike
|
|
562
|
+
// react/pixi), so it's a mount candidate in its own right rather than one
|
|
563
|
+
// of `readCandidates`'s Boundary-only entries.
|
|
564
|
+
const uiChildEntry = composite.childAdapters().find((c) => c.kind === 'scene-ui');
|
|
565
|
+
const uiAdapter =
|
|
566
|
+
uiChildEntry && uiChildEntry.adapter instanceof UIAuthoringAdapter
|
|
567
|
+
? uiChildEntry.adapter
|
|
568
|
+
: null;
|
|
569
|
+
if ((candidates.length === 0 && !uiAdapter) || !projectRoot) {
|
|
570
|
+
return () => {};
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// Bottom-to-top paint order — mirrors `create-runtime.ts`'s own stacking
|
|
574
|
+
// pass, over exactly the worlds this module lays out (react/pixi
|
|
575
|
+
// candidates only; the focused threejs world keeps its untouched canvas
|
|
576
|
+
// slot below all of these — see the module doc comment).
|
|
577
|
+
const sorted = stackOrder(candidates.map((c) => ({ id: c.worldId, zOrder: c.zOrder })));
|
|
578
|
+
const orderedCandidates = sorted.map((s) => candidates.find((c) => c.worldId === s.id)!);
|
|
579
|
+
|
|
580
|
+
const candidateByWorld = new Map(orderedCandidates.map((c) => [c.worldId, c]));
|
|
581
|
+
const layers = new Map<string, HTMLElement>();
|
|
582
|
+
const disposers: Array<() => void> = [];
|
|
583
|
+
// Worlds whose Boundary child we upgraded to a LIVE design-time adapter
|
|
584
|
+
// (successful react mounts). Tracked so the one-way play handoff can put an
|
|
585
|
+
// HONEST disclosure node back in the composite instead of leaving a live
|
|
586
|
+
// adapter over torn-down DOM (which renders silently empty — a #18 violation).
|
|
587
|
+
const upgraded = new Set<string>();
|
|
588
|
+
let torndown = false; // the whole-module disposer (ViewportPanel unmount) ran
|
|
589
|
+
let playTeardownDone = false; // the one-way play-mode handoff (see doc comment) ran
|
|
590
|
+
|
|
591
|
+
// C3 — the scene-UI layer's own disposer, tracked separately from
|
|
592
|
+
// `disposers`/`layers` (react/pixi's Boundary-upgrade bookkeeping doesn't
|
|
593
|
+
// apply to it — the UI child's adapter stays live throughout; only its DOM
|
|
594
|
+
// layer needs tearing down, here and on the play handoff below).
|
|
595
|
+
let disposeUILayer: (() => void) | undefined;
|
|
596
|
+
if (uiAdapter && uiChildEntry) {
|
|
597
|
+
disposeUILayer = mountSceneUILayer(
|
|
598
|
+
container,
|
|
599
|
+
uiChildEntry.worldId,
|
|
600
|
+
uiAdapter,
|
|
601
|
+
store,
|
|
602
|
+
BASE_Z_INDEX + orderedCandidates.length,
|
|
603
|
+
);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function disposeEverything(): void {
|
|
607
|
+
for (const dispose of disposers.splice(0)) {
|
|
608
|
+
try {
|
|
609
|
+
dispose();
|
|
610
|
+
} catch (err) {
|
|
611
|
+
editorConsole.error(`[design-time-layers] dispose failed: ${err}`, 'authoring');
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
for (const layer of layers.values()) layer.remove();
|
|
615
|
+
layers.clear();
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* One-way play handoff: dispose the design-time layers AND revert every
|
|
620
|
+
* upgraded world back to a `BoundaryAuthoringAdapter` disclosure node
|
|
621
|
+
* (#18 — never silently empty). Not called on the whole-module disposer
|
|
622
|
+
* (`torndown`, ViewportPanel unmount), where the composite is going away
|
|
623
|
+
* anyway. Design-time layers do not remount when play stops again — reload
|
|
624
|
+
* restores them (see the module doc comment). The scene-UI layer's DOM is
|
|
625
|
+
* torn down the same way (play mode renders scene-UI through the real
|
|
626
|
+
* engine's HUD mount instead — two live DOM trees for the same testids
|
|
627
|
+
* would be the same hazard `design-time-layers.ts`'s own doc comment
|
|
628
|
+
* already calls out for react worlds); its adapter is left alone (still
|
|
629
|
+
* live, just without a mounted layer) since there is no Boundary form of it.
|
|
630
|
+
*/
|
|
631
|
+
function suspendForPlay(): void {
|
|
632
|
+
disposeEverything();
|
|
633
|
+
disposeUILayer?.();
|
|
634
|
+
disposeUILayer = undefined;
|
|
635
|
+
for (const worldId of upgraded) {
|
|
636
|
+
const c = candidateByWorld.get(worldId);
|
|
637
|
+
if (!c) continue;
|
|
638
|
+
const info: BoundaryWorldInfo = {
|
|
639
|
+
id: c.worldId,
|
|
640
|
+
kind: c.kind,
|
|
641
|
+
entryOrScenePath: c.path,
|
|
642
|
+
zOrder: c.zOrder,
|
|
643
|
+
pausable: c.pausable,
|
|
644
|
+
};
|
|
645
|
+
composite.replaceChild(
|
|
646
|
+
worldId,
|
|
647
|
+
new BoundaryAuthoringAdapter(
|
|
648
|
+
store,
|
|
649
|
+
info,
|
|
650
|
+
'Design-time layer suspended by play mode — reload the editor to restore it.',
|
|
651
|
+
),
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
upgraded.clear();
|
|
655
|
+
store.notifyIngestEdit();
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
orderedCandidates.forEach((candidate, i) => {
|
|
659
|
+
const layer = createLayerElement(container, candidate.worldId, BASE_Z_INDEX + i);
|
|
660
|
+
layers.set(candidate.worldId, layer);
|
|
661
|
+
|
|
662
|
+
const mountPromise: Promise<LayerMountResult> =
|
|
663
|
+
candidate.kind === 'react'
|
|
664
|
+
? mountReactLayer(candidate, layer, projectRoot, store)
|
|
665
|
+
: mountPixiLayer(candidate, layer, container);
|
|
666
|
+
|
|
667
|
+
mountPromise
|
|
668
|
+
.then((result) => {
|
|
669
|
+
if (torndown || playTeardownDone) {
|
|
670
|
+
result.dispose();
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
if (result.adapter) {
|
|
674
|
+
composite.replaceChild(candidate.worldId, result.adapter);
|
|
675
|
+
registerEditModeChild(result.adapter);
|
|
676
|
+
upgraded.add(candidate.worldId);
|
|
677
|
+
} else if (result.pick) {
|
|
678
|
+
// D12 (B4) — a pixi layer produces a `pick`, not a full adapter
|
|
679
|
+
// (pixi stays Boundary-grade for the whole S-B effort). Re-wrap the
|
|
680
|
+
// existing Boundary with the pick attached, and track it in
|
|
681
|
+
// `upgraded` too: `suspendForPlay` must revert THIS one back to a
|
|
682
|
+
// plain pick-less Boundary same as a react upgrade — a pick closing
|
|
683
|
+
// over a disposed pixi stage must not survive into play mode.
|
|
684
|
+
const info: BoundaryWorldInfo = {
|
|
685
|
+
id: candidate.worldId,
|
|
686
|
+
kind: candidate.kind,
|
|
687
|
+
entryOrScenePath: candidate.path,
|
|
688
|
+
zOrder: candidate.zOrder,
|
|
689
|
+
pausable: candidate.pausable,
|
|
690
|
+
};
|
|
691
|
+
composite.replaceChild(
|
|
692
|
+
candidate.worldId,
|
|
693
|
+
new BoundaryAuthoringAdapter(store, info, undefined, result.pick),
|
|
694
|
+
);
|
|
695
|
+
upgraded.add(candidate.worldId);
|
|
696
|
+
}
|
|
697
|
+
disposers.push(result.dispose);
|
|
698
|
+
store.notifyIngestEdit();
|
|
699
|
+
})
|
|
700
|
+
.catch((err: unknown) => {
|
|
701
|
+
layer.remove();
|
|
702
|
+
layers.delete(candidate.worldId);
|
|
703
|
+
if (torndown || playTeardownDone) return;
|
|
704
|
+
const message = formatMountFailureMessage(err);
|
|
705
|
+
editorConsole.error(
|
|
706
|
+
`[design-time-layers] world "${candidate.worldId}" (${candidate.kind}) failed to ` +
|
|
707
|
+
`mount: ${message}`,
|
|
708
|
+
'authoring',
|
|
709
|
+
);
|
|
710
|
+
const info: BoundaryWorldInfo = {
|
|
711
|
+
id: candidate.worldId,
|
|
712
|
+
kind: candidate.kind,
|
|
713
|
+
entryOrScenePath: candidate.path,
|
|
714
|
+
zOrder: candidate.zOrder,
|
|
715
|
+
pausable: candidate.pausable,
|
|
716
|
+
};
|
|
717
|
+
composite.replaceChild(
|
|
718
|
+
candidate.worldId,
|
|
719
|
+
new BoundaryAuthoringAdapter(store, info, message),
|
|
720
|
+
);
|
|
721
|
+
addMountFailureReport({
|
|
722
|
+
worldId: candidate.worldId,
|
|
723
|
+
kind: candidate.kind,
|
|
724
|
+
identity: candidate.kind === 'react' ? 'default-react' : 'default-pixi',
|
|
725
|
+
message,
|
|
726
|
+
});
|
|
727
|
+
store.notifyIngestEdit();
|
|
728
|
+
});
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
// D4 (spec27 §8 "space-pan" row) — the lockstep half of the pan feature:
|
|
732
|
+
// re-apply the current shared transform to every currently-mounted layer
|
|
733
|
+
// the instant it changes (`WorldSelectionOverlay`'s own outer wrapper binds
|
|
734
|
+
// the SAME `subscribeWorldPan`/`panTransformValue` pair independently — see
|
|
735
|
+
// `world-pan-state.ts`'s doc comment for why applying the identical value
|
|
736
|
+
// at both mount points is what keeps `rect(id)` pan-invariant). A cheap
|
|
737
|
+
// style write, never a remount.
|
|
738
|
+
const unsubPan = subscribeWorldPan(() => {
|
|
739
|
+
for (const layer of layers.values()) applyPanTransform(layer);
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
const unsubStore = store.subscribe(() => {
|
|
743
|
+
if (!playTeardownDone && store.playState !== 'stopped') {
|
|
744
|
+
playTeardownDone = true;
|
|
745
|
+
suspendForPlay();
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
// Session toggles (eye/interactive) change via `store.notifyIngestEdit()`
|
|
749
|
+
// (`GameHierarchy.tsx`'s row affordances) — this is a cheap style
|
|
750
|
+
// re-apply, never a remount.
|
|
751
|
+
if (!playTeardownDone) {
|
|
752
|
+
for (const [worldId, layer] of layers) applySessionStyle(layer, worldId);
|
|
753
|
+
// B3 — "select elsewhere restores the entry": any-panel selection
|
|
754
|
+
// change (hierarchy row click, viewport pick, …) that no longer
|
|
755
|
+
// includes the isolated node exits isolation. Every store notification
|
|
756
|
+
// runs this (cheap — one hierarchy.node lookup per upgraded react
|
|
757
|
+
// world), covering every selection surface uniformly rather than
|
|
758
|
+
// wiring each one individually.
|
|
759
|
+
for (const { worldId, adapter } of composite.childAdapters()) {
|
|
760
|
+
if (!upgraded.has(worldId) || !(adapter instanceof ReactWorldAuthoringAdapter)) continue;
|
|
761
|
+
const isolatedId = adapter.isolatedNodeId;
|
|
762
|
+
if (isolatedId && !store.selectedEntityIds.has(isolatedId)) {
|
|
763
|
+
adapter.stories.isolate?.(null);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
});
|
|
768
|
+
|
|
769
|
+
return () => {
|
|
770
|
+
torndown = true;
|
|
771
|
+
unsubStore();
|
|
772
|
+
unsubPan();
|
|
773
|
+
disposeEverything();
|
|
774
|
+
disposeUILayer?.();
|
|
775
|
+
};
|
|
776
|
+
}
|