@vgai/engine 0.5.2 → 0.5.3
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 +18 -11
- package/package.json +10 -6
- package/schemas/engine-api.json +1 -68
- package/schemas/engine-api.md +1 -32
- package/schemas/engine-capabilities.json +30 -42
- package/schemas/{vgai-game.schema.json → vgai-project.schema.json} +52 -34
- package/src/adapter/adapter-surface.ts +5 -5
- package/src/adapter/authoring.ts +168 -226
- package/src/adapter/colyseus-networking-adapter.ts +44 -5
- package/src/adapter/first-party-systems.ts +156 -42
- package/src/adapter/host-context.ts +177 -46
- package/src/adapter/index.ts +47 -51
- package/src/adapter/ingest/game-contract.ts +2 -2
- package/src/adapter/ingest/scene-capture.ts +18 -19
- package/src/adapter/ingest/structural-ids.ts +127 -0
- package/src/adapter/ingest/upstream-pin.ts +9 -12
- package/src/adapter/loop-gate-report.ts +11 -11
- package/src/adapter/rapier-physics-adapter.ts +27 -9
- package/src/adapter/root-adapter.ts +217 -0
- package/src/adapter/{vgai-scene-game-adapter.ts → setup-three-root-adapter.ts} +173 -351
- package/src/adapter/system-adapter.ts +80 -63
- package/src/ai/navigation.ts +1 -1
- package/src/animation/animation-clock.ts +1 -1
- package/src/animation/camera-ownership.ts +1 -2
- package/src/animation/cubic-spline-interpolant.ts +132 -0
- package/src/animation/theatre-clock-binding.ts +2 -2
- package/src/animation/theatre-object-binding.ts +4 -4
- package/src/animation/xstate-animation-binding.ts +75 -5
- package/src/{scene/schema → asset-formats}/camera.ts +2 -5
- package/src/{scene/schema → asset-formats}/collider.ts +2 -5
- package/src/asset-formats/index.ts +54 -0
- package/src/{scene/schema → asset-formats}/instances.ts +10 -6
- package/src/{scene/schema → asset-formats}/light.ts +3 -6
- package/src/{scene/schema → asset-formats}/material.ts +4 -7
- package/src/{scene/schema → asset-formats}/mesh.ts +4 -7
- package/src/asset-formats/parse.ts +39 -0
- package/src/{scene/schema → asset-formats}/particles.ts +3 -6
- package/src/{scene/schema/environment.ts → asset-formats/render-env.ts} +23 -86
- package/src/{scene/schema → asset-formats}/tuples.ts +1 -1
- package/src/{scene/asset-loaders.ts → asset-loaders.ts} +8 -9
- package/src/asset-parse-error.ts +33 -0
- package/src/{scene/asset-registry.ts → asset-registry.ts} +1 -1
- package/src/assets.ts +1 -1
- package/src/audio/wav-encode.ts +9 -9
- package/src/canvas-react/engine-bridge.ts +59 -0
- package/src/canvas-react/index.ts +50 -0
- package/src/canvas-react/pixi-primitive.tsx +202 -0
- package/src/canvas-react/pixi-react-adapter.tsx +290 -0
- package/src/canvas-react/pixi-react-root-factory.tsx +88 -0
- package/src/canvas-react/world-context.ts +328 -0
- package/src/core/frame-pacing.ts +100 -0
- package/src/core/game-loop.ts +50 -28
- package/src/core/seeded-random.ts +7 -7
- package/src/core/sim-clock.ts +388 -0
- package/src/core/system-runner.ts +17 -63
- package/src/core/types.ts +34 -15
- package/src/data/data-asset.ts +3 -3
- package/src/data/data-check-core.ts +6 -7
- package/src/data/data-ref.ts +11 -11
- package/src/data/vite-plugin-data.ts +10 -10
- package/src/{scene/defaults.ts → defaults.ts} +18 -40
- package/src/dev/render-debug-adapter.ts +1 -1
- package/src/dev/webgl-frame-capture.ts +1 -1
- package/src/ecs/scene-index.ts +439 -0
- package/src/ecs/scene-query.ts +43 -0
- package/src/{scene → ecs}/user-data.ts +17 -36
- package/src/index.ts +7 -9
- package/src/input/input-manager.ts +29 -32
- package/src/input/input-types.ts +2 -2
- package/src/input/schema.ts +5 -5
- package/src/loader.ts +57 -0
- package/src/manifest/editor-port.ts +69 -0
- package/src/manifest/filename.ts +49 -0
- package/src/manifest/index.ts +8 -2
- package/src/manifest/load-file.ts +11 -0
- package/src/manifest/load.ts +65 -77
- package/src/manifest/locate.ts +55 -0
- package/src/manifest/schema.ts +400 -233
- package/src/{scene → physics}/collider-dimensions.ts +3 -3
- package/src/physics/physics-registry.ts +1 -1
- package/src/{world2d/authoring-2d.ts → pixi/authoring.ts} +24 -11
- package/src/pixi/index.ts +43 -0
- package/src/{world2d/ingest-iframe-2d.ts → pixi/ingest-iframe.ts} +9 -9
- package/src/{world2d/ingest2d.ts → pixi/ingest.ts} +28 -28
- package/src/{world2d/physics2d-registry.ts → pixi/physics-registry.ts} +1 -1
- package/src/{world2d/scene-capture-2d.ts → pixi/scene-capture.ts} +3 -3
- package/src/{world2d/system-adapters-2d.ts → pixi/system-adapters.ts} +2 -2
- package/src/react/unmanaged-root-detector.ts +26 -1
- package/src/react/use-data.ts +5 -5
- package/src/react/use-selection.tsx +15 -42
- package/src/react/{game-state.tsx → world-state.tsx} +44 -46
- package/src/render/auto-batcher.ts +1 -2
- package/src/{scene → render}/instance-mesh.ts +1 -1
- package/src/{scene → render}/light-camera-factory.ts +14 -13
- package/src/render/lod.ts +17 -0
- package/src/{scene → render}/material-factory.ts +8 -7
- package/src/{scene → render}/particles-factory.ts +62 -12
- package/src/render/render-batch-system.ts +14 -41
- package/src/render/render-features.ts +1 -1
- package/src/render/render-settings.ts +1 -2
- package/src/render/spark-renderer-lifecycle.ts +1 -1
- package/src/runtime/create-runtime.ts +352 -463
- package/src/runtime/debug-bridge.ts +148 -93
- package/src/runtime/debug-registry.ts +79 -54
- package/src/runtime/dev-layers.ts +40 -0
- package/src/runtime/frame-selector-cache.ts +4 -4
- package/src/runtime/game.ts +618 -369
- package/src/runtime/gameplay-rng-trap.ts +6 -7
- package/src/runtime/input-router.ts +11 -11
- package/src/runtime/mount-game.ts +54 -55
- package/src/runtime/mount-manifest.ts +154 -150
- package/src/runtime/presentation.ts +141 -0
- package/src/runtime/render-audio-control.ts +64 -53
- package/src/runtime/render-control.ts +45 -51
- package/src/runtime/render-seed.ts +3 -4
- package/src/runtime/state-bridge.ts +17 -18
- package/src/runtime/types.ts +94 -61
- package/src/setup/setup-renderer.ts +11 -6
- package/src/world3d-react/engine-bridge.ts +46 -33
- package/src/world3d-react/index.ts +31 -26
- package/src/world3d-react/r3f-adapter.tsx +211 -96
- package/src/world3d-react/r3f-root-factory.tsx +91 -0
- package/src/world3d-react/renderer-config.ts +137 -0
- package/src/world3d-react/world-context.ts +141 -111
- package/schemas/entity2d.schema.json +0 -468
- package/schemas/prefab.schema.json +0 -9992
- package/schemas/scn2d.schema.json +0 -494
- package/schemas/vscn.schema.json +0 -10851
- package/src/adapter/game-adapter.ts +0 -164
- package/src/adapter/ingest/overlay-applier.ts +0 -207
- package/src/adapter/ingest/overlay-apply.ts +0 -168
- package/src/adapter/ingest/overlay-file.ts +0 -126
- package/src/adapter/ingest/overlay-report.ts +0 -176
- package/src/animation/gsap-registration.ts +0 -184
- package/src/audio/audio-introspection.ts +0 -290
- package/src/audio/index.ts +0 -39
- package/src/audio/tone-clock-binding.ts +0 -98
- package/src/audio/tone-context.ts +0 -175
- package/src/audio/tone-offline-render.ts +0 -167
- package/src/ecs/component-manager.ts +0 -814
- package/src/ecs/game-component.ts +0 -260
- package/src/ecs/hmr-swap-report.ts +0 -65
- package/src/physics/trigger-dispatch.ts +0 -97
- package/src/react/root-adapter.tsx +0 -49
- package/src/scene/asset-paths.ts +0 -121
- package/src/scene/asset-ref-check.ts +0 -248
- package/src/scene/component-registry.ts +0 -51
- package/src/scene/parse.ts +0 -204
- package/src/scene/scene-apply.ts +0 -407
- package/src/scene/scene-diff-schema.ts +0 -115
- package/src/scene/scene-diff-types.ts +0 -29
- package/src/scene/scene-loader.ts +0 -1526
- package/src/scene/scene-query.ts +0 -63
- package/src/scene/scene-types.ts +0 -33
- package/src/scene/scene-version.ts +0 -40
- package/src/scene/schema/animation.ts +0 -46
- package/src/scene/schema/audio.ts +0 -25
- package/src/scene/schema/entity-ref.ts +0 -78
- package/src/scene/schema/entity.ts +0 -189
- package/src/scene/schema/index.ts +0 -51
- package/src/scene/schema/joint.ts +0 -26
- package/src/scene/schema/physics.ts +0 -49
- package/src/scene/schema/scene-file.ts +0 -292
- package/src/scene/schema/shadow.ts +0 -24
- package/src/scene/schema/spline.ts +0 -21
- package/src/world2d/asset-paths2d.ts +0 -44
- package/src/world2d/capture-to-scene2d.ts +0 -52
- package/src/world2d/collision-2d.ts +0 -99
- package/src/world2d/entity2d-asset.ts +0 -22
- package/src/world2d/index.ts +0 -91
- package/src/world2d/physics2d-transform.ts +0 -173
- package/src/world2d/physics2d-units.ts +0 -10
- package/src/world2d/pixi-game-adapter.ts +0 -439
- package/src/world2d/pixi-surface.ts +0 -78
- package/src/world2d/scene2d-identity.ts +0 -49
- package/src/world2d/scene2d-loader.ts +0 -433
- package/src/world2d/schema/entity2d.ts +0 -163
- package/src/world2d/schema/physics2d.ts +0 -64
- package/src/world2d/schema/sprite.ts +0 -99
- package/src/world2d/schema/tilemap.ts +0 -39
- package/src/world2d/schema/tuples2d.ts +0 -25
- package/src/world2d/transform-writer-2d.ts +0 -42
- package/src/world2d/types.ts +0 -74
- package/src/world3d-react/behavior.tsx +0 -146
- /package/src/{scene → render}/mesh-shadow.ts +0 -0
package/src/manifest/load.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//
|
|
1
|
+
// The game manifest loader/validator.
|
|
2
2
|
//
|
|
3
3
|
// Pure data-in/data-out: `loadGameManifest(raw)` parses `raw` against
|
|
4
4
|
// `GameManifestSchema`, runs the cross-field checks §3 promises beyond what
|
|
@@ -31,23 +31,22 @@ import {
|
|
|
31
31
|
export type ResolvedAdapter =
|
|
32
32
|
| {
|
|
33
33
|
readonly type: 'builtin';
|
|
34
|
-
readonly identity: '
|
|
35
|
-
readonly surface: '
|
|
34
|
+
readonly identity: 'three' | 'canvas' | 'dom';
|
|
35
|
+
readonly surface: 'three' | 'canvas' | 'dom';
|
|
36
36
|
}
|
|
37
37
|
| {
|
|
38
38
|
readonly type: 'module';
|
|
39
39
|
readonly identity: 'module';
|
|
40
40
|
readonly module: string;
|
|
41
|
-
readonly surface: '
|
|
41
|
+
readonly surface: 'three' | 'canvas' | 'dom';
|
|
42
42
|
}
|
|
43
43
|
| {
|
|
44
44
|
readonly type: 'ingest';
|
|
45
|
-
// 'ingest-react' (Track N, N1
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
// `strategy` regardless of `kind`). The v1 "deduped rung only" rule
|
|
45
|
+
// 'ingest-react' (Track N, N1) is assigned here purely structurally (kind
|
|
46
|
+
// === 'dom'), same as 'ingest-three'/'ingest-pixi': this loader stays
|
|
47
|
+
// strategy-agnostic at resolve time, exactly like the other two ingest
|
|
48
|
+
// identities (`ceilingFor`/`resolveCapabilities` below already derive tiers
|
|
49
|
+
// per `strategy` regardless of `kind`). The v1 "deduped rung only" rule
|
|
51
50
|
// (D-N3) is enforced editor-side, at the resolver
|
|
52
51
|
// (`packages/editor/src/adapter-resolver.ts`'s `resolveIngestReactAdapter`)
|
|
53
52
|
// — not here — mirroring how THIS file never rejects
|
|
@@ -55,13 +54,13 @@ export type ResolvedAdapter =
|
|
|
55
54
|
// strategies either; `resolveIngestDescriptor`/`resolveIngest2DDescriptor`
|
|
56
55
|
// are where "not yet buildable" strategies throw.
|
|
57
56
|
readonly identity: 'ingest-three' | 'ingest-pixi' | 'ingest-react';
|
|
58
|
-
readonly surface: '
|
|
57
|
+
readonly surface: 'three' | 'canvas' | 'dom';
|
|
59
58
|
readonly strategy: IngestStrategy;
|
|
60
59
|
readonly entryHtml: string | undefined;
|
|
61
60
|
readonly assets: Record<string, string> | undefined;
|
|
62
61
|
readonly domStubs: string[] | undefined;
|
|
63
62
|
readonly captureTimeoutMs: number | undefined;
|
|
64
|
-
/**
|
|
63
|
+
/** Iframe-reachable-multi mount fields. */
|
|
65
64
|
readonly bundleUrl: string | undefined;
|
|
66
65
|
readonly baseHref: string | undefined;
|
|
67
66
|
readonly assetBaseUrl: string | undefined;
|
|
@@ -75,10 +74,12 @@ export interface ResolvedAdapterRoot {
|
|
|
75
74
|
readonly surface: ResolvedAdapter['surface'];
|
|
76
75
|
readonly description: string | undefined;
|
|
77
76
|
readonly adapter: ResolvedAdapter;
|
|
78
|
-
readonly scene: string | undefined;
|
|
79
77
|
readonly entry: string | undefined;
|
|
80
78
|
readonly zOrder: number;
|
|
81
79
|
readonly pausable: boolean;
|
|
80
|
+
/** `dev: true` — this root is a DEV LAYER, not shipped game content. See
|
|
81
|
+
* `manifest/schema.ts`'s `dev` field for the four readers. */
|
|
82
|
+
readonly dev: boolean;
|
|
82
83
|
readonly loop: 'gated' | 'self-driven';
|
|
83
84
|
/** Every optional resolved: both contexts always present (§4/§5). */
|
|
84
85
|
readonly capabilities: { readonly local: Tier; readonly hosted: Tier };
|
|
@@ -88,7 +89,6 @@ export interface ResolvedGameManifest {
|
|
|
88
89
|
readonly manifestVersion: 2;
|
|
89
90
|
readonly name: string;
|
|
90
91
|
readonly appId: string | undefined;
|
|
91
|
-
readonly editorPort: number | undefined;
|
|
92
92
|
readonly version: string;
|
|
93
93
|
readonly engine: { readonly version: string };
|
|
94
94
|
/** Sorted by zOrder; ties broken by original array order (§3). */
|
|
@@ -116,7 +116,7 @@ export interface ResolvedGameManifest {
|
|
|
116
116
|
/** D15 — read by `mount-manifest.ts`'s boot-time seeding reader (gates
|
|
117
117
|
* whether `ctx.random` is seeded from `defaultSeed`/`?vgai-seed=`), the
|
|
118
118
|
* gameplay-rng-ban burn-down scan, and the dev-mode Math.random phase
|
|
119
|
-
* trap
|
|
119
|
+
* trap.. */
|
|
120
120
|
readonly determinism:
|
|
121
121
|
| { readonly seededRandom: boolean; readonly defaultSeed?: number | undefined }
|
|
122
122
|
| undefined;
|
|
@@ -133,16 +133,16 @@ export interface ResolvedGameManifest {
|
|
|
133
133
|
readonly maxTriangles?: number | undefined;
|
|
134
134
|
}
|
|
135
135
|
| undefined;
|
|
136
|
-
/**
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
136
|
+
/** Learner-facing metadata on shipped example projects; read by the editor
|
|
137
|
+
* server's registry surface (`GET /__editor/examples`,
|
|
138
|
+
* packages/editor/server/editor-server.ts) to generate the
|
|
139
|
+
* gallery/wizard/Learn-catalog registries. */
|
|
140
140
|
readonly learn: LearnMetadata | undefined;
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
// ---------------------------------------------------------------------------
|
|
144
|
-
// §4 — the per-context ceiling table (named + exported
|
|
145
|
-
//
|
|
144
|
+
// §4 — the per-context ceiling table (named + exported so the tier-table test
|
|
145
|
+
// can pin its rows against this).
|
|
146
146
|
// ---------------------------------------------------------------------------
|
|
147
147
|
|
|
148
148
|
/**
|
|
@@ -204,45 +204,26 @@ function isModuleAdapter(
|
|
|
204
204
|
return typeof adapter === 'object' && 'module' in adapter;
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
-
function isIngestAdapter(
|
|
208
|
-
adapter: RootAdapter,
|
|
209
|
-
): adapter is Extract<RootAdapter, { ingest: { strategy: IngestStrategy } }> {
|
|
210
|
-
return typeof adapter === 'object' && 'ingest' in adapter;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
207
|
// ---------------------------------------------------------------------------
|
|
214
208
|
// Cross-field checks (§3 — beyond what Zod alone enforces)
|
|
215
209
|
// ---------------------------------------------------------------------------
|
|
216
210
|
//
|
|
217
|
-
//
|
|
218
|
-
//
|
|
219
|
-
//
|
|
220
|
-
//
|
|
221
|
-
//
|
|
222
|
-
//
|
|
223
|
-
// `
|
|
224
|
-
//
|
|
225
|
-
//
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
function checkAdapterSceneEntryRules(root: AdapterRoot): void {
|
|
211
|
+
// Root-id uniqueness used to be a cross-field check HERE
|
|
212
|
+
// (`checkUniqueRootIds`, removed) — it moved INTO `GameManifestSchema`'s
|
|
213
|
+
// `roots` `superRefine` (schema.ts) instead, so the generated JSON Schema and
|
|
214
|
+
// any other schema consumer see the same constraint the loader always
|
|
215
|
+
// enforced. `parseManifest` below catches that one recognizable schema-level
|
|
216
|
+
// issue and rethrows it as a clean, single `Error` — the exact message shape
|
|
217
|
+
// `checkUniqueRootIds` used to throw directly — rather than a raw multi-issue
|
|
218
|
+
// `ZodError` dump, so existing callers/tests that pattern-match the message
|
|
219
|
+
// text are unaffected by the enforcement moving one layer down.
|
|
220
|
+
|
|
221
|
+
function checkAdapterEntryRules(root: AdapterRoot): void {
|
|
229
222
|
const { adapter } = root;
|
|
230
|
-
if (typeof adapter === 'string' && !root.
|
|
231
|
-
throw new Error(
|
|
232
|
-
`Game manifest: root "${root.id}" uses the '${adapter}' adapter but declares neither ` +
|
|
233
|
-
'`scene` nor `entry` — a built-in adapter root requires an authored document or entry.',
|
|
234
|
-
);
|
|
235
|
-
}
|
|
236
|
-
if (adapter === 'react' && root.scene !== undefined) {
|
|
237
|
-
throw new Error(
|
|
238
|
-
`Game manifest: React root "${root.id}" declares \`scene\` — React roots use JSX entry modules.`,
|
|
239
|
-
);
|
|
240
|
-
}
|
|
241
|
-
if (isIngestAdapter(adapter) && root.scene !== undefined) {
|
|
223
|
+
if (typeof adapter === 'string' && !root.entry) {
|
|
242
224
|
throw new Error(
|
|
243
|
-
`Game manifest: root "${root.id}" uses
|
|
244
|
-
'
|
|
245
|
-
"formats; overlay persistence is T3.2's) (§3).",
|
|
225
|
+
`Game manifest: root "${root.id}" uses the '${adapter}' adapter but declares no ` +
|
|
226
|
+
'`entry` — a built-in adapter root is authored as an entry module.',
|
|
246
227
|
);
|
|
247
228
|
}
|
|
248
229
|
}
|
|
@@ -250,14 +231,12 @@ function checkAdapterSceneEntryRules(root: AdapterRoot): void {
|
|
|
250
231
|
// Historical note (Track N, N1): this file used to reject EVERY `kind:
|
|
251
232
|
// 'react'` + `{ ingest }` root outright here (`checkReactAdapterRestriction`,
|
|
252
233
|
// citing "no react ingest program is un-parked"). Track N un-parks exactly
|
|
253
|
-
// that program
|
|
254
|
-
//
|
|
255
|
-
//
|
|
256
|
-
//
|
|
257
|
-
//
|
|
258
|
-
//
|
|
259
|
-
// (D-N3), not a manifest-shape concern — see the `ResolvedAdapter` type's
|
|
260
|
-
// comment above.
|
|
234
|
+
// that program — react+ingest is now a structurally legal combination,
|
|
235
|
+
// resolved to the `'ingest-react'` identity by `resolveAdapter` below like
|
|
236
|
+
// any other ingest root. There is nothing left to reject at THIS layer: the
|
|
237
|
+
// v1 "deduped strategy only" restriction is an editor-resolver concern (D-N3),
|
|
238
|
+
// not a manifest-shape concern — see the `ResolvedAdapter` type's comment
|
|
239
|
+
// above.
|
|
261
240
|
|
|
262
241
|
// ---------------------------------------------------------------------------
|
|
263
242
|
// Resolution (§5)
|
|
@@ -283,9 +262,9 @@ function resolveAdapter(root: AdapterRoot): ResolvedAdapter {
|
|
|
283
262
|
// 'ingest-pixi' — see the `ResolvedAdapter` type's comment above for why
|
|
284
263
|
// this stays strategy-agnostic here).
|
|
285
264
|
const identity =
|
|
286
|
-
adapter.surface === '
|
|
265
|
+
adapter.surface === 'three'
|
|
287
266
|
? 'ingest-three'
|
|
288
|
-
: adapter.surface === '
|
|
267
|
+
: adapter.surface === 'canvas'
|
|
289
268
|
? 'ingest-pixi'
|
|
290
269
|
: 'ingest-react';
|
|
291
270
|
const ingest = adapter.ingest;
|
|
@@ -363,16 +342,19 @@ function checkEngineVersionPin(version: string): void {
|
|
|
363
342
|
* Every `AdapterRoot` field participates here or in a check above:
|
|
364
343
|
* `id` drives uniqueness; `adapter` (incl. its surface and
|
|
365
344
|
* `module`/`ingest.*` sub-fields) drives identity + capability-ceiling
|
|
366
|
-
* resolution; `
|
|
345
|
+
* resolution; `entry` drives the entry cross-field rule and is
|
|
367
346
|
* carried through; `zOrder` drives the final sort; `pausable`/`loop` are
|
|
368
347
|
* carried through as-is (their consumers are the runtime/CLI, T3.2/T3.3);
|
|
348
|
+
* `dev` is carried through for the four dev-layer readers (`mountManifestRoots`'s
|
|
349
|
+
* ship gate, the host's topmost stacking pass, the play compositor's capture
|
|
350
|
+
* exclusion, and the editor's chrome-not-content seam);
|
|
369
351
|
* `capabilities.{local,hosted}` drive tier resolution; `description` (T3.3
|
|
370
352
|
* slice 3) is carried through as-is — its consumer today is
|
|
371
353
|
* `resolveIngestDescriptor` (packages/editor/src/adapter-resolver.ts), which
|
|
372
354
|
* threads an ingest-three root's description into `IngestGame.description`.
|
|
373
355
|
*/
|
|
374
356
|
function resolveRoot(root: AdapterRoot): ResolvedAdapterRoot {
|
|
375
|
-
|
|
357
|
+
checkAdapterEntryRules(root);
|
|
376
358
|
const adapter = resolveAdapter(root);
|
|
377
359
|
|
|
378
360
|
return {
|
|
@@ -380,10 +362,10 @@ function resolveRoot(root: AdapterRoot): ResolvedAdapterRoot {
|
|
|
380
362
|
surface: adapter.surface,
|
|
381
363
|
description: root.description,
|
|
382
364
|
adapter,
|
|
383
|
-
scene: root.scene,
|
|
384
365
|
entry: root.entry,
|
|
385
366
|
zOrder: root.zOrder,
|
|
386
367
|
pausable: root.pausable,
|
|
368
|
+
dev: root.dev,
|
|
387
369
|
loop: root.loop,
|
|
388
370
|
capabilities: resolveCapabilities(root),
|
|
389
371
|
};
|
|
@@ -400,18 +382,25 @@ function resolveRoot(root: AdapterRoot): ResolvedAdapterRoot {
|
|
|
400
382
|
* any Zod validation failure or cross-field rule violation.
|
|
401
383
|
*/
|
|
402
384
|
export function loadGameManifest(raw: unknown): ResolvedGameManifest {
|
|
385
|
+
// Removed-format guard FIRST, on the raw value: a legacy `.vscn.json` root
|
|
386
|
+
// would otherwise fail Zod's `roots` discriminated union with a confusing
|
|
387
|
+
// shape error instead of a migration message (WO-8).
|
|
403
388
|
const parsed = GameManifestSchema.safeParse(raw);
|
|
404
389
|
if (!parsed.success) {
|
|
405
|
-
// D-V6: the `roots` `superRefine`'s
|
|
406
|
-
//
|
|
407
|
-
//
|
|
408
|
-
//
|
|
409
|
-
//
|
|
410
|
-
//
|
|
411
|
-
|
|
412
|
-
|
|
390
|
+
// D-V6: the `roots` `superRefine`'s composition issues — a duplicate root
|
|
391
|
+
// id, or more than one world root of the same medium — are the
|
|
392
|
+
// schema-level violations this loader surfaces as a clean, single `Error`
|
|
393
|
+
// (matching `checkUniqueRootIds`'s pre-D-V6 message shape) rather than a
|
|
394
|
+
// raw multi-issue dump, because each one already reads as a finished
|
|
395
|
+
// sentence naming the offending ids. Every other schema violation keeps
|
|
396
|
+
// propagating as the raw `ZodError` it always has (unchanged behavior,
|
|
397
|
+
// e.g. the iframe-reachable field-exclusivity / manifestVersion checks).
|
|
398
|
+
const compositionIssue = parsed.error.issues.find(
|
|
399
|
+
(issue) =>
|
|
400
|
+
issue.message.startsWith('Game manifest: duplicate root id') ||
|
|
401
|
+
issue.message.startsWith('Game manifest: a project has at most one'),
|
|
413
402
|
);
|
|
414
|
-
if (
|
|
403
|
+
if (compositionIssue) throw new Error(compositionIssue.message);
|
|
415
404
|
throw parsed.error;
|
|
416
405
|
}
|
|
417
406
|
const manifest: GameManifest = parsed.data;
|
|
@@ -437,7 +426,6 @@ export function loadGameManifest(raw: unknown): ResolvedGameManifest {
|
|
|
437
426
|
manifestVersion: manifest.manifestVersion,
|
|
438
427
|
name: manifest.name,
|
|
439
428
|
appId: manifest.appId,
|
|
440
|
-
editorPort: manifest.editorPort,
|
|
441
429
|
version: manifest.version,
|
|
442
430
|
engine: { version: manifest.engine.version },
|
|
443
431
|
roots,
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Finding the project manifest ON DISK. There is ONE filename — see
|
|
2
|
+
// `./filename.ts` for why the old one was removed rather than kept readable.
|
|
3
|
+
//
|
|
4
|
+
// Deliberately dependency-light — `node:fs` + `node:path` + `./filename` and
|
|
5
|
+
// NOTHING else (no Zod, no `./load`). Several callers are explicitly
|
|
6
|
+
// schema-free by design (`packages/editor/server/project-root-surface.ts`,
|
|
7
|
+
// `packages/editor/vite-plugin-ui-oid.ts`) and must be able to locate a
|
|
8
|
+
// manifest without dragging the validator in.
|
|
9
|
+
|
|
10
|
+
import { existsSync } from 'node:fs';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
import {
|
|
13
|
+
MANIFEST_FILENAME,
|
|
14
|
+
REMOVED_MANIFEST_FILENAME,
|
|
15
|
+
removedManifestFilenameMessage,
|
|
16
|
+
} from './filename';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Throw the rename instruction if `dir` carries only the REMOVED manifest
|
|
20
|
+
* filename. The `assertNoRemoved*` shape: a removed thing must reject loudly
|
|
21
|
+
* naming the exact fix, never be silently ignored (which would present as
|
|
22
|
+
* "this folder is not a project" — a diagnosis pointing nowhere).
|
|
23
|
+
*
|
|
24
|
+
* Returns normally when `dir` holds the current manifest, or neither file.
|
|
25
|
+
*/
|
|
26
|
+
export function assertNoRemovedManifestFilename(dir: string): void {
|
|
27
|
+
if (existsSync(join(dir, MANIFEST_FILENAME))) return;
|
|
28
|
+
if (!existsSync(join(dir, REMOVED_MANIFEST_FILENAME))) return;
|
|
29
|
+
throw new Error(removedManifestFilenameMessage(join(dir, REMOVED_MANIFEST_FILENAME)));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The manifest path inside `dir`. Always `vgai.project.json` — so a "not
|
|
34
|
+
* found" message names the filename a project must be using — except that a
|
|
35
|
+
* directory carrying only the removed `vgai.game.json` THROWS the rename
|
|
36
|
+
* instruction instead of reporting a phantom path.
|
|
37
|
+
*
|
|
38
|
+
* This is the drop-in replacement for a hand-written `join(dir, …)` everywhere
|
|
39
|
+
* a manifest is READ. Writers want {@link manifestWritePath}.
|
|
40
|
+
*/
|
|
41
|
+
export function resolveManifestPath(dir: string): string {
|
|
42
|
+
assertNoRemovedManifestFilename(dir);
|
|
43
|
+
return join(dir, MANIFEST_FILENAME);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** True when `dir` holds a manifest. A lone `vgai.game.json` throws. */
|
|
47
|
+
export function hasManifest(dir: string): boolean {
|
|
48
|
+
assertNoRemovedManifestFilename(dir);
|
|
49
|
+
return existsSync(join(dir, MANIFEST_FILENAME));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Where a manifest gets WRITTEN in `dir`. There is one answer. */
|
|
53
|
+
export function manifestWritePath(dir: string): string {
|
|
54
|
+
return join(dir, MANIFEST_FILENAME);
|
|
55
|
+
}
|