@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.
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
@@ -2,12 +2,16 @@ import { z } from 'zod';
2
2
 
3
3
  /**
4
4
  * `.instances.json` — a declarative, authored-data instance-transform asset
5
- * (F3, docs/VSCN-STRUCTURAL-GAPS-DESIGN.md). Referenced by `mesh.instances`
6
- * as a bare array of 10-number tuples, one per instance, each
7
- * `[posX,posY,posZ, quatX,quatY,quatZ,quatW, scaleX,scaleY,scaleZ]`. The
8
- * scene loader fetches+parses this file and renders it as one
9
- * `THREE.InstancedMesh` (one draw call) bulk transform data is referenced
10
- * as an asset, never inlined into the vscn (keeps vscn thin — gate G4).
5
+ * (F3). Referenced by `mesh.instances` as a bare array of 10-number tuples,
6
+ * one per instance, each `[posX,posY,posZ, quatX,quatY,quatZ,quatW,
7
+ * scaleX,scaleY,scaleZ]`. The scene loader fetches+parses this file and
8
+ * renders it as one `THREE.InstancedMesh` (one draw call) bulk transform
9
+ * data is referenced as an asset, never inlined into the referencing
10
+ * document (gate G4).
11
+ *
12
+ * WO-8: the document that referenced it was `.vscn.json`, and that format is
13
+ * deleted. `.instances.json` survives as a FETCHED-ASSET format with its own
14
+ * `parseInstancesFile` — nothing in the shipped tree references one today.
11
15
  */
12
16
  export const InstancesFileSchema = z
13
17
  .array(
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
2
 
3
- const SceneLightBase = z.object({
3
+ const LightDescriptorBase = z.object({
4
4
  type: z.enum(['directional', 'point', 'spot', 'hemisphere', 'area']).describe('Light type'),
5
5
  color: z.string().optional().describe('Light color as CSS hex string'),
6
6
  intensity: z
@@ -42,12 +42,9 @@ const SceneLightBase = z.object({
42
42
  ),
43
43
  });
44
44
 
45
- export const SceneLightSchema = SceneLightBase.refine(
45
+ export const LightDescriptorSchema = LightDescriptorBase.refine(
46
46
  (l) => l.type !== 'spot' || l.angle === undefined || l.angle > 0,
47
47
  { message: 'Spot light angle must be positive' },
48
48
  ).describe('Light source configuration');
49
49
 
50
- /** Partial schema for prefab instance overrides (type not required). */
51
- export const SceneLightOverrideSchema = SceneLightBase.partial();
52
-
53
- export type SceneLight = z.infer<typeof SceneLightSchema>;
50
+ export type LightDescriptor = z.infer<typeof LightDescriptorSchema>;
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
2
 
3
- const SceneMaterialObjectSchema = z
3
+ const MaterialDescriptorObjectSchema = z
4
4
  .object({
5
5
  type: z.enum(['standard', 'physical', 'basic', 'toon']).describe('Material shading model'),
6
6
  color: z.string().optional().describe('Base color as CSS hex string (e.g. "#ff0000")'),
@@ -86,21 +86,18 @@ const SceneMaterialObjectSchema = z
86
86
  })
87
87
  .strict();
88
88
 
89
- export const SceneMaterialSchema = SceneMaterialObjectSchema.describe(
89
+ export const MaterialDescriptorSchema = MaterialDescriptorObjectSchema.describe(
90
90
  'Material appearance properties',
91
91
  );
92
92
 
93
- /** Partial schema for prefab instance overrides (type not required). */
94
- export const SceneMaterialOverrideSchema = SceneMaterialObjectSchema.partial();
95
-
96
- export type SceneMaterial = z.infer<typeof SceneMaterialSchema>;
93
+ export type MaterialDescriptor = z.infer<typeof MaterialDescriptorSchema>;
97
94
 
98
95
  /** .mat.json file format for shared/reusable material assets. */
99
96
  export const MaterialFileSchema = z
100
97
  .object({
101
98
  version: z.number().describe('File format version'),
102
99
  name: z.string().describe('Human-readable material name'),
103
- material: SceneMaterialSchema.describe('Material definition'),
100
+ material: MaterialDescriptorSchema.describe('Material definition'),
104
101
  })
105
102
  .describe('Shared material asset file (.mat.json)');
106
103
 
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
2
 
3
- const SceneMeshBase = z
3
+ const MeshDescriptorBase = z
4
4
  .object({
5
5
  type: z
6
6
  .enum(['box', 'sphere', 'plane', 'cylinder', 'capsule', 'gltf', 'splat'])
@@ -37,7 +37,7 @@ const SceneMeshBase = z
37
37
  'Path to an instance-transform asset (.instances.json: an array of ' +
38
38
  '[posX,posY,posZ, quatX,quatY,quatZ,quatW, scaleX,scaleY,scaleZ] tuples). Renders one ' +
39
39
  'THREE.InstancedMesh (one draw call). Bulk transform data is referenced as an asset, ' +
40
- 'never inlined — keeps vscn thin.',
40
+ 'never inlined — keeps the document thin.',
41
41
  ),
42
42
  lod: z
43
43
  .array(
@@ -66,7 +66,7 @@ const SceneMeshBase = z
66
66
  })
67
67
  .strict();
68
68
 
69
- export const SceneMeshSchema = SceneMeshBase.refine(
69
+ export const MeshDescriptorSchema = MeshDescriptorBase.refine(
70
70
  (m) => (m.type !== 'gltf' && m.type !== 'splat') || m.src != null,
71
71
  {
72
72
  message: "External render asset requires 'src' field",
@@ -88,7 +88,4 @@ export const SceneMeshSchema = SceneMeshBase.refine(
88
88
  })
89
89
  .describe('Mesh geometry definition');
90
90
 
91
- /** Partial schema for prefab instance overrides (type not required). */
92
- export const SceneMeshOverrideSchema = SceneMeshBase.partial();
93
-
94
- export type SceneMesh = z.infer<typeof SceneMeshSchema>;
91
+ export type MeshDescriptor = z.infer<typeof MeshDescriptorSchema>;
@@ -0,0 +1,39 @@
1
+ import { AssetParseError } from '../asset-parse-error';
2
+ import type { InstancesFile } from './instances';
3
+ import { InstancesFileSchema } from './instances';
4
+ import type { MaterialFile } from './material';
5
+ import { MaterialFileSchema } from './material';
6
+
7
+ // The error class itself is format-neutral and lives in
8
+ // `engine/src/asset-parse-error.ts` (the input-map loader raises it too).
9
+ // Re-exported here so callers have one import for parse + error.
10
+ export { AssetParseError };
11
+
12
+ /**
13
+ * Parse a `.mat.json` shared material asset (T4.6). There is no version-range
14
+ * gate for this format — `MaterialFileSchema`'s `version` field is RESERVED
15
+ * (see `schema-consumption-map.ts`).
16
+ *
17
+ * Formerly `@engine/scene/parse`'s `parseMaterialFile`; moved here when WO-8
18
+ * deleted the `.vscn` scene format that module was named for.
19
+ */
20
+ export function parseMaterialFile(json: unknown, filePath?: string): MaterialFile {
21
+ const result = MaterialFileSchema.safeParse(json);
22
+ if (!result.success) {
23
+ throw new AssetParseError(result.error.issues, filePath);
24
+ }
25
+ return result.data;
26
+ }
27
+
28
+ /**
29
+ * Parse a `.instances.json` instance-transform asset (F3). Mirrors
30
+ * `parseMaterialFile` exactly — no version-range gate (the format is a bare
31
+ * tuple array, no `version` field).
32
+ */
33
+ export function parseInstancesFile(json: unknown, filePath?: string): InstancesFile {
34
+ const result = InstancesFileSchema.safeParse(json);
35
+ if (!result.success) {
36
+ throw new AssetParseError(result.error.issues, filePath);
37
+ }
38
+ return result.data;
39
+ }
@@ -320,9 +320,9 @@ const BurstSchema = z
320
320
  })
321
321
  .describe('Timed particle burst');
322
322
 
323
- // --- Top-level SceneParticles ---
323
+ // --- Top-level ParticlesDescriptor ---
324
324
 
325
- export const SceneParticlesSchema = z
325
+ export const ParticlesDescriptorSchema = z
326
326
  .object({
327
327
  // Lifecycle
328
328
  autoDestroy: z
@@ -392,7 +392,4 @@ export const SceneParticlesSchema = z
392
392
  })
393
393
  .describe('Particle system configuration');
394
394
 
395
- /** Partial schema for prefab instance overrides. */
396
- export const SceneParticlesOverrideSchema = SceneParticlesSchema.partial();
397
-
398
- export type SceneParticles = z.infer<typeof SceneParticlesSchema>;
395
+ export type ParticlesDescriptor = z.infer<typeof ParticlesDescriptorSchema>;
@@ -1,45 +1,18 @@
1
+ /**
2
+ * Render settings — the tone-mapping / post-processing / render-feature scope
3
+ * the engine's renderer setup reads (`setup/setup-renderer.ts`).
4
+ *
5
+ * This used to be the `environment` block of the retired `.vscn.json` scene
6
+ * format (WO-8). The format is gone; these three scopes stayed because
7
+ * `setup-renderer.ts` genuinely reads every field and builds the
8
+ * postprocessing chain from them. Everything that was `.vscn`-only (`skybox`,
9
+ * `fog`, `ambient`, `background`, `envMapIntensity`, `navigation`, `physics`)
10
+ * went with the format.
11
+ */
1
12
  import { z } from 'zod';
2
- import { buildRenderingZod } from '../../render/render-features';
13
+ import { buildRenderingZod } from '../render/render-features';
3
14
 
4
- export const SceneNavigationSchema = z
5
- .object({
6
- cellSize: z
7
- .number()
8
- .optional()
9
- .describe('Navmesh voxel cell size (smaller = more detail, slower bake)'),
10
- cellHeight: z.number().optional().describe('Navmesh voxel cell height'),
11
- walkableSlopeAngle: z
12
- .number()
13
- .optional()
14
- .describe('Maximum slope angle (degrees) agents can walk on'),
15
- walkableHeight: z
16
- .number()
17
- .optional()
18
- .describe('Minimum ceiling height for walkable areas (in voxels)'),
19
- walkableClimb: z
20
- .number()
21
- .optional()
22
- .describe('Maximum step height agents can climb (in voxels)'),
23
- walkableRadius: z.number().optional().describe('Agent radius for navmesh erosion (in voxels)'),
24
- maxEdgeLen: z.number().optional().describe('Maximum contour edge length in the navmesh'),
25
- maxSimplificationError: z
26
- .number()
27
- .optional()
28
- .describe('Maximum deviation for navmesh edge simplification'),
29
- minRegionArea: z
30
- .number()
31
- .optional()
32
- .describe('Minimum region area — smaller regions are removed'),
33
- mergeRegionArea: z
34
- .number()
35
- .optional()
36
- .describe('Regions smaller than this are merged with neighbors'),
37
- })
38
- .describe('Recast navigation mesh bake parameters');
39
-
40
- export type SceneNavigation = z.infer<typeof SceneNavigationSchema>;
41
-
42
- export const SceneToneMappingSchema = z
15
+ export const ToneMappingDescriptorSchema = z
43
16
  .object({
44
17
  mode: z
45
18
  .enum(['none', 'linear', 'reinhard', 'cineon', 'aces', 'agx'])
@@ -49,9 +22,9 @@ export const SceneToneMappingSchema = z
49
22
  })
50
23
  .describe('Tone mapping configuration');
51
24
 
52
- export type SceneToneMapping = z.infer<typeof SceneToneMappingSchema>;
25
+ export type ToneMappingDescriptor = z.infer<typeof ToneMappingDescriptorSchema>;
53
26
 
54
- export const ScenePostProcessingSchema = z
27
+ export const PostProcessingDescriptorSchema = z
55
28
  .object({
56
29
  // ── Bloom & Vignette ──────────────────────────────────────────────────
57
30
  bloom: z
@@ -329,56 +302,20 @@ export const ScenePostProcessingSchema = z
329
302
  })
330
303
  .describe('Post-processing effects');
331
304
 
332
- export type ScenePostProcessing = z.infer<typeof ScenePostProcessingSchema>;
305
+ export type PostProcessingDescriptor = z.infer<typeof PostProcessingDescriptorSchema>;
333
306
 
334
- export const ScenePhysicsSettingsSchema = z
307
+ export const RenderEnvironmentSchema = z
335
308
  .object({
336
- collisionLayers: z
337
- .array(z.string())
338
- .optional()
339
- .describe('Named collision layers (up to 16). Index = bit position in collision bitmasks'),
340
- })
341
- .describe('Physics world settings');
342
-
343
- export type ScenePhysicsSettings = z.infer<typeof ScenePhysicsSettingsSchema>;
344
-
345
- export const SceneEnvironmentSchema = z
346
- .object({
347
- background: z.string().optional().describe('Background color as CSS hex string'),
348
- ambient: z
349
- .object({
350
- color: z.string().describe('Ambient light color'),
351
- intensity: z.number().describe('Ambient light intensity'),
352
- })
353
- .optional()
354
- .describe('Global ambient light'),
355
- fog: z
356
- .object({
357
- type: z.enum(['linear', 'exponential']).describe('Fog falloff type'),
358
- color: z.string().describe('Fog color'),
359
- near: z.number().optional().describe('Fog start distance (linear only)'),
360
- far: z.number().optional().describe('Fog end distance (linear only)'),
361
- density: z.number().optional().describe('Fog density (exponential only)'),
362
- })
363
- .optional()
364
- .describe('Distance fog settings'),
365
- skybox: z.string().optional().describe('Path to HDR/EXR skybox environment map'),
366
- envMapIntensity: z
367
- .number()
368
- .optional()
369
- .describe('Environment map reflection intensity multiplier'),
370
- toneMapping: SceneToneMappingSchema.optional().describe('Tone mapping configuration'),
371
- postProcessing: ScenePostProcessingSchema.optional().describe(
372
- 'Post-processing effects (bloom, vignette)',
309
+ toneMapping: ToneMappingDescriptorSchema.optional().describe('Tone mapping configuration'),
310
+ postProcessing: PostProcessingDescriptorSchema.optional().describe(
311
+ 'Post-processing effects (bloom, vignette, SSAO, SSR, ...)',
373
312
  ),
374
- navigation: SceneNavigationSchema.optional().describe('Navigation mesh bake settings'),
375
- physics: ScenePhysicsSettingsSchema.optional().describe('Physics world settings'),
376
313
  rendering: buildRenderingZod()
377
314
  .optional()
378
315
  .describe(
379
- 'Scene-level render feature toggles (auto-batching, culling, LOD, shadows, etc.). Omit a field to inherit the engine default. Derived from the render-feature registry.',
316
+ 'Render feature toggles (auto-batching, culling, LOD, shadows, etc.). Omit a field to inherit the engine default. Derived from the render-feature registry.',
380
317
  ),
381
318
  })
382
- .describe('Scene-level environment and rendering settings');
319
+ .describe('Renderer-level settings read by setup/setup-renderer.ts');
383
320
 
384
- export type SceneEnvironment = z.infer<typeof SceneEnvironmentSchema>;
321
+ export type RenderEnvironment = z.infer<typeof RenderEnvironmentSchema>;
@@ -18,4 +18,4 @@ export const TransformSchema = z
18
18
  })
19
19
  .describe('Local transform relative to parent entity');
20
20
 
21
- export type SceneTransform = z.infer<typeof TransformSchema>;
21
+ export type Transform = z.infer<typeof TransformSchema>;
@@ -25,9 +25,9 @@
25
25
  import type { SplatMesh } from '@sparkjsdev/spark';
26
26
  import * as THREE from 'three';
27
27
  import * as SkeletonUtils from 'three/addons/utils/SkeletonUtils.js';
28
- import { gltfLoader, resolveUrl, textureLoader } from '../loader';
29
- import { SceneParseError } from './parse';
30
- import { setUserData } from './user-data';
28
+ import { AssetParseError } from './asset-parse-error';
29
+ import { setUserData } from './ecs/user-data';
30
+ import { gltfLoader, resolveUrl, textureLoader } from './loader';
31
31
 
32
32
  const textureCache = new Map<string, THREE.Texture>();
33
33
  const gltfCache = new Map<
@@ -146,9 +146,8 @@ export async function loadSplat(url: string, providedBytes?: Uint8Array): Promis
146
146
 
147
147
  /**
148
148
  * Resolve a single named node inside an already-loaded glTF scene graph (F4,
149
- * docs/VSCN-STRUCTURAL-GAPS-DESIGN.md `mesh.node`). Pure and headlessly
150
- * testable — no fetch, no cache lookups; callers pass the `scene` they already
151
- * got from {@link loadGLTF}.
149
+ * `mesh.node`). Pure and headlessly testable — no fetch, no cache lookups;
150
+ * callers pass the `scene` they already got from {@link loadGLTF}.
152
151
  *
153
152
  * The returned node is the SAME object (and shares the SAME geometry) as
154
153
  * found in `scene` — nothing is cloned or copied out. `scene` is already a
@@ -158,7 +157,7 @@ export async function loadSplat(url: string, providedBytes?: Uint8Array): Promis
158
157
  * Atomic-subtree guard: a `SkinnedMesh` cannot be lifted out of its armature
159
158
  * (its skeleton/bind matrices reference sibling bone nodes elsewhere in the
160
159
  * hierarchy), so resolving a node that IS a SkinnedMesh, or that CONTAINS one,
161
- * throws a loud `SceneParseError` instead of silently producing broken skinning.
160
+ * throws a loud `AssetParseError` instead of silently producing broken skinning.
162
161
  */
163
162
  export function resolveGltfNode(
164
163
  scene: THREE.Object3D,
@@ -167,7 +166,7 @@ export function resolveGltfNode(
167
166
  ): THREE.Object3D {
168
167
  const node = scene.getObjectByName(nodeName);
169
168
  if (!node) {
170
- throw new SceneParseError(
169
+ throw new AssetParseError(
171
170
  [
172
171
  {
173
172
  code: 'custom',
@@ -187,7 +186,7 @@ export function resolveGltfNode(
187
186
  });
188
187
  }
189
188
  if (containsSkinned) {
190
- throw new SceneParseError(
189
+ throw new AssetParseError(
191
190
  [
192
191
  {
193
192
  code: 'custom',
@@ -0,0 +1,33 @@
1
+ /**
2
+ * The GENERIC fetched-asset parse error.
3
+ *
4
+ * Every authored asset format the engine validates at fetch time throws this
5
+ * when Zod rejects the payload, naming the offending file (T4.6). It lives
6
+ * here — beside `loader.ts`/`assets.ts` — rather than inside a format module so
7
+ * every caller (the `.mat.json` parser, the input-map loader in
8
+ * `input/input-manager.ts`) does not have to reach into the format half for
9
+ * it.
10
+ *
11
+ * It was called `SceneParseError` until WO-9 task #6. The extraction that gave
12
+ * this file its name deliberately deferred the class rename ("extraction, not
13
+ * redesign"); with `.vscn` gone the old name described nothing — a `.mat.json`
14
+ * or `.inputmap.json` failure is not a scene failure — so the class and its
15
+ * message header now say `asset`.
16
+ */
17
+
18
+ import type { ZodIssue } from 'zod';
19
+
20
+ export class AssetParseError extends Error {
21
+ readonly issues: ZodIssue[];
22
+ /** The offending file's path/URL, when the caller knows it (T4.6). */
23
+ readonly filePath?: string | undefined;
24
+
25
+ constructor(issues: ZodIssue[], filePath?: string) {
26
+ const msg = issues.map((i) => ` ${i.path.join('.')}: ${i.message}`).join('\n');
27
+ const header = filePath ? `Asset validation failed (${filePath}):` : 'Asset validation failed:';
28
+ super(`${header}\n${msg}`);
29
+ this.name = 'AssetParseError';
30
+ this.issues = issues;
31
+ this.filePath = filePath;
32
+ }
33
+ }
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import * as THREE from 'three';
9
- import { resolveUrl } from '../loader';
9
+ import { resolveUrl } from './loader';
10
10
 
11
11
  export interface ImportCorrection {
12
12
  /** Uniform scale factor applied to the GLTF clone before entity transform. */
package/src/assets.ts CHANGED
@@ -34,7 +34,7 @@ export function createAssetCache(): AssetCache {
34
34
  // Use the shared, DRACO-wired GLTFLoader (A2 / CB2) — no second loader instance.
35
35
  // This cache keeps the parsed source GLTFResult (load/get semantics for gameplay
36
36
  // code); it does NOT clone. Callers that need disposal-safe clones use
37
- // scene/asset-loaders `loadGLTF`, which clones + tags `__sharedGeometry`.
37
+ // asset-loaders `loadGLTF`, which clones + tags `__sharedGeometry`.
38
38
 
39
39
  function load<T = unknown>(url: string): Promise<T> {
40
40
  const existing = cache.get(url);
@@ -1,13 +1,13 @@
1
1
  /**
2
- * Pure PCM -> 16-bit WAV encoder (spec §15 I6 "Encode and Mux", audio half
3
- * `docs/AI-NATIVE-AUTHORING-IMPLEMENTATION-SPEC.md`). This module takes
4
- * already-rendered Float32 channel data (e.g. `AudioBuffer.getChannelData(n)`
5
- * from a `Tone.Offline` render, `audio/tone-offline-render.ts`) and produces
6
- * a standard little-endian, 16-bit-PCM, interleaved RIFF/WAVE file as raw
7
- * bytes — no `AudioBuffer`/`AudioContext` dependency, so this runs equally in
8
- * a browser render-mode page (`runtime/render-audio-control.ts`) and in
9
- * plain Node (this module's own `packages/engine/test/audio/wav-encode.test.ts`,
10
- * no Playwright needed).
2
+ * Pure PCM -> 16-bit WAV encoder (spec §15 I6 "Encode and Mux", audio half).
3
+ * This module takes already-rendered Float32 channel data (e.g.
4
+ * `AudioBuffer.getChannelData(n)` from a `Tone.Offline` render,
5
+ * `audio/tone-offline-render.ts`) and produces a standard little-endian,
6
+ * 16-bit-PCM, interleaved RIFF/WAVE file as raw bytes — no
7
+ * `AudioBuffer`/`AudioContext` dependency, so this runs equally in a browser
8
+ * render-mode page (`runtime/render-audio-control.ts`) and in plain Node (this
9
+ * module's own `packages/engine/test/audio/wav-encode.test.ts`, no Playwright
10
+ * needed).
11
11
  *
12
12
  * 16-bit PCM (not 32-bit float) is the deliberate choice named by the I6 AC
13
13
  * ("16-bit PCM WAV is fine") — it is universally decodable by every FFmpeg
@@ -0,0 +1,59 @@
1
+ /**
2
+ * `@engine/canvas-react` — the React context that carries a canvas world's
3
+ * engine runtime into its `@pixi/react` tree: the world's `GameContext`, read
4
+ * with {@link useGameContext}.
5
+ *
6
+ * The bridge value is built by `createPixiReactAdapter`'s `mount()`
7
+ * (./pixi-react-adapter.tsx), whose `update(dt)` runs this world's phases under
8
+ * the HOST loop — so registered systems tick in engine phase order, pause
9
+ * included (no update while the world is frozen).
10
+ *
11
+ * Behavior in a canvas world is components + hooks: `useGameContext` reaches
12
+ * engine services, `@pixi/react`'s own `useTick` is the per-frame hook, and
13
+ * `useApplication` is the door to the live `Application`.
14
+ */
15
+
16
+ import { createContext, useContext } from 'react';
17
+ import type { GameContext } from '../runtime/types';
18
+
19
+ export interface EngineBridgeValue {
20
+ /** This root's `GameContext` — `input`, `debug`, `random`, `clock`, `game`,
21
+ * `roots`, `systems`, `registerSystemAdapter`. Present so ordinary
22
+ * components can reach engine services with a HOOK (see
23
+ * {@link useGameContext}). */
24
+ readonly ctx?: GameContext | undefined;
25
+ }
26
+
27
+ export const EngineBridge = createContext<EngineBridgeValue | null>(null);
28
+
29
+ /**
30
+ * This canvas root's `GameContext`, for use inside any component in the tree.
31
+ *
32
+ * An ordinary component does what a model would write:
33
+ *
34
+ * ```tsx
35
+ * function Player({ speed = 220 }) {
36
+ * const { input } = useGameContext();
37
+ * const ref = useRef<Sprite>(null);
38
+ * useTick(({ deltaMS }) => {
39
+ * if (input.isPressed('move_right')) ref.current!.x += speed * (deltaMS / 1000);
40
+ * });
41
+ * return <pixiSprite ref={ref} texture={Texture.WHITE} />;
42
+ * }
43
+ * ```
44
+ *
45
+ * Throws when called outside a canvas root mounted by
46
+ * `createPixiReactAdapter` — absent context is a wiring bug, and a silent
47
+ * `null` would surface as a mystery `undefined` deep in a tick callback.
48
+ */
49
+ export function useGameContext(): GameContext {
50
+ const bridge = useContext(EngineBridge);
51
+ if (!bridge?.ctx) {
52
+ throw new Error(
53
+ 'useGameContext(): no engine root context. This hook only works inside a canvas ' +
54
+ 'root mounted by createPixiReactAdapter (@engine/canvas-react) — check that the ' +
55
+ 'component is rendered inside your world entry, not in a standalone <Application>.',
56
+ );
57
+ }
58
+ return bridge.ctx;
59
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * canvas-react — the `@pixi/react` lane for vgai's canvas surface
3
+ * (`@engine/canvas-react`).
4
+ *
5
+ * The canvas surface is source-as-truth: a canvas root's document IS its
6
+ * TSX world file, exactly as a three root's document is its R3F source. This
7
+ * module is the peer of `world3d-react/` — same seam, same shape, Pixi instead
8
+ * of three — and the peer of `pixi/`, which holds what the host needs to mount
9
+ * and inspect a PixiJS game it did NOT write.
10
+ *
11
+ * The engine CORE never imports this (enforced by
12
+ * `packages/engine/test/react-core-import-ban.test.ts` — this directory is an
13
+ * allowed react-importing entry alongside `react/` and `world3d-react/`, and
14
+ * core files may not import it), so a three-only bundle never pays for react
15
+ * or the Pixi reconciler.
16
+ *
17
+ * Surface:
18
+ * - `createPixiReactAdapter({ id, content })` — mount a `@pixi/react` tree as
19
+ * a first-party `surface: "canvas"` root under the host's gated loop.
20
+ * - `pixiReactRootFactory` / `resolveCanvasEntryAdapter` — what a canvas entry
21
+ * module MEANS: a default-exported component (or an explicit `adapter`).
22
+ * - `PixiPrimitive` / `adoptNow` — the canvas surface's `<primitive object={…}>`:
23
+ * render a container the game already owns, and place a spawn in the
24
+ * display tree ahead of the commit that renders it.
25
+ * - `useGameContext()` — this root's `GameContext` (`input`, `debug`,
26
+ * `random`, `clock`, `game`, `roots`) from inside any component in the tree.
27
+ * Behavior in a canvas world is components + hooks, never a class under a
28
+ * string key.
29
+ * - `EngineBridge` / `EngineBridgeValue` — the context contract, exported for
30
+ * adopters building their own bridge.
31
+ *
32
+ * The Pixi runtime itself is NOT re-exported here. A component reaches the live
33
+ * `Application` (and therefore `stage`, `renderer`, `ticker`) with
34
+ * `@pixi/react`'s own `useApplication()`, ticks with its `useTick()`, and loads
35
+ * assets with Pixi's own `Assets` — the library's API, not a second one.
36
+ */
37
+
38
+ export { EngineBridge, type EngineBridgeValue, useGameContext } from './engine-bridge';
39
+ export { adoptNow, PixiPrimitive, type PixiPrimitiveProps } from './pixi-primitive';
40
+ export {
41
+ type CreatePixiReactAdapterOptions,
42
+ createPixiReactAdapter,
43
+ } from './pixi-react-adapter';
44
+ export { pixiReactRootFactory, resolveCanvasEntryAdapter } from './pixi-react-root-factory';
45
+ export {
46
+ type CanvasRootContextOptions,
47
+ type CanvasRootRuntime,
48
+ createCanvasRootContext,
49
+ DEFAULT_INPUT_MAP_PATH,
50
+ } from './world-context';