@vgai/engine 0.2.0 → 0.4.0-canary.20260715.0
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/README.md +3 -1
- package/package.json +24 -4
- package/schemas/engine-api.json +124 -0
- package/schemas/engine-api.md +53 -0
- package/schemas/engine-capabilities.json +124 -0
- package/schemas/inputmap.schema.json +314 -0
- package/schemas/mat.schema.json +286 -0
- package/schemas/prefab.schema.json +10148 -0
- package/schemas/scn2d.schema.json +475 -0
- package/schemas/vgai-game.schema.json +383 -0
- package/schemas/vscn.schema.json +11007 -0
- package/src/adapter/{world-kind.ts → adapter-surface.ts} +6 -6
- package/src/adapter/authoring.ts +77 -0
- package/src/adapter/first-party-systems.ts +23 -34
- package/src/adapter/game-adapter.ts +8 -8
- package/src/adapter/host-context.ts +2 -4
- package/src/adapter/index.ts +4 -4
- package/src/adapter/system-adapter.ts +88 -22
- package/src/adapter/vgai-scene-game-adapter.ts +244 -194
- package/src/animation/anim-graph-types.ts +12 -43
- package/src/animation/animation-clock.ts +479 -0
- package/src/animation/camera-ownership.ts +467 -0
- package/src/animation/cinematic-cues.ts +451 -0
- package/src/animation/clip-map.ts +41 -0
- package/src/animation/gsap-registration.ts +184 -0
- package/src/animation/theatre-clock-binding.ts +111 -0
- package/src/animation/theatre-director.ts +347 -0
- package/src/animation/theatre-object-binding.ts +661 -0
- package/src/animation/xstate-animation-binding.ts +436 -0
- package/src/animation/xstate-animation-meta.ts +319 -0
- package/src/audio/index.ts +39 -7
- package/src/audio/tone-clock-binding.ts +98 -0
- package/src/audio/tone-context.ts +129 -0
- package/src/audio/tone-offline-render.ts +167 -0
- package/src/audio/wav-encode.ts +119 -0
- package/src/character/cloth-sim.ts +533 -0
- package/src/character/spring-chain.ts +307 -0
- package/src/core/game-loop.ts +57 -2
- package/src/core/seeded-random.ts +161 -0
- package/src/core/system-runner.ts +20 -3
- package/src/core/types.ts +50 -0
- package/src/data/data-asset.ts +167 -0
- package/src/data/data-check-core.ts +242 -0
- package/src/data/data-ref.ts +145 -0
- package/src/data/vite-plugin-data.ts +290 -0
- package/src/dev/performance-profiler.ts +213 -0
- package/src/dev/webgl-gpu-timer.ts +53 -0
- package/src/ecs/component-manager.ts +45 -12
- package/src/ecs/game-component.ts +95 -11
- package/src/humanoid/bake.operation.ts +326 -0
- package/src/humanoid/body.ts +663 -0
- package/src/humanoid/clips.ts +149 -0
- package/src/humanoid/compose.ts +209 -0
- package/src/humanoid/generate.ts +189 -0
- package/src/humanoid/index.ts +36 -0
- package/src/humanoid/schema.ts +108 -0
- package/src/humanoid/skeleton.ts +345 -0
- package/src/index.ts +48 -0
- package/src/input/input-manager.ts +1886 -33
- package/src/input/input-types.ts +158 -3
- package/src/input/prompt-labels.ts +122 -0
- package/src/input/rebind-controller.ts +105 -0
- package/src/input/schema.ts +206 -52
- package/src/manifest/index.ts +5 -5
- package/src/manifest/load.ts +125 -72
- package/src/manifest/schema.ts +362 -255
- package/src/react/game-state.tsx +135 -32
- package/src/react/root-adapter.tsx +49 -0
- package/src/react/unmanaged-root-detector.ts +66 -0
- package/src/react/use-data.ts +124 -0
- package/src/react/use-selection.tsx +135 -0
- package/src/runtime/create-runtime.ts +112 -273
- package/src/runtime/debug-bridge.ts +483 -0
- package/src/runtime/debug-registry.ts +856 -0
- package/src/runtime/game.ts +342 -93
- package/src/runtime/gameplay-rng-trap.ts +134 -0
- package/src/runtime/input-router.ts +7 -7
- package/src/runtime/mount-game.ts +40 -38
- package/src/runtime/mount-manifest.ts +169 -37
- package/src/runtime/render-audio-control.ts +168 -0
- package/src/runtime/render-control.ts +522 -0
- package/src/runtime/render-seed.ts +79 -0
- package/src/runtime/state-bridge.ts +24 -10
- package/src/runtime/types.ts +110 -33
- package/src/scene/asset-loaders.ts +10 -36
- package/src/scene/asset-paths.ts +0 -2
- package/src/scene/asset-ref-check.ts +248 -0
- package/src/scene/asset-registry.ts +22 -0
- package/src/scene/component-registry.ts +14 -3
- package/src/scene/defaults.ts +1 -0
- package/src/scene/light-camera-factory.ts +11 -3
- package/src/scene/parse.ts +133 -0
- package/src/scene/scene-apply.ts +55 -4
- package/src/scene/scene-loader.ts +91 -123
- package/src/scene/scene-types.ts +0 -1
- package/src/scene/schema/animation.ts +30 -79
- package/src/scene/schema/entity.ts +20 -0
- package/src/scene/schema/index.ts +2 -46
- package/src/scene/schema/light.ts +16 -1
- package/src/scene/schema/material.ts +96 -91
- package/src/scene/schema/scene-file.ts +1 -7
- package/src/scene/user-data.ts +22 -10
- package/src/setup/setup-renderer.ts +10 -3
- package/src/tools/define-tool.ts +191 -0
- package/src/world2d/authoring-2d.ts +17 -1
- package/src/world2d/collision-2d.ts +1 -1
- package/src/world2d/pixi-game-adapter.ts +19 -17
- package/src/world2d/scene2d-loader.ts +1 -0
- package/src/world2d/types.ts +8 -2
- package/src/animation/anim-graph.ts +0 -406
- package/src/animation/anim-system.ts +0 -28
- package/src/animation/property-track.ts +0 -178
- package/src/animation/schema.ts +0 -204
- package/src/audio/ambient.ts +0 -300
- package/src/audio/impacts.ts +0 -212
- package/src/audio/movement.ts +0 -140
- package/src/audio/musical.ts +0 -200
- package/src/audio/ui-sounds.ts +0 -171
- package/src/audio/vehicle.ts +0 -235
- package/src/audio/weapons.ts +0 -152
- package/src/runtime/scene-ui-bridge.ts +0 -86
- package/src/runtime/scene-ui-data.ts +0 -119
- package/src/scene/schema/ui.ts +0 -602
package/src/manifest/load.ts
CHANGED
|
@@ -14,12 +14,12 @@
|
|
|
14
14
|
// only need `loadGameManifest` never pull in `node:fs`.
|
|
15
15
|
|
|
16
16
|
import {
|
|
17
|
+
type AdapterRoot,
|
|
17
18
|
type GameManifest,
|
|
18
19
|
GameManifestSchema,
|
|
19
20
|
type IngestStrategy,
|
|
21
|
+
type RootAdapter,
|
|
20
22
|
type Tier,
|
|
21
|
-
type WorldAdapter,
|
|
22
|
-
type WorldEntry,
|
|
23
23
|
} from './schema';
|
|
24
24
|
|
|
25
25
|
// ---------------------------------------------------------------------------
|
|
@@ -29,10 +29,16 @@ import {
|
|
|
29
29
|
/** D6's resolved adapter identities. */
|
|
30
30
|
export type ResolvedAdapter =
|
|
31
31
|
| {
|
|
32
|
-
readonly type: '
|
|
33
|
-
readonly identity: '
|
|
32
|
+
readonly type: 'builtin';
|
|
33
|
+
readonly identity: 'threejs' | 'pixijs' | 'react';
|
|
34
|
+
readonly surface: 'threejs' | 'pixijs' | 'react';
|
|
35
|
+
}
|
|
36
|
+
| {
|
|
37
|
+
readonly type: 'module';
|
|
38
|
+
readonly identity: 'module';
|
|
39
|
+
readonly module: string;
|
|
40
|
+
readonly surface: 'threejs' | 'pixijs' | 'react';
|
|
34
41
|
}
|
|
35
|
-
| { readonly type: 'module'; readonly identity: 'module'; readonly module: string }
|
|
36
42
|
| {
|
|
37
43
|
readonly type: 'ingest';
|
|
38
44
|
// 'ingest-react' (Track N, N1 — docs/REACT-INGEST-LANDING-DESIGN.md
|
|
@@ -48,6 +54,7 @@ export type ResolvedAdapter =
|
|
|
48
54
|
// strategies either; `resolveIngestDescriptor`/`resolveIngest2DDescriptor`
|
|
49
55
|
// are where "not yet buildable" strategies throw.
|
|
50
56
|
readonly identity: 'ingest-three' | 'ingest-pixi' | 'ingest-react';
|
|
57
|
+
readonly surface: 'threejs' | 'pixijs' | 'react';
|
|
51
58
|
readonly strategy: IngestStrategy;
|
|
52
59
|
readonly entryHtml: string | undefined;
|
|
53
60
|
readonly assets: Record<string, string> | undefined;
|
|
@@ -62,9 +69,9 @@ export type ResolvedAdapter =
|
|
|
62
69
|
readonly bodyHtml: string | undefined;
|
|
63
70
|
};
|
|
64
71
|
|
|
65
|
-
export interface
|
|
72
|
+
export interface ResolvedAdapterRoot {
|
|
66
73
|
readonly id: string;
|
|
67
|
-
readonly
|
|
74
|
+
readonly surface: ResolvedAdapter['surface'];
|
|
68
75
|
readonly description: string | undefined;
|
|
69
76
|
readonly adapter: ResolvedAdapter;
|
|
70
77
|
readonly scene: string | undefined;
|
|
@@ -77,14 +84,41 @@ export interface ResolvedWorldEntry {
|
|
|
77
84
|
}
|
|
78
85
|
|
|
79
86
|
export interface ResolvedGameManifest {
|
|
80
|
-
readonly manifestVersion:
|
|
87
|
+
readonly manifestVersion: 2;
|
|
81
88
|
readonly name: string;
|
|
89
|
+
readonly appId: string | undefined;
|
|
90
|
+
readonly editorPort: number | undefined;
|
|
82
91
|
readonly version: string;
|
|
83
92
|
readonly engine: { readonly version: string };
|
|
84
93
|
/** Sorted by zOrder; ties broken by original array order (§3). */
|
|
85
|
-
readonly
|
|
94
|
+
readonly roots: readonly ResolvedAdapterRoot[];
|
|
86
95
|
readonly server: { readonly room: string; readonly module: string } | undefined;
|
|
87
96
|
readonly resolution: { readonly width: number; readonly height: number } | undefined;
|
|
97
|
+
readonly authoring:
|
|
98
|
+
| {
|
|
99
|
+
readonly hierarchy?:
|
|
100
|
+
| {
|
|
101
|
+
readonly rootLabels?: Readonly<Record<string, string>> | undefined;
|
|
102
|
+
readonly groups?:
|
|
103
|
+
| readonly {
|
|
104
|
+
readonly id: string;
|
|
105
|
+
readonly label: string;
|
|
106
|
+
readonly roots: readonly string[];
|
|
107
|
+
}[]
|
|
108
|
+
| undefined;
|
|
109
|
+
}
|
|
110
|
+
| undefined;
|
|
111
|
+
}
|
|
112
|
+
| undefined;
|
|
113
|
+
/** D18 — read by the debug-bridge installer to gate `?vgai-debug=1` in production builds. */
|
|
114
|
+
readonly debug: { readonly allowInProduction: boolean } | undefined;
|
|
115
|
+
/** D15 — read by `mount-manifest.ts`'s boot-time seeding reader (gates
|
|
116
|
+
* whether `ctx.random` is seeded from `defaultSeed`/`?vgai-seed=`), the
|
|
117
|
+
* gameplay-rng-ban burn-down scan, and the dev-mode Math.random phase
|
|
118
|
+
* trap. See `docs/D15-DETERMINISM-DESIGN.md`. */
|
|
119
|
+
readonly determinism:
|
|
120
|
+
| { readonly seededRandom: boolean; readonly defaultSeed?: number | undefined }
|
|
121
|
+
| undefined;
|
|
88
122
|
}
|
|
89
123
|
|
|
90
124
|
// ---------------------------------------------------------------------------
|
|
@@ -94,12 +128,12 @@ export interface ResolvedGameManifest {
|
|
|
94
128
|
|
|
95
129
|
/**
|
|
96
130
|
* Ceiling (maximum legal declared `Tier`) per adapter type and delivery
|
|
97
|
-
* context. `default`
|
|
98
|
-
* a first-party
|
|
131
|
+
* context. `default` roots are always first-party in both contexts (D6/§2 —
|
|
132
|
+
* a first-party root is fully vgai-native everywhere). `ingest` roots are
|
|
99
133
|
* ceilinged by their own capture `strategy`: locally the local CLI's Vite
|
|
100
134
|
* pipeline gives bundler dedupe, so every strategy reaches its own natural
|
|
101
135
|
* rung; hosted has no bundler (esbuild-wasm + externals only), and a
|
|
102
|
-
* `deduped`
|
|
136
|
+
* `deduped` root is DEFINED by being source built through our pipeline — no
|
|
103
137
|
* prebuilt bundle exists to fall back to an iframe rung, and nothing
|
|
104
138
|
* implements a hosted build for it, so hosted is `unsupported` (honesty over
|
|
105
139
|
* optimism; if the hosted esbuild-wasm path ever proves capable, raising
|
|
@@ -109,11 +143,11 @@ export interface ResolvedGameManifest {
|
|
|
109
143
|
* adapter's reach is not derivable", §4) — modeled here as `first-party`
|
|
110
144
|
* (the top rank), i.e. no ceiling actually constrains a module adapter's
|
|
111
145
|
* declared tier; the loader instead requires the tier to be declared
|
|
112
|
-
* explicitly for module
|
|
146
|
+
* explicitly for module roots (see `resolveCapabilities`), since there is no
|
|
113
147
|
* default to derive.
|
|
114
148
|
*/
|
|
115
149
|
export const CAPABILITY_CEILINGS = {
|
|
116
|
-
|
|
150
|
+
builtin: { local: 'first-party', hosted: 'first-party' },
|
|
117
151
|
ingest: {
|
|
118
152
|
shared: { local: 'shared', hosted: 'shared' },
|
|
119
153
|
deduped: { local: 'deduped', hosted: 'unsupported' },
|
|
@@ -146,14 +180,14 @@ const SEMVER_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?(?:\+[0-9A-Za-z-.]+)?$/;
|
|
|
146
180
|
// ---------------------------------------------------------------------------
|
|
147
181
|
|
|
148
182
|
function isModuleAdapter(
|
|
149
|
-
adapter:
|
|
150
|
-
): adapter is Extract<
|
|
183
|
+
adapter: RootAdapter,
|
|
184
|
+
): adapter is Extract<RootAdapter, { module: string }> {
|
|
151
185
|
return typeof adapter === 'object' && 'module' in adapter;
|
|
152
186
|
}
|
|
153
187
|
|
|
154
188
|
function isIngestAdapter(
|
|
155
|
-
adapter:
|
|
156
|
-
): adapter is Extract<
|
|
189
|
+
adapter: RootAdapter,
|
|
190
|
+
): adapter is Extract<RootAdapter, { ingest: { strategy: IngestStrategy } }> {
|
|
157
191
|
return typeof adapter === 'object' && 'ingest' in adapter;
|
|
158
192
|
}
|
|
159
193
|
|
|
@@ -161,9 +195,9 @@ function isIngestAdapter(
|
|
|
161
195
|
// Cross-field checks (§3 — beyond what Zod alone enforces)
|
|
162
196
|
// ---------------------------------------------------------------------------
|
|
163
197
|
//
|
|
164
|
-
// D-V6 (docs/WAVE5-MULTIWORLD-INGEST-DESIGN.md):
|
|
198
|
+
// D-V6 (docs/WAVE5-MULTIWORLD-INGEST-DESIGN.md): root-id uniqueness used to
|
|
165
199
|
// be a cross-field check HERE (`checkUniqueWorldIds`, removed) — it moved
|
|
166
|
-
// INTO `GameManifestSchema`'s `
|
|
200
|
+
// INTO `GameManifestSchema`'s `roots` `superRefine` (schema.ts) instead, so
|
|
167
201
|
// the generated JSON Schema and any other schema consumer see the same
|
|
168
202
|
// constraint the loader always enforced. `parseManifest` below catches that
|
|
169
203
|
// one recognizable schema-level issue and rethrows it as a clean, single
|
|
@@ -172,17 +206,22 @@ function isIngestAdapter(
|
|
|
172
206
|
// callers/tests that pattern-match the message text are unaffected by the
|
|
173
207
|
// enforcement moving one layer down.
|
|
174
208
|
|
|
175
|
-
function checkAdapterSceneEntryRules(
|
|
176
|
-
const { adapter } =
|
|
177
|
-
if (adapter === '
|
|
209
|
+
function checkAdapterSceneEntryRules(root: AdapterRoot): void {
|
|
210
|
+
const { adapter } = root;
|
|
211
|
+
if (typeof adapter === 'string' && !root.scene && !root.entry) {
|
|
178
212
|
throw new Error(
|
|
179
|
-
`Game manifest:
|
|
180
|
-
'`scene` nor `entry` — a
|
|
213
|
+
`Game manifest: root "${root.id}" uses the '${adapter}' adapter but declares neither ` +
|
|
214
|
+
'`scene` nor `entry` — a built-in adapter root requires an authored document or entry.',
|
|
181
215
|
);
|
|
182
216
|
}
|
|
183
|
-
if (
|
|
217
|
+
if (adapter === 'react' && root.scene !== undefined) {
|
|
184
218
|
throw new Error(
|
|
185
|
-
`Game manifest:
|
|
219
|
+
`Game manifest: React root "${root.id}" declares \`scene\` — React roots use JSX entry modules.`,
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
if (isIngestAdapter(adapter) && root.scene !== undefined) {
|
|
223
|
+
throw new Error(
|
|
224
|
+
`Game manifest: root "${root.id}" uses an { ingest } adapter but declares \`scene\` — ` +
|
|
186
225
|
'ingest adapters forbid `scene` (adapt-never-migrate: ingested games keep their own ' +
|
|
187
226
|
"formats; overlay persistence is T3.2's) (§3).",
|
|
188
227
|
);
|
|
@@ -190,13 +229,13 @@ function checkAdapterSceneEntryRules(world: WorldEntry): void {
|
|
|
190
229
|
}
|
|
191
230
|
|
|
192
231
|
// Historical note (Track N, N1): this file used to reject EVERY `kind:
|
|
193
|
-
// 'react'` + `{ ingest }`
|
|
232
|
+
// 'react'` + `{ ingest }` root outright here (`checkReactAdapterRestriction`,
|
|
194
233
|
// citing "no react ingest program is un-parked"). Track N un-parks exactly
|
|
195
234
|
// that program (docs/REACT-INGEST-LANDING-DESIGN.md) — react+ingest is now a
|
|
196
235
|
// structurally legal combination, resolved to the `'ingest-react'` identity
|
|
197
|
-
// by `resolveAdapter` below like any other ingest
|
|
236
|
+
// by `resolveAdapter` below like any other ingest root. There is nothing
|
|
198
237
|
// left to reject at THIS layer: `checkAdapterSceneEntryRules` above already
|
|
199
|
-
// forbids `scene` on any `{ ingest }`-adapter
|
|
238
|
+
// forbids `scene` on any `{ ingest }`-adapter root (kind-agnostic), and the
|
|
200
239
|
// v1 "deduped strategy only" restriction is an editor-resolver concern
|
|
201
240
|
// (D-N3), not a manifest-shape concern — see the `ResolvedAdapter` type's
|
|
202
241
|
// comment above.
|
|
@@ -205,28 +244,36 @@ function checkAdapterSceneEntryRules(world: WorldEntry): void {
|
|
|
205
244
|
// Resolution (§5)
|
|
206
245
|
// ---------------------------------------------------------------------------
|
|
207
246
|
|
|
208
|
-
function resolveAdapter(
|
|
209
|
-
const { adapter
|
|
247
|
+
function resolveAdapter(root: AdapterRoot): ResolvedAdapter {
|
|
248
|
+
const { adapter } = root;
|
|
210
249
|
|
|
211
|
-
if (adapter === '
|
|
212
|
-
|
|
213
|
-
kind === 'threejs' ? 'default-three' : kind === 'pixijs' ? 'default-pixi' : 'default-react';
|
|
214
|
-
return { type: 'default', identity };
|
|
250
|
+
if (typeof adapter === 'string') {
|
|
251
|
+
return { type: 'builtin', identity: adapter, surface: adapter };
|
|
215
252
|
}
|
|
216
253
|
|
|
217
254
|
if (isModuleAdapter(adapter)) {
|
|
218
|
-
return {
|
|
255
|
+
return {
|
|
256
|
+
type: 'module',
|
|
257
|
+
identity: 'module',
|
|
258
|
+
module: adapter.module,
|
|
259
|
+
surface: adapter.surface,
|
|
260
|
+
};
|
|
219
261
|
}
|
|
220
262
|
|
|
221
263
|
// isIngestAdapter (Track N, N1: 'ingest-react' joins 'ingest-three'/
|
|
222
264
|
// 'ingest-pixi' — see the `ResolvedAdapter` type's comment above for why
|
|
223
265
|
// this stays strategy-agnostic here).
|
|
224
266
|
const identity =
|
|
225
|
-
|
|
267
|
+
adapter.surface === 'threejs'
|
|
268
|
+
? 'ingest-three'
|
|
269
|
+
: adapter.surface === 'pixijs'
|
|
270
|
+
? 'ingest-pixi'
|
|
271
|
+
: 'ingest-react';
|
|
226
272
|
const ingest = adapter.ingest;
|
|
227
273
|
return {
|
|
228
274
|
type: 'ingest',
|
|
229
275
|
identity,
|
|
276
|
+
surface: adapter.surface,
|
|
230
277
|
strategy: ingest.strategy,
|
|
231
278
|
entryHtml: ingest.entryHtml,
|
|
232
279
|
assets: ingest.assets,
|
|
@@ -241,23 +288,23 @@ function resolveAdapter(world: WorldEntry): ResolvedAdapter {
|
|
|
241
288
|
};
|
|
242
289
|
}
|
|
243
290
|
|
|
244
|
-
function ceilingFor(adapter:
|
|
245
|
-
if (adapter === '
|
|
291
|
+
function ceilingFor(adapter: RootAdapter, context: 'local' | 'hosted'): Tier {
|
|
292
|
+
if (typeof adapter === 'string') return CAPABILITY_CEILINGS.builtin[context];
|
|
246
293
|
if (isModuleAdapter(adapter)) return CAPABILITY_CEILINGS.module[context];
|
|
247
294
|
return CAPABILITY_CEILINGS.ingest[adapter.ingest.strategy][context];
|
|
248
295
|
}
|
|
249
296
|
|
|
250
|
-
function resolveCapabilities(
|
|
297
|
+
function resolveCapabilities(root: AdapterRoot): { local: Tier; hosted: Tier } {
|
|
251
298
|
const contexts = ['local', 'hosted'] as const;
|
|
252
299
|
const result = {} as { local: Tier; hosted: Tier };
|
|
253
300
|
|
|
254
301
|
for (const context of contexts) {
|
|
255
|
-
const declared =
|
|
302
|
+
const declared = root.capabilities?.[context];
|
|
256
303
|
if (declared !== undefined) {
|
|
257
|
-
const ceiling = ceilingFor(
|
|
304
|
+
const ceiling = ceilingFor(root.adapter, context);
|
|
258
305
|
if (TIER_RANK[declared] > TIER_RANK[ceiling]) {
|
|
259
306
|
throw new Error(
|
|
260
|
-
`Game manifest:
|
|
307
|
+
`Game manifest: root "${root.id}": declared capabilities.${context} tier "${declared}" ` +
|
|
261
308
|
`exceeds the "${ceiling}" ceiling for this adapter in the ${context} context (§4). ` +
|
|
262
309
|
'Under-claiming (including `unsupported`) is legal; over-claiming is not.',
|
|
263
310
|
);
|
|
@@ -267,15 +314,15 @@ function resolveCapabilities(world: WorldEntry): { local: Tier; hosted: Tier } {
|
|
|
267
314
|
}
|
|
268
315
|
|
|
269
316
|
// Omitted -> derive.
|
|
270
|
-
if (
|
|
317
|
+
if (typeof root.adapter === 'string') {
|
|
271
318
|
result[context] = 'first-party';
|
|
272
|
-
} else if (isModuleAdapter(
|
|
319
|
+
} else if (isModuleAdapter(root.adapter)) {
|
|
273
320
|
throw new Error(
|
|
274
|
-
`Game manifest:
|
|
321
|
+
`Game manifest: root "${root.id}": { module } adapter requires an explicit ` +
|
|
275
322
|
`capabilities.${context} declaration — a custom adapter's reach is not derivable (§4).`,
|
|
276
323
|
);
|
|
277
324
|
} else {
|
|
278
|
-
result[context] = CAPABILITY_CEILINGS.ingest[
|
|
325
|
+
result[context] = CAPABILITY_CEILINGS.ingest[root.adapter.ingest.strategy][context];
|
|
279
326
|
}
|
|
280
327
|
}
|
|
281
328
|
|
|
@@ -292,10 +339,10 @@ function checkEngineVersionPin(version: string): void {
|
|
|
292
339
|
}
|
|
293
340
|
|
|
294
341
|
/**
|
|
295
|
-
* Resolve one already-Zod-validated
|
|
342
|
+
* Resolve one already-Zod-validated root entry into its `ResolvedAdapterRoot`
|
|
296
343
|
* (adapter identity, capabilities, and a pass-through of every other field).
|
|
297
|
-
* Every `
|
|
298
|
-
* `id
|
|
344
|
+
* Every `AdapterRoot` field participates here or in a check above:
|
|
345
|
+
* `id` drives uniqueness; `adapter` (incl. its surface and
|
|
299
346
|
* `module`/`ingest.*` sub-fields) drives identity + capability-ceiling
|
|
300
347
|
* resolution; `scene`/`entry` drive the scene/entry cross-field rule and are
|
|
301
348
|
* carried through; `zOrder` drives the final sort; `pausable`/`loop` are
|
|
@@ -303,22 +350,23 @@ function checkEngineVersionPin(version: string): void {
|
|
|
303
350
|
* `capabilities.{local,hosted}` drive tier resolution; `description` (T3.3
|
|
304
351
|
* slice 3) is carried through as-is — its consumer today is
|
|
305
352
|
* `resolveIngestDescriptor` (packages/editor/src/adapter-resolver.ts), which
|
|
306
|
-
* threads an ingest-three
|
|
353
|
+
* threads an ingest-three root's description into `IngestGame.description`.
|
|
307
354
|
*/
|
|
308
|
-
function
|
|
309
|
-
checkAdapterSceneEntryRules(
|
|
355
|
+
function resolveRoot(root: AdapterRoot): ResolvedAdapterRoot {
|
|
356
|
+
checkAdapterSceneEntryRules(root);
|
|
357
|
+
const adapter = resolveAdapter(root);
|
|
310
358
|
|
|
311
359
|
return {
|
|
312
|
-
id:
|
|
313
|
-
|
|
314
|
-
description:
|
|
315
|
-
adapter
|
|
316
|
-
scene:
|
|
317
|
-
entry:
|
|
318
|
-
zOrder:
|
|
319
|
-
pausable:
|
|
320
|
-
loop:
|
|
321
|
-
capabilities: resolveCapabilities(
|
|
360
|
+
id: root.id,
|
|
361
|
+
surface: adapter.surface,
|
|
362
|
+
description: root.description,
|
|
363
|
+
adapter,
|
|
364
|
+
scene: root.scene,
|
|
365
|
+
entry: root.entry,
|
|
366
|
+
zOrder: root.zOrder,
|
|
367
|
+
pausable: root.pausable,
|
|
368
|
+
loop: root.loop,
|
|
369
|
+
capabilities: resolveCapabilities(root),
|
|
322
370
|
};
|
|
323
371
|
}
|
|
324
372
|
|
|
@@ -329,20 +377,20 @@ function resolveWorld(world: WorldEntry): ResolvedWorldEntry {
|
|
|
329
377
|
/**
|
|
330
378
|
* Parse + validate + resolve a raw manifest value into a `ResolvedGameManifest`.
|
|
331
379
|
* Pure data-in/data-out (no `fs`) — see `load-file.ts` for the Node path-based
|
|
332
|
-
* wrapper. Throws a descriptive `Error` (naming the offending
|
|
380
|
+
* wrapper. Throws a descriptive `Error` (naming the offending root/field) on
|
|
333
381
|
* any Zod validation failure or cross-field rule violation.
|
|
334
382
|
*/
|
|
335
383
|
export function loadGameManifest(raw: unknown): ResolvedGameManifest {
|
|
336
384
|
const parsed = GameManifestSchema.safeParse(raw);
|
|
337
385
|
if (!parsed.success) {
|
|
338
|
-
// D-V6: the `
|
|
386
|
+
// D-V6: the `roots` `superRefine`'s duplicate-id issue is the one
|
|
339
387
|
// schema-level violation this loader still surfaces as a clean, single
|
|
340
388
|
// `Error` (matching `checkUniqueWorldIds`'s pre-D-V6 message shape) —
|
|
341
389
|
// every other schema violation keeps propagating as the raw `ZodError`
|
|
342
390
|
// it always has (unchanged behavior, e.g. the iframe-reachable
|
|
343
391
|
// field-exclusivity / manifestVersion checks).
|
|
344
392
|
const dupIssue = parsed.error.issues.find((issue) =>
|
|
345
|
-
issue.message.startsWith('Game manifest: duplicate
|
|
393
|
+
issue.message.startsWith('Game manifest: duplicate root id'),
|
|
346
394
|
);
|
|
347
395
|
if (dupIssue) throw new Error(dupIssue.message);
|
|
348
396
|
throw parsed.error;
|
|
@@ -351,16 +399,16 @@ export function loadGameManifest(raw: unknown): ResolvedGameManifest {
|
|
|
351
399
|
|
|
352
400
|
checkEngineVersionPin(manifest.engine.version);
|
|
353
401
|
|
|
354
|
-
const resolvedWithIndex = manifest.
|
|
355
|
-
|
|
402
|
+
const resolvedWithIndex = manifest.roots.map((root, index) => ({
|
|
403
|
+
root: resolveRoot(root),
|
|
356
404
|
index,
|
|
357
405
|
}));
|
|
358
406
|
|
|
359
407
|
resolvedWithIndex.sort(
|
|
360
|
-
(a, b) => a.
|
|
408
|
+
(a, b) => a.root.zOrder - b.root.zOrder || a.index - b.index, // ties -> array order (§3)
|
|
361
409
|
);
|
|
362
410
|
|
|
363
|
-
const
|
|
411
|
+
const roots = resolvedWithIndex.map(({ root }) => root);
|
|
364
412
|
|
|
365
413
|
const server = manifest.server
|
|
366
414
|
? { room: manifest.server.room, module: manifest.server.module ?? DEFAULT_SERVER_MODULE }
|
|
@@ -369,10 +417,15 @@ export function loadGameManifest(raw: unknown): ResolvedGameManifest {
|
|
|
369
417
|
return {
|
|
370
418
|
manifestVersion: manifest.manifestVersion,
|
|
371
419
|
name: manifest.name,
|
|
420
|
+
appId: manifest.appId,
|
|
421
|
+
editorPort: manifest.editorPort,
|
|
372
422
|
version: manifest.version,
|
|
373
423
|
engine: { version: manifest.engine.version },
|
|
374
|
-
|
|
424
|
+
roots,
|
|
375
425
|
server,
|
|
376
426
|
resolution: manifest.resolution,
|
|
427
|
+
authoring: manifest.authoring,
|
|
428
|
+
debug: manifest.debug,
|
|
429
|
+
determinism: manifest.determinism,
|
|
377
430
|
};
|
|
378
431
|
}
|