@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.
Files changed (302) hide show
  1. package/LICENSE +661 -0
  2. package/NOTICE +23 -0
  3. package/contributions/asset-budget-asset.menu.ts +26 -0
  4. package/contributions/asset-budget.action.ts +18 -0
  5. package/contributions/asset-budget.document.tsx +21 -0
  6. package/contributions/asset-budget.menu.ts +22 -0
  7. package/contributions/audio-unlock.service.ts +17 -0
  8. package/contributions/audio.utility.tsx +15 -0
  9. package/contributions/autoplay.service.ts +48 -0
  10. package/contributions/bridge.command.ts +172 -0
  11. package/contributions/build-profiles.document.tsx +21 -0
  12. package/contributions/build-progress.status.tsx +45 -0
  13. package/contributions/build.action.ts +26 -0
  14. package/contributions/build.command.ts +27 -0
  15. package/contributions/build.header.tsx +37 -0
  16. package/contributions/build.menu.ts +31 -0
  17. package/contributions/build.service.ts +31 -0
  18. package/contributions/connection.status.tsx +43 -0
  19. package/contributions/coverage.service.ts +118 -0
  20. package/contributions/edit-mode-audio.service.ts +23 -0
  21. package/contributions/edit-mode-networking.service.ts +30 -0
  22. package/contributions/game-document.service.ts +25 -0
  23. package/contributions/game-eval.command.ts +210 -0
  24. package/contributions/game.layout.ts +19 -0
  25. package/contributions/gameplay.command.ts +255 -0
  26. package/contributions/generation.service.ts +96 -0
  27. package/contributions/generations.status.tsx +53 -0
  28. package/contributions/ingest.service.ts +80 -0
  29. package/contributions/instances.command.ts +75 -0
  30. package/contributions/navmesh.menu.ts +39 -0
  31. package/contributions/navmesh.service.ts +19 -0
  32. package/contributions/network.utility.tsx +17 -0
  33. package/contributions/play.command.ts +355 -0
  34. package/contributions/profiler.action.ts +16 -0
  35. package/contributions/profiler.menu.ts +22 -0
  36. package/contributions/profiler.utility.tsx +17 -0
  37. package/contributions/react/component-board.service.ts +21 -0
  38. package/contributions/react/design-time-mount.service.ts +60 -0
  39. package/contributions/react/pasteboard.action.ts +41 -0
  40. package/contributions/react/react-inspector.service.ts +85 -0
  41. package/contributions/react/story-documents.service.ts +44 -0
  42. package/contributions/scene-document.service.ts +32 -0
  43. package/contributions/state-watch.action.ts +17 -0
  44. package/contributions/state-watch.menu.ts +23 -0
  45. package/contributions/state-watch.utility.tsx +20 -0
  46. package/contributions/team-playtest.service.ts +124 -0
  47. package/contributions/three/camera-runtime.inspector.tsx +34 -0
  48. package/contributions/three/component-board.service.ts +24 -0
  49. package/contributions/three/component-verbs.command.ts +110 -0
  50. package/contributions/three/component-verbs.service.ts +92 -0
  51. package/contributions/three/constraints.inspector.tsx +34 -0
  52. package/contributions/three/model-asset-sections.service.ts +26 -0
  53. package/contributions/three/reflection-probe-capture.inspector.tsx +32 -0
  54. package/contributions/three/story-documents.service.ts +31 -0
  55. package/contributions/three/three-authoring.service.ts +66 -0
  56. package/contributions/transport.header.tsx +19 -0
  57. package/contributions/xstate-behavior.action.ts +42 -0
  58. package/contributions/xstate-behavior.document.tsx +73 -0
  59. package/contributions/xstate-behavior.inspector.tsx +28 -0
  60. package/contributions/xstate-behavior.menu.ts +23 -0
  61. package/package.json +144 -0
  62. package/src/asset-budget/AssetBudgetPanel.tsx +1172 -0
  63. package/src/asset-budget/asset-budget-model.ts +799 -0
  64. package/src/asset-budget/basis-encoder.ts +165 -0
  65. package/src/asset-budget/gltf-io.ts +154 -0
  66. package/src/asset-budget/gltf-optimize.ts +384 -0
  67. package/src/asset-budget/image-dims.ts +78 -0
  68. package/src/asset-budget/optimize-apply.ts +221 -0
  69. package/src/audio/AudioDebuggerPanel.tsx +457 -0
  70. package/src/audio/audio-debugger-model.ts +87 -0
  71. package/src/bridge/call.ts +60 -0
  72. package/src/bridge/dispatch.ts +459 -0
  73. package/src/bridge/live-frames.ts +25 -0
  74. package/src/bridge/screenshot.ts +316 -0
  75. package/src/build/BuildProfilesPanel.tsx +476 -0
  76. package/src/build/build-session.ts +247 -0
  77. package/src/build/format-bytes.ts +14 -0
  78. package/src/command-results.ts +36 -0
  79. package/src/coverage/live-authoring-surface.ts +30 -0
  80. package/src/coverage/live-project-verbs.ts +162 -0
  81. package/src/coverage/native-system-coverage.ts +143 -0
  82. package/src/coverage/root-coverage.ts +79 -0
  83. package/src/coverage/session-coverage.ts +193 -0
  84. package/src/design-system-stories/ApplicationChrome.stories.tsx +100 -0
  85. package/src/design-system-stories/InspectorNarrowBodies.stories.tsx +406 -0
  86. package/src/edit-mode/edit-mode-audio.ts +56 -0
  87. package/src/edit-mode/edit-mode-networking.ts +112 -0
  88. package/src/game-document/DevicePresetPicker.tsx +84 -0
  89. package/src/game-document/GameCaptureFrameButton.tsx +45 -0
  90. package/src/game-document/GameDocument.tsx +215 -0
  91. package/src/game-document/GamePanel.tsx +662 -0
  92. package/src/game-document/InstanceInspectorPicker.tsx +183 -0
  93. package/src/game-document/crowd-debug.ts +183 -0
  94. package/src/game-document/device-preview.ts +336 -0
  95. package/src/game-document/game-view-store.ts +107 -0
  96. package/src/game-document/physics-debug.ts +187 -0
  97. package/src/generation/GenerationActivity.tsx +421 -0
  98. package/src/generation/GenerationGallery.css +231 -0
  99. package/src/generation/generation-documents.tsx +338 -0
  100. package/src/generation/generation-jobs.ts +128 -0
  101. package/src/generation/generation-presentation.ts +257 -0
  102. package/src/host/adapter-reach.ts +445 -0
  103. package/src/host/adapter-runtime-bindings.ts +303 -0
  104. package/src/host/after-paint.ts +112 -0
  105. package/src/host/api/configurations.ts +66 -0
  106. package/src/host/authoring/babylon-authoring-adapter.ts +703 -0
  107. package/src/host/authoring/canvas-runtime-recognition.ts +50 -0
  108. package/src/host/authoring/contract-hierarchy-authoring.ts +144 -0
  109. package/src/host/authoring/contract-scenes-stories.ts +204 -0
  110. package/src/host/authoring/creation-site-related.ts +55 -0
  111. package/src/host/authoring/ephemeral-persistence.ts +29 -0
  112. package/src/host/authoring/gesture-persist.ts +84 -0
  113. package/src/host/authoring/ingest-data-writer.ts +232 -0
  114. package/src/host/authoring/ingest-source-persistence.ts +826 -0
  115. package/src/host/authoring/mount-isolated-pixi-screen.ts +250 -0
  116. package/src/host/authoring/mounted-authoring.ts +41 -0
  117. package/src/host/authoring/owned-pixi-ticker-listeners.ts +96 -0
  118. package/src/host/authoring/phaser-live-authoring-adapter.ts +265 -0
  119. package/src/host/authoring/pixi-authoring-adapter.ts +1554 -0
  120. package/src/host/authoring/pixi-creation-site-write-target.ts +60 -0
  121. package/src/host/authoring/pixi-isolation-assets.ts +25 -0
  122. package/src/host/authoring/pixi-live-write-target.ts +979 -0
  123. package/src/host/authoring/pixi-source-identity.ts +141 -0
  124. package/src/host/authoring/pixi-still-presentation.ts +71 -0
  125. package/src/host/authoring/pixi-structure-history.ts +237 -0
  126. package/src/host/authoring/pixi-transform-channels.ts +205 -0
  127. package/src/host/authoring/selection-remount-handoff.ts +23 -0
  128. package/src/host/authoring/source-persistence-backend.ts +373 -0
  129. package/src/host/authoring/source-refresh-revisions.ts +81 -0
  130. package/src/host/authoring/struct-write-pipe.ts +143 -0
  131. package/src/host/auto-frame-window.ts +89 -0
  132. package/src/host/binding-resolver.ts +393 -0
  133. package/src/host/browser-transpile.ts +631 -0
  134. package/src/host/canvas-entry-runtime.ts +95 -0
  135. package/src/host/components/CameraAuthoringOverlay.tsx +216 -0
  136. package/src/host/components/HeaderTelemetry.tsx +341 -0
  137. package/src/host/components/PixiIsolationSceneContent.tsx +280 -0
  138. package/src/host/components/ResolutionPicker.tsx +89 -0
  139. package/src/host/components/ThreeIsolationSceneContent.tsx +180 -0
  140. package/src/host/components/frame-debugger-model.ts +579 -0
  141. package/src/host/components/header-telemetry-model.ts +74 -0
  142. package/src/host/components/scene-document.tsx +447 -0
  143. package/src/host/components/utility-view-state.ts +87 -0
  144. package/src/host/components/world-root-stage-binding.tsx +133 -0
  145. package/src/host/components/world-root-stage.ts +1046 -0
  146. package/src/host/coverage/authoring-read-probe.ts +583 -0
  147. package/src/host/coverage/capability-coverage.ts +1587 -0
  148. package/src/host/coverage/coverage-accounting.ts +261 -0
  149. package/src/host/coverage/game-contract-seam-evidence.ts +82 -0
  150. package/src/host/coverage/project-verb-coverage.ts +148 -0
  151. package/src/host/coverage/system-adapter-coverage.ts +373 -0
  152. package/src/host/design-system-stories/StoryLayout.tsx +104 -0
  153. package/src/host/design-system-stories/fixtures/authoring.ts +247 -0
  154. package/src/host/design-system-stories/fixtures/editor-runtime.tsx +115 -0
  155. package/src/host/document-preview-three.ts +110 -0
  156. package/src/host/entry-adjudication.ts +89 -0
  157. package/src/host/game-location-guard.ts +150 -0
  158. package/src/host/game-module-access.ts +196 -0
  159. package/src/host/game-realm-page.ts +358 -0
  160. package/src/host/game-realm-reclaim.ts +55 -0
  161. package/src/host/game-realm-storage.ts +104 -0
  162. package/src/host/gameplay-export.ts +288 -0
  163. package/src/host/gameplay-recording.ts +717 -0
  164. package/src/host/gated-globals.ts +1511 -0
  165. package/src/host/history/json-history-resource.ts +254 -0
  166. package/src/host/ingest/registry.ts +261 -0
  167. package/src/host/instance-extract-actions.ts +120 -0
  168. package/src/host/instance-fork-actions.ts +120 -0
  169. package/src/host/play-control-hook.ts +26 -0
  170. package/src/host/playwright-shim.ts +479 -0
  171. package/src/host/projection/dom.ts +295 -0
  172. package/src/host/projection/pixi.ts +288 -0
  173. package/src/host/r3f-entry-runtime.ts +77 -0
  174. package/src/host/react-mount-runtime.ts +162 -0
  175. package/src/host/realm-services.ts +148 -0
  176. package/src/host/recording-preview.ts +106 -0
  177. package/src/host/roots/module-root.ts +203 -0
  178. package/src/host/roots/react-root.ts +181 -0
  179. package/src/host/same-realm-loop-gate.ts +544 -0
  180. package/src/host/scene-view-drawability.ts +69 -0
  181. package/src/host/sdk/tools.ts +31 -0
  182. package/src/host/served-bundle-runtime-modules.ts +331 -0
  183. package/src/host/server-log-bridge.ts +60 -0
  184. package/src/host/staged-projects.ts +24 -0
  185. package/src/host/stories/mounted-story-viewport-source.ts +64 -0
  186. package/src/host/stories/story-arg-descriptors.ts +50 -0
  187. package/src/host/stories/story-media-presence.ts +152 -0
  188. package/src/host/surface-content.ts +87 -0
  189. package/src/host/take-named-export.ts +23 -0
  190. package/src/host/three-ingest-runtime.ts +76 -0
  191. package/src/host/types-fastnoise-lite.d.ts +7 -0
  192. package/src/host/types-mikktspace.d.ts +20 -0
  193. package/src/host/types-troika-three-text.d.ts +7 -0
  194. package/src/host/use-active-performance-source.ts +53 -0
  195. package/src/host/viewport-pose-memory.ts +48 -0
  196. package/src/host/viewport-root-presentation.ts +40 -0
  197. package/src/ingest/active-ingest.ts +251 -0
  198. package/src/ingest/active-scene-navigation.ts +43 -0
  199. package/src/ingest/authoring/ingest-dom-surface-authoring.ts +191 -0
  200. package/src/ingest/authoring/ingest-root-adapter.ts +897 -0
  201. package/src/ingest/capture-wait-report.ts +122 -0
  202. package/src/ingest/deferred-ingest-play.ts +216 -0
  203. package/src/ingest/deferred-ingest-session.ts +23 -0
  204. package/src/ingest/discovery-public-ingest.ts +242 -0
  205. package/src/ingest/dom-stub-mark.ts +7 -0
  206. package/src/ingest/entry-load.ts +56 -0
  207. package/src/ingest/game-contract-realm.ts +34 -0
  208. package/src/ingest/game-pointer-lock.ts +89 -0
  209. package/src/ingest/held-scene-repaint.ts +73 -0
  210. package/src/ingest/host-surface-box.ts +174 -0
  211. package/src/ingest/ingest-boot-viewport.ts +54 -0
  212. package/src/ingest/ingest-canvas-scene-document.tsx +177 -0
  213. package/src/ingest/ingest-canvas-scene.ts +50 -0
  214. package/src/ingest/ingest-evidence-hook.ts +131 -0
  215. package/src/ingest/ingest-frame-snapshot.ts +183 -0
  216. package/src/ingest/ingest-play-commands.ts +134 -0
  217. package/src/ingest/ingest-play-control.ts +299 -0
  218. package/src/ingest/ingest-render-debug.ts +320 -0
  219. package/src/ingest/ingest-siblings.ts +487 -0
  220. package/src/ingest/ingest-status.ts +62 -0
  221. package/src/ingest/live-ingest-facet.ts +46 -0
  222. package/src/ingest/module-mode.ts +229 -0
  223. package/src/ingest/mount-canvas-ingest-root.ts +899 -0
  224. package/src/ingest/mount-coverage.ts +296 -0
  225. package/src/ingest/mount-dom-ingest-root.ts +282 -0
  226. package/src/ingest/mount-ingest-root.ts +744 -0
  227. package/src/ingest/mount-three-ingest-root.ts +366 -0
  228. package/src/ingest/resolve-canvas.ts +47 -0
  229. package/src/ingest/resolve-three.ts +123 -0
  230. package/src/ingest/served-bundle.ts +109 -0
  231. package/src/ingest/served-html-boot.ts +292 -0
  232. package/src/ingest/surface-canvas.ts +84 -0
  233. package/src/ingest/surface-dom.ts +84 -0
  234. package/src/ingest/surface-three.ts +128 -0
  235. package/src/ingest/types.ts +76 -0
  236. package/src/ingest/unmount-ingest-root.ts +224 -0
  237. package/src/navmesh/navmesh-actions.ts +27 -0
  238. package/src/navmesh/navmesh-handler.ts +237 -0
  239. package/src/navmesh/navmesh-workflow-store.ts +78 -0
  240. package/src/network/NetworkInspectorPanel.tsx +644 -0
  241. package/src/network/network-inspector-model.ts +225 -0
  242. package/src/play/play-boot-stall.ts +118 -0
  243. package/src/play/play-log-events.ts +26 -0
  244. package/src/play/play-mode.ts +2553 -0
  245. package/src/play/play-recording.ts +335 -0
  246. package/src/play/react-play-live-authoring.ts +165 -0
  247. package/src/play-bar/PlayBar.tsx +488 -0
  248. package/src/play-bar/PlayerCountPicker.tsx +100 -0
  249. package/src/profiler/FrameDebuggerPanel.tsx +458 -0
  250. package/src/profiler/PerformancePanel.tsx +1068 -0
  251. package/src/profiler/ProfilerPanel.tsx +53 -0
  252. package/src/profiler/frame-debugger-store.ts +97 -0
  253. package/src/profiler/main-thread-busy.ts +90 -0
  254. package/src/react/design-time-react-mount.ts +782 -0
  255. package/src/react/dom-authoring-adapter.ts +764 -0
  256. package/src/react/pasteboard-materialize.ts +154 -0
  257. package/src/react/react-inspector-section.tsx +2496 -0
  258. package/src/react/react-world-authoring-adapter.ts +3795 -0
  259. package/src/react/story-documents/story-args-section.ts +19 -0
  260. package/src/react/story-documents/story-documents.tsx +1380 -0
  261. package/src/react/story-paint-bounds.ts +94 -0
  262. package/src/react/ui-board-document.tsx +101 -0
  263. package/src/react/ui-board-title.ts +9 -0
  264. package/src/react/ui-component-board.ts +72 -0
  265. package/src/services/audio-pose-guard.ts +81 -0
  266. package/src/services/game-audio-unlock.ts +48 -0
  267. package/src/state-watch/StateWatchPanel.tsx +535 -0
  268. package/src/three/authoring/camera-runtime-inspector-section.tsx +135 -0
  269. package/src/three/authoring/constraint-inspector-section.tsx +189 -0
  270. package/src/three/authoring/design-time-renderer.ts +189 -0
  271. package/src/three/authoring/model-asset-inspector-section.css +41 -0
  272. package/src/three/authoring/model-asset-inspector-section.tsx +869 -0
  273. package/src/three/authoring/oid-source-persistence.ts +557 -0
  274. package/src/three/authoring/r3f-design-session.ts +1174 -0
  275. package/src/three/authoring/r3f-source-authoring-adapter.ts +5949 -0
  276. package/src/three/authoring/reflection-probe-inspector-section.tsx +76 -0
  277. package/src/three/authoring/spatial-audio-handles.ts +215 -0
  278. package/src/three/authoring/spatial-collider-handles.ts +229 -0
  279. package/src/three/authoring/spatial-joint-handles.ts +114 -0
  280. package/src/three/authoring/spatial-light-handles.ts +178 -0
  281. package/src/three/authoring/spatial-lod-handles.ts +93 -0
  282. package/src/three/authoring/spatial-particle-handles.ts +368 -0
  283. package/src/three/authoring/three-authoring-adapter.ts +1953 -0
  284. package/src/three/authoring/three-scene-identity.ts +19 -0
  285. package/src/three/authoring/three-spatial-handles.ts +161 -0
  286. package/src/three/authoring/typed-three-inspector.ts +528 -0
  287. package/src/three/component-verbs/extract-menu.ts +71 -0
  288. package/src/three/component-verbs/fork-menu.ts +78 -0
  289. package/src/three/component-verbs/internals-menu.ts +91 -0
  290. package/src/three/story-documents/three-story-documents.tsx +607 -0
  291. package/src/three/three-board/ThreeBoardDocument.tsx +888 -0
  292. package/src/three/three-board/board-framing.ts +412 -0
  293. package/src/three/three-board/board-layout.ts +401 -0
  294. package/src/three/three-board/board-scene.ts +901 -0
  295. package/src/three/three-board/three-component-board.ts +62 -0
  296. package/src/xstate/XStateBehaviorSection.tsx +130 -0
  297. package/src/xstate/XStateMachineInspector.tsx +566 -0
  298. package/src/xstate/character-animation-machine.fixture.ts +74 -0
  299. package/src/xstate/live-behaviors.ts +106 -0
  300. package/src/xstate/use-live-actor-state.ts +44 -0
  301. package/src/xstate/xstate-graph.ts +235 -0
  302. package/src/xstate/xstate-layout.ts +76 -0
@@ -0,0 +1,250 @@
1
+ /**
2
+ * DESIGN-TIME isolation of a Pixi screen CLASS — Edit mounts the piece
3
+ * on the same Scene camera first-party canvas Edit uses
4
+ * (`canvas-design-mount.ts`).
5
+ *
6
+ * The construct keeps its authored stage size. The viewport is a presentation
7
+ * surface: the editor camera frames it, wheel zooms, and the grid shows
8
+ * around it. The pane is never the game's output rectangle — that is how a
9
+ * letterboxed 1280×720 screen made zoom a no-op.
10
+ *
11
+ * `new Ctor()`, settle at rest. No ticker, no `show()` (GameScreen.show
12
+ * starts the match). The live ingest host is not used.
13
+ */
14
+
15
+ import type { Application, Container } from 'pixi.js';
16
+ import { type CanvasPixiNamespace, resolveCanvasPixiForEditor } from '../canvas-entry-runtime';
17
+ import { createWithOwnedPixiTickerListeners } from './owned-pixi-ticker-listeners';
18
+ import { initializePixiIsolationAssets } from './pixi-isolation-assets';
19
+ import { fitSceneView, registerStillFramePresenter } from './pixi-still-presentation';
20
+ import type { RootViewController } from '@volter/editor-core/authoring/world-pan-state';
21
+
22
+ export type IsolatedPixiScreenCtor = (new () => IsolatedPixiScreen) & {
23
+ readonly assetBundles?: readonly string[];
24
+ };
25
+
26
+ export type IsolatedPixiScreen = Container & {
27
+ prepare?(data: unknown): void;
28
+ resize?(width: number, height: number): void;
29
+ };
30
+
31
+ const EMPTY_RESULT = { score: 0, popped: 0, combo: 0, powerups: 0, highscore: 0 };
32
+
33
+ type PoseNode = {
34
+ x: number;
35
+ y: number;
36
+ alpha: number;
37
+ readonly children?: readonly PoseNode[];
38
+ };
39
+
40
+ /**
41
+ * Isolation shows the CONSTRUCT at rest, not the intro tween and not the
42
+ * running match.
43
+ *
44
+ * `prepare` is the game's pre-show hook: Result fills labels; Title parks
45
+ * intro groups off-screen pending `show()`. Snapshot the constructed pose,
46
+ * let prepare write data, then restore pose and keep alpha 1. Never call
47
+ * `show()` — that starts the match / starts tweens.
48
+ */
49
+ export function settleIsolatedScreen(screen: IsolatedPixiScreen): void {
50
+ const poses = snapshotPoses(screen as PoseNode);
51
+ screen.prepare?.(EMPTY_RESULT);
52
+ restorePoses(screen as PoseNode, poses);
53
+ screen.alpha = 1;
54
+ }
55
+
56
+ function snapshotPoses(node: PoseNode): Map<PoseNode, { x: number; y: number; alpha: number }> {
57
+ const out = new Map<PoseNode, { x: number; y: number; alpha: number }>();
58
+ const walk = (current: PoseNode) => {
59
+ out.set(current, { x: current.x, y: current.y, alpha: current.alpha });
60
+ for (const child of current.children ?? []) walk(child);
61
+ };
62
+ walk(node);
63
+ return out;
64
+ }
65
+
66
+ function restorePoses(
67
+ node: PoseNode,
68
+ poses: Map<PoseNode, { x: number; y: number; alpha: number }>,
69
+ ): void {
70
+ const pose = poses.get(node);
71
+ if (pose) {
72
+ node.x = pose.x;
73
+ node.y = pose.y;
74
+ node.alpha = pose.alpha;
75
+ }
76
+ for (const child of node.children ?? []) restorePoses(child, poses);
77
+ }
78
+
79
+ /**
80
+ * Authored stage size — the construct's own units, never the pane.
81
+ *
82
+ * Height is the Bubbo HUD's design content height (925). Isolation used to
83
+ * resize to 720, so `windowHeight - content.height` went negative, the
84
+ * thin-screen Title sat above the stage, and opening-fit of `getBounds()`
85
+ * zoomed the camera out around that off-screen wordmark.
86
+ */
87
+ export const ISOLATED_PIXI_STAGE = { width: 1280, height: 925 } as const;
88
+
89
+ /** The Scene camera frames this rectangle, not the AABB of every child. */
90
+ export function isolatedStageBounds(): {
91
+ x: number;
92
+ y: number;
93
+ width: number;
94
+ height: number;
95
+ } {
96
+ return { x: 0, y: 0, width: ISOLATED_PIXI_STAGE.width, height: ISOLATED_PIXI_STAGE.height };
97
+ }
98
+
99
+ export interface MountedIsolatedPixiScreen {
100
+ readonly canvas: HTMLCanvasElement;
101
+ readonly app: Application;
102
+ readonly screen: IsolatedPixiScreen;
103
+ /** The namespace this screen was constructed and rendered in — the caller's
104
+ * authoring adapter and preview take the SAME one. */
105
+ readonly pixi: CanvasPixiNamespace;
106
+ dispose(): void;
107
+ }
108
+
109
+ export interface MountIsolatedPixiScreenOptions {
110
+ readonly initAssets?: () => Promise<void>;
111
+ /** Project-relative identity of the module behind `initAssets`. */
112
+ readonly assetSourcePath?: string;
113
+ /** Adapter-declared prerequisite for constructing this isolated piece. */
114
+ readonly isolationSetup?: () => void | Promise<void>;
115
+ /** The document's independent Scene camera. Required: isolation is a
116
+ * Scene-camera piece, not a letterboxed output. */
117
+ readonly view: RootViewController;
118
+ /** Already-mounted document pane the viewport canvas draws into. */
119
+ readonly layer: HTMLElement;
120
+ }
121
+
122
+ /** Frame authored bounds in the Scene camera — the shared design-time fit
123
+ * (`pixi-still-presentation.ts`), re-exported under this mount's own name for
124
+ * its existing callers. */
125
+ export const fitIsolatedSceneView = fitSceneView;
126
+
127
+ export async function mountIsolatedPixiScreen(
128
+ Ctor: IsolatedPixiScreenCtor,
129
+ options: MountIsolatedPixiScreenOptions,
130
+ ): Promise<MountedIsolatedPixiScreen> {
131
+ const { view, layer } = options;
132
+ const stage = ISOLATED_PIXI_STAGE;
133
+ // THE namespace: `Ctor` is one of the GAME's own classes, loaded through the
134
+ // project graph, so its `pixi.js` is the project's under the packaged
135
+ // runtime. Everything this mount builds around it — the Application that
136
+ // draws it, the `Assets` bundles it needs, the `Ticker.shared` whose
137
+ // constructor-time registrations are withheld, the presentation Matrix —
138
+ // has to be that same one. See `../../vite-plugin-module-doorways.ts`.
139
+ const pixi = await resolveCanvasPixiForEditor();
140
+ const app = new pixi.Application();
141
+ await app.init({
142
+ autoStart: false,
143
+ sharedTicker: false,
144
+ backgroundAlpha: 0,
145
+ width: stage.width,
146
+ height: stage.height,
147
+ antialias: true,
148
+ preserveDrawingBuffer: true,
149
+ });
150
+ const canvas = app.canvas as HTMLCanvasElement;
151
+ canvas.dataset['vgaiCanvasSceneSurface'] = 'true';
152
+ canvas.style.position = 'absolute';
153
+ canvas.style.inset = '0';
154
+ canvas.style.width = '100%';
155
+ canvas.style.height = '100%';
156
+ canvas.style.display = 'block';
157
+ layer.appendChild(canvas);
158
+
159
+ if (options.initAssets) {
160
+ await initializePixiIsolationAssets(
161
+ options.assetSourcePath ?? options.initAssets.name,
162
+ options.initAssets,
163
+ );
164
+ }
165
+ if (Ctor.assetBundles && Ctor.assetBundles.length > 0) {
166
+ await pixi.Assets.loadBundle([...Ctor.assetBundles]);
167
+ }
168
+ await options.isolationSetup?.();
169
+ const ownedScreen = createWithOwnedPixiTickerListeners(() => {
170
+ const isolated = new Ctor();
171
+ // `prepare` is part of constructing the at-rest authoring document and may
172
+ // schedule delayed GSAP work (Bubbo's Porthole does). Keep it inside the
173
+ // owned-clock window so teardown can kill that work before Play begins.
174
+ settleIsolatedScreen(isolated);
175
+ return isolated;
176
+ }, pixi);
177
+ const screen = ownedScreen.value;
178
+ app.stage.addChild(screen);
179
+ // World size once. The pane is the camera, not a second resolution.
180
+ screen.resize?.(stage.width, stage.height);
181
+
182
+ const resizeRenderer = (): void => {
183
+ const rect = layer.getBoundingClientRect();
184
+ if (rect.width < 2 || rect.height < 2) return;
185
+ const renderer = app.renderer;
186
+ if (renderer.screen.width !== rect.width || renderer.screen.height !== rect.height) {
187
+ renderer.resize(rect.width, rect.height);
188
+ }
189
+ };
190
+
191
+ const renderScene = (): boolean => {
192
+ const renderer = app.renderer;
193
+ if (!renderer) return false;
194
+ resizeRenderer();
195
+ const pose = view.get();
196
+ renderer.render({
197
+ container: app.stage,
198
+ transform: new pixi.Matrix(pose.zoom, 0, 0, pose.zoom, pose.x, pose.y),
199
+ });
200
+ return true;
201
+ };
202
+
203
+ const unregisterPresented = registerStillFramePresenter(canvas, renderScene);
204
+
205
+ let frame = 0;
206
+ let disposed = false;
207
+ const draw = (): void => {
208
+ frame = requestAnimationFrame(draw);
209
+ if (disposed) return;
210
+ renderScene();
211
+ };
212
+ frame = requestAnimationFrame(draw);
213
+
214
+ const observer = new ResizeObserver(() => {
215
+ if (disposed) return;
216
+ renderScene();
217
+ });
218
+ observer.observe(layer);
219
+
220
+ let openingFit = 0;
221
+ let openingAttempts = 0;
222
+ let openingCancelled = false;
223
+ const stopOpeningOnNavigation = view.subscribe(() => {
224
+ openingCancelled = true;
225
+ });
226
+ const tryOpeningFit = (): void => {
227
+ if (openingCancelled || disposed || openingAttempts++ > 180) return;
228
+ if (fitIsolatedSceneView(layer, isolatedStageBounds(), view)) return;
229
+ openingFit = requestAnimationFrame(tryOpeningFit);
230
+ };
231
+ openingFit = requestAnimationFrame(tryOpeningFit);
232
+
233
+ return {
234
+ canvas,
235
+ app,
236
+ screen,
237
+ pixi,
238
+ dispose() {
239
+ disposed = true;
240
+ cancelAnimationFrame(frame);
241
+ cancelAnimationFrame(openingFit);
242
+ stopOpeningOnNavigation();
243
+ observer.disconnect();
244
+ unregisterPresented();
245
+ ownedScreen.dispose();
246
+ app.destroy(true, { children: true });
247
+ canvas.remove();
248
+ },
249
+ };
250
+ }
@@ -0,0 +1,41 @@
1
+ import type { AuthoringAdapter } from '@volter/editor-project/adapter';
2
+ import type * as THREE from 'three';
3
+ import type { EditorShellStore } from '@volter/editor-core/editor-shell-store';
4
+ import { stampEntityId } from '@volter/editor-core/entity-object';
5
+
6
+ /**
7
+ * Selection is editor-global UI state. A mounted adapter may deliberately own
8
+ * selection; when it does not, connect its semantic ids to the shared editor
9
+ * selection rather than requiring project code to know about EditorShellStore.
10
+ */
11
+ export function withStoreSelection(
12
+ authoring: AuthoringAdapter,
13
+ store: EditorShellStore,
14
+ ): AuthoringAdapter {
15
+ if (authoring.selection) return authoring;
16
+ const selection = {
17
+ get: () => [...store.selectedEntityIds],
18
+ set: (ids: string[]) => store.selectMultiple(ids),
19
+ };
20
+ return new Proxy(authoring, {
21
+ get(target, property) {
22
+ if (property === 'selection') return selection;
23
+ const value = Reflect.get(target, property, target) as unknown;
24
+ return typeof value === 'function' ? value.bind(target) : value;
25
+ },
26
+ });
27
+ }
28
+
29
+ /**
30
+ * Make the adopted Three scene use the mounted adapter's own semantic ids.
31
+ * The store, viewport raycast, gizmo and adapter hierarchy must all address an
32
+ * object by the same id; a later generic live-scene walk preserves these tags.
33
+ */
34
+ export function stampMountedAuthoringIds(scene: THREE.Scene, authoring: AuthoringAdapter): void {
35
+ const idForObject3D = authoring.hierarchy.idForObject3D;
36
+ if (!idForObject3D) return;
37
+ scene.traverse((object) => {
38
+ const id = idForObject3D(object);
39
+ if (id) stampEntityId(object, id);
40
+ });
41
+ }
@@ -0,0 +1,96 @@
1
+ import type { Container, TickerCallback } from 'pixi.js';
2
+ import * as shellPixi from 'pixi.js';
3
+ import type { CanvasPixiNamespace } from '../canvas-entry-runtime';
4
+ import { currentGameRealmMountId, gameRealmForMountId } from '../gated-globals';
5
+
6
+ /** The part of a GSAP instance this reads: its global timeline's live work. */
7
+ interface GsapAnimation {
8
+ kill(): unknown;
9
+ }
10
+ interface GameGsap {
11
+ readonly globalTimeline: {
12
+ getChildren(nested: boolean, tweens: boolean, timelines: boolean): GsapAnimation[];
13
+ };
14
+ }
15
+
16
+ /**
17
+ * THE GAME'S OWN GSAP, when it has one. GSAP installs itself as `gsap` on the
18
+ * global its code runs against — the game realm's, or the page's — so the
19
+ * animations a game's widgets start are read from the instance the GAME
20
+ * loaded. The editor carries no GSAP of its own.
21
+ */
22
+ function gameGsap(): GameGsap | null {
23
+ const mountId = currentGameRealmMountId();
24
+ const realmGlobal = mountId === null ? null : gameRealmForMountId(mountId).globalThis;
25
+ const candidate =
26
+ (realmGlobal as { gsap?: unknown } | null)?.gsap ?? (globalThis as { gsap?: unknown }).gsap;
27
+ const timeline = (candidate as Partial<GameGsap> | undefined)?.globalTimeline;
28
+ return typeof timeline?.getChildren === 'function' ? (candidate as GameGsap) : null;
29
+ }
30
+
31
+ function liveGsapAnimations(gsap: GameGsap | null): GsapAnimation[] {
32
+ return gsap ? gsap.globalTimeline.getChildren(true, true, true) : [];
33
+ }
34
+
35
+ function newGsapAnimations(
36
+ gsap: GameGsap | null,
37
+ before: ReadonlySet<GsapAnimation>,
38
+ ): GsapAnimation[] {
39
+ return liveGsapAnimations(gsap).filter((animation) => !before.has(animation));
40
+ }
41
+
42
+ /**
43
+ * Construct one short-lived Pixi object while withholding shared-ticker
44
+ * listeners installed synchronously by its constructor.
45
+ *
46
+ * `@pixi/ui` FancyButton installs an anonymous `Ticker.shared` callback for
47
+ * each animated button and exposes no matching disposer. That is harmless for
48
+ * the original page-lifetime game, but editor isolation deliberately mounts
49
+ * and destroys the same classes repeatedly. Registering the callback and only
50
+ * removing it at teardown is too late: while this preview is alive it advances
51
+ * the package-global tween group, including stale tweens from a preview React
52
+ * just destroyed. Edit is contractually static, so constructor-time ownership
53
+ * of a wall-clock shared ticker is withheld at the isolation boundary. Play
54
+ * never uses this helper and keeps the game's original clock unchanged.
55
+ *
56
+ * `pixi` is the namespace `create()` CONSTRUCTS IN, because `Ticker.shared` is
57
+ * a module-global: the shell's is a different clock from the project's, and
58
+ * withholding registrations on the wrong one withholds nothing at all — the
59
+ * `@pixi/ui` callbacks this exists to intercept install on whichever
60
+ * `Ticker.shared` the constructed class imported. Absent ⇒ this bundle's own
61
+ * copy (see `../../vite-plugin-module-doorways.ts`).
62
+ */
63
+ export function createWithOwnedPixiTickerListeners<T extends Container>(
64
+ create: () => T,
65
+ pixi: CanvasPixiNamespace = shellPixi,
66
+ ): {
67
+ readonly value: T;
68
+ dispose(): void;
69
+ } {
70
+ const ticker = pixi.Ticker.shared;
71
+ const originalAdd = ticker.add;
72
+ const gsap = gameGsap();
73
+ const animationsBefore = new Set(liveGsapAnimations(gsap));
74
+ ticker.add = ((_fn: TickerCallback<unknown>, _context?: unknown, _priority?: number) =>
75
+ ticker) as typeof ticker.add;
76
+
77
+ try {
78
+ const value = create();
79
+ // Game interaction is not an authoring gesture. Besides keeping Edit
80
+ // static, disabling the isolated subtree prevents @pixi/ui hover handlers
81
+ // from enqueueing tweedle animations that would survive React teardown in
82
+ // the package-global group and crash when Play later advances it.
83
+ value.eventMode = 'none';
84
+ const ownedAnimations = newGsapAnimations(gsap, animationsBefore);
85
+ // A delayed GSAP call is already live once construction returns. Waiting
86
+ // until disposal lets it mutate the supposedly held document for its whole
87
+ // visible lifetime, so remove construction-owned work immediately.
88
+ for (const animation of ownedAnimations) animation.kill();
89
+ return { value, dispose: () => {} };
90
+ } catch (error) {
91
+ for (const animation of newGsapAnimations(gsap, animationsBefore)) animation.kill();
92
+ throw error;
93
+ } finally {
94
+ ticker.add = originalAdd;
95
+ }
96
+ }
@@ -0,0 +1,265 @@
1
+ /**
2
+ * A structural authoring projection over a live Phaser 3 game.
3
+ *
4
+ * Phaser belongs behind the generic `canvas` surface: this adapter never
5
+ * imports Phaser and never asks the host to become a Phaser runtime. It reads
6
+ * the public shape of the game instance the editor's realm holds, projects
7
+ * active scenes and their display lists into ordinary EditorNodes, and writes
8
+ * the native object's own transform/visibility fields directly. The game
9
+ * remains the entity model; no mirror tree or editor scene is fabricated.
10
+ */
11
+
12
+ import type {
13
+ AuthoringAdapter,
14
+ AuthoringCapabilities,
15
+ AuthoringProvenance,
16
+ EditorNode,
17
+ PropertyDescriptor,
18
+ Transform,
19
+ } from '@volter/editor-project/adapter';
20
+ import type { EditorShellStore } from '@volter/editor-core/editor-shell-store';
21
+ import { createEphemeralPersistence } from './ephemeral-persistence';
22
+
23
+ export interface PhaserGameObjectLike {
24
+ name?: string;
25
+ type?: string;
26
+ active?: boolean;
27
+ visible?: boolean;
28
+ alpha?: number;
29
+ depth?: number;
30
+ x?: number;
31
+ y?: number;
32
+ rotation?: number;
33
+ scaleX?: number;
34
+ scaleY?: number;
35
+ list?: PhaserGameObjectLike[];
36
+ constructor?: { name?: string };
37
+ }
38
+
39
+ export interface PhaserSceneLike {
40
+ sys?: { settings?: { key?: string; active?: boolean } };
41
+ children?: { list?: PhaserGameObjectLike[] };
42
+ }
43
+
44
+ export interface PhaserGameLike {
45
+ scene?: { getScenes?: (activeOnly?: boolean) => PhaserSceneLike[] };
46
+ canvas?: HTMLCanvasElement;
47
+ destroy?: (removeCanvas?: boolean, noReturn?: boolean) => void;
48
+ }
49
+
50
+ type IndexedValue =
51
+ | { kind: 'scene'; scene: PhaserSceneLike; node: EditorNode }
52
+ | { kind: 'object'; object: PhaserGameObjectLike; node: EditorNode };
53
+
54
+ function angleToQuat(angle: number): [number, number, number, number] {
55
+ return [0, 0, Math.sin(angle / 2), Math.cos(angle / 2)];
56
+ }
57
+
58
+ function quatToAngle(q: [number, number, number, number]): number {
59
+ return 2 * Math.atan2(q[2], q[3]);
60
+ }
61
+
62
+ function cleanSegment(value: string): string {
63
+ return encodeURIComponent(value || 'unnamed');
64
+ }
65
+
66
+ function objectChildren(object: PhaserGameObjectLike): PhaserGameObjectLike[] {
67
+ return Array.isArray(object.list) ? object.list : [];
68
+ }
69
+
70
+ export class PhaserLiveAuthoringAdapter implements AuthoringAdapter {
71
+ readonly capabilities: AuthoringCapabilities = {
72
+ transform: true,
73
+ inspectorFields: true,
74
+ persist: false,
75
+ };
76
+
77
+ readonly provenance: AuthoringProvenance = {
78
+ source: 'foreign',
79
+ label: 'live-only',
80
+ detail:
81
+ 'Live Phaser scene/display lists projected directly — edits apply to native game objects for this session only.',
82
+ };
83
+
84
+ readonly persistence = {
85
+ ...createEphemeralPersistence(),
86
+ destination: 'live-only (not saved)',
87
+ };
88
+
89
+ private readonly indexed = new Map<string, IndexedValue>();
90
+ private readonly listeners = new Set<() => void>();
91
+
92
+ constructor(
93
+ private readonly game: PhaserGameLike,
94
+ private readonly store: EditorShellStore,
95
+ ) {}
96
+
97
+ refresh(): { scenes: number; objects: number } {
98
+ this.reindex();
99
+ let scenes = 0;
100
+ let objects = 0;
101
+ for (const value of this.indexed.values()) {
102
+ if (value.kind === 'scene') scenes++;
103
+ else objects++;
104
+ }
105
+ return { scenes, objects };
106
+ }
107
+
108
+ private scenes(): PhaserSceneLike[] {
109
+ const getScenes = this.game.scene?.getScenes;
110
+ if (typeof getScenes !== 'function') return [];
111
+ const active = getScenes.call(this.game.scene, true);
112
+ return Array.isArray(active) ? active : [];
113
+ }
114
+
115
+ private reindex(): void {
116
+ this.indexed.clear();
117
+ for (const [sceneIndex, scene] of this.scenes().entries()) {
118
+ const sceneName = scene.sys?.settings?.key || `Scene ${sceneIndex + 1}`;
119
+ const sceneId = `phaser:scene:${sceneIndex}:${cleanSegment(sceneName)}`;
120
+ const roots = Array.isArray(scene.children?.list) ? scene.children.list : [];
121
+ const sceneNode: EditorNode = {
122
+ id: sceneId,
123
+ label: sceneName,
124
+ role: 'root',
125
+ kind: 'phaser-scene',
126
+ typeLabel: 'Phaser.Scene',
127
+ parentId: null,
128
+ childIds: [],
129
+ };
130
+ this.indexed.set(sceneId, { kind: 'scene', scene, node: sceneNode });
131
+ sceneNode.childIds = roots.map((object, index) =>
132
+ this.indexObject(object, sceneId, `${sceneIndex}/${index}`),
133
+ );
134
+ }
135
+ }
136
+
137
+ private indexObject(object: PhaserGameObjectLike, parentId: string, path: string): string {
138
+ const nativeType = object.type || object.constructor?.name || 'GameObject';
139
+ const label = object.name || nativeType;
140
+ // Structural path only: renaming a native object must not change the id
141
+ // the editor selection and undo surfaces use for it.
142
+ const id = `phaser:object:${path}`;
143
+ const node: EditorNode = {
144
+ id,
145
+ label,
146
+ role: 'entity',
147
+ kind: `phaser-${nativeType.toLowerCase()}`,
148
+ // Bundled Phaser classes are routinely minified (`c`, `a`, ...). Its
149
+ // public GameObject `type` is the stable player-facing type name.
150
+ typeLabel: nativeType,
151
+ parentId,
152
+ childIds: [],
153
+ };
154
+ this.indexed.set(id, { kind: 'object', object, node });
155
+ node.childIds = objectChildren(object).map((child, index) =>
156
+ this.indexObject(child, id, `${path}/${index}`),
157
+ );
158
+ return id;
159
+ }
160
+
161
+ readonly hierarchy = {
162
+ roots: (): EditorNode[] => {
163
+ this.reindex();
164
+ return [...this.indexed.values()]
165
+ .filter((value) => value.kind === 'scene')
166
+ .map((value) => value.node);
167
+ },
168
+ node: (id: string): EditorNode | null => {
169
+ this.reindex();
170
+ return this.indexed.get(id)?.node ?? null;
171
+ },
172
+ };
173
+
174
+ readonly selection = {
175
+ get: (): string[] => [...this.store.selectedEntityIds],
176
+ set: (ids: string[]): void => {
177
+ this.store.selectMultiple(ids);
178
+ this.notify();
179
+ },
180
+ };
181
+
182
+ readonly transforms = {
183
+ dimensions: (id: string): '2d' | null => {
184
+ this.reindex();
185
+ return this.indexed.get(id)?.kind === 'object' ? '2d' : null;
186
+ },
187
+ get: (id: string): Transform => {
188
+ this.reindex();
189
+ const value = this.indexed.get(id);
190
+ if (!value || value.kind !== 'object') {
191
+ return { position: [0, 0, 0], rotation: [0, 0, 0, 1], scale: [1, 1, 1] };
192
+ }
193
+ const object = value.object;
194
+ return {
195
+ position: [object.x ?? 0, object.y ?? 0, object.depth ?? 0],
196
+ rotation: angleToQuat(object.rotation ?? 0),
197
+ scale: [object.scaleX ?? 1, object.scaleY ?? 1, 1],
198
+ };
199
+ },
200
+ beginEdit: (_id: string): void => {},
201
+ apply: (id: string, transform: Transform): void => {
202
+ this.reindex();
203
+ const value = this.indexed.get(id);
204
+ if (!value || value.kind !== 'object') return;
205
+ value.object.x = transform.position[0];
206
+ value.object.y = transform.position[1];
207
+ value.object.depth = transform.position[2];
208
+ value.object.rotation = quatToAngle(transform.rotation);
209
+ value.object.scaleX = transform.scale[0];
210
+ value.object.scaleY = transform.scale[1];
211
+ this.notify();
212
+ },
213
+ endEdit: (_id: string): void => {},
214
+ };
215
+
216
+ readonly inspector = {
217
+ properties: (id: string): PropertyDescriptor[] => {
218
+ this.reindex();
219
+ const value = this.indexed.get(id);
220
+ if (!value) return [];
221
+ if (value.kind === 'scene') {
222
+ return [{ path: 'key', label: 'Scene key', type: 'string', readonly: true }];
223
+ }
224
+ return [
225
+ { path: 'type', label: 'Type', type: 'string', readonly: true },
226
+ { path: 'name', label: 'Name', type: 'string' },
227
+ { path: 'active', label: 'Active', type: 'boolean' },
228
+ { path: 'visible', label: 'Visible', type: 'boolean' },
229
+ { path: 'alpha', label: 'Alpha', type: 'number' },
230
+ { path: 'depth', label: 'Depth', type: 'number' },
231
+ ];
232
+ },
233
+ get: (id: string, path: string): unknown => {
234
+ this.reindex();
235
+ const value = this.indexed.get(id);
236
+ if (!value) return undefined;
237
+ if (value.kind === 'scene')
238
+ return path === 'key' ? value.scene.sys?.settings?.key : undefined;
239
+ if (path === 'type') return value.object.type || value.object.constructor?.name;
240
+ return value.object[path as keyof PhaserGameObjectLike];
241
+ },
242
+ set: (id: string, path: string, value: unknown): void => {
243
+ this.reindex();
244
+ const indexed = this.indexed.get(id);
245
+ if (!indexed || indexed.kind !== 'object' || path === 'type') return;
246
+ (indexed.object as unknown as Record<string, unknown>)[path] = value;
247
+ this.notify();
248
+ },
249
+ };
250
+
251
+ subscribe(listener: () => void): () => void {
252
+ this.listeners.add(listener);
253
+ return () => this.listeners.delete(listener);
254
+ }
255
+
256
+ dispose(): void {
257
+ this.listeners.clear();
258
+ this.indexed.clear();
259
+ }
260
+
261
+ private notify(): void {
262
+ for (const listener of this.listeners) listener();
263
+ this.store.notifyIngestEdit();
264
+ }
265
+ }