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