@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
@@ -1,292 +0,0 @@
1
- import { z } from 'zod';
2
- import { type SceneEntity, SceneEntitySchema } from './entity';
3
- import { SceneEnvironmentSchema } from './environment';
4
-
5
- export const SceneFileSchema = z
6
- .object({
7
- version: z.number().describe('Scene file format version'),
8
- name: z.string().describe('Scene display name'),
9
- environment: SceneEnvironmentSchema.optional().describe('Scene-level environment settings'),
10
- entities: z.array(SceneEntitySchema).describe('Top-level entities in the scene'),
11
- })
12
- .strict()
13
- .describe('Scene file (.vscn.json)');
14
-
15
- export type SceneFile = z.infer<typeof SceneFileSchema>;
16
-
17
- export const PrefabFileSchema = z
18
- .object({
19
- version: z.number().describe('Prefab file format version'),
20
- name: z.string().describe('Prefab display name'),
21
- root: SceneEntitySchema.describe('Root entity of the prefab'),
22
- })
23
- .describe('Prefab file (.prefab.json)');
24
-
25
- export type PrefabFile = z.infer<typeof PrefabFileSchema>;
26
-
27
- /**
28
- * Shallow-merge a section, but deep-merge specific named nested sub-objects
29
- * within it (e.g. `physics.collider`, `material.clearcoat`). A partial
30
- * instance override of a nested sub-object would otherwise wholesale-replace
31
- * it and drop the prefab's other fields in that sub-object (e.g. an instance
32
- * overriding one collider field would silently lose the prefab's `type`).
33
- */
34
- function mergeSectionDeepNested<T extends Record<string, unknown>>(
35
- prefabSection: T | undefined,
36
- instanceSection: T | undefined,
37
- nestedKeys: readonly (keyof T)[],
38
- ): T | undefined {
39
- if (instanceSection === undefined) return prefabSection;
40
- if (prefabSection === undefined) return instanceSection;
41
- const merged: Record<string, unknown> = { ...prefabSection, ...instanceSection };
42
- for (const key of nestedKeys) {
43
- const p = prefabSection[key];
44
- const i = instanceSection[key];
45
- if (
46
- i !== undefined &&
47
- p !== undefined &&
48
- typeof i === 'object' &&
49
- typeof p === 'object' &&
50
- !Array.isArray(i) &&
51
- !Array.isArray(p)
52
- ) {
53
- merged[key as string] = { ...(p as object), ...(i as object) };
54
- }
55
- }
56
- return merged as T;
57
- }
58
-
59
- /**
60
- * Sections whose nested sub-object needs deep merging (T1.10) — see the
61
- * `mergePrefabInstance` docstring for the reasoning behind each.
62
- */
63
- const DEEP_NESTED_SECTIONS = [
64
- { key: 'physics', nestedKeys: ['collider'] },
65
- { key: 'material', nestedKeys: ['clearcoat', 'transmission', 'sheen', 'iridescence'] },
66
- { key: 'shadow', nestedKeys: ['camera'] },
67
- ] as const;
68
-
69
- /**
70
- * Re-validate a merged entity through `SceneEntitySchema` (T1.10). This is
71
- * the gate that catches a prefab's inline `material` combined with an
72
- * instance's `materialRef` (or vice versa) — each half is individually valid,
73
- * so only the MERGED entity can trip the material/materialRef
74
- * mutual-exclusion refine. Throws naming the offending prefab path + instance
75
- * so a bad scene file points straight at the offender.
76
- */
77
- function validateMergedEntity(merged: SceneEntity, instance: SceneEntity): SceneEntity {
78
- const validated = SceneEntitySchema.safeParse(merged);
79
- if (validated.success) return validated.data;
80
- const issues = validated.error.issues.map((issue) => issue.message).join('; ');
81
- const prefabPath = instance.prefab ?? '<unknown prefab>';
82
- throw new Error(
83
- `Prefab merge produced an invalid entity — prefab "${prefabPath}", instance "${instance.name}": ${issues}`,
84
- );
85
- }
86
-
87
- /**
88
- * Merge a prefab root definition with an instance's overrides.
89
- *
90
- * Per-field merge rules (exactly as implemented below):
91
- *
92
- * - transform: instance wins entirely if set, else the prefab root's transform
93
- * (placement is per-instance; transforms are not blended field-by-field).
94
- * - name: instance always wins.
95
- * - id: instance wins if set (each instance has its own id); else prefab's.
96
- * - prefab: stripped from the result so spawnEntity recursion terminates.
97
- *
98
- * - Sections — `mesh`, `light`, `camera`, `animation`, `audio`, `particles`,
99
- * `spline`, `render`: SHALLOW merge per section. If the instance sets the
100
- * section, its top-level fields override the prefab root's same-named
101
- * fields ({ ...prefabSection, ...instanceSection }); fields the instance
102
- * omits fall through to the prefab. Nested objects are replaced, not
103
- * deep-merged. If only the prefab has the section it is kept as-is.
104
- * (CB4: `spline` is included here; CB5: see metadata below; T1.10: `render`
105
- * is now included — an instance's per-entity render overrides are honored
106
- * and merged the same way as `mesh`/`material`.)
107
- *
108
- * NOTE (T1.10 audit): `animation.parameters`/`animation.clipAliases` are keyed dict maps nested
109
- * one level down, and `particles` nests discriminated-union shapes/behaviors
110
- * arrays. These have the same "shallow replace" defect as `material`/
111
- * `physics` did, but no single well-defined field-merge semantics (dict keys
112
- * authors may legitimately want fully replaced; discriminated unions can't
113
- * be safely field-merged across different variants). Left as wholesale
114
- * per-section replace; flagged here rather than silently "fixed" with a
115
- * guess. Revisit if this causes real authoring pain.
116
- *
117
- * - `material`: shallow merge at the top level, PLUS deep-merge of the four
118
- * nested physical-material feature groups (`clearcoat`, `transmission`,
119
- * `sheen`, `iridescence`) so an instance overriding e.g. `clearcoat.clearcoat`
120
- * preserves the prefab's `clearcoat.clearcoatRoughness` (T1.10 — previously
121
- * these nested groups were wholesale-replaced like any other shallow field).
122
- *
123
- * - `physics`: shallow merge at the top level (`bodyType`, `mass`), PLUS
124
- * deep-merge of the nested `collider` object so a partial instance collider
125
- * override (e.g. just `isSensor`) preserves the prefab's `type` and other
126
- * collider fields instead of dropping them (T1.10 AC).
127
- *
128
- * - `shadow`: shallow merge at the top level, PLUS deep-merge of the nested
129
- * `camera` (shadow-camera frustum bounds) object, for the same reason as
130
- * `physics.collider` (T1.10 audit — same defect, same fix).
131
- *
132
- * - components: DEEP merge, keyed by component name. The union of component
133
- * names is taken; for each, instance fields override prefab fields
134
- * ({ ...prefabComp, ...instanceComp }).
135
- *
136
- * - joints: REPLACE — if the instance sets joints, its array wins wholesale
137
- * (arrays are not field-mergeable); else the prefab's joints are kept.
138
- * - children: APPEND — prefab children first, then instance-only children.
139
- *
140
- * - Instance-level metadata `visible`, `materialRef`, `tags`, `locked`,
141
- * `navigation`, `pivot`: instance wins if explicitly set, otherwise the
142
- * prefab root's value is inherited. (CB5: spline + these metadata fields are
143
- * now instance-wins; previously dropped.)
144
- *
145
- * - Re-validation (T1.10): the merged entity is re-parsed through
146
- * `SceneEntitySchema` before being returned. This is what makes the
147
- * material/materialRef mutual-exclusion refine fire on the MERGED result —
148
- * previously a prefab's inline `material` plus an instance's `materialRef`
149
- * (or vice versa) silently combined into an entity carrying both, bypassing
150
- * the refine entirely (it only ever saw the pre-merge, individually-valid
151
- * halves). A merge that produces an invalid entity now throws, naming the
152
- * offending prefab path + instance name.
153
- */
154
- export function mergePrefabInstance(prefabRoot: SceneEntity, instance: SceneEntity): SceneEntity {
155
- const merged: SceneEntity = {
156
- ...prefabRoot,
157
- name: instance.name,
158
- };
159
-
160
- // Handle optional fields explicitly to satisfy exactOptionalPropertyTypes
161
- const resolvedTransform = instance.transform ?? prefabRoot.transform;
162
- if (resolvedTransform) merged.transform = resolvedTransform;
163
- if (instance.id) merged.id = instance.id;
164
-
165
- // Strip prefab field so spawnEntity won't recurse infinitely
166
- delete merged.prefab;
167
-
168
- applyInstanceMetadata(merged, prefabRoot, instance);
169
- applyDeepNestedSections(merged, prefabRoot, instance);
170
- applyShallowSections(merged, prefabRoot, instance);
171
-
172
- const components = mergeComponents(prefabRoot, instance);
173
- if (components !== undefined) merged.components = components;
174
-
175
- // Joints: instance wins (array, not shallow-mergeable)
176
- if (instance.joints !== undefined) {
177
- merged.joints = instance.joints;
178
- }
179
-
180
- const children = mergeChildren(prefabRoot, instance);
181
- if (children !== undefined) merged.children = children;
182
-
183
- // Re-validate the merged result (T1.10) — see `validateMergedEntity` above.
184
- return validateMergedEntity(merged, instance);
185
- }
186
-
187
- /**
188
- * Visibility + instance-level metadata (`materialRef`, `tags`, `locked`,
189
- * `navigation`, `pivot`): instance wins if explicitly set, otherwise the
190
- * prefab root's value (already present on `merged` via the initial spread in
191
- * `mergePrefabInstance`) is kept.
192
- */
193
- function applyInstanceMetadata(
194
- merged: SceneEntity,
195
- prefabRoot: SceneEntity,
196
- instance: SceneEntity,
197
- ): void {
198
- if (instance.visible !== undefined) merged.visible = instance.visible;
199
- if (instance.materialRef !== undefined) merged.materialRef = instance.materialRef;
200
- if (instance.tags !== undefined) merged.tags = instance.tags;
201
- if (instance.locked !== undefined) merged.locked = instance.locked;
202
- if (instance.navigation !== undefined) merged.navigation = instance.navigation;
203
-
204
- if (instance.pivot !== undefined) {
205
- merged.pivot = instance.pivot;
206
- } else if (prefabRoot.pivot !== undefined) {
207
- merged.pivot = prefabRoot.pivot;
208
- }
209
- }
210
-
211
- /**
212
- * Sections whose nested sub-object needs deep (not shallow) merging — see the
213
- * `mergePrefabInstance` docstring for the reasoning behind each.
214
- */
215
- function applyDeepNestedSections(
216
- merged: SceneEntity,
217
- prefabRoot: SceneEntity,
218
- instance: SceneEntity,
219
- ): void {
220
- for (const { key, nestedKeys } of DEEP_NESTED_SECTIONS) {
221
- const result = mergeSectionDeepNested(
222
- // biome-ignore lint/suspicious/noExplicitAny: TS can't correlate indexed access across a union of keys
223
- (prefabRoot as any)[key],
224
- // biome-ignore lint/suspicious/noExplicitAny: TS can't correlate indexed access across a union of keys
225
- (instance as any)[key],
226
- nestedKeys,
227
- );
228
- // biome-ignore lint/suspicious/noExplicitAny: TS can't correlate indexed access across a union of keys
229
- if (result !== undefined) (merged as any)[key] = result;
230
- }
231
- }
232
-
233
- /** Sections shallow-merged whole (material/physics/shadow handled separately above). */
234
- const SHALLOW_SECTIONS = [
235
- 'mesh',
236
- 'light',
237
- 'camera',
238
- 'animation',
239
- 'audio',
240
- 'particles',
241
- 'spline',
242
- 'render',
243
- ] as const;
244
-
245
- /**
246
- * Shallow merge per remaining section — instance fields override within the
247
- * section ({ ...prefabSection, ...instanceSection }); if only the prefab has
248
- * the section it is kept as-is.
249
- */
250
- function applyShallowSections(
251
- merged: SceneEntity,
252
- prefabRoot: SceneEntity,
253
- instance: SceneEntity,
254
- ): void {
255
- for (const key of SHALLOW_SECTIONS) {
256
- if (instance[key] === undefined) continue;
257
- // biome-ignore lint/suspicious/noExplicitAny: TS can't correlate indexed access across a union of keys
258
- (merged as any)[key] =
259
- prefabRoot[key] !== undefined
260
- ? // biome-ignore lint/suspicious/noExplicitAny: TS can't correlate indexed access across a union of keys
261
- { ...(prefabRoot as any)[key], ...(instance as any)[key] }
262
- : instance[key];
263
- }
264
- }
265
-
266
- /** Deep merge `components`, keyed by component name (instance fields win per-component). */
267
- function mergeComponents(
268
- prefabRoot: SceneEntity,
269
- instance: SceneEntity,
270
- ):
271
- | Record<string, Record<string, number | string | boolean | number[] | string[] | boolean[]>>
272
- | undefined {
273
- if (!instance.components && !prefabRoot.components) return undefined;
274
- const base = prefabRoot.components ?? {};
275
- const over = instance.components ?? {};
276
- const result: Record<
277
- string,
278
- Record<string, number | string | boolean | number[] | string[] | boolean[]>
279
- > = {};
280
- for (const comp of new Set([...Object.keys(base), ...Object.keys(over)])) {
281
- result[comp] = { ...base[comp], ...over[comp] };
282
- }
283
- return result;
284
- }
285
-
286
- /** Children: prefab children first, then instance-only children appended. */
287
- function mergeChildren(prefabRoot: SceneEntity, instance: SceneEntity): SceneEntity[] | undefined {
288
- const prefabChildren = prefabRoot.children ?? [];
289
- const instanceChildren = instance.children ?? [];
290
- if (prefabChildren.length === 0 && instanceChildren.length === 0) return undefined;
291
- return [...prefabChildren, ...instanceChildren];
292
- }
@@ -1,24 +0,0 @@
1
- import { z } from 'zod';
2
-
3
- export const SceneShadowSchema = z
4
- .object({
5
- enabled: z.boolean().describe('Whether this light casts shadows'),
6
- mapSize: z.number().optional().describe('Shadow map resolution in pixels (e.g. 1024, 2048)'),
7
- camera: z
8
- .object({
9
- left: z.number().optional().describe('Left bound of shadow camera frustum'),
10
- right: z.number().optional().describe('Right bound of shadow camera frustum'),
11
- top: z.number().optional().describe('Top bound of shadow camera frustum'),
12
- bottom: z.number().optional().describe('Bottom bound of shadow camera frustum'),
13
- })
14
- .optional()
15
- .describe('Shadow camera frustum bounds (directional lights)'),
16
- radius: z.number().optional().describe('Shadow map blur radius for soft shadows'),
17
- bias: z.number().optional().describe('Shadow bias to reduce shadow acne artifacts'),
18
- })
19
- .describe('Shadow casting configuration');
20
-
21
- /** Partial schema for prefab instance overrides (enabled not required). */
22
- export const SceneShadowOverrideSchema = SceneShadowSchema.partial();
23
-
24
- export type SceneShadow = z.infer<typeof SceneShadowSchema>;
@@ -1,21 +0,0 @@
1
- import { z } from 'zod';
2
- import { Vec3Schema } from './tuples';
3
-
4
- export const SceneSplineSchema = z.object({
5
- curveType: z
6
- .enum(['centripetal', 'chordal', 'catmullrom'])
7
- .describe('CatmullRomCurve3 parameterization type. Matches Three.js curveType parameter'),
8
- points: z.array(Vec3Schema).min(2).describe('Control point positions in local space'),
9
- closed: z.boolean().optional().describe('Whether the spline forms a closed loop'),
10
- tension: z
11
- .number()
12
- .optional()
13
- .describe(
14
- 'Curve tension (default 0.5). Only used when curveType is catmullrom; ignored for centripetal and chordal',
15
- ),
16
- resolution: z.number().optional().describe('Number of line segments for rendering the curve'),
17
- });
18
-
19
- export type SceneSpline = z.infer<typeof SceneSplineSchema>;
20
-
21
- export const SceneSplineOverrideSchema = SceneSplineSchema.partial();
@@ -1,44 +0,0 @@
1
- import type { Entity2D, Entity2DAsset, Scene2DFile } from './schema/entity2d';
2
-
3
- function visit(entities: Entity2D[], apply: (entity: Entity2D) => void): void {
4
- for (const entity of entities) {
5
- apply(entity);
6
- if (entity.children) visit(entity.children, apply);
7
- }
8
- }
9
-
10
- export function collectEntity2DAssetPaths(entities: Entity2D[]): Set<string> {
11
- const paths = new Set<string>();
12
- visit(entities, (entity) => {
13
- if (entity.sprite?.texture) paths.add(entity.sprite.texture);
14
- if (entity.tilemap?.texture) paths.add(entity.tilemap.texture);
15
- });
16
- return paths;
17
- }
18
-
19
- function rename(entities: Entity2D[], oldPath: string, newPath: string): void {
20
- visit(entities, (entity) => {
21
- if (entity.sprite?.texture === oldPath) entity.sprite.texture = newPath;
22
- if (entity.tilemap?.texture === oldPath) entity.tilemap.texture = newPath;
23
- });
24
- }
25
-
26
- export function applyScene2DAssetRename(
27
- document: Scene2DFile,
28
- oldPath: string,
29
- newPath: string,
30
- ): Scene2DFile {
31
- const cloned = structuredClone(document);
32
- rename(cloned.entities, oldPath, newPath);
33
- return cloned;
34
- }
35
-
36
- export function applyEntity2DAssetRename(
37
- asset: Entity2DAsset,
38
- oldPath: string,
39
- newPath: string,
40
- ): Entity2DAsset {
41
- const cloned = structuredClone(asset);
42
- rename([cloned.root], oldPath, newPath);
43
- return cloned;
44
- }
@@ -1,52 +0,0 @@
1
- import type { Container } from 'pixi.js';
2
- import type { Entity2D, Scene2DFile } from './schema/entity2d';
3
-
4
- /**
5
- * Capture an ingested/live PixiJS subtree into authored Scene2D entities — the
6
- * world2d analog of capture-to-vscn (`ingest-capture.ts`). Recognizable sprites/
7
- * text/containers map to first-party `sprite`/`text`/`transform2d` sections;
8
- * turns inspection into portable authored content.
9
- *
10
- * Honest ceiling (mirrors the 3D one): a Scene2D `sprite` expresses tint + size +
11
- * anchor, not an arbitrary texture/GraphicsContext, so a captured display keeps its
12
- * transform + tint + size; bespoke Graphics geometry is captured as a plain tinted
13
- * sprite placeholder.
14
- */
15
- function numToHex(n: number | undefined): string {
16
- return `#${((n ?? 0xffffff) & 0xffffff).toString(16).padStart(6, '0')}`;
17
- }
18
-
19
- function captureNode(o: Container): Entity2D {
20
- const ctor = o.constructor?.name?.toLowerCase() ?? '';
21
- const entity: Entity2D = {
22
- name: o.label ?? ctor ?? 'entity',
23
- surface: 'world2d',
24
- transform2d: {
25
- position: [o.position.x, o.position.y],
26
- rotation: o.rotation,
27
- scale: [o.scale.x, o.scale.y],
28
- },
29
- };
30
- if (ctor.includes('text')) {
31
- entity.text = { text: (o as { text?: string }).text ?? '' };
32
- } else if (ctor.includes('sprite')) {
33
- entity.sprite = {
34
- tint: numToHex((o as { tint?: number }).tint),
35
- width: Math.round(o.width),
36
- height: Math.round(o.height),
37
- };
38
- }
39
- const kids = o.children?.filter((c) => (c as Container).label !== undefined) ?? [];
40
- if (kids.length) entity.children = kids.map((c) => captureNode(c as Container));
41
- return entity;
42
- }
43
-
44
- /** Capture selected subtree roots (by display object) into a Scene2D file. */
45
- export function captureToScene2D(roots: Container[], name = 'captured-world2d'): Scene2DFile {
46
- return {
47
- version: 1,
48
- name,
49
- surface: 'world2d',
50
- entities: roots.map(captureNode),
51
- };
52
- }
@@ -1,99 +0,0 @@
1
- import type RAPIER from '@dimforge/rapier2d-compat';
2
- import type { NodeOf } from '../ecs/game-component';
3
- import type { Physics2DRegistry } from './physics2d-registry';
4
- import type { World2DContext } from './types';
5
-
6
- export type Collision2DHandler = (handleA: number, handleB: number, started: boolean) => void;
7
-
8
- /**
9
- * Drains the Rapier 2D event queue each postPhysics and dispatches collision
10
- * events — the world2d analog of `createCollisionSystem`. Events are buffered then
11
- * handlers run after the drain returns (same re-entrancy guard as the 3D system:
12
- * a handler must not call back into Rapier while the queue is borrowed).
13
- */
14
- export function createCollision2DSystem(rapierWorld: RAPIER.World, eventQueue: RAPIER.EventQueue) {
15
- const handlers: Collision2DHandler[] = [];
16
- return {
17
- onCollision(handler: Collision2DHandler) {
18
- handlers.push(handler);
19
- },
20
- offCollision(handler: Collision2DHandler) {
21
- const idx = handlers.indexOf(handler);
22
- if (idx !== -1) handlers.splice(idx, 1);
23
- },
24
- drain() {
25
- const events: Array<[number, number, boolean]> = [];
26
- eventQueue.drainCollisionEvents((h1, h2, started) => {
27
- events.push([h1, h2, started]);
28
- });
29
- for (const [h1, h2, started] of events) {
30
- for (let i = 0; i < handlers.length; i++) handlers[i]!(h1, h2, started);
31
- }
32
- },
33
- getBodyHandleFromCollider(colliderHandle: number): number | undefined {
34
- const collider = rapierWorld.getCollider(colliderHandle);
35
- return collider?.parent()?.handle;
36
- },
37
- };
38
- }
39
-
40
- export type Collision2DSystem = ReturnType<typeof createCollision2DSystem>;
41
-
42
- /**
43
- * The minimal component-manager capability `createTriggerDispatch2D` needs —
44
- * `getComponents` returning instances with optional trigger callbacks, typed
45
- * honestly against `NodeOf<'pixijs'>` (T7.2, D8 — this dispatch is always
46
- * pixi-world-scoped) rather than the full manager type. Deliberately a
47
- * structural slice: the unified `ComponentManager` (`ecs/component-manager.ts`,
48
- * what production wires up since T7.3 slice 1, and the only manager left —
49
- * the world2d silo's manager is deleted, T7.3 slice 2) satisfies it — this
50
- * dispatcher has no dependency on which `AdapterSurface` a caller's manager was
51
- * constructed for.
52
- */
53
- export interface TriggerDispatchComponents {
54
- getComponents(node: NodeOf<'pixijs'>): ReadonlyArray<{
55
- onTriggerEnter?(other: NodeOf<'pixijs'>, ctx: World2DContext): void;
56
- onTriggerExit?(other: NodeOf<'pixijs'>, ctx: World2DContext): void;
57
- }>;
58
- }
59
-
60
- /**
61
- * Turn Rapier 2D sensor overlaps into `onTriggerEnter`/`onTriggerExit` callbacks on
62
- * the GameComponents of the two involved display objects — the 2D analog of
63
- * `createTriggerDispatch`. Only sensor-involved collisions dispatch triggers.
64
- *
65
- * The callback receives the native `World2DContext`; Pixi components do not
66
- * widen or cast their lifecycle context to reach their own first-party APIs.
67
- */
68
- export function createTriggerDispatch2D(
69
- physics: Physics2DRegistry,
70
- components: TriggerDispatchComponents,
71
- rapierWorld: RAPIER.World,
72
- ctx: World2DContext,
73
- ): Collision2DHandler {
74
- return (handleA, handleB, started) => {
75
- const colA = rapierWorld.getCollider(handleA);
76
- const colB = rapierWorld.getCollider(handleB);
77
- if (!colA?.isSensor() && !colB?.isSensor()) return;
78
-
79
- const objA = physics.getByColliderHandle(handleA);
80
- const objB = physics.getByColliderHandle(handleB);
81
- if (!objA || !objB) return;
82
-
83
- notify(components, objA, objB, started, ctx);
84
- notify(components, objB, objA, started, ctx);
85
- };
86
- }
87
-
88
- function notify(
89
- components: TriggerDispatchComponents,
90
- self: NodeOf<'pixijs'>,
91
- other: NodeOf<'pixijs'>,
92
- started: boolean,
93
- ctx: World2DContext,
94
- ): void {
95
- for (const inst of components.getComponents(self)) {
96
- if (started) inst.onTriggerEnter?.(other, ctx);
97
- else inst.onTriggerExit?.(other, ctx);
98
- }
99
- }
@@ -1,22 +0,0 @@
1
- import { SceneParseError } from '../scene/parse';
2
- import { type Entity2D, type Entity2DAsset, Entity2DAssetSchema } from './schema/entity2d';
3
-
4
- /** Parse fetched reusable 2D entity bytes through the same named-error
5
- * convention as every other authored project asset. */
6
- export function parseEntity2DAsset(value: unknown, sourceName?: string): Entity2DAsset {
7
- const result = Entity2DAssetSchema.safeParse(value);
8
- if (!result.success) throw new SceneParseError(result.error.issues, sourceName);
9
- return result.data;
10
- }
11
-
12
- /** Copy a reusable subtree into a scene. Every copy receives fresh authored
13
- * identities so repeated drops cannot alias hierarchy/history targets. */
14
- export function instantiateEntity2DAsset(asset: Entity2DAsset): Entity2D {
15
- const root = structuredClone(asset.root);
16
- const assignFreshIds = (entity: Entity2D): void => {
17
- entity.id = crypto.randomUUID();
18
- for (const child of entity.children ?? []) assignFreshIds(child);
19
- };
20
- assignFreshIds(root);
21
- return root;
22
- }
@@ -1,91 +0,0 @@
1
- /**
2
- * world2d — the PixiJS 2D render surface for vgai.
3
- *
4
- * A peer to the Three.js world3d surface and the React UI surface (ARCHITECTURE.md
5
- * §Render Surfaces). It LITERALLY reuses the engine's loop primitives (`createGameLoop`,
6
- * `createSystemRunner`, `PHASE_ORDER`) and faithfully MIRRORS the entity / authored-data
7
- * model as a 2D twin (`Scene2D`), instantiating entities as PixiJS display objects
8
- * instead of Object3Ds, with 2D physics/collision via native Rapier 2D — built on
9
- * native libraries with thin adapters. Gameplay behavior attaches via the SAME
10
- * `GameComponent<'pixijs'>` (`ecs/game-component.ts`) and unified `ComponentManager`
11
- * (`ecs/component-manager.ts`) every threejs world uses (T7.3).
12
- *
13
- * See docs/PIXI-WORLD2D-MAXIMAL-SPEC.json (FROZEN).
14
- */
15
-
16
- export {
17
- applyEntity2DAssetRename,
18
- applyScene2DAssetRename,
19
- collectEntity2DAssetPaths,
20
- } from './asset-paths2d';
21
- export {
22
- AuthoringAdapter2D,
23
- type EditorNode2D,
24
- type Overlay2D,
25
- type Override2D,
26
- type Property2D,
27
- type Transform2DValue,
28
- } from './authoring-2d';
29
- export { captureToScene2D } from './capture-to-scene2d';
30
- export {
31
- type Collision2DSystem,
32
- createCollision2DSystem,
33
- createTriggerDispatch2D,
34
- } from './collision-2d';
35
- export { instantiateEntity2DAsset, parseEntity2DAsset } from './entity2d-asset';
36
- export {
37
- type EmbedMount2D,
38
- type IframeReachableMount2D,
39
- mountIngestGame2DEmbed,
40
- mountIngestGame2DIframeReachable,
41
- } from './ingest-iframe-2d';
42
- export { type IngestGame2D, type IngestMount2D, mountIngestGame2D } from './ingest2d';
43
- export {
44
- createPhysics2DRegistry,
45
- type Physics2DRefs,
46
- type Physics2DRegistry,
47
- } from './physics2d-registry';
48
- export {
49
- assertMovingPhysicsParent2D,
50
- computePhysicsPose2D,
51
- type PhysicsPose2D,
52
- relativeTransform2D,
53
- type ScaledCollider2D,
54
- scaleCollider2D,
55
- } from './physics2d-transform';
56
- export {
57
- DEFAULT_WORLD2D_GRAVITY_METERS_PER_SECOND_SQUARED,
58
- DEFAULT_WORLD2D_PIXELS_PER_METER,
59
- defaultWorld2DGravityY,
60
- } from './physics2d-units';
61
- export {
62
- createWorld2DRuntime,
63
- type MountedGame2D,
64
- type PixiSceneConfig,
65
- PixiSceneGameAdapter,
66
- type World2DHost,
67
- type World2DSession,
68
- type World2DSetupFn,
69
- } from './pixi-game-adapter';
70
- export { createPixiSurface, type PixiSurface, type PixiSurfaceOptions } from './pixi-surface';
71
- export {
72
- type CapturedRuntime2D,
73
- installSceneCapture2D,
74
- type SceneCapture2DHandle,
75
- } from './scene-capture-2d';
76
- export { materializeScene2DEntityIds, resolveScene2DEntityIds } from './scene2d-identity';
77
- export {
78
- applySceneEntityTransform2D,
79
- captureSceneSpriteBaseScale2D,
80
- getSceneTilemapTextures2D,
81
- loadScene2DFromData,
82
- type Scene2DInstance,
83
- } from './scene2d-loader';
84
- export * from './schema/entity2d';
85
- export * from './schema/physics2d';
86
- export * from './schema/sprite';
87
- export * from './schema/tilemap';
88
- export * from './schema/tuples2d';
89
- export { createPhysicsAdapter2D, type PhysicsAdapter2D } from './system-adapters-2d';
90
- export { createTransformWriter2D } from './transform-writer-2d';
91
- export type { Component2DRegistry, World2DContext } from './types';