@vgai/engine 0.5.13 → 0.5.14
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 +447 -0
- package/dist/adapter/adapter-module.d.ts.map +1 -0
- package/dist/adapter/adapter-module.js +308 -0
- package/dist/adapter/authoring.d.ts +357 -10
- package/dist/adapter/authoring.d.ts.map +1 -1
- package/dist/adapter/authoring.js +87 -1
- package/dist/adapter/constraint.d.ts +9 -3
- package/dist/adapter/constraint.d.ts.map +1 -1
- package/dist/adapter/finders/finder-result.d.ts +28 -0
- package/dist/adapter/finders/finder-result.d.ts.map +1 -0
- package/dist/adapter/finders/finder-result.js +10 -0
- package/dist/adapter/finders/index.d.ts +38 -0
- package/dist/adapter/finders/index.d.ts.map +1 -0
- package/dist/adapter/finders/index.js +41 -0
- package/dist/adapter/finders/prefabs-from-stories.d.ts +64 -0
- package/dist/adapter/finders/prefabs-from-stories.d.ts.map +1 -0
- package/dist/adapter/finders/prefabs-from-stories.js +92 -0
- package/dist/adapter/finders/scenes-from-entrypoint-selection.d.ts +67 -0
- package/dist/adapter/finders/scenes-from-entrypoint-selection.d.ts.map +1 -0
- package/dist/adapter/finders/scenes-from-entrypoint-selection.js +226 -0
- package/dist/adapter/first-party-systems.d.ts.map +1 -1
- package/dist/adapter/first-party-systems.js +29 -0
- package/dist/adapter/index.d.ts +7 -3
- package/dist/adapter/index.d.ts.map +1 -1
- package/dist/adapter/index.js +5 -2
- package/dist/adapter/ingest/contract-system-adapters.d.ts +24 -1
- package/dist/adapter/ingest/contract-system-adapters.d.ts.map +1 -1
- package/dist/adapter/ingest/contract-system-adapters.js +100 -34
- package/dist/adapter/ingest/game-contract.d.ts +188 -2
- package/dist/adapter/ingest/game-contract.d.ts.map +1 -1
- package/dist/adapter/ingest/game-contract.js +107 -0
- package/dist/adapter/ingest/mount-readiness.d.ts +84 -0
- package/dist/adapter/ingest/mount-readiness.d.ts.map +1 -0
- package/dist/adapter/ingest/mount-readiness.js +81 -0
- package/dist/adapter/ingest/scene-capture.d.ts +91 -2
- package/dist/adapter/ingest/scene-capture.d.ts.map +1 -1
- package/dist/adapter/ingest/scene-capture.js +110 -18
- package/dist/adapter/ingest/structural-ids.d.ts +11 -5
- package/dist/adapter/ingest/structural-ids.d.ts.map +1 -1
- package/dist/adapter/ingest/structural-ids.js +18 -9
- package/dist/adapter/ingest/upstream-pin.d.ts +4 -2
- package/dist/adapter/ingest/upstream-pin.d.ts.map +1 -1
- package/dist/adapter/ingest/upstream-pin.js +4 -2
- package/dist/adapter/ingest/visible-capture-window.d.ts +116 -0
- package/dist/adapter/ingest/visible-capture-window.d.ts.map +1 -0
- package/dist/adapter/ingest/visible-capture-window.js +141 -0
- package/dist/adapter/loop-gate-report.d.ts +3 -4
- package/dist/adapter/loop-gate-report.d.ts.map +1 -1
- package/dist/adapter/loop-gate-report.js +3 -4
- package/dist/adapter/root-adapter.d.ts +22 -2
- package/dist/adapter/root-adapter.d.ts.map +1 -1
- package/dist/adapter/setup-three-root-adapter.d.ts.map +1 -1
- package/dist/adapter/setup-three-root-adapter.js +15 -0
- package/dist/adapter/system-adapter.d.ts +161 -2
- package/dist/adapter/system-adapter.d.ts.map +1 -1
- package/dist/adapter/system-adapter.js +18 -1
- package/dist/animation/camera-ownership.d.ts +4 -2
- package/dist/animation/camera-ownership.d.ts.map +1 -1
- package/dist/animation/camera-ownership.js +58 -1
- package/dist/animation/runtime-inspection.d.ts +70 -0
- package/dist/animation/runtime-inspection.d.ts.map +1 -0
- package/dist/animation/runtime-inspection.js +25 -0
- package/dist/animation/xstate-animation-binding.d.ts +9 -1
- package/dist/animation/xstate-animation-binding.d.ts.map +1 -1
- package/dist/animation/xstate-animation-binding.js +48 -1
- package/dist/asset-formats/material.d.ts +1 -1
- package/dist/asset-formats/mesh.d.ts +1 -1
- package/dist/asset-formats/particles.d.ts +13 -0
- package/dist/asset-formats/particles.d.ts.map +1 -1
- package/dist/asset-formats/particles.js +28 -0
- package/dist/behavior/xstate-inspection.d.ts +24 -0
- package/dist/behavior/xstate-inspection.d.ts.map +1 -0
- package/dist/behavior/xstate-inspection.js +25 -0
- package/dist/core/game-loop.d.ts +7 -5
- package/dist/core/game-loop.d.ts.map +1 -1
- package/dist/core/game-loop.js +39 -18
- package/dist/core/types.d.ts +11 -12
- package/dist/core/types.d.ts.map +1 -1
- package/dist/data/data-asset.js +1 -1
- package/dist/dev/instruments.d.ts.map +1 -1
- package/dist/dev/instruments.js +5 -1
- package/dist/dev/render-debug-adapter.d.ts +19 -2
- package/dist/dev/render-debug-adapter.d.ts.map +1 -1
- package/dist/dev/render-debug-adapter.js +10 -0
- package/dist/ecs/user-data.d.ts +10 -2
- package/dist/ecs/user-data.d.ts.map +1 -1
- package/dist/ecs/user-data.js +6 -2
- package/dist/input/binding-identity.d.ts +10 -0
- package/dist/input/binding-identity.d.ts.map +1 -0
- package/dist/input/binding-identity.js +36 -0
- package/dist/input/input-manager.d.ts +18 -185
- package/dist/input/input-manager.d.ts.map +1 -1
- package/dist/input/input-manager.js +78 -68
- package/dist/manifest/load.d.ts +14 -0
- package/dist/manifest/load.d.ts.map +1 -1
- package/dist/manifest/load.js +6 -1
- package/dist/manifest/schema.d.ts +18 -0
- package/dist/manifest/schema.d.ts.map +1 -1
- package/dist/manifest/schema.js +64 -7
- package/dist/pixi/authoring.d.ts +27 -0
- package/dist/pixi/authoring.d.ts.map +1 -1
- package/dist/pixi/authoring.js +10 -3
- package/dist/pixi/index.d.ts +1 -1
- package/dist/pixi/index.d.ts.map +1 -1
- package/dist/pixi/index.js +1 -1
- package/dist/pixi/ingest.d.ts +8 -0
- package/dist/pixi/ingest.d.ts.map +1 -1
- package/dist/pixi/ingest.js +10 -1
- package/dist/pixi/render-pass-bracket.d.ts +56 -0
- package/dist/pixi/render-pass-bracket.d.ts.map +1 -0
- package/dist/pixi/render-pass-bracket.js +98 -0
- package/dist/pixi/scene-capture.d.ts +32 -1
- package/dist/pixi/scene-capture.d.ts.map +1 -1
- package/dist/pixi/scene-capture.js +21 -9
- package/dist/pixi/system-adapters.d.ts +29 -0
- package/dist/pixi/system-adapters.d.ts.map +1 -1
- package/dist/pixi/system-adapters.js +27 -0
- package/dist/render/particles-factory.d.ts +16 -2
- package/dist/render/particles-factory.d.ts.map +1 -1
- package/dist/render/particles-factory.js +88 -5
- package/dist/render/soft-particle-depth.d.ts +149 -0
- package/dist/render/soft-particle-depth.d.ts.map +1 -0
- package/dist/render/soft-particle-depth.js +305 -0
- package/dist/render/voxel-surface.d.ts +117 -0
- package/dist/render/voxel-surface.d.ts.map +1 -0
- package/dist/render/voxel-surface.js +263 -0
- package/dist/runtime/create-runtime.js +7 -0
- package/dist/runtime/debug-bridge.d.ts +15 -15
- package/dist/runtime/debug-bridge.d.ts.map +1 -1
- package/dist/runtime/debug-bridge.js +27 -26
- package/dist/runtime/game.d.ts.map +1 -1
- package/dist/runtime/game.js +1 -2
- package/dist/setup/setup-particles.d.ts.map +1 -1
- package/dist/setup/setup-particles.js +9 -0
- package/dist/world3d-react/r3f-adapter.d.ts.map +1 -1
- package/dist/world3d-react/r3f-adapter.js +42 -3
- package/dist/world3d-react/rapier-physics-bridge.d.ts.map +1 -1
- package/dist/world3d-react/rapier-physics-bridge.js +201 -11
- package/dist-config/config.js.map +1 -1
- package/package.json +4 -2
- package/schemas/engine-capabilities.json +12 -12
- package/schemas/vgai-project.schema.json +41 -2
- package/src/adapter/adapter-module.ts +607 -0
- package/src/adapter/authoring.ts +440 -10
- package/src/adapter/constraint.ts +10 -3
- package/src/adapter/finders/finder-result.ts +29 -0
- package/src/adapter/finders/index.ts +62 -0
- package/src/adapter/finders/prefabs-from-stories.ts +142 -0
- package/src/adapter/finders/scenes-from-entrypoint-selection.ts +304 -0
- package/src/adapter/first-party-systems.ts +30 -0
- package/src/adapter/index.ts +32 -1
- package/src/adapter/ingest/contract-system-adapters.ts +116 -32
- package/src/adapter/ingest/game-contract.ts +265 -2
- package/src/adapter/ingest/mount-readiness.ts +140 -0
- package/src/adapter/ingest/scene-capture.ts +199 -22
- package/src/adapter/ingest/structural-ids.ts +21 -9
- package/src/adapter/ingest/upstream-pin.ts +4 -2
- package/src/adapter/ingest/visible-capture-window.ts +213 -0
- package/src/adapter/loop-gate-report.ts +3 -4
- package/src/adapter/root-adapter.ts +22 -2
- package/src/adapter/setup-three-root-adapter.ts +16 -0
- package/src/adapter/system-adapter.ts +188 -2
- package/src/animation/camera-ownership.ts +68 -3
- package/src/animation/runtime-inspection.ts +104 -0
- package/src/animation/xstate-animation-binding.ts +53 -1
- package/src/asset-formats/particles.ts +31 -0
- package/src/behavior/xstate-inspection.ts +51 -0
- package/src/core/game-loop.ts +39 -17
- package/src/core/types.ts +11 -12
- package/src/data/data-asset.ts +1 -1
- package/src/dev/instruments.ts +5 -1
- package/src/dev/render-debug-adapter.ts +22 -2
- package/src/ecs/user-data.ts +10 -2
- package/src/input/binding-identity.ts +37 -0
- package/src/input/input-manager.ts +93 -68
- package/src/manifest/load.ts +23 -2
- package/src/manifest/schema.ts +76 -7
- package/src/pixi/authoring.ts +37 -5
- package/src/pixi/index.ts +2 -0
- package/src/pixi/ingest.ts +15 -2
- package/src/pixi/render-pass-bracket.ts +117 -0
- package/src/pixi/scene-capture.ts +63 -13
- package/src/pixi/system-adapters.ts +41 -0
- package/src/render/particles-factory.ts +89 -6
- package/src/render/soft-particle-depth.ts +336 -0
- package/src/render/voxel-surface.ts +358 -0
- package/src/runtime/create-runtime.ts +7 -0
- package/src/runtime/debug-bridge.ts +32 -31
- package/src/runtime/game.ts +1 -2
- package/src/setup/setup-particles.ts +9 -0
- package/src/world3d-react/r3f-adapter.tsx +48 -2
- package/src/world3d-react/rapier-physics-bridge.tsx +249 -11
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `vgai.adapter.ts` — THE ADAPTER MODULE CONTRACT.
|
|
3
|
+
*
|
|
4
|
+
* ARCHITECTURE-CORE §The editor protocol, "The adapter is the game's own
|
|
5
|
+
* module": the editor is a universal CLIENT of a fixed protocol and every game
|
|
6
|
+
* supplies a SERVER — its adapter. That adapter is the game's OWN module,
|
|
7
|
+
* `vgai.adapter.ts`, sitting beside `vgai.project.json`, default-exporting
|
|
8
|
+
* {@link defineAdapter}'s result.
|
|
9
|
+
*
|
|
10
|
+
* It is CODE (bindings are expressions — a scene entry may carry a closure the
|
|
11
|
+
* host evaluates against the mounted game) with a CONFIG's discipline: the top
|
|
12
|
+
* level is a STATICALLY EVALUABLE BINDING TABLE, readable without booting the
|
|
13
|
+
* game. Concretely, that is the split this module enforces:
|
|
14
|
+
*
|
|
15
|
+
* - the TABLE SHAPE — regions, the scene table, observation declarations —
|
|
16
|
+
* is plain data, validated by {@link AdapterDefinitionSchema} the moment
|
|
17
|
+
* the module is evaluated. `.strict()` throughout: an unrecognized key is
|
|
18
|
+
* an error naming the key, never a silent pass-through (CLAUDE.md,
|
|
19
|
+
* "Unknown input must REJECT LOUDLY").
|
|
20
|
+
* - individual FIELDS may be closures, evaluated lazily against the mounted
|
|
21
|
+
* game (`ObservationDeclaration.answer`). A closure is never required to
|
|
22
|
+
* read the table.
|
|
23
|
+
*
|
|
24
|
+
* **Zero inference.** A binding is a DECLARATION or a finder SELECTION the
|
|
25
|
+
* adapter makes — never something the host sniffs out. Finder algorithms
|
|
26
|
+
* (scenes from an entrypoint selection, prefabs from story registrations, …)
|
|
27
|
+
* ship host-side under `adapter/finders/`, and this module names them by
|
|
28
|
+
* SELECTION only ({@link FinderSelection}). That is deliberate and load-bearing
|
|
29
|
+
* twice over:
|
|
30
|
+
* 1. the engine never runs a finder nobody selected — enforced mechanically
|
|
31
|
+
* by `packages/engine/test/finder-import-boundary.test.ts`, whose only
|
|
32
|
+
* sanctioned importer of the finder namespace is the editor's adapter
|
|
33
|
+
* loader;
|
|
34
|
+
* 2. this module therefore imports NOTHING from `adapter/finders/`, so a
|
|
35
|
+
* game's `vgai.adapter.ts` pulls no finder implementation (and no source
|
|
36
|
+
* parser) into its own bundle. The dependency runs one way: finders
|
|
37
|
+
* import their parameter types from HERE.
|
|
38
|
+
*
|
|
39
|
+
* The manifest keeps only what must be readable without evaluating any module
|
|
40
|
+
* — identity, roots, boot mode, `server.room`. Everything that binds behavior
|
|
41
|
+
* is the adapter's. This wave adds NO manifest field.
|
|
42
|
+
*/
|
|
43
|
+
import { z } from 'zod';
|
|
44
|
+
import { WRITE_ANCHOR_KINDS } from './authoring';
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Regions
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
/**
|
|
49
|
+
* The projector serving a surface — one per surface, host-owned
|
|
50
|
+
* (ARCHITECTURE-CORE: "Projection — nodes, stable identity, bounds, picking …
|
|
51
|
+
* one projector per surface"). Named, never imported: the adapter states WHICH
|
|
52
|
+
* library serves the region; the host binds the implementation.
|
|
53
|
+
*/
|
|
54
|
+
export const PROJECTOR_NAMES = ['three', 'pixi', 'dom'];
|
|
55
|
+
/**
|
|
56
|
+
* The NATIVE basis — what {@link regionsFromManifestRoots} emits and what a
|
|
57
|
+
* region that declares none is read as. +Y up, ground at 0: three.js' own
|
|
58
|
+
* convention, which every first-party root is authored in. Stated as a value so
|
|
59
|
+
* "the default" has one home instead of a copy at each reader.
|
|
60
|
+
*/
|
|
61
|
+
export const NATIVE_REGION_BASIS = Object.freeze({
|
|
62
|
+
up: 'y',
|
|
63
|
+
groundHeight: 0,
|
|
64
|
+
});
|
|
65
|
+
const AdapterRegionBasisSchema = z
|
|
66
|
+
.object({
|
|
67
|
+
up: z.enum(['y', 'z']).describe('World axis pointing away from the ground'),
|
|
68
|
+
groundHeight: z.number().describe('Ground offset along `up`, in world units'),
|
|
69
|
+
})
|
|
70
|
+
.strict();
|
|
71
|
+
const AdapterRegionSchema = z
|
|
72
|
+
.object({
|
|
73
|
+
id: z.string().min(1).describe('Manifest root id this region grades'),
|
|
74
|
+
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
|
+
projector: z.enum(PROJECTOR_NAMES).describe('Projection library serving this surface, by name'),
|
|
80
|
+
dialect: z
|
|
81
|
+
.string()
|
|
82
|
+
.min(1)
|
|
83
|
+
.nullable()
|
|
84
|
+
.describe('Dialect writer serving this region, by name; null states outright that the host writes ' +
|
|
85
|
+
"no source here (the region's truth is the game's own code)"),
|
|
86
|
+
anchors: z
|
|
87
|
+
.array(z.enum(WRITE_ANCHOR_KINDS))
|
|
88
|
+
.describe('Anchor kinds this region’s writer serves'),
|
|
89
|
+
basis: AdapterRegionBasisSchema.optional().describe('World basis (up axis + ground height); omit for the native +Y / 0 default'),
|
|
90
|
+
include: z
|
|
91
|
+
.array(z.string().min(1))
|
|
92
|
+
.min(1)
|
|
93
|
+
.optional()
|
|
94
|
+
.describe('Project-relative globs (`**`, `*`, `?`) naming source files this region owns, for files ' +
|
|
95
|
+
'no root entry’s import closure reaches; a declared include wins over reach'),
|
|
96
|
+
})
|
|
97
|
+
.strict();
|
|
98
|
+
const RegionBindingSchema = z.union([
|
|
99
|
+
z.literal('manifest-roots'),
|
|
100
|
+
z.array(AdapterRegionSchema).min(1),
|
|
101
|
+
]);
|
|
102
|
+
/**
|
|
103
|
+
* The MECHANICAL derivation behind the `'manifest-roots'` rule: one region per
|
|
104
|
+
* declared root, its surface and adapter identity read straight off the
|
|
105
|
+
* RESOLVED manifest (`@engine/manifest/load` already decided both — this never
|
|
106
|
+
* re-derives them).
|
|
107
|
+
*
|
|
108
|
+
* The per-surface library table below is an ENGINE fact, stated once:
|
|
109
|
+
*
|
|
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 |
|
|
117
|
+
*
|
|
118
|
+
* A module/ingest root's truth is the game's own source, which no first-party
|
|
119
|
+
* dialect writer authors — so it declares `live-only` rather than implying a
|
|
120
|
+
* write path it does not have. That is a red cell and therefore a work order,
|
|
121
|
+
* which is the point of stating it.
|
|
122
|
+
*
|
|
123
|
+
* Every derived region carries {@link NATIVE_REGION_BASIS} outright, so the
|
|
124
|
+
* editor's ground plane READS a declaration on the native path too — the
|
|
125
|
+
* default is published, never assumed at the read site.
|
|
126
|
+
*/
|
|
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
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
id: root.id,
|
|
158
|
+
surface,
|
|
159
|
+
dev,
|
|
160
|
+
projector,
|
|
161
|
+
dialect: surface === 'three' ? 'r3f' : 'pixi-react',
|
|
162
|
+
anchors: ['source-prop', 'construction-literal'],
|
|
163
|
+
basis,
|
|
164
|
+
};
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
// ---------------------------------------------------------------------------
|
|
168
|
+
// Finder selections
|
|
169
|
+
// ---------------------------------------------------------------------------
|
|
170
|
+
/**
|
|
171
|
+
* 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.
|
|
175
|
+
*/
|
|
176
|
+
export const EXPORTED_COMPOSITION_REGIONS = 'exported-composition-regions';
|
|
177
|
+
/** The finder names, as a value — what the loader's dispatcher switches on. */
|
|
178
|
+
export const FINDER_NAMES = ['scenesFromEntrypointSelection', 'prefabsFromStories'];
|
|
179
|
+
const ScenesFromEntrypointSelectionSchema = z
|
|
180
|
+
.object({
|
|
181
|
+
finder: z.literal('scenesFromEntrypointSelection'),
|
|
182
|
+
regions: z
|
|
183
|
+
.union([z.array(z.string().min(1)).min(1), z.literal(EXPORTED_COMPOSITION_REGIONS)])
|
|
184
|
+
.describe('Region ids whose entrypoints carry the selection, or the rule form'),
|
|
185
|
+
selection: z
|
|
186
|
+
.string()
|
|
187
|
+
.min(1)
|
|
188
|
+
.optional()
|
|
189
|
+
.describe("Identifier of the entrypoint's module-level scene selection table; omit for a " +
|
|
190
|
+
'single-composition entrypoint'),
|
|
191
|
+
})
|
|
192
|
+
.strict()
|
|
193
|
+
.refine((value) => value.selection === undefined || Array.isArray(value.regions), {
|
|
194
|
+
message: 'scenesFromEntrypointSelection: `selection` names an identifier in ONE entrypoint’s ' +
|
|
195
|
+
`source, so it cannot be paired with the \`${EXPORTED_COMPOSITION_REGIONS}\` rule — ` +
|
|
196
|
+
'name the region explicitly (`regions: ["<rootId>"]`).',
|
|
197
|
+
});
|
|
198
|
+
const PrefabsFromStoriesSchema = z.object({ finder: z.literal('prefabsFromStories') }).strict();
|
|
199
|
+
const FinderSelectionSchema = z.union([
|
|
200
|
+
ScenesFromEntrypointSelectionSchema,
|
|
201
|
+
PrefabsFromStoriesSchema,
|
|
202
|
+
]);
|
|
203
|
+
const SceneReachSchema = z.union([
|
|
204
|
+
z.object({ kind: z.literal('root-mount') }).strict(),
|
|
205
|
+
z
|
|
206
|
+
.object({
|
|
207
|
+
kind: z.literal('entrypoint-selection'),
|
|
208
|
+
selection: z.string().min(1),
|
|
209
|
+
key: z.string().min(1),
|
|
210
|
+
})
|
|
211
|
+
.strict(),
|
|
212
|
+
z.object({ kind: z.literal('story'), storyId: z.string().min(1) }).strict(),
|
|
213
|
+
z.object({ kind: z.literal('none'), reason: z.string().min(1) }).strict(),
|
|
214
|
+
]);
|
|
215
|
+
const SceneSourceSchema = z
|
|
216
|
+
.object({ path: z.string().min(1), export: z.string().min(1).optional() })
|
|
217
|
+
.strict();
|
|
218
|
+
const SceneEntrySchema = z
|
|
219
|
+
.object({
|
|
220
|
+
id: z.string().min(1),
|
|
221
|
+
label: z.string().min(1),
|
|
222
|
+
kind: z.enum(['scene', 'prefab']),
|
|
223
|
+
region: z.string().min(1).nullable(),
|
|
224
|
+
authorable: z.boolean(),
|
|
225
|
+
reach: SceneReachSchema,
|
|
226
|
+
source: SceneSourceSchema.optional(),
|
|
227
|
+
finder: z.enum(FINDER_NAMES).optional(),
|
|
228
|
+
})
|
|
229
|
+
.strict();
|
|
230
|
+
const AdapterSceneTableSchema = z
|
|
231
|
+
.object({
|
|
232
|
+
entries: z.array(SceneEntrySchema).optional(),
|
|
233
|
+
find: z.array(FinderSelectionSchema).optional(),
|
|
234
|
+
default: z.string().min(1).optional(),
|
|
235
|
+
})
|
|
236
|
+
.strict();
|
|
237
|
+
const ObservationDeclarationSchema = z
|
|
238
|
+
.object({
|
|
239
|
+
id: z.string().min(1),
|
|
240
|
+
kind: z.enum(['system', 'command', 'state']),
|
|
241
|
+
answer: z
|
|
242
|
+
.custom((value) => typeof value === 'function', {
|
|
243
|
+
message: 'observation.answer must be a function evaluated against the mounted game',
|
|
244
|
+
})
|
|
245
|
+
.optional(),
|
|
246
|
+
})
|
|
247
|
+
.strict();
|
|
248
|
+
export const AdapterDefinitionSchema = z
|
|
249
|
+
.object({
|
|
250
|
+
regions: RegionBindingSchema.optional(),
|
|
251
|
+
scenes: AdapterSceneTableSchema.optional(),
|
|
252
|
+
observation: z.array(ObservationDeclarationSchema).optional(),
|
|
253
|
+
})
|
|
254
|
+
.strict();
|
|
255
|
+
/**
|
|
256
|
+
* Validate and freeze a game's binding table.
|
|
257
|
+
*
|
|
258
|
+
* Called TWICE by design and with one schema: once here, in the game's own
|
|
259
|
+
* module, so a malformed table fails at the author's own file with the key
|
|
260
|
+
* named; and once in the host's loader, because the loaded default export is
|
|
261
|
+
* untrusted input crossing a module boundary. Two call sites, one rule — not
|
|
262
|
+
* two ways to say the same thing.
|
|
263
|
+
*/
|
|
264
|
+
export function defineAdapter(input = {}) {
|
|
265
|
+
const parsed = AdapterDefinitionSchema.safeParse(input);
|
|
266
|
+
if (!parsed.success) {
|
|
267
|
+
throw new Error(`vgai.adapter.ts: invalid adapter definition — ${parsed.error.issues
|
|
268
|
+
.map((issue) => `${issue.path.join('.') || '(root)'}: ${issue.message}`)
|
|
269
|
+
.join('; ')}`);
|
|
270
|
+
}
|
|
271
|
+
return Object.freeze({
|
|
272
|
+
regions: input.regions ?? 'manifest-roots',
|
|
273
|
+
scenes: Object.freeze({ ...(input.scenes ?? {}) }),
|
|
274
|
+
observation: Object.freeze([...(input.observation ?? [])]),
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* THE NATIVE DEFAULT — regions derived mechanically from the manifest's
|
|
279
|
+
* `roots[]`, and a scene table produced by the two shipped finders. This is
|
|
280
|
+
* what a first-party project ships (`export default nativeAdapter()`), and it
|
|
281
|
+
* is also what a project with NO `vgai.adapter.ts` gets: that absence is the
|
|
282
|
+
* declared native default, not a silent fallback.
|
|
283
|
+
*/
|
|
284
|
+
export function nativeAdapter(options = {}) {
|
|
285
|
+
return defineAdapter({
|
|
286
|
+
regions: 'manifest-roots',
|
|
287
|
+
scenes: {
|
|
288
|
+
...(options.scenes ? { entries: options.scenes } : {}),
|
|
289
|
+
find: [
|
|
290
|
+
{ finder: 'scenesFromEntrypointSelection', regions: EXPORTED_COMPOSITION_REGIONS },
|
|
291
|
+
{ finder: 'prefabsFromStories' },
|
|
292
|
+
],
|
|
293
|
+
},
|
|
294
|
+
...(options.observation ? { observation: options.observation } : {}),
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Parse an untrusted default export (the loaded `vgai.adapter.ts`) into a
|
|
299
|
+
* definition. Rejects loudly — an unknown key, a bad finder name, a
|
|
300
|
+
* `selection` paired with the rule form all fail by name.
|
|
301
|
+
*/
|
|
302
|
+
export function parseAdapterDefinition(value) {
|
|
303
|
+
if (value === null || typeof value !== 'object') {
|
|
304
|
+
throw new Error('vgai.adapter.ts must default-export defineAdapter({…}) / nativeAdapter() — ' +
|
|
305
|
+
`got ${value === null ? 'null' : typeof value}`);
|
|
306
|
+
}
|
|
307
|
+
return defineAdapter(value);
|
|
308
|
+
}
|