@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,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The union of what a mounted DOM story actually PAINTS, in the mount box's
|
|
3
|
+
* own authored pixels — the measurement half of "mount in the truth, frame the
|
|
4
|
+
* subject" for the UI board (the capture lane's alpha-bounds scan is the same
|
|
5
|
+
* decision made on pixels; a live card cannot rasterize per frame, so this
|
|
6
|
+
* walks the DOM instead).
|
|
7
|
+
*
|
|
8
|
+
* The filter matters more than the union: a translated uGUI screen is nested
|
|
9
|
+
* full-bleed wrapper boxes (anchor stretch, transparent) around a few painted
|
|
10
|
+
* leaves, so a naive union of ALL element rects is always the whole mount box
|
|
11
|
+
* and the crop never fires. An element counts only when it paints something a
|
|
12
|
+
* viewer can see: a non-transparent background, a background image, replaced
|
|
13
|
+
* content, a visible border/shadow, or its own text.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** True when this element itself puts pixels on screen. */
|
|
17
|
+
export function elementPaints(element: Element): boolean {
|
|
18
|
+
const tag = element.tagName;
|
|
19
|
+
if (
|
|
20
|
+
tag === 'IMG' ||
|
|
21
|
+
tag === 'VIDEO' ||
|
|
22
|
+
tag === 'CANVAS' ||
|
|
23
|
+
tag === 'svg' ||
|
|
24
|
+
tag === 'SVG' ||
|
|
25
|
+
tag === 'PICTURE'
|
|
26
|
+
) {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
const style = getComputedStyle(element);
|
|
30
|
+
if (style.visibility === 'hidden' || style.display === 'none') return false;
|
|
31
|
+
if (backgroundColorPaints(style.backgroundColor)) return true;
|
|
32
|
+
if (style.backgroundImage && style.backgroundImage !== 'none') return true;
|
|
33
|
+
if (style.boxShadow && style.boxShadow !== 'none') return true;
|
|
34
|
+
if (
|
|
35
|
+
style.borderStyle !== 'none' &&
|
|
36
|
+
style.borderStyle !== '' &&
|
|
37
|
+
(parseFloat(style.borderTopWidth) > 0 ||
|
|
38
|
+
parseFloat(style.borderRightWidth) > 0 ||
|
|
39
|
+
parseFloat(style.borderBottomWidth) > 0 ||
|
|
40
|
+
parseFloat(style.borderLeftWidth) > 0)
|
|
41
|
+
) {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
for (const node of element.childNodes) {
|
|
45
|
+
if (node.nodeType === Node.TEXT_NODE && (node.textContent ?? '').trim() !== '') return true;
|
|
46
|
+
}
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** `transparent` serializes as `rgba(0, 0, 0, 0)`; anything with alpha paints. */
|
|
51
|
+
function backgroundColorPaints(color: string): boolean {
|
|
52
|
+
if (!color || color === 'transparent') return false;
|
|
53
|
+
const rgba = /^rgba\(\s*[\d.]+\s*,\s*[\d.]+\s*,\s*[\d.]+\s*,\s*([\d.]+)\s*\)$/.exec(color);
|
|
54
|
+
if (rgba) return parseFloat(rgba[1]!) > 0;
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface PaintedBounds {
|
|
59
|
+
readonly x: number;
|
|
60
|
+
readonly y: number;
|
|
61
|
+
readonly width: number;
|
|
62
|
+
readonly height: number;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Union of the painted descendants' boxes in the mount box's authored px, or
|
|
67
|
+
* `null` when nothing paints. Rects are read through the live client transform
|
|
68
|
+
* (the board zooms its canvas), so the mount box's own client width supplies
|
|
69
|
+
* the scale — self-normalizing, no zoom parameter to drift.
|
|
70
|
+
*/
|
|
71
|
+
export function paintedContentBounds(
|
|
72
|
+
content: HTMLElement,
|
|
73
|
+
rectOf: (element: Element) => DOMRect = (element) => element.getBoundingClientRect(),
|
|
74
|
+
): PaintedBounds | null {
|
|
75
|
+
const home = rectOf(content);
|
|
76
|
+
const authoredWidth = content.offsetWidth;
|
|
77
|
+
if (home.width <= 0 || authoredWidth <= 0) return null;
|
|
78
|
+
const scale = home.width / authoredWidth;
|
|
79
|
+
let left = Number.POSITIVE_INFINITY;
|
|
80
|
+
let top = Number.POSITIVE_INFINITY;
|
|
81
|
+
let right = Number.NEGATIVE_INFINITY;
|
|
82
|
+
let bottom = Number.NEGATIVE_INFINITY;
|
|
83
|
+
for (const element of content.querySelectorAll('*')) {
|
|
84
|
+
if (!elementPaints(element)) continue;
|
|
85
|
+
const rect = rectOf(element);
|
|
86
|
+
if (rect.width <= 0 || rect.height <= 0) continue;
|
|
87
|
+
left = Math.min(left, (rect.left - home.left) / scale);
|
|
88
|
+
top = Math.min(top, (rect.top - home.top) / scale);
|
|
89
|
+
right = Math.max(right, (rect.right - home.left) / scale);
|
|
90
|
+
bottom = Math.max(bottom, (rect.bottom - home.top) / scale);
|
|
91
|
+
}
|
|
92
|
+
if (right <= left || bottom <= top) return null;
|
|
93
|
+
return { x: left, y: top, width: right - left, height: bottom - top };
|
|
94
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { registerAvailableWorkspaceDocument } from '@volter/editor-core/workspace-available-documents';
|
|
2
|
+
/**
|
|
3
|
+
* THE `UI` BOARD's DOCUMENT — what `ui-component-board.ts` installs, kept in
|
|
4
|
+
* its own module so the registration's eager closure is the verdict and
|
|
5
|
+
* nothing else. This half reaches the host's whole root-document machinery
|
|
6
|
+
* (and through it the design-time layer stack); loading that at contribution
|
|
7
|
+
* time for a project that has no dom stories is what the lazy `install`
|
|
8
|
+
* contract exists to avoid — the 2D board's own reason, applied to all three.
|
|
9
|
+
*
|
|
10
|
+
* It was `openUiBoard` inside `@editor/components/world-documents.tsx`, one
|
|
11
|
+
* arm of a literal table of three media that the host reconciled against a
|
|
12
|
+
* CSF census it also imported. What is dom-specific about it — that its
|
|
13
|
+
* medium is `dom`, that it exists because this project has `dom` stories,
|
|
14
|
+
* what a human should be told when it does not, and that its captured frame
|
|
15
|
+
* round-trips through the remembered portable story — travels here. What is
|
|
16
|
+
* HOST machinery stays host and is called through: `RootDocumentContent` (the
|
|
17
|
+
* board substrate every root document mounts, which draws the infinite canvas,
|
|
18
|
+
* the selection overlay, the rulers and the surface-state explanation) and the
|
|
19
|
+
* workspace document registry.
|
|
20
|
+
*
|
|
21
|
+
* A DOM ROOT IS NOT WHY THIS BOARD EXISTS — it only decides what the board is
|
|
22
|
+
* a document OF. With a dom root the board carries that root's provenance and
|
|
23
|
+
* its route, so `activateRootDocument(rootId)` reaches it; without one the
|
|
24
|
+
* board is still the project's UI component gallery, mounted rootless on
|
|
25
|
+
* `projectStoryBoardDescriptor()`.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import {
|
|
29
|
+
designTimeRootDescriptors,
|
|
30
|
+
projectStoryBoardDescriptor,
|
|
31
|
+
rememberedPortableStory,
|
|
32
|
+
} from '@volter/editor-core/authoring/design-time-layers';
|
|
33
|
+
import type { ComponentBoardContext } from '@volter/editor-core/component-board-registry';
|
|
34
|
+
import { RootDocumentContent } from '@volter/editor-core/components/world-documents';
|
|
35
|
+
import type { DocumentPreviewSource } from '@volter/editor-core/document-preview-source';
|
|
36
|
+
import { getCurrentProject } from '@volter/editor-core/project-manager';
|
|
37
|
+
import {
|
|
38
|
+
getProjectPreviewStories,
|
|
39
|
+
subscribeProjectStoryModules,
|
|
40
|
+
} from '@volter/editor-core/stories/story-registry';
|
|
41
|
+
import { UI_COMPONENTS_DOCUMENT_ID } from '@volter/editor-core/workspace-document-ids';
|
|
42
|
+
import type { WorkspaceDocumentContentProps } from '@volter/editor-core/workspace-document-registry';
|
|
43
|
+
|
|
44
|
+
import { UI_COMPONENTS_TITLE } from './ui-board-title';
|
|
45
|
+
|
|
46
|
+
const uiCanvasPreview: DocumentPreviewSource = {
|
|
47
|
+
revision: () =>
|
|
48
|
+
JSON.stringify(getProjectPreviewStories().map(({ id, modulePath }) => [id, modulePath])),
|
|
49
|
+
subscribe: subscribeProjectStoryModules,
|
|
50
|
+
capture: async ({ width, height }) => {
|
|
51
|
+
const { captureDomStoryBoardPreview } = await import('@volter/editor-core/stories/story-capture');
|
|
52
|
+
return captureDomStoryBoardPreview(width, height);
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export function installUiBoard({ store, composite }: ComponentBoardContext): void {
|
|
57
|
+
const descriptor =
|
|
58
|
+
designTimeRootDescriptors(composite).find((root) => root.kind === 'dom') ??
|
|
59
|
+
projectStoryBoardDescriptor();
|
|
60
|
+
const Content = (props: WorkspaceDocumentContentProps) => (
|
|
61
|
+
<RootDocumentContent {...props} composite={composite} descriptor={descriptor} store={store} />
|
|
62
|
+
);
|
|
63
|
+
registerAvailableWorkspaceDocument(
|
|
64
|
+
{
|
|
65
|
+
id: UI_COMPONENTS_DOCUMENT_ID,
|
|
66
|
+
title: UI_COMPONENTS_TITLE,
|
|
67
|
+
kind: 'world',
|
|
68
|
+
workspaceRole: 'authored-subject',
|
|
69
|
+
provenance: {
|
|
70
|
+
rootId: descriptor.worldId,
|
|
71
|
+
...(descriptor.path ? { sourcePath: descriptor.path } : {}),
|
|
72
|
+
},
|
|
73
|
+
Content,
|
|
74
|
+
preview: uiCanvasPreview,
|
|
75
|
+
presentation: () => {
|
|
76
|
+
// Round-trip the board's active frame: a captured view names the
|
|
77
|
+
// story it was on (the design ledger's board story selector).
|
|
78
|
+
const projectRoot = getCurrentProject()?.rootPath;
|
|
79
|
+
const story = projectRoot
|
|
80
|
+
? rememberedPortableStory(projectRoot, descriptor.worldId)
|
|
81
|
+
: undefined;
|
|
82
|
+
return {
|
|
83
|
+
kind: 'workspace',
|
|
84
|
+
id: UI_COMPONENTS_DOCUMENT_ID,
|
|
85
|
+
...(story ? { story } : {}),
|
|
86
|
+
};
|
|
87
|
+
},
|
|
88
|
+
onActivate: () => store.setActiveViewportTab('edit'),
|
|
89
|
+
},
|
|
90
|
+
// A board NEVER steals focus — presence is answered asynchronously, so a
|
|
91
|
+
// board that arrives late would otherwise yank the author off whatever
|
|
92
|
+
// they were looking at (measured: the canvas root's `Scene` opened, then a
|
|
93
|
+
// board landed a second later and took the tab).
|
|
94
|
+
{
|
|
95
|
+
category: 'canvas',
|
|
96
|
+
default: !composite
|
|
97
|
+
.childAdapters()
|
|
98
|
+
.some((root) => root.kind === 'three' || root.kind === 'canvas'),
|
|
99
|
+
},
|
|
100
|
+
);
|
|
101
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** The tab reads `UI`, the peer of `Scene` and `3D` in the center strip. The
|
|
2
|
+
* document ID keeps its longer historical spelling because persisted layouts
|
|
3
|
+
* address panels by id (and deriving either from the root only made the panel
|
|
4
|
+
* identity churn on a rename, stranding the old panel in every layout).
|
|
5
|
+
*
|
|
6
|
+
* Its own module because the registration reads it for the host's absence
|
|
7
|
+
* prose while the DOCUMENT reads it for the tab, and the two halves are
|
|
8
|
+
* deliberately in different closures. */
|
|
9
|
+
export const UI_COMPONENTS_TITLE = 'UI';
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE `UI` BOARD's registration — the dom medium's answer to
|
|
3
|
+
* `@editor/component-board-registry` (WORK.md §The open-source launch item
|
|
4
|
+
* 18a).
|
|
5
|
+
*
|
|
6
|
+
* It was one arm of a literal table of three media inside
|
|
7
|
+
* `@editor/components/world-documents.tsx`, which the host reconciled against
|
|
8
|
+
* a CSF census it imported itself. What is dom-specific about it — that its
|
|
9
|
+
* medium is `dom`, that the board exists because this project has `dom`
|
|
10
|
+
* stories, and what a human should be told when it does not — lives here; the
|
|
11
|
+
* document it installs is `ui-board-document.tsx`, loaded only for a project
|
|
12
|
+
* whose verdict is `'present'`.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { commandLine } from '@volter/editor-core/product-command';
|
|
16
|
+
import type { ComponentBoard, ComponentBoardContext } from '@volter/editor-core/component-board-registry';
|
|
17
|
+
import { editorConsole } from '@volter/editor-core/editor-console';
|
|
18
|
+
import {
|
|
19
|
+
getStoryMediaPresence,
|
|
20
|
+
subscribeStoryMediaPresence,
|
|
21
|
+
} from '../host/stories/story-media-presence';
|
|
22
|
+
import { projectStoriesReady, subscribeProjectStoryModules } from '@volter/editor-core/stories/story-registry';
|
|
23
|
+
import { UI_COMPONENTS_DOCUMENT_ID } from '@volter/editor-core/workspace-document-ids';
|
|
24
|
+
import { UI_COMPONENTS_TITLE } from './ui-board-title';
|
|
25
|
+
|
|
26
|
+
export const uiComponentBoard: ComponentBoard = {
|
|
27
|
+
medium: 'dom',
|
|
28
|
+
owner: '@volter/editor-game/react/ui-board',
|
|
29
|
+
documentId: UI_COMPONENTS_DOCUMENT_ID,
|
|
30
|
+
title: UI_COMPONENTS_TITLE,
|
|
31
|
+
// A COMPONENT BOARD EXISTS BECAUSE THE PROJECT HAS STORIES OF ITS MEDIUM,
|
|
32
|
+
// and for no other reason (owner ruling, 2026-08-15; ARCHITECTURE-CORE
|
|
33
|
+
// §center-surface ontology). Root kinds are irrelevant: a project whose UI
|
|
34
|
+
// components live inside an ingested game — no `dom` root in its manifest at
|
|
35
|
+
// all — still gets this board the moment one of them declares a story.
|
|
36
|
+
//
|
|
37
|
+
// `null` while discovery has not published: an empty UNREADY registry is
|
|
38
|
+
// boot in flight, never a verdict (`story-registry.ts` says so at the flag
|
|
39
|
+
// itself). That tri-state is what the host used to spell for itself as
|
|
40
|
+
// `projectStoriesReady()` in its own gap thunks.
|
|
41
|
+
presence: () =>
|
|
42
|
+
!projectStoriesReady() ? null : getStoryMediaPresence().dom ? 'present' : 'absent',
|
|
43
|
+
subscribe: (listener) => {
|
|
44
|
+
// Two signals, because the verdict is a function of both: the census
|
|
45
|
+
// (which media the project's stories declare) and the discovery flag that
|
|
46
|
+
// decides whether the census counts yet.
|
|
47
|
+
const stopPresence = subscribeStoryMediaPresence(listener);
|
|
48
|
+
const stopRegistry = subscribeProjectStoryModules(listener);
|
|
49
|
+
return () => {
|
|
50
|
+
stopPresence();
|
|
51
|
+
stopRegistry();
|
|
52
|
+
};
|
|
53
|
+
},
|
|
54
|
+
install: async (context: ComponentBoardContext) => {
|
|
55
|
+
try {
|
|
56
|
+
const { installUiBoard } = await import('./ui-board-document');
|
|
57
|
+
installUiBoard(context);
|
|
58
|
+
} catch (error: unknown) {
|
|
59
|
+
editorConsole.error(
|
|
60
|
+
`Failed to load the ${UI_COMPONENTS_TITLE} board: ${String(error)}`,
|
|
61
|
+
'scene',
|
|
62
|
+
);
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
absentReason: () =>
|
|
67
|
+
`This project has no ${UI_COMPONENTS_TITLE} board (${UI_COMPONENTS_DOCUMENT_ID}): board ` +
|
|
68
|
+
"presence keys on STORIES, not on roots, and none of this project's stories declares the " +
|
|
69
|
+
'"dom" medium. Declare it — a manifest root entry naming the story\'s module, or a ' +
|
|
70
|
+
`\`vgai.adapter.ts\` regionInclude that covers it — and the board appears. ${commandLine('console')} ` +
|
|
71
|
+
'names every story it could not place.',
|
|
72
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE AUDIO-POSE GUARD: the editor never drives Web Audio with a pose that is
|
|
3
|
+
* not a number.
|
|
4
|
+
*
|
|
5
|
+
* ## The failure this ends
|
|
6
|
+
*
|
|
7
|
+
* The editor draws scene graphs it did not author. `WebGLRenderer.render`
|
|
8
|
+
* calls `scene.updateMatrixWorld()` (three's own first line, gated only on
|
|
9
|
+
* `scene.matrixWorldAutoUpdate`), and that recursion reaches every
|
|
10
|
+
* `THREE.AudioListener` / `THREE.PositionalAudio` in the tree. Both override
|
|
11
|
+
* `updateMatrixWorld` to push their world pose straight into Web Audio via
|
|
12
|
+
* `AudioParam.linearRampToValueAtTime`, which **throws** on a non-finite
|
|
13
|
+
* value. So one NaN anywhere above an audio node turns the editor's own draw
|
|
14
|
+
* of an adopted play scene into a thrown error, once per frame.
|
|
15
|
+
*
|
|
16
|
+
* The NaN is not hypothetical and its cause is already measured in this repo
|
|
17
|
+
* (`content-bounds.ts`, same measurement, same game): a physics binding writes
|
|
18
|
+
* `object.matrix` from worker buffers the worker has not answered into yet, so
|
|
19
|
+
* at frame zero real nodes carry `NaN` transforms. In racing-game that
|
|
20
|
+
* propagates two ways — a `<PositionalAudio>` parented under the cannon-driven
|
|
21
|
+
* chassis, and the game camera, whose pose is lerped from a physics-derived
|
|
22
|
+
* speed and which every drei-minted `AudioListener` is a child of. A single
|
|
23
|
+
* NaN lerp poisons that camera permanently; there is no re-seed.
|
|
24
|
+
*
|
|
25
|
+
* (The design-time half of the same root cause is fixed differently and
|
|
26
|
+
* upstream: `authoring/design-time-settle.ts` runs the world's physics for a
|
|
27
|
+
* bounded settle at mount, so Edit mode is no longer looking at unanswered
|
|
28
|
+
* worker buffers at all. This guard is what covers PLAY, where the game's own
|
|
29
|
+
* loop is running and the editor is a second reader of its graph.)
|
|
30
|
+
*
|
|
31
|
+
* ## Why the guard is on the audio node, and not on the render call
|
|
32
|
+
*
|
|
33
|
+
* The throw happens INSIDE three's `updateMatrixWorld` recursion, so nothing
|
|
34
|
+
* that wraps `composer.render` can intercept a single node, and
|
|
35
|
+
* `matrixWorldAutoUpdate` cannot help: the subclass body runs after
|
|
36
|
+
* `super.updateMatrixWorld()` regardless of any flag. The one honest intercept
|
|
37
|
+
* is the two prototypes themselves. Prototype-level (not per-instance) is
|
|
38
|
+
* required because these nodes are minted LATE — drei's `<PositionalAudio>`
|
|
39
|
+
* does `camera.add(new AudioListener())` in an effect after its buffer
|
|
40
|
+
* resolves — so any walk done at adoption time would miss them.
|
|
41
|
+
*
|
|
42
|
+
* Patching a library prototype to host a foreign game is an established
|
|
43
|
+
* mechanism here, not a new one: `@volter/threejs-runtime/adapter/ingest/scene-capture` traps
|
|
44
|
+
* `WebGLRenderer.prototype.render` for exactly the same reason (the host must
|
|
45
|
+
* observe a game it does not own, at a seam the game never offered).
|
|
46
|
+
*
|
|
47
|
+
* ## What it does, precisely
|
|
48
|
+
*
|
|
49
|
+
* The scene graph stays EXACTLY correct — `Object3D.prototype.updateMatrixWorld`
|
|
50
|
+
* (which is what both overrides call as `super`) always runs, so children,
|
|
51
|
+
* bounds, picking and selection see the truth including the NaN. Only the Web
|
|
52
|
+
* Audio push is conditional: a running context and finite pose use three's
|
|
53
|
+
* own implementation; otherwise the node keeps its last good audio pose.
|
|
54
|
+
* A suspended context cannot consume pose ramps, so scheduling them every
|
|
55
|
+
* render would grow its automation queue while its audio clock stands still.
|
|
56
|
+
*
|
|
57
|
+
* The shared engine guard also installs through setupAudio in exported games.
|
|
58
|
+
* Its prototype marker prevents duplicate wrapping when the packaged editor
|
|
59
|
+
* and source-served runtime import separate copies against the same Three.
|
|
60
|
+
*/
|
|
61
|
+
|
|
62
|
+
import { editorHost } from '@volter/editor-sdk/host';
|
|
63
|
+
import { installAudioPoseGuard as installSharedAudioPoseGuard } from '@volter/game-runtime/audio/pose-guard';
|
|
64
|
+
|
|
65
|
+
export { audioPoseUpdatesDropped } from '@volter/game-runtime/audio/pose-guard';
|
|
66
|
+
|
|
67
|
+
let reported = false;
|
|
68
|
+
|
|
69
|
+
/** Attach the editor diagnostic to the shared runtime guard. */
|
|
70
|
+
export function installAudioPoseGuard(): void {
|
|
71
|
+
installSharedAudioPoseGuard((type) => {
|
|
72
|
+
if (reported) return;
|
|
73
|
+
reported = true;
|
|
74
|
+
editorHost().console.log(
|
|
75
|
+
`[audio-pose-guard] dropped a Web Audio pose update for a ${type} whose ` +
|
|
76
|
+
'world matrix is not finite — the scene graph is unchanged and the node keeps its ' +
|
|
77
|
+
'last good pose. Reported once; see audioPoseUpdatesDropped() for the running count.',
|
|
78
|
+
'authoring',
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HOST-LEVEL audio unlock for game-created AudioContexts.
|
|
3
|
+
*
|
|
4
|
+
* A game running in the editor's page can create its AudioContext before the
|
|
5
|
+
* visitor's first gesture (survivor's auto-attack lands kills while they are
|
|
6
|
+
* still reading), and browsers with a strict autoplay policy then hold it
|
|
7
|
+
* suspended until `resume()` runs INSIDE a trusted input handler. The game's
|
|
8
|
+
* own unlock listeners go through the realm's gated re-dispatch, which is no
|
|
9
|
+
* longer that trusted stack — two human passes heard both flagship canvas
|
|
10
|
+
* games silent on first load and cured them only by restarting under a
|
|
11
|
+
* gesture (runhuman 23/33).
|
|
12
|
+
*
|
|
13
|
+
* The EDITOR owns the page, so it does what a game cannot: wrap the page's
|
|
14
|
+
* AudioContext constructor once at boot to track instances, and resume any
|
|
15
|
+
* suspended one from capture-phase pointerdown/keydown handlers — the real,
|
|
16
|
+
* trusted stack. Contexts already running are untouched; the listeners stay
|
|
17
|
+
* (cheap) so a context created later unlocks on the next gesture.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const tracked = new Set<WeakRef<AudioContext>>();
|
|
21
|
+
|
|
22
|
+
function resumeSuspended(): void {
|
|
23
|
+
for (const ref of tracked) {
|
|
24
|
+
const ctx = ref.deref();
|
|
25
|
+
if (!ctx || ctx.state === 'closed') {
|
|
26
|
+
tracked.delete(ref);
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (ctx.state === 'suspended') void ctx.resume();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Install once at editor boot (main.tsx). Idempotent. */
|
|
34
|
+
export function installGameAudioUnlock(): void {
|
|
35
|
+
const host = window as unknown as { __vgaiAudioUnlock?: boolean } & typeof window;
|
|
36
|
+
if (host.__vgaiAudioUnlock) return;
|
|
37
|
+
host.__vgaiAudioUnlock = true;
|
|
38
|
+
const Orig = window.AudioContext;
|
|
39
|
+
if (typeof Orig !== 'function') return;
|
|
40
|
+
window.AudioContext = class extends Orig {
|
|
41
|
+
constructor(...args: ConstructorParameters<typeof AudioContext>) {
|
|
42
|
+
super(...args);
|
|
43
|
+
tracked.add(new WeakRef(this));
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
window.addEventListener('pointerdown', resumeSuspended, { capture: true });
|
|
47
|
+
window.addEventListener('keydown', resumeSuspended, { capture: true });
|
|
48
|
+
}
|