@vgai/engine 0.5.2 → 0.5.4

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 (185) hide show
  1. package/README.md +18 -11
  2. package/package.json +10 -6
  3. package/schemas/engine-api.json +1 -68
  4. package/schemas/engine-api.md +1 -32
  5. package/schemas/engine-capabilities.json +30 -42
  6. package/schemas/{vgai-game.schema.json → vgai-project.schema.json} +52 -34
  7. package/src/adapter/adapter-surface.ts +5 -5
  8. package/src/adapter/authoring.ts +168 -226
  9. package/src/adapter/colyseus-networking-adapter.ts +44 -5
  10. package/src/adapter/first-party-systems.ts +156 -42
  11. package/src/adapter/host-context.ts +177 -46
  12. package/src/adapter/index.ts +47 -51
  13. package/src/adapter/ingest/game-contract.ts +2 -2
  14. package/src/adapter/ingest/scene-capture.ts +18 -19
  15. package/src/adapter/ingest/structural-ids.ts +127 -0
  16. package/src/adapter/ingest/upstream-pin.ts +9 -12
  17. package/src/adapter/loop-gate-report.ts +11 -11
  18. package/src/adapter/rapier-physics-adapter.ts +27 -9
  19. package/src/adapter/root-adapter.ts +217 -0
  20. package/src/adapter/{vgai-scene-game-adapter.ts → setup-three-root-adapter.ts} +173 -351
  21. package/src/adapter/system-adapter.ts +80 -63
  22. package/src/ai/navigation.ts +1 -1
  23. package/src/animation/animation-clock.ts +1 -1
  24. package/src/animation/camera-ownership.ts +1 -2
  25. package/src/animation/cubic-spline-interpolant.ts +132 -0
  26. package/src/animation/theatre-clock-binding.ts +2 -2
  27. package/src/animation/theatre-object-binding.ts +4 -4
  28. package/src/animation/xstate-animation-binding.ts +75 -5
  29. package/src/{scene/schema → asset-formats}/camera.ts +2 -5
  30. package/src/{scene/schema → asset-formats}/collider.ts +2 -5
  31. package/src/asset-formats/index.ts +54 -0
  32. package/src/{scene/schema → asset-formats}/instances.ts +10 -6
  33. package/src/{scene/schema → asset-formats}/light.ts +3 -6
  34. package/src/{scene/schema → asset-formats}/material.ts +4 -7
  35. package/src/{scene/schema → asset-formats}/mesh.ts +4 -7
  36. package/src/asset-formats/parse.ts +39 -0
  37. package/src/{scene/schema → asset-formats}/particles.ts +3 -6
  38. package/src/{scene/schema/environment.ts → asset-formats/render-env.ts} +23 -86
  39. package/src/{scene/schema → asset-formats}/tuples.ts +1 -1
  40. package/src/{scene/asset-loaders.ts → asset-loaders.ts} +8 -9
  41. package/src/asset-parse-error.ts +33 -0
  42. package/src/{scene/asset-registry.ts → asset-registry.ts} +1 -1
  43. package/src/assets.ts +1 -1
  44. package/src/audio/wav-encode.ts +9 -9
  45. package/src/canvas-react/engine-bridge.ts +59 -0
  46. package/src/canvas-react/index.ts +50 -0
  47. package/src/canvas-react/pixi-primitive.tsx +202 -0
  48. package/src/canvas-react/pixi-react-adapter.tsx +290 -0
  49. package/src/canvas-react/pixi-react-root-factory.tsx +88 -0
  50. package/src/canvas-react/world-context.ts +328 -0
  51. package/src/core/frame-pacing.ts +100 -0
  52. package/src/core/game-loop.ts +50 -28
  53. package/src/core/seeded-random.ts +7 -7
  54. package/src/core/sim-clock.ts +388 -0
  55. package/src/core/system-runner.ts +17 -63
  56. package/src/core/types.ts +34 -15
  57. package/src/data/data-asset.ts +3 -3
  58. package/src/data/data-check-core.ts +6 -7
  59. package/src/data/data-ref.ts +11 -11
  60. package/src/data/vite-plugin-data.ts +10 -10
  61. package/src/{scene/defaults.ts → defaults.ts} +18 -40
  62. package/src/dev/render-debug-adapter.ts +1 -1
  63. package/src/dev/webgl-frame-capture.ts +1 -1
  64. package/src/ecs/scene-index.ts +439 -0
  65. package/src/ecs/scene-query.ts +43 -0
  66. package/src/{scene → ecs}/user-data.ts +17 -36
  67. package/src/index.ts +7 -9
  68. package/src/input/input-manager.ts +29 -32
  69. package/src/input/input-types.ts +2 -2
  70. package/src/input/schema.ts +5 -5
  71. package/src/loader.ts +57 -0
  72. package/src/manifest/editor-port.ts +69 -0
  73. package/src/manifest/filename.ts +49 -0
  74. package/src/manifest/index.ts +8 -2
  75. package/src/manifest/load-file.ts +11 -0
  76. package/src/manifest/load.ts +65 -77
  77. package/src/manifest/locate.ts +55 -0
  78. package/src/manifest/schema.ts +400 -233
  79. package/src/{scene → physics}/collider-dimensions.ts +3 -3
  80. package/src/physics/physics-registry.ts +1 -1
  81. package/src/{world2d/authoring-2d.ts → pixi/authoring.ts} +24 -11
  82. package/src/pixi/index.ts +43 -0
  83. package/src/{world2d/ingest-iframe-2d.ts → pixi/ingest-iframe.ts} +9 -9
  84. package/src/{world2d/ingest2d.ts → pixi/ingest.ts} +28 -28
  85. package/src/{world2d/physics2d-registry.ts → pixi/physics-registry.ts} +1 -1
  86. package/src/{world2d/scene-capture-2d.ts → pixi/scene-capture.ts} +3 -3
  87. package/src/{world2d/system-adapters-2d.ts → pixi/system-adapters.ts} +2 -2
  88. package/src/react/unmanaged-root-detector.ts +26 -1
  89. package/src/react/use-data.ts +5 -5
  90. package/src/react/use-selection.tsx +15 -42
  91. package/src/react/{game-state.tsx → world-state.tsx} +44 -46
  92. package/src/render/auto-batcher.ts +1 -2
  93. package/src/{scene → render}/instance-mesh.ts +1 -1
  94. package/src/{scene → render}/light-camera-factory.ts +14 -13
  95. package/src/render/lod.ts +17 -0
  96. package/src/{scene → render}/material-factory.ts +8 -7
  97. package/src/{scene → render}/particles-factory.ts +62 -12
  98. package/src/render/render-batch-system.ts +14 -41
  99. package/src/render/render-features.ts +1 -1
  100. package/src/render/render-settings.ts +1 -2
  101. package/src/render/spark-renderer-lifecycle.ts +1 -1
  102. package/src/runtime/create-runtime.ts +352 -463
  103. package/src/runtime/debug-bridge.ts +148 -93
  104. package/src/runtime/debug-registry.ts +79 -54
  105. package/src/runtime/dev-layers.ts +40 -0
  106. package/src/runtime/frame-selector-cache.ts +4 -4
  107. package/src/runtime/game.ts +618 -369
  108. package/src/runtime/gameplay-rng-trap.ts +6 -7
  109. package/src/runtime/input-router.ts +11 -11
  110. package/src/runtime/mount-game.ts +54 -55
  111. package/src/runtime/mount-manifest.ts +154 -150
  112. package/src/runtime/presentation.ts +141 -0
  113. package/src/runtime/render-audio-control.ts +64 -53
  114. package/src/runtime/render-control.ts +45 -51
  115. package/src/runtime/render-seed.ts +3 -4
  116. package/src/runtime/state-bridge.ts +17 -18
  117. package/src/runtime/types.ts +94 -61
  118. package/src/setup/setup-renderer.ts +11 -6
  119. package/src/world3d-react/engine-bridge.ts +46 -33
  120. package/src/world3d-react/index.ts +31 -26
  121. package/src/world3d-react/r3f-adapter.tsx +211 -96
  122. package/src/world3d-react/r3f-root-factory.tsx +91 -0
  123. package/src/world3d-react/renderer-config.ts +137 -0
  124. package/src/world3d-react/world-context.ts +141 -111
  125. package/schemas/entity2d.schema.json +0 -468
  126. package/schemas/prefab.schema.json +0 -9992
  127. package/schemas/scn2d.schema.json +0 -494
  128. package/schemas/vscn.schema.json +0 -10851
  129. package/src/adapter/game-adapter.ts +0 -164
  130. package/src/adapter/ingest/overlay-applier.ts +0 -207
  131. package/src/adapter/ingest/overlay-apply.ts +0 -168
  132. package/src/adapter/ingest/overlay-file.ts +0 -126
  133. package/src/adapter/ingest/overlay-report.ts +0 -176
  134. package/src/animation/gsap-registration.ts +0 -184
  135. package/src/audio/audio-introspection.ts +0 -290
  136. package/src/audio/index.ts +0 -39
  137. package/src/audio/tone-clock-binding.ts +0 -98
  138. package/src/audio/tone-context.ts +0 -175
  139. package/src/audio/tone-offline-render.ts +0 -167
  140. package/src/ecs/component-manager.ts +0 -814
  141. package/src/ecs/game-component.ts +0 -260
  142. package/src/ecs/hmr-swap-report.ts +0 -65
  143. package/src/physics/trigger-dispatch.ts +0 -97
  144. package/src/react/root-adapter.tsx +0 -49
  145. package/src/scene/asset-paths.ts +0 -121
  146. package/src/scene/asset-ref-check.ts +0 -248
  147. package/src/scene/component-registry.ts +0 -51
  148. package/src/scene/parse.ts +0 -204
  149. package/src/scene/scene-apply.ts +0 -407
  150. package/src/scene/scene-diff-schema.ts +0 -115
  151. package/src/scene/scene-diff-types.ts +0 -29
  152. package/src/scene/scene-loader.ts +0 -1526
  153. package/src/scene/scene-query.ts +0 -63
  154. package/src/scene/scene-types.ts +0 -33
  155. package/src/scene/scene-version.ts +0 -40
  156. package/src/scene/schema/animation.ts +0 -46
  157. package/src/scene/schema/audio.ts +0 -25
  158. package/src/scene/schema/entity-ref.ts +0 -78
  159. package/src/scene/schema/entity.ts +0 -189
  160. package/src/scene/schema/index.ts +0 -51
  161. package/src/scene/schema/joint.ts +0 -26
  162. package/src/scene/schema/physics.ts +0 -49
  163. package/src/scene/schema/scene-file.ts +0 -292
  164. package/src/scene/schema/shadow.ts +0 -24
  165. package/src/scene/schema/spline.ts +0 -21
  166. package/src/world2d/asset-paths2d.ts +0 -44
  167. package/src/world2d/capture-to-scene2d.ts +0 -52
  168. package/src/world2d/collision-2d.ts +0 -99
  169. package/src/world2d/entity2d-asset.ts +0 -22
  170. package/src/world2d/index.ts +0 -91
  171. package/src/world2d/physics2d-transform.ts +0 -173
  172. package/src/world2d/physics2d-units.ts +0 -10
  173. package/src/world2d/pixi-game-adapter.ts +0 -439
  174. package/src/world2d/pixi-surface.ts +0 -78
  175. package/src/world2d/scene2d-identity.ts +0 -49
  176. package/src/world2d/scene2d-loader.ts +0 -433
  177. package/src/world2d/schema/entity2d.ts +0 -163
  178. package/src/world2d/schema/physics2d.ts +0 -64
  179. package/src/world2d/schema/sprite.ts +0 -99
  180. package/src/world2d/schema/tilemap.ts +0 -39
  181. package/src/world2d/schema/tuples2d.ts +0 -25
  182. package/src/world2d/transform-writer-2d.ts +0 -42
  183. package/src/world2d/types.ts +0 -74
  184. package/src/world3d-react/behavior.tsx +0 -146
  185. /package/src/{scene → render}/mesh-shadow.ts +0 -0
@@ -8,7 +8,7 @@
8
8
  * interface and the advertised `capabilities` — it never branches on which
9
9
  * implementer it is talking to.
10
10
  *
11
- * The interface lives in the engine (not the editor) so `MountedGame.authoring`
11
+ * The interface lives in the engine (not the editor) so `MountedThreeRoot.authoring`
12
12
  * can reference it without the engine depending on the editor; implementers live
13
13
  * in `packages/editor/src/authoring/`.
14
14
  *
@@ -21,39 +21,34 @@
21
21
  */
22
22
 
23
23
  import type * as THREE from 'three';
24
- import type { SceneEntity } from '../scene/scene-types';
25
- import type { Transform, TransformOwner } from './transform';
24
+ import type { Transform } from './transform';
26
25
 
27
- /** Booleans the editor UI gates affordances on (hide what an adapter can't do). */
26
+ /**
27
+ * Booleans the editor UI gates affordances on (hide what an adapter can't do).
28
+ *
29
+ * Deliberately SMALL: a capability flag earns its place only when the shell
30
+ * actually branches on it. Structural affordances (create/delete/reparent) are
31
+ * gated by `structure` PROVIDER PRESENCE instead — the flag and the provider
32
+ * cannot disagree that way. (WO-2 deleted `material`/`create`/`delete`/
33
+ * `reparent`/`layout`, which no shell code ever read.)
34
+ */
28
35
  export interface AuthoringCapabilities {
29
36
  transform: boolean;
30
- material: boolean;
31
37
  inspectorFields: boolean;
32
- create: boolean;
33
- delete: boolean;
34
- reparent: boolean;
35
38
  persist: boolean;
36
- /**
37
- * UI layout authoring (anchors/offsets/pivot/flex) — distinct from 3D
38
- * `transform`. Optional so existing 3D adapters need not declare it. A UI
39
- * adapter sets this true and implements {@link LayoutProvider}; the 3D gizmo /
40
- * TransformProvider are NOT the UI manipulation path (spec K7).
41
- */
42
- layout?: boolean;
43
39
  }
44
40
 
45
41
  /**
46
42
  * The adapter's own answer to "what is truth behind these rows, and how
47
- * writable is it" (spec 29 §5, docs/unified-world-editor/
48
- * 29-hierarchy-philosophy.md). Provenance never varies WITHIN one adapter's
43
+ * writable is it" (spec 29 §5). Provenance never varies WITHIN one adapter's
49
44
  * rows — it is a property of the world boundary — so it is declared ONCE per
50
45
  * adapter and rendered at the SEAM (the composite's `world:<id>` group row,
51
46
  * or the panel header for a bare adapter), never per row. Only the adapter
52
47
  * knows its truth: the shell must read this field, never guess from adapter
53
- * identity (rule zero). Machine-readable operation gates stay in
54
- * {@link AuthoringCapabilities}/provider presence — this type carries the
55
- * HUMAN explanation those gates can point at when an affordance is present
56
- * but unavailable.
48
+ * identity (rule zero). Machine-readable operation gates stay in {@link
49
+ * AuthoringCapabilities}/provider presence — this type carries the HUMAN
50
+ * explanation those gates can point at when an affordance is present but
51
+ * unavailable.
57
52
  */
58
53
  export interface AuthoringProvenance {
59
54
  /**
@@ -106,21 +101,16 @@ export interface EditorNode {
106
101
  * as `<header>` beneath a React component name or a document's file path. */
107
102
  secondaryLabel?: string;
108
103
  kind: 'mesh' | 'light' | 'camera' | 'group' | 'object' | (string & {});
104
+ /**
105
+ * What to CALL this node's type in the inspector header — the component or
106
+ * class an author thinks of it as, when that differs from the native `kind`
107
+ * the shell uses to pick an icon. An `<Enemy>` renders a `group`, but "group"
108
+ * is a fact about its implementation, not its identity; Godot names the class
109
+ * here and Unity names the prefab. Adapters that omit it keep showing `kind`.
110
+ */
111
+ typeLabel?: string;
109
112
  parentId: string | null;
110
113
  childIds: string[];
111
- flags: {
112
- runtimeOnly?: boolean;
113
- locked?: boolean;
114
- transformOwner?: TransformOwner;
115
- /** D8 — member of a RootGroup, not the active one; children may be unloaded. */
116
- inactiveRoot?: boolean;
117
- /** Design state currently projected into the live authoring surface. */
118
- active?: boolean;
119
- /** The adapter resolved this boundary, but it is not editable here. */
120
- readOnly?: boolean;
121
- /** The adapter could not resolve the declared authoring boundary. */
122
- error?: boolean;
123
- };
124
114
  }
125
115
 
126
116
  export interface PropertyDescriptor {
@@ -130,25 +120,64 @@ export interface PropertyDescriptor {
130
120
  readonly?: boolean;
131
121
  options?: unknown[];
132
122
  /**
133
- * Optional domain-shaped grouping (T3.4 slice 2, docs/ADAPTER-AUTHORING-
134
- * DESIGN.md §1.E): properties sharing the same `group` label render
135
- * together under a titled sub-section in the generic inspector, instead of
136
- * the flat property grid. Backward-compatible omitted (or two
137
- * descriptors with different/no `group`) renders exactly as before this
138
- * field existed. This is the ONLY authoring-richness addition v1 makes;
139
- * there is no schema and no new PropertyDescriptor `type` — a custom
140
- * adapter reports its own domain concepts (health/score/an enum, …) through
141
- * the SAME provider, merely labeled into a named group.
123
+ * Optional domain-shaped grouping (T3.4 slice 2): properties sharing the
124
+ * same `group` label render together under a titled sub-section in the
125
+ * generic inspector, instead of the flat property grid. Backward-compatible
126
+ * omitted (or two descriptors with different/no `group`) renders exactly
127
+ * as before this field existed. This is the ONLY authoring-richness
128
+ * addition v1 makes; there is no schema and no new PropertyDescriptor
129
+ * `type` a custom adapter reports its own domain concepts
130
+ * (health/score/an enum, …) through the SAME provider, merely labeled into
131
+ * a named group.
142
132
  */
143
133
  group?: string;
134
+ /**
135
+ * The value shown is the DECLARED DEFAULT — the document does not carry this
136
+ * property, the thing that owns it does. Every mainstream inspector
137
+ * distinguishes these (Unity greys an unmodified field and bolds an
138
+ * overridden one) because otherwise you cannot tell what a document actually
139
+ * says from what it merely inherits. Editing a defaulted property writes it
140
+ * into the document for the first time.
141
+ */
142
+ defaulted?: boolean;
143
+ /**
144
+ * This property can be REMOVED from the document, reverting it to the
145
+ * declared default — Unity's revert arrow, Godot's reset arrow. Only set when
146
+ * removal is legal: the owner declares the property optional (or gives it a
147
+ * default), so dropping it leaves a still-valid document. The inverse of
148
+ * editing a `defaulted` property.
149
+ */
150
+ resettable?: boolean;
151
+ /**
152
+ * The SOURCE TEXT of the default a revert returns this property to (`1.55`,
153
+ * `'raider'`), so the revert affordance can name its destination instead of
154
+ * saying "default" and hoping.
155
+ *
156
+ * Set ONLY when the owner actually declares a default the adapter can read.
157
+ * Absence is the honest, common case: an optional property with no declared
158
+ * default is still removable — what takes over is then the owner's own
159
+ * internal fallback or nothing at all, which this side does not know. Never
160
+ * fill it with the property's CURRENT value: that is what the author is
161
+ * discarding, not what they get back.
162
+ */
163
+ revertsTo?: string;
144
164
  }
145
165
 
146
166
  export interface HierarchyProvider {
147
167
  roots(): EditorNode[];
148
168
  node(id: string): EditorNode | null;
149
- /** The live object for raycast/gizmo binding (null for non-object nodes). */
150
- object3D(id: string): THREE.Object3D | null;
151
- idForObject3D(o: THREE.Object3D): string | null;
169
+ /**
170
+ * OPTIONAL, THREE-SPECIFIC — the live `Object3D` for raycast/gizmo binding
171
+ * (null for a node this adapter has no object for). A Pixi, DOM or React
172
+ * adapter has no `Object3D` at all: it OMITS this method rather than
173
+ * implementing a `() => null` stub that pretends the concept applies. Callers
174
+ * already treat `null` as "no object here", so absence and `null` mean the
175
+ * same thing to them — optional-chain it (`hierarchy.object3D?.(id) ?? null`).
176
+ */
177
+ object3D?(id: string): THREE.Object3D | null;
178
+ /** OPTIONAL, THREE-SPECIFIC — inverse of {@link object3D}. Same rule: an
179
+ * adapter with no `Object3D` tree omits it. */
180
+ idForObject3D?(o: THREE.Object3D): string | null;
152
181
  }
153
182
 
154
183
  export interface SelectionProvider {
@@ -163,17 +192,13 @@ export interface SelectionProvider {
163
192
  ): SelectionResolution | null;
164
193
  }
165
194
 
166
- /** Adapter-owned result of semantic selection resolution. Separate ids keep
167
- * selection, inspection, and manipulation from becoming accidental aliases. */
195
+ /** Adapter-owned result of semantic selection resolution: the raw native/render
196
+ * hit mapped to the node the author actually means. (The former
197
+ * `inspectorId`/`transformId`/`boundaryId` aliases were never read by the
198
+ * shell — deleted in WO-2.) */
168
199
  export interface SelectionResolution {
169
- /** Hierarchy selection and default Inspector subject. */
200
+ /** Hierarchy selection and Inspector subject. */
170
201
  id: string;
171
- /** Optional alternate Inspector subject. Defaults to {@link id}. */
172
- inspectorId?: string;
173
- /** Native transform subject. `null` means the selection is non-spatial. */
174
- transformId?: string | null;
175
- /** Nearest closed component/prefab/assembly boundary, when any. */
176
- boundaryId?: string | null;
177
202
  }
178
203
 
179
204
  export type TransformChannel = 'position' | 'rotation' | 'scale';
@@ -188,7 +213,6 @@ export interface TransformProvider {
188
213
  /** Honest inspector presentation for the substrate's transform vocabulary. */
189
214
  dimensions?(id: string): '2d' | '3d' | null;
190
215
  get(id: string): Transform;
191
- owner(id: string): TransformOwner;
192
216
  /** Pre-gesture persistence check. Omitted means the adapter's transform
193
217
  * capability applies uniformly. A false result must suppress manipulation
194
218
  * before the live object is moved, never discover refusal at drag end. */
@@ -199,6 +223,17 @@ export interface TransformProvider {
199
223
  endEdit(id: string): void;
200
224
  }
201
225
 
226
+ /**
227
+ * One transform gesture event (see {@link AuthoringAdapter.observeTransforms}).
228
+ * `live` is a mid-gesture preview pose (the gizmo's live drag path); `commit`
229
+ * closes the gesture.
230
+ */
231
+ export interface TransformObservation {
232
+ readonly id: string;
233
+ readonly transform: Transform;
234
+ readonly phase: 'live' | 'commit';
235
+ }
236
+
202
237
  export interface InspectorProvider {
203
238
  /** Schema-driven — NOT fixed to `SceneEntity`. */
204
239
  properties(id: string): PropertyDescriptor[];
@@ -217,21 +252,33 @@ export interface InspectorProvider {
217
252
  }
218
253
 
219
254
  /**
220
- * UI layout authoring (K7) anchors/offsets/pivot/flex props on a UI node. This
221
- * is the manipulation path for the DOM-overlay UI editor (handles, anchor presets,
222
- * flex inspector) and is deliberately SEPARATE from {@link TransformProvider}
223
- * (which is 3D position/quaternion/scale and cannot express flex/anchor layout).
224
- * The layout shape is format-neutral (a plain object the UI adapter understands).
255
+ * An authored node that is itself an asset, even when its bytes are embedded
256
+ * in another source document instead of living at a standalone project path.
257
+ *
258
+ * The first concrete case is inline SVG in a DOM/React root: its `<path>` and
259
+ * `<g>` implementation is no more part of the authoring hierarchy than the
260
+ * meshes inside an imported model. The adapter owns the native-to-asset
261
+ * conversion; the shell only opens the returned image in its ordinary Asset
262
+ * Editor document.
225
263
  */
226
- export interface LayoutProvider {
227
- /** The node's current layout object (anchors/offsets/pivot/flex), or null. */
228
- get(id: string): Record<string, unknown> | null;
229
- /** Pause whatever drives layout (animation) before a gesture, if needed. */
230
- beginEdit(id: string): void;
231
- /** Apply a (partial) layout patch merged onto the node's layout. */
232
- apply(id: string, layoutPatch: Record<string, unknown>): void;
233
- /** Commit the gesture (one undo step). */
234
- endEdit(id: string): void;
264
+ export interface AuthoringAssetSubject {
265
+ readonly kind: 'image';
266
+ readonly name: string;
267
+ readonly mediaType: 'image/svg+xml';
268
+ readonly text: string;
269
+ /** Project-root-relative source definition, when the adapter can prove it. */
270
+ readonly sourcePath?: string;
271
+ }
272
+
273
+ export interface AssetSubjectProvider {
274
+ get(id: string): AuthoringAssetSubject | null;
275
+ /**
276
+ * Assets known to this authoring document even when they are not in its
277
+ * currently focused hierarchy projection. React story boards use this to
278
+ * expose SVGs mounted in inactive story frames without flattening those
279
+ * frames into the active hierarchy.
280
+ */
281
+ entries?(): ReadonlyArray<{ id: string; subject: AuthoringAssetSubject }>;
235
282
  }
236
283
 
237
284
  export interface StructureProvider {
@@ -300,21 +347,18 @@ export interface StructureProvider {
300
347
  }
301
348
 
302
349
  /**
303
- * PersistenceProvider — whether and where an adapter's edits persist (design:
304
- * `docs/PERSISTENCE-PROVIDER-DESIGN.md` §2). The ACTIVE adapter's provider is the
305
- * ONE place this is decided — the host (editor) never hard-codes a destination or
306
- * guards on session flags; it asks the provider.
350
+ * PersistenceProvider — whether and where an adapter's edits persist (design).
351
+ * The ACTIVE adapter's provider is the ONE place this is decided — the host
352
+ * (editor) never hard-codes a destination or guards on session flags; it asks the
353
+ * provider.
307
354
  */
308
355
  export interface PersistenceProvider {
309
356
  isDirty(): boolean;
310
357
  /** Save to the adapter's OWN source of truth (.vscn / overlay file / …). */
311
358
  save(): Promise<void>;
312
- serialize(): unknown;
313
- /** Migration aid (first-party direction): pull a subtree into `.vscn` entities. */
314
- captureToVscn?(ids: string[]): SceneEntity[];
315
359
  /**
316
360
  * Human/agent-readable destination this provider persists to — e.g.
317
- * `"scenes/main.vscn.json"`, `".vgai/overlays/fps.json"`, or `"ephemeral
361
+ * `"scenes/main.vscn.json"`, `"live-only (not saved)"`, or `"ephemeral
318
362
  * (discarded on stop)"`. Drives the save-status UI and makes routing
319
363
  * inspectable (design §2). Implementers whose destination can change during
320
364
  * the session (e.g. the first-party provider tracks the loaded `.vscn` path)
@@ -378,43 +422,21 @@ export interface StoriesProvider {
378
422
  isolate?(nodeId: string | null, storyId?: string): void;
379
423
  }
380
424
 
381
- /** D6 (S-C) — GameComponent management; config editing stays on InspectorProvider. */
382
- export interface ComponentsProvider {
383
- available(nodeId: string): string[];
384
- list(nodeId: string): { type: string }[];
385
- add(nodeId: string, type: string): void;
386
- remove(nodeId: string, type: string): void;
387
- /**
388
- * OPTIONAL read accessor (W3.1, docs/DATA-TOOLS-DESIGN.md §3.3): the raw
389
- * authored config record for one attached component (`list()` names the
390
- * types; this reads one type's per-entity data), or `null` when the entity
391
- * or component doesn't exist. Returns a shallow copy — callers must write
392
- * through `InspectorProvider.set('components.<Type>.<field>', v)`, never by
393
- * mutating this. Absent ⇒ config is not readable through this provider
394
- * (tools degrade to `config: null`).
395
- */
396
- config?(nodeId: string, type: string): Record<string, unknown> | null;
425
+ /**
426
+ * Where a drop landed, when the shell knows. The VIEWPORT knows a world point
427
+ * (its ground-plane raycast under the cursor); the hierarchy panel does not,
428
+ * and passes nothing. Optional both ways: an adapter with no placement concept
429
+ * ignores it, and a caller with no point omits it — so neither side has to
430
+ * pretend. Coordinates are world-space, in the adapter's own units.
431
+ */
432
+ export interface AssetDropContext {
433
+ readonly position?: readonly [number, number, number];
397
434
  }
398
435
 
399
436
  /** Asset drop (hierarchy + viewport). */
400
437
  export interface AssetDropProvider {
401
438
  accepts(nodeId: string, assetPath: string): boolean;
402
- drop(nodeId: string, assetPath: string): void | Promise<void>;
403
- }
404
-
405
- /** One adapter-owned reusable-entity format the shared hierarchy can save.
406
- * The adapter owns the bytes and semantics; the shell owns only the filename
407
- * dialog. This keeps native 2D entity assets out of Three prefab code while
408
- * preserving one editor language for the operation. */
409
- export interface EntityAssetSaveDescriptor {
410
- defaultFolder: string;
411
- extension: string;
412
- label: string;
413
- }
414
-
415
- export interface EntityAssetProvider {
416
- describeSave(nodeId: string): EntityAssetSaveDescriptor | null;
417
- save(nodeId: string, path: string): Promise<boolean>;
439
+ drop(nodeId: string, assetPath: string, context?: AssetDropContext): void | Promise<void>;
418
440
  }
419
441
 
420
442
  /** Plain-object rect shape shared by {@link RectProvider} — the same fields a real
@@ -488,106 +510,6 @@ export interface BoxEditProvider {
488
510
  referencePoint?(id: string): BoxEditReferencePoint | null;
489
511
  }
490
512
 
491
- export interface TilemapCell {
492
- column: number;
493
- row: number;
494
- }
495
-
496
- /** One bounded native tile grid projected into the shared world overlay.
497
- * Geometry is affine and host-relative: `origin` is local cell corner [0,0],
498
- * while `columnStep`/`rowStep` are the transformed one-cell basis vectors.
499
- * This compact shape represents nested rotation, non-uniform scale, reflection,
500
- * and zoom without fabricating axis-aligned cell polygons. */
501
- export interface TilemapEditGrid {
502
- columns: number;
503
- columnStep: { x: number; y: number };
504
- origin: { x: number; y: number };
505
- palette: ReadonlyArray<{ frame: string; index: number }>;
506
- rows: number;
507
- rowStep: { x: number; y: number };
508
- }
509
-
510
- /** Native tile-cell authoring. Palette selection is editor-session state;
511
- * source truth receives only atlas-frame indices, with -1 meaning an empty cell.
512
- * begin/apply/end bracket one exact project-history transaction. */
513
- export interface TilemapEditProvider {
514
- apply(id: string, cell: TilemapCell): void;
515
- begin(id: string, tileIndex: number): void;
516
- /** Resolve a host-relative pointer through the adapter's native transform. */
517
- cellAt(id: string, point: { x: number; y: number }): TilemapCell | null;
518
- end(id: string): void;
519
- grid(id: string): TilemapEditGrid | null;
520
- /** Load the native Pixi atlas used to render palette thumbnails. */
521
- loadTileset(id: string): Promise<SpriteAtlas2D>;
522
- }
523
-
524
- export type PhysicsShape2DHandle = 'half-height' | 'half-width' | 'radius';
525
-
526
- /** One native 2D physics shape projected into host-relative viewport pixels.
527
- * `origin` is collider-local [0,0]; `xAxis`/`yAxis` are the transformed
528
- * one-pixel basis vectors. Keeping the complete affine basis preserves nested
529
- * rotation, non-uniform scale, reflection, centered canvas placement, and
530
- * viewport zoom without teaching the shared shell about Pixi or Rapier. */
531
- export interface PhysicsShape2DProjection {
532
- collider:
533
- | { shape: 'cuboid'; halfExtents: readonly [number, number] }
534
- | { shape: 'ball'; radius: number }
535
- | { shape: 'capsule'; halfHeight: number; radius: number };
536
- editable: boolean;
537
- id: string;
538
- origin: { x: number; y: number };
539
- sensor: boolean;
540
- xAxis: { x: number; y: number };
541
- yAxis: { x: number; y: number };
542
- }
543
-
544
- /** Adapter-owned native collider inspection and spatial editing. Shapes are
545
- * readable independently of editability so locked/read-only source remains
546
- * inspectable. begin/apply/end bracket one exact project-history transaction. */
547
- export interface PhysicsShape2DProvider {
548
- apply(id: string, handle: PhysicsShape2DHandle, point: { x: number; y: number }): void;
549
- begin(id: string, handle: PhysicsShape2DHandle): void;
550
- end(id: string): void;
551
- shapes(): readonly PhysicsShape2DProjection[];
552
- }
553
-
554
- /** One named sub-texture in a Pixi-compatible spritesheet atlas. `x`/`y` are
555
- * source-image pixels; width/height are the runtime-facing dimensions Pixi
556
- * swaps when `rotated` reports packed 90-degree storage. */
557
- export interface SpriteAtlasFrame2D {
558
- height: number;
559
- name: string;
560
- rotated: boolean;
561
- width: number;
562
- x: number;
563
- y: number;
564
- }
565
-
566
- export interface SpriteAtlas2D {
567
- atlasPath: string;
568
- frames: readonly SpriteAtlasFrame2D[];
569
- /** Project-relative image source named by the atlas. */
570
- imagePath: string;
571
- /** Image bytes loaded through the owning adapter's storage backend. */
572
- imageBytes: Uint8Array;
573
- imageMediaType: string;
574
- }
575
-
576
- export interface SpriteFrames2DDescriptor {
577
- atlasPath: string;
578
- editable: boolean;
579
- selectedFrame: string | null;
580
- }
581
-
582
- /** Source-backed Pixi spritesheet discovery and frame selection. Atlas parsing
583
- * remains adapter-owned; the universal Inspector only renders this projection.
584
- * `select` writes source truth and creates one project-history transaction. */
585
- export interface SpriteFrames2DProvider {
586
- describe(id: string): SpriteFrames2DDescriptor | null;
587
- load(id: string): Promise<SpriteAtlas2D>;
588
- select(id: string, frame: string | null): void;
589
- }
590
-
591
513
  /** Text-content editing — currently OFF-contract (the react adapter has an `editText(id)`
592
514
  * method, react-world-authoring-adapter.ts:728, not reachable via the contract). Bring it
593
515
  * on so the overlay's double-click-to-edit-text (D3) is contract-driven. */
@@ -616,6 +538,27 @@ export interface ColorSampleProvider {
616
538
  backgroundChainAt(clientX: number, clientY: number): string[] | null;
617
539
  }
618
540
 
541
+ /**
542
+ * WO-IDX1 — where a node's live object was CREATED in the game's own source.
543
+ *
544
+ * `display` is the `file:line` the editor shows verbatim. The unanchored shape
545
+ * carries a `reason` rather than a blank, because "we don't know" and "a
546
+ * library built it" are different facts and neither may present as silence
547
+ * (the honest-floor rule).
548
+ */
549
+ export type NodeCreationSite =
550
+ | { anchored: true; file: string; line: number; col: number; display: string }
551
+ | { anchored: false; reason: string };
552
+
553
+ /**
554
+ * Absent ⇒ this adapter's substrate has no creation-site index at all (a
555
+ * document-backed world's truth is the document, not a construction site).
556
+ * Present ⇒ every id gets an answer, anchored or reasoned.
557
+ */
558
+ export interface CreationSiteProvider {
559
+ anchor(id: string): NodeCreationSite;
560
+ }
561
+
619
562
  export interface AuthoringAdapter {
620
563
  readonly capabilities: AuthoringCapabilities;
621
564
  /** Spec 29 §5 — seam-level provenance ("what is truth behind these rows").
@@ -626,8 +569,8 @@ export interface AuthoringAdapter {
626
569
  readonly selection?: SelectionProvider;
627
570
  readonly transforms?: TransformProvider;
628
571
  readonly inspector?: InspectorProvider;
629
- /** UI layout authoring path (K7) present on UI adapters, absent on 3D adapters. */
630
- readonly layout?: LayoutProvider;
572
+ /** The selected node is an atomic asset that can open in the Asset Editor. */
573
+ readonly assetSubject?: AssetSubjectProvider;
631
574
  readonly structure?: StructureProvider;
632
575
  readonly persistence?: PersistenceProvider;
633
576
  /** D8 — mutually-exclusive root groups (radio roots). Absent ⇒ no grouped roots. */
@@ -642,12 +585,6 @@ export interface AuthoringAdapter {
642
585
  /** T0 (spec 27 §2) — spatial drag-resize/move → source/data write for non-Object3D
643
586
  * (DOM) nodes. Absent ⇒ no box-edit gesture for this adapter. */
644
587
  readonly boxEdit?: BoxEditProvider;
645
- /** Native 2D physics debug geometry and shape handles. */
646
- readonly physicsShapes2D?: PhysicsShape2DProvider;
647
- /** Native Pixi spritesheet frame discovery/selection. */
648
- readonly spriteFrames2D?: SpriteFrames2DProvider;
649
- /** Native bounded tile-cell painting. Absent means no viewport tile editor. */
650
- readonly tilemapEdit?: TilemapEditProvider;
651
588
  /** T0 (spec 27 §2) — double-click-to-edit-text on the contract (wires the react
652
589
  * adapter's existing off-contract `editText`). Absent ⇒ no in-place text edit. */
653
590
  readonly text?: TextProvider;
@@ -656,16 +593,21 @@ export interface AuthoringAdapter {
656
593
  readonly colorSample?: ColorSampleProvider;
657
594
  /** D4 — storybook stories. Absent ⇒ no stories for any node in this adapter. */
658
595
  readonly stories?: StoriesProvider;
659
- /** D6 (S-C)GameComponent management. Absent no component authoring. */
660
- readonly components?: ComponentsProvider;
596
+ /** WO-IDX1the source location that constructed a node's live object.
597
+ * Absent this adapter indexes no creation sites. */
598
+ readonly creationSite?: CreationSiteProvider;
661
599
  /** Asset drop (hierarchy + viewport). Absent ⇒ this adapter accepts no drops. */
662
600
  readonly assetDrop?: AssetDropProvider;
663
- /** Save a native entity subtree as an adapter-owned reusable asset. */
664
- readonly entityAssets?: EntityAssetProvider;
665
- /** Undo/redo for this adapter's edits (first-party delegates to the store; an
666
- * ingest adapter keeps its own overlay-backed stack). Absent ⇒ host falls back. */
667
- undo?(): void;
668
- redo?(): void;
669
601
  /** Change notification → UI refresh. */
670
602
  subscribe?(listener: () => void): () => void;
603
+ /**
604
+ * Observe transform GESTURES on this adapter's nodes — the one seam the
605
+ * animation sequencer needs to record a drag as keyframes and to refresh its
606
+ * design baselines. Deliberately narrow (one event shape, one consumer, no
607
+ * general event bus): before it existed, the sequencer could only observe
608
+ * the first-party store's own `.vscn` transform path, so a live or
609
+ * source-backed world's gestures were invisible to it. Absent ⇒ this
610
+ * adapter reports no gestures.
611
+ */
612
+ observeTransforms?(listener: (event: TransformObservation) => void): () => void;
671
613
  }
@@ -19,6 +19,7 @@ import type {
19
19
  NetConditioning,
20
20
  NetMessageEvent,
21
21
  NetPeer,
22
+ NetPlayerIdentity,
22
23
  NetRates,
23
24
  NetworkingAdapter,
24
25
  ReplicationStats,
@@ -31,6 +32,16 @@ export interface ColyseusNetworkingConfig {
31
32
  localSessionId(): string | null;
32
33
  /** Peers currently in the room. */
33
34
  peers(): NetPeer[];
35
+ /**
36
+ * The game's own `id → Object3D` map (P-4). The SEAM
37
+ * (`NetworkingAdapter`) is keyed by node id so a non-three world can
38
+ * implement it at all; the accessors below stay `Object3D`-keyed because a
39
+ * three game already thinks in objects. This one member is the bridge, and
40
+ * it is the game's to declare — the engine never guesses an identity
41
+ * convention on a game's behalf. Returning `null` means "no such node here",
42
+ * and every accessor degrades exactly as it does for an unreplicated object.
43
+ */
44
+ resolve(nodeId: string): THREE.Object3D | null;
34
45
  /** Network id for an object, or null if it is not replicated (→ local authored). */
35
46
  networkId(o: THREE.Object3D): string | null;
36
47
  /** Owning peer/session id for a replicated object (null = server-owned). */
@@ -65,19 +76,36 @@ export interface ColyseusNetworkingConfig {
65
76
  * must be supplied together for the inspector to show the controls. */
66
77
  getConditioning?(): NetConditioning;
67
78
  setConditioning?(c: NetConditioning): void;
79
+ /** Optional capability: read the local player's identity off the game's own
80
+ * room state (→ adapter `getPlayerIdentity`, a seat label). Independent of
81
+ * the setter below — a game can expose a read-only identity. */
82
+ playerIdentity?(): NetPlayerIdentity | undefined;
83
+ /** Optional capability: set the local player's identity through the game's own
84
+ * mechanism (→ adapter `setPlayerIdentity`, an editable name field). Present
85
+ * only when the game supports a settable name; drives the game's real path
86
+ * (a join option the server honours, a rename message, …). */
87
+ setPlayerIdentity?(identity: NetPlayerIdentity): void | Promise<void>;
68
88
  }
69
89
 
70
90
  export function createColyseusNetworkingAdapter(cfg: ColyseusNetworkingConfig): NetworkingAdapter {
71
91
  const adapter: NetworkingAdapter = {
72
92
  peers: () => cfg.peers(),
73
- networkId: (o) => cfg.networkId(o),
74
- authority: (o) => {
75
- if (cfg.networkId(o) == null) return 'local'; // not replicated local authored
93
+ // The seam speaks node ids; `cfg.resolve` is the one crossing into Three's
94
+ // vocabulary. An unresolvable id behaves exactly like an unreplicated
95
+ // object local authority, editable rather than throwing or fabricating.
96
+ networkId: (nodeId) => {
97
+ const o = cfg.resolve(nodeId);
98
+ return o ? cfg.networkId(o) : null;
99
+ },
100
+ authority: (nodeId) => {
101
+ const o = cfg.resolve(nodeId);
102
+ if (!o || cfg.networkId(o) == null) return 'local'; // not replicated → local authored
76
103
  if (cfg.serverAuthoritative?.(o)) return 'server';
77
104
  return cfg.ownerId(o) === cfg.localSessionId() ? 'local' : 'remote';
78
105
  },
79
- editable: (o) => {
80
- if (cfg.networkId(o) == null) return true; // local authored object
106
+ editable: (nodeId) => {
107
+ const o = cfg.resolve(nodeId);
108
+ if (!o || cfg.networkId(o) == null) return true; // local authored object
81
109
  if (cfg.serverAuthoritative?.(o)) return false; // server owns it → inspect-only
82
110
  return cfg.ownerId(o) === cfg.localSessionId(); // only your own peer's objects
83
111
  },
@@ -102,5 +130,16 @@ export function createColyseusNetworkingAdapter(cfg: ColyseusNetworkingConfig):
102
130
  adapter.getConditioning = () => getConditioning();
103
131
  adapter.setConditioning = (c) => setConditioning(c);
104
132
  }
133
+ // Identity read and write are INDEPENDENTLY optional: a game may expose a
134
+ // read-only seat label (identity, no setter) or a settable name (both). The
135
+ // editor shows an editable field only when `setPlayerIdentity` is present.
136
+ const playerIdentity = cfg.playerIdentity?.bind(cfg);
137
+ if (playerIdentity) {
138
+ adapter.getPlayerIdentity = () => playerIdentity() ?? {};
139
+ }
140
+ const setPlayerIdentity = cfg.setPlayerIdentity?.bind(cfg);
141
+ if (setPlayerIdentity) {
142
+ adapter.setPlayerIdentity = (id) => setPlayerIdentity(id);
143
+ }
105
144
  return adapter;
106
145
  }