@volter/editor-game 0.5.66 → 0.5.67

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.
Files changed (236) hide show
  1. package/contributions/audio-unlock.service.ts +2 -2
  2. package/contributions/autoplay.service.ts +1 -1
  3. package/contributions/bridge.command.ts +3 -3
  4. package/contributions/canvas/component-board.service.ts +16 -0
  5. package/contributions/canvas/design-time-mount.service.ts +45 -0
  6. package/contributions/canvas-story-capture.service.ts +12 -0
  7. package/contributions/edit-mode-audio.service.ts +2 -2
  8. package/contributions/edit-mode-networking.service.ts +1 -1
  9. package/contributions/gameplay.command.ts +4 -4
  10. package/contributions/generation.service.ts +1 -1
  11. package/contributions/godot.style.ts +26 -6
  12. package/contributions/godot.view.ts +6 -0
  13. package/contributions/ingest.service.ts +2 -2
  14. package/contributions/instances.command.ts +1 -1
  15. package/contributions/navmesh.menu.ts +1 -1
  16. package/contributions/network-observer.service.ts +14 -0
  17. package/contributions/play.command.ts +10 -4
  18. package/contributions/react/component-board.service.ts +1 -1
  19. package/contributions/react/design-time-mount.service.ts +1 -1
  20. package/contributions/scene-document.service.ts +3 -3
  21. package/contributions/state-watch.menu.ts +1 -1
  22. package/contributions/state-watch.utility.tsx +1 -1
  23. package/contributions/team-playtest.service.ts +4 -4
  24. package/contributions/three/component-board.service.ts +1 -1
  25. package/contributions/three/component-verbs.command.ts +6 -6
  26. package/contributions/three/three-authoring.service.ts +8 -5
  27. package/contributions/three-story-capture.service.ts +12 -0
  28. package/contributions/unity.style.ts +17 -7
  29. package/contributions/unity.view.ts +6 -0
  30. package/contributions/unreal.style.ts +10 -2
  31. package/contributions/unreal.view.ts +5 -0
  32. package/package.json +20 -10
  33. package/src/asset-budget/AssetBudgetPanel.tsx +1 -1
  34. package/src/asset-budget/asset-budget-model.ts +1 -1
  35. package/src/audio/AudioDebuggerPanel.tsx +1 -1
  36. package/src/bridge/dispatch.ts +14 -14
  37. package/src/bridge/live-frames.ts +1 -1
  38. package/src/bridge/screenshot.ts +3 -3
  39. package/src/build/BuildProfilesPanel.tsx +3 -3
  40. package/src/canvas/canvas-board/CanvasBoardDocument.tsx +676 -0
  41. package/src/canvas/canvas-board/canvas-board-model.ts +407 -0
  42. package/src/canvas/canvas-board/canvas-component-board.ts +56 -0
  43. package/src/canvas/canvas-design-mount.ts +524 -0
  44. package/src/canvas/design-time-canvas-mount.ts +79 -0
  45. package/src/coverage/live-authoring-surface.ts +5 -5
  46. package/src/coverage/live-project-verbs.ts +1 -1
  47. package/src/coverage/native-system-coverage.ts +5 -5
  48. package/src/coverage/root-coverage.ts +1 -1
  49. package/src/coverage/session-coverage.ts +3 -3
  50. package/src/design-system-stories/ApplicationChrome.stories.tsx +5 -5
  51. package/src/design-system-stories/InspectorNarrowBodies.stories.tsx +7 -7
  52. package/src/edit-mode/edit-mode-audio.ts +4 -4
  53. package/src/edit-mode/edit-mode-networking.ts +2 -2
  54. package/src/game-document/GameCaptureFrameButton.tsx +1 -1
  55. package/src/game-document/GameDocument.tsx +2 -2
  56. package/src/game-document/GamePanel.tsx +4 -4
  57. package/src/game-document/InstanceInspectorPicker.tsx +2 -2
  58. package/src/game-document/crowd-debug.ts +1 -1
  59. package/src/game-document/device-preview.ts +10 -11
  60. package/src/game-document/physics-debug.ts +1 -1
  61. package/src/generation/GenerationActivity.tsx +3 -3
  62. package/src/generation/generation-documents.tsx +5 -5
  63. package/src/generation/generation-jobs.ts +1 -1
  64. package/src/host/adapter-runtime-bindings.ts +96 -5
  65. package/src/host/authoring/babylon-authoring-adapter.ts +6 -6
  66. package/src/host/authoring/gesture-persist.ts +1 -1
  67. package/src/host/authoring/ingest-data-writer.ts +1 -1
  68. package/src/host/authoring/ingest-source-persistence.ts +4 -4
  69. package/src/host/authoring/mounted-authoring.ts +4 -4
  70. package/src/host/authoring/phaser-live-authoring-adapter.ts +4 -4
  71. package/src/host/authoring/pixi-authoring-adapter.ts +40 -15
  72. package/src/host/authoring/pixi-creation-site-write-target.ts +2 -2
  73. package/src/host/authoring/pixi-live-write-target.ts +4 -4
  74. package/src/host/authoring/pixi-source-identity.ts +3 -3
  75. package/src/host/authoring/pixi-source-write-target.ts +1614 -0
  76. package/src/host/authoring/pixi-still-presentation.ts +1 -1
  77. package/src/host/authoring/pixi-structure-history.ts +2 -2
  78. package/src/host/authoring/pixi-transform-channels.ts +16 -14
  79. package/src/host/authoring/source-persistence-backend.ts +3 -3
  80. package/src/host/authoring/struct-write-pipe.ts +1 -1
  81. package/src/host/binding-resolver.ts +8 -9
  82. package/src/host/browser-transpile.ts +1 -1
  83. package/src/host/canvas-entry-runtime.ts +58 -47
  84. package/src/host/canvas-preview-frames.ts +482 -0
  85. package/src/host/components/CameraAuthoringOverlay.tsx +1 -1
  86. package/src/host/components/HeaderTelemetry.tsx +4 -4
  87. package/src/host/components/PixiIsolationSceneContent.tsx +10 -10
  88. package/src/host/components/ThreeIsolationSceneContent.tsx +3 -3
  89. package/src/host/components/frame-debugger-model.ts +2 -2
  90. package/src/host/components/header-telemetry-model.ts +2 -2
  91. package/src/host/components/scene-document.tsx +14 -14
  92. package/src/host/components/utility-view-state.ts +1 -1
  93. package/src/host/components/world-root-stage-binding.tsx +12 -12
  94. package/src/host/components/world-root-stage.ts +70 -49
  95. package/src/host/coverage/system-adapter-coverage.ts +3 -4
  96. package/src/host/design-system-stories/fixtures/editor-runtime.tsx +9 -11
  97. package/src/host/document-preview-three.ts +1 -1
  98. package/src/host/entry-adjudication.ts +6 -6
  99. package/src/host/game-css-scope-transform.ts +4 -0
  100. package/src/host/game-realm-page.ts +1 -1
  101. package/src/host/gameplay-export.ts +25 -14
  102. package/src/host/gameplay-recording.ts +5 -5
  103. package/src/host/gated-globals.ts +2 -2
  104. package/src/host/history/json-history-resource.ts +1 -1
  105. package/src/host/projection/pixi.ts +24 -2
  106. package/src/host/r3f-entry-runtime.ts +65 -34
  107. package/src/host/react-mount-runtime.ts +7 -48
  108. package/src/host/realm-services.ts +1 -1
  109. package/src/host/roots/canvas-root.tsx +361 -0
  110. package/src/host/roots/r3f-root.tsx +473 -0
  111. package/src/host/roots/react-root.ts +9 -43
  112. package/src/host/served-bundle-runtime-modules.ts +1 -19
  113. package/src/host/server-log-bridge.ts +2 -2
  114. package/src/host/stories/mounted-story-viewport-source.ts +1 -1
  115. package/src/host/stories/pixi-story-model.ts +30 -0
  116. package/src/host/stories/story-media-captures.ts +46 -0
  117. package/src/host/stories/story-media-presence.ts +3 -3
  118. package/src/host/stories/story-pixi-preview.ts +408 -0
  119. package/src/host/stories/story-three-preview.ts +806 -0
  120. package/src/host/stories/three-story-captures.ts +35 -0
  121. package/src/host/story-three-preview-runtime.ts +56 -0
  122. package/src/host/use-active-performance-source.ts +2 -2
  123. package/src/host/viewport-pose-memory.ts +1 -1
  124. package/src/host/viewport-root-presentation.ts +6 -5
  125. package/src/ingest/active-ingest.ts +1 -1
  126. package/src/ingest/authoring/ingest-dom-surface-authoring.ts +4 -4
  127. package/src/ingest/authoring/ingest-root-adapter.ts +8 -8
  128. package/src/ingest/deferred-ingest-play.ts +6 -6
  129. package/src/ingest/discovery-public-ingest.ts +2 -2
  130. package/src/ingest/ingest-boot-viewport.ts +2 -2
  131. package/src/ingest/ingest-canvas-scene-document.tsx +8 -8
  132. package/src/ingest/ingest-canvas-scene.ts +3 -3
  133. package/src/ingest/ingest-evidence-hook.ts +1 -1
  134. package/src/ingest/ingest-frame-snapshot.ts +1 -1
  135. package/src/ingest/ingest-play-control.ts +1 -1
  136. package/src/ingest/ingest-render-debug.ts +10 -10
  137. package/src/ingest/ingest-siblings.ts +13 -25
  138. package/src/ingest/module-mode.ts +14 -14
  139. package/src/ingest/mount-canvas-ingest-root.ts +21 -21
  140. package/src/ingest/mount-coverage.ts +2 -2
  141. package/src/ingest/mount-dom-ingest-root.ts +11 -11
  142. package/src/ingest/mount-ingest-root.ts +8 -8
  143. package/src/ingest/mount-three-ingest-root.ts +8 -8
  144. package/src/ingest/resolve-canvas.ts +1 -1
  145. package/src/ingest/served-html-boot.ts +1 -1
  146. package/src/ingest/surface-canvas.ts +1 -1
  147. package/src/ingest/unmount-ingest-root.ts +5 -5
  148. package/src/navmesh/navmesh-handler.ts +24 -16
  149. package/src/network/NetworkInspectorPanel.tsx +449 -14
  150. package/src/network/network-inspector-model.ts +14 -2
  151. package/src/play/play-log-events.ts +1 -1
  152. package/src/play/play-mode.ts +76 -133
  153. package/src/play/play-recording.ts +1 -1
  154. package/src/play/react-play-live-authoring.ts +3 -3
  155. package/src/play/run-selection.ts +93 -0
  156. package/src/play-bar/PlayBar.tsx +20 -39
  157. package/src/profiler/FrameDebuggerPanel.tsx +1 -1
  158. package/src/profiler/PerformancePanel.tsx +3 -3
  159. package/src/react/design-time-react-mount.ts +23 -65
  160. package/src/react/dom-authoring-adapter.ts +9 -9
  161. package/src/react/react-inspector-section.tsx +5 -5
  162. package/src/react/react-world-authoring-adapter.ts +11 -11
  163. package/src/react/story-documents/story-documents.tsx +9 -9
  164. package/src/react/ui-board-document.tsx +7 -7
  165. package/src/react/ui-component-board.ts +2 -2
  166. package/src/runtime/adapter/audio-meter.ts +21 -0
  167. package/src/runtime/adapter/first-party-audio-system.ts +230 -0
  168. package/src/runtime/adapter/ingest/contract-debug-adapter.ts +114 -0
  169. package/src/runtime/adapter/ingest/contract-system-adapters.ts +256 -0
  170. package/src/runtime/adapter/ingest/merge-debug-adapters.ts +197 -0
  171. package/src/runtime/adapter/ingest/observation-debug-adapter.ts +162 -0
  172. package/src/runtime/adapter/ingest/upstream-pin.ts +51 -0
  173. package/src/runtime/adapter/native-debug-module.ts +498 -0
  174. package/src/runtime/audio/bus-mixer.ts +161 -0
  175. package/src/runtime/audio/pose-guard.ts +80 -0
  176. package/src/runtime/core/frame-pacing.ts +126 -0
  177. package/src/runtime/core/game-loop.ts +225 -0
  178. package/src/runtime/core/game-scoped-slot.ts +28 -0
  179. package/src/runtime/core/seeded-random.ts +162 -0
  180. package/src/runtime/core/sim-clock.ts +391 -0
  181. package/src/runtime/core/system-runner.ts +269 -0
  182. package/src/runtime/core/types.ts +104 -0
  183. package/src/runtime/create-runtime.ts +1128 -0
  184. package/src/runtime/debug-bridge.ts +570 -0
  185. package/src/runtime/debug-registry.ts +899 -0
  186. package/src/runtime/dev/chrome-trace.ts +153 -0
  187. package/src/runtime/dev/instruments.ts +403 -0
  188. package/src/runtime/dev/logger.ts +119 -0
  189. package/src/runtime/dev/performance-profiler.ts +367 -0
  190. package/src/runtime/dev/register-render-vitals.ts +276 -0
  191. package/src/runtime/dev/render-census.ts +354 -0
  192. package/src/runtime/dev/render-debug-adapter.ts +218 -0
  193. package/src/runtime/dev/render-memory.ts +226 -0
  194. package/src/runtime/dev/render-vitals.ts +338 -0
  195. package/src/runtime/dev/static-batch-advisor.ts +188 -0
  196. package/src/runtime/dev/webgl-frame-capture.ts +366 -0
  197. package/src/runtime/dev/webgl-gpu-timer.ts +53 -0
  198. package/src/runtime/dev-build.ts +47 -0
  199. package/src/runtime/game.ts +1636 -0
  200. package/src/runtime/gameplay-rng-trap.ts +135 -0
  201. package/src/runtime/host-context.ts +64 -0
  202. package/src/runtime/input-router.ts +169 -0
  203. package/src/runtime/mount-manifest.ts +480 -0
  204. package/src/runtime/pixi/authoring.ts +706 -0
  205. package/src/runtime/pixi/ingest.ts +116 -0
  206. package/src/runtime/pixi/physics-registry.ts +49 -0
  207. package/src/runtime/pixi/render-pass-bracket.ts +117 -0
  208. package/src/runtime/pixi/scene-capture.ts +179 -0
  209. package/src/runtime/pixi/system-adapters.ts +69 -0
  210. package/src/runtime/playtest.ts +22 -0
  211. package/src/runtime/presentation.ts +141 -0
  212. package/src/runtime/render-control.ts +642 -0
  213. package/src/runtime/render-seed.ts +77 -0
  214. package/src/runtime/run-ticks-settled.ts +73 -0
  215. package/src/runtime/setup/setup-audio.ts +72 -0
  216. package/src/services/audio-pose-guard.ts +2 -2
  217. package/src/services/game-audio.ts +152 -0
  218. package/src/services/game-network.ts +657 -0
  219. package/src/services/game-physics.ts +334 -0
  220. package/src/state-watch/StateWatchPanel.tsx +1 -1
  221. package/src/three/authoring/camera-runtime-inspector-section.tsx +3 -2
  222. package/src/three/authoring/constraint-inspector-section.tsx +6 -5
  223. package/src/three/authoring/model-asset-inspector-section.tsx +7 -6
  224. package/src/three/authoring/oid-source-persistence.ts +7 -7
  225. package/src/three/authoring/r3f-design-session.ts +58 -54
  226. package/src/three/authoring/r3f-source-authoring-adapter.ts +95 -91
  227. package/src/three/authoring/reflection-probe-inspector-section.tsx +3 -2
  228. package/src/three/authoring/three-authoring-adapter.ts +29 -29
  229. package/src/three/component-verbs/extract-menu.ts +7 -6
  230. package/src/three/component-verbs/fork-menu.ts +7 -6
  231. package/src/three/component-verbs/internals-menu.ts +2 -2
  232. package/src/three/story-documents/three-story-documents.tsx +13 -13
  233. package/src/three/three-board/ThreeBoardDocument.tsx +13 -12
  234. package/src/three/three-board/board-scene.ts +6 -6
  235. package/src/three/three-board/three-component-board.ts +2 -2
  236. package/src/services/game-audio-unlock.ts +0 -48
@@ -0,0 +1,482 @@
1
+ /**
2
+ * CANVAS PREVIEW AND PRESENTED PIXELS — the host's Pixi pixel plumbing, over a
3
+ * live display subtree that is already there. Two jobs, one substrate:
4
+ *
5
+ * - PHOTOGRAPHING a live subtree on an independent Application, under a node
6
+ * budget, so selecting a node costs a bounded amount whatever it selected
7
+ * ({@link capturePixiDisplayObjectThumbnail}, {@link snapshotPixiPreviewSubject}).
8
+ * - Answering the composite screenshot's `canvasFrame` seam for a canvas a
9
+ * Pixi renderer owns, whose drawing buffer is gone by the time the
10
+ * compositor reads it ({@link presentedPixiFrame}, over the surfaces that
11
+ * registered themselves).
12
+ *
13
+ * Neither job knows what put the objects there. A first-party canvas world, an
14
+ * ingested game and a portable CSF story register the same way and are
15
+ * photographed the same way — which is why this is the HOST's and not the story
16
+ * estate's. It lived inside `stories/story-pixi-preview.ts` and four of its
17
+ * five callers never had a story in hand (`authoring/canvas-design-mount.ts`,
18
+ * `authoring/pixi-still-presentation.ts`, `components/world-root-stage.ts`,
19
+ * `live-canvas-frame.ts`, and the ingest lane's canvas mount); moving it out is
20
+ * the part of the CSF estate's exit to `@vgai/game` (WORK.md §The open-source
21
+ * launch, phase 1 unit 3) that does not wait on anything else.
22
+ */
23
+
24
+ import type { Application, Container, Matrix, Rectangle } from 'pixi.js';
25
+ import * as shellPixi from 'pixi.js';
26
+ type CanvasPixiNamespace = typeof shellPixi;
27
+
28
+ /** Requested presentation size for a photographed subject. Presentation only:
29
+ * it never becomes a second authored size or a component-specific asset. */
30
+ export interface CanvasThumbnailSize {
31
+ readonly width?: number;
32
+ readonly height?: number;
33
+ }
34
+
35
+ /**
36
+ * How many display objects one selection preview may photograph.
37
+ *
38
+ * THE COST CLASS: selecting a node cloned and rendered that node's WHOLE
39
+ * subtree to produce a 128-pixel picture. Selection is the editor's most
40
+ * frequent gesture, and the cost was O(selected subtree) — unbounded.
41
+ *
42
+ * MEASURED in the page (`new Profiler`, 2.5s window around ONE relayed
43
+ * `select` of the world root, scale harness's canvas N=20000 world, same tab,
44
+ * same world, the only difference being whether this budget is live):
45
+ *
46
+ * inclusive samples, of the window's total
47
+ * before 112 / 346 capturePixiDisplayObjectThumbnail
48
+ * — under it: generateTexture 70, collectRenderables
49
+ * 34, getLocalBounds 19, i.e. a 20 000-object
50
+ * clone photographed on a real renderer
51
+ * after 0 / 241 the frame is absent from the trace entirely, and
52
+ * so are generateTexture, collectRenderables and
53
+ * getLocalBounds
54
+ *
55
+ * WHAT IT WAS NOT, stated because the shape misleads: this cost is NOT the
56
+ * long-animation-frame block a selection reports. It is asynchronous GPU and
57
+ * clone work spread across the frames after the click, so LoAF never named it
58
+ * and the editor's worst SINGLE block at N=20000 is unchanged by this fix (see
59
+ * `scale-speed-thumbnail-budget-{before,after}.json`; that block belongs to
60
+ * React's own layout-effect commit). What the profiler measures, and what this
61
+ * removes, is a third of all sampled main-thread time after a click.
62
+ *
63
+ * Deferral alone does not fix it — `components/inspector-preview-section.tsx`
64
+ * already holds the picture back a frame (`useAfterPaint`) and the work simply
65
+ * lands in the next frames instead. The only bound that holds is a bound on the
66
+ * WORK, so the subtree is COUNTED first (no further than this many nodes, so
67
+ * the count is itself O(budget), never O(subtree)) and a subtree above the
68
+ * budget is DECLINED — no clone, no render, no allocation.
69
+ *
70
+ * The number: an authored component — a HUD, a character, a prefab, the thing
71
+ * a human actually selects and wants to see — is tens to low hundreds of
72
+ * display objects, so this is roughly an order of magnitude of headroom above
73
+ * the case the preview exists to serve. What it declines is a LEVEL ROOT, and
74
+ * a level root at 128 pixels was never a picture of anything.
75
+ */
76
+ export const PREVIEW_SUBTREE_NODE_BUDGET = 2_000;
77
+
78
+ /**
79
+ * Display objects in `source`'s subtree, counted no further than `limit`.
80
+ *
81
+ * The return is the true count when that is at most `limit`, and `limit + 1` —
82
+ * "more than `limit`" — otherwise. That ceiling is the whole point: the caller's
83
+ * question is "may I afford this?", and a count that answered it exactly would
84
+ * pay the very cost it is being asked to avoid.
85
+ *
86
+ * Drained with a CURSOR rather than `Array.prototype.shift()`, for the reason
87
+ * `authoring/hierarchy-walk.ts` records: `shift` moves every remaining element
88
+ * down, so a flat-fanned tree turns a linear-looking walk quadratic.
89
+ */
90
+ export function countPixiSubtree(source: Container, limit: number): number {
91
+ const queue: Container[] = [source];
92
+ let cursor = 0;
93
+ let count = 0;
94
+ while (cursor < queue.length) {
95
+ const node = queue[cursor++]!;
96
+ count++;
97
+ if (count > limit) return limit + 1;
98
+ for (const child of node.children) queue.push(child as Container);
99
+ }
100
+ return count;
101
+ }
102
+
103
+ /**
104
+ * Render-only snapshot of a live display subtree, or `null` when the subtree is
105
+ * larger than `budget`. The source is never reparented, filtered, or rendered:
106
+ * same stance as three's `createAssetPreviewSnapshot`. Textures are shared;
107
+ * filters are not (they bind to the live renderer).
108
+ *
109
+ * The budget is a DEFAULT rather than an opt-in — see
110
+ * {@link PREVIEW_SUBTREE_NODE_BUDGET}. A caller that genuinely wants the whole
111
+ * tree whatever it costs passes `Number.POSITIVE_INFINITY` and says why.
112
+ */
113
+ export function snapshotPixiPreviewSubject(
114
+ source: Container,
115
+ budget: number = PREVIEW_SUBTREE_NODE_BUDGET,
116
+ pixi: CanvasPixiNamespace = shellPixi,
117
+ ): Container | null {
118
+ if (countPixiSubtree(source, budget) > budget) return null;
119
+ const snapshot = clonePixiVisual(pixi, source);
120
+ const world = composedWorldMatrix(pixi, source);
121
+ snapshot.position.set(0, 0);
122
+ snapshot.scale.set(Math.hypot(world.a, world.b), Math.hypot(world.c, world.d));
123
+ snapshot.rotation = Math.atan2(world.b, world.a);
124
+ snapshot.visible = true;
125
+ return snapshot;
126
+ }
127
+
128
+ /** Compose local transforms up to the stage. Avoids `updateTransform()`,
129
+ * which requires a live render-group parent the snapshot must not assume. */
130
+ function composedWorldMatrix(pixi: CanvasPixiNamespace, source: Container): Matrix {
131
+ const world = new pixi.Matrix();
132
+ const chain: Container[] = [];
133
+ for (let node: Container | null = source; node; node = node.parent) chain.push(node);
134
+ for (let i = chain.length - 1; i >= 0; i--) {
135
+ chain[i]!.updateLocalTransform();
136
+ world.append(chain[i]!.localTransform);
137
+ }
138
+ return world;
139
+ }
140
+
141
+ /**
142
+ * `pixi` is the namespace `source` ITSELF came from — see
143
+ * `../../vite-plugin-module-doorways.ts`. Every `instanceof` below is a class
144
+ * from that same graph, which is what makes the chain answer at all: against
145
+ * the shell's classes a project subtree missed EVERY branch, fell to the
146
+ * generic Container tail, and produced a preview with no textures in it —
147
+ * exactly the empty-preview failure the NineSlice note below records, at the
148
+ * scale of the whole tree.
149
+ */
150
+ function clonePixiVisual(pixi: CanvasPixiNamespace, source: Container): Container {
151
+ // NineSliceSprite is a ViewContainer, not a Sprite. Falling through to the
152
+ // generic Container branch drops the texture and yields an empty preview —
153
+ // the measured Bubbo HUD `decorContainer` trays and borders.
154
+ if (source instanceof pixi.NineSliceSprite) {
155
+ const slice = new pixi.NineSliceSprite({
156
+ texture: source.texture,
157
+ leftWidth: source.leftWidth,
158
+ rightWidth: source.rightWidth,
159
+ topHeight: source.topHeight,
160
+ bottomHeight: source.bottomHeight,
161
+ width: source.width,
162
+ height: source.height,
163
+ });
164
+ slice.anchor.copyFrom(source.anchor);
165
+ slice.tint = source.tint;
166
+ copyPixiLocalPose(source, slice);
167
+ return slice;
168
+ }
169
+ if (source instanceof pixi.TilingSprite) {
170
+ const tile = new pixi.TilingSprite({
171
+ texture: source.texture,
172
+ width: source.width,
173
+ height: source.height,
174
+ });
175
+ tile.tilePosition.copyFrom(source.tilePosition);
176
+ tile.tileScale.copyFrom(source.tileScale);
177
+ tile.anchor.copyFrom(source.anchor);
178
+ copyPixiLocalPose(source, tile);
179
+ return tile;
180
+ }
181
+ if (source instanceof pixi.Text) {
182
+ const text = new pixi.Text({
183
+ text: source.text,
184
+ style: source.style.clone(),
185
+ });
186
+ if ('anchor' in source) text.anchor.copyFrom(source.anchor);
187
+ copyPixiLocalPose(source, text);
188
+ return text;
189
+ }
190
+ if (source instanceof pixi.Graphics) {
191
+ const graphics = source.clone(true);
192
+ copyPixiLocalPose(source, graphics);
193
+ return graphics;
194
+ }
195
+ if (source instanceof pixi.Sprite) {
196
+ const sprite = new pixi.Sprite(source.texture);
197
+ sprite.anchor.copyFrom(source.anchor);
198
+ sprite.tint = source.tint;
199
+ copyPixiLocalPose(source, sprite);
200
+ return sprite;
201
+ }
202
+ const container = new pixi.Container();
203
+ copyPixiLocalPose(source, container);
204
+ container.pivot.copyFrom(source.pivot);
205
+ for (const child of source.children) {
206
+ container.addChild(clonePixiVisual(pixi, child as Container));
207
+ }
208
+ return container;
209
+ }
210
+
211
+ function copyPixiLocalPose(source: Container, target: Container): void {
212
+ target.alpha = source.alpha;
213
+ target.visible = source.visible;
214
+ target.rotation = source.rotation;
215
+ target.scale.copyFrom(source.scale);
216
+ target.position.copyFrom(source.position);
217
+ target.skew.copyFrom(source.skew);
218
+ }
219
+
220
+ /**
221
+ * OWNER — this module. SHARERS — {@link capturePixiDisplayObjectThumbnail}.
222
+ * THE ONE TEARDOWN — {@link disposeInspectorPixiPreviewApp}. The live scene
223
+ * Application is never a sharer: three photographs a snapshot on its own
224
+ * WebGLRenderer, and this is that lane for canvas.
225
+ *
226
+ * KEYED BY NAMESPACE, not a singleton. The subject's textures belong to the
227
+ * graph the subject came from, and a renderer only understands its own
228
+ * `TextureSource`s — so a shell Application photographing a project subtree is
229
+ * one renderer looking at another renderer's GPU resources. There is at most
230
+ * one entry per namespace and at most two namespaces on a page (the shell's,
231
+ * and the opened project's under the packaged runtime), so this is a two-slot
232
+ * map, not a cache with a growth story.
233
+ */
234
+ const previewApps = new Map<CanvasPixiNamespace, Application>();
235
+ const previewAppsReady = new Map<CanvasPixiNamespace, Promise<Application>>();
236
+
237
+ async function inspectorPixiPreviewApp(pixi: CanvasPixiNamespace): Promise<Application> {
238
+ const existing = previewApps.get(pixi);
239
+ if (existing) return existing;
240
+ let pending = previewAppsReady.get(pixi);
241
+ if (!pending) {
242
+ pending = (async () => {
243
+ const app = new pixi.Application();
244
+ await app.init({
245
+ autoStart: false,
246
+ sharedTicker: false,
247
+ backgroundAlpha: 0,
248
+ width: 128,
249
+ height: 96,
250
+ antialias: true,
251
+ });
252
+ previewApps.set(pixi, app);
253
+ return app;
254
+ })();
255
+ previewAppsReady.set(pixi, pending);
256
+ }
257
+ return pending;
258
+ }
259
+
260
+ /** Test and session teardown — every namespace's app. Idempotent. */
261
+ export function disposeInspectorPixiPreviewApp(): void {
262
+ previewAppsReady.clear();
263
+ const apps = [...previewApps.values()];
264
+ previewApps.clear();
265
+ for (const app of apps) {
266
+ app.destroy(true, { children: true, texture: false, textureSource: false });
267
+ }
268
+ }
269
+
270
+ function paddedAspectFrame(
271
+ pixi: CanvasPixiNamespace,
272
+ bounds: { x: number; y: number; width: number; height: number },
273
+ width: number,
274
+ height: number,
275
+ ): Rectangle {
276
+ const padding = 8;
277
+ const frame = new pixi.Rectangle(
278
+ bounds.x - padding,
279
+ bounds.y - padding,
280
+ Math.max(1, bounds.width + padding * 2),
281
+ Math.max(1, bounds.height + padding * 2),
282
+ );
283
+ const targetAspect = width / height;
284
+ if (frame.width / frame.height < targetAspect) {
285
+ const nextWidth = frame.height * targetAspect;
286
+ frame.x -= (nextWidth - frame.width) / 2;
287
+ frame.width = nextWidth;
288
+ } else if (frame.width / frame.height > targetAspect) {
289
+ const nextHeight = frame.width / targetAspect;
290
+ frame.y -= (nextHeight - frame.height) / 2;
291
+ frame.height = nextHeight;
292
+ }
293
+ return frame;
294
+ }
295
+
296
+ /**
297
+ * Photograph a live display subtree on an independent Application.
298
+ * The live scene renderer is never a render target.
299
+ *
300
+ * `null` — the answer `previewImage` already carries for "this surface has no
301
+ * picture of that node" — when the subtree is over
302
+ * {@link PREVIEW_SUBTREE_NODE_BUDGET}. Nothing is cloned and nothing is
303
+ * rendered in that case: the whole cost is the bounded count.
304
+ */
305
+ export async function capturePixiDisplayObjectThumbnail(
306
+ target: Container,
307
+ options: CanvasThumbnailSize & {
308
+ budget?: number;
309
+ /** THE namespace `target` came from. Absent ⇒ this bundle's own copy,
310
+ * which is right on every one-graph runtime. */
311
+ pixi?: CanvasPixiNamespace;
312
+ } = {},
313
+ ): Promise<string | null> {
314
+ const width = options.width ?? 128;
315
+ const height = options.height ?? 96;
316
+ const pixi = options.pixi ?? shellPixi;
317
+ const budget = options.budget ?? PREVIEW_SUBTREE_NODE_BUDGET;
318
+ // DECLINE BEFORE THE RENDERER. An over-budget subtree costs this call the
319
+ // bounded count and nothing else — no preview Application is resolved (the
320
+ // first selection would otherwise create a real WebGL renderer to draw a
321
+ // picture it is about to refuse), nothing is cloned, nothing is rasterized.
322
+ if (countPixiSubtree(target, budget) > budget) return null;
323
+ const app = await inspectorPixiPreviewApp(pixi);
324
+ // Counted a second time inside, and deliberately: the world keeps running
325
+ // across the `await` above, so the subtree that gets CLONED is not provably
326
+ // the one that was counted. The snapshot's own budget is what bounds the
327
+ // clone; the count above is what keeps the common refusal free.
328
+ const snapshot = snapshotPixiPreviewSubject(target, budget, pixi);
329
+ if (snapshot === null) return null;
330
+ app.stage.removeChildren();
331
+ app.stage.addChild(snapshot);
332
+ try {
333
+ const frame = paddedAspectFrame(pixi, snapshot.getLocalBounds(), width, height);
334
+ const source = (await app.renderer.extract.canvas({
335
+ target: app.stage,
336
+ frame,
337
+ })) as HTMLCanvasElement;
338
+ const output = document.createElement('canvas');
339
+ output.width = width;
340
+ output.height = height;
341
+ const context = output.getContext('2d');
342
+ if (!context)
343
+ throw new Error('Canvas selection preview could not create a 2D readback context.');
344
+ const scale = Math.min(width / source.width, height / source.height);
345
+ const drawWidth = source.width * scale;
346
+ const drawHeight = source.height * scale;
347
+ context.drawImage(
348
+ source,
349
+ (width - drawWidth) / 2,
350
+ (height - drawHeight) / 2,
351
+ drawWidth,
352
+ drawHeight,
353
+ );
354
+ return output.toDataURL('image/png');
355
+ } finally {
356
+ app.stage.removeChildren();
357
+ snapshot.destroy({ children: true, texture: false, textureSource: false });
358
+ }
359
+ }
360
+
361
+ /**
362
+ * A `canvasFrame` seam (`composite-screenshot.ts`) for Pixi-owned canvases.
363
+ *
364
+ * THE OTHER HALF of the blank capture, and the half waiting alone cannot fix.
365
+ * The compositor reads a canvas back LATE — after paint — and a WebGL context
366
+ * created without `preserveDrawingBuffer` has had its drawing buffer cleared by
367
+ * then, so `drawImage` of a perfectly-rendered Pixi canvas yields nothing. The
368
+ * story owns its own `<Application>` and does not pass that flag (nor should it
369
+ * have to), so the pixels are taken through Pixi's own extraction instead:
370
+ * `renderer.extract.canvas` renders the stage into a texture and reads it back,
371
+ * which is correct whatever the context was created with.
372
+ *
373
+ * Returns `null` for a canvas no collected Application owns — the seam's
374
+ * documented "read the canvas directly" answer, so an ordinary DOM story's
375
+ * canvas is untouched.
376
+ */
377
+ export function pixiCanvasFrame(
378
+ apps: readonly Application[],
379
+ ): (canvas: HTMLCanvasElement) => Promise<CanvasImageSource | null> {
380
+ return async (canvas) => {
381
+ const app = apps.find((candidate) => candidate.canvas === canvas);
382
+ if (!app?.renderer) return null;
383
+ // `frame` and `clearColor` are what make this a picture OF THE CANVAS
384
+ // rather than of the display tree: extract's own default frame is the
385
+ // target's content BOUNDS, so a 24-texel gem in a 240x180 cell came back
386
+ // as a 24x24 image that the compositor then stretched over the whole cell,
387
+ // and the story's own `<Application background>` — a renderer clear, not a
388
+ // display object — was missing behind it. The screen shows the app's
389
+ // screen rectangle cleared to its background; so does this.
390
+ const background = app.renderer.background;
391
+ return app.renderer.extract.canvas({
392
+ target: app.stage,
393
+ frame: app.screen,
394
+ ...(background.alpha > 0 ? { clearColor: background.color } : {}),
395
+ }) as unknown as CanvasImageSource;
396
+ };
397
+ }
398
+
399
+ /**
400
+ * The Applications a mounted editor SURFACE is presenting right now.
401
+ *
402
+ * The story sheet knows its own Applications because it created them inside
403
+ * one call ({@link withApplicationCollector}); a board does not — its exhibits
404
+ * outlive any single capture, and the capture is taken by a door
405
+ * (`capture-active-document`) that knows nothing about Pixi. So a surface that
406
+ * adopts mounted stories registers them here, and {@link presentedPixiFrame}
407
+ * is the `canvasFrame` the composite lane passes for EVERY document: it
408
+ * answers for a registered canvas and returns `null` — "read the canvas
409
+ * directly" — for every other. Without it a canvas board photographs blank,
410
+ * for exactly the reason {@link pixiCanvasFrame} records.
411
+ *
412
+ * OWNER: the presenting surface. The returned function is the ONE way to
413
+ * unregister, and it runs in that surface's own teardown.
414
+ */
415
+ const presentedApps = new Set<Application>();
416
+ const presentedCanvasFrames = new Map<HTMLCanvasElement, () => Promise<CanvasImageSource | null>>();
417
+
418
+ export function registerPresentedPixiApps(apps: readonly Application[]): () => void {
419
+ for (const app of apps) presentedApps.add(app);
420
+ return () => {
421
+ for (const app of apps) presentedApps.delete(app);
422
+ };
423
+ }
424
+
425
+ /** Register a surface whose presented pixels need preparation beyond Pixi's
426
+ * ordinary raw-stage extraction. The native canvas Scene uses this to render
427
+ * its independent editor camera immediately before capture; returning `null`
428
+ * then tells the compositor to read the already-presented canvas directly.
429
+ *
430
+ * Kept at the pixel boundary, not in the capture command: the presenting
431
+ * surface is the only owner that knows whether its view is a raw stage, a
432
+ * camera, a render texture, or some other native Pixi presentation. */
433
+ export function registerPresentedCanvasFrame(
434
+ canvas: HTMLCanvasElement,
435
+ frame: () => Promise<CanvasImageSource | null>,
436
+ ): () => void {
437
+ presentedCanvasFrames.set(canvas, frame);
438
+ return () => {
439
+ if (presentedCanvasFrames.get(canvas) === frame) presentedCanvasFrames.delete(canvas);
440
+ };
441
+ }
442
+
443
+ /** The `canvasFrame` seam over {@link registerPresentedPixiApps}' live set. */
444
+ export function presentedPixiFrame(canvas: HTMLCanvasElement): Promise<CanvasImageSource | null> {
445
+ const prepared = presentedCanvasFrames.get(canvas);
446
+ if (prepared) return prepared();
447
+ return pixiCanvasFrame([...presentedApps])(canvas);
448
+ }
449
+
450
+ /**
451
+ * Run `task` with every `Application` created during it collected.
452
+ *
453
+ * `__PIXI_APP_INIT__` is Pixi's own global init hook (`ApplicationInitHook`,
454
+ * the seam its devtools use), fired once per `Application.init()`. It is the
455
+ * only observation point keyed on the MOUNT itself: `@pixi/react` keys its
456
+ * reconciler roots off the canvas element in a map it does not export, and a
457
+ * story's `<Application>` is written by the story, so its `onInit` prop is not
458
+ * ours to pass. Any previously-installed hook (a devtools extension) is
459
+ * forwarded, and the previous value is restored when the window closes.
460
+ *
461
+ * Exported because the CAPTURE lane needs the same observation for a different
462
+ * reason — see {@link awaitPixiStoryFrame}.
463
+ */
464
+ export async function withApplicationCollector<T>(
465
+ task: (created: Application[]) => Promise<T>,
466
+ ): Promise<T> {
467
+ const created: Application[] = [];
468
+ const previous = globalThis.__PIXI_APP_INIT__;
469
+ globalThis.__PIXI_APP_INIT__ = (instance, version) => {
470
+ // The hook's declared parameter is `Application | Renderer`; only
471
+ // `ApplicationInitHook` calls THIS one, but narrow structurally rather than
472
+ // trust that, since a mis-typed entry would put a renderer on the board.
473
+ const candidate = instance as Partial<Application>;
474
+ if (candidate.stage && candidate.ticker) created.push(instance as Application);
475
+ previous?.(instance, version);
476
+ };
477
+ try {
478
+ return await task(created);
479
+ } finally {
480
+ globalThis.__PIXI_APP_INIT__ = previous;
481
+ }
482
+ }
@@ -28,7 +28,7 @@ import {
28
28
  toggleCameraCompositionGuides,
29
29
  toggleCameraPreviewPin,
30
30
  viewThroughCamera,
31
- } from '@volter/editor-core/camera-authoring';
31
+ } from '@volter/editor-threejs/kit/camera-authoring';
32
32
 
33
33
  export interface CameraAuthoringOverlayProps {
34
34
  readonly previewRef: RefObject<HTMLDivElement | null>;
@@ -1,7 +1,7 @@
1
1
  import { faVolumeHigh, faVolumeXmark } from '@fortawesome/free-solid-svg-icons';
2
2
  import { userLocalSection, writeUserLocalSection } from '@volter/editor-sdk/kit/user-local-state';
3
3
  import { AnchoredMenu, Button, EditorIcon, Inline, MenuItem } from '@volter/editor-sdk/widgets';
4
- import type { PerformanceSnapshot } from '@volter/game-runtime/dev/performance-profiler';
4
+ import type { PerformanceSnapshot } from '../../runtime/dev/performance-profiler';
5
5
  import type { AudioAdapter, AudioMeterFrame } from '@volter/editor-project/adapter';
6
6
  import { useEffect, useRef, useState, useSyncExternalStore } from 'react';
7
7
  import {
@@ -9,14 +9,14 @@ import {
9
9
  getAllActiveAudio,
10
10
  getInspectedAudio,
11
11
  subscribeActiveAudio,
12
- } from '@volter/editor-core/authoring/active-systems';
13
- import { useEditorStore } from '@volter/editor-core/editor-runtime';
12
+ } from '@volter/editor-sdk/kit/authoring/active-systems';
13
+ import { useEditorStore } from '@volter/editor-sdk/kit/editor-runtime';
14
14
  import { useActivePerformanceSource } from '../use-active-performance-source';
15
15
  import {
16
16
  activeChromeRegions,
17
17
  chromeRegionsKey,
18
18
  subscribeChromeRegions,
19
- } from '@volter/editor-core/workspace-regions';
19
+ } from '@volter/editor-sdk/kit/workspace-regions';
20
20
  import {
21
21
  AUDIO_METER_SEGMENTS,
22
22
  audioMeterSegmentCount,
@@ -6,8 +6,8 @@
6
6
 
7
7
  import { isolationImportUrl } from '@volter/editor-sdk/session/project-module-url';
8
8
  import { themeVars } from '@volter/editor-sdk/widgets';
9
- import { createPhysics2DRegistry } from '@volter/game-runtime/pixi/physics-registry';
10
- import { createPhysicsAdapter2D } from '@volter/game-runtime/pixi/system-adapters';
9
+ import { createPhysics2DRegistry } from '../../runtime/pixi/physics-registry';
10
+ import { createPhysicsAdapter2D } from '../../runtime/pixi/system-adapters';
11
11
  import { useEffect, useRef, useState } from 'react';
12
12
  import {
13
13
  type IsolatedPixiScreenCtor,
@@ -16,22 +16,22 @@ import {
16
16
  } from '../authoring/mount-isolated-pixi-screen';
17
17
  import { PixiAuthoringAdapter } from '../authoring/pixi-authoring-adapter';
18
18
  import { createCreationSiteCanvasWriteTarget } from '../authoring/pixi-creation-site-write-target';
19
- import { installCanvasSceneNavigation } from '@volter/editor-core/authoring/react-canvas-navigation';
19
+ import { installCanvasSceneNavigation } from '@volter/editor-sdk/kit/authoring/react-canvas-navigation';
20
20
  import { createRootViewController } from '@volter/editor-sdk/kit/world-pan-state';
21
- import { capturePixiDisplayObjectThumbnail } from '@volter/editor-core/canvas-preview-frames';
21
+ import { capturePixiDisplayObjectThumbnail } from '../canvas-preview-frames';
22
22
  import type { DocumentPreviewCaptureRequest } from '@volter/editor-sdk/kit/document-preview-source';
23
- import { useThreeEditorStore } from '@volter/editor-core/editor-runtime';
24
23
  import { authoringJournal } from '../history/json-history-resource';
25
24
  import { setActiveScope } from '@volter/editor-sdk/kit/hotkeys';
26
- import { getCurrentProject } from '@volter/editor-core/project-manager';
25
+ import { getCurrentProject } from '@volter/editor-sdk/kit/active-project';
27
26
  import { takeNamedExport } from '../take-named-export';
28
27
  import { registerWorkspaceDocumentSelection } from '@volter/editor-sdk/kit/workspace-document-registry';
29
28
  import {
30
29
  CANVAS_SCENE_BACKGROUND,
31
30
  CanvasSceneBackdrop,
32
31
  CanvasSceneControls,
33
- } from '@volter/editor-core/components/CanvasSceneViewport';
34
- import { RootSelectionOverlay } from '@volter/editor-core/components/RootSelectionOverlay';
32
+ } from '@volter/editor-sdk/kit/components/CanvasSceneViewport';
33
+ import { RootSelectionOverlay } from '@volter/editor-sdk/kit/components/RootSelectionOverlay';
34
+ import { useThreeEditorStore } from '@volter/editor-threejs/kit/three-state';
35
35
 
36
36
  export interface PixiIsolationSceneContentProps {
37
37
  readonly active: boolean;
@@ -93,7 +93,7 @@ export function PixiIsolationSceneContent({
93
93
  if (!adapter) return;
94
94
  return registerWorkspaceDocumentSelection(documentId, () => ({
95
95
  adapter,
96
- nodeId: [...store.selectedEntityIds].find((id) => adapter.hierarchy.node(id)) ?? null,
96
+ nodeId: [...store.shell.selectedEntityIds].find((id) => adapter.hierarchy.node(id)) ?? null,
97
97
  }));
98
98
  }, [adapter, documentId, store]);
99
99
 
@@ -133,7 +133,7 @@ export function PixiIsolationSceneContent({
133
133
  journal: authoringJournal(`isolation/${documentId.slice('scene:'.length)}`),
134
134
  target: createCreationSiteCanvasWriteTarget({
135
135
  physics: createPhysicsAdapter2D(createPhysics2DRegistry()),
136
- history: store.projectHistory,
136
+ history: store.shell.projectHistory,
137
137
  }),
138
138
  surface: () => mounted.canvas,
139
139
  capturePreview: (object, size) =>
@@ -29,7 +29,7 @@ import { themeVars } from '@volter/editor-sdk/widgets';
29
29
  import { useEffect, useRef, useState } from 'react';
30
30
  import type { DocumentPreviewCaptureRequest } from '@volter/editor-sdk/kit/document-preview-source';
31
31
  import { captureAuthoredThreeScenePreview } from '../document-preview-three';
32
- import { getCurrentProject } from '@volter/editor-core/project-manager';
32
+ import { getCurrentProject } from '@volter/editor-sdk/kit/active-project';
33
33
  import {
34
34
  type MountedStoryViewportSource,
35
35
  mountedStoryViewportSource,
@@ -38,10 +38,10 @@ import {
38
38
  disposeStoryObject3D,
39
39
  mountStoryObject3D,
40
40
  type StoryPreviewComponent,
41
- } from '@volter/editor-core/stories/story-three-preview';
41
+ } from '../stories/story-three-preview';
42
42
  import { takeNamedExport } from '../take-named-export';
43
43
  import type { WorkspaceDocumentContentProps } from '@volter/editor-sdk/kit/workspace-document-registry';
44
- import { Object3DDocumentViewport } from '@volter/editor-core/components/Object3DDocumentViewport';
44
+ import { Object3DDocumentViewport } from '@volter/editor-threejs/kit/components/Object3DDocumentViewport';
45
45
 
46
46
  export interface ThreeIsolationSceneState {
47
47
  /** Project-relative module the composition lives in. */
@@ -16,11 +16,11 @@
16
16
  * and the plain capture/frame shapes — never the WebGL2 instrument itself.
17
17
  */
18
18
 
19
- import type { PerformanceFrame } from '@volter/game-runtime/dev/performance-profiler';
19
+ import type { PerformanceFrame } from '../../runtime/dev/performance-profiler';
20
20
  import type {
21
21
  FrameCapture,
22
22
  FrameCaptureDrawCall,
23
- } from '@volter/game-runtime/dev/webgl-frame-capture';
23
+ } from '../../runtime/dev/webgl-frame-capture';
24
24
  import type { RenderDebugAdapter } from '@volter/editor-project/adapter';
25
25
 
26
26
  /** Which optional render-debug capabilities the active adapter provides — the
@@ -1,5 +1,5 @@
1
- import { meterPercent } from '@volter/game-runtime/adapter/audio-meter';
2
- import type { PerformanceFrame } from '@volter/game-runtime/dev/performance-profiler';
1
+ import { meterPercent } from '../../runtime/adapter/audio-meter';
2
+ import type { PerformanceFrame } from '../../runtime/dev/performance-profiler';
3
3
 
4
4
  export const AUDIO_METER_SEGMENTS = 8;
5
5
  export const PERFORMANCE_SPARKLINE_SAMPLES = 24;