@vgai/editor 0.2.0 → 0.4.0-canary.20260715.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/README.md +24 -0
- package/dist/assets/axe-C7YBtOuM.js +30 -0
- package/dist/assets/browserAll-DDtGGE48.js +1 -0
- package/dist/assets/create-runtime-DbTlBlKG.js +1 -0
- package/dist/assets/{game-BI111o5f.js → game-BE0r7DMI.js} +3 -3
- package/dist/assets/game-BdTHR2_X.js +1 -0
- package/dist/assets/{game-pt_1lQLv.js → game-C3iQGSqo.js} +1 -1
- package/dist/assets/game-CcHfYxjP.js +1 -0
- package/dist/assets/game-Cqzj_lpD.js +1 -0
- package/dist/assets/game-DSZdeCHu.js +1 -0
- package/dist/assets/game-Dch9sAPc.js +1 -0
- package/dist/assets/{game-xsOho_wg.js → game-SAFn90pA.js} +3 -3
- package/dist/assets/game-loop-DMIgSMV-.js +1 -0
- package/dist/assets/{game-source-CvidN7AB.js → game-source-Dk83w26t.js} +1 -1
- package/dist/assets/{index-Bp5cecD6.js → index-4idIsHHJ.js} +2 -2
- package/dist/assets/{index-DL7RcQ0n.js → index-C-LhOTBN.js} +1 -1
- package/dist/assets/{index-Cs8xwv3e.js → index-CDkxceqE.js} +1 -1
- package/dist/assets/index-CQEfi217.js +15412 -0
- package/dist/assets/index-Dvi-V0NE.css +1 -0
- package/dist/assets/ingest-siblings-s0P5I1Ub.js +1 -0
- package/dist/assets/mount-manifest-DXMOlF79.js +1 -0
- package/dist/assets/react-18-C9eHn8x1.js +1 -0
- package/dist/assets/webworkerAll-Fn0e-ouF.js +1 -0
- package/dist/assets/world-documents-GM0a9Kv7.js +1 -0
- package/dist/index.html +15 -13
- package/dist/ingest/racing-game/vgai.game.json +5 -4
- package/dist/ingest/simcity/vgai.game.json +5 -4
- package/dist/ingest/tanks/vgai.game.json +5 -4
- package/dist/project-scripts.bundle.js +3917 -0
- package/dist/scenes/untitled.vscn.json +2 -0
- package/dist-server/packaged.mjs +7697 -3435
- package/package.json +31 -4
- package/scripts/probe-reporter.ts +349 -0
- package/scripts/run-probe.ts +2260 -0
- package/server/asset-catalog-audit.ts +268 -0
- package/server/asset-catalog-v2.ts +146 -0
- package/server/asset-library-routes.ts +276 -58
- package/server/canonical-path.ts +1 -1
- package/server/dev.ts +148 -44
- package/server/editor-server.ts +888 -61
- package/server/editor-sse.ts +115 -4
- package/server/harness-chat-service.ts +796 -0
- package/server/local-asset-catalog.ts +157 -22
- package/server/packaged.ts +51 -21
- package/server/process-shutdown.ts +136 -0
- package/server/prod.ts +9 -6
- package/server/project-hmr-files.ts +41 -0
- package/server/project-operations.ts +343 -0
- package/server/project-optimize-deps-entries.ts +4 -4
- package/server/project-output-writer.ts +156 -0
- package/server/project-validation.ts +140 -0
- package/server/project-view.ts +8 -50
- package/server/server-utils.ts +75 -10
- package/server/session-registry.ts +12 -0
- package/src/EditorContext.tsx +107 -3
- package/src/action-registry.ts +115 -17
- package/src/adapter-resolver.ts +213 -478
- package/src/asset-events.ts +64 -1
- package/src/asset-preview.ts +906 -0
- package/src/asset-selection.ts +121 -0
- package/src/asset-workflow/asset-capabilities.ts +259 -0
- package/src/asset-workflow/asset-editor-errors.ts +46 -0
- package/src/asset-workflow/asset-import-pipeline.ts +176 -0
- package/src/asset-workflow/asset-library-query.ts +107 -0
- package/src/asset-workflow/asset-types.ts +192 -0
- package/src/asset-workflow/asset-workflow-quality.ts +65 -0
- package/src/asset-workflow/import-contract.ts +113 -0
- package/src/asset-workflow/model-conversion.ts +126 -0
- package/src/asset-workflow/model-inspection.ts +185 -0
- package/src/asset-workflow/project-asset-commands.ts +23 -0
- package/src/asset-workflow/project-asset-health.ts +153 -0
- package/src/asset-workflow/project-asset-operations.ts +119 -0
- package/src/asset-workflow/thumbnail-system.ts +172 -0
- package/src/authoring/active-systems.ts +15 -0
- package/src/authoring/boundary-authoring-adapter.ts +16 -5
- package/src/authoring/composite-authoring-adapter.ts +305 -124
- package/src/authoring/design-time-layers.ts +300 -117
- package/src/authoring/edit-mode-authoring.ts +278 -293
- package/src/authoring/embed-only-adapter.ts +4 -2
- package/src/authoring/first-party-hierarchy-menu.ts +7 -2
- package/src/authoring/first-party-inspector/AddSectionMenu.tsx +5 -12
- package/src/authoring/first-party-inspector/EnvironmentSection.tsx +53 -126
- package/src/authoring/first-party-inspector/HeaderSection.tsx +54 -65
- package/src/authoring/first-party-inspector/is-first-party-selection.ts +30 -11
- package/src/authoring/first-party-inspector/register.tsx +13 -2
- package/src/authoring/ingest-authoring-adapter.ts +142 -45
- package/src/authoring/mount-failure-report.ts +5 -5
- package/src/authoring/provenance.ts +41 -0
- package/src/authoring/react-dom-authoring-adapter.ts +92 -92
- package/src/authoring/react-inspector-section.tsx +489 -51
- package/src/authoring/react-world-authoring-adapter.ts +541 -306
- package/src/authoring/story-overlay.ts +1 -1
- package/src/authoring/story-types.ts +11 -0
- package/src/authoring/vgai-scene-authoring-adapter.ts +54 -25
- package/src/authoring/viewport-tool-context.ts +78 -0
- package/src/authoring/world-canvas-viewport-state.ts +35 -0
- package/src/authoring/world-hidden-viewport.ts +186 -0
- package/src/authoring/world-pan-state.ts +33 -17
- package/src/authoring/world-session-state.ts +1 -1
- package/src/authoring/world2d-authoring-adapter.ts +109 -52
- package/src/availability-tick.ts +56 -0
- package/src/browser-play-entry.ts +5 -5
- package/src/browser-project-scripts.ts +1 -0
- package/src/build-identity.ts +15 -0
- package/src/command-listener.ts +893 -19
- package/src/components/AlignToolbar.tsx +74 -63
- package/src/components/AppRoot.tsx +193 -26
- package/src/components/ApplicationMenus.tsx +272 -0
- package/src/components/AssetBrowser.tsx +1450 -293
- package/src/components/AssetEditorShell.tsx +141 -0
- package/src/components/AssetImportDetails.tsx +218 -0
- package/src/components/AssetLibraryPanel.tsx +14 -0
- package/src/components/CameraInfo.tsx +11 -20
- package/src/components/CenterDocuments.tsx +585 -0
- package/src/components/CommandPalette.tsx +75 -30
- package/src/components/ConsolePanel.css +55 -0
- package/src/components/ConsolePanel.tsx +57 -112
- package/src/components/DebugConsolePanel.tsx +461 -0
- package/src/components/DebuggerPanel.tsx +165 -0
- package/src/components/DefaultEditorLayout.tsx +167 -231
- package/src/components/GameHierarchy.tsx +873 -290
- package/src/components/GamePerformanceOverlay.tsx +46 -0
- package/src/components/HarnessChatPanel.tsx +2352 -0
- package/src/components/HistoryPanel.tsx +226 -0
- package/src/components/Inspector.tsx +153 -99
- package/src/components/InspectorToolSection.tsx +6 -4
- package/src/components/MountFailureBanner.tsx +31 -48
- package/src/components/NetworkSection.tsx +25 -14
- package/src/components/OnlineAssetBrowser.tsx +794 -343
- package/src/components/OverlayReportBanner.tsx +38 -64
- package/src/components/PerformancePanel.tsx +206 -0
- package/src/components/PlayBar.tsx +93 -263
- package/src/components/PrefabSaveDialog.tsx +55 -127
- package/src/components/ProjectHeader.css +60 -0
- package/src/components/ProjectHeader.tsx +170 -105
- package/src/components/ProjectScreen.tsx +246 -184
- package/src/components/ReactCanvasControls.tsx +185 -0
- package/src/components/ResolutionPicker.tsx +85 -0
- package/src/components/RootActivateDialog.tsx +39 -68
- package/src/components/SaveDialog.tsx +55 -127
- package/src/components/SaveStatus.tsx +11 -29
- package/src/components/StartupLoadingScreen.tsx +96 -0
- package/src/components/StateWatchPanel.tsx +302 -0
- package/src/components/StatsOverlay.tsx +12 -5
- package/src/components/StatusBar.tsx +64 -0
- package/src/components/ThemeManagerDialog.css +89 -0
- package/src/components/ThemeManagerDialog.tsx +371 -0
- package/src/components/ToolHost.tsx +26 -15
- package/src/components/Toolbar.tsx +117 -214
- package/src/components/VgaiVoxelLogo.tsx +89 -0
- package/src/components/ViewportOverlay.tsx +97 -172
- package/src/components/ViewportPanel.tsx +130 -31
- package/src/components/WorkspaceDock.tsx +891 -0
- package/src/components/WorldSelectionOverlay.tsx +51 -63
- package/src/components/WorldTextEditor.tsx +9 -5
- package/src/components/asset-documents.tsx +390 -0
- package/src/components/asset-editor-persistence.ts +44 -0
- package/src/components/asset-selection-section.tsx +338 -0
- package/src/components/asset-viewers/AudioViewer.tsx +73 -21
- package/src/components/asset-viewers/GenericJsonViewer.tsx +26 -11
- package/src/components/asset-viewers/ImageViewer.tsx +43 -11
- package/src/components/asset-viewers/InputMapViewer.tsx +512 -134
- package/src/components/asset-viewers/MaterialAssetEditor.tsx +540 -0
- package/src/components/asset-viewers/ModelViewer.tsx +805 -121
- package/src/components/asset-viewers/OnlineAssetDetail.tsx +177 -60
- package/src/components/asset-viewers/SourceAssetViewer.tsx +121 -0
- package/src/components/asset-workflow.css +506 -0
- package/src/components/build-documents.tsx +696 -0
- package/src/components/core-utilities.tsx +75 -0
- package/src/components/data-documents.tsx +364 -0
- package/src/components/{DataPanel.tsx → data-editor.tsx} +138 -288
- package/src/components/debugger-panel-state.ts +57 -0
- package/src/components/inspector-widgets/AlignmentGrid.tsx +8 -14
- package/src/components/inspector-widgets/BorderEditor.tsx +41 -33
- package/src/components/inspector-widgets/ColorPicker.tsx +39 -39
- package/src/components/inspector-widgets/FontPicker.tsx +13 -21
- package/src/components/inspector-widgets/GradientEditor.tsx +11 -19
- package/src/components/inspector-widgets/ScrubbableInput.tsx +6 -3
- package/src/components/inspector-widgets/ShadowEditor.tsx +20 -33
- package/src/components/inspector-widgets/shared.tsx +90 -49
- package/src/components/inspectors/AnimationSection.tsx +39 -40
- package/src/components/inspectors/AudioSection.tsx +28 -39
- package/src/components/inspectors/BoneAttachmentSection.tsx +19 -24
- package/src/components/inspectors/CameraSection.tsx +14 -23
- package/src/components/inspectors/ComponentsSection.tsx +39 -43
- package/src/components/inspectors/JointsSection.tsx +34 -43
- package/src/components/inspectors/LightSection.tsx +90 -35
- package/src/components/inspectors/MaterialSection.tsx +137 -174
- package/src/components/inspectors/MeshSection.tsx +9 -17
- package/src/components/inspectors/NavigationSection.tsx +8 -15
- package/src/components/inspectors/ParticleSection.tsx +91 -76
- package/src/components/inspectors/PhysicsSection.tsx +42 -46
- package/src/components/inspectors/PostProcessingSection.tsx +28 -36
- package/src/components/inspectors/RenderingSection.tsx +12 -20
- package/src/components/inspectors/ShadowSection.tsx +7 -8
- package/src/components/inspectors/SplineSection.tsx +31 -42
- package/src/components/inspectors/TransformSection.tsx +14 -22
- package/src/components/patterns/Dialog.tsx +95 -0
- package/src/components/patterns/Fields.tsx +44 -0
- package/src/components/patterns/List.tsx +25 -0
- package/src/components/patterns/StateSurface.tsx +40 -0
- package/src/components/patterns/Surfaces.tsx +115 -0
- package/src/components/patterns/Tabs.tsx +80 -0
- package/src/components/patterns/Toolbar.tsx +51 -0
- package/src/components/patterns/Tree.tsx +72 -0
- package/src/components/primitives/Button.tsx +46 -0
- package/src/components/primitives/ColorInput.tsx +11 -47
- package/src/components/primitives/EditorIcon.tsx +99 -0
- package/src/components/primitives/FormControls.tsx +71 -0
- package/src/components/primitives/JsonInput.tsx +15 -2
- package/src/components/primitives/Layout.tsx +100 -0
- package/src/components/primitives/Menu.tsx +105 -0
- package/src/components/primitives/NumberInput.tsx +8 -35
- package/src/components/primitives/Panel.tsx +49 -44
- package/src/components/primitives/SectionHeader.tsx +10 -28
- package/src/components/primitives/Text.tsx +46 -0
- package/src/components/primitives/Tooltip.tsx +20 -12
- package/src/components/primitives/Vec3Input.tsx +3 -0
- package/src/components/primitives/banner-tones.ts +33 -0
- package/src/components/primitives/editor-icons.ts +102 -0
- package/src/components/primitives/panel-header-styles.ts +30 -0
- package/src/components/project-operation-documents.tsx +226 -0
- package/src/components/status-contributions.tsx +175 -0
- package/src/components/story-documents-register.ts +15 -0
- package/src/components/story-documents.tsx +1185 -0
- package/src/components/tool-documents.tsx +110 -0
- package/src/components/workspace-dock.css +293 -0
- package/src/components/workspace-history.ts +33 -0
- package/src/components/world-documents.tsx +145 -0
- package/src/components/xstate-documents.tsx +209 -0
- package/src/composite-screenshot.ts +248 -0
- package/src/design-system/index.ts +70 -0
- package/src/design-system-stories/Foundation.stories.tsx +183 -0
- package/src/design-system-stories/Patterns.stories.tsx +251 -0
- package/src/design-system-stories/Workspace.stories.tsx +538 -0
- package/src/doctor/report.ts +57 -13
- package/src/editor-api.ts +295 -31
- package/src/editor-compatibility.ts +200 -0
- package/src/editor-console.ts +35 -17
- package/src/editor-hotkeys.ts +85 -33
- package/src/editor-store.ts +538 -233
- package/src/editor-viewport.ts +160 -34
- package/src/entity-factory.ts +14 -3
- package/src/gizmo-registry.ts +21 -5
- package/src/harness-chat-editor-context.ts +37 -0
- package/src/harness-chat-indicator.ts +68 -0
- package/src/harness-chat-layout.ts +53 -0
- package/src/harness-chat-lifecycle.ts +170 -0
- package/src/harness-chat-reconnect.ts +85 -0
- package/src/harness-chat-types.ts +409 -0
- package/src/hierarchy-drop.ts +50 -0
- package/src/hierarchy-expansion-state.ts +100 -0
- package/src/hierarchy-menu-registry.ts +1 -1
- package/src/hierarchy-projection.ts +76 -0
- package/src/history/editor-session.ts +21 -0
- package/src/history/history-commands.ts +147 -0
- package/src/history/history-service.ts +904 -0
- package/src/history/json-history-resource.ts +165 -0
- package/src/history/persistence-coordinator.ts +35 -0
- package/src/history/project-file-history.ts +266 -0
- package/src/history/project-root-history-backends.ts +53 -0
- package/src/history/resource-registry.ts +209 -0
- package/src/history/snapshot-store.ts +108 -0
- package/src/history/source-history-backend.ts +374 -0
- package/src/history/types.ts +100 -0
- package/src/hmr-registration-group.ts +67 -0
- package/src/hmr-stable-react-context.ts +23 -0
- package/src/hosted-example.ts +12 -2
- package/src/hotkeys.ts +89 -18
- package/src/ingest/discovery-react.ts +9 -10
- package/src/ingest/discovery.ts +4 -4
- package/src/ingest/discovery2d.ts +5 -5
- package/src/ingest/games/bloom-composer/vgai.game.json +3 -3
- package/src/ingest/games/embed-bundle/vgai.game.json +3 -3
- package/src/ingest/games/games-fps/vgai.game.json +3 -3
- package/src/ingest/games/iframe-reachable/vgai.game.json +3 -3
- package/src/ingest/games/interactive-cubes/vgai.game.json +3 -3
- package/src/ingest/games/mismatched-three/vgai.game.json +3 -3
- package/src/ingest/games/own-three-bundled/vgai.game.json +3 -3
- package/src/ingest/games/pointerlock-maze/vgai.game.json +7 -4
- package/src/ingest/games-2d/bubbo-bubbo/vgai.game.json +3 -3
- package/src/ingest/games-2d/bunnymark/vgai.game.json +3 -3
- package/src/ingest/games-2d/flappy/vgai.game.json +3 -3
- package/src/ingest/games-2d/flappy-pixi/vgai.game.json +3 -3
- package/src/ingest/games-2d/puzzling-potions/vgai.game.json +3 -3
- package/src/ingest/games-2d/tilemap-camera/vgai.game.json +3 -3
- package/src/ingest/games-react/react-rpg/vgai.game.json +3 -3
- package/src/ingest/types.ts +1 -1
- package/src/ingest-mode.ts +47 -52
- package/src/ingest-siblings.ts +23 -33
- package/src/initial-scene.ts +1 -2
- package/src/main.tsx +16 -8
- package/src/manifest-project.ts +10 -12
- package/src/model-thumbnail.ts +261 -26
- package/src/module-mode.ts +44 -50
- package/src/play-log-events.ts +26 -0
- package/src/play-mode.ts +495 -401
- package/src/play-scene-adoption.ts +23 -0
- package/src/playground/main.ts +68 -24
- package/src/playwright-shim.ts +473 -0
- package/src/project-manager.ts +90 -10
- package/src/project-operations.ts +112 -0
- package/src/project-story-discovery.ts +25 -0
- package/src/project-tool-discovery.ts +43 -0
- package/src/scene-serializer.ts +0 -6
- package/src/scene-sync.ts +7 -2
- package/src/script-loader.ts +3 -3
- package/src/skeleton-helper.ts +57 -0
- package/src/storage/seed.ts +68 -8
- package/src/stories/StoryPreviewMount.tsx +265 -0
- package/src/stories/action-stories.fixture.tsx +44 -0
- package/src/stories/button-stories.fixture.tsx +50 -0
- package/src/stories/compose-project-stories.ts +182 -0
- package/src/stories/loader-stories.fixture.tsx +53 -0
- package/src/stories/story-discovery.ts +130 -0
- package/src/stories/story-registry.ts +146 -0
- package/src/theme-css.ts +1049 -0
- package/src/theme-library.ts +238 -0
- package/src/theme-preference.ts +130 -0
- package/src/theme.ts +527 -0
- package/src/tool-loader.ts +80 -24
- package/src/ui-source/inspect.ts +27 -4
- package/src/ui-source/source-write-backend.ts +83 -22
- package/src/widgets/index.ts +9 -18
- package/src/workspace-aux-commands.ts +11 -0
- package/src/workspace-dock-controller.ts +64 -0
- package/src/workspace-document-ids.ts +5 -0
- package/src/workspace-document-registry.ts +367 -0
- package/src/workspace-state-persistence.ts +455 -0
- package/src/workspace-status-registry.ts +110 -0
- package/src/workspace-utility-commands.ts +49 -0
- package/src/workspace-utility-registry.ts +161 -0
- package/src/world-document-routing.ts +16 -0
- package/src/xstate-inspect/XStateMachineInspector.tsx +544 -0
- package/src/xstate-inspect/character-animation-machine.fixture.ts +74 -0
- package/src/xstate-inspect/use-live-actor-state.ts +39 -0
- package/src/xstate-inspect/xstate-graph.ts +265 -0
- package/src/xstate-inspect/xstate-layout.ts +76 -0
- package/src/z-index.ts +26 -0
- package/template/.claude/skills/editor/SKILL.md +129 -90
- package/template/AGENTS.md +1110 -0
- package/template/CLAUDE.md +1 -512
- package/template/IDIOMS.md +50 -0
- package/template/check-idioms.ts +706 -0
- package/template/manifest-entry-modules-plugin.ts +53 -0
- package/template/package.json +13 -4
- package/template/public/scenes/default.scn2d.json +6 -0
- package/template/public/scenes/default.vscn.json +7 -1
- package/template/public/scenes/untitled.vscn.json +10 -0
- package/template/scripts/playtest.ts +40 -0
- package/template/server/loopback.ts +75 -0
- package/template/server/rooms/arena-room.ts +69 -1
- package/template/server/rooms/game-room.ts +68 -1
- package/template/src/data/README.md +9 -4
- package/template/src/data/data-ref.ts +35 -0
- package/template/src/data/tuning.schema.ts +1 -1
- package/template/src/main.ts +41 -62
- package/template/src/manifest-entry-modules.d.ts +3 -0
- package/template/src/operations/README.md +88 -0
- package/template/src/runtime/pixi-adapter.ts +24 -0
- package/template/src/scripts/components/data-spinner.ts +43 -2
- package/template/src/scripts/components/spin-lap-math.ts +27 -0
- package/template/src/tools/README.md +5 -3
- package/template/src/tools/example.tool.tsx +13 -7
- package/template/src/ui/Button.stories.tsx +66 -0
- package/template/src/ui/RebindPanel.tsx +2 -6
- package/template/src/ui/game.tsx +17 -0
- package/template/tests/acceptance/example.spec.ts +64 -0
- package/template/tests/logic/example.test.ts +31 -0
- package/template/tests/logic/room.test.ts +100 -0
- package/template/tests/tsconfig.json +30 -0
- package/template/validate-manifest.ts +5 -5
- package/template/validate-scenes.ts +50 -18
- package/template/vgai.game.json +20 -13
- package/template/vite.config.ts +77 -16
- package/vite-plugin-ui-oid.ts +261 -17
- package/dist/assets/CCDIKSolver-Dz-ysxZF.js +0 -1
- package/dist/assets/SelectionBox-BXsv_6aH.js +0 -1
- package/dist/assets/browser-project-scripts-DOru4L1x.js +0 -2
- package/dist/assets/browserAll-CEkWYlFk.js +0 -1
- package/dist/assets/create-runtime-DYIWr3va.js +0 -1
- package/dist/assets/data-asset-Bb9sIM7i.js +0 -5
- package/dist/assets/game-8SDZPPgH.js +0 -1
- package/dist/assets/game-CevorxyV.js +0 -1
- package/dist/assets/game-Di7XlywD.js +0 -1
- package/dist/assets/game-DjnCaw8R.js +0 -1
- package/dist/assets/game-Q9A0aXSV.js +0 -1
- package/dist/assets/index-CPVV6pjw.js +0 -1
- package/dist/assets/index-CpOnYyeD.js +0 -213
- package/dist/assets/index-Dr8H_6Rl.js +0 -1
- package/dist/assets/index-_bCaipUk.js +0 -13650
- package/dist/assets/ingest-siblings-Dz8eEqrV.js +0 -1
- package/dist/assets/mount-manifest-Bhk0ohsl.js +0 -1
- package/dist/assets/scene-query-BivAHhJu.js +0 -1
- package/dist/assets/webworkerAll-BLrH0F6F.js +0 -1
- package/dist/assets/xstate-animation-binding-1NGLmw6y.js +0 -1
- package/dist/assets/xstate.esm-BgUD33nl.js +0 -3
- package/src/authoring/scene-ui-layer.ts +0 -485
- package/src/authoring/ui-authoring-adapter.ts +0 -325
- package/src/authoring/ui-hierarchy-menu.ts +0 -40
- package/src/authoring/ui-inspector-sections.tsx +0 -156
- package/src/authoring/undo-timeline.ts +0 -145
- package/src/components/BottomPanel.tsx +0 -326
- package/src/components/BuildPanel.tsx +0 -455
- package/src/components/FolderTree.tsx +0 -179
- package/src/components/RightPanel.tsx +0 -166
- package/src/components/WorldContextMenu.tsx +0 -257
- package/src/components/asset-viewers/AssetViewer.tsx +0 -38
- package/src/ingest/games-react/react-rpg/src/ui/game-state.tsx +0 -129
- package/src/scene-ui-mode/active-scene-ui.ts +0 -58
- package/src/scene-ui-mode/scene-ui-palette-panel.tsx +0 -152
- package/src/scene-ui-mode/scene-ui-timeline-tab.tsx +0 -29
- package/src/scene-ui-mode/ui-prefab-dialog-host.tsx +0 -74
- package/src/scene-ui-mode/ui-prefab-hierarchy-menu.ts +0 -49
- package/src/scene-ui-mode/ui-prefab-io.ts +0 -76
- package/src/ui-editor/inspector.tsx +0 -563
- package/src/ui-editor/node-ops.ts +0 -266
- package/src/ui-editor/overlay.tsx +0 -380
- package/src/ui-editor/palette.tsx +0 -114
- package/src/ui-editor/react-store.ts +0 -78
- package/src/ui-editor/timeline-panel.tsx +0 -443
- package/src/ui-editor/ui-edit-store.ts +0 -552
- package/src/ui-editor/ui-prefab.ts +0 -95
- package/template/project.json +0 -9
- package/template/src/scripts/main.ts +0 -94
- package/template/src/ui/DefaultHud.tsx +0 -40
- package/template/src/ui/game-state.tsx +0 -37
- package/template/src/ui/mount-default-hud.tsx +0 -19
- package/template/src/ui/mount-ui.ts +0 -15
- package/vite-plugin-react-world-provider.ts +0 -81
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { themeVars } from '../design-system';
|
|
2
|
+
/**
|
|
3
|
+
* XState contributions (W3, docs/EDITOR-WORKSPACE-SHELL-TODO.md §4.3/§9 W3 —
|
|
4
|
+
* inventory row B6): the permanent bottom XSTATE tab and its demo-machine
|
|
5
|
+
* fallback are GONE from production UI. What replaces them, per §4.3's
|
|
6
|
+
* truthfulness rules:
|
|
7
|
+
*
|
|
8
|
+
* - a compact **State Machine** Inspector section that renders ONLY for a
|
|
9
|
+
* selected live entity whose object carries a real `bindXStateAnimation`
|
|
10
|
+
* binding (`_xstateAnimation` user-data, engine
|
|
11
|
+
* `animation/xstate-animation-binding.ts`) — active state(s) per layer,
|
|
12
|
+
* the machine's own id as its source identity, and an `Inspect Machine`
|
|
13
|
+
* action;
|
|
14
|
+
* - `Inspect Machine` opens the REAL machine inside the bottom Debugger.
|
|
15
|
+
* That instrument observes the live Game/Scene without replacing it;
|
|
16
|
+
* - an `Open State Machine: <id>` palette action per live registered
|
|
17
|
+
* machine (`listLiveMachines` scans the live object map for real
|
|
18
|
+
* bindings — the only truthful registry that exists today).
|
|
19
|
+
*
|
|
20
|
+
* NO fabricated hierarchy rows, NO demo machine anywhere: the shipped
|
|
21
|
+
* `character-animation-machine.fixture.ts` is now a TEST fixture only
|
|
22
|
+
* (`test/xstate-fixture-ban.test.ts` pins that), and every open path here
|
|
23
|
+
* requires an actual live binding — with none, nothing renders and nothing
|
|
24
|
+
* can be opened. The template project has no XState machine, so a template
|
|
25
|
+
* session shows no XState UI at all.
|
|
26
|
+
*
|
|
27
|
+
* `Open Source` (§4.3 mentions it MAY exist): deliberately NOT built —
|
|
28
|
+
* neither XState nor the binding carries a machine→source-file identity, and
|
|
29
|
+
* fabricating one (e.g. grepping for the machine id) would violate §4.3's
|
|
30
|
+
* "no ids decoded for magic words". When the engine grows a real
|
|
31
|
+
* machine→module provenance, the section is where it lands.
|
|
32
|
+
*
|
|
33
|
+
* Editing stays a code change: the graph view is inspect-only
|
|
34
|
+
* (docs/decisions/e4-xstate-authoring-boundary.md — its ownership decision
|
|
35
|
+
* is unchanged; only its bottom-dock placement is superseded, TODO §12).
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
import {
|
|
39
|
+
getXStateAnimationBindingsVersion,
|
|
40
|
+
subscribeXStateAnimationBindings,
|
|
41
|
+
type XStateAnimationBinding,
|
|
42
|
+
} from '@engine/animation/xstate-animation-binding';
|
|
43
|
+
import { getUserData } from '@engine/scene/user-data';
|
|
44
|
+
import { useSyncExternalStore } from 'react';
|
|
45
|
+
import type * as THREE from 'three';
|
|
46
|
+
import type { AnyStateMachine } from 'xstate';
|
|
47
|
+
import type { EditorAction } from '../action-registry';
|
|
48
|
+
import { Button } from '../design-system';
|
|
49
|
+
import { createHmrRegistrationGroup } from '../hmr-registration-group';
|
|
50
|
+
import { registerInspectorSections } from '../inspector-section-registry';
|
|
51
|
+
import { useLiveActorState } from '../xstate-inspect/use-live-actor-state';
|
|
52
|
+
import { openDebuggerPanel } from './debugger-panel-state';
|
|
53
|
+
|
|
54
|
+
/** The narrow store surface the XState contributions read: the live object
|
|
55
|
+
* map where real bindings live. */
|
|
56
|
+
export interface XStateInspectorStore {
|
|
57
|
+
readonly objectMap: Map<string, THREE.Object3D>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let _store: XStateInspectorStore | null = null;
|
|
61
|
+
const registrationGroup = createHmrRegistrationGroup(import.meta.hot, 'xstate-inspector');
|
|
62
|
+
|
|
63
|
+
/** The selected entity's live binding, or `undefined` — the ONLY discovery
|
|
64
|
+
* path (§4.3: a machine is truthfully discoverable only through a live
|
|
65
|
+
* `bindXStateAnimation` binding). */
|
|
66
|
+
export function liveBindingFor(nodeId: string): XStateAnimationBinding | undefined {
|
|
67
|
+
const object = _store?.objectMap.get(nodeId);
|
|
68
|
+
return object ? getUserData(object, '_xstateAnimation') : undefined;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Wire the store and register the Inspector section (idempotent — the
|
|
73
|
+
* registration is permanent; `match` reads the CURRENT store, so with no
|
|
74
|
+
* configured store nothing ever matches). Returns a cleanup that clears the
|
|
75
|
+
* store reference (project switch / layout teardown).
|
|
76
|
+
*/
|
|
77
|
+
export function configureXStateInspector(store: XStateInspectorStore): () => void {
|
|
78
|
+
_store = store;
|
|
79
|
+
registrationGroup.ensure((track) => {
|
|
80
|
+
track(
|
|
81
|
+
registerInspectorSections({
|
|
82
|
+
// §4.3 gating: matches ONLY a real node whose live object carries a
|
|
83
|
+
// real binding. Template entities (no machine) never match — the
|
|
84
|
+
// section must not render for them (unit-pinned).
|
|
85
|
+
match: (node) => node !== null && liveBindingFor(node.id) !== undefined,
|
|
86
|
+
Section: XStateMachineSection,
|
|
87
|
+
}),
|
|
88
|
+
);
|
|
89
|
+
});
|
|
90
|
+
return () => {
|
|
91
|
+
if (_store === store) _store = null;
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Every live machine, by scanning the live object map for real bindings —
|
|
96
|
+
* the `Open State Machine…` command's truthful listing. */
|
|
97
|
+
export function listLiveMachines(): {
|
|
98
|
+
nodeId: string;
|
|
99
|
+
machineId: string;
|
|
100
|
+
label: string;
|
|
101
|
+
}[] {
|
|
102
|
+
if (!_store) return [];
|
|
103
|
+
const seen = new Set<XStateAnimationBinding>();
|
|
104
|
+
const out: { nodeId: string; machineId: string; label: string }[] = [];
|
|
105
|
+
for (const [nodeId, object] of _store.objectMap) {
|
|
106
|
+
const binding = getUserData(object, '_xstateAnimation');
|
|
107
|
+
if (!binding || seen.has(binding)) continue;
|
|
108
|
+
seen.add(binding);
|
|
109
|
+
const machine = binding.actor.logic as AnyStateMachine;
|
|
110
|
+
out.push({ nodeId, machineId: machine.id, label: object.name || nodeId });
|
|
111
|
+
}
|
|
112
|
+
return out;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** `Open State Machine: <id>` palette actions — one per live machine; an
|
|
116
|
+
* empty list when none are live (never a demo entry). */
|
|
117
|
+
export function buildXStateMachineActions(): EditorAction[] {
|
|
118
|
+
return listLiveMachines().map((m) => ({
|
|
119
|
+
id: `xstate-machine:${m.machineId}:${m.nodeId}`,
|
|
120
|
+
label: `Open State Machine: ${m.machineId}`,
|
|
121
|
+
category: 'action' as const,
|
|
122
|
+
execute: () => void openXStateGraphDocument(m.nodeId),
|
|
123
|
+
}));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Open a live machine in the bottom Debugger while preserving the Game. */
|
|
127
|
+
export function openXStateGraphDocument(nodeId: string): boolean {
|
|
128
|
+
const binding = liveBindingFor(nodeId);
|
|
129
|
+
if (!_store || !binding) return false;
|
|
130
|
+
openDebuggerPanel('machines', nodeId);
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Test-only reset — PAIR with `__resetInspectorSectionRegistryForTest`
|
|
135
|
+
* (clearing `_sectionRegistered` without clearing the section registry
|
|
136
|
+
* would double-register on the next configure). */
|
|
137
|
+
export function __resetXStateContributionsForTest(): void {
|
|
138
|
+
_store = null;
|
|
139
|
+
registrationGroup.reset();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// --- The compact Inspector section (§4.3) -----------------------------------
|
|
143
|
+
|
|
144
|
+
/** Registered via `configureXStateInspector` — renders ONLY when `match`
|
|
145
|
+
* found a real live binding for the selected node. */
|
|
146
|
+
export function XStateMachineSection({ nodeId }: { nodeId: string | null }) {
|
|
147
|
+
useSyncExternalStore(
|
|
148
|
+
subscribeXStateAnimationBindings,
|
|
149
|
+
getXStateAnimationBindingsVersion,
|
|
150
|
+
getXStateAnimationBindingsVersion,
|
|
151
|
+
);
|
|
152
|
+
const binding = nodeId ? liveBindingFor(nodeId) : undefined;
|
|
153
|
+
const machine = binding ? (binding.actor.logic as AnyStateMachine) : undefined;
|
|
154
|
+
// Follow live transitions (re-render per snapshot) — the layer readout
|
|
155
|
+
// below re-reads `getActiveLayers()` on each.
|
|
156
|
+
useLiveActorState(machine as AnyStateMachine, binding?.actor);
|
|
157
|
+
if (!binding || !machine || !nodeId) return null;
|
|
158
|
+
return (
|
|
159
|
+
<div
|
|
160
|
+
data-testid="inspector-xstate-section"
|
|
161
|
+
style={{
|
|
162
|
+
pointerEvents: 'auto',
|
|
163
|
+
padding: 8,
|
|
164
|
+
borderBottom: `1px solid ${themeVars.boundary.default}`,
|
|
165
|
+
}}
|
|
166
|
+
>
|
|
167
|
+
<div
|
|
168
|
+
style={{
|
|
169
|
+
fontSize: 11,
|
|
170
|
+
color: themeVars.content.dim,
|
|
171
|
+
textTransform: 'uppercase',
|
|
172
|
+
marginBottom: 4,
|
|
173
|
+
}}
|
|
174
|
+
>
|
|
175
|
+
State Machine
|
|
176
|
+
</div>
|
|
177
|
+
<div
|
|
178
|
+
style={{ fontSize: 11, color: themeVars.content.primary, fontWeight: 600, marginBottom: 2 }}
|
|
179
|
+
>
|
|
180
|
+
{machine.id}
|
|
181
|
+
</div>
|
|
182
|
+
<div style={{ fontSize: 10, color: themeVars.content.muted, marginBottom: 4 }}>
|
|
183
|
+
<span style={{ color: themeVars.semantic.success, fontWeight: 700 }}>●</span> Live —{' '}
|
|
184
|
+
{binding.getActiveLayers().map((layer, i) => (
|
|
185
|
+
<span key={layer.layer} data-testid={`xstate-section-layer-${layer.layer}`}>
|
|
186
|
+
{i > 0 && ' · '}
|
|
187
|
+
{layer.layer}:{' '}
|
|
188
|
+
<span
|
|
189
|
+
style={{ fontFamily: themeVars.typography.mono, color: themeVars.accent.default }}
|
|
190
|
+
>
|
|
191
|
+
{layer.stateId}
|
|
192
|
+
</span>
|
|
193
|
+
</span>
|
|
194
|
+
))}
|
|
195
|
+
</div>
|
|
196
|
+
<div style={{ fontSize: 10, color: themeVars.content.dim, marginBottom: 6 }}>
|
|
197
|
+
Source: hand-authored XState TypeScript — inspect-only
|
|
198
|
+
(docs/decisions/e4-xstate-authoring-boundary.md).
|
|
199
|
+
</div>
|
|
200
|
+
<Button
|
|
201
|
+
size="compact"
|
|
202
|
+
data-testid="xstate-open-graph"
|
|
203
|
+
onClick={() => void openXStateGraphDocument(nodeId)}
|
|
204
|
+
>
|
|
205
|
+
Inspect Machine
|
|
206
|
+
</Button>
|
|
207
|
+
</div>
|
|
208
|
+
);
|
|
209
|
+
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #146 — full-stack play screenshot: the game canvas(es) PLUS the DOM UI
|
|
3
|
+
* layers stacked over/beside them (React adapter roots,
|
|
4
|
+
* world layers), composited into one PNG. This is "what the game looks
|
|
5
|
+
* like" for agent eyes — a canvas-only capture of a game whose score/health
|
|
6
|
+
* lives in the HUD reads as a different (often emptier) game than the one
|
|
7
|
+
* the human is watching.
|
|
8
|
+
*
|
|
9
|
+
* How: each `<canvas>` child of the play container is drawn onto an
|
|
10
|
+
* offscreen 2D canvas at its on-screen rect (readable because every canvas
|
|
11
|
+
* the runtime mounts sets `preserveDrawingBuffer: true` — see
|
|
12
|
+
* `create-runtime.ts`), then the NON-canvas children are serialized into an
|
|
13
|
+
* SVG `<foreignObject>` and drawn on top. The foreignObject leg is faithful
|
|
14
|
+
* by construction: the HUD rule is inline-styles-only (CLAUDE.md), and
|
|
15
|
+
* react-world layers are engine-mounted with inline positioning — so a
|
|
16
|
+
* detached serialization loses no styling. The SVG rides a `data:` URL with
|
|
17
|
+
* no external references, which keeps the output canvas untainted (the same
|
|
18
|
+
* mechanism html-to-image libraries rely on).
|
|
19
|
+
*
|
|
20
|
+
* Editor chrome is deliberately OUT of scope (docs/SHARED-SESSION-SPEC.md —
|
|
21
|
+
* capturing it needs CDP, deferred as an opt-in niche).
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
export interface OverlaySvg {
|
|
25
|
+
svg: string;
|
|
26
|
+
/** How many non-canvas layers were serialized (0 ⇒ callers skip the leg). */
|
|
27
|
+
overlayCount: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Serialize the play container's NON-canvas children into a standalone SVG
|
|
32
|
+
* sized `width`×`height` (CSS pixels). Children are cloned into a neutral
|
|
33
|
+
* relatively-positioned wrapper — never the container itself, whose own
|
|
34
|
+
* styling comes from editor CSS classes that a detached serialization would
|
|
35
|
+
* silently drop. Returns null when there is nothing but canvases to show.
|
|
36
|
+
* Pure DOM (no rasterizing), so it's unit-testable under jsdom.
|
|
37
|
+
*/
|
|
38
|
+
export function buildOverlaySvg(
|
|
39
|
+
container: HTMLElement,
|
|
40
|
+
width: number,
|
|
41
|
+
height: number,
|
|
42
|
+
): OverlaySvg | null {
|
|
43
|
+
const overlays = Array.from(container.children).filter(
|
|
44
|
+
(el) => el.tagName.toLowerCase() !== 'canvas',
|
|
45
|
+
);
|
|
46
|
+
if (overlays.length === 0) return null;
|
|
47
|
+
|
|
48
|
+
const wrapper = container.ownerDocument.createElement('div');
|
|
49
|
+
wrapper.setAttribute('style', `position:relative;width:${width}px;height:${height}px;margin:0;`);
|
|
50
|
+
// Snapshot a stable DOM frame. CSS animations/transitions inside an SVG
|
|
51
|
+
// foreignObject can be rasterized while Chrome is between compositor
|
|
52
|
+
// layers, producing large opaque-black rectangles even though the live DOM
|
|
53
|
+
// is fine (reproduced by the 2048 dogfood run during a tile-pop frame).
|
|
54
|
+
// Disabling motion in the detached clone leaves the live game untouched
|
|
55
|
+
// and makes the captured evidence atomic.
|
|
56
|
+
const freezeMotion = container.ownerDocument.createElement('style');
|
|
57
|
+
freezeMotion.textContent =
|
|
58
|
+
'*,*::before,*::after{animation:none!important;transition:none!important;caret-color:transparent!important;' +
|
|
59
|
+
'backdrop-filter:none!important;-webkit-backdrop-filter:none!important;}';
|
|
60
|
+
wrapper.appendChild(freezeMotion);
|
|
61
|
+
for (const el of overlays) {
|
|
62
|
+
const clone = el.cloneNode(true) as Element;
|
|
63
|
+
// A canvas nested INSIDE an overlay layer (react roots may render one)
|
|
64
|
+
// serializes as an empty box — strip it rather than ship a lying blank.
|
|
65
|
+
for (const nested of Array.from(clone.querySelectorAll('canvas'))) nested.remove();
|
|
66
|
+
wrapper.appendChild(clone);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const serialized = new XMLSerializer().serializeToString(wrapper);
|
|
70
|
+
const svg =
|
|
71
|
+
`<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}">` +
|
|
72
|
+
`<foreignObject width="100%" height="100%">${serialized}</foreignObject></svg>`;
|
|
73
|
+
return { svg, overlayCount: overlays.length };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface CompositeCapture {
|
|
77
|
+
base64: string;
|
|
78
|
+
mimeType: 'image/png';
|
|
79
|
+
/** Honest capture provenance: how many canvases were drawn, and how many
|
|
80
|
+
* DOM layers rode the foreignObject leg. */
|
|
81
|
+
layers: { canvases: number; domOverlays: number };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Wait briefly for finite CSS/Web Animations to settle before serializing a
|
|
85
|
+
* DOM frame. Chromium can rasterize an actively animated foreignObject as
|
|
86
|
+
* opaque black compositor tiles; a settled frame is both more legible and
|
|
87
|
+
* closer to what a human sees after the interaction. Infinite ambience never
|
|
88
|
+
* blocks evidence capture, and the timeout keeps a broken animation from
|
|
89
|
+
* wedging the relay. */
|
|
90
|
+
export async function waitForFiniteMotion(container: HTMLElement, maxWaitMs = 350): Promise<void> {
|
|
91
|
+
if (typeof container.getAnimations !== 'function') return;
|
|
92
|
+
const animations = container.getAnimations({ subtree: true }).filter((animation) => {
|
|
93
|
+
const endTime = animation.effect?.getComputedTiming().endTime;
|
|
94
|
+
return typeof endTime === 'number' && Number.isFinite(endTime);
|
|
95
|
+
});
|
|
96
|
+
if (animations.length === 0) return;
|
|
97
|
+
await Promise.race([
|
|
98
|
+
Promise.allSettled(animations.map((animation) => animation.finished)),
|
|
99
|
+
new Promise<void>((resolve) => setTimeout(resolve, maxWaitMs)),
|
|
100
|
+
]);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Cross two paint boundaries when the tab is rendering, but never depend on
|
|
104
|
+
* rAF firing: Chromium suspends it for hidden/background editor tabs, and a
|
|
105
|
+
* screenshot is specifically expected to work there. The timer is the
|
|
106
|
+
* bounded fallback, not an extra delay after a successful frame. */
|
|
107
|
+
export async function waitForPaint(container: HTMLElement, maxWaitMs = 75): Promise<void> {
|
|
108
|
+
const requestFrame = container.ownerDocument.defaultView?.requestAnimationFrame.bind(
|
|
109
|
+
container.ownerDocument.defaultView,
|
|
110
|
+
);
|
|
111
|
+
if (!requestFrame) return;
|
|
112
|
+
await new Promise<void>((resolve) => {
|
|
113
|
+
let settled = false;
|
|
114
|
+
const finish = () => {
|
|
115
|
+
if (settled) return;
|
|
116
|
+
settled = true;
|
|
117
|
+
clearTimeout(timeout);
|
|
118
|
+
resolve();
|
|
119
|
+
};
|
|
120
|
+
const timeout = setTimeout(finish, maxWaitMs);
|
|
121
|
+
requestFrame(() => requestFrame(finish));
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Rasterize the full play container (canvases + DOM UI layers) to a PNG.
|
|
127
|
+
* Output is sized to the container's CSS rect — the same geometry the human
|
|
128
|
+
* sees. Throws on any rasterization failure; the caller
|
|
129
|
+
* (`handleBridgeScreenshot`) degrades to the canvas-only capture and marks
|
|
130
|
+
* the result honestly rather than failing the op.
|
|
131
|
+
*/
|
|
132
|
+
export async function capturePlayComposite(container: HTMLElement): Promise<CompositeCapture> {
|
|
133
|
+
// Debug state can become observable just before React commits the matching
|
|
134
|
+
// DOM. Cross two paint boundaries first, then let newly-mounted finite
|
|
135
|
+
// transitions finish, then cross one more stable paint before cloning.
|
|
136
|
+
await waitForPaint(container);
|
|
137
|
+
// Chrome's foreignObject renderer can retain opaque-black compositor tiles
|
|
138
|
+
// for several frames after a React-only tree replaces an overlay (observed
|
|
139
|
+
// on an immediate game-over -> restart capture even after its 180ms tile
|
|
140
|
+
// animation reported finished). Give DOM-only roots one short compositor
|
|
141
|
+
// settle window; canvas-backed games do not use this fragile leg as their
|
|
142
|
+
// sole image and keep the faster path.
|
|
143
|
+
if (container.querySelectorAll('canvas').length === 0) {
|
|
144
|
+
await new Promise<void>((resolve) => setTimeout(resolve, 650));
|
|
145
|
+
}
|
|
146
|
+
await waitForFiniteMotion(container);
|
|
147
|
+
await waitForPaint(container);
|
|
148
|
+
return capturePlayCompositeAttempt(container, 2);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function hasExcessTransparentPixels(
|
|
152
|
+
rgba: Uint8ClampedArray,
|
|
153
|
+
pixelCount: number,
|
|
154
|
+
limit = 0.03,
|
|
155
|
+
): boolean {
|
|
156
|
+
if (pixelCount <= 0) return false;
|
|
157
|
+
let transparent = 0;
|
|
158
|
+
for (let alpha = 3; alpha < rgba.length; alpha += 4) {
|
|
159
|
+
if (rgba[alpha] === 0) transparent += 1;
|
|
160
|
+
}
|
|
161
|
+
return transparent / pixelCount > limit;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Chromium's foreignObject compositor sometimes fills a torn tile with
|
|
165
|
+
* fully-opaque black rather than transparency. Catch only a LARGE region of
|
|
166
|
+
* near-pure black; a legitimately dark game merely takes the bounded retry
|
|
167
|
+
* path and is still returned unchanged when it remains dark. */
|
|
168
|
+
export function hasExcessNearBlackPixels(
|
|
169
|
+
rgba: Uint8ClampedArray,
|
|
170
|
+
pixelCount: number,
|
|
171
|
+
limit = 0.08,
|
|
172
|
+
): boolean {
|
|
173
|
+
if (pixelCount <= 0) return false;
|
|
174
|
+
let black = 0;
|
|
175
|
+
for (let offset = 0; offset < rgba.length; offset += 4) {
|
|
176
|
+
if (
|
|
177
|
+
rgba[offset]! <= 8 &&
|
|
178
|
+
rgba[offset + 1]! <= 8 &&
|
|
179
|
+
rgba[offset + 2]! <= 8 &&
|
|
180
|
+
rgba[offset + 3]! >= 250
|
|
181
|
+
) {
|
|
182
|
+
black += 1;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return black / pixelCount > limit;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async function capturePlayCompositeAttempt(
|
|
189
|
+
container: HTMLElement,
|
|
190
|
+
retriesRemaining: number,
|
|
191
|
+
): Promise<CompositeCapture> {
|
|
192
|
+
const rect = container.getBoundingClientRect();
|
|
193
|
+
const width = Math.max(1, Math.round(rect.width));
|
|
194
|
+
const height = Math.max(1, Math.round(rect.height));
|
|
195
|
+
|
|
196
|
+
const out = container.ownerDocument.createElement('canvas');
|
|
197
|
+
out.width = width;
|
|
198
|
+
out.height = height;
|
|
199
|
+
const ctx2d = out.getContext('2d');
|
|
200
|
+
if (!ctx2d) throw new Error('composite screenshot: no 2d canvas context');
|
|
201
|
+
|
|
202
|
+
const canvases = Array.from(container.querySelectorAll('canvas'));
|
|
203
|
+
for (const c of canvases) {
|
|
204
|
+
const r = c.getBoundingClientRect();
|
|
205
|
+
ctx2d.drawImage(c, r.left - rect.left, r.top - rect.top, r.width, r.height);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
let domOverlays = 0;
|
|
209
|
+
const overlay = buildOverlaySvg(container, width, height);
|
|
210
|
+
if (overlay) {
|
|
211
|
+
const img = new Image();
|
|
212
|
+
img.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(overlay.svg)}`;
|
|
213
|
+
await img.decode();
|
|
214
|
+
ctx2d.drawImage(img, 0, 0, width, height);
|
|
215
|
+
domOverlays = overlay.overlayCount;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// A Chromium foreignObject can occasionally decode while React is between
|
|
219
|
+
// compositor frames, leaving large transparent rectangles in an otherwise
|
|
220
|
+
// opaque React-only game. Those pixels show up as black in the PNG. Detect
|
|
221
|
+
// that lying frame and retry after a real paint; rounded-corner edge pixels
|
|
222
|
+
// stay far below the 3% threshold. Canvas-backed games already have their
|
|
223
|
+
// canvas underneath, so this retry is specific to DOM-only captures.
|
|
224
|
+
if (canvases.length === 0 && domOverlays > 0 && retriesRemaining > 0) {
|
|
225
|
+
try {
|
|
226
|
+
const pixels = ctx2d.getImageData(0, 0, width, height);
|
|
227
|
+
if (
|
|
228
|
+
hasExcessTransparentPixels(pixels.data, width * height) ||
|
|
229
|
+
hasExcessNearBlackPixels(pixels.data, width * height)
|
|
230
|
+
) {
|
|
231
|
+
await new Promise<void>((resolve) => setTimeout(resolve, 300));
|
|
232
|
+
await waitForPaint(container);
|
|
233
|
+
return capturePlayCompositeAttempt(container, retriesRemaining - 1);
|
|
234
|
+
}
|
|
235
|
+
} catch {
|
|
236
|
+
// If readback is unavailable, preserve the existing honest capture or
|
|
237
|
+
// let toDataURL surface the underlying taint below.
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const dataUrl = out.toDataURL('image/png');
|
|
242
|
+
const comma = dataUrl.indexOf(',');
|
|
243
|
+
return {
|
|
244
|
+
base64: comma >= 0 ? dataUrl.slice(comma + 1) : dataUrl,
|
|
245
|
+
mimeType: 'image/png',
|
|
246
|
+
layers: { canvases: canvases.length, domOverlays },
|
|
247
|
+
};
|
|
248
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The single internal editor design-system boundary.
|
|
3
|
+
*
|
|
4
|
+
* Product surfaces import from this module. Theme definitions, CSS recipes,
|
|
5
|
+
* primitives, and recurring patterns remain implementation details below it.
|
|
6
|
+
* Project-authored tools receive the same surface through `@editor/widgets`.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export * from '../components/patterns/Dialog';
|
|
10
|
+
export * from '../components/patterns/Fields';
|
|
11
|
+
export * from '../components/patterns/List';
|
|
12
|
+
export * from '../components/patterns/StateSurface';
|
|
13
|
+
export * from '../components/patterns/Surfaces';
|
|
14
|
+
export * from '../components/patterns/Tabs';
|
|
15
|
+
export * from '../components/patterns/Toolbar';
|
|
16
|
+
export * from '../components/patterns/Tree';
|
|
17
|
+
export * from '../components/primitives/Button';
|
|
18
|
+
export * from '../components/primitives/banner-tones';
|
|
19
|
+
export * from '../components/primitives/ColorInput';
|
|
20
|
+
export * from '../components/primitives/EditorIcon';
|
|
21
|
+
export { editorIcons } from '../components/primitives/editor-icons';
|
|
22
|
+
export * from '../components/primitives/FormControls';
|
|
23
|
+
export * from '../components/primitives/JsonInput';
|
|
24
|
+
export * from '../components/primitives/Layout';
|
|
25
|
+
export * from '../components/primitives/Menu';
|
|
26
|
+
export * from '../components/primitives/NumberInput';
|
|
27
|
+
export * from '../components/primitives/Panel';
|
|
28
|
+
export * from '../components/primitives/SectionHeader';
|
|
29
|
+
export * from '../components/primitives/Text';
|
|
30
|
+
export * from '../components/primitives/Tooltip';
|
|
31
|
+
export * from '../components/primitives/Vec3Input';
|
|
32
|
+
|
|
33
|
+
// Product surfaces consume runtime theme references through this boundary as
|
|
34
|
+
// they migrate away from direct `theme.ts` imports. Keeping the references
|
|
35
|
+
// here preserves custom-theme behavior without exposing design-system internals.
|
|
36
|
+
export {
|
|
37
|
+
accent,
|
|
38
|
+
accentMuted,
|
|
39
|
+
bg,
|
|
40
|
+
border,
|
|
41
|
+
chromeSize,
|
|
42
|
+
controlSize,
|
|
43
|
+
danger,
|
|
44
|
+
dangerFaint,
|
|
45
|
+
dangerMuted,
|
|
46
|
+
EDITOR_THEME_CLASS,
|
|
47
|
+
type EditorTheme,
|
|
48
|
+
type EditorThemeId,
|
|
49
|
+
type EditorThemeVariable,
|
|
50
|
+
editorThemes,
|
|
51
|
+
editorThemeVariables,
|
|
52
|
+
fontMono,
|
|
53
|
+
fontSans,
|
|
54
|
+
fontSize,
|
|
55
|
+
fontWeight,
|
|
56
|
+
graphiteDarkEditorTheme,
|
|
57
|
+
midnightHighContrastEditorTheme,
|
|
58
|
+
radius,
|
|
59
|
+
scrim,
|
|
60
|
+
shadow,
|
|
61
|
+
space,
|
|
62
|
+
strokeWidth,
|
|
63
|
+
success,
|
|
64
|
+
successMuted,
|
|
65
|
+
text,
|
|
66
|
+
themeVars,
|
|
67
|
+
warn,
|
|
68
|
+
warnMuted,
|
|
69
|
+
} from '../theme';
|
|
70
|
+
export { zIndex } from '../z-index';
|