@vgai/engine 0.5.15 → 0.5.17

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 (220) hide show
  1. package/dist/adapter/adapter-module.d.ts +229 -33
  2. package/dist/adapter/adapter-module.d.ts.map +1 -1
  3. package/dist/adapter/adapter-module.js +222 -50
  4. package/dist/adapter/authoring-seam-contract.d.ts +530 -0
  5. package/dist/adapter/authoring-seam-contract.d.ts.map +1 -0
  6. package/dist/adapter/authoring-seam-contract.js +162 -0
  7. package/dist/adapter/authoring.d.ts +237 -50
  8. package/dist/adapter/authoring.d.ts.map +1 -1
  9. package/dist/adapter/authoring.js +2 -1
  10. package/dist/adapter/entrypoint-selection-source.d.ts +35 -0
  11. package/dist/adapter/entrypoint-selection-source.d.ts.map +1 -0
  12. package/dist/adapter/entrypoint-selection-source.js +151 -0
  13. package/dist/adapter/finders/scenes-from-entrypoint-selection.d.ts +7 -3
  14. package/dist/adapter/finders/scenes-from-entrypoint-selection.d.ts.map +1 -1
  15. package/dist/adapter/finders/scenes-from-entrypoint-selection.js +145 -27
  16. package/dist/adapter/first-party-systems.d.ts +13 -3
  17. package/dist/adapter/first-party-systems.d.ts.map +1 -1
  18. package/dist/adapter/first-party-systems.js +10 -3
  19. package/dist/adapter/index.d.ts +7 -2
  20. package/dist/adapter/index.d.ts.map +1 -1
  21. package/dist/adapter/index.js +4 -0
  22. package/dist/adapter/ingest/contract-system-adapters.d.ts +1 -1
  23. package/dist/adapter/ingest/contract-system-adapters.d.ts.map +1 -1
  24. package/dist/adapter/ingest/contract-system-adapters.js +13 -25
  25. package/dist/adapter/ingest/game-contract-seams.d.ts +214 -0
  26. package/dist/adapter/ingest/game-contract-seams.d.ts.map +1 -0
  27. package/dist/adapter/ingest/game-contract-seams.js +62 -0
  28. package/dist/adapter/ingest/game-contract.d.ts +5 -3
  29. package/dist/adapter/ingest/game-contract.d.ts.map +1 -1
  30. package/dist/adapter/ingest/game-contract.js +4 -1
  31. package/dist/adapter/ingest/merge-debug-adapters.d.ts +52 -0
  32. package/dist/adapter/ingest/merge-debug-adapters.d.ts.map +1 -0
  33. package/dist/adapter/ingest/merge-debug-adapters.js +141 -0
  34. package/dist/adapter/ingest/observation-debug-adapter.d.ts +56 -0
  35. package/dist/adapter/ingest/observation-debug-adapter.d.ts.map +1 -0
  36. package/dist/adapter/ingest/observation-debug-adapter.js +131 -0
  37. package/dist/adapter/ingest/scene-capture.d.ts +38 -13
  38. package/dist/adapter/ingest/scene-capture.d.ts.map +1 -1
  39. package/dist/adapter/ingest/scene-capture.js +101 -24
  40. package/dist/adapter/ingest/visible-capture-window.d.ts +22 -5
  41. package/dist/adapter/ingest/visible-capture-window.d.ts.map +1 -1
  42. package/dist/adapter/ingest/visible-capture-window.js +46 -7
  43. package/dist/adapter/native-debug-module.d.ts +43 -0
  44. package/dist/adapter/native-debug-module.d.ts.map +1 -0
  45. package/dist/adapter/native-debug-module.js +230 -0
  46. package/dist/adapter/object3d-authoring-subject.d.ts +32 -0
  47. package/dist/adapter/object3d-authoring-subject.d.ts.map +1 -0
  48. package/dist/adapter/object3d-authoring-subject.js +25 -0
  49. package/dist/adapter/root-adapter.d.ts +25 -56
  50. package/dist/adapter/root-adapter.d.ts.map +1 -1
  51. package/dist/adapter/root-adapter.js +4 -20
  52. package/dist/adapter/root-seam-contract.d.ts +129 -0
  53. package/dist/adapter/root-seam-contract.d.ts.map +1 -0
  54. package/dist/adapter/root-seam-contract.js +37 -0
  55. package/dist/adapter/seam-evidence.d.ts +118 -0
  56. package/dist/adapter/seam-evidence.d.ts.map +1 -0
  57. package/dist/adapter/seam-evidence.js +231 -0
  58. package/dist/adapter/setup-three-root-adapter.d.ts +2 -3
  59. package/dist/adapter/setup-three-root-adapter.d.ts.map +1 -1
  60. package/dist/adapter/setup-three-root-adapter.js +11 -12
  61. package/dist/adapter/system-adapter.d.ts +6 -1
  62. package/dist/adapter/system-adapter.d.ts.map +1 -1
  63. package/dist/adapter/system-seam-contract.d.ts +354 -0
  64. package/dist/adapter/system-seam-contract.d.ts.map +1 -0
  65. package/dist/adapter/system-seam-contract.js +95 -0
  66. package/dist/animation/xstate-animation-binding.d.ts +6 -0
  67. package/dist/animation/xstate-animation-binding.d.ts.map +1 -1
  68. package/dist/animation/xstate-animation-binding.js +142 -4
  69. package/dist/asset-formats/collider.d.ts +1 -1
  70. package/dist/asset-formats/mesh.d.ts +2 -2
  71. package/dist/asset-formats/particles.d.ts +1 -1
  72. package/dist/canvas-react/engine-bridge.d.ts +6 -6
  73. package/dist/canvas-react/engine-bridge.d.ts.map +1 -1
  74. package/dist/canvas-react/engine-bridge.js +2 -2
  75. package/dist/canvas-react/index.d.ts +2 -2
  76. package/dist/canvas-react/index.d.ts.map +1 -1
  77. package/dist/canvas-react/index.js +1 -1
  78. package/dist/canvas-react/pixi-react-adapter.d.ts +15 -9
  79. package/dist/canvas-react/pixi-react-adapter.d.ts.map +1 -1
  80. package/dist/canvas-react/pixi-react-adapter.js +129 -64
  81. package/dist/canvas-react/pixi-react-root-factory.d.ts.map +1 -1
  82. package/dist/canvas-react/pixi-react-root-factory.js +4 -0
  83. package/dist/canvas-react/world-context.d.ts +22 -12
  84. package/dist/canvas-react/world-context.d.ts.map +1 -1
  85. package/dist/canvas-react/world-context.js +12 -51
  86. package/dist/data/curve.d.ts +1 -1
  87. package/dist/data/curve.js +2 -2
  88. package/dist/dev/instruments.d.ts +4 -4
  89. package/dist/dev/instruments.js +4 -4
  90. package/dist/dev/register-render-vitals.d.ts +2 -2
  91. package/dist/dev/register-render-vitals.js +2 -2
  92. package/dist/dev/static-batch-advisor.d.ts +1 -1
  93. package/dist/dev/static-batch-advisor.js +2 -2
  94. package/dist/ecs/user-data.d.ts +5 -0
  95. package/dist/ecs/user-data.d.ts.map +1 -1
  96. package/dist/ecs/user-data.js +3 -0
  97. package/dist/input/input-manager.d.ts +64 -0
  98. package/dist/input/input-manager.d.ts.map +1 -1
  99. package/dist/input/input-manager.js +149 -0
  100. package/dist/manifest/load.d.ts +0 -3
  101. package/dist/manifest/load.d.ts.map +1 -1
  102. package/dist/manifest/load.js +0 -4
  103. package/dist/manifest/schema.d.ts +9 -11
  104. package/dist/manifest/schema.d.ts.map +1 -1
  105. package/dist/manifest/schema.js +2 -23
  106. package/dist/pixi/authoring.d.ts +33 -0
  107. package/dist/pixi/authoring.d.ts.map +1 -1
  108. package/dist/pixi/authoring.js +301 -2
  109. package/dist/pixi/ingest.d.ts +2 -1
  110. package/dist/pixi/ingest.d.ts.map +1 -1
  111. package/dist/pixi/ingest.js +4 -2
  112. package/dist/pixi/scene-capture.d.ts +9 -1
  113. package/dist/pixi/scene-capture.d.ts.map +1 -1
  114. package/dist/pixi/scene-capture.js +9 -6
  115. package/dist/react/world-state.d.ts +12 -0
  116. package/dist/react/world-state.d.ts.map +1 -1
  117. package/dist/react/world-state.js +14 -0
  118. package/dist/render/particles-factory.d.ts +13 -6
  119. package/dist/render/particles-factory.d.ts.map +1 -1
  120. package/dist/render/particles-factory.js +6 -3
  121. package/dist/render/soft-particle-depth.d.ts +3 -3
  122. package/dist/render/soft-particle-depth.d.ts.map +1 -1
  123. package/dist/render/soft-particle-depth.js +8 -13
  124. package/dist/runtime/create-runtime.d.ts +25 -62
  125. package/dist/runtime/create-runtime.d.ts.map +1 -1
  126. package/dist/runtime/create-runtime.js +58 -105
  127. package/dist/runtime/dev-build.d.ts +37 -0
  128. package/dist/runtime/dev-build.d.ts.map +1 -0
  129. package/dist/runtime/dev-build.js +49 -0
  130. package/dist/runtime/game.d.ts +38 -72
  131. package/dist/runtime/game.d.ts.map +1 -1
  132. package/dist/runtime/game.js +40 -55
  133. package/dist/runtime/mount-game.d.ts.map +1 -1
  134. package/dist/runtime/mount-game.js +12 -1
  135. package/dist/runtime/mount-manifest.d.ts +0 -13
  136. package/dist/runtime/mount-manifest.d.ts.map +1 -1
  137. package/dist/runtime/mount-manifest.js +5 -25
  138. package/dist/runtime/types.d.ts +18 -0
  139. package/dist/runtime/types.d.ts.map +1 -1
  140. package/dist/setup/setup-renderer.d.ts +3 -4
  141. package/dist/setup/setup-renderer.d.ts.map +1 -1
  142. package/dist/setup/setup-renderer.js +3 -4
  143. package/dist/world3d-react/engine-bridge.d.ts +1 -1
  144. package/dist/world3d-react/engine-bridge.d.ts.map +1 -1
  145. package/dist/world3d-react/engine-bridge.js +29 -4
  146. package/dist/world3d-react/index.d.ts +1 -1
  147. package/dist/world3d-react/index.js +1 -1
  148. package/dist/world3d-react/r3f-adapter.d.ts +7 -0
  149. package/dist/world3d-react/r3f-adapter.d.ts.map +1 -1
  150. package/dist/world3d-react/r3f-adapter.js +132 -28
  151. package/dist/world3d-react/r3f-root-factory.d.ts.map +1 -1
  152. package/dist/world3d-react/r3f-root-factory.js +14 -0
  153. package/dist/world3d-react/rapier-physics-bridge.d.ts +4 -4
  154. package/dist/world3d-react/rapier-physics-bridge.js +3 -3
  155. package/dist/world3d-react/world-context.d.ts +52 -3
  156. package/dist/world3d-react/world-context.d.ts.map +1 -1
  157. package/dist/world3d-react/world-context.js +94 -64
  158. package/dist-config/config.js.map +1 -1
  159. package/package.json +1 -1
  160. package/schemas/engine-capabilities.json +8 -8
  161. package/schemas/vgai-project.schema.json +1 -6
  162. package/src/adapter/adapter-module.ts +405 -72
  163. package/src/adapter/authoring-seam-contract.ts +211 -0
  164. package/src/adapter/authoring.ts +242 -50
  165. package/src/adapter/entrypoint-selection-source.ts +159 -0
  166. package/src/adapter/finders/scenes-from-entrypoint-selection.ts +162 -36
  167. package/src/adapter/first-party-systems.ts +17 -3
  168. package/src/adapter/index.ts +51 -1
  169. package/src/adapter/ingest/contract-system-adapters.ts +26 -30
  170. package/src/adapter/ingest/game-contract-seams.ts +85 -0
  171. package/src/adapter/ingest/game-contract.ts +9 -4
  172. package/src/adapter/ingest/merge-debug-adapters.ts +193 -0
  173. package/src/adapter/ingest/observation-debug-adapter.ts +158 -0
  174. package/src/adapter/ingest/scene-capture.ts +126 -23
  175. package/src/adapter/ingest/visible-capture-window.ts +63 -9
  176. package/src/adapter/native-debug-module.ts +307 -0
  177. package/src/adapter/object3d-authoring-subject.ts +50 -0
  178. package/src/adapter/root-adapter.ts +25 -56
  179. package/src/adapter/root-seam-contract.ts +57 -0
  180. package/src/adapter/seam-evidence.ts +336 -0
  181. package/src/adapter/setup-three-root-adapter.ts +13 -15
  182. package/src/adapter/system-adapter.ts +6 -1
  183. package/src/adapter/system-seam-contract.ts +121 -0
  184. package/src/animation/xstate-animation-binding.ts +151 -4
  185. package/src/canvas-react/engine-bridge.ts +6 -6
  186. package/src/canvas-react/index.ts +2 -1
  187. package/src/canvas-react/pixi-react-adapter.tsx +143 -77
  188. package/src/canvas-react/pixi-react-root-factory.tsx +4 -0
  189. package/src/canvas-react/world-context.ts +26 -101
  190. package/src/data/curve.ts +2 -2
  191. package/src/dev/instruments.ts +4 -4
  192. package/src/dev/register-render-vitals.ts +2 -2
  193. package/src/dev/static-batch-advisor.ts +2 -2
  194. package/src/ecs/user-data.ts +5 -0
  195. package/src/input/input-manager.ts +145 -0
  196. package/src/manifest/load.ts +0 -7
  197. package/src/manifest/schema.ts +2 -25
  198. package/src/pixi/authoring.ts +329 -2
  199. package/src/pixi/ingest.ts +9 -2
  200. package/src/pixi/scene-capture.ts +20 -5
  201. package/src/react/world-state.tsx +15 -0
  202. package/src/render/particles-factory.ts +16 -6
  203. package/src/render/soft-particle-depth.ts +8 -13
  204. package/src/runtime/create-runtime.ts +79 -158
  205. package/src/runtime/dev-build.ts +49 -0
  206. package/src/runtime/game.ts +72 -113
  207. package/src/runtime/mount-game.ts +19 -1
  208. package/src/runtime/mount-manifest.ts +5 -37
  209. package/src/runtime/types.ts +22 -0
  210. package/src/setup/setup-renderer.ts +3 -4
  211. package/src/world3d-react/engine-bridge.ts +29 -4
  212. package/src/world3d-react/index.ts +1 -1
  213. package/src/world3d-react/r3f-adapter.tsx +140 -31
  214. package/src/world3d-react/r3f-root-factory.tsx +14 -0
  215. package/src/world3d-react/rapier-physics-bridge.tsx +4 -4
  216. package/src/world3d-react/world-context.ts +111 -87
  217. package/dist/runtime/dev-layers.d.ts +0 -34
  218. package/dist/runtime/dev-layers.d.ts.map +0 -1
  219. package/dist/runtime/dev-layers.js +0 -46
  220. package/src/runtime/dev-layers.ts +0 -46
@@ -62,6 +62,18 @@ export const NATIVE_REGION_BASIS = Object.freeze({
62
62
  up: 'y',
63
63
  groundHeight: 0,
64
64
  });
65
+ const AdapterRegionMountSchema = z
66
+ .object({
67
+ surface: z
68
+ .enum(['three', 'canvas', 'dom'])
69
+ .describe('An additional surface this root mounts, beside the manifest root’s own adapter'),
70
+ include: z
71
+ .array(z.string().min(1))
72
+ .min(1)
73
+ .describe('Project-relative globs (`**`, `*`, `?`) naming the source files that render on this ' +
74
+ 'additional surface'),
75
+ })
76
+ .strict();
65
77
  const AdapterRegionBasisSchema = z
66
78
  .object({
67
79
  up: z.enum(['y', 'z']).describe('World axis pointing away from the ground'),
@@ -72,10 +84,6 @@ const AdapterRegionSchema = z
72
84
  .object({
73
85
  id: z.string().min(1).describe('Manifest root id this region grades'),
74
86
  surface: z.enum(['three', 'canvas', 'dom']).describe('Render surface the host hands this root'),
75
- dev: z
76
- .boolean()
77
- .describe("true = this region grades the game's own dev layer, not shipped content (the " +
78
- "manifest root's `dev: true`)"),
79
87
  projector: z.enum(PROJECTOR_NAMES).describe('Projection library serving this surface, by name'),
80
88
  dialect: z
81
89
  .string()
@@ -93,12 +101,62 @@ const AdapterRegionSchema = z
93
101
  .optional()
94
102
  .describe('Project-relative globs (`**`, `*`, `?`) naming source files this region owns, for files ' +
95
103
  'no root entry’s import closure reaches; a declared include wins over reach'),
104
+ mounts: z
105
+ .array(AdapterRegionMountSchema)
106
+ .min(1)
107
+ .optional()
108
+ .describe('Surfaces this root mounts BESIDE its own adapter, each with the files that render on ' +
109
+ 'them; with `surface`, the root’s full mounted surface set'),
96
110
  })
97
111
  .strict();
98
112
  const RegionBindingSchema = z.union([
99
113
  z.literal('manifest-roots'),
100
114
  z.array(AdapterRegionSchema).min(1),
101
115
  ]);
116
+ const AdapterRegionOverlaySchema = z
117
+ .object({
118
+ include: z
119
+ .array(z.string().min(1))
120
+ .min(1)
121
+ .optional()
122
+ .describe('Project-relative globs (`**`, `*`, `?`) naming source files this derived region owns, ' +
123
+ 'for files no root entry’s import closure reaches; a declared include wins over reach'),
124
+ mounts: z
125
+ .array(AdapterRegionMountSchema)
126
+ .min(1)
127
+ .optional()
128
+ .describe('Surfaces this root mounts BESIDE its own adapter, each with the files that render on ' +
129
+ 'them; with the derived region’s own surface, the root’s full mounted surface set'),
130
+ })
131
+ .strict()
132
+ .refine((value) => value.include !== undefined || value.mounts !== undefined, {
133
+ message: 'regionIncludes entry declares neither `include` nor `mounts` — an empty overlay states ' +
134
+ 'nothing the derivation did not already say. Remove it, or name the files it owns.',
135
+ });
136
+ /**
137
+ * PARAMETERS FOR THE RULE, keyed by manifest root id (decision, 2026-08-17;
138
+ * ARCHITECTURE-CORE §The editor protocol, "Declared includes LAYER onto the
139
+ * manifest-roots rule").
140
+ *
141
+ * The finder-selection shape, one level up: `regions: 'manifest-roots'` SELECTS
142
+ * the mechanical derivation, and this table DECLARES the per-region parameters
143
+ * it cannot derive. The two compose — {@link regionsFromManifestRoots} merges
144
+ * them — so a first-party project states one include without hand-writing its
145
+ * whole region table, and adding a root still grows the table for free.
146
+ *
147
+ * Illegal beside an explicit `regions` LIST, and rejected by name: that form is
148
+ * the full REPLACEMENT, and its entries carry `include`/`mounts` themselves.
149
+ * Two homes for one fact is how a declaration goes stale in one of them.
150
+ *
151
+ * MEASURED BASIS (PR #2281, verified twice): the region binding was
152
+ * replace-only, so no `nativeAdapter()` project could declare a single include
153
+ * without restating every region — which is what blocked the source-text R3F
154
+ * floor's retirement across the five shipped 3D projects.
155
+ */
156
+ const RegionIncludesSchema = z
157
+ .record(z.string().min(1), AdapterRegionOverlaySchema)
158
+ .describe('Per-region parameters for the `manifest-roots` rule, keyed by manifest root id: the ' +
159
+ '`include` globs and `mounts` surfaces the mechanical derivation cannot produce');
102
160
  /**
103
161
  * The MECHANICAL derivation behind the `'manifest-roots'` rule: one region per
104
162
  * declared root, its surface and adapter identity read straight off the
@@ -107,13 +165,20 @@ const RegionBindingSchema = z.union([
107
165
  *
108
166
  * The per-surface library table below is an ENGINE fact, stated once:
109
167
  *
110
- * | root `adapter` | projector | dialect | anchors |
111
- * |-----------------------|-----------|---------------|------------------------------------|
112
- * | `three` | `three` | `r3f` | source-prop, construction-literal |
113
- * | `canvas` | `pixi` | `pixi-react` | source-prop, construction-literal |
114
- * | `dom` | `dom` | `jsx` | source-prop |
115
- * | `{module,surface}` | (surface) | `null` | live-only |
116
- * | `{ingest,surface}` | (surface) | `null` | live-only |
168
+ * | root `adapter` | projector | dialect | anchors |
169
+ * |-----------------------|-----------|---------------|------------------------------------------------------|
170
+ * | `three` | `three` | `r3f` | source-prop, source-structure, construction-literal |
171
+ * | `canvas` | `pixi` | `pixi-react` | source-prop, source-structure, construction-literal |
172
+ * | `dom` | `dom` | `jsx` | source-prop, source-structure |
173
+ * | `{module,surface}` | (surface) | `null` | live-only |
174
+ * | `{ingest,surface}` | (surface) | `null` | live-only |
175
+ *
176
+ * `source-structure` rides with every first-party dialect because all three
177
+ * write through the SAME structural door (`/__ui-source/struct`) — a region
178
+ * that can rewrite a JSX attribute in a file can rewrite that file's element
179
+ * tree. It is listed separately rather than folded into `source-prop` because
180
+ * they are different doors: a lane could lose one and keep the other, and the
181
+ * matrix has to be able to say so.
117
182
  *
118
183
  * A module/ingest root's truth is the game's own source, which no first-party
119
184
  * dialect writer authors — so it declares `live-only` rather than implying a
@@ -123,55 +188,81 @@ const RegionBindingSchema = z.union([
123
188
  * Every derived region carries {@link NATIVE_REGION_BASIS} outright, so the
124
189
  * editor's ground plane READS a declaration on the native path too — the
125
190
  * default is published, never assumed at the read site.
191
+ *
192
+ * It DERIVES no {@link AdapterRegion.include} and no {@link AdapterRegion.mounts},
193
+ * and there is no derivation that could: the manifest states ONE adapter per
194
+ * root and names only its entry, so a root's extra owned files and its second
195
+ * mounted surface are facts only the game can state. It states them as
196
+ * `regionIncludes` PARAMETERS, which this function merges onto the region it
197
+ * derives for that root — the rule stays selected, the parameters are declared
198
+ * (ARCHITECTURE-CORE §The editor protocol, "Declared includes LAYER onto the
199
+ * manifest-roots rule"). A key naming no declared root merges onto nothing;
200
+ * the LOADER reports that, because only it can see both sides.
126
201
  */
127
- export function regionsFromManifestRoots(roots) {
128
- return roots.map((root) => {
129
- const surface = root.surface;
130
- const dev = root.dev;
131
- const firstParty = root.adapter.type === 'builtin';
132
- const projector = surface === 'canvas' ? 'pixi' : surface;
133
- const basis = NATIVE_REGION_BASIS;
134
- if (!firstParty) {
135
- return {
136
- id: root.id,
137
- surface,
138
- dev,
139
- projector,
140
- dialect: null,
141
- anchors: ['live-only'],
142
- basis,
143
- };
144
- }
145
- if (surface === 'dom') {
146
- return {
147
- id: root.id,
148
- surface,
149
- dev,
150
- projector,
151
- dialect: 'jsx',
152
- anchors: ['source-prop'],
153
- basis,
154
- };
155
- }
202
+ export function regionsFromManifestRoots(roots, regionIncludes = {}) {
203
+ return roots.map((root) => ({
204
+ ...derivedRegion(root),
205
+ ...overlayOf(regionIncludes[root.id]),
206
+ }));
207
+ }
208
+ /** The declared half of a merged region — omitted keys leave the derived
209
+ * region untouched, so an overlay can only ADD what the rule cannot derive. */
210
+ function overlayOf(overlay) {
211
+ if (!overlay)
212
+ return {};
213
+ return {
214
+ ...(overlay.include ? { include: overlay.include } : {}),
215
+ ...(overlay.mounts ? { mounts: overlay.mounts } : {}),
216
+ };
217
+ }
218
+ /** Region ids an overlay names that no declared root answers to. A typo here
219
+ * would otherwise be a declaration that silently does nothing, which is the
220
+ * failure class the whole declaration lane exists to end. */
221
+ export function unmatchedRegionIncludeIds(roots, regionIncludes) {
222
+ const declared = new Set(roots.map((root) => root.id));
223
+ return Object.keys(regionIncludes).filter((id) => !declared.has(id));
224
+ }
225
+ /** One root's region, before any declared parameters are merged onto it. */
226
+ function derivedRegion(root) {
227
+ const surface = root.surface;
228
+ const firstParty = root.adapter.type === 'builtin';
229
+ const projector = surface === 'canvas' ? 'pixi' : surface;
230
+ const basis = NATIVE_REGION_BASIS;
231
+ if (!firstParty) {
156
232
  return {
157
233
  id: root.id,
158
234
  surface,
159
- dev,
160
235
  projector,
161
- dialect: surface === 'three' ? 'r3f' : 'pixi-react',
162
- anchors: ['source-prop', 'construction-literal'],
236
+ dialect: null,
237
+ anchors: ['live-only'],
163
238
  basis,
164
239
  };
165
- });
240
+ }
241
+ if (surface === 'dom') {
242
+ return {
243
+ id: root.id,
244
+ surface,
245
+ projector,
246
+ dialect: 'jsx',
247
+ anchors: ['source-prop', 'source-structure'],
248
+ basis,
249
+ };
250
+ }
251
+ return {
252
+ id: root.id,
253
+ surface,
254
+ projector,
255
+ dialect: surface === 'three' ? 'r3f' : 'pixi-react',
256
+ anchors: ['source-prop', 'source-structure', 'construction-literal'],
257
+ basis,
258
+ };
166
259
  }
167
260
  // ---------------------------------------------------------------------------
168
261
  // Finder selections
169
262
  // ---------------------------------------------------------------------------
170
263
  /**
171
264
  * The rule form of a finder's region targeting: every region the host mounts
172
- * from an exported composition (a root with an `entry`) that is not a declared
173
- * DEV layer. A dev root is the game's own dev GUI — the manifest already says
174
- * so (`dev: true`), so skipping it reads a declaration rather than guessing.
265
+ * from an exported composition a root with an `entry`.
175
266
  */
176
267
  export const EXPORTED_COMPOSITION_REGIONS = 'exported-composition-regions';
177
268
  /** The finder names, as a value — what the loader's dispatcher switches on. */
@@ -207,9 +298,11 @@ const SceneReachSchema = z.union([
207
298
  kind: z.literal('entrypoint-selection'),
208
299
  selection: z.string().min(1),
209
300
  key: z.string().min(1),
301
+ active: z.boolean().optional(),
210
302
  })
211
303
  .strict(),
212
304
  z.object({ kind: z.literal('story'), storyId: z.string().min(1) }).strict(),
305
+ z.object({ kind: z.literal('game-contract'), sceneId: z.string().min(1) }).strict(),
213
306
  z.object({ kind: z.literal('none'), reason: z.string().min(1) }).strict(),
214
307
  ]);
215
308
  const SceneSourceSchema = z
@@ -224,6 +317,7 @@ const SceneEntrySchema = z
224
317
  authorable: z.boolean(),
225
318
  reach: SceneReachSchema,
226
319
  source: SceneSourceSchema.optional(),
320
+ isolationSetup: SceneSourceSchema.optional(),
227
321
  finder: z.enum(FINDER_NAMES).optional(),
228
322
  })
229
323
  .strict();
@@ -234,24 +328,100 @@ const AdapterSceneTableSchema = z
234
328
  default: z.string().min(1).optional(),
235
329
  })
236
330
  .strict();
331
+ // ---------------------------------------------------------------------------
332
+ // Observation
333
+ // ---------------------------------------------------------------------------
334
+ /**
335
+ * The two faces a declaration projects onto. They are exactly the two
336
+ * `DebugAdapter` halves (`adapter/system-adapter.ts`): `state` becomes a
337
+ * provider (`providers()` / `state(name)`), `command` becomes a verb
338
+ * (`commands()` / `invoke(name, args)`). There is deliberately no third kind —
339
+ * see {@link OBSERVATION_KIND_REFUSAL}.
340
+ */
341
+ export const OBSERVATION_KINDS = ['command', 'state'];
342
+ /**
343
+ * Why a declaration may not name a `SystemAdapters` slot here.
344
+ *
345
+ * A game's system slots are already declared through the game contract's own
346
+ * `systemAdapters` carrier (`adapter/ingest/game-contract.ts`), which validates
347
+ * each slot per-surface and reports a malformed one BY NAME
348
+ * (`contract-system-adapters.ts`). A second door onto the same slots is exactly
349
+ * the two-declarations-one-slot problem `game-contract.ts` records for `debug`,
350
+ * where it says outright that a second door "would let a game declare two
351
+ * different debug planes with no rule for which wins".
352
+ */
353
+ const OBSERVATION_KIND_REFUSAL = "observation.kind must be 'command' or 'state' — the two DebugAdapter faces a declaration " +
354
+ 'projects onto. A SystemAdapters slot is declared through the game contract’s `systemAdapters` ' +
355
+ 'carrier, which validates it per-surface; declaring one here would be a second door onto the ' +
356
+ 'same slot with no rule for which wins.';
237
357
  const ObservationDeclarationSchema = z
238
358
  .object({
239
359
  id: z.string().min(1),
240
- kind: z.enum(['system', 'command', 'state']),
360
+ kind: z.enum(OBSERVATION_KINDS, { error: () => OBSERVATION_KIND_REFUSAL }),
361
+ description: z.string().min(1).optional(),
362
+ locus: z.enum(['client', 'server']).optional(),
241
363
  answer: z
242
364
  .custom((value) => typeof value === 'function', {
243
365
  message: 'observation.answer must be a function evaluated against the mounted game',
244
366
  })
245
367
  .optional(),
368
+ })
369
+ .strict()
370
+ .refine((value) => value.kind === 'command' || value.locus === undefined, {
371
+ path: ['locus'],
372
+ message: 'observation.locus is valid only for a command declaration',
373
+ });
374
+ // ---------------------------------------------------------------------------
375
+ // Native input
376
+ // ---------------------------------------------------------------------------
377
+ /** The action value shapes the session input door already speaks. */
378
+ export const ADAPTER_INPUT_VALUE_TYPES = [
379
+ 'digital',
380
+ 'scalar',
381
+ 'vector2',
382
+ 'pointerDelta',
383
+ 'pointerPosition',
384
+ ];
385
+ const AdapterInputBindingSchema = z
386
+ .object({
387
+ root: z.string().min(1),
388
+ actions: z.custom((value) => typeof value === 'function', {
389
+ message: 'input.actions must be a function returning the app-owned action vocabulary',
390
+ }),
391
+ set: z.custom((value) => typeof value === 'function', {
392
+ message: 'input.set must be a function writing the app-owned input store',
393
+ }),
394
+ clear: z.custom((value) => typeof value === 'function', {
395
+ message: 'input.clear must be a function releasing app-owned virtual actions',
396
+ }),
246
397
  })
247
398
  .strict();
399
+ /**
400
+ * Why the two forms may not be combined, said in the error a game's author
401
+ * reads. An explicit region list REPLACES the derivation and its entries carry
402
+ * `include`/`mounts` themselves; layering a second table over it would put one
403
+ * fact in two homes, with no rule for which wins.
404
+ */
405
+ const REGION_INCLUDES_WITH_LIST_REFUSAL = '`regionIncludes` states parameters for the `manifest-roots` RULE, so it cannot be paired ' +
406
+ 'with an explicit `regions` list — that form is the full replacement, and each entry carries ' +
407
+ 'its own `include`/`mounts`. Move these globs onto the matching `regions[]` entry, or drop the ' +
408
+ 'list and let the derivation run.';
248
409
  export const AdapterDefinitionSchema = z
249
410
  .object({
250
411
  regions: RegionBindingSchema.optional(),
412
+ regionIncludes: RegionIncludesSchema.optional(),
251
413
  scenes: AdapterSceneTableSchema.optional(),
252
414
  observation: z.array(ObservationDeclarationSchema).optional(),
415
+ input: AdapterInputBindingSchema.optional(),
253
416
  })
254
- .strict();
417
+ .strict()
418
+ // An EMPTY table beside a list is fine and must stay fine: `defineAdapter`
419
+ // publishes `regionIncludes: {}` on every definition, and its output is
420
+ // parsed a second time by the host's loader — a refusal on the key's mere
421
+ // presence would reject the module's own valid output on the round trip.
422
+ .refine((value) => value.regionIncludes === undefined ||
423
+ Object.keys(value.regionIncludes).length === 0 ||
424
+ !Array.isArray(value.regions), { message: REGION_INCLUDES_WITH_LIST_REFUSAL });
255
425
  /**
256
426
  * Validate and freeze a game's binding table.
257
427
  *
@@ -270,8 +440,10 @@ export function defineAdapter(input = {}) {
270
440
  }
271
441
  return Object.freeze({
272
442
  regions: input.regions ?? 'manifest-roots',
443
+ regionIncludes: Object.freeze({ ...(input.regionIncludes ?? {}) }),
273
444
  scenes: Object.freeze({ ...(input.scenes ?? {}) }),
274
445
  observation: Object.freeze([...(input.observation ?? [])]),
446
+ ...(input.input ? { input: input.input } : {}),
275
447
  });
276
448
  }
277
449
  /**
@@ -284,6 +456,7 @@ export function defineAdapter(input = {}) {
284
456
  export function nativeAdapter(options = {}) {
285
457
  return defineAdapter({
286
458
  regions: 'manifest-roots',
459
+ ...(options.regionIncludes ? { regionIncludes: options.regionIncludes } : {}),
287
460
  scenes: {
288
461
  ...(options.scenes ? { entries: options.scenes } : {}),
289
462
  find: [
@@ -291,7 +464,6 @@ export function nativeAdapter(options = {}) {
291
464
  { finder: 'prefabsFromStories' },
292
465
  ],
293
466
  },
294
- ...(options.observation ? { observation: options.observation } : {}),
295
467
  });
296
468
  }
297
469
  /**