@volter/editor-game 0.5.65
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/NOTICE +23 -0
- package/contributions/asset-budget-asset.menu.ts +26 -0
- package/contributions/asset-budget.action.ts +18 -0
- package/contributions/asset-budget.document.tsx +21 -0
- package/contributions/asset-budget.menu.ts +22 -0
- package/contributions/audio-unlock.service.ts +17 -0
- package/contributions/audio.utility.tsx +15 -0
- package/contributions/autoplay.service.ts +48 -0
- package/contributions/bridge.command.ts +172 -0
- package/contributions/build-profiles.document.tsx +21 -0
- package/contributions/build-progress.status.tsx +45 -0
- package/contributions/build.action.ts +26 -0
- package/contributions/build.command.ts +27 -0
- package/contributions/build.header.tsx +37 -0
- package/contributions/build.menu.ts +31 -0
- package/contributions/build.service.ts +31 -0
- package/contributions/connection.status.tsx +43 -0
- package/contributions/coverage.service.ts +118 -0
- package/contributions/edit-mode-audio.service.ts +23 -0
- package/contributions/edit-mode-networking.service.ts +30 -0
- package/contributions/game-document.service.ts +25 -0
- package/contributions/game-eval.command.ts +210 -0
- package/contributions/game.layout.ts +19 -0
- package/contributions/gameplay.command.ts +255 -0
- package/contributions/generation.service.ts +96 -0
- package/contributions/generations.status.tsx +53 -0
- package/contributions/ingest.service.ts +80 -0
- package/contributions/instances.command.ts +75 -0
- package/contributions/navmesh.menu.ts +39 -0
- package/contributions/navmesh.service.ts +19 -0
- package/contributions/network.utility.tsx +17 -0
- package/contributions/play.command.ts +355 -0
- package/contributions/profiler.action.ts +16 -0
- package/contributions/profiler.menu.ts +22 -0
- package/contributions/profiler.utility.tsx +17 -0
- package/contributions/react/component-board.service.ts +21 -0
- package/contributions/react/design-time-mount.service.ts +60 -0
- package/contributions/react/pasteboard.action.ts +41 -0
- package/contributions/react/react-inspector.service.ts +85 -0
- package/contributions/react/story-documents.service.ts +44 -0
- package/contributions/scene-document.service.ts +32 -0
- package/contributions/state-watch.action.ts +17 -0
- package/contributions/state-watch.menu.ts +23 -0
- package/contributions/state-watch.utility.tsx +20 -0
- package/contributions/team-playtest.service.ts +124 -0
- package/contributions/three/camera-runtime.inspector.tsx +34 -0
- package/contributions/three/component-board.service.ts +24 -0
- package/contributions/three/component-verbs.command.ts +110 -0
- package/contributions/three/component-verbs.service.ts +92 -0
- package/contributions/three/constraints.inspector.tsx +34 -0
- package/contributions/three/model-asset-sections.service.ts +26 -0
- package/contributions/three/reflection-probe-capture.inspector.tsx +32 -0
- package/contributions/three/story-documents.service.ts +31 -0
- package/contributions/three/three-authoring.service.ts +66 -0
- package/contributions/transport.header.tsx +19 -0
- package/contributions/xstate-behavior.action.ts +42 -0
- package/contributions/xstate-behavior.document.tsx +73 -0
- package/contributions/xstate-behavior.inspector.tsx +28 -0
- package/contributions/xstate-behavior.menu.ts +23 -0
- package/package.json +144 -0
- package/src/asset-budget/AssetBudgetPanel.tsx +1172 -0
- package/src/asset-budget/asset-budget-model.ts +799 -0
- package/src/asset-budget/basis-encoder.ts +165 -0
- package/src/asset-budget/gltf-io.ts +154 -0
- package/src/asset-budget/gltf-optimize.ts +384 -0
- package/src/asset-budget/image-dims.ts +78 -0
- package/src/asset-budget/optimize-apply.ts +221 -0
- package/src/audio/AudioDebuggerPanel.tsx +457 -0
- package/src/audio/audio-debugger-model.ts +87 -0
- package/src/bridge/call.ts +60 -0
- package/src/bridge/dispatch.ts +459 -0
- package/src/bridge/live-frames.ts +25 -0
- package/src/bridge/screenshot.ts +316 -0
- package/src/build/BuildProfilesPanel.tsx +476 -0
- package/src/build/build-session.ts +247 -0
- package/src/build/format-bytes.ts +14 -0
- package/src/command-results.ts +36 -0
- package/src/coverage/live-authoring-surface.ts +30 -0
- package/src/coverage/live-project-verbs.ts +162 -0
- package/src/coverage/native-system-coverage.ts +143 -0
- package/src/coverage/root-coverage.ts +79 -0
- package/src/coverage/session-coverage.ts +193 -0
- package/src/design-system-stories/ApplicationChrome.stories.tsx +100 -0
- package/src/design-system-stories/InspectorNarrowBodies.stories.tsx +406 -0
- package/src/edit-mode/edit-mode-audio.ts +56 -0
- package/src/edit-mode/edit-mode-networking.ts +112 -0
- package/src/game-document/DevicePresetPicker.tsx +84 -0
- package/src/game-document/GameCaptureFrameButton.tsx +45 -0
- package/src/game-document/GameDocument.tsx +215 -0
- package/src/game-document/GamePanel.tsx +662 -0
- package/src/game-document/InstanceInspectorPicker.tsx +183 -0
- package/src/game-document/crowd-debug.ts +183 -0
- package/src/game-document/device-preview.ts +336 -0
- package/src/game-document/game-view-store.ts +107 -0
- package/src/game-document/physics-debug.ts +187 -0
- package/src/generation/GenerationActivity.tsx +421 -0
- package/src/generation/GenerationGallery.css +231 -0
- package/src/generation/generation-documents.tsx +338 -0
- package/src/generation/generation-jobs.ts +128 -0
- package/src/generation/generation-presentation.ts +257 -0
- package/src/host/adapter-reach.ts +445 -0
- package/src/host/adapter-runtime-bindings.ts +303 -0
- package/src/host/after-paint.ts +112 -0
- package/src/host/api/configurations.ts +66 -0
- package/src/host/authoring/babylon-authoring-adapter.ts +703 -0
- package/src/host/authoring/canvas-runtime-recognition.ts +50 -0
- package/src/host/authoring/contract-hierarchy-authoring.ts +144 -0
- package/src/host/authoring/contract-scenes-stories.ts +204 -0
- package/src/host/authoring/creation-site-related.ts +55 -0
- package/src/host/authoring/ephemeral-persistence.ts +29 -0
- package/src/host/authoring/gesture-persist.ts +84 -0
- package/src/host/authoring/ingest-data-writer.ts +232 -0
- package/src/host/authoring/ingest-source-persistence.ts +826 -0
- package/src/host/authoring/mount-isolated-pixi-screen.ts +250 -0
- package/src/host/authoring/mounted-authoring.ts +41 -0
- package/src/host/authoring/owned-pixi-ticker-listeners.ts +96 -0
- package/src/host/authoring/phaser-live-authoring-adapter.ts +265 -0
- package/src/host/authoring/pixi-authoring-adapter.ts +1554 -0
- package/src/host/authoring/pixi-creation-site-write-target.ts +60 -0
- package/src/host/authoring/pixi-isolation-assets.ts +25 -0
- package/src/host/authoring/pixi-live-write-target.ts +979 -0
- package/src/host/authoring/pixi-source-identity.ts +141 -0
- package/src/host/authoring/pixi-still-presentation.ts +71 -0
- package/src/host/authoring/pixi-structure-history.ts +237 -0
- package/src/host/authoring/pixi-transform-channels.ts +205 -0
- package/src/host/authoring/selection-remount-handoff.ts +23 -0
- package/src/host/authoring/source-persistence-backend.ts +373 -0
- package/src/host/authoring/source-refresh-revisions.ts +81 -0
- package/src/host/authoring/struct-write-pipe.ts +143 -0
- package/src/host/auto-frame-window.ts +89 -0
- package/src/host/binding-resolver.ts +393 -0
- package/src/host/browser-transpile.ts +631 -0
- package/src/host/canvas-entry-runtime.ts +95 -0
- package/src/host/components/CameraAuthoringOverlay.tsx +216 -0
- package/src/host/components/HeaderTelemetry.tsx +341 -0
- package/src/host/components/PixiIsolationSceneContent.tsx +280 -0
- package/src/host/components/ResolutionPicker.tsx +89 -0
- package/src/host/components/ThreeIsolationSceneContent.tsx +180 -0
- package/src/host/components/frame-debugger-model.ts +579 -0
- package/src/host/components/header-telemetry-model.ts +74 -0
- package/src/host/components/scene-document.tsx +447 -0
- package/src/host/components/utility-view-state.ts +87 -0
- package/src/host/components/world-root-stage-binding.tsx +133 -0
- package/src/host/components/world-root-stage.ts +1046 -0
- package/src/host/coverage/authoring-read-probe.ts +583 -0
- package/src/host/coverage/capability-coverage.ts +1587 -0
- package/src/host/coverage/coverage-accounting.ts +261 -0
- package/src/host/coverage/game-contract-seam-evidence.ts +82 -0
- package/src/host/coverage/project-verb-coverage.ts +148 -0
- package/src/host/coverage/system-adapter-coverage.ts +373 -0
- package/src/host/design-system-stories/StoryLayout.tsx +104 -0
- package/src/host/design-system-stories/fixtures/authoring.ts +247 -0
- package/src/host/design-system-stories/fixtures/editor-runtime.tsx +115 -0
- package/src/host/document-preview-three.ts +110 -0
- package/src/host/entry-adjudication.ts +89 -0
- package/src/host/game-location-guard.ts +150 -0
- package/src/host/game-module-access.ts +196 -0
- package/src/host/game-realm-page.ts +358 -0
- package/src/host/game-realm-reclaim.ts +55 -0
- package/src/host/game-realm-storage.ts +104 -0
- package/src/host/gameplay-export.ts +288 -0
- package/src/host/gameplay-recording.ts +717 -0
- package/src/host/gated-globals.ts +1511 -0
- package/src/host/history/json-history-resource.ts +254 -0
- package/src/host/ingest/registry.ts +261 -0
- package/src/host/instance-extract-actions.ts +120 -0
- package/src/host/instance-fork-actions.ts +120 -0
- package/src/host/play-control-hook.ts +26 -0
- package/src/host/playwright-shim.ts +479 -0
- package/src/host/projection/dom.ts +295 -0
- package/src/host/projection/pixi.ts +288 -0
- package/src/host/r3f-entry-runtime.ts +77 -0
- package/src/host/react-mount-runtime.ts +162 -0
- package/src/host/realm-services.ts +148 -0
- package/src/host/recording-preview.ts +106 -0
- package/src/host/roots/module-root.ts +203 -0
- package/src/host/roots/react-root.ts +181 -0
- package/src/host/same-realm-loop-gate.ts +544 -0
- package/src/host/scene-view-drawability.ts +69 -0
- package/src/host/sdk/tools.ts +31 -0
- package/src/host/served-bundle-runtime-modules.ts +331 -0
- package/src/host/server-log-bridge.ts +60 -0
- package/src/host/staged-projects.ts +24 -0
- package/src/host/stories/mounted-story-viewport-source.ts +64 -0
- package/src/host/stories/story-arg-descriptors.ts +50 -0
- package/src/host/stories/story-media-presence.ts +152 -0
- package/src/host/surface-content.ts +87 -0
- package/src/host/take-named-export.ts +23 -0
- package/src/host/three-ingest-runtime.ts +76 -0
- package/src/host/types-fastnoise-lite.d.ts +7 -0
- package/src/host/types-mikktspace.d.ts +20 -0
- package/src/host/types-troika-three-text.d.ts +7 -0
- package/src/host/use-active-performance-source.ts +53 -0
- package/src/host/viewport-pose-memory.ts +48 -0
- package/src/host/viewport-root-presentation.ts +40 -0
- package/src/ingest/active-ingest.ts +251 -0
- package/src/ingest/active-scene-navigation.ts +43 -0
- package/src/ingest/authoring/ingest-dom-surface-authoring.ts +191 -0
- package/src/ingest/authoring/ingest-root-adapter.ts +897 -0
- package/src/ingest/capture-wait-report.ts +122 -0
- package/src/ingest/deferred-ingest-play.ts +216 -0
- package/src/ingest/deferred-ingest-session.ts +23 -0
- package/src/ingest/discovery-public-ingest.ts +242 -0
- package/src/ingest/dom-stub-mark.ts +7 -0
- package/src/ingest/entry-load.ts +56 -0
- package/src/ingest/game-contract-realm.ts +34 -0
- package/src/ingest/game-pointer-lock.ts +89 -0
- package/src/ingest/held-scene-repaint.ts +73 -0
- package/src/ingest/host-surface-box.ts +174 -0
- package/src/ingest/ingest-boot-viewport.ts +54 -0
- package/src/ingest/ingest-canvas-scene-document.tsx +177 -0
- package/src/ingest/ingest-canvas-scene.ts +50 -0
- package/src/ingest/ingest-evidence-hook.ts +131 -0
- package/src/ingest/ingest-frame-snapshot.ts +183 -0
- package/src/ingest/ingest-play-commands.ts +134 -0
- package/src/ingest/ingest-play-control.ts +299 -0
- package/src/ingest/ingest-render-debug.ts +320 -0
- package/src/ingest/ingest-siblings.ts +487 -0
- package/src/ingest/ingest-status.ts +62 -0
- package/src/ingest/live-ingest-facet.ts +46 -0
- package/src/ingest/module-mode.ts +229 -0
- package/src/ingest/mount-canvas-ingest-root.ts +899 -0
- package/src/ingest/mount-coverage.ts +296 -0
- package/src/ingest/mount-dom-ingest-root.ts +282 -0
- package/src/ingest/mount-ingest-root.ts +744 -0
- package/src/ingest/mount-three-ingest-root.ts +366 -0
- package/src/ingest/resolve-canvas.ts +47 -0
- package/src/ingest/resolve-three.ts +123 -0
- package/src/ingest/served-bundle.ts +109 -0
- package/src/ingest/served-html-boot.ts +292 -0
- package/src/ingest/surface-canvas.ts +84 -0
- package/src/ingest/surface-dom.ts +84 -0
- package/src/ingest/surface-three.ts +128 -0
- package/src/ingest/types.ts +76 -0
- package/src/ingest/unmount-ingest-root.ts +224 -0
- package/src/navmesh/navmesh-actions.ts +27 -0
- package/src/navmesh/navmesh-handler.ts +237 -0
- package/src/navmesh/navmesh-workflow-store.ts +78 -0
- package/src/network/NetworkInspectorPanel.tsx +644 -0
- package/src/network/network-inspector-model.ts +225 -0
- package/src/play/play-boot-stall.ts +118 -0
- package/src/play/play-log-events.ts +26 -0
- package/src/play/play-mode.ts +2553 -0
- package/src/play/play-recording.ts +335 -0
- package/src/play/react-play-live-authoring.ts +165 -0
- package/src/play-bar/PlayBar.tsx +488 -0
- package/src/play-bar/PlayerCountPicker.tsx +100 -0
- package/src/profiler/FrameDebuggerPanel.tsx +458 -0
- package/src/profiler/PerformancePanel.tsx +1068 -0
- package/src/profiler/ProfilerPanel.tsx +53 -0
- package/src/profiler/frame-debugger-store.ts +97 -0
- package/src/profiler/main-thread-busy.ts +90 -0
- package/src/react/design-time-react-mount.ts +782 -0
- package/src/react/dom-authoring-adapter.ts +764 -0
- package/src/react/pasteboard-materialize.ts +154 -0
- package/src/react/react-inspector-section.tsx +2496 -0
- package/src/react/react-world-authoring-adapter.ts +3795 -0
- package/src/react/story-documents/story-args-section.ts +19 -0
- package/src/react/story-documents/story-documents.tsx +1380 -0
- package/src/react/story-paint-bounds.ts +94 -0
- package/src/react/ui-board-document.tsx +101 -0
- package/src/react/ui-board-title.ts +9 -0
- package/src/react/ui-component-board.ts +72 -0
- package/src/services/audio-pose-guard.ts +81 -0
- package/src/services/game-audio-unlock.ts +48 -0
- package/src/state-watch/StateWatchPanel.tsx +535 -0
- package/src/three/authoring/camera-runtime-inspector-section.tsx +135 -0
- package/src/three/authoring/constraint-inspector-section.tsx +189 -0
- package/src/three/authoring/design-time-renderer.ts +189 -0
- package/src/three/authoring/model-asset-inspector-section.css +41 -0
- package/src/three/authoring/model-asset-inspector-section.tsx +869 -0
- package/src/three/authoring/oid-source-persistence.ts +557 -0
- package/src/three/authoring/r3f-design-session.ts +1174 -0
- package/src/three/authoring/r3f-source-authoring-adapter.ts +5949 -0
- package/src/three/authoring/reflection-probe-inspector-section.tsx +76 -0
- package/src/three/authoring/spatial-audio-handles.ts +215 -0
- package/src/three/authoring/spatial-collider-handles.ts +229 -0
- package/src/three/authoring/spatial-joint-handles.ts +114 -0
- package/src/three/authoring/spatial-light-handles.ts +178 -0
- package/src/three/authoring/spatial-lod-handles.ts +93 -0
- package/src/three/authoring/spatial-particle-handles.ts +368 -0
- package/src/three/authoring/three-authoring-adapter.ts +1953 -0
- package/src/three/authoring/three-scene-identity.ts +19 -0
- package/src/three/authoring/three-spatial-handles.ts +161 -0
- package/src/three/authoring/typed-three-inspector.ts +528 -0
- package/src/three/component-verbs/extract-menu.ts +71 -0
- package/src/three/component-verbs/fork-menu.ts +78 -0
- package/src/three/component-verbs/internals-menu.ts +91 -0
- package/src/three/story-documents/three-story-documents.tsx +607 -0
- package/src/three/three-board/ThreeBoardDocument.tsx +888 -0
- package/src/three/three-board/board-framing.ts +412 -0
- package/src/three/three-board/board-layout.ts +401 -0
- package/src/three/three-board/board-scene.ts +901 -0
- package/src/three/three-board/three-component-board.ts +62 -0
- package/src/xstate/XStateBehaviorSection.tsx +130 -0
- package/src/xstate/XStateMachineInspector.tsx +566 -0
- package/src/xstate/character-animation-machine.fixture.ts +74 -0
- package/src/xstate/live-behaviors.ts +106 -0
- package/src/xstate/use-live-actor-state.ts +44 -0
- package/src/xstate/xstate-graph.ts +235 -0
- package/src/xstate/xstate-layout.ts +76 -0
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NARROW-COLUMN coverage over the REAL content-heavy section bodies, at rail
|
|
3
|
+
* width. The projection is subject-agnostic, so what these prove is not the
|
|
4
|
+
* projection but whether the bodies BUILT FOR THE OLD WIDER COLUMN survive the
|
|
5
|
+
* slim rail. Each story renders a real body (or a real composed subject) inside
|
|
6
|
+
* a ~300px `RailColumn`, which is the narrow column's target width, with
|
|
7
|
+
* `overflow: hidden` so anything that spills is VISIBLE rather than scrolled
|
|
8
|
+
* away.
|
|
9
|
+
*
|
|
10
|
+
* Which bodies are REAL vs stand-in (stated honestly, per the review ask):
|
|
11
|
+
* - CSS editors — the REAL `inspector-widgets` components (BorderEditor,
|
|
12
|
+
* AlignmentGrid, Gradient/Shadow/Filter composers, ScrubbableInput),
|
|
13
|
+
* assembled into a fixture subject. The BODY COMPONENTS are production; the
|
|
14
|
+
* subject wrapping them is synthetic (a live react/DOM subject needs a
|
|
15
|
+
* running document session + OID index, not constructible in a bounded
|
|
16
|
+
* story).
|
|
17
|
+
* - State machine — the REAL `XStateMachineInspector` over the REAL
|
|
18
|
+
* `characterAnimationMachine` fixture. Fully real; no live actor is needed
|
|
19
|
+
* (the static graph view is the design-time state).
|
|
20
|
+
* - Ingest coverage — the REAL `ingestCoverageSection(deriveCapabilityCoverage(…))`
|
|
21
|
+
* section, the exact one the Game Inspector composes.
|
|
22
|
+
* - Typed-three groups — a REAL composed subject: `AuthoringInspectorSurface`
|
|
23
|
+
* over the shared `createAuthoringFixture` adapter, selected to the mesh, so
|
|
24
|
+
* its grouped descriptor bodies (Material, Gameplay, loose) render exactly as
|
|
25
|
+
* the composer builds them.
|
|
26
|
+
* - Model asset — a REAL composed subject: `AuthoringInspectorSurface` over a
|
|
27
|
+
* `SourceObject3DAuthoringAdapter` built from a hand-authored THREE scene
|
|
28
|
+
* (the same fixture the model-asset workflow exercises), so the Geometry /
|
|
29
|
+
* Animation / Materials / Source sections are the real contributions.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import { UNCAPTURED_REACH } from '../host/adapter-reach';
|
|
33
|
+
import { SourceObject3DAuthoringAdapter } from '@volter/editor-core/authoring/source-object3d-authoring-adapter';
|
|
34
|
+
import { faBrain, faPalette } from '@fortawesome/free-solid-svg-icons';
|
|
35
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
36
|
+
import { type ReactNode, useState } from 'react';
|
|
37
|
+
import * as THREE from 'three';
|
|
38
|
+
// Side-effect: registers the model-asset section contributions (Geometry,
|
|
39
|
+
// Animation, Materials, Source) into the inspector-section registry.
|
|
40
|
+
import '@volter/editor-core/authoring/model-asset-inspector-section';
|
|
41
|
+
import { ingestCoverageSection } from '@volter/editor-core/components/CapabilityCoverageSection';
|
|
42
|
+
import { InspectionProjectionView } from '@volter/editor-core/components/InspectionProjection';
|
|
43
|
+
import { AuthoringInspectorSurface } from '@volter/editor-core/components/Inspector';
|
|
44
|
+
import { deriveCapabilityCoverage } from '../host/coverage/capability-coverage';
|
|
45
|
+
import { createAuthoringFixture } from '../host/design-system-stories/fixtures/authoring';
|
|
46
|
+
import { StoryEditorRuntime } from '../host/design-system-stories/fixtures/editor-runtime';
|
|
47
|
+
import type { InspectionSection, InspectionSubject } from '@volter/editor-core/inspection/model';
|
|
48
|
+
import {
|
|
49
|
+
AlignmentGrid,
|
|
50
|
+
type AlignmentValue,
|
|
51
|
+
AnchorPresets,
|
|
52
|
+
BorderEditor,
|
|
53
|
+
BorderRadiusEditor,
|
|
54
|
+
type BorderRadiusValue,
|
|
55
|
+
type BorderValue,
|
|
56
|
+
ColorPicker,
|
|
57
|
+
DEFAULT_SHADOW,
|
|
58
|
+
FilterEditor,
|
|
59
|
+
GradientEditor,
|
|
60
|
+
type GradientValue,
|
|
61
|
+
parseFilterFunctions,
|
|
62
|
+
ScrubbableInput,
|
|
63
|
+
ShadowEditor,
|
|
64
|
+
type ShadowValue,
|
|
65
|
+
uniformBorder,
|
|
66
|
+
uniformRadius,
|
|
67
|
+
} from '@volter/editor-sdk/widgets';
|
|
68
|
+
import { characterAnimationMachine } from '../xstate/character-animation-machine.fixture';
|
|
69
|
+
import { XStateMachineInspector } from '../xstate/XStateMachineInspector';
|
|
70
|
+
|
|
71
|
+
/** The narrow column's target width. `overflow: hidden` so a body that was
|
|
72
|
+
* built wider than the rail SPILLS visibly instead of scrolling. */
|
|
73
|
+
const RAIL_WIDTH = 300;
|
|
74
|
+
function RailColumn({ children }: { readonly children: ReactNode }) {
|
|
75
|
+
return (
|
|
76
|
+
<div
|
|
77
|
+
style={{
|
|
78
|
+
width: RAIL_WIDTH,
|
|
79
|
+
height: 600,
|
|
80
|
+
display: 'flex',
|
|
81
|
+
border: '1px solid var(--vgai-border-2)',
|
|
82
|
+
borderRadius: 8,
|
|
83
|
+
overflow: 'hidden',
|
|
84
|
+
background: 'var(--vgai-bg-1)',
|
|
85
|
+
}}
|
|
86
|
+
>
|
|
87
|
+
{children}
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function customSection(
|
|
93
|
+
id: string,
|
|
94
|
+
title: string,
|
|
95
|
+
icon: InspectionSection['icon'],
|
|
96
|
+
order: number,
|
|
97
|
+
body: ReactNode,
|
|
98
|
+
): InspectionSection {
|
|
99
|
+
return { id, title, icon, order, body: { kind: 'custom', render: () => body } };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function columnSubject(input: {
|
|
103
|
+
readonly id: string;
|
|
104
|
+
readonly title: string;
|
|
105
|
+
readonly kindLabel: string;
|
|
106
|
+
readonly sections: readonly InspectionSection[];
|
|
107
|
+
}): InspectionSubject {
|
|
108
|
+
return {
|
|
109
|
+
id: input.id,
|
|
110
|
+
title: input.title,
|
|
111
|
+
identity: { rename: { readOnly: false, set: () => undefined }, kindLabel: input.kindLabel },
|
|
112
|
+
presentation: { preferred: 'column' },
|
|
113
|
+
quickActions: [],
|
|
114
|
+
related: [],
|
|
115
|
+
sections: input.sections,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ---- CSS editors (real widget bodies) ------------------------------------
|
|
120
|
+
|
|
121
|
+
function StyleBody() {
|
|
122
|
+
const [color, setColor] = useState('#12203a');
|
|
123
|
+
const [border, setBorder] = useState<BorderValue>(
|
|
124
|
+
uniformBorder({ width: 1, style: 'solid', color: '#4f8cff' }),
|
|
125
|
+
);
|
|
126
|
+
const [radius, setRadius] = useState<BorderRadiusValue>(uniformRadius(8));
|
|
127
|
+
return (
|
|
128
|
+
<div style={{ padding: 8, display: 'flex', flexDirection: 'column', gap: 8 }}>
|
|
129
|
+
<ColorPicker value={color} onChange={setColor} onChangeEnd={setColor} />
|
|
130
|
+
<BorderEditor value={border} onChange={setBorder} onChangeEnd={setBorder} />
|
|
131
|
+
<BorderRadiusEditor value={radius} onChange={setRadius} onChangeEnd={setRadius} />
|
|
132
|
+
</div>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function FillBody() {
|
|
137
|
+
const [gradient, setGradient] = useState<GradientValue>({
|
|
138
|
+
type: 'linear',
|
|
139
|
+
angle: 180,
|
|
140
|
+
stops: [
|
|
141
|
+
{ color: '#ff7f50', position: 0 },
|
|
142
|
+
{ color: '#4f8cff', position: 1 },
|
|
143
|
+
],
|
|
144
|
+
});
|
|
145
|
+
return (
|
|
146
|
+
<div style={{ padding: 8 }}>
|
|
147
|
+
<GradientEditor value={gradient} onChange={setGradient} onChangeEnd={setGradient} />
|
|
148
|
+
</div>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function EffectsBody() {
|
|
153
|
+
const [shadows, setShadows] = useState<ShadowValue[]>([DEFAULT_SHADOW]);
|
|
154
|
+
const [filter, setFilter] = useState(parseFilterFunctions('blur(4px) saturate(120%)'));
|
|
155
|
+
return (
|
|
156
|
+
<div style={{ padding: 8, display: 'flex', flexDirection: 'column', gap: 8 }}>
|
|
157
|
+
<ShadowEditor value={shadows} onChange={setShadows} onChangeEnd={setShadows} />
|
|
158
|
+
<FilterEditor value={filter} onChange={setFilter} onChangeEnd={setFilter} />
|
|
159
|
+
</div>
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function LayoutBody() {
|
|
164
|
+
const [alignment, setAlignment] = useState<AlignmentValue>({
|
|
165
|
+
justify: 'center',
|
|
166
|
+
align: 'center',
|
|
167
|
+
});
|
|
168
|
+
return (
|
|
169
|
+
<div style={{ padding: 8, display: 'flex', flexDirection: 'column', gap: 8 }}>
|
|
170
|
+
<AlignmentGrid value={alignment} onChange={setAlignment} />
|
|
171
|
+
<AnchorPresets onSelect={() => undefined} />
|
|
172
|
+
</div>
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function PositionBody() {
|
|
177
|
+
const [x, setX] = useState(0);
|
|
178
|
+
const [y, setY] = useState(0);
|
|
179
|
+
const [w, setW] = useState(240);
|
|
180
|
+
const [h, setH] = useState(64);
|
|
181
|
+
return (
|
|
182
|
+
<div
|
|
183
|
+
style={{
|
|
184
|
+
padding: 8,
|
|
185
|
+
display: 'grid',
|
|
186
|
+
gridTemplateColumns: '1fr 1fr',
|
|
187
|
+
gap: 6,
|
|
188
|
+
}}
|
|
189
|
+
>
|
|
190
|
+
<ScrubbableInput label="X" value={x} unit="px" onChange={setX} onChangeEnd={setX} />
|
|
191
|
+
<ScrubbableInput label="Y" value={y} unit="px" onChange={setY} onChangeEnd={setY} />
|
|
192
|
+
<ScrubbableInput label="W" value={w} unit="px" onChange={setW} onChangeEnd={setW} />
|
|
193
|
+
<ScrubbableInput label="H" value={h} unit="px" onChange={setH} onChangeEnd={setH} />
|
|
194
|
+
</div>
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const cssSubject = columnSubject({
|
|
199
|
+
id: 'dom:header-bar',
|
|
200
|
+
title: 'HeaderBar',
|
|
201
|
+
kindLabel: 'div · react',
|
|
202
|
+
sections: [
|
|
203
|
+
customSection('group:style', 'Style', faPalette, 2000, <StyleBody />),
|
|
204
|
+
customSection('group:fill', 'Fill', faPalette, 2100, <FillBody />),
|
|
205
|
+
customSection('group:effects', 'Effects', faPalette, 2200, <EffectsBody />),
|
|
206
|
+
customSection('group:layout', 'Layout', faPalette, 2300, <LayoutBody />),
|
|
207
|
+
customSection('group:position', 'Position', faPalette, 2400, <PositionBody />),
|
|
208
|
+
],
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// ---- State machine (real inspector + real fixture machine) ---------------
|
|
212
|
+
|
|
213
|
+
const stateMachineSubject = columnSubject({
|
|
214
|
+
id: 'asset:character-animation',
|
|
215
|
+
title: 'Character Animation',
|
|
216
|
+
kindLabel: 'State machine',
|
|
217
|
+
sections: [
|
|
218
|
+
customSection(
|
|
219
|
+
'state-machine',
|
|
220
|
+
'State machine',
|
|
221
|
+
faBrain,
|
|
222
|
+
4000,
|
|
223
|
+
<div style={{ height: 300, minHeight: 0 }}>
|
|
224
|
+
<XStateMachineInspector
|
|
225
|
+
machine={characterAnimationMachine}
|
|
226
|
+
currentStateId="character-animation.idle"
|
|
227
|
+
/>
|
|
228
|
+
</div>,
|
|
229
|
+
),
|
|
230
|
+
],
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// ---- Ingest coverage (the real composed section) -------------------------
|
|
234
|
+
|
|
235
|
+
const coverageReport = deriveCapabilityCoverage({
|
|
236
|
+
worldId: 'wild-three-game',
|
|
237
|
+
reach: UNCAPTURED_REACH,
|
|
238
|
+
surface: 'three',
|
|
239
|
+
reachMechanism:
|
|
240
|
+
"the game's own three was unreachable or version-mismatched at mount, so nothing was captured",
|
|
241
|
+
loop: {
|
|
242
|
+
verdict: 'gated',
|
|
243
|
+
evidence: {
|
|
244
|
+
pendingWhileHeld: 1,
|
|
245
|
+
firedWhileHeld: 0,
|
|
246
|
+
progressBefore: 412,
|
|
247
|
+
progressAfter: 412,
|
|
248
|
+
windowMs: 1500,
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
loopReason: 'verified: one scheduled callback withheld and no draw advanced',
|
|
252
|
+
contract: {
|
|
253
|
+
declared: true,
|
|
254
|
+
root: true,
|
|
255
|
+
start: false,
|
|
256
|
+
pause: true,
|
|
257
|
+
resume: true,
|
|
258
|
+
systems: { commands: ['level.quickStart', 'hud.message'], state: ['player', 'runtime'] },
|
|
259
|
+
},
|
|
260
|
+
ownership: { writable: false, reason: 'vendored upstream bytes are locked' },
|
|
261
|
+
dataWriter: null,
|
|
262
|
+
writeReach: null,
|
|
263
|
+
systemAdapters: [
|
|
264
|
+
{ slot: 'physics', state: 'unanswered' },
|
|
265
|
+
{
|
|
266
|
+
slot: 'networking',
|
|
267
|
+
state: 'empty',
|
|
268
|
+
evidence: 'no socket, peer or state-fetch call anywhere in the game source',
|
|
269
|
+
},
|
|
270
|
+
{ slot: 'navigation', state: 'bound' },
|
|
271
|
+
{ slot: 'audio', state: 'bound' },
|
|
272
|
+
{ slot: 'camera', state: 'bound' },
|
|
273
|
+
{ slot: 'debug', state: 'bound' },
|
|
274
|
+
{ slot: 'renderDebug', state: 'bound' },
|
|
275
|
+
],
|
|
276
|
+
projectVerbs: [
|
|
277
|
+
{
|
|
278
|
+
slot: 'export',
|
|
279
|
+
state: 'absent',
|
|
280
|
+
evidence: 'every root this project declares is an ingest root',
|
|
281
|
+
missing: 'this game can never leave the editor',
|
|
282
|
+
fix: 'there is no mechanism today — the export path supports only built-in three roots',
|
|
283
|
+
},
|
|
284
|
+
],
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
const coverageSubject = columnSubject({
|
|
288
|
+
id: 'game:wild-three-game',
|
|
289
|
+
title: 'wild-three-game',
|
|
290
|
+
kindLabel: 'Ingested game',
|
|
291
|
+
sections: [ingestCoverageSection(coverageReport)],
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// ---- Composed-subject fixtures (real adapters) ---------------------------
|
|
295
|
+
|
|
296
|
+
/** A real THREE model scene → the source-object3d model adapter, the same
|
|
297
|
+
* fixture the model-asset workflow exercises. */
|
|
298
|
+
function buildModelAdapter(store: Parameters<typeof AuthoringInspectorSurface>[0]['store']) {
|
|
299
|
+
const scene = new THREE.Scene();
|
|
300
|
+
const root = new THREE.Group();
|
|
301
|
+
root.name = 'Ship';
|
|
302
|
+
const material = new THREE.MeshStandardMaterial({ color: 0x884422 });
|
|
303
|
+
material.name = 'Hull';
|
|
304
|
+
const mesh = new THREE.Mesh(new THREE.BoxGeometry(), material);
|
|
305
|
+
mesh.name = 'Hull mesh';
|
|
306
|
+
root.add(mesh);
|
|
307
|
+
root.animations = [
|
|
308
|
+
new THREE.AnimationClip('Hover', 2, [
|
|
309
|
+
new THREE.VectorKeyframeTrack('Ship.position', [0, 2], [0, 0, 0, 0, 1, 0]),
|
|
310
|
+
]),
|
|
311
|
+
];
|
|
312
|
+
scene.add(root);
|
|
313
|
+
return new SourceObject3DAuthoringAdapter(store, scene, {
|
|
314
|
+
documentId: 'ship.glb',
|
|
315
|
+
title: 'ship.glb',
|
|
316
|
+
sourcePath: '/models/ship.glb',
|
|
317
|
+
documentKind: 'asset',
|
|
318
|
+
modelSource: { kind: 'project-file', path: '/models/ship.glb' },
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const meta = {
|
|
323
|
+
title: 'Design System/Product Systems/Inspector Narrow Bodies',
|
|
324
|
+
parameters: { controls: { disable: true }, layout: 'centered' },
|
|
325
|
+
} satisfies Meta;
|
|
326
|
+
|
|
327
|
+
export default meta;
|
|
328
|
+
type Story = StoryObj<typeof meta>;
|
|
329
|
+
|
|
330
|
+
/** (1) react/DOM CSS editors — the real border/gradient/shadow/filter/alignment
|
|
331
|
+
* widgets, at rail width. */
|
|
332
|
+
export const CssEditorsColumn: Story = {
|
|
333
|
+
render: () => (
|
|
334
|
+
<RailColumn>
|
|
335
|
+
<InspectionProjectionView subject={cssSubject} presentation="column" surface="dom" />
|
|
336
|
+
</RailColumn>
|
|
337
|
+
),
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
/** (2) The real xstate machine inspector (static graph view). */
|
|
341
|
+
export const StateMachineColumn: Story = {
|
|
342
|
+
render: () => (
|
|
343
|
+
<RailColumn>
|
|
344
|
+
<InspectionProjectionView
|
|
345
|
+
subject={stateMachineSubject}
|
|
346
|
+
presentation="column"
|
|
347
|
+
surface="asset-lab"
|
|
348
|
+
/>
|
|
349
|
+
</RailColumn>
|
|
350
|
+
),
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
/** (3) The real ingest coverage section. */
|
|
354
|
+
export const IngestCoverageColumn: Story = {
|
|
355
|
+
render: () => (
|
|
356
|
+
<RailColumn>
|
|
357
|
+
<InspectionProjectionView subject={coverageSubject} presentation="column" surface="three" />
|
|
358
|
+
</RailColumn>
|
|
359
|
+
),
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
/** (4) Typed-three grouped descriptor bodies — a real composed mesh subject
|
|
363
|
+
* (Material / Gameplay groups + loose fields). */
|
|
364
|
+
export const TypedThreeGroupsColumn: Story = {
|
|
365
|
+
render: () => {
|
|
366
|
+
const adapter = createAuthoringFixture();
|
|
367
|
+
return (
|
|
368
|
+
<StoryEditorRuntime>
|
|
369
|
+
{(runtime) => (
|
|
370
|
+
<RailColumn>
|
|
371
|
+
<AuthoringInspectorSurface
|
|
372
|
+
store={runtime.store}
|
|
373
|
+
adapter={adapter}
|
|
374
|
+
documentSelection={{ adapter, nodeId: 'player' }}
|
|
375
|
+
presentation="column"
|
|
376
|
+
surface="three"
|
|
377
|
+
/>
|
|
378
|
+
</RailColumn>
|
|
379
|
+
)}
|
|
380
|
+
</StoryEditorRuntime>
|
|
381
|
+
);
|
|
382
|
+
},
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
/** (5) Model-asset sections — a real composed subject (Geometry / Animation /
|
|
386
|
+
* Materials / Source) from a hand-authored THREE scene. */
|
|
387
|
+
export const ModelAssetColumn: Story = {
|
|
388
|
+
render: () => (
|
|
389
|
+
<StoryEditorRuntime>
|
|
390
|
+
{(runtime) => {
|
|
391
|
+
const adapter = buildModelAdapter(runtime.store);
|
|
392
|
+
return (
|
|
393
|
+
<RailColumn>
|
|
394
|
+
<AuthoringInspectorSurface
|
|
395
|
+
store={runtime.store}
|
|
396
|
+
adapter={adapter}
|
|
397
|
+
documentSelection={{ adapter, nodeId: adapter.documentId }}
|
|
398
|
+
presentation="column"
|
|
399
|
+
surface="asset-lab"
|
|
400
|
+
/>
|
|
401
|
+
</RailColumn>
|
|
402
|
+
);
|
|
403
|
+
}}
|
|
404
|
+
</StoryEditorRuntime>
|
|
405
|
+
),
|
|
406
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project-lifetime audio for the editor authoring session.
|
|
3
|
+
*
|
|
4
|
+
* Edit mode uses the same first-party Three/Web Audio graph and AudioAdapter
|
|
5
|
+
* implementation as play mode. The mounted game's adapter temporarily wins
|
|
6
|
+
* while Play is active; on Stop, `getActiveAudio()` falls back to this graph.
|
|
7
|
+
*
|
|
8
|
+
* It lives in `@volter/editor-game` because it IS the engine's audio runtime — the
|
|
9
|
+
* mixer, the bus graph and the pose guard behind `@volter/game-runtime/setup/setup-audio` —
|
|
10
|
+
* and the host was installing all of it at boot for every project, including a
|
|
11
|
+
* models project that plays nothing and has no sound (WORK.md §The open-source
|
|
12
|
+
* launch, phase 1 unit 7). The one host surface that reads it, the header
|
|
13
|
+
* meter, already answers "Audio unavailable" when no adapter is installed, and
|
|
14
|
+
* the panel that inspects it (`audio.utility`) has been this package's since
|
|
15
|
+
* unit 2. Same shape as `edit-mode-networking.ts` beside it.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { setEditModeAudio } from '@volter/editor-core/authoring/active-systems';
|
|
19
|
+
import {
|
|
20
|
+
createAudioSystemAdapter,
|
|
21
|
+
releaseAudioMeters,
|
|
22
|
+
} from '@volter/game-runtime/adapter/first-party-audio-system';
|
|
23
|
+
import { setupAudio } from '@volter/game-runtime/setup/setup-audio';
|
|
24
|
+
import * as THREE from 'three';
|
|
25
|
+
|
|
26
|
+
export function installEditModeAudio(camera: THREE.Camera = new THREE.Camera()): () => void {
|
|
27
|
+
const audio = setupAudio(camera);
|
|
28
|
+
const adapter = createAudioSystemAdapter(audio);
|
|
29
|
+
let disposed = false;
|
|
30
|
+
|
|
31
|
+
setEditModeAudio(adapter);
|
|
32
|
+
|
|
33
|
+
// No Tone here, deliberately. This used to `import('@volter/game-runtime/audio/audio-
|
|
34
|
+
// introspection')` unconditionally, which loaded the whole `tone` package on
|
|
35
|
+
// EVERY editor boot to draw a meter — and the editor has no score of its
|
|
36
|
+
// own to report a transport for. The graph and meters the Audio panel
|
|
37
|
+
// actually shows in edit mode are the base adapter's (535d248dc), and the
|
|
38
|
+
// Tone half now lives in the `music` capability, attached by a game that
|
|
39
|
+
// uses it.
|
|
40
|
+
|
|
41
|
+
return () => {
|
|
42
|
+
if (disposed) return;
|
|
43
|
+
disposed = true;
|
|
44
|
+
releaseAudioMeters(adapter);
|
|
45
|
+
setEditModeAudio(null);
|
|
46
|
+
|
|
47
|
+
// Do not suspend the shared native AudioContext: a concurrently starting
|
|
48
|
+
// play session can own it. Disconnect only this authoring graph — the
|
|
49
|
+
// mixer's own teardown covers every bus it minted (master, music, sfx,
|
|
50
|
+
// voice, and anything a project asked it for by name), so this cannot go
|
|
51
|
+
// stale the way four hardcoded bus names would.
|
|
52
|
+
audio.listener.gain.disconnect();
|
|
53
|
+
audio.mixer.disconnect();
|
|
54
|
+
camera.remove(audio.listener);
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project-lifetime networking config for the editor authoring session.
|
|
3
|
+
*
|
|
4
|
+
* The engine ships the `NetworkingAdapter` seam and the editor's Network
|
|
5
|
+
* inspector reads it; at RUNTIME a game produces one over a live room (the
|
|
6
|
+
* `colyseus` capability). This is the EDIT-TIME half: with no game running, a
|
|
7
|
+
* multiplayer project still has a DECLARED server. This installs a networking
|
|
8
|
+
* adapter that answers the ONE edit-time question it honestly can —
|
|
9
|
+
* `getServerConfig()`, read straight from the manifest (`server.room` and
|
|
10
|
+
* `server.endpoint`, the latter absent = the scaffold default below) — plus
|
|
11
|
+
* "not connected" stubs for the live members it cannot answer offline. It
|
|
12
|
+
* never fabricates a connection.
|
|
13
|
+
*
|
|
14
|
+
* It also never fabricates a PLAYER IDENTITY, and that is the load-bearing part
|
|
15
|
+
* of this file. An adapter is an INTERFACE the game implements, not a place the
|
|
16
|
+
* editor imposes a way of doing things: WHO a player is — and whether a name is
|
|
17
|
+
* even author-settable — is the game's own multiplayer to define. Many games
|
|
18
|
+
* (the obby's Roblox-idiom `Player${joinCount}` server naming, for one) assign
|
|
19
|
+
* names server-side and honour no client name at all. So this edit-time adapter
|
|
20
|
+
* exposes NO `getPlayerIdentity`/`setPlayerIdentity`: the editor renders
|
|
21
|
+
* identity UI only when a REAL runtime implementer provides those members
|
|
22
|
+
* (consume-by-presence), and shows nothing otherwise — never a settable field
|
|
23
|
+
* wired to a sidecar the game ignores.
|
|
24
|
+
*
|
|
25
|
+
* Unlike edit-mode AUDIO (always installed — every project has an audio graph),
|
|
26
|
+
* this installs ONLY when the open project declares a `server`: networking
|
|
27
|
+
* PRESENCE is meaningful in the editor (it gates the Network debugger tab and
|
|
28
|
+
* the network utility), so a single-player project must keep `getActive
|
|
29
|
+
* Networking()` null. The mounted game's adapter temporarily wins while Play is
|
|
30
|
+
* active; on Stop, `getActiveNetworking()` falls back to this one.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import { setEditModeNetworking } from '@volter/editor-core/authoring/active-systems';
|
|
34
|
+
import { getCurrentProject } from '@volter/editor-core/project-manager';
|
|
35
|
+
import type {
|
|
36
|
+
ConnectionState,
|
|
37
|
+
NetPeer,
|
|
38
|
+
NetServerConfig,
|
|
39
|
+
NetworkingAdapter,
|
|
40
|
+
ReplicationStats,
|
|
41
|
+
} from '@volter/editor-project/adapter';
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The endpoint a project gets when its manifest declares none — the SCAFFOLD
|
|
45
|
+
* DEFAULT, stated in `server.endpoint`'s own schema description and spelled
|
|
46
|
+
* here: the port `server/colyseus-setup.ts` listens on, against the host the
|
|
47
|
+
* editor page is served from.
|
|
48
|
+
*
|
|
49
|
+
* This is a declaration read, not a guess: a project whose room lives anywhere
|
|
50
|
+
* else says so in `server.endpoint` (ARCHITECTURE-CORE §The editor protocol,
|
|
51
|
+
* "Zero inference" — the fix for a guessed fact is a declaration slot). The
|
|
52
|
+
* host still comes from `location`, because "the machine this editor is served
|
|
53
|
+
* from" is an environment fact the game cannot state.
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
/** Read the declared server from the open project's manifest, or `null` when
|
|
57
|
+
* the project declares none (single-player). Defensive: an unreadable or
|
|
58
|
+
* malformed manifest is treated as "no server", never a throw. */
|
|
59
|
+
function readServerConfig(): NetServerConfig | null {
|
|
60
|
+
// The declared `process` with a port is the edit-time server: its endpoint
|
|
61
|
+
// is what the game will reach; the room is the game's own networking
|
|
62
|
+
// adapter's to report once it runs (ARCHITECTURE-CORE §The project model).
|
|
63
|
+
const declared = (getCurrentProject()?.config.configurations ?? []).find(
|
|
64
|
+
(configuration) =>
|
|
65
|
+
configuration.kind === 'process' && typeof configuration['port'] === 'number',
|
|
66
|
+
);
|
|
67
|
+
if (!declared) return null;
|
|
68
|
+
const host = globalThis.location?.hostname || 'localhost';
|
|
69
|
+
return { endpoint: `ws://${host}:${declared['port'] as number}` };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const NO_PEERS: NetPeer[] = [];
|
|
73
|
+
const DISCONNECTED_STATS: ReplicationStats = {
|
|
74
|
+
entities: 0,
|
|
75
|
+
msgsInPerSec: 0,
|
|
76
|
+
msgsOutPerSec: 0,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/** Build the edit-time adapter over the declared server. The live members
|
|
80
|
+
* answer "not connected" honestly; `getServerConfig` reports the declared
|
|
81
|
+
* room. There are deliberately NO identity members — see the file header. */
|
|
82
|
+
function createEditModeNetworkingAdapter(serverConfig: NetServerConfig): NetworkingAdapter {
|
|
83
|
+
return {
|
|
84
|
+
// Live members: no connection at edit time — honest stubs, never fabricated.
|
|
85
|
+
peers: () => NO_PEERS,
|
|
86
|
+
networkId: () => null,
|
|
87
|
+
authority: () => 'local',
|
|
88
|
+
editable: () => true,
|
|
89
|
+
getConnectionState: (): ConnectionState => 'disconnected',
|
|
90
|
+
getRoomInfo: () => null,
|
|
91
|
+
getReplicationStats: () => DISCONNECTED_STATS,
|
|
92
|
+
// Edit-time state is static (nothing connects offline), so there is nothing
|
|
93
|
+
// to notify — an honest no-op subscription rather than a live one.
|
|
94
|
+
subscribe: () => () => {},
|
|
95
|
+
// The one honest edit-time capability: the room the manifest declares.
|
|
96
|
+
getServerConfig: () => serverConfig,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Install the edit-mode networking adapter for the open project, if it declares
|
|
102
|
+
* a server. Returns a dispose that clears it. A single-player project (no
|
|
103
|
+
* declared server) never installs one, so `getActiveNetworking()` stays null
|
|
104
|
+
* and no networking UI appears. Synchronous: the only input is the
|
|
105
|
+
* already-parsed manifest.
|
|
106
|
+
*/
|
|
107
|
+
export function installEditModeNetworking(): () => void {
|
|
108
|
+
const serverConfig = readServerConfig();
|
|
109
|
+
if (!serverConfig) return () => {};
|
|
110
|
+
setEditModeNetworking(createEditModeNetworkingAdapter(serverConfig));
|
|
111
|
+
return () => setEditModeNetworking(null);
|
|
112
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* W2c (F13) — Game-tab device preset dropdown. Chrome-device-mode-style:
|
|
3
|
+
* picking a preset constrains the game mount to that device's CSS resolution
|
|
4
|
+
* + DPR (letterboxed/scaled by `GamePanel`), phone/tablet presets add the
|
|
5
|
+
* safe-area overlay + `--vgai-safe-area-inset-*` CSS vars and auto-enable
|
|
6
|
+
* pointer-as-touch. State lives in `../device-preview.ts`; this component is
|
|
7
|
+
* pure UI over it.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { faMobileScreenButton } from '@fortawesome/free-solid-svg-icons';
|
|
11
|
+
import { AnchoredMenu, Button, EditorIcon, MenuItem, Text } from '@volter/editor-sdk/widgets';
|
|
12
|
+
import { useRef, useState } from 'react';
|
|
13
|
+
import {
|
|
14
|
+
DEVICE_PRESETS,
|
|
15
|
+
type DevicePreset,
|
|
16
|
+
devicePreset,
|
|
17
|
+
setTouchEmulationOverride,
|
|
18
|
+
touchEmulationActive,
|
|
19
|
+
} from './device-preview';
|
|
20
|
+
|
|
21
|
+
export function DevicePresetPicker({
|
|
22
|
+
onPresetChange,
|
|
23
|
+
}: {
|
|
24
|
+
/** Owner applies the preset (device-preview store + game resolution). */
|
|
25
|
+
onPresetChange: (preset: DevicePreset) => void;
|
|
26
|
+
}) {
|
|
27
|
+
const [open, setOpen] = useState(false);
|
|
28
|
+
const ref = useRef<HTMLButtonElement>(null);
|
|
29
|
+
const active = devicePreset();
|
|
30
|
+
const isFit = active.kind === 'fit';
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div className="vgai-playbar-popover-anchor">
|
|
34
|
+
<Button
|
|
35
|
+
ref={ref}
|
|
36
|
+
variant="ghost"
|
|
37
|
+
size="comfortable"
|
|
38
|
+
aria-haspopup="menu"
|
|
39
|
+
aria-expanded={open}
|
|
40
|
+
aria-controls={open ? 'vgai-device-preset-menu' : undefined}
|
|
41
|
+
data-testid="device-preset-picker"
|
|
42
|
+
onClick={() => setOpen((value) => !value)}
|
|
43
|
+
title={
|
|
44
|
+
'Device preview — constrain the game to a device resolution + DPR. Phone/tablet presets show the safe area, set --vgai-safe-area-inset-top/right/bottom/left CSS vars on the game mount, and send mouse input as touch.'
|
|
45
|
+
}
|
|
46
|
+
>
|
|
47
|
+
<EditorIcon icon={faMobileScreenButton} size="md" />
|
|
48
|
+
<Text variant="code">{isFit ? 'Device' : `${active.width}×${active.height}`}</Text>
|
|
49
|
+
</Button>
|
|
50
|
+
{open && (
|
|
51
|
+
<AnchoredMenu
|
|
52
|
+
id="vgai-device-preset-menu"
|
|
53
|
+
anchorRef={ref}
|
|
54
|
+
className="vgai-resolution-menu"
|
|
55
|
+
gap={4}
|
|
56
|
+
onDismiss={() => setOpen(false)}
|
|
57
|
+
>
|
|
58
|
+
{DEVICE_PRESETS.map((candidate) => (
|
|
59
|
+
<MenuItem
|
|
60
|
+
key={candidate.id}
|
|
61
|
+
role="menuitemradio"
|
|
62
|
+
aria-checked={candidate.id === active.id}
|
|
63
|
+
data-testid={`device-preset-${candidate.id}`}
|
|
64
|
+
onSelect={() => {
|
|
65
|
+
onPresetChange(candidate);
|
|
66
|
+
setOpen(false);
|
|
67
|
+
}}
|
|
68
|
+
>
|
|
69
|
+
{candidate.label}
|
|
70
|
+
</MenuItem>
|
|
71
|
+
))}
|
|
72
|
+
<MenuItem
|
|
73
|
+
role="menuitemcheckbox"
|
|
74
|
+
aria-checked={touchEmulationActive()}
|
|
75
|
+
data-testid="device-touch-toggle"
|
|
76
|
+
onSelect={() => setTouchEmulationOverride(!touchEmulationActive())}
|
|
77
|
+
>
|
|
78
|
+
{touchEmulationActive() ? '✓ ' : ' '}Pointer as touch
|
|
79
|
+
</MenuItem>
|
|
80
|
+
</AnchoredMenu>
|
|
81
|
+
)}
|
|
82
|
+
</div>
|
|
83
|
+
);
|
|
84
|
+
}
|