@vgai/engine 0.5.2 → 0.5.4

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.
Files changed (185) hide show
  1. package/README.md +18 -11
  2. package/package.json +10 -6
  3. package/schemas/engine-api.json +1 -68
  4. package/schemas/engine-api.md +1 -32
  5. package/schemas/engine-capabilities.json +30 -42
  6. package/schemas/{vgai-game.schema.json → vgai-project.schema.json} +52 -34
  7. package/src/adapter/adapter-surface.ts +5 -5
  8. package/src/adapter/authoring.ts +168 -226
  9. package/src/adapter/colyseus-networking-adapter.ts +44 -5
  10. package/src/adapter/first-party-systems.ts +156 -42
  11. package/src/adapter/host-context.ts +177 -46
  12. package/src/adapter/index.ts +47 -51
  13. package/src/adapter/ingest/game-contract.ts +2 -2
  14. package/src/adapter/ingest/scene-capture.ts +18 -19
  15. package/src/adapter/ingest/structural-ids.ts +127 -0
  16. package/src/adapter/ingest/upstream-pin.ts +9 -12
  17. package/src/adapter/loop-gate-report.ts +11 -11
  18. package/src/adapter/rapier-physics-adapter.ts +27 -9
  19. package/src/adapter/root-adapter.ts +217 -0
  20. package/src/adapter/{vgai-scene-game-adapter.ts → setup-three-root-adapter.ts} +173 -351
  21. package/src/adapter/system-adapter.ts +80 -63
  22. package/src/ai/navigation.ts +1 -1
  23. package/src/animation/animation-clock.ts +1 -1
  24. package/src/animation/camera-ownership.ts +1 -2
  25. package/src/animation/cubic-spline-interpolant.ts +132 -0
  26. package/src/animation/theatre-clock-binding.ts +2 -2
  27. package/src/animation/theatre-object-binding.ts +4 -4
  28. package/src/animation/xstate-animation-binding.ts +75 -5
  29. package/src/{scene/schema → asset-formats}/camera.ts +2 -5
  30. package/src/{scene/schema → asset-formats}/collider.ts +2 -5
  31. package/src/asset-formats/index.ts +54 -0
  32. package/src/{scene/schema → asset-formats}/instances.ts +10 -6
  33. package/src/{scene/schema → asset-formats}/light.ts +3 -6
  34. package/src/{scene/schema → asset-formats}/material.ts +4 -7
  35. package/src/{scene/schema → asset-formats}/mesh.ts +4 -7
  36. package/src/asset-formats/parse.ts +39 -0
  37. package/src/{scene/schema → asset-formats}/particles.ts +3 -6
  38. package/src/{scene/schema/environment.ts → asset-formats/render-env.ts} +23 -86
  39. package/src/{scene/schema → asset-formats}/tuples.ts +1 -1
  40. package/src/{scene/asset-loaders.ts → asset-loaders.ts} +8 -9
  41. package/src/asset-parse-error.ts +33 -0
  42. package/src/{scene/asset-registry.ts → asset-registry.ts} +1 -1
  43. package/src/assets.ts +1 -1
  44. package/src/audio/wav-encode.ts +9 -9
  45. package/src/canvas-react/engine-bridge.ts +59 -0
  46. package/src/canvas-react/index.ts +50 -0
  47. package/src/canvas-react/pixi-primitive.tsx +202 -0
  48. package/src/canvas-react/pixi-react-adapter.tsx +290 -0
  49. package/src/canvas-react/pixi-react-root-factory.tsx +88 -0
  50. package/src/canvas-react/world-context.ts +328 -0
  51. package/src/core/frame-pacing.ts +100 -0
  52. package/src/core/game-loop.ts +50 -28
  53. package/src/core/seeded-random.ts +7 -7
  54. package/src/core/sim-clock.ts +388 -0
  55. package/src/core/system-runner.ts +17 -63
  56. package/src/core/types.ts +34 -15
  57. package/src/data/data-asset.ts +3 -3
  58. package/src/data/data-check-core.ts +6 -7
  59. package/src/data/data-ref.ts +11 -11
  60. package/src/data/vite-plugin-data.ts +10 -10
  61. package/src/{scene/defaults.ts → defaults.ts} +18 -40
  62. package/src/dev/render-debug-adapter.ts +1 -1
  63. package/src/dev/webgl-frame-capture.ts +1 -1
  64. package/src/ecs/scene-index.ts +439 -0
  65. package/src/ecs/scene-query.ts +43 -0
  66. package/src/{scene → ecs}/user-data.ts +17 -36
  67. package/src/index.ts +7 -9
  68. package/src/input/input-manager.ts +29 -32
  69. package/src/input/input-types.ts +2 -2
  70. package/src/input/schema.ts +5 -5
  71. package/src/loader.ts +57 -0
  72. package/src/manifest/editor-port.ts +69 -0
  73. package/src/manifest/filename.ts +49 -0
  74. package/src/manifest/index.ts +8 -2
  75. package/src/manifest/load-file.ts +11 -0
  76. package/src/manifest/load.ts +65 -77
  77. package/src/manifest/locate.ts +55 -0
  78. package/src/manifest/schema.ts +400 -233
  79. package/src/{scene → physics}/collider-dimensions.ts +3 -3
  80. package/src/physics/physics-registry.ts +1 -1
  81. package/src/{world2d/authoring-2d.ts → pixi/authoring.ts} +24 -11
  82. package/src/pixi/index.ts +43 -0
  83. package/src/{world2d/ingest-iframe-2d.ts → pixi/ingest-iframe.ts} +9 -9
  84. package/src/{world2d/ingest2d.ts → pixi/ingest.ts} +28 -28
  85. package/src/{world2d/physics2d-registry.ts → pixi/physics-registry.ts} +1 -1
  86. package/src/{world2d/scene-capture-2d.ts → pixi/scene-capture.ts} +3 -3
  87. package/src/{world2d/system-adapters-2d.ts → pixi/system-adapters.ts} +2 -2
  88. package/src/react/unmanaged-root-detector.ts +26 -1
  89. package/src/react/use-data.ts +5 -5
  90. package/src/react/use-selection.tsx +15 -42
  91. package/src/react/{game-state.tsx → world-state.tsx} +44 -46
  92. package/src/render/auto-batcher.ts +1 -2
  93. package/src/{scene → render}/instance-mesh.ts +1 -1
  94. package/src/{scene → render}/light-camera-factory.ts +14 -13
  95. package/src/render/lod.ts +17 -0
  96. package/src/{scene → render}/material-factory.ts +8 -7
  97. package/src/{scene → render}/particles-factory.ts +62 -12
  98. package/src/render/render-batch-system.ts +14 -41
  99. package/src/render/render-features.ts +1 -1
  100. package/src/render/render-settings.ts +1 -2
  101. package/src/render/spark-renderer-lifecycle.ts +1 -1
  102. package/src/runtime/create-runtime.ts +352 -463
  103. package/src/runtime/debug-bridge.ts +148 -93
  104. package/src/runtime/debug-registry.ts +79 -54
  105. package/src/runtime/dev-layers.ts +40 -0
  106. package/src/runtime/frame-selector-cache.ts +4 -4
  107. package/src/runtime/game.ts +618 -369
  108. package/src/runtime/gameplay-rng-trap.ts +6 -7
  109. package/src/runtime/input-router.ts +11 -11
  110. package/src/runtime/mount-game.ts +54 -55
  111. package/src/runtime/mount-manifest.ts +154 -150
  112. package/src/runtime/presentation.ts +141 -0
  113. package/src/runtime/render-audio-control.ts +64 -53
  114. package/src/runtime/render-control.ts +45 -51
  115. package/src/runtime/render-seed.ts +3 -4
  116. package/src/runtime/state-bridge.ts +17 -18
  117. package/src/runtime/types.ts +94 -61
  118. package/src/setup/setup-renderer.ts +11 -6
  119. package/src/world3d-react/engine-bridge.ts +46 -33
  120. package/src/world3d-react/index.ts +31 -26
  121. package/src/world3d-react/r3f-adapter.tsx +211 -96
  122. package/src/world3d-react/r3f-root-factory.tsx +91 -0
  123. package/src/world3d-react/renderer-config.ts +137 -0
  124. package/src/world3d-react/world-context.ts +141 -111
  125. package/schemas/entity2d.schema.json +0 -468
  126. package/schemas/prefab.schema.json +0 -9992
  127. package/schemas/scn2d.schema.json +0 -494
  128. package/schemas/vscn.schema.json +0 -10851
  129. package/src/adapter/game-adapter.ts +0 -164
  130. package/src/adapter/ingest/overlay-applier.ts +0 -207
  131. package/src/adapter/ingest/overlay-apply.ts +0 -168
  132. package/src/adapter/ingest/overlay-file.ts +0 -126
  133. package/src/adapter/ingest/overlay-report.ts +0 -176
  134. package/src/animation/gsap-registration.ts +0 -184
  135. package/src/audio/audio-introspection.ts +0 -290
  136. package/src/audio/index.ts +0 -39
  137. package/src/audio/tone-clock-binding.ts +0 -98
  138. package/src/audio/tone-context.ts +0 -175
  139. package/src/audio/tone-offline-render.ts +0 -167
  140. package/src/ecs/component-manager.ts +0 -814
  141. package/src/ecs/game-component.ts +0 -260
  142. package/src/ecs/hmr-swap-report.ts +0 -65
  143. package/src/physics/trigger-dispatch.ts +0 -97
  144. package/src/react/root-adapter.tsx +0 -49
  145. package/src/scene/asset-paths.ts +0 -121
  146. package/src/scene/asset-ref-check.ts +0 -248
  147. package/src/scene/component-registry.ts +0 -51
  148. package/src/scene/parse.ts +0 -204
  149. package/src/scene/scene-apply.ts +0 -407
  150. package/src/scene/scene-diff-schema.ts +0 -115
  151. package/src/scene/scene-diff-types.ts +0 -29
  152. package/src/scene/scene-loader.ts +0 -1526
  153. package/src/scene/scene-query.ts +0 -63
  154. package/src/scene/scene-types.ts +0 -33
  155. package/src/scene/scene-version.ts +0 -40
  156. package/src/scene/schema/animation.ts +0 -46
  157. package/src/scene/schema/audio.ts +0 -25
  158. package/src/scene/schema/entity-ref.ts +0 -78
  159. package/src/scene/schema/entity.ts +0 -189
  160. package/src/scene/schema/index.ts +0 -51
  161. package/src/scene/schema/joint.ts +0 -26
  162. package/src/scene/schema/physics.ts +0 -49
  163. package/src/scene/schema/scene-file.ts +0 -292
  164. package/src/scene/schema/shadow.ts +0 -24
  165. package/src/scene/schema/spline.ts +0 -21
  166. package/src/world2d/asset-paths2d.ts +0 -44
  167. package/src/world2d/capture-to-scene2d.ts +0 -52
  168. package/src/world2d/collision-2d.ts +0 -99
  169. package/src/world2d/entity2d-asset.ts +0 -22
  170. package/src/world2d/index.ts +0 -91
  171. package/src/world2d/physics2d-transform.ts +0 -173
  172. package/src/world2d/physics2d-units.ts +0 -10
  173. package/src/world2d/pixi-game-adapter.ts +0 -439
  174. package/src/world2d/pixi-surface.ts +0 -78
  175. package/src/world2d/scene2d-identity.ts +0 -49
  176. package/src/world2d/scene2d-loader.ts +0 -433
  177. package/src/world2d/schema/entity2d.ts +0 -163
  178. package/src/world2d/schema/physics2d.ts +0 -64
  179. package/src/world2d/schema/sprite.ts +0 -99
  180. package/src/world2d/schema/tilemap.ts +0 -39
  181. package/src/world2d/schema/tuples2d.ts +0 -25
  182. package/src/world2d/transform-writer-2d.ts +0 -42
  183. package/src/world2d/types.ts +0 -74
  184. package/src/world3d-react/behavior.tsx +0 -146
  185. /package/src/{scene → render}/mesh-shadow.ts +0 -0
@@ -1,63 +0,0 @@
1
- import type * as THREE from 'three';
2
- import { getUserData } from './user-data';
3
-
4
- /**
5
- * Runtime queries over a loaded THREE scene graph (P1.6d).
6
- *
7
- * The scene's `tags` schema field is documented as "for runtime queries" but,
8
- * until now, no query existed. These helpers walk a THREE.Object3D graph and
9
- * read entity metadata off `userData`, so gameplay code can find objects by tag,
10
- * name, or attached component without bookkeeping its own indexes.
11
- *
12
- * Canonical metadata locations (in priority order):
13
- * - tags: `userData['tags']` (string[]) — falls back to `userData['entity'].tags`
14
- * - name: `Object3D.name`
15
- * - components: `userData['components']` (record or string[]) — falls back to
16
- * `userData['entity'].components` (record keyed by component name)
17
- */
18
-
19
- function getTags(obj: THREE.Object3D): string[] {
20
- const direct = getUserData(obj, 'tags');
21
- if (Array.isArray(direct)) return direct;
22
- const fromDef = getUserData(obj, 'entity')?.tags;
23
- return Array.isArray(fromDef) ? fromDef : [];
24
- }
25
-
26
- function getComponentNames(obj: THREE.Object3D): string[] {
27
- const direct = getUserData(obj, 'components');
28
- if (Array.isArray(direct)) return direct;
29
- if (direct && typeof direct === 'object') return Object.keys(direct);
30
- const fromDef = getUserData(obj, 'entity')?.components;
31
- if (fromDef && typeof fromDef === 'object') return Object.keys(fromDef);
32
- return [];
33
- }
34
-
35
- /** All objects in the graph carrying the given tag (depth-first, document order). */
36
- export function queryByTag(root: THREE.Object3D, tag: string): THREE.Object3D[] {
37
- const matches: THREE.Object3D[] = [];
38
- root.traverse((obj) => {
39
- if (getTags(obj).includes(tag)) matches.push(obj);
40
- });
41
- return matches;
42
- }
43
-
44
- /** All objects in the graph with the given name (depth-first, document order). */
45
- export function queryByName(root: THREE.Object3D, name: string): THREE.Object3D[] {
46
- const matches: THREE.Object3D[] = [];
47
- root.traverse((obj) => {
48
- if (obj.name === name) matches.push(obj);
49
- });
50
- return matches;
51
- }
52
-
53
- /**
54
- * All objects in the graph that have the named component attached
55
- * (depth-first, document order).
56
- */
57
- export function queryByComponent(root: THREE.Object3D, componentName: string): THREE.Object3D[] {
58
- const matches: THREE.Object3D[] = [];
59
- root.traverse((obj) => {
60
- if (getComponentNames(obj).includes(componentName)) matches.push(obj);
61
- });
62
- return matches;
63
- }
@@ -1,33 +0,0 @@
1
- /**
2
- * Scene file types — derived from Zod schemas (single source of truth).
3
- *
4
- * Raw types for the .vscn.json file format. Both the editor and the runtime
5
- * loader work with these directly, applying DEFAULTS (./defaults.ts) for omitted
6
- * fields — DEFAULTS is the single source of truth for default values.
7
- */
8
-
9
- export type {
10
- InstancesFile,
11
- MaterialFile,
12
- PrefabFile,
13
- SceneAnimation,
14
- SceneAudio,
15
- SceneCamera,
16
- SceneCollider,
17
- SceneEntity,
18
- SceneEnvironment,
19
- SceneFile,
20
- SceneJoint,
21
- SceneLight,
22
- SceneMaterial,
23
- SceneMesh,
24
- SceneNavigation,
25
- SceneParticles,
26
- ScenePhysics,
27
- ScenePhysicsSettings,
28
- ScenePostProcessing,
29
- SceneSpline,
30
- SceneToneMapping,
31
- } from './schema';
32
-
33
- export { mergePrefabInstance } from './schema';
@@ -1,40 +0,0 @@
1
- /**
2
- * The scene/prefab file format `version` range this engine checkout can
3
- * load (T2.3/D3 §1.E, docs/ENGINE-UPGRADE-DESIGN.md). `[min, max]` — today
4
- * `[1, 1]`: every shipped `.vscn.json`/`.prefab.json` is version 1, and no
5
- * migration machinery exists yet (a future format bump widens `max`, and a
6
- * future engine that drops support for `1` would raise `min`).
7
- *
8
- * Deliberately NOT in `./schema/` (that directory's byte contents are
9
- * pinned by `packages/engine/schemas/engine-capabilities.json`'s
10
- * `schemaDirHash`, refreshed only by `scripts/capabilities-hash.ts` — this
11
- * slice does not run that script). This is also why the check below is a
12
- * POST-Zod-parse function here in `scene/`, not a Zod refinement inside
13
- * `SceneFileSchema`/`PrefabFileSchema`: the generated `vscn.schema.json`/
14
- * `prefab.schema.json` stay byte-identical (the `version` field itself is
15
- * still just `z.number()`).
16
- */
17
- export const SUPPORTED_SCENE_VERSION_RANGE: readonly [number, number] = [1, 1];
18
-
19
- /**
20
- * Throw if `version` falls outside `SUPPORTED_SCENE_VERSION_RANGE`; silently
21
- * accept in-range versions. `fileKind` only changes the error message's noun
22
- * (`.vscn.json` and `.prefab.json` share this single version axis — verified
23
- * both schemas declare their own `version: z.number()` field, not a shared
24
- * ref, but the same counter/semantics per docs/ENGINE-UPGRADE-DESIGN.md).
25
- */
26
- export function checkSceneVersionInRange(
27
- version: number,
28
- fileKind: 'scene' | 'prefab' = 'scene',
29
- ): void {
30
- const [min, max] = SUPPORTED_SCENE_VERSION_RANGE;
31
- if (version >= min && version <= max) return;
32
- const cause =
33
- version > max
34
- ? `the engine checkout is older than this ${fileKind}`
35
- : `this ${fileKind} predates the oldest supported format version`;
36
- throw new Error(
37
- `${fileKind} version ${version} is outside the supported range [${min}, ${max}] — ` +
38
- `${cause}; see docs/ENGINE-UPGRADE-DESIGN.md`,
39
- );
40
- }
@@ -1,46 +0,0 @@
1
- import { z } from 'zod';
2
-
3
- // ---------------------------------------------------------------------------
4
- // D8 — declarative property tracks (formerly F5,
5
- // docs/VSCN-STRUCTURAL-GAPS-DESIGN.md) were REMOVED: Theatre
6
- // (`@theatre/core`) is the sole authored continuous-animation source (spec
7
- // §3.2, §10 D2-D4/D8). `PropertyTrackSchema`/`PropertyTrackRunner` and the
8
- // `tracks` field are deleted, not deprecated-in-place — see
9
- // packages/engine/src/scene/parse.ts (`assertNoRemovedPropertyTracks`) for
10
- // the loud migration-error guard that rejects any `.vscn`/`.prefab.json`
11
- // still authoring `animation.tracks`, since Zod would otherwise silently
12
- // strip an unrecognized field rather than failing loudly (Global AC §6).
13
- //
14
- // E5 — the AnimGraph state-machine runtime, `.animgraph.json` format, and
15
- // every consumer were removed (spec §3.3, §11 E5; XState + native Three
16
- // `AnimationMixer` replace it — see `xstate-animation-binding.ts`). The
17
- // `animGraph` field is deleted outright, not deprecated-in-place, for the
18
- // same reason as `tracks` above: Zod's default object mode would otherwise
19
- // silently STRIP an authored `animation.animGraph` rather than reporting it,
20
- // violating Global AC §6 ("Removed formats fail with a concise migration/
21
- // removal error rather than being partially read"). The raw-JSON guard
22
- // (`assertNoRemovedAnimGraph` in packages/engine/src/scene/parse.ts) rejects
23
- // any `.vscn`/`.prefab.json` still authoring it before Zod ever parses the
24
- // data — see packages/engine/test/anim-graph-removed.test.ts.
25
- //
26
- // `parameters` is deleted alongside `animGraph`, though it isn't itself named
27
- // in the spec/removal-inventory: its ONLY documented purpose was seeding an
28
- // `AnimGraph`'s initial parameter values (its only readers were
29
- // `scene-loader.ts`'s former animGraph branch, the editor's entity-factory.ts,
30
- // and AnimationSection.tsx's parameter-row UI — all deleted with the runtime).
31
- // Leaving it declared with zero remaining readers would violate this repo's
32
- // "no schema field without a runtime reader" policy (CLAUDE.md).
33
- // ---------------------------------------------------------------------------
34
-
35
- export const SceneAnimationSchema = z
36
- .object({
37
- autoplay: z.string().optional().describe('Clip name to play automatically on load'),
38
- loop: z.boolean().optional().describe('Whether the autoplay clip should loop'),
39
- clipAliases: z
40
- .record(z.string(), z.string())
41
- .optional()
42
- .describe('Map raw GLTF clip names to clean names'),
43
- })
44
- .describe('Animation playback configuration');
45
-
46
- export type SceneAnimation = z.infer<typeof SceneAnimationSchema>;
@@ -1,25 +0,0 @@
1
- import { z } from 'zod';
2
-
3
- export const SceneAudioSchema = z
4
- .object({
5
- src: z.string().optional().describe('Path to audio file (WAV, MP3, OGG)'),
6
- spatial: z.boolean().optional().describe('Enable 3D spatial audio (positional sound)'),
7
- volume: z.number().optional().describe('Playback volume 0–1'),
8
- refDistance: z
9
- .number()
10
- .optional()
11
- .describe('Distance at which volume starts to attenuate (spatial only)'),
12
- rolloffFactor: z
13
- .number()
14
- .optional()
15
- .describe('How quickly volume attenuates with distance (spatial only)'),
16
- maxDistance: z
17
- .number()
18
- .optional()
19
- .describe('Maximum distance at which sound is audible (spatial only)'),
20
- loop: z.boolean().optional().describe('Whether the audio should loop'),
21
- autoplay: z.boolean().optional().describe('Start playing automatically when the scene loads'),
22
- })
23
- .describe('Audio source configuration');
24
-
25
- export type SceneAudio = z.infer<typeof SceneAudioSchema>;
@@ -1,78 +0,0 @@
1
- import { z } from 'zod';
2
-
3
- /**
4
- * F6 (`docs/VSCN-STRUCTURAL-GAPS-DESIGN.md`) — a first-class, reusable
5
- * reference from one entity to another, for a `GameComponent`'s own static
6
- * Zod schema (e.g. `target: EntityRefSchema` — "this button opens THAT
7
- * door"). A branded string: on disk (and through generic component-data
8
- * validation) it is a plain string id, but a component field typed
9
- * `EntityRef` is compile-time distinct from an arbitrary `string` field, so a
10
- * bare label can't be assigned where a ref is expected.
11
- *
12
- * Resolution is a runtime concern, not a schema one: `scene-loader.ts`'s
13
- * `resolveEntityRefs` (the post-spawn pass, modeled on `joint.target`'s
14
- * existing resolution) rewrites the field from the authored string id to the
15
- * live `THREE.Object3D` — through the SAME `idMap` `createSceneJoints`
16
- * already uses, no new registry (G3) — before the referencing component's
17
- * `init()` runs.
18
- */
19
- export const EntityRefSchema = z
20
- .string()
21
- .brand<'EntityRef'>()
22
- .describe(
23
- 'A reference to another entity by its authored id. On disk it is a plain string id; the ' +
24
- 'scene loader resolves it to the live THREE.Object3D (WorldNode-as-truth) before the ' +
25
- "referencing component's init() runs. A ref to a missing id is a loud load error.",
26
- );
27
-
28
- export type EntityRef = z.infer<typeof EntityRefSchema>;
29
-
30
- /**
31
- * Identity registry of the `EntityRefSchema` object (there is exactly one —
32
- * this module's export), used by {@link isEntityRefSchema}.
33
- *
34
- * Why identity rather than a Zod "type name" check: zod 4's `.brand()` is a
35
- * type-only cast (see zod's own source — `inst.brand = () => inst`), not a
36
- * distinct wrapper class runtime code could switch on. A branded schema is,
37
- * at runtime, just the underlying `ZodString` (here, after `.describe()`
38
- * clones it once more — see zod's `check()`/`clone()`). So "is this an
39
- * EntityRef field" can only be answered by reference identity against the
40
- * one schema object every ref field is declared with — which is exactly
41
- * what we want anyway: a look-alike `z.string()` field must NOT count.
42
- */
43
- const ENTITY_REF_SCHEMAS = new WeakSet<object>();
44
- ENTITY_REF_SCHEMAS.add(EntityRefSchema);
45
-
46
- /**
47
- * Just enough of zod 4's internal `_def` shape to unwrap wrapper schemas.
48
- * Typed against `z.core.$ZodType` (the base/"core" schema type, NOT the
49
- * fuller "classic" `z.ZodType`) because that's what a `z.ZodObject`'s
50
- * `.shape` values are typed as (`ZodRawShape = core.$ZodShape`) — the type
51
- * every field schema `resolveEntityRefs` (`scene-loader.ts`) actually walks
52
- * arrives as.
53
- */
54
- interface UnwrappableDef {
55
- innerType?: z.core.$ZodType;
56
- }
57
-
58
- /**
59
- * Is `schema` an {@link EntityRefSchema} field — including when wrapped by
60
- * `.optional()`, `.default(...)`, or `.nullable()` (the common ways a
61
- * component author would declare an optional ref)? Unwraps those via zod 4's
62
- * internal `_def.innerType` (there is no public reflection API for this —
63
- * `packages/editor/src/components/inspectors/zod-fields.ts` uses the same
64
- * mechanism to introspect component schemas for the inspector) down to the
65
- * core schema, then checks it against the registered `EntityRefSchema`
66
- * instance by reference.
67
- */
68
- export function isEntityRefSchema(schema: z.core.$ZodType): boolean {
69
- let cur: z.core.$ZodType | undefined = schema;
70
- while (cur) {
71
- if (ENTITY_REF_SCHEMAS.has(cur)) return true;
72
- const inner: z.core.$ZodType | undefined = (cur as unknown as { _def?: UnwrappableDef })._def
73
- ?.innerType;
74
- if (!inner) return false;
75
- cur = inner;
76
- }
77
- return false;
78
- }
@@ -1,189 +0,0 @@
1
- import { z } from 'zod';
2
- import { buildRenderingZod } from '../../render/render-features';
3
- import { SceneAnimationSchema } from './animation';
4
- import { SceneAudioSchema } from './audio';
5
- import { SceneCameraOverrideSchema, SceneCameraSchema } from './camera';
6
- import { SceneJointSchema } from './joint';
7
- import { SceneLightOverrideSchema, SceneLightSchema } from './light';
8
- import { SceneMaterialOverrideSchema, SceneMaterialSchema } from './material';
9
- import { SceneMeshOverrideSchema, SceneMeshSchema } from './mesh';
10
- import { SceneParticlesOverrideSchema, SceneParticlesSchema } from './particles';
11
- import { ScenePhysicsOverrideSchema, ScenePhysicsSchema } from './physics';
12
- import { SceneShadowOverrideSchema, SceneShadowSchema } from './shadow';
13
- import { SceneSplineOverrideSchema, SceneSplineSchema } from './spline';
14
- import { TransformSchema, Vec3Schema } from './tuples';
15
-
16
- const BoneAttachmentSchema = z
17
- .object({
18
- target: z.string().describe('Entity ID whose animated skeleton owns the target bone'),
19
- bone: z.string().describe('Exact name of the target bone or socket Object3D'),
20
- })
21
- .describe(
22
- 'Attach this entity to a named bone/socket after the complete scene has loaded. The entity transform becomes its local attachment offset',
23
- );
24
-
25
- /**
26
- * User-defined ECS component data. Validated at runtime against ComponentRegistry.
27
- *
28
- * Scalars (number/string/boolean) plus homogeneous arrays of those scalars so
29
- * that vec3 (number[3]) and other array fields produced by the schema-driven
30
- * inspector round-trip through parse/validate. The component's own static Zod
31
- * schema (GameComponent.schema) does the precise per-field validation at load.
32
- *
33
- * F6 (`docs/VSCN-STRUCTURAL-GAPS-DESIGN.md`) entity refs (`EntityRefSchema`,
34
- * `./entity-ref.ts`) are branded strings — structurally still a `string` on
35
- * disk and through this coarse, per-component-agnostic schema, so they
36
- * already parse via the `z.string()` branch below without a dedicated union
37
- * member. (Deliberately not added as an explicit branch: this record's VALUE
38
- * schema is the one place in the file with no `.describe()` calls anywhere in
39
- * it — see `schema-consumption-map.ts`'s `entity.components` entry — and
40
- * `EntityRefSchema` carries one, which would flip `entity.components` from a
41
- * plain leaf into a `subtree: true` entry for no behavioral gain: the
42
- * precise, per-field validation of a ref — and the brand — comes from the
43
- * referencing component's OWN static schema in `applyComponents`, not from
44
- * this generic envelope.)
45
- */
46
- const ComponentScalarSchema = z.union([z.number(), z.string(), z.boolean()]);
47
- const ComponentValueSchema = z.union([
48
- ComponentScalarSchema,
49
- z.array(z.number()),
50
- z.array(z.string()),
51
- z.array(z.boolean()),
52
- ]);
53
- const ComponentDataSchema = z.record(z.string(), ComponentValueSchema);
54
- const ComponentsSchema = z.record(z.string(), ComponentDataSchema);
55
-
56
- const NavigationRoleSchema = z
57
- .object({
58
- role: z
59
- .enum(['walkable', 'obstacle'])
60
- .describe('Whether this entity is walkable surface or an obstacle'),
61
- })
62
- .describe('Navigation mesh role');
63
-
64
- // Fields shared by both standalone and prefab instance entities
65
- const SharedEntityFields = {
66
- id: z.string().optional().describe('Unique entity ID. Auto-generated if omitted'),
67
- name: z.string().describe('Human-readable entity name (shown in hierarchy panel)'),
68
- visible: z.boolean().optional().describe('Whether the entity and its children are rendered'),
69
- locked: z.boolean().optional().describe('Prevent selection and editing in the scene editor'),
70
- tags: z.array(z.string()).optional().describe('Arbitrary string tags for runtime queries'),
71
- prefab: z.string().optional().describe('Path to .prefab.json file to instantiate'),
72
- materialRef: z
73
- .string()
74
- .optional()
75
- .describe('Path to shared .mat.json material asset. Mutually exclusive with inline material'),
76
- transform: TransformSchema.optional().describe('Local transform relative to parent'),
77
- boneAttachment: BoneAttachmentSchema.optional().describe(
78
- 'Animated bone/socket attachment. Use transform for the local position, rotation, and scale offset',
79
- ),
80
- pivot: Vec3Schema.optional().describe(
81
- 'Local-space pivot point for rotation/scale. [0,0,0] = geometry center',
82
- ),
83
- navigation: NavigationRoleSchema.optional().describe('Navigation mesh role for this entity'),
84
- render: buildRenderingZod({ perEntityOnly: true })
85
- .optional()
86
- .describe(
87
- 'Per-entity render overrides (e.g. autoBatch:false to keep a custom-shader entity out of batching). Omit to inherit from the scene/engine default.',
88
- ),
89
- components: ComponentsSchema.optional().describe(
90
- 'User-defined ECS component data (validated against component registry)',
91
- ),
92
- };
93
-
94
- // Standalone entities: full section validation (type fields required)
95
- const StandaloneEntityBase = z.object({
96
- ...SharedEntityFields,
97
- mesh: SceneMeshSchema.optional().describe('Mesh geometry'),
98
- material: SceneMaterialSchema.optional().describe('Material appearance'),
99
- light: SceneLightSchema.optional().describe('Light source'),
100
- shadow: SceneShadowSchema.optional().describe('Shadow configuration'),
101
- physics: ScenePhysicsSchema.optional().describe('Physics rigid body'),
102
- camera: SceneCameraSchema.optional().describe('Camera'),
103
- audio: SceneAudioSchema.optional().describe('Audio source'),
104
- animation: SceneAnimationSchema.optional().describe('Animation configuration'),
105
- particles: SceneParticlesSchema.optional().describe('Particle system'),
106
- spline: SceneSplineSchema.optional().describe('Spline path'),
107
- joints: z
108
- .array(SceneJointSchema)
109
- .optional()
110
- .describe('Physics joints connecting to other bodies'),
111
- });
112
-
113
- // Prefab instance entities: section overrides are partial (merged with prefab root at load time)
114
- const PrefabInstanceBase = z.object({
115
- ...SharedEntityFields,
116
- prefab: z.string().describe('Path to .prefab.json file to instantiate'),
117
- mesh: SceneMeshOverrideSchema.optional().describe('Mesh overrides merged with prefab'),
118
- material: SceneMaterialOverrideSchema.optional().describe(
119
- 'Material overrides merged with prefab',
120
- ),
121
- light: SceneLightOverrideSchema.optional().describe('Light overrides merged with prefab'),
122
- shadow: SceneShadowOverrideSchema.optional().describe('Shadow overrides merged with prefab'),
123
- physics: ScenePhysicsOverrideSchema.optional().describe('Physics overrides merged with prefab'),
124
- camera: SceneCameraOverrideSchema.optional().describe('Camera overrides merged with prefab'),
125
- audio: SceneAudioSchema.optional().describe('Audio source'),
126
- animation: SceneAnimationSchema.optional().describe('Animation configuration'),
127
- particles: SceneParticlesOverrideSchema.optional().describe(
128
- 'Particle overrides merged with prefab',
129
- ),
130
- spline: SceneSplineOverrideSchema.optional().describe('Spline overrides merged with prefab'),
131
- joints: z
132
- .array(SceneJointSchema)
133
- .optional()
134
- .describe('Physics joints connecting to other bodies'),
135
- });
136
-
137
- /**
138
- * SceneEntity type — the strict form used by all consumer code.
139
- * Section types have required discriminant fields (type, bodyType, etc.).
140
- *
141
- * Prefab instances in scene files may have partial sections, but they are
142
- * always completed by mergePrefabInstance() before being consumed.
143
- */
144
- export type SceneEntity = z.infer<typeof StandaloneEntityBase> & {
145
- children?: SceneEntity[] | undefined;
146
- };
147
-
148
- /**
149
- * Validation schema for scene entity JSON.
150
- *
151
- * Standalone entities are validated strictly (section discriminants required).
152
- * Prefab instances accept partial section overrides — these are merged with
153
- * the prefab root at load time via mergePrefabInstance().
154
- *
155
- * Typed as SceneEntity (strict) because prefab instances always go through
156
- * mergePrefabInstance() before any code reads their section data.
157
- */
158
- export const SceneEntitySchema: z.ZodType<SceneEntity> = z.lazy(() =>
159
- z
160
- .union([
161
- StandaloneEntityBase.extend({
162
- children: z.array(SceneEntitySchema).optional().describe('Child entities'),
163
- }),
164
- PrefabInstanceBase.extend({
165
- children: z.array(SceneEntitySchema).optional().describe('Child entities'),
166
- }),
167
- ])
168
- // P1.6a: `material` (inline) and `materialRef` (shared .mat.json asset) are
169
- // mutually exclusive — allowing both lets one silently win at load time.
170
- // Reject up front instead of trapping the author.
171
- .superRefine((entity, ctx) => {
172
- if (entity.material !== undefined && entity.materialRef !== undefined) {
173
- ctx.addIssue({
174
- code: z.ZodIssueCode.custom,
175
- message:
176
- 'An entity cannot set both `material` (inline) and `materialRef` (shared .mat.json asset). Choose one.',
177
- path: ['material'],
178
- });
179
- }
180
- if (entity.boneAttachment !== undefined && entity.physics !== undefined) {
181
- ctx.addIssue({
182
- code: z.ZodIssueCode.custom,
183
- message:
184
- 'A bone-attached entity cannot declare physics. Attachments follow an animated transform; add gameplay collision to a separate entity.',
185
- path: ['boneAttachment'],
186
- });
187
- }
188
- }),
189
- ) as z.ZodType<SceneEntity>;
@@ -1,51 +0,0 @@
1
- // Zod schemas
2
-
3
- export type { SceneAnimation } from './animation';
4
- export { SceneAnimationSchema } from './animation';
5
- export type { SceneAudio } from './audio';
6
- export { SceneAudioSchema } from './audio';
7
- export type { SceneCamera } from './camera';
8
- export { SceneCameraSchema } from './camera';
9
- export type { SceneCollider } from './collider';
10
- export { SceneColliderSchema } from './collider';
11
- export type { SceneEntity } from './entity';
12
- export { SceneEntitySchema } from './entity';
13
- export type { EntityRef } from './entity-ref';
14
- export { EntityRefSchema, isEntityRefSchema } from './entity-ref';
15
- export type {
16
- SceneEnvironment,
17
- SceneNavigation,
18
- ScenePhysicsSettings,
19
- ScenePostProcessing,
20
- SceneToneMapping,
21
- } from './environment';
22
- export {
23
- SceneEnvironmentSchema,
24
- SceneNavigationSchema,
25
- ScenePhysicsSettingsSchema,
26
- ScenePostProcessingSchema,
27
- SceneToneMappingSchema,
28
- } from './environment';
29
- export type { InstancesFile } from './instances';
30
- export { InstancesFileSchema } from './instances';
31
- export type { SceneJoint } from './joint';
32
- export { SceneJointSchema } from './joint';
33
- export type { SceneLight } from './light';
34
- export { SceneLightSchema } from './light';
35
- export type { MaterialFile, SceneMaterial } from './material';
36
- export { MaterialFileSchema, SceneMaterialSchema } from './material';
37
- export type { SceneMesh } from './mesh';
38
- export { SceneMeshSchema } from './mesh';
39
- export type { SceneParticles } from './particles';
40
- export { SceneParticlesOverrideSchema, SceneParticlesSchema } from './particles';
41
- export type { ScenePhysics } from './physics';
42
- export { ScenePhysicsSchema } from './physics';
43
- export type { PrefabFile, SceneFile } from './scene-file';
44
- export { mergePrefabInstance, PrefabFileSchema, SceneFileSchema } from './scene-file';
45
- export type { SceneShadow } from './shadow';
46
- export { SceneShadowSchema } from './shadow';
47
- export type { SceneSpline } from './spline';
48
- export { SceneSplineOverrideSchema, SceneSplineSchema } from './spline';
49
- // Inferred types
50
- export type { Quat, SceneTransform, Vec3 } from './tuples';
51
- export { QuatSchema, TransformSchema, Vec3Schema } from './tuples';
@@ -1,26 +0,0 @@
1
- import { z } from 'zod';
2
- import { Vec3Schema } from './tuples';
3
-
4
- export const SceneJointSchema = z
5
- .object({
6
- type: z
7
- .enum(['fixed', 'revolute', 'prismatic', 'spherical', 'spring'])
8
- .describe('Joint constraint type'),
9
- target: z.string().describe('Entity ID of the body this joint connects to'),
10
- anchor: Vec3Schema.optional().describe('Local anchor point on this body'),
11
- targetAnchor: Vec3Schema.optional().describe('Local anchor point on the target body'),
12
- axis: Vec3Schema.optional().describe('Rotation/slide axis for revolute and prismatic joints'),
13
- limits: z
14
- .object({
15
- min: z.number().describe('Minimum angle (degrees) or distance'),
16
- max: z.number().describe('Maximum angle (degrees) or distance'),
17
- })
18
- .optional()
19
- .describe('Angle or distance limits for revolute/prismatic joints'),
20
- stiffness: z.number().optional().describe('Spring stiffness coefficient'),
21
- damping: z.number().optional().describe('Spring damping coefficient'),
22
- restLength: z.number().optional().describe('Spring rest length'),
23
- })
24
- .describe('Physics joint constraint between two bodies');
25
-
26
- export type SceneJoint = z.infer<typeof SceneJointSchema>;
@@ -1,49 +0,0 @@
1
- import { z } from 'zod';
2
- import { SceneColliderOverrideSchema, SceneColliderSchema } from './collider';
3
-
4
- export const ScenePhysicsSchema = z
5
- .object({
6
- bodyType: z
7
- .enum(['dynamic', 'fixed', 'kinematic'])
8
- .describe(
9
- 'Physics body type: dynamic (simulated), fixed (immovable), kinematic (script-driven). ' +
10
- 'T1.1/R1c: ANY physics body (dynamic, fixed, OR kinematic) nested under an ancestor ' +
11
- 'entity that itself has a "dynamic" or "kinematic" body is unsupported (loud load ' +
12
- 'error) — Rapier has no parent/child body relationship, so a moving ancestor cannot ' +
13
- "carry a nested body of ANY type with it; the child's Object3D would stay pinned to " +
14
- "its own (non-moving) body's pose while the ancestor moves out from under it. " +
15
- 'Nesting under a "fixed"/static or physics-less transformed ancestor is fine.',
16
- ),
17
- mass: z
18
- .number()
19
- .optional()
20
- .describe('Explicit mass override. If omitted, computed from collider density and volume'),
21
- collider: SceneColliderSchema.describe(
22
- 'Collider shape attached to this body. T1.1/T1.2: the Rapier body is created at this ' +
23
- "entity's COMPOSED WORLD transform (parent chain × own local transform) — Rapier has " +
24
- "no concept of a parent Object3D, so nested entities' transforms are baked in at spawn " +
25
- 'time, not read live thereafter.',
26
- ),
27
- })
28
- .describe('Physics rigid body configuration');
29
-
30
- /** Partial override — uses SceneColliderOverrideSchema for deep partial on collider. */
31
- export const ScenePhysicsOverrideSchema = z.object({
32
- bodyType: z
33
- .enum(['dynamic', 'fixed', 'kinematic'])
34
- .optional()
35
- .describe(
36
- 'Physics body type: dynamic (simulated), fixed (immovable), kinematic (script-driven). ' +
37
- 'A body of ANY type nested under a "dynamic"/"kinematic"-bodied ancestor throws a loud ' +
38
- 'load error (T1.1/R1c) — see ScenePhysicsSchema.bodyType.',
39
- ),
40
- mass: z
41
- .number()
42
- .optional()
43
- .describe('Explicit mass override. If omitted, computed from collider density and volume'),
44
- collider: SceneColliderOverrideSchema.optional().describe(
45
- 'Partial collider overrides merged with prefab',
46
- ),
47
- });
48
-
49
- export type ScenePhysics = z.infer<typeof ScenePhysicsSchema>;