@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.
- package/dist/adapter/adapter-module.d.ts +229 -33
- package/dist/adapter/adapter-module.d.ts.map +1 -1
- package/dist/adapter/adapter-module.js +222 -50
- package/dist/adapter/authoring-seam-contract.d.ts +530 -0
- package/dist/adapter/authoring-seam-contract.d.ts.map +1 -0
- package/dist/adapter/authoring-seam-contract.js +162 -0
- package/dist/adapter/authoring.d.ts +237 -50
- package/dist/adapter/authoring.d.ts.map +1 -1
- package/dist/adapter/authoring.js +2 -1
- package/dist/adapter/entrypoint-selection-source.d.ts +35 -0
- package/dist/adapter/entrypoint-selection-source.d.ts.map +1 -0
- package/dist/adapter/entrypoint-selection-source.js +151 -0
- package/dist/adapter/finders/scenes-from-entrypoint-selection.d.ts +7 -3
- package/dist/adapter/finders/scenes-from-entrypoint-selection.d.ts.map +1 -1
- package/dist/adapter/finders/scenes-from-entrypoint-selection.js +145 -27
- package/dist/adapter/first-party-systems.d.ts +13 -3
- package/dist/adapter/first-party-systems.d.ts.map +1 -1
- package/dist/adapter/first-party-systems.js +10 -3
- package/dist/adapter/index.d.ts +7 -2
- package/dist/adapter/index.d.ts.map +1 -1
- package/dist/adapter/index.js +4 -0
- package/dist/adapter/ingest/contract-system-adapters.d.ts +1 -1
- package/dist/adapter/ingest/contract-system-adapters.d.ts.map +1 -1
- package/dist/adapter/ingest/contract-system-adapters.js +13 -25
- package/dist/adapter/ingest/game-contract-seams.d.ts +214 -0
- package/dist/adapter/ingest/game-contract-seams.d.ts.map +1 -0
- package/dist/adapter/ingest/game-contract-seams.js +62 -0
- package/dist/adapter/ingest/game-contract.d.ts +5 -3
- package/dist/adapter/ingest/game-contract.d.ts.map +1 -1
- package/dist/adapter/ingest/game-contract.js +4 -1
- package/dist/adapter/ingest/merge-debug-adapters.d.ts +52 -0
- package/dist/adapter/ingest/merge-debug-adapters.d.ts.map +1 -0
- package/dist/adapter/ingest/merge-debug-adapters.js +141 -0
- package/dist/adapter/ingest/observation-debug-adapter.d.ts +56 -0
- package/dist/adapter/ingest/observation-debug-adapter.d.ts.map +1 -0
- package/dist/adapter/ingest/observation-debug-adapter.js +131 -0
- package/dist/adapter/ingest/scene-capture.d.ts +38 -13
- package/dist/adapter/ingest/scene-capture.d.ts.map +1 -1
- package/dist/adapter/ingest/scene-capture.js +101 -24
- package/dist/adapter/ingest/visible-capture-window.d.ts +22 -5
- package/dist/adapter/ingest/visible-capture-window.d.ts.map +1 -1
- package/dist/adapter/ingest/visible-capture-window.js +46 -7
- package/dist/adapter/native-debug-module.d.ts +43 -0
- package/dist/adapter/native-debug-module.d.ts.map +1 -0
- package/dist/adapter/native-debug-module.js +230 -0
- package/dist/adapter/object3d-authoring-subject.d.ts +32 -0
- package/dist/adapter/object3d-authoring-subject.d.ts.map +1 -0
- package/dist/adapter/object3d-authoring-subject.js +25 -0
- package/dist/adapter/root-adapter.d.ts +25 -56
- package/dist/adapter/root-adapter.d.ts.map +1 -1
- package/dist/adapter/root-adapter.js +4 -20
- package/dist/adapter/root-seam-contract.d.ts +129 -0
- package/dist/adapter/root-seam-contract.d.ts.map +1 -0
- package/dist/adapter/root-seam-contract.js +37 -0
- package/dist/adapter/seam-evidence.d.ts +118 -0
- package/dist/adapter/seam-evidence.d.ts.map +1 -0
- package/dist/adapter/seam-evidence.js +231 -0
- package/dist/adapter/setup-three-root-adapter.d.ts +2 -3
- package/dist/adapter/setup-three-root-adapter.d.ts.map +1 -1
- package/dist/adapter/setup-three-root-adapter.js +11 -12
- package/dist/adapter/system-adapter.d.ts +6 -1
- package/dist/adapter/system-adapter.d.ts.map +1 -1
- package/dist/adapter/system-seam-contract.d.ts +354 -0
- package/dist/adapter/system-seam-contract.d.ts.map +1 -0
- package/dist/adapter/system-seam-contract.js +95 -0
- package/dist/animation/xstate-animation-binding.d.ts +6 -0
- package/dist/animation/xstate-animation-binding.d.ts.map +1 -1
- package/dist/animation/xstate-animation-binding.js +142 -4
- package/dist/asset-formats/collider.d.ts +1 -1
- package/dist/asset-formats/mesh.d.ts +2 -2
- package/dist/asset-formats/particles.d.ts +1 -1
- package/dist/canvas-react/engine-bridge.d.ts +6 -6
- package/dist/canvas-react/engine-bridge.d.ts.map +1 -1
- package/dist/canvas-react/engine-bridge.js +2 -2
- package/dist/canvas-react/index.d.ts +2 -2
- package/dist/canvas-react/index.d.ts.map +1 -1
- package/dist/canvas-react/index.js +1 -1
- package/dist/canvas-react/pixi-react-adapter.d.ts +15 -9
- package/dist/canvas-react/pixi-react-adapter.d.ts.map +1 -1
- package/dist/canvas-react/pixi-react-adapter.js +129 -64
- package/dist/canvas-react/pixi-react-root-factory.d.ts.map +1 -1
- package/dist/canvas-react/pixi-react-root-factory.js +4 -0
- package/dist/canvas-react/world-context.d.ts +22 -12
- package/dist/canvas-react/world-context.d.ts.map +1 -1
- package/dist/canvas-react/world-context.js +12 -51
- package/dist/data/curve.d.ts +1 -1
- package/dist/data/curve.js +2 -2
- package/dist/dev/instruments.d.ts +4 -4
- package/dist/dev/instruments.js +4 -4
- package/dist/dev/register-render-vitals.d.ts +2 -2
- package/dist/dev/register-render-vitals.js +2 -2
- package/dist/dev/static-batch-advisor.d.ts +1 -1
- package/dist/dev/static-batch-advisor.js +2 -2
- package/dist/ecs/user-data.d.ts +5 -0
- package/dist/ecs/user-data.d.ts.map +1 -1
- package/dist/ecs/user-data.js +3 -0
- package/dist/input/input-manager.d.ts +64 -0
- package/dist/input/input-manager.d.ts.map +1 -1
- package/dist/input/input-manager.js +149 -0
- package/dist/manifest/load.d.ts +0 -3
- package/dist/manifest/load.d.ts.map +1 -1
- package/dist/manifest/load.js +0 -4
- package/dist/manifest/schema.d.ts +9 -11
- package/dist/manifest/schema.d.ts.map +1 -1
- package/dist/manifest/schema.js +2 -23
- package/dist/pixi/authoring.d.ts +33 -0
- package/dist/pixi/authoring.d.ts.map +1 -1
- package/dist/pixi/authoring.js +301 -2
- package/dist/pixi/ingest.d.ts +2 -1
- package/dist/pixi/ingest.d.ts.map +1 -1
- package/dist/pixi/ingest.js +4 -2
- package/dist/pixi/scene-capture.d.ts +9 -1
- package/dist/pixi/scene-capture.d.ts.map +1 -1
- package/dist/pixi/scene-capture.js +9 -6
- package/dist/react/world-state.d.ts +12 -0
- package/dist/react/world-state.d.ts.map +1 -1
- package/dist/react/world-state.js +14 -0
- package/dist/render/particles-factory.d.ts +13 -6
- package/dist/render/particles-factory.d.ts.map +1 -1
- package/dist/render/particles-factory.js +6 -3
- package/dist/render/soft-particle-depth.d.ts +3 -3
- package/dist/render/soft-particle-depth.d.ts.map +1 -1
- package/dist/render/soft-particle-depth.js +8 -13
- package/dist/runtime/create-runtime.d.ts +25 -62
- package/dist/runtime/create-runtime.d.ts.map +1 -1
- package/dist/runtime/create-runtime.js +58 -105
- package/dist/runtime/dev-build.d.ts +37 -0
- package/dist/runtime/dev-build.d.ts.map +1 -0
- package/dist/runtime/dev-build.js +49 -0
- package/dist/runtime/game.d.ts +38 -72
- package/dist/runtime/game.d.ts.map +1 -1
- package/dist/runtime/game.js +40 -55
- package/dist/runtime/mount-game.d.ts.map +1 -1
- package/dist/runtime/mount-game.js +12 -1
- package/dist/runtime/mount-manifest.d.ts +0 -13
- package/dist/runtime/mount-manifest.d.ts.map +1 -1
- package/dist/runtime/mount-manifest.js +5 -25
- package/dist/runtime/types.d.ts +18 -0
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/setup/setup-renderer.d.ts +3 -4
- package/dist/setup/setup-renderer.d.ts.map +1 -1
- package/dist/setup/setup-renderer.js +3 -4
- package/dist/world3d-react/engine-bridge.d.ts +1 -1
- package/dist/world3d-react/engine-bridge.d.ts.map +1 -1
- package/dist/world3d-react/engine-bridge.js +29 -4
- package/dist/world3d-react/index.d.ts +1 -1
- package/dist/world3d-react/index.js +1 -1
- package/dist/world3d-react/r3f-adapter.d.ts +7 -0
- package/dist/world3d-react/r3f-adapter.d.ts.map +1 -1
- package/dist/world3d-react/r3f-adapter.js +132 -28
- package/dist/world3d-react/r3f-root-factory.d.ts.map +1 -1
- package/dist/world3d-react/r3f-root-factory.js +14 -0
- package/dist/world3d-react/rapier-physics-bridge.d.ts +4 -4
- package/dist/world3d-react/rapier-physics-bridge.js +3 -3
- package/dist/world3d-react/world-context.d.ts +52 -3
- package/dist/world3d-react/world-context.d.ts.map +1 -1
- package/dist/world3d-react/world-context.js +94 -64
- package/dist-config/config.js.map +1 -1
- package/package.json +1 -1
- package/schemas/engine-capabilities.json +8 -8
- package/schemas/vgai-project.schema.json +1 -6
- package/src/adapter/adapter-module.ts +405 -72
- package/src/adapter/authoring-seam-contract.ts +211 -0
- package/src/adapter/authoring.ts +242 -50
- package/src/adapter/entrypoint-selection-source.ts +159 -0
- package/src/adapter/finders/scenes-from-entrypoint-selection.ts +162 -36
- package/src/adapter/first-party-systems.ts +17 -3
- package/src/adapter/index.ts +51 -1
- package/src/adapter/ingest/contract-system-adapters.ts +26 -30
- package/src/adapter/ingest/game-contract-seams.ts +85 -0
- package/src/adapter/ingest/game-contract.ts +9 -4
- package/src/adapter/ingest/merge-debug-adapters.ts +193 -0
- package/src/adapter/ingest/observation-debug-adapter.ts +158 -0
- package/src/adapter/ingest/scene-capture.ts +126 -23
- package/src/adapter/ingest/visible-capture-window.ts +63 -9
- package/src/adapter/native-debug-module.ts +307 -0
- package/src/adapter/object3d-authoring-subject.ts +50 -0
- package/src/adapter/root-adapter.ts +25 -56
- package/src/adapter/root-seam-contract.ts +57 -0
- package/src/adapter/seam-evidence.ts +336 -0
- package/src/adapter/setup-three-root-adapter.ts +13 -15
- package/src/adapter/system-adapter.ts +6 -1
- package/src/adapter/system-seam-contract.ts +121 -0
- package/src/animation/xstate-animation-binding.ts +151 -4
- package/src/canvas-react/engine-bridge.ts +6 -6
- package/src/canvas-react/index.ts +2 -1
- package/src/canvas-react/pixi-react-adapter.tsx +143 -77
- package/src/canvas-react/pixi-react-root-factory.tsx +4 -0
- package/src/canvas-react/world-context.ts +26 -101
- package/src/data/curve.ts +2 -2
- package/src/dev/instruments.ts +4 -4
- package/src/dev/register-render-vitals.ts +2 -2
- package/src/dev/static-batch-advisor.ts +2 -2
- package/src/ecs/user-data.ts +5 -0
- package/src/input/input-manager.ts +145 -0
- package/src/manifest/load.ts +0 -7
- package/src/manifest/schema.ts +2 -25
- package/src/pixi/authoring.ts +329 -2
- package/src/pixi/ingest.ts +9 -2
- package/src/pixi/scene-capture.ts +20 -5
- package/src/react/world-state.tsx +15 -0
- package/src/render/particles-factory.ts +16 -6
- package/src/render/soft-particle-depth.ts +8 -13
- package/src/runtime/create-runtime.ts +79 -158
- package/src/runtime/dev-build.ts +49 -0
- package/src/runtime/game.ts +72 -113
- package/src/runtime/mount-game.ts +19 -1
- package/src/runtime/mount-manifest.ts +5 -37
- package/src/runtime/types.ts +22 -0
- package/src/setup/setup-renderer.ts +3 -4
- package/src/world3d-react/engine-bridge.ts +29 -4
- package/src/world3d-react/index.ts +1 -1
- package/src/world3d-react/r3f-adapter.tsx +140 -31
- package/src/world3d-react/r3f-root-factory.tsx +14 -0
- package/src/world3d-react/rapier-physics-bridge.tsx +4 -4
- package/src/world3d-react/world-context.ts +111 -87
- package/dist/runtime/dev-layers.d.ts +0 -34
- package/dist/runtime/dev-layers.d.ts.map +0 -1
- package/dist/runtime/dev-layers.js +0 -46
- package/src/runtime/dev-layers.ts +0 -46
|
@@ -100,18 +100,6 @@ export interface AdapterRegion {
|
|
|
100
100
|
readonly id: string;
|
|
101
101
|
/** What the host hands this root. */
|
|
102
102
|
readonly surface: AdapterSurface;
|
|
103
|
-
/**
|
|
104
|
-
* `true` = this region grades the game's own DEV LAYER (the manifest root's
|
|
105
|
-
* `dev: true`), not shipped game content.
|
|
106
|
-
*
|
|
107
|
-
* Carried here because a dev root occupies the same MEDIUM as the content
|
|
108
|
-
* root beside it — a dev GUI is a `dom` root next to the HUD's `dom` root —
|
|
109
|
-
* and anything that maps a subject to "the region on this surface" would
|
|
110
|
-
* otherwise be choosing between them by array order. A region consumer that
|
|
111
|
-
* means shipped content says so by skipping `dev` regions (the loader's
|
|
112
|
-
* `regionForSurface` does), rather than by guessing which one matched.
|
|
113
|
-
*/
|
|
114
|
-
readonly dev: boolean;
|
|
115
103
|
/** Projection library, by name. */
|
|
116
104
|
readonly projector: ProjectorName;
|
|
117
105
|
/**
|
|
@@ -162,8 +150,68 @@ export interface AdapterRegion {
|
|
|
162
150
|
* how a declaration slot turns back into a language.
|
|
163
151
|
*/
|
|
164
152
|
readonly include?: readonly string[];
|
|
153
|
+
/**
|
|
154
|
+
* Surfaces this root mounts BESIDE {@link AdapterRegion.surface}, each with
|
|
155
|
+
* the source files that render on it. Together with `surface` — the manifest
|
|
156
|
+
* root's own adapter, which is always the PRIMARY — this states the root's
|
|
157
|
+
* FULL mounted surface set (decision, 2026-08-16; ARCHITECTURE-CORE §The
|
|
158
|
+
* editor protocol).
|
|
159
|
+
*
|
|
160
|
+
* A root is not always one surface. The shipped case is an R3F game whose
|
|
161
|
+
* `three` root mounts a canvas AND adopts a DOM HUD beside it
|
|
162
|
+
* (`adoptGameDomRoot` already adopts the pair together): its HUD modules
|
|
163
|
+
* render react-dom host elements, and `userData-oid` on a `<div>` is a prop
|
|
164
|
+
* react-dom does not recognize, so the editor must know they are the `dom`
|
|
165
|
+
* half. Before this slot there was nowhere to say so — `id` names the
|
|
166
|
+
* manifest root this region grades, so a second `dom` region for such a root
|
|
167
|
+
* would either name a root the host would try to MOUNT (fabrication, which
|
|
168
|
+
* the anti-shim rule forbids) or name no root at all.
|
|
169
|
+
*
|
|
170
|
+
* Each mount's `include` is REQUIRED and carries the same glob vocabulary as
|
|
171
|
+
* {@link AdapterRegion.include} (`**`, `*`, `?`). A mount with no files would
|
|
172
|
+
* declare a surface without saying which source renders on it, which answers
|
|
173
|
+
* no question any reader asks.
|
|
174
|
+
*
|
|
175
|
+
* READER: the same one — `resolveFileRegion`'s rung 1. A file claimed by a
|
|
176
|
+
* region's own `include` AND by a mount is a contradiction in the game's own
|
|
177
|
+
* declaration and settles nothing, exactly as two regions claiming one file
|
|
178
|
+
* do; it falls through to reach, and failing that to the caller's loud
|
|
179
|
+
* ambiguous path.
|
|
180
|
+
*
|
|
181
|
+
* {@link regionsFromManifestRoots} emits none: the manifest states one
|
|
182
|
+
* adapter per root and NOTHING infers a second surface from source text.
|
|
183
|
+
* Where the declaration and what a file renders disagree, the disagreement is
|
|
184
|
+
* reported as drift (the editor's `OID003`), never resolved by reading bytes.
|
|
185
|
+
*/
|
|
186
|
+
readonly mounts?: readonly AdapterRegionMount[];
|
|
165
187
|
}
|
|
166
188
|
|
|
189
|
+
/**
|
|
190
|
+
* One ADDITIONAL surface a root mounts, and the source files that render on it.
|
|
191
|
+
* See {@link AdapterRegion.mounts}.
|
|
192
|
+
*/
|
|
193
|
+
export interface AdapterRegionMount {
|
|
194
|
+
/** The surface this part of the root mounts on. */
|
|
195
|
+
readonly surface: AdapterSurface;
|
|
196
|
+
/** Project-relative globs (`**`, `*`, `?`) naming the files that render here. */
|
|
197
|
+
readonly include: readonly string[];
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const AdapterRegionMountSchema = z
|
|
201
|
+
.object({
|
|
202
|
+
surface: z
|
|
203
|
+
.enum(['three', 'canvas', 'dom'])
|
|
204
|
+
.describe('An additional surface this root mounts, beside the manifest root’s own adapter'),
|
|
205
|
+
include: z
|
|
206
|
+
.array(z.string().min(1))
|
|
207
|
+
.min(1)
|
|
208
|
+
.describe(
|
|
209
|
+
'Project-relative globs (`**`, `*`, `?`) naming the source files that render on this ' +
|
|
210
|
+
'additional surface',
|
|
211
|
+
),
|
|
212
|
+
})
|
|
213
|
+
.strict();
|
|
214
|
+
|
|
167
215
|
const AdapterRegionBasisSchema = z
|
|
168
216
|
.object({
|
|
169
217
|
up: z.enum(['y', 'z']).describe('World axis pointing away from the ground'),
|
|
@@ -175,12 +223,6 @@ const AdapterRegionSchema = z
|
|
|
175
223
|
.object({
|
|
176
224
|
id: z.string().min(1).describe('Manifest root id this region grades'),
|
|
177
225
|
surface: z.enum(['three', 'canvas', 'dom']).describe('Render surface the host hands this root'),
|
|
178
|
-
dev: z
|
|
179
|
-
.boolean()
|
|
180
|
-
.describe(
|
|
181
|
-
"true = this region grades the game's own dev layer, not shipped content (the " +
|
|
182
|
-
"manifest root's `dev: true`)",
|
|
183
|
-
),
|
|
184
226
|
projector: z.enum(PROJECTOR_NAMES).describe('Projection library serving this surface, by name'),
|
|
185
227
|
dialect: z
|
|
186
228
|
.string()
|
|
@@ -204,6 +246,14 @@ const AdapterRegionSchema = z
|
|
|
204
246
|
'Project-relative globs (`**`, `*`, `?`) naming source files this region owns, for files ' +
|
|
205
247
|
'no root entry’s import closure reaches; a declared include wins over reach',
|
|
206
248
|
),
|
|
249
|
+
mounts: z
|
|
250
|
+
.array(AdapterRegionMountSchema)
|
|
251
|
+
.min(1)
|
|
252
|
+
.optional()
|
|
253
|
+
.describe(
|
|
254
|
+
'Surfaces this root mounts BESIDE its own adapter, each with the files that render on ' +
|
|
255
|
+
'them; with `surface`, the root’s full mounted surface set',
|
|
256
|
+
),
|
|
207
257
|
})
|
|
208
258
|
.strict();
|
|
209
259
|
|
|
@@ -220,6 +270,73 @@ const RegionBindingSchema = z.union([
|
|
|
220
270
|
z.array(AdapterRegionSchema).min(1),
|
|
221
271
|
]);
|
|
222
272
|
|
|
273
|
+
/**
|
|
274
|
+
* The per-region PARAMETERS the `'manifest-roots'` rule takes — see
|
|
275
|
+
* {@link AdapterDefinition.regionIncludes}. Exactly the two fields of an
|
|
276
|
+
* {@link AdapterRegion} the mechanical derivation cannot produce, because
|
|
277
|
+
* neither is a fact the manifest states: which files a region owns beyond
|
|
278
|
+
* its entry's reach, and which surfaces its root mounts beside its own.
|
|
279
|
+
*/
|
|
280
|
+
export interface AdapterRegionOverlay {
|
|
281
|
+
/** See {@link AdapterRegion.include}. */
|
|
282
|
+
readonly include?: readonly string[];
|
|
283
|
+
/** See {@link AdapterRegion.mounts}. */
|
|
284
|
+
readonly mounts?: readonly AdapterRegionMount[];
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const AdapterRegionOverlaySchema = z
|
|
288
|
+
.object({
|
|
289
|
+
include: z
|
|
290
|
+
.array(z.string().min(1))
|
|
291
|
+
.min(1)
|
|
292
|
+
.optional()
|
|
293
|
+
.describe(
|
|
294
|
+
'Project-relative globs (`**`, `*`, `?`) naming source files this derived region owns, ' +
|
|
295
|
+
'for files no root entry’s import closure reaches; a declared include wins over reach',
|
|
296
|
+
),
|
|
297
|
+
mounts: z
|
|
298
|
+
.array(AdapterRegionMountSchema)
|
|
299
|
+
.min(1)
|
|
300
|
+
.optional()
|
|
301
|
+
.describe(
|
|
302
|
+
'Surfaces this root mounts BESIDE its own adapter, each with the files that render on ' +
|
|
303
|
+
'them; with the derived region’s own surface, the root’s full mounted surface set',
|
|
304
|
+
),
|
|
305
|
+
})
|
|
306
|
+
.strict()
|
|
307
|
+
.refine((value) => value.include !== undefined || value.mounts !== undefined, {
|
|
308
|
+
message:
|
|
309
|
+
'regionIncludes entry declares neither `include` nor `mounts` — an empty overlay states ' +
|
|
310
|
+
'nothing the derivation did not already say. Remove it, or name the files it owns.',
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* PARAMETERS FOR THE RULE, keyed by manifest root id (decision, 2026-08-17;
|
|
315
|
+
* ARCHITECTURE-CORE §The editor protocol, "Declared includes LAYER onto the
|
|
316
|
+
* manifest-roots rule").
|
|
317
|
+
*
|
|
318
|
+
* The finder-selection shape, one level up: `regions: 'manifest-roots'` SELECTS
|
|
319
|
+
* the mechanical derivation, and this table DECLARES the per-region parameters
|
|
320
|
+
* it cannot derive. The two compose — {@link regionsFromManifestRoots} merges
|
|
321
|
+
* them — so a first-party project states one include without hand-writing its
|
|
322
|
+
* whole region table, and adding a root still grows the table for free.
|
|
323
|
+
*
|
|
324
|
+
* Illegal beside an explicit `regions` LIST, and rejected by name: that form is
|
|
325
|
+
* the full REPLACEMENT, and its entries carry `include`/`mounts` themselves.
|
|
326
|
+
* Two homes for one fact is how a declaration goes stale in one of them.
|
|
327
|
+
*
|
|
328
|
+
* MEASURED BASIS (PR #2281, verified twice): the region binding was
|
|
329
|
+
* replace-only, so no `nativeAdapter()` project could declare a single include
|
|
330
|
+
* without restating every region — which is what blocked the source-text R3F
|
|
331
|
+
* floor's retirement across the five shipped 3D projects.
|
|
332
|
+
*/
|
|
333
|
+
const RegionIncludesSchema = z
|
|
334
|
+
.record(z.string().min(1), AdapterRegionOverlaySchema)
|
|
335
|
+
.describe(
|
|
336
|
+
'Per-region parameters for the `manifest-roots` rule, keyed by manifest root id: the ' +
|
|
337
|
+
'`include` globs and `mounts` surfaces the mechanical derivation cannot produce',
|
|
338
|
+
);
|
|
339
|
+
|
|
223
340
|
/**
|
|
224
341
|
* The MECHANICAL derivation behind the `'manifest-roots'` rule: one region per
|
|
225
342
|
* declared root, its surface and adapter identity read straight off the
|
|
@@ -228,13 +345,20 @@ const RegionBindingSchema = z.union([
|
|
|
228
345
|
*
|
|
229
346
|
* The per-surface library table below is an ENGINE fact, stated once:
|
|
230
347
|
*
|
|
231
|
-
* | root `adapter` | projector | dialect | anchors
|
|
232
|
-
*
|
|
233
|
-
* | `three` | `three` | `r3f` | source-prop, construction-literal
|
|
234
|
-
* | `canvas` | `pixi` | `pixi-react` | source-prop, construction-literal
|
|
235
|
-
* | `dom` | `dom` | `jsx` | source-prop |
|
|
236
|
-
* | `{module,surface}` | (surface) | `null` | live-only
|
|
237
|
-
* | `{ingest,surface}` | (surface) | `null` | live-only
|
|
348
|
+
* | root `adapter` | projector | dialect | anchors |
|
|
349
|
+
* |-----------------------|-----------|---------------|------------------------------------------------------|
|
|
350
|
+
* | `three` | `three` | `r3f` | source-prop, source-structure, construction-literal |
|
|
351
|
+
* | `canvas` | `pixi` | `pixi-react` | source-prop, source-structure, construction-literal |
|
|
352
|
+
* | `dom` | `dom` | `jsx` | source-prop, source-structure |
|
|
353
|
+
* | `{module,surface}` | (surface) | `null` | live-only |
|
|
354
|
+
* | `{ingest,surface}` | (surface) | `null` | live-only |
|
|
355
|
+
*
|
|
356
|
+
* `source-structure` rides with every first-party dialect because all three
|
|
357
|
+
* write through the SAME structural door (`/__ui-source/struct`) — a region
|
|
358
|
+
* that can rewrite a JSX attribute in a file can rewrite that file's element
|
|
359
|
+
* tree. It is listed separately rather than folded into `source-prop` because
|
|
360
|
+
* they are different doors: a lane could lose one and keep the other, and the
|
|
361
|
+
* matrix has to be able to say so.
|
|
238
362
|
*
|
|
239
363
|
* A module/ingest root's truth is the game's own source, which no first-party
|
|
240
364
|
* dialect writer authors — so it declares `live-only` rather than implying a
|
|
@@ -244,46 +368,82 @@ const RegionBindingSchema = z.union([
|
|
|
244
368
|
* Every derived region carries {@link NATIVE_REGION_BASIS} outright, so the
|
|
245
369
|
* editor's ground plane READS a declaration on the native path too — the
|
|
246
370
|
* default is published, never assumed at the read site.
|
|
371
|
+
*
|
|
372
|
+
* It DERIVES no {@link AdapterRegion.include} and no {@link AdapterRegion.mounts},
|
|
373
|
+
* and there is no derivation that could: the manifest states ONE adapter per
|
|
374
|
+
* root and names only its entry, so a root's extra owned files and its second
|
|
375
|
+
* mounted surface are facts only the game can state. It states them as
|
|
376
|
+
* `regionIncludes` PARAMETERS, which this function merges onto the region it
|
|
377
|
+
* derives for that root — the rule stays selected, the parameters are declared
|
|
378
|
+
* (ARCHITECTURE-CORE §The editor protocol, "Declared includes LAYER onto the
|
|
379
|
+
* manifest-roots rule"). A key naming no declared root merges onto nothing;
|
|
380
|
+
* the LOADER reports that, because only it can see both sides.
|
|
247
381
|
*/
|
|
248
|
-
export function regionsFromManifestRoots(
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
382
|
+
export function regionsFromManifestRoots(
|
|
383
|
+
roots: readonly ResolvedAdapterRoot[],
|
|
384
|
+
regionIncludes: Readonly<Record<string, AdapterRegionOverlay>> = {},
|
|
385
|
+
): AdapterRegion[] {
|
|
386
|
+
return roots.map((root) => ({
|
|
387
|
+
...derivedRegion(root),
|
|
388
|
+
...overlayOf(regionIncludes[root.id]),
|
|
389
|
+
}));
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/** The declared half of a merged region — omitted keys leave the derived
|
|
393
|
+
* region untouched, so an overlay can only ADD what the rule cannot derive. */
|
|
394
|
+
function overlayOf(overlay: AdapterRegionOverlay | undefined): Partial<AdapterRegion> {
|
|
395
|
+
if (!overlay) return {};
|
|
396
|
+
return {
|
|
397
|
+
...(overlay.include ? { include: overlay.include } : {}),
|
|
398
|
+
...(overlay.mounts ? { mounts: overlay.mounts } : {}),
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/** Region ids an overlay names that no declared root answers to. A typo here
|
|
403
|
+
* would otherwise be a declaration that silently does nothing, which is the
|
|
404
|
+
* failure class the whole declaration lane exists to end. */
|
|
405
|
+
export function unmatchedRegionIncludeIds(
|
|
406
|
+
roots: readonly ResolvedAdapterRoot[],
|
|
407
|
+
regionIncludes: Readonly<Record<string, AdapterRegionOverlay>>,
|
|
408
|
+
): string[] {
|
|
409
|
+
const declared = new Set(roots.map((root) => root.id));
|
|
410
|
+
return Object.keys(regionIncludes).filter((id) => !declared.has(id));
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/** One root's region, before any declared parameters are merged onto it. */
|
|
414
|
+
function derivedRegion(root: ResolvedAdapterRoot): AdapterRegion {
|
|
415
|
+
const surface = root.surface;
|
|
416
|
+
const firstParty = root.adapter.type === 'builtin';
|
|
417
|
+
const projector: ProjectorName = surface === 'canvas' ? 'pixi' : surface;
|
|
418
|
+
const basis = NATIVE_REGION_BASIS;
|
|
419
|
+
if (!firstParty) {
|
|
277
420
|
return {
|
|
278
421
|
id: root.id,
|
|
279
422
|
surface,
|
|
280
|
-
dev,
|
|
281
423
|
projector,
|
|
282
|
-
dialect:
|
|
283
|
-
anchors: ['
|
|
424
|
+
dialect: null,
|
|
425
|
+
anchors: ['live-only'] as const,
|
|
284
426
|
basis,
|
|
285
427
|
};
|
|
286
|
-
}
|
|
428
|
+
}
|
|
429
|
+
if (surface === 'dom') {
|
|
430
|
+
return {
|
|
431
|
+
id: root.id,
|
|
432
|
+
surface,
|
|
433
|
+
projector,
|
|
434
|
+
dialect: 'jsx',
|
|
435
|
+
anchors: ['source-prop', 'source-structure'] as const,
|
|
436
|
+
basis,
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
return {
|
|
440
|
+
id: root.id,
|
|
441
|
+
surface,
|
|
442
|
+
projector,
|
|
443
|
+
dialect: surface === 'three' ? 'r3f' : 'pixi-react',
|
|
444
|
+
anchors: ['source-prop', 'source-structure', 'construction-literal'] as const,
|
|
445
|
+
basis,
|
|
446
|
+
};
|
|
287
447
|
}
|
|
288
448
|
|
|
289
449
|
// ---------------------------------------------------------------------------
|
|
@@ -292,9 +452,7 @@ export function regionsFromManifestRoots(roots: readonly ResolvedAdapterRoot[]):
|
|
|
292
452
|
|
|
293
453
|
/**
|
|
294
454
|
* The rule form of a finder's region targeting: every region the host mounts
|
|
295
|
-
* from an exported composition
|
|
296
|
-
* DEV layer. A dev root is the game's own dev GUI — the manifest already says
|
|
297
|
-
* so (`dev: true`), so skipping it reads a declaration rather than guessing.
|
|
455
|
+
* from an exported composition — a root with an `entry`.
|
|
298
456
|
*/
|
|
299
457
|
export const EXPORTED_COMPOSITION_REGIONS = 'exported-composition-regions';
|
|
300
458
|
|
|
@@ -385,10 +543,44 @@ const FinderSelectionSchema = z.union([
|
|
|
385
543
|
export type SceneReach =
|
|
386
544
|
/** Mounting the region IS opening it — the single-composition degenerate case. */
|
|
387
545
|
| { readonly kind: 'root-mount' }
|
|
388
|
-
/**
|
|
389
|
-
|
|
546
|
+
/**
|
|
547
|
+
* The entrypoint's own selection table reaches it under `key`.
|
|
548
|
+
*
|
|
549
|
+
* `active` is the SECOND load-bearing fact about the same slot: the
|
|
550
|
+
* entrypoint indexes its table with one key right now (`scenes[activeScene]`),
|
|
551
|
+
* and that key's composition is literally what mounting the region renders.
|
|
552
|
+
* So an active entry is the region's own standing document — it NAMES that
|
|
553
|
+
* document rather than earning a second one beside it, exactly as
|
|
554
|
+
* `root-mount` does. Absent means "not known to be the one at the slot",
|
|
555
|
+
* which is the honest answer whenever the key cannot be read statically; it
|
|
556
|
+
* is never a guess, and it is per-entry because two regions each have their
|
|
557
|
+
* own slot with its own occupant.
|
|
558
|
+
*/
|
|
559
|
+
| {
|
|
560
|
+
readonly kind: 'entrypoint-selection';
|
|
561
|
+
readonly selection: string;
|
|
562
|
+
readonly key: string;
|
|
563
|
+
readonly active?: boolean;
|
|
564
|
+
}
|
|
390
565
|
/** A portable story mounts it in isolation. */
|
|
391
566
|
| { readonly kind: 'story'; readonly storyId: string }
|
|
567
|
+
/**
|
|
568
|
+
* The RUNNING game's own published scenes contract reaches it, under
|
|
569
|
+
* `sceneId` (`adapter/ingest/game-contract.ts`'s `scenes.list()` ids).
|
|
570
|
+
*
|
|
571
|
+
* This is the LIVE reach: opening it while playing means asking the game to
|
|
572
|
+
* navigate, the way its own buttons do. An authorable entry that also names
|
|
573
|
+
* `source` can independently own an Edit isolation document; reach never
|
|
574
|
+
* decides whether a source composition can be mounted as a piece.
|
|
575
|
+
*
|
|
576
|
+
* A DECLARATION, never an inference: the host does not sniff a mapping
|
|
577
|
+
* between a table entry and a contract id, and it does not assume the two
|
|
578
|
+
* vocabularies coincide. The adapter states which contract id this entry is,
|
|
579
|
+
* and the host validates that id against the game's OWN `list()` at the
|
|
580
|
+
* moment it navigates — so a game that stops publishing the scene refuses by
|
|
581
|
+
* name instead of navigating somewhere else.
|
|
582
|
+
*/
|
|
583
|
+
| { readonly kind: 'game-contract'; readonly sceneId: string }
|
|
392
584
|
/** Declared, with no code path that opens it. */
|
|
393
585
|
| { readonly kind: 'none'; readonly reason: string };
|
|
394
586
|
|
|
@@ -399,9 +591,11 @@ const SceneReachSchema = z.union([
|
|
|
399
591
|
kind: z.literal('entrypoint-selection'),
|
|
400
592
|
selection: z.string().min(1),
|
|
401
593
|
key: z.string().min(1),
|
|
594
|
+
active: z.boolean().optional(),
|
|
402
595
|
})
|
|
403
596
|
.strict(),
|
|
404
597
|
z.object({ kind: z.literal('story'), storyId: z.string().min(1) }).strict(),
|
|
598
|
+
z.object({ kind: z.literal('game-contract'), sceneId: z.string().min(1) }).strict(),
|
|
405
599
|
z.object({ kind: z.literal('none'), reason: z.string().min(1) }).strict(),
|
|
406
600
|
]);
|
|
407
601
|
|
|
@@ -437,6 +631,10 @@ export interface SceneEntry {
|
|
|
437
631
|
readonly authorable: boolean;
|
|
438
632
|
readonly reach: SceneReach;
|
|
439
633
|
readonly source?: SceneSource;
|
|
634
|
+
/** Optional adapter-declared prerequisite run before `source` is constructed
|
|
635
|
+
* in Edit isolation. The export is a zero-argument function; the host never
|
|
636
|
+
* infers setup from neighboring filenames or game globals. */
|
|
637
|
+
readonly isolationSetup?: SceneSource;
|
|
440
638
|
/** Which finder produced it; absent = the adapter stated it outright. */
|
|
441
639
|
readonly finder?: FinderName;
|
|
442
640
|
}
|
|
@@ -450,6 +648,7 @@ const SceneEntrySchema = z
|
|
|
450
648
|
authorable: z.boolean(),
|
|
451
649
|
reach: SceneReachSchema,
|
|
452
650
|
source: SceneSourceSchema.optional(),
|
|
651
|
+
isolationSetup: SceneSourceSchema.optional(),
|
|
453
652
|
finder: z.enum(FINDER_NAMES).optional(),
|
|
454
653
|
})
|
|
455
654
|
.strict();
|
|
@@ -481,32 +680,128 @@ const AdapterSceneTableSchema = z
|
|
|
481
680
|
// Observation
|
|
482
681
|
// ---------------------------------------------------------------------------
|
|
483
682
|
|
|
683
|
+
/**
|
|
684
|
+
* The two faces a declaration projects onto. They are exactly the two
|
|
685
|
+
* `DebugAdapter` halves (`adapter/system-adapter.ts`): `state` becomes a
|
|
686
|
+
* provider (`providers()` / `state(name)`), `command` becomes a verb
|
|
687
|
+
* (`commands()` / `invoke(name, args)`). There is deliberately no third kind —
|
|
688
|
+
* see {@link OBSERVATION_KIND_REFUSAL}.
|
|
689
|
+
*/
|
|
690
|
+
export const OBSERVATION_KINDS = ['command', 'state'] as const;
|
|
691
|
+
export type ObservationKind = (typeof OBSERVATION_KINDS)[number];
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* Why a declaration may not name a `SystemAdapters` slot here.
|
|
695
|
+
*
|
|
696
|
+
* A game's system slots are already declared through the game contract's own
|
|
697
|
+
* `systemAdapters` carrier (`adapter/ingest/game-contract.ts`), which validates
|
|
698
|
+
* each slot per-surface and reports a malformed one BY NAME
|
|
699
|
+
* (`contract-system-adapters.ts`). A second door onto the same slots is exactly
|
|
700
|
+
* the two-declarations-one-slot problem `game-contract.ts` records for `debug`,
|
|
701
|
+
* where it says outright that a second door "would let a game declare two
|
|
702
|
+
* different debug planes with no rule for which wins".
|
|
703
|
+
*/
|
|
704
|
+
const OBSERVATION_KIND_REFUSAL =
|
|
705
|
+
"observation.kind must be 'command' or 'state' — the two DebugAdapter faces a declaration " +
|
|
706
|
+
'projects onto. A SystemAdapters slot is declared through the game contract’s `systemAdapters` ' +
|
|
707
|
+
'carrier, which validates it per-surface; declaring one here would be a second door onto the ' +
|
|
708
|
+
'same slot with no rule for which wins.';
|
|
709
|
+
|
|
484
710
|
/**
|
|
485
711
|
* One statically declared observation slot. Declaring a slot and ANSWERING it
|
|
486
712
|
* live are separate facts (the two-state rule): a slot declared here whose
|
|
487
|
-
* `answer` throws or whose game never booted reads as
|
|
488
|
-
* "the game has none".
|
|
713
|
+
* `answer` throws, whose `answer` is absent, or whose game never booted reads as
|
|
714
|
+
* broken, never as "the game has none". The projector
|
|
715
|
+
* (`adapter/ingest/observation-debug-adapter.ts`) keeps that distinction: an
|
|
716
|
+
* unanswerable slot is still LISTED, and refuses by name when read.
|
|
489
717
|
*/
|
|
490
718
|
export interface ObservationDeclaration {
|
|
491
719
|
readonly id: string;
|
|
492
|
-
readonly kind:
|
|
720
|
+
readonly kind: ObservationKind;
|
|
721
|
+
readonly description?: string | undefined;
|
|
722
|
+
/** Command execution side; omitted means a local app-owned command. */
|
|
723
|
+
readonly locus?: 'client' | 'server' | undefined;
|
|
493
724
|
/**
|
|
494
725
|
* Lazily evaluated against the mounted game. The field may be a closure; the
|
|
495
726
|
* TABLE around it stays statically readable, which is the whole discipline.
|
|
727
|
+
*
|
|
728
|
+
* What `game` IS is the host's to hand over and is stated at the projector:
|
|
729
|
+
* for an ingest mount it is the REALM the game's own modules ran in, because
|
|
730
|
+
* a foreign game's public handles are the only thing the host can honestly
|
|
731
|
+
* pass (`observation-debug-adapter.ts`).
|
|
496
732
|
*/
|
|
497
|
-
readonly answer?: (game: unknown) => unknown;
|
|
733
|
+
readonly answer?: (game: unknown, ...args: unknown[]) => unknown;
|
|
498
734
|
}
|
|
499
735
|
|
|
500
736
|
const ObservationDeclarationSchema = z
|
|
501
737
|
.object({
|
|
502
738
|
id: z.string().min(1),
|
|
503
|
-
kind: z.enum(
|
|
739
|
+
kind: z.enum(OBSERVATION_KINDS, { error: () => OBSERVATION_KIND_REFUSAL }),
|
|
740
|
+
description: z.string().min(1).optional(),
|
|
741
|
+
locus: z.enum(['client', 'server']).optional(),
|
|
504
742
|
answer: z
|
|
505
743
|
.custom<(game: unknown) => unknown>((value) => typeof value === 'function', {
|
|
506
744
|
message: 'observation.answer must be a function evaluated against the mounted game',
|
|
507
745
|
})
|
|
508
746
|
.optional(),
|
|
509
747
|
})
|
|
748
|
+
.strict()
|
|
749
|
+
.refine((value) => value.kind === 'command' || value.locus === undefined, {
|
|
750
|
+
path: ['locus'],
|
|
751
|
+
message: 'observation.locus is valid only for a command declaration',
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
// ---------------------------------------------------------------------------
|
|
755
|
+
// Native input
|
|
756
|
+
// ---------------------------------------------------------------------------
|
|
757
|
+
|
|
758
|
+
/** The action value shapes the session input door already speaks. */
|
|
759
|
+
export const ADAPTER_INPUT_VALUE_TYPES = [
|
|
760
|
+
'digital',
|
|
761
|
+
'scalar',
|
|
762
|
+
'vector2',
|
|
763
|
+
'pointerDelta',
|
|
764
|
+
'pointerPosition',
|
|
765
|
+
] as const;
|
|
766
|
+
export type AdapterInputValueType = (typeof ADAPTER_INPUT_VALUE_TYPES)[number];
|
|
767
|
+
export type AdapterInputValue = boolean | number | { readonly x: number; readonly y: number };
|
|
768
|
+
|
|
769
|
+
/** One action in an app-owned input store. */
|
|
770
|
+
export interface AdapterInputAction {
|
|
771
|
+
readonly name: string;
|
|
772
|
+
readonly valueType: AdapterInputValueType;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* Adapter-side binding from the universal session input door to a game's
|
|
777
|
+
* ordinary input store. The game owns both its physical-device listeners and
|
|
778
|
+
* its state; this table only tells the host how to drive that same state for a
|
|
779
|
+
* bot or live REPL session. No component imports this contract.
|
|
780
|
+
*/
|
|
781
|
+
export interface AdapterInputBinding {
|
|
782
|
+
/** Manifest root whose native scheduler consumes these actions. */
|
|
783
|
+
readonly root: string;
|
|
784
|
+
/** Current action vocabulary. A closure permits an app-owned dynamic map. */
|
|
785
|
+
readonly actions: (game: unknown) => readonly AdapterInputAction[];
|
|
786
|
+
/** Apply one action through the app's own input-store write path. */
|
|
787
|
+
readonly set: (game: unknown, action: string, value: AdapterInputValue) => void;
|
|
788
|
+
/** Release every app-owned virtual action. */
|
|
789
|
+
readonly clear: (game: unknown) => void;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
const AdapterInputBindingSchema = z
|
|
793
|
+
.object({
|
|
794
|
+
root: z.string().min(1),
|
|
795
|
+
actions: z.custom<AdapterInputBinding['actions']>((value) => typeof value === 'function', {
|
|
796
|
+
message: 'input.actions must be a function returning the app-owned action vocabulary',
|
|
797
|
+
}),
|
|
798
|
+
set: z.custom<AdapterInputBinding['set']>((value) => typeof value === 'function', {
|
|
799
|
+
message: 'input.set must be a function writing the app-owned input store',
|
|
800
|
+
}),
|
|
801
|
+
clear: z.custom<AdapterInputBinding['clear']>((value) => typeof value === 'function', {
|
|
802
|
+
message: 'input.clear must be a function releasing app-owned virtual actions',
|
|
803
|
+
}),
|
|
804
|
+
})
|
|
510
805
|
.strict();
|
|
511
806
|
|
|
512
807
|
// ---------------------------------------------------------------------------
|
|
@@ -516,8 +811,16 @@ const ObservationDeclarationSchema = z
|
|
|
516
811
|
/** The adapter module's default export, after {@link defineAdapter}. */
|
|
517
812
|
export interface AdapterDefinition {
|
|
518
813
|
readonly regions: RegionBinding;
|
|
814
|
+
/**
|
|
815
|
+
* Per-region parameters for the `'manifest-roots'` RULE, keyed by manifest
|
|
816
|
+
* root id — the `include` globs and `mounts` surfaces the mechanical
|
|
817
|
+
* derivation cannot produce. {@link regionsFromManifestRoots} merges them;
|
|
818
|
+
* an explicit `regions` LIST carries its own and rejects these by name.
|
|
819
|
+
*/
|
|
820
|
+
readonly regionIncludes: Readonly<Record<string, AdapterRegionOverlay>>;
|
|
519
821
|
readonly scenes: AdapterSceneTable;
|
|
520
822
|
readonly observation: readonly ObservationDeclaration[];
|
|
823
|
+
readonly input?: AdapterInputBinding | undefined;
|
|
521
824
|
}
|
|
522
825
|
|
|
523
826
|
/** What a game writes. Every field optional — the near-empty adapter is the
|
|
@@ -525,17 +828,44 @@ export interface AdapterDefinition {
|
|
|
525
828
|
* native. */
|
|
526
829
|
export interface AdapterDefinitionInput {
|
|
527
830
|
readonly regions?: RegionBinding;
|
|
831
|
+
readonly regionIncludes?: Readonly<Record<string, AdapterRegionOverlay>>;
|
|
528
832
|
readonly scenes?: AdapterSceneTable;
|
|
529
833
|
readonly observation?: readonly ObservationDeclaration[];
|
|
834
|
+
readonly input?: AdapterInputBinding | undefined;
|
|
530
835
|
}
|
|
531
836
|
|
|
837
|
+
/**
|
|
838
|
+
* Why the two forms may not be combined, said in the error a game's author
|
|
839
|
+
* reads. An explicit region list REPLACES the derivation and its entries carry
|
|
840
|
+
* `include`/`mounts` themselves; layering a second table over it would put one
|
|
841
|
+
* fact in two homes, with no rule for which wins.
|
|
842
|
+
*/
|
|
843
|
+
const REGION_INCLUDES_WITH_LIST_REFUSAL =
|
|
844
|
+
'`regionIncludes` states parameters for the `manifest-roots` RULE, so it cannot be paired ' +
|
|
845
|
+
'with an explicit `regions` list — that form is the full replacement, and each entry carries ' +
|
|
846
|
+
'its own `include`/`mounts`. Move these globs onto the matching `regions[]` entry, or drop the ' +
|
|
847
|
+
'list and let the derivation run.';
|
|
848
|
+
|
|
532
849
|
export const AdapterDefinitionSchema = z
|
|
533
850
|
.object({
|
|
534
851
|
regions: RegionBindingSchema.optional(),
|
|
852
|
+
regionIncludes: RegionIncludesSchema.optional(),
|
|
535
853
|
scenes: AdapterSceneTableSchema.optional(),
|
|
536
854
|
observation: z.array(ObservationDeclarationSchema).optional(),
|
|
855
|
+
input: AdapterInputBindingSchema.optional(),
|
|
537
856
|
})
|
|
538
|
-
.strict()
|
|
857
|
+
.strict()
|
|
858
|
+
// An EMPTY table beside a list is fine and must stay fine: `defineAdapter`
|
|
859
|
+
// publishes `regionIncludes: {}` on every definition, and its output is
|
|
860
|
+
// parsed a second time by the host's loader — a refusal on the key's mere
|
|
861
|
+
// presence would reject the module's own valid output on the round trip.
|
|
862
|
+
.refine(
|
|
863
|
+
(value) =>
|
|
864
|
+
value.regionIncludes === undefined ||
|
|
865
|
+
Object.keys(value.regionIncludes).length === 0 ||
|
|
866
|
+
!Array.isArray(value.regions),
|
|
867
|
+
{ message: REGION_INCLUDES_WITH_LIST_REFUSAL },
|
|
868
|
+
);
|
|
539
869
|
|
|
540
870
|
/**
|
|
541
871
|
* Validate and freeze a game's binding table.
|
|
@@ -557,8 +887,10 @@ export function defineAdapter(input: AdapterDefinitionInput = {}): AdapterDefini
|
|
|
557
887
|
}
|
|
558
888
|
return Object.freeze({
|
|
559
889
|
regions: input.regions ?? 'manifest-roots',
|
|
890
|
+
regionIncludes: Object.freeze({ ...(input.regionIncludes ?? {}) }),
|
|
560
891
|
scenes: Object.freeze({ ...(input.scenes ?? {}) }),
|
|
561
892
|
observation: Object.freeze([...(input.observation ?? [])]),
|
|
893
|
+
...(input.input ? { input: input.input } : {}),
|
|
562
894
|
});
|
|
563
895
|
}
|
|
564
896
|
|
|
@@ -566,8 +898,9 @@ export function defineAdapter(input: AdapterDefinitionInput = {}): AdapterDefini
|
|
|
566
898
|
export interface NativeAdapterOptions {
|
|
567
899
|
/** Scene-table entries this game states outright, beside what the finders find. */
|
|
568
900
|
readonly scenes?: readonly SceneEntry[];
|
|
569
|
-
/**
|
|
570
|
-
|
|
901
|
+
/** Per-region `include`/`mounts` parameters for the derivation — see
|
|
902
|
+
* {@link AdapterDefinition.regionIncludes}. */
|
|
903
|
+
readonly regionIncludes?: Readonly<Record<string, AdapterRegionOverlay>>;
|
|
571
904
|
}
|
|
572
905
|
|
|
573
906
|
/**
|
|
@@ -580,6 +913,7 @@ export interface NativeAdapterOptions {
|
|
|
580
913
|
export function nativeAdapter(options: NativeAdapterOptions = {}): AdapterDefinition {
|
|
581
914
|
return defineAdapter({
|
|
582
915
|
regions: 'manifest-roots',
|
|
916
|
+
...(options.regionIncludes ? { regionIncludes: options.regionIncludes } : {}),
|
|
583
917
|
scenes: {
|
|
584
918
|
...(options.scenes ? { entries: options.scenes } : {}),
|
|
585
919
|
find: [
|
|
@@ -587,7 +921,6 @@ export function nativeAdapter(options: NativeAdapterOptions = {}): AdapterDefini
|
|
|
587
921
|
{ finder: 'prefabsFromStories' },
|
|
588
922
|
],
|
|
589
923
|
},
|
|
590
|
-
...(options.observation ? { observation: options.observation } : {}),
|
|
591
924
|
});
|
|
592
925
|
}
|
|
593
926
|
|