@volter/editor-game 0.5.66 → 0.5.68

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 (239) 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/build-player.document.tsx +95 -0
  5. package/contributions/canvas/component-board.service.ts +16 -0
  6. package/contributions/canvas/design-time-mount.service.ts +45 -0
  7. package/contributions/canvas-story-capture.service.ts +12 -0
  8. package/contributions/edit-mode-audio.service.ts +2 -2
  9. package/contributions/edit-mode-networking.service.ts +1 -1
  10. package/contributions/gameplay.command.ts +4 -4
  11. package/contributions/generation.service.ts +1 -1
  12. package/contributions/godot.style.ts +26 -6
  13. package/contributions/godot.view.ts +6 -0
  14. package/contributions/ingest.service.ts +2 -2
  15. package/contributions/instances.command.ts +1 -1
  16. package/contributions/navmesh.menu.ts +1 -1
  17. package/contributions/network-observer.service.ts +14 -0
  18. package/contributions/play.command.ts +10 -4
  19. package/contributions/react/component-board.service.ts +1 -1
  20. package/contributions/react/design-time-mount.service.ts +1 -1
  21. package/contributions/scene-document.service.ts +3 -3
  22. package/contributions/state-watch.menu.ts +1 -1
  23. package/contributions/state-watch.utility.tsx +1 -1
  24. package/contributions/team-playtest.service.ts +4 -4
  25. package/contributions/three/component-board.service.ts +1 -1
  26. package/contributions/three/component-verbs.command.ts +6 -6
  27. package/contributions/three/three-authoring.service.ts +8 -5
  28. package/contributions/three-story-capture.service.ts +12 -0
  29. package/contributions/unity.style.ts +17 -7
  30. package/contributions/unity.view.ts +6 -0
  31. package/contributions/unreal.style.ts +10 -2
  32. package/contributions/unreal.view.ts +5 -0
  33. package/package.json +21 -10
  34. package/src/asset-budget/AssetBudgetPanel.tsx +1 -1
  35. package/src/asset-budget/asset-budget-model.ts +1 -1
  36. package/src/audio/AudioDebuggerPanel.tsx +1 -1
  37. package/src/bridge/dispatch.ts +14 -14
  38. package/src/bridge/live-frames.ts +1 -1
  39. package/src/bridge/screenshot.ts +3 -3
  40. package/src/build/BuildProfilesPanel.tsx +14 -3
  41. package/src/build/build-session.ts +35 -0
  42. package/src/canvas/canvas-board/CanvasBoardDocument.tsx +749 -0
  43. package/src/canvas/canvas-board/canvas-board-model.ts +407 -0
  44. package/src/canvas/canvas-board/canvas-component-board.ts +56 -0
  45. package/src/canvas/canvas-design-mount.ts +524 -0
  46. package/src/canvas/design-time-canvas-mount.ts +79 -0
  47. package/src/coverage/live-authoring-surface.ts +5 -5
  48. package/src/coverage/live-project-verbs.ts +1 -1
  49. package/src/coverage/native-system-coverage.ts +5 -5
  50. package/src/coverage/root-coverage.ts +1 -1
  51. package/src/coverage/session-coverage.ts +3 -3
  52. package/src/design-system-stories/ApplicationChrome.stories.tsx +5 -5
  53. package/src/design-system-stories/InspectorNarrowBodies.stories.tsx +7 -7
  54. package/src/edit-mode/edit-mode-audio.ts +4 -4
  55. package/src/edit-mode/edit-mode-networking.ts +2 -2
  56. package/src/game-document/GameCaptureFrameButton.tsx +1 -1
  57. package/src/game-document/GameDocument.tsx +2 -2
  58. package/src/game-document/GamePanel.tsx +4 -4
  59. package/src/game-document/InstanceInspectorPicker.tsx +2 -2
  60. package/src/game-document/crowd-debug.ts +1 -1
  61. package/src/game-document/device-preview.ts +10 -11
  62. package/src/game-document/physics-debug.ts +1 -1
  63. package/src/generation/GenerationActivity.tsx +3 -3
  64. package/src/generation/generation-documents.tsx +5 -5
  65. package/src/generation/generation-jobs.ts +1 -1
  66. package/src/host/adapter-runtime-bindings.ts +96 -5
  67. package/src/host/authoring/babylon-authoring-adapter.ts +6 -6
  68. package/src/host/authoring/gesture-persist.ts +1 -1
  69. package/src/host/authoring/ingest-data-writer.ts +1 -1
  70. package/src/host/authoring/ingest-source-persistence.ts +4 -4
  71. package/src/host/authoring/mounted-authoring.ts +4 -4
  72. package/src/host/authoring/phaser-live-authoring-adapter.ts +4 -4
  73. package/src/host/authoring/pixi-authoring-adapter.ts +178 -31
  74. package/src/host/authoring/pixi-creatable-kinds.ts +62 -0
  75. package/src/host/authoring/pixi-creation-site-write-target.ts +2 -2
  76. package/src/host/authoring/pixi-live-write-target.ts +4 -4
  77. package/src/host/authoring/pixi-source-identity.ts +3 -3
  78. package/src/host/authoring/pixi-source-write-target.ts +1709 -0
  79. package/src/host/authoring/pixi-still-presentation.ts +1 -1
  80. package/src/host/authoring/pixi-structure-history.ts +2 -2
  81. package/src/host/authoring/pixi-transform-channels.ts +16 -14
  82. package/src/host/authoring/source-persistence-backend.ts +3 -3
  83. package/src/host/authoring/struct-write-pipe.ts +1 -1
  84. package/src/host/binding-resolver.ts +8 -9
  85. package/src/host/browser-transpile.ts +1 -1
  86. package/src/host/canvas-entry-runtime.ts +58 -47
  87. package/src/host/canvas-preview-frames.ts +482 -0
  88. package/src/host/components/CameraAuthoringOverlay.tsx +1 -1
  89. package/src/host/components/HeaderTelemetry.tsx +4 -4
  90. package/src/host/components/PixiIsolationSceneContent.tsx +11 -11
  91. package/src/host/components/ThreeIsolationSceneContent.tsx +3 -3
  92. package/src/host/components/frame-debugger-model.ts +2 -2
  93. package/src/host/components/header-telemetry-model.ts +2 -2
  94. package/src/host/components/scene-document.tsx +14 -14
  95. package/src/host/components/utility-view-state.ts +1 -1
  96. package/src/host/components/world-root-stage-binding.tsx +12 -12
  97. package/src/host/components/world-root-stage.ts +70 -49
  98. package/src/host/coverage/system-adapter-coverage.ts +3 -4
  99. package/src/host/design-system-stories/fixtures/editor-runtime.tsx +9 -11
  100. package/src/host/document-preview-three.ts +1 -1
  101. package/src/host/entry-adjudication.ts +6 -6
  102. package/src/host/game-css-scope-transform.ts +4 -0
  103. package/src/host/game-realm-page.ts +1 -1
  104. package/src/host/gameplay-export.ts +25 -14
  105. package/src/host/gameplay-recording.ts +5 -5
  106. package/src/host/gated-globals.ts +2 -2
  107. package/src/host/history/json-history-resource.ts +1 -1
  108. package/src/host/projection/pixi.ts +24 -2
  109. package/src/host/r3f-entry-runtime.ts +65 -34
  110. package/src/host/react-mount-runtime.ts +7 -48
  111. package/src/host/realm-services.ts +1 -1
  112. package/src/host/roots/canvas-root.tsx +373 -0
  113. package/src/host/roots/r3f-root.tsx +473 -0
  114. package/src/host/roots/react-root.ts +9 -43
  115. package/src/host/served-bundle-runtime-modules.ts +3 -19
  116. package/src/host/server-log-bridge.ts +2 -2
  117. package/src/host/stories/mounted-story-viewport-source.ts +1 -1
  118. package/src/host/stories/pixi-story-model.ts +30 -0
  119. package/src/host/stories/story-media-captures.ts +46 -0
  120. package/src/host/stories/story-media-presence.ts +3 -3
  121. package/src/host/stories/story-pixi-preview.ts +408 -0
  122. package/src/host/stories/story-three-preview.ts +806 -0
  123. package/src/host/stories/three-story-captures.ts +35 -0
  124. package/src/host/story-three-preview-runtime.ts +56 -0
  125. package/src/host/use-active-performance-source.ts +2 -2
  126. package/src/host/viewport-pose-memory.ts +1 -1
  127. package/src/host/viewport-root-presentation.ts +6 -5
  128. package/src/ingest/active-ingest.ts +1 -1
  129. package/src/ingest/authoring/ingest-dom-surface-authoring.ts +4 -4
  130. package/src/ingest/authoring/ingest-root-adapter.ts +8 -8
  131. package/src/ingest/deferred-ingest-play.ts +6 -6
  132. package/src/ingest/discovery-public-ingest.ts +2 -2
  133. package/src/ingest/ingest-boot-viewport.ts +2 -2
  134. package/src/ingest/ingest-canvas-scene-document.tsx +9 -9
  135. package/src/ingest/ingest-canvas-scene.ts +3 -3
  136. package/src/ingest/ingest-evidence-hook.ts +1 -1
  137. package/src/ingest/ingest-frame-snapshot.ts +1 -1
  138. package/src/ingest/ingest-play-control.ts +1 -1
  139. package/src/ingest/ingest-render-debug.ts +10 -10
  140. package/src/ingest/ingest-siblings.ts +13 -25
  141. package/src/ingest/module-mode.ts +14 -14
  142. package/src/ingest/mount-canvas-ingest-root.ts +21 -21
  143. package/src/ingest/mount-coverage.ts +2 -2
  144. package/src/ingest/mount-dom-ingest-root.ts +11 -11
  145. package/src/ingest/mount-ingest-root.ts +8 -8
  146. package/src/ingest/mount-three-ingest-root.ts +8 -8
  147. package/src/ingest/resolve-canvas.ts +1 -1
  148. package/src/ingest/served-html-boot.ts +1 -1
  149. package/src/ingest/surface-canvas.ts +1 -1
  150. package/src/ingest/unmount-ingest-root.ts +5 -5
  151. package/src/navmesh/navmesh-handler.ts +24 -16
  152. package/src/network/NetworkInspectorPanel.tsx +939 -37
  153. package/src/network/network-inspector-model.ts +20 -2
  154. package/src/play/play-log-events.ts +1 -1
  155. package/src/play/play-mode.ts +76 -133
  156. package/src/play/play-recording.ts +1 -1
  157. package/src/play/react-play-live-authoring.ts +3 -3
  158. package/src/play/run-selection.ts +93 -0
  159. package/src/play-bar/PlayBar.tsx +20 -39
  160. package/src/profiler/FrameDebuggerPanel.tsx +1 -1
  161. package/src/profiler/PerformancePanel.tsx +3 -3
  162. package/src/react/design-time-react-mount.ts +23 -65
  163. package/src/react/dom-authoring-adapter.ts +9 -9
  164. package/src/react/react-inspector-section.tsx +5 -5
  165. package/src/react/react-world-authoring-adapter.ts +11 -11
  166. package/src/react/story-documents/story-documents.tsx +9 -9
  167. package/src/react/ui-board-document.tsx +7 -7
  168. package/src/react/ui-component-board.ts +2 -2
  169. package/src/runtime/adapter/audio-meter.ts +21 -0
  170. package/src/runtime/adapter/first-party-audio-system.ts +230 -0
  171. package/src/runtime/adapter/ingest/contract-debug-adapter.ts +114 -0
  172. package/src/runtime/adapter/ingest/contract-system-adapters.ts +256 -0
  173. package/src/runtime/adapter/ingest/merge-debug-adapters.ts +197 -0
  174. package/src/runtime/adapter/ingest/observation-debug-adapter.ts +162 -0
  175. package/src/runtime/adapter/ingest/upstream-pin.ts +51 -0
  176. package/src/runtime/adapter/native-debug-module.ts +498 -0
  177. package/src/runtime/audio/bus-mixer.ts +161 -0
  178. package/src/runtime/audio/pose-guard.ts +80 -0
  179. package/src/runtime/core/frame-pacing.ts +126 -0
  180. package/src/runtime/core/game-loop.ts +225 -0
  181. package/src/runtime/core/game-scoped-slot.ts +28 -0
  182. package/src/runtime/core/seeded-random.ts +162 -0
  183. package/src/runtime/core/sim-clock.ts +391 -0
  184. package/src/runtime/core/system-runner.ts +269 -0
  185. package/src/runtime/core/types.ts +104 -0
  186. package/src/runtime/create-runtime.ts +1128 -0
  187. package/src/runtime/debug-bridge.ts +570 -0
  188. package/src/runtime/debug-registry.ts +899 -0
  189. package/src/runtime/dev/chrome-trace.ts +153 -0
  190. package/src/runtime/dev/instruments.ts +403 -0
  191. package/src/runtime/dev/logger.ts +119 -0
  192. package/src/runtime/dev/performance-profiler.ts +367 -0
  193. package/src/runtime/dev/register-render-vitals.ts +276 -0
  194. package/src/runtime/dev/render-census.ts +354 -0
  195. package/src/runtime/dev/render-debug-adapter.ts +218 -0
  196. package/src/runtime/dev/render-memory.ts +226 -0
  197. package/src/runtime/dev/render-vitals.ts +338 -0
  198. package/src/runtime/dev/static-batch-advisor.ts +188 -0
  199. package/src/runtime/dev/webgl-frame-capture.ts +366 -0
  200. package/src/runtime/dev/webgl-gpu-timer.ts +53 -0
  201. package/src/runtime/dev-build.ts +47 -0
  202. package/src/runtime/game.ts +1636 -0
  203. package/src/runtime/gameplay-rng-trap.ts +135 -0
  204. package/src/runtime/host-context.ts +64 -0
  205. package/src/runtime/input-router.ts +182 -0
  206. package/src/runtime/mount-manifest.ts +480 -0
  207. package/src/runtime/pixi/authoring.ts +706 -0
  208. package/src/runtime/pixi/ingest.ts +116 -0
  209. package/src/runtime/pixi/physics-registry.ts +49 -0
  210. package/src/runtime/pixi/render-pass-bracket.ts +117 -0
  211. package/src/runtime/pixi/scene-capture.ts +179 -0
  212. package/src/runtime/pixi/system-adapters.ts +69 -0
  213. package/src/runtime/playtest.ts +22 -0
  214. package/src/runtime/presentation.ts +141 -0
  215. package/src/runtime/render-control.ts +642 -0
  216. package/src/runtime/render-seed.ts +77 -0
  217. package/src/runtime/run-ticks-settled.ts +73 -0
  218. package/src/runtime/setup/setup-audio.ts +72 -0
  219. package/src/services/audio-pose-guard.ts +2 -2
  220. package/src/services/game-audio.ts +152 -0
  221. package/src/services/game-network.ts +767 -0
  222. package/src/services/game-physics.ts +334 -0
  223. package/src/state-watch/StateWatchPanel.tsx +1 -1
  224. package/src/three/authoring/camera-runtime-inspector-section.tsx +3 -2
  225. package/src/three/authoring/constraint-inspector-section.tsx +6 -5
  226. package/src/three/authoring/model-asset-inspector-section.tsx +7 -6
  227. package/src/three/authoring/oid-source-persistence.ts +7 -7
  228. package/src/three/authoring/r3f-design-session.ts +58 -54
  229. package/src/three/authoring/r3f-source-authoring-adapter.ts +95 -91
  230. package/src/three/authoring/reflection-probe-inspector-section.tsx +3 -2
  231. package/src/three/authoring/three-authoring-adapter.ts +29 -29
  232. package/src/three/component-verbs/extract-menu.ts +7 -6
  233. package/src/three/component-verbs/fork-menu.ts +7 -6
  234. package/src/three/component-verbs/internals-menu.ts +2 -2
  235. package/src/three/story-documents/three-story-documents.tsx +13 -13
  236. package/src/three/three-board/ThreeBoardDocument.tsx +13 -12
  237. package/src/three/three-board/board-scene.ts +6 -6
  238. package/src/three/three-board/three-component-board.ts +2 -2
  239. package/src/services/game-audio-unlock.ts +0 -48
@@ -0,0 +1,524 @@
1
+ /**
2
+ * The canvas root's DESIGN-TIME mount — the game's own native world, on screen
3
+ * and authorable in EDIT mode, with the host clock never advanced.
4
+ *
5
+ * ## Why this is a layer mount and not a second design session
6
+ *
7
+ * `r3f-design-session.ts` exists because a three world has to be ADOPTED into
8
+ * the editor's own WebGL viewport: the editor already owns a renderer, a
9
+ * camera and a raycaster, so the session mounts fiber against a mock renderer
10
+ * and hands the resulting `THREE.Scene` to the store. None of that applies
11
+ * here. A canvas world draws through its OWN substrate into its OWN
12
+ * viewport-sized canvas. Pixi receives the editor's independent 2D Scene
13
+ * camera; Babylon receives its native camera and GizmoManager. Neither path
14
+ * reparents or mirrors the native entity tree.
15
+ *
16
+ * So this module is the `mountReactLayer` sibling, and it inherits that
17
+ * module's whole lifecycle rather than restating one: candidate discovery,
18
+ * the play handoff (layers torn down and the world's Boundary node restored
19
+ * the instant play starts; Stop queues one edit-mode rebuild), the failure
20
+ * degrade to a `BoundaryAuthoringAdapter` disclosure node, the pan
21
+ * subscription, and the eye/interactive session toggles. `r3f-design-session`
22
+ * says of its own play handoff that it "mirrors design-time-layers.ts"; this
23
+ * lane uses the original instead of a second copy.
24
+ *
25
+ * ## The paused clock, stated once
26
+ *
27
+ * Pixi initializes its `Application` with `autoStart: false` and
28
+ * `sharedTicker: false`; the ticker never starts and this module calls only
29
+ * `app.render()`. Babylon receives `mounted.update(0)`, explicitly a zero-delta
30
+ * still-frame request. Both paths render inspector and gizmo edits without
31
+ * advancing game time. Same shape as the editor's own three viewport, which
32
+ * also draws every frame over a scene nothing is ticking.
33
+ *
34
+ * ## Input is off
35
+ *
36
+ * The design host carries a REAL `Game` — the world's components read
37
+ * `ctx.input`/`ctx.random`/`ctx.debug` at their first commit and a fabricated
38
+ * stand-in would throw there — but `game.input.setEnabled(false)` is called
39
+ * before the mount, because design time is not play. `InputManager` binds
40
+ * window-level listeners in its constructor; `game.dispose()` unbinds them and
41
+ * this module's disposer is what calls it.
42
+ */
43
+
44
+ import { setActiveSystems, updateInstanceSystems } from '@volter/editor-sdk/kit/authoring/active-systems';
45
+ import {
46
+ BabylonAuthoringAdapter,
47
+ type BabylonEngineLike,
48
+ } from '../host/authoring/babylon-authoring-adapter';
49
+ import { PixiAuthoringAdapter } from '../host/authoring/pixi-authoring-adapter';
50
+ import { createOidCanvasIdentity } from '../host/authoring/pixi-source-identity';
51
+ import { createSourceCanvasWriteTarget } from '../host/authoring/pixi-source-write-target';
52
+ import {
53
+ fitSceneView,
54
+ registerStillFramePresenter,
55
+ } from '../host/authoring/pixi-still-presentation';
56
+ import { createCreationSitePersistence } from '../host/authoring/source-persistence-backend';
57
+ import {
58
+ type RootViewController,
59
+ sharedRootViewController,
60
+ } from '@volter/editor-sdk/kit/world-pan-state';
61
+ import {
62
+ resolveCanvasEntryAdapterForEditor,
63
+ resolveCanvasPixiForEditor,
64
+ } from '../host/canvas-entry-runtime';
65
+ import {
66
+ capturePixiDisplayObjectThumbnail,
67
+ withApplicationCollector,
68
+ } from '../host/canvas-preview-frames';
69
+ import { editorConsole } from '@volter/editor-sdk/kit/editor-console';
70
+ import type { EditorShellStore } from '@volter/editor-threejs/kit/editor-shell-store';
71
+ import { authoringJournal } from '../host/history/json-history-resource';
72
+ import { getCurrentProject } from '@volter/editor-sdk/kit/project-manager';
73
+ import { beginProjectMountEpoch } from '@volter/editor-sdk/session/project-module-url';
74
+ import { activeRealmServices } from '../host/realm-services';
75
+ import { tierSourceWriteBackend } from '@volter/editor-sdk/kit/ui-source/tier-source-write-backend';
76
+ import type { AuthoringAdapter, MountedCanvasRoot } from '@volter/editor-project/adapter';
77
+ import {
78
+ installNativeDebugBindings,
79
+ installNativeSystemsBindings,
80
+ nativeDebugBindingFromEntryModule,
81
+ nativeSystemsBindingFromEntryModule,
82
+ } from '../runtime/adapter/native-debug-module';
83
+ import { createAssetCache } from '@volter/threejs-runtime/assets';
84
+ import { createGameLoop } from '../runtime/core/game-loop';
85
+ import { registerCanvasRoot } from '../runtime/create-runtime';
86
+ import { createGame, type GameInternal } from '../runtime/game';
87
+ import type { GameCanvasHostContext } from '../runtime/host-context';
88
+ import type { Application } from 'pixi.js';
89
+
90
+ /** What a design-time canvas layer hands back to `design-time-layers.ts`. */
91
+ export interface MountedCanvasDesignLayer {
92
+ readonly adapter: AuthoringAdapter;
93
+ dispose(): void;
94
+ }
95
+
96
+ /** Fallback stage size when the project declares no `resolution` — the same
97
+ * 720p the canvas host defaults to elsewhere. */
98
+ const DEFAULT_STAGE = { width: 1280, height: 720 } as const;
99
+
100
+ /**
101
+ * The inert design-time `Game`.
102
+ *
103
+ * A REAL `createGame` whose loop is constructed and NEVER started (the same
104
+ * decision, for the same reason, as `r3f-design-session.ts`'s `createDesignHost`
105
+ * and `design-time-layers.ts`'s `createDesignTimeGame`): the design-time Game
106
+ * shell is what the lib-legal react doors (`useOptionalGame`,
107
+ * `useDebugProvider`) resolve against, so a world whose components read the
108
+ * game handle design-mounts exactly as it plays.
109
+ */
110
+ function createDesignGame(): GameInternal {
111
+ const game = createGame({
112
+ loop: createGameLoop({ update: () => {} }),
113
+ assets: createAssetCache(),
114
+ });
115
+ return game;
116
+ }
117
+
118
+ /** The viewport-sized surface the native stage draws into. World extents are
119
+ * deliberately not encoded in this element: the editor camera matrix decides
120
+ * which part of the unbounded authored plane is visible. */
121
+ function createDesignCanvas(): HTMLCanvasElement {
122
+ const canvas = document.createElement('canvas');
123
+ canvas.dataset['vgaiCanvasSceneSurface'] = 'true';
124
+ canvas.style.position = 'absolute';
125
+ canvas.style.inset = '0';
126
+ canvas.style.width = '100%';
127
+ canvas.style.height = '100%';
128
+ canvas.style.display = 'block';
129
+ return canvas;
130
+ }
131
+
132
+ interface BabylonDesignMountOptions {
133
+ readonly worldId: string;
134
+ readonly entry: string;
135
+ readonly layer: HTMLElement;
136
+ readonly canvas: HTMLCanvasElement;
137
+ readonly game: GameInternal;
138
+ readonly mounted: MountedCanvasRoot;
139
+ readonly store: EditorShellStore;
140
+ readonly onFirstFrame?: (() => void) | undefined;
141
+ }
142
+
143
+ /** Babylon's native 3D Scene document. The host asks the mounted root to draw
144
+ * a zero-delta still frame; simulation remains frozen, while native picking
145
+ * and GizmoManager author the real Babylon nodes. */
146
+ function mountBabylonDesignLayer(options: BabylonDesignMountOptions): MountedCanvasDesignLayer {
147
+ const { worldId, entry, layer, canvas, game, mounted, store, onFirstFrame } = options;
148
+ const backend = createCreationSitePersistence({ history: store.shell.projectHistory });
149
+ const adapter = new BabylonAuthoringAdapter(mounted.substrate.root as BabylonEngineLike, store, {
150
+ canvas,
151
+ api: mounted.substrate.api as never,
152
+ persistence: backend,
153
+ journal: authoringJournal(worldId),
154
+ provenance: {
155
+ source: 'source-code',
156
+ label: 'babylon',
157
+ detail:
158
+ 'Native Babylon.js scene graph; imperative edits write to the construction site in the game’s own source.',
159
+ },
160
+ });
161
+ let publishedSystems = game.systemAdapters;
162
+ setActiveSystems(publishedSystems);
163
+ const unsubscribeSystemAdapters = game.subscribeSystemAdapters?.(() => {
164
+ publishedSystems = game.systemAdapters;
165
+ updateInstanceSystems(publishedSystems);
166
+ store.shell.notifyIngestEdit();
167
+ });
168
+ const resize = (): void => {
169
+ const rect = layer.getBoundingClientRect();
170
+ if (rect.width > 0 && rect.height > 0) mounted.resize?.(rect.width, rect.height);
171
+ };
172
+ const observer = new ResizeObserver(resize);
173
+ observer.observe(layer);
174
+ resize();
175
+ let disposed = false;
176
+ let first = false;
177
+ let frame = 0;
178
+ const draw = (): void => {
179
+ frame = requestAnimationFrame(draw);
180
+ if (disposed) return;
181
+ try {
182
+ mounted.update?.(0);
183
+ if (!first) {
184
+ first = true;
185
+ onFirstFrame?.();
186
+ }
187
+ } catch (error) {
188
+ cancelAnimationFrame(frame);
189
+ editorConsole.error(
190
+ `[babylon-design] world "${worldId}" failed to render: ${String(error)}`,
191
+ 'authoring',
192
+ );
193
+ }
194
+ };
195
+ frame = requestAnimationFrame(draw);
196
+ editorConsole.log(
197
+ `[babylon-design] world "${worldId}" mounted as a native Babylon Scene (${entry})`,
198
+ 'authoring',
199
+ );
200
+ return {
201
+ adapter,
202
+ dispose(): void {
203
+ disposed = true;
204
+ cancelAnimationFrame(frame);
205
+ observer.disconnect();
206
+ adapter.dispose();
207
+ unsubscribeSystemAdapters?.();
208
+ setActiveSystems(null);
209
+ mounted.dispose();
210
+ canvas.remove();
211
+ game.dispose();
212
+ },
213
+ };
214
+ }
215
+
216
+ /**
217
+ * Mount one canvas root's world into `layer` for design-time authoring.
218
+ *
219
+ * Throws on any failure (a missing entry, a module that is neither shape, a
220
+ * tree that crashes before its first commit — the canvas mount already
221
+ * converts that last one into a named error rather than a hang). The caller
222
+ * degrades a throw to the world's `BoundaryAuthoringAdapter` disclosure node.
223
+ */
224
+ /** The views a canvas world has already opened in (see the opening framing below). */
225
+ const openedViews = new WeakSet<RootViewController>();
226
+
227
+ export async function mountCanvasDesignLayer(
228
+ worldId: string,
229
+ entry: string | undefined,
230
+ layer: HTMLElement,
231
+ projectRoot: string,
232
+ store: EditorShellStore,
233
+ suppliedView?: RootViewController,
234
+ onFirstFrame?: () => void,
235
+ ): Promise<MountedCanvasDesignLayer> {
236
+ if (!entry) {
237
+ throw new Error(`canvas root "${worldId}": no \`entry\` declared — nothing to mount.`);
238
+ }
239
+
240
+ // One epoch per design mount: this world shares no project module graph with
241
+ // anything else (`ingest-siblings.ts`'s sibling layer does the same).
242
+ const realm = await activeRealmServices(projectRoot, beginProjectMountEpoch());
243
+ const entryModule = await realm.loadEntryModule(entry, worldId, 'canvas');
244
+
245
+ const entryDebug = nativeDebugBindingFromEntryModule(worldId, entryModule);
246
+ const entrySystems = nativeSystemsBindingFromEntryModule(worldId, entryModule, 'canvas');
247
+
248
+ // Pixi and the reconciler are loaded ONLY when a project actually has a
249
+ // canvas root: a static import would put them in every editor bundle,
250
+ // including one opening a three-only project. Under the PACKAGED runtime the
251
+ // adjudicator itself comes from the project's own Vite graph, so the adapter
252
+ // it builds closes over the project's React/`@pixi/react`/`pixi.js` rather
253
+ // than the prebuilt shell's — see `canvas-entry-runtime.ts`.
254
+ const rootAdapter = await resolveCanvasEntryAdapterForEditor(entryModule, worldId);
255
+ // THE namespace for everything this mount builds — see
256
+ // `../../vite-plugin-module-doorways.ts`. Resolved beside the adjudicator
257
+ // because it comes from the same graph the adjudicator does.
258
+ if (!rootAdapter) {
259
+ throw new Error(
260
+ `canvas root "${worldId}": entry module "${entry}" must default-export a React component ` +
261
+ '(`export default function World() { … }`) — or export an `adapter` for full control.',
262
+ );
263
+ }
264
+
265
+ const activeProject = getCurrentProject();
266
+ const width = activeProject?.config.resolution?.width ?? DEFAULT_STAGE.width;
267
+ const height = activeProject?.config.resolution?.height ?? DEFAULT_STAGE.height;
268
+ const view = suppliedView ?? sharedRootViewController;
269
+ const canvas = createDesignCanvas();
270
+ layer.appendChild(canvas);
271
+
272
+ const game = createDesignGame();
273
+ const host: GameCanvasHostContext = {
274
+ canvas,
275
+ width,
276
+ height,
277
+ game,
278
+ headless: false,
279
+ // The Scene grid is editor chrome behind native world content. Explicit
280
+ // source-owned backgrounds still draw normally; the renderer itself must
281
+ // not fabricate the runtime's opaque output surface over the grid.
282
+ transparent: true,
283
+ // Scene capture must photograph the exact PRESENTED frame, including this
284
+ // document's independent editor-camera matrix. Registering the app with
285
+ // the generic Pixi extraction seam would re-render its raw stage and omit
286
+ // that presentation transform. This design-only renderer therefore keeps
287
+ // its drawing buffer so the compositor reads the viewport canvas itself.
288
+ preserveDrawingBuffer: true,
289
+ };
290
+
291
+ // The Pixi `Application`, when Pixi is the declared substrate. It is
292
+ // collected through Pixi's own `__PIXI_APP_INIT__` hook; imperative adapters
293
+ // such as Babylon simply leave this handle undefined.
294
+ let mounted: MountedCanvasRoot;
295
+ let app: Application | undefined;
296
+ try {
297
+ mounted = await withApplicationCollector(async (created) => {
298
+ const result = await rootAdapter.mount(host);
299
+ app = created[0];
300
+ return result;
301
+ });
302
+ } catch (error) {
303
+ canvas.remove();
304
+ game.dispose();
305
+ throw error;
306
+ }
307
+ registerCanvasRoot(game, rootAdapter, mounted, { id: worldId });
308
+ installNativeDebugBindings(game, entryDebug ? [entryDebug] : []);
309
+ installNativeSystemsBindings(game, entrySystems ? [entrySystems] : []);
310
+
311
+ if (mounted.substrate.name === 'babylon') {
312
+ return mountBabylonDesignLayer({
313
+ worldId,
314
+ entry,
315
+ layer,
316
+ canvas,
317
+ game,
318
+ mounted,
319
+ store,
320
+ onFirstFrame,
321
+ });
322
+ }
323
+ if (mounted.substrate.name !== 'pixi') {
324
+ mounted.dispose();
325
+ canvas.remove();
326
+ game.dispose();
327
+ throw new Error(
328
+ `canvas root "${worldId}" mounted undeclared editor substrate ` +
329
+ `"${mounted.substrate.name}" — declare pixi or babylon and provide its authoring adapter.`,
330
+ );
331
+ }
332
+ const stage = mounted.substrate.root as import('pixi.js').Container;
333
+ const pixi = await resolveCanvasPixiForEditor();
334
+
335
+ // A MOUNT WITHOUT AN APPLICATION IS A DESIGN VIEW THAT CAN NEVER DRAW, and it
336
+ // used to say nothing at all: `renderScene()` below simply answers `false`
337
+ // forever, the rAF loop spins, the capture door hands back a canvas nothing
338
+ // ever rendered into, and the Scene reads as an empty grid over a world that
339
+ // mounted perfectly. The hook is Pixi's own devtools global, so anything that
340
+ // moves it (a Pixi upgrade, a second collector, a mount that never reaches
341
+ // `Application.init`) breaks the handle without breaking the mount. Say so.
342
+ if (app === undefined) {
343
+ editorConsole.error(
344
+ `[canvas-design] world "${worldId}" mounted, but its Pixi Application was never handed to ` +
345
+ "the design view (Pixi's `__PIXI_APP_INIT__` hook did not report one) — this Scene will " +
346
+ 'render no frames at all. The world itself is fine; the editor cannot draw it.',
347
+ 'authoring',
348
+ );
349
+ }
350
+ // This inert editor mount is still a real Game shell. Register its native
351
+ // root so Game.systemAdapters sees the mounted bag, then publish that live
352
+ // aggregate exactly as the R3F design session does. Without this, Canvas
353
+ // authoring exists while system coverage reads an empty active plane and
354
+ // fabricates a missing-debug warning over a registry the mount did create.
355
+ let publishedSystems = game.systemAdapters;
356
+ setActiveSystems(publishedSystems);
357
+ const unsubscribeSystemAdapters = game.subscribeSystemAdapters?.(() => {
358
+ publishedSystems = game.systemAdapters;
359
+ updateInstanceSystems(publishedSystems);
360
+ store.shell.notifyIngestEdit();
361
+ });
362
+ // THE canvas authoring adapter, on its two declared axes: oid identity (this
363
+ // world's containers carry the `data-oid` the editor's transform stamped) and
364
+ // a TSX-source persistence target. The very same class projects an ingested
365
+ // game's stage, with the structural identity and the live-only target — the
366
+ // adapter is keyed on the SURFACE, never on provenance.
367
+ const writeBackend = await tierSourceWriteBackend();
368
+ const adapter = new PixiAuthoringAdapter(stage, store, {
369
+ pixi,
370
+ identity: createOidCanvasIdentity(worldId),
371
+ journal: authoringJournal(worldId),
372
+ target: createSourceCanvasWriteTarget({
373
+ worldId,
374
+ entryPath: entry,
375
+ pixi,
376
+ writeBackend,
377
+ }),
378
+ surface: () => canvas,
379
+ capturePreview: (object, size) => capturePixiDisplayObjectThumbnail(object, { ...size, pixi }),
380
+ pointFromClient: (clientX, clientY, surfaceRect) => {
381
+ const pose = view.get();
382
+ return {
383
+ x: (clientX - surfaceRect.left - pose.x) / pose.zoom,
384
+ y: (clientY - surfaceRect.top - pose.y) / pose.zoom,
385
+ };
386
+ },
387
+ });
388
+
389
+ // The Pixi renderer belongs to the document viewport, not the game's
390
+ // declared output resolution. Resize only presentation; the world mounted
391
+ // against the real project width/height above and keeps those semantics.
392
+ const resize = (): void => {
393
+ const rect = layer.getBoundingClientRect();
394
+ const renderer = app?.renderer;
395
+ if (renderer && rect.width > 0 && rect.height > 0) renderer.resize(rect.width, rect.height);
396
+ };
397
+ const resizeObserver = new ResizeObserver(resize);
398
+ resizeObserver.observe(layer);
399
+ resize();
400
+
401
+ const renderScene = (): boolean => {
402
+ const renderer = app?.renderer;
403
+ if (!renderer) return false;
404
+ const rect = layer.getBoundingClientRect();
405
+ if (
406
+ rect.width > 0 &&
407
+ rect.height > 0 &&
408
+ (renderer.screen.width !== rect.width || renderer.screen.height !== rect.height)
409
+ ) {
410
+ renderer.resize(rect.width, rect.height);
411
+ }
412
+ const pose = view.get();
413
+ renderer.render({
414
+ container: stage,
415
+ // The world's OWN `Matrix` — this value is handed to the world's own
416
+ // renderer, which is free to type-check what it is given.
417
+ transform: new pixi.Matrix(pose.zoom, 0, 0, pose.zoom, pose.x, pose.y),
418
+ });
419
+ return true;
420
+ };
421
+
422
+ const unregisterPresented = registerStillFramePresenter(canvas, renderScene);
423
+
424
+ // THE PAUSED CLOCK (see this module's header): draw, never advance. A frame
425
+ // costs one render of a still stage and stops the moment the tab is hidden,
426
+ // because that is what `requestAnimationFrame` does.
427
+ let frame = 0;
428
+ let disposed = false;
429
+ let firstFrameReported = false;
430
+ const draw = (): void => {
431
+ frame = requestAnimationFrame(draw);
432
+ if (disposed) return;
433
+ try {
434
+ const rendered = renderScene();
435
+ if (rendered && !firstFrameReported) {
436
+ firstFrameReported = true;
437
+ onFirstFrame?.();
438
+ }
439
+ } catch (error) {
440
+ cancelAnimationFrame(frame);
441
+ frame = 0;
442
+ editorConsole.error(
443
+ `[canvas-design] world "${worldId}" failed to render — the design view is frozen: ${String(error)}`,
444
+ 'authoring',
445
+ );
446
+ }
447
+ };
448
+ frame = requestAnimationFrame(draw);
449
+
450
+ // Open on the authored WORLD, not on the declared output rectangle. Content
451
+ // may arrive after an atlas resolves, so keep a short frame window open
452
+ // until real bounds exist. Any user navigation cancels it immediately.
453
+ //
454
+ // ONCE PER VIEW. The world remounts whenever its source changes (every committed gesture writes
455
+ // source), and a view the person has already been looking at keeps its pose across that: a
456
+ // resize that re-framed the whole scene on commit moved everything under the pointer.
457
+ const reopening = openedViews.has(view);
458
+ openedViews.add(view);
459
+ const initialRect = layer.getBoundingClientRect();
460
+ if (!reopening && initialRect.width > 0 && initialRect.height > 0) {
461
+ view.setView(initialRect.width / 2, initialRect.height / 2, 1);
462
+ }
463
+ let openingFit = 0;
464
+ let openingAttempts = 0;
465
+ let openingCancelled = reopening;
466
+ const stopOpeningOnNavigation = view.subscribe(() => {
467
+ openingCancelled = true;
468
+ });
469
+ // …but never SMALLER than the player's own view of it. Fitting the whole
470
+ // world let a 1792×1280 floor open the survivor example at 26–34 %, where
471
+ // the 48 px hero is ~13 screen px on a busy grid: a tester read the scene as
472
+ // "only an infinite grid, nothing selectable" while the hero was drawn at
473
+ // the origin the whole time (runhuman pass 123; reproduced 2026-09-02). The
474
+ // declared resolution is what the player sees, so the zoom that frames IT
475
+ // is the floor: a world larger than the screen opens at screen scale,
476
+ // centred on the world; a smaller world still fills the pane as before.
477
+ // MEASURED after the first cut (which floored at the declared-resolution
478
+ // fit): the default dock leaves a 700×898 pane, the 1280×720 frame fits it
479
+ // at 47 %, and the 48 px hero was a 6 px speck — an improvement on 26 %
480
+ // and still a bare grid (build-45 gate, 2026-09-02). The player's view is
481
+ // 1:1 pixels, so the floor is 1:1: a big world opens at native scale,
482
+ // cropped to the pane around the world's centre — the hero at the origin,
483
+ // the floor art at its own size — and the author zooms out on purpose. A
484
+ // world that fits the pane at more than 1:1 still fills it, as before.
485
+ const declaredFitZoom = (): number => {
486
+ const rect = layer.getBoundingClientRect();
487
+ if (rect.width < 2 || rect.height < 2) return 1;
488
+ return Math.max(1, Math.min((rect.width - 96) / width, (rect.height - 96) / height));
489
+ };
490
+ const tryOpeningFit = (): void => {
491
+ if (openingCancelled || disposed || openingAttempts++ > 180) return;
492
+ const bounds = stage.getBounds();
493
+ if (fitSceneView(layer, bounds, view, declaredFitZoom())) return;
494
+ openingFit = requestAnimationFrame(tryOpeningFit);
495
+ };
496
+ openingFit = requestAnimationFrame(tryOpeningFit);
497
+
498
+ editorConsole.log(
499
+ `[canvas-design] world "${worldId}" mounted for design-time authoring (${entry})`,
500
+ 'authoring',
501
+ );
502
+
503
+ return {
504
+ adapter,
505
+ dispose(): void {
506
+ disposed = true;
507
+ cancelAnimationFrame(frame);
508
+ cancelAnimationFrame(openingFit);
509
+ stopOpeningOnNavigation();
510
+ resizeObserver.disconnect();
511
+ unregisterPresented();
512
+ adapter.dispose();
513
+ unsubscribeSystemAdapters?.();
514
+ setActiveSystems(null);
515
+ try {
516
+ mounted.dispose();
517
+ } catch (error) {
518
+ editorConsole.error(`[canvas-design] dispose failed: ${String(error)}`, 'authoring');
519
+ }
520
+ canvas.remove();
521
+ game.dispose();
522
+ },
523
+ };
524
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * THE CANVAS MEDIUM'S DESIGN-TIME MOUNT, as the SDK's design-time mount
3
+ * registry asks for it — the layer-shaped wrapper around
4
+ * `canvas-design-mount.ts`, registered by
5
+ * `contributions/canvas/design-time-mount.service.ts`. The design-time layer
6
+ * stack (`@volter/editor-sdk/kit/authoring/design-time-layers`) owns the
7
+ * failure, teardown and Play lifecycle for every medium.
8
+ */
9
+
10
+ import type {
11
+ DesignTimeRootDescriptor,
12
+ LayerMountResult,
13
+ } from '@volter/editor-sdk/kit/authoring/design-time-layers';
14
+ import type { DesignTimeMountContext } from '@volter/editor-sdk/kit/authoring/design-time-mount-registry';
15
+ import { connectSourceFileEvents } from '@volter/editor-sdk/kit/asset-events';
16
+ import { recordViewportFirstFrame } from '@volter/editor-sdk/kit/viewport-activation-timings';
17
+ import { threeStoreForHost } from '@volter/editor-threejs/kit/three-state';
18
+
19
+ /**
20
+ * A canvas world mounts its own `@pixi/react` `<Application>` (or its
21
+ * Babylon equivalent) into this layer and is never ticked — see
22
+ * `canvas-design-mount.ts` for the paused clock and the disabled input.
23
+ *
24
+ * `canvas-design-mount.ts` is imported dynamically so `pixi.js`/`@pixi/react`
25
+ * enter a bundle only for a project that actually declares a canvas root. It
26
+ * has been that way since the dispatch was a ternary in the host, and it is
27
+ * the reason the contribution module beside this one stays a handful of lines.
28
+ */
29
+ export async function mountCanvasDesignTimeLayer(
30
+ candidate: DesignTimeRootDescriptor,
31
+ layer: HTMLElement,
32
+ context: DesignTimeMountContext,
33
+ ): Promise<LayerMountResult> {
34
+ const { mountCanvasDesignLayer } = await import('./canvas-design-mount');
35
+ const { projectRootPath, view, activationDocumentId } = context;
36
+ // The Pixi authoring adapter edits through the Three store's selection and
37
+ // history, the same store Play's canvas adapter is handed.
38
+ const store = threeStoreForHost();
39
+ if (!store) throw new Error(`canvas root "${candidate.worldId}": the editor store is not bound yet.`);
40
+ const mounted = await mountCanvasDesignLayer(
41
+ candidate.worldId,
42
+ candidate.path,
43
+ layer,
44
+ projectRootPath,
45
+ store,
46
+ view,
47
+ activationDocumentId ? () => recordViewportFirstFrame(activationDocumentId) : undefined,
48
+ );
49
+ return { adapter: mounted.adapter, dispose: () => mounted.dispose() };
50
+ }
51
+
52
+ /**
53
+ * ABSORB-BY-REMOUNT (`DesignTimeMount.remountWhen`): a canvas world is re-executed from its source
54
+ * whenever a project source module changes, whoever wrote it. Nothing else re-runs it: the design
55
+ * layer's entry is imported once per mount, the packaged editor has no Vite client to deliver Fast
56
+ * Refresh, and an external edit to the world left the scene (and every value the Inspector and a
57
+ * gesture read from it) on the old source until the document was reopened. The server's source
58
+ * watcher reports every `src/` module change as `source-files-changed` (a hosted project with no
59
+ * path reports `''`, which still means something changed).
60
+ *
61
+ * REMOUNT, not re-project: a canvas layer's content is one native `<Application>` the disposer must
62
+ * tear down before a second one draws into the same box.
63
+ */
64
+ export function remountWhenSourceIsWritten(invalidate: () => void): () => void {
65
+ let timer: ReturnType<typeof setTimeout> | null = null;
66
+ const unwatch = connectSourceFileEvents((path) => {
67
+ if (path !== '' && !/^src\/.+\.(?:[cm]?tsx?|jsx?)$/.test(path)) return;
68
+ if (timer !== null) clearTimeout(timer);
69
+ timer = setTimeout(() => {
70
+ timer = null;
71
+ invalidate();
72
+ }, 150);
73
+ });
74
+ return () => {
75
+ if (timer !== null) clearTimeout(timer);
76
+ timer = null;
77
+ unwatch();
78
+ };
79
+ }
@@ -9,11 +9,11 @@ import {
9
9
  type CapabilityCoverageReport,
10
10
  deriveCapabilityCoverage,
11
11
  } from '../host/coverage/capability-coverage';
12
- import { projectAdapterFacet } from '@volter/editor-core/project-adapter';
13
- import { getCurrentProject } from '@volter/editor-core/project-manager';
14
- import { authoringSurfaceFromTable } from '@volter/editor-core/scene-document-plan';
15
- import { projectStoriesReady } from '@volter/editor-core/stories/story-registry';
16
- import { availableWorkspaceDocuments } from '@volter/editor-core/workspace-available-documents';
12
+ import { projectAdapterFacet } from '@volter/editor-sdk/kit/project-adapter';
13
+ import { getCurrentProject } from '@volter/editor-sdk/kit/active-project';
14
+ import { authoringSurfaceFromTable } from '@volter/editor-sdk/kit/scene-document-plan';
15
+ import { projectStoriesReady } from '@volter/editor-sdk/kit/stories/story-registry';
16
+ import { availableWorkspaceDocuments } from '@volter/editor-sdk/kit/workspace-available-documents';
17
17
  import { openWorkspaceDocuments } from '@volter/editor-sdk/kit/workspace-document-registry';
18
18
 
19
19
  export function authoringSurfaceCoverage(): CapabilityCoverageReport | null {
@@ -39,7 +39,7 @@ import {
39
39
  type ProjectVerbFacts,
40
40
  } from '../host/coverage/project-verb-coverage';
41
41
  import { editorServerJson } from '@volter/editor-sdk/kit/editor-server-response';
42
- import { getCurrentProject } from '@volter/editor-core/project-manager';
42
+ import { getCurrentProject } from '@volter/editor-sdk/kit/active-project';
43
43
 
44
44
  /** What the server answers on `/__editor/project-verbs` — facts only. */
45
45
  interface ProjectFileFacts {
@@ -28,17 +28,17 @@
28
28
  * for the mounts that have no contract to read.
29
29
  */
30
30
 
31
- import { getActiveSystems } from '@volter/editor-core/authoring/active-systems';
32
- import { mountedRootSubjects } from '@volter/editor-core/authoring/mounted-root-subjects';
31
+ import { getActiveSystems } from '@volter/editor-sdk/kit/authoring/active-systems';
32
+ import { mountedRootSubjects } from '@volter/editor-sdk/kit/authoring/mounted-root-subjects';
33
33
  import {
34
34
  type CapabilityCoverageReport,
35
35
  deriveCapabilityCoverage,
36
36
  } from '../host/coverage/capability-coverage';
37
37
  import { measureNativeSystemAdapters } from '../host/coverage/system-adapter-coverage';
38
38
  import { liveCoverage } from '@volter/editor-sdk/kit/live-session-registry';
39
- import { getCurrentProject } from '@volter/editor-core/project-manager';
40
- import { toolContributionPlay } from '@volter/editor-core/tool-contribution-play';
41
- import type { DeclaredSystemAbsence, Game } from '@volter/game-runtime/runtime/game';
39
+ import { getCurrentProject } from '@volter/editor-sdk/kit/active-project';
40
+ import { toolContributionPlay } from '@volter/editor-sdk/kit/tool-contribution-play';
41
+ import type { DeclaredSystemAbsence, Game } from '../runtime/game';
42
42
  import { projectDependencyNames } from './live-project-verbs';
43
43
 
44
44
  /**
@@ -36,7 +36,7 @@ import { measureAdapter } from '../host/adapter-reach';
36
36
  import {
37
37
  type MountedRootSubject,
38
38
  mountedRootSubjects,
39
- } from '@volter/editor-core/authoring/mounted-root-subjects';
39
+ } from '@volter/editor-sdk/kit/authoring/mounted-root-subjects';
40
40
  import {
41
41
  type CapabilityCoverageReport,
42
42
  deriveCapabilityCoverage,