@vgai/engine 0.2.0 → 0.4.0-canary.20260715.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/README.md +3 -1
  2. package/package.json +24 -4
  3. package/schemas/engine-api.json +124 -0
  4. package/schemas/engine-api.md +53 -0
  5. package/schemas/engine-capabilities.json +124 -0
  6. package/schemas/inputmap.schema.json +314 -0
  7. package/schemas/mat.schema.json +286 -0
  8. package/schemas/prefab.schema.json +10148 -0
  9. package/schemas/scn2d.schema.json +475 -0
  10. package/schemas/vgai-game.schema.json +383 -0
  11. package/schemas/vscn.schema.json +11007 -0
  12. package/src/adapter/{world-kind.ts → adapter-surface.ts} +6 -6
  13. package/src/adapter/authoring.ts +77 -0
  14. package/src/adapter/first-party-systems.ts +23 -34
  15. package/src/adapter/game-adapter.ts +8 -8
  16. package/src/adapter/host-context.ts +2 -4
  17. package/src/adapter/index.ts +4 -4
  18. package/src/adapter/system-adapter.ts +88 -22
  19. package/src/adapter/vgai-scene-game-adapter.ts +244 -194
  20. package/src/animation/anim-graph-types.ts +12 -43
  21. package/src/animation/animation-clock.ts +479 -0
  22. package/src/animation/camera-ownership.ts +467 -0
  23. package/src/animation/cinematic-cues.ts +451 -0
  24. package/src/animation/clip-map.ts +41 -0
  25. package/src/animation/gsap-registration.ts +184 -0
  26. package/src/animation/theatre-clock-binding.ts +111 -0
  27. package/src/animation/theatre-director.ts +347 -0
  28. package/src/animation/theatre-object-binding.ts +661 -0
  29. package/src/animation/xstate-animation-binding.ts +436 -0
  30. package/src/animation/xstate-animation-meta.ts +319 -0
  31. package/src/audio/index.ts +39 -7
  32. package/src/audio/tone-clock-binding.ts +98 -0
  33. package/src/audio/tone-context.ts +129 -0
  34. package/src/audio/tone-offline-render.ts +167 -0
  35. package/src/audio/wav-encode.ts +119 -0
  36. package/src/character/cloth-sim.ts +533 -0
  37. package/src/character/spring-chain.ts +307 -0
  38. package/src/core/game-loop.ts +57 -2
  39. package/src/core/seeded-random.ts +161 -0
  40. package/src/core/system-runner.ts +20 -3
  41. package/src/core/types.ts +50 -0
  42. package/src/data/data-asset.ts +167 -0
  43. package/src/data/data-check-core.ts +242 -0
  44. package/src/data/data-ref.ts +145 -0
  45. package/src/data/vite-plugin-data.ts +290 -0
  46. package/src/dev/performance-profiler.ts +213 -0
  47. package/src/dev/webgl-gpu-timer.ts +53 -0
  48. package/src/ecs/component-manager.ts +45 -12
  49. package/src/ecs/game-component.ts +95 -11
  50. package/src/humanoid/bake.operation.ts +326 -0
  51. package/src/humanoid/body.ts +663 -0
  52. package/src/humanoid/clips.ts +149 -0
  53. package/src/humanoid/compose.ts +209 -0
  54. package/src/humanoid/generate.ts +189 -0
  55. package/src/humanoid/index.ts +36 -0
  56. package/src/humanoid/schema.ts +108 -0
  57. package/src/humanoid/skeleton.ts +345 -0
  58. package/src/index.ts +48 -0
  59. package/src/input/input-manager.ts +1886 -33
  60. package/src/input/input-types.ts +158 -3
  61. package/src/input/prompt-labels.ts +122 -0
  62. package/src/input/rebind-controller.ts +105 -0
  63. package/src/input/schema.ts +206 -52
  64. package/src/manifest/index.ts +5 -5
  65. package/src/manifest/load.ts +125 -72
  66. package/src/manifest/schema.ts +362 -255
  67. package/src/react/game-state.tsx +135 -32
  68. package/src/react/root-adapter.tsx +49 -0
  69. package/src/react/unmanaged-root-detector.ts +66 -0
  70. package/src/react/use-data.ts +124 -0
  71. package/src/react/use-selection.tsx +135 -0
  72. package/src/runtime/create-runtime.ts +112 -273
  73. package/src/runtime/debug-bridge.ts +483 -0
  74. package/src/runtime/debug-registry.ts +856 -0
  75. package/src/runtime/game.ts +342 -93
  76. package/src/runtime/gameplay-rng-trap.ts +134 -0
  77. package/src/runtime/input-router.ts +7 -7
  78. package/src/runtime/mount-game.ts +40 -38
  79. package/src/runtime/mount-manifest.ts +169 -37
  80. package/src/runtime/render-audio-control.ts +168 -0
  81. package/src/runtime/render-control.ts +522 -0
  82. package/src/runtime/render-seed.ts +79 -0
  83. package/src/runtime/state-bridge.ts +24 -10
  84. package/src/runtime/types.ts +110 -33
  85. package/src/scene/asset-loaders.ts +10 -36
  86. package/src/scene/asset-paths.ts +0 -2
  87. package/src/scene/asset-ref-check.ts +248 -0
  88. package/src/scene/asset-registry.ts +22 -0
  89. package/src/scene/component-registry.ts +14 -3
  90. package/src/scene/defaults.ts +1 -0
  91. package/src/scene/light-camera-factory.ts +11 -3
  92. package/src/scene/parse.ts +133 -0
  93. package/src/scene/scene-apply.ts +55 -4
  94. package/src/scene/scene-loader.ts +91 -123
  95. package/src/scene/scene-types.ts +0 -1
  96. package/src/scene/schema/animation.ts +30 -79
  97. package/src/scene/schema/entity.ts +20 -0
  98. package/src/scene/schema/index.ts +2 -46
  99. package/src/scene/schema/light.ts +16 -1
  100. package/src/scene/schema/material.ts +96 -91
  101. package/src/scene/schema/scene-file.ts +1 -7
  102. package/src/scene/user-data.ts +22 -10
  103. package/src/setup/setup-renderer.ts +10 -3
  104. package/src/tools/define-tool.ts +191 -0
  105. package/src/world2d/authoring-2d.ts +17 -1
  106. package/src/world2d/collision-2d.ts +1 -1
  107. package/src/world2d/pixi-game-adapter.ts +19 -17
  108. package/src/world2d/scene2d-loader.ts +1 -0
  109. package/src/world2d/types.ts +8 -2
  110. package/src/animation/anim-graph.ts +0 -406
  111. package/src/animation/anim-system.ts +0 -28
  112. package/src/animation/property-track.ts +0 -178
  113. package/src/animation/schema.ts +0 -204
  114. package/src/audio/ambient.ts +0 -300
  115. package/src/audio/impacts.ts +0 -212
  116. package/src/audio/movement.ts +0 -140
  117. package/src/audio/musical.ts +0 -200
  118. package/src/audio/ui-sounds.ts +0 -171
  119. package/src/audio/vehicle.ts +0 -235
  120. package/src/audio/weapons.ts +0 -152
  121. package/src/runtime/scene-ui-bridge.ts +0 -86
  122. package/src/runtime/scene-ui-data.ts +0 -119
  123. package/src/scene/schema/ui.ts +0 -602
@@ -3,22 +3,36 @@
3
3
  // Was TS-types-only (`input-types.ts`) until T4.6 authored this Zod schema —
4
4
  // see docs/BACKBONE-TASKS.md T4.6. `InputManager.loadMap` (input-manager.ts)
5
5
  // is this schema's runtime reader: it parses via this schema, then copies
6
- // each action's `bindings` array straight into `this.actions`.
6
+ // each action's `bindings` array (and its declared `valueType`, F1) straight
7
+ // into `this.actions`/`this.actionValueTypes`.
7
8
  //
8
9
  // Every field has a `.describe()` (repo policy — powers
9
10
  // `scripts/generate-schema.ts` and the T4.1/T4.6 schema-walk coverage test).
10
11
  // No runtime dependency beyond `zod`, matching the `scene/schema/` and
11
12
  // `manifest/schema.ts` precedent.
12
13
  //
13
- // T4.1 inheritance: `input-manager.ts`'s `isPressed`/`isJustPressed`/
14
- // `isJustReleased` switch on `binding.type` and only ever handle `key`,
15
- // `mouse_button`, `gamepad_button`, and `gamepad_axis` `mouse_move` and
16
- // `gamepad_axis_pair` are authored-but-unhandled binding kinds (mouse look is
17
- // read via a free-standing `mouseDelta`/`lookStick` accumulator that no
18
- // binding gates, and there is no code path that reads `xAxis`/`yAxis` as a
19
- // pair). Authoring either kind is a confirmed dead stub — REJECTED at parse,
20
- // mirroring the `scene/schema/ui.ts` dead-field mechanism (a `.superRefine`
21
- // that throws naming the field).
14
+ // T4.1 history (now closed by F2): `mouse_move` and `gamepad_axis_pair` were
15
+ // authored-but-unhandled binding kinds REJECTED at parse via a `.superRefine`
16
+ // that threw naming the field, mirroring the `scene/schema/ui.ts` dead-field
17
+ // mechanism. F2 (spec §12 "Complete Device Backends") un-rejects both: real
18
+ // `InputManager` readers now exist for them (`collectPointerDeltaContributions`/
19
+ // `collectVector2Contributions` `mouse_move` feeds pointerDelta/vector2 from
20
+ // the existing mouseDelta accumulator; `gamepad_axis_pair` feeds vector2 from
21
+ // the coupled `xAxis`/`yAxis` gamepad reading). F2 also adds two new LIVE
22
+ // binding kinds, `touch_button`/`touch_stick` (the touch/virtual-control
23
+ // backend — see `InputManager.setTouchButton`/`setTouchStick`).
24
+ //
25
+ // F1 (spec §12 "Define Typed Action Values") adds:
26
+ // - `InputAction.valueType` — an action now DECLARES its value shape
27
+ // (`digital`/`scalar`/`vector2`/`pointerDelta`/`pointerPosition`),
28
+ // defaulted to `digital` so every pre-F1 action stays valid unchanged.
29
+ // - four new, LIVE (not dead-rejected) binding kinds — `test_axis`,
30
+ // `test_vector2`, `test_pointer_delta`, `test_pointer_position` — the
31
+ // "injected test input" backend F2's acceptance criteria names as a
32
+ // peer of keyboard/mouse/gamepad/touch. They read back a caller-injected
33
+ // raw value (InputManager.injectAxis/injectVector2/injectPointerDelta/
34
+ // injectPointerPosition) through the SAME deadzone/normalize/combine path
35
+ // real device bindings use.
22
36
 
23
37
  import { z } from 'zod';
24
38
  import type { InputAction, InputBinding, InputMapFile } from './input-types';
@@ -37,8 +51,18 @@ const MouseMoveBindingSchema = z.object({
37
51
  type: z
38
52
  .literal('mouse_move')
39
53
  .describe(
40
- 'Binding kind: raw mouse movement. REJECTED at parse (T4.1/T4.6): declared but has no ' +
41
- 'runtime reader authoring this binding throws. Do not author.',
54
+ 'Binding kind: raw mouse movement (F2, spec §12 "Complete Device Backends"). Feeds a ' +
55
+ "'pointerDelta'-valueType action with the raw accumulated mouse delta (no deadzone), and " +
56
+ "can also feed a 'vector2'-valueType action (the same delta, radial-deadzoned + unit-" +
57
+ 'circle-clamped via `deadzone`).',
58
+ ),
59
+ deadzone: z
60
+ .number()
61
+ .optional()
62
+ .describe(
63
+ "Radial deadzone magnitude applied only when this binding feeds a 'vector2'-valueType " +
64
+ "action (default 0 — no deadzone; ignored for 'pointerDelta' reads, which are never " +
65
+ 'deadzoned).',
42
66
  ),
43
67
  });
44
68
 
@@ -61,57 +85,187 @@ const GamepadAxisPairBindingSchema = z.object({
61
85
  .literal('gamepad_axis_pair')
62
86
  .describe(
63
87
  'Binding kind: a coupled (x, y) gamepad axis pair, e.g. a stick used as a 2D vector ' +
64
- '(look/aim). REJECTED at parse (T4.1/T4.6): declared but has no runtime reader only ' +
65
- '`gamepad_axis` (a single thresholded axis) is handled by isPressed/isJustPressed/' +
66
- 'isJustReleased. Authoring this binding throws. Do not author.',
88
+ '(look/aim/movement) (F2, spec §12 "Complete Device Backends"). Feeds a ' +
89
+ "'vector2'-valueType action: (xAxis, yAxis) read together, radial-deadzoned + unit-" +
90
+ 'circle-clamped the same way a real analog stick is.',
67
91
  ),
68
92
  xAxis: z.number().describe('Standard Gamepad API axis index for the X component'),
69
93
  yAxis: z.number().describe('Standard Gamepad API axis index for the Y component'),
70
94
  deadzone: z.number().optional().describe('Deadzone magnitude (default 0.15 if omitted)'),
71
95
  });
72
96
 
73
- /**
74
- * A single input binding. `mouse_move`/`gamepad_axis_pair` are dead-by-decision
75
- * (see the per-variant `.describe()` above) — the `.superRefine` below is the
76
- * actual enforcement, mirroring `scene/schema/ui.ts`'s dead-field mechanism.
77
- */
78
- export const InputBindingSchema: z.ZodType<InputBinding> = z
79
- .discriminatedUnion('type', [
80
- KeyBindingSchema,
81
- MouseButtonBindingSchema,
82
- MouseMoveBindingSchema,
83
- GamepadButtonBindingSchema,
84
- GamepadAxisBindingSchema,
85
- GamepadAxisPairBindingSchema,
86
- ])
87
- .superRefine((binding, ctx) => {
88
- if (binding.type === 'mouse_move') {
89
- ctx.addIssue({
90
- code: z.ZodIssueCode.custom,
91
- message:
92
- '`mouse_move` binding kind is authored but not implemented — InputManager.isPressed/' +
93
- 'isJustPressed/isJustReleased never handle it (mouse look is read via a free-standing ' +
94
- 'mouseDelta/lookStick accumulator, not gated by any action binding). See T4.1/T4.6.',
95
- path: ['type'],
96
- });
97
- }
98
- if (binding.type === 'gamepad_axis_pair') {
99
- ctx.addIssue({
100
- code: z.ZodIssueCode.custom,
101
- message:
102
- '`gamepad_axis_pair` binding kind is authored but not implemented — no reader consumes ' +
103
- '`xAxis`/`yAxis` as a coupled pair (only `gamepad_axis`, a single thresholded axis, is ' +
104
- 'handled by isPressed/isJustPressed/isJustReleased). See T4.1/T4.6.',
105
- path: ['type'],
106
- });
107
- }
108
- }) as z.ZodType<InputBinding>;
97
+ // ---------------------------------------------------------------------------
98
+ // F1 (spec §12) — injected test input bindings. LIVE (real runtime readers in
99
+ // InputManager's getScalar/getVector2/getPointerDelta/getPointerPosition),
100
+ // unlike the two dead stubs above. Foreshadows F2's "injected test input"
101
+ // backend: real device kinds F2 adds later feed the same typed-value
102
+ // aggregation path these exercise today.
103
+ // ---------------------------------------------------------------------------
104
+
105
+ const TestAxisBindingSchema = z.object({
106
+ type: z
107
+ .literal('test_axis')
108
+ .describe(
109
+ "Binding kind: an injected synthetic scalar value for a 'scalar'-valueType action " +
110
+ '(InputManager.injectAxis/getScalar) — the "injected test input" backend named in F2, ' +
111
+ "built in F1 as the typed scalar value model's testable source.",
112
+ ),
113
+ sourceId: z
114
+ .string()
115
+ .describe('Names the injected test source this binding reads (InputManager.injectAxis).'),
116
+ deadzone: z
117
+ .number()
118
+ .optional()
119
+ .describe(
120
+ '1D deadzone magnitude applied to the injected raw value before combine (default 0 — no ' +
121
+ 'deadzone). Values below it read as 0; values at/above it rescale from 0 (at the ' +
122
+ 'deadzone edge) to ±1 (at |raw| = 1), sign preserved.',
123
+ ),
124
+ });
125
+
126
+ const TestVector2BindingSchema = z.object({
127
+ type: z
128
+ .literal('test_vector2')
129
+ .describe(
130
+ "Binding kind: an injected synthetic {x,y} value for a 'vector2'-valueType action " +
131
+ '(InputManager.injectVector2/getVector2) — the "injected test input" backend named in ' +
132
+ "F2, built in F1 as the typed Vector2 value model's testable source.",
133
+ ),
134
+ sourceId: z
135
+ .string()
136
+ .describe('Names the injected test source this binding reads (InputManager.injectVector2).'),
137
+ deadzone: z
138
+ .number()
139
+ .optional()
140
+ .describe(
141
+ 'Radial deadzone magnitude applied to the injected raw {x,y} before combine (default 0 ' +
142
+ '— no deadzone). Vectors whose magnitude is below it read as {0,0}; at/above it, ' +
143
+ 'direction is preserved and magnitude rescales the same way the scalar deadzone does, ' +
144
+ 'then the result is clamped to the unit circle (magnitude <= 1).',
145
+ ),
146
+ });
147
+
148
+ const TestPointerDeltaBindingSchema = z.object({
149
+ type: z
150
+ .literal('test_pointer_delta')
151
+ .describe(
152
+ "Binding kind: an injected synthetic per-frame {x,y} delta for a 'pointerDelta'-" +
153
+ 'valueType action (InputManager.injectPointerDelta/getPointerDelta) — the "injected ' +
154
+ 'test input" backend named in F2. No deadzone (deltas are not deadzoned).',
155
+ ),
156
+ sourceId: z
157
+ .string()
158
+ .describe(
159
+ 'Names the injected test source this binding reads (InputManager.injectPointerDelta).',
160
+ ),
161
+ });
162
+
163
+ const TestPointerPositionBindingSchema = z.object({
164
+ type: z
165
+ .literal('test_pointer_position')
166
+ .describe(
167
+ "Binding kind: an injected synthetic absolute {x,y} position for a 'pointerPosition'-" +
168
+ 'valueType action (InputManager.injectPointerPosition/getPointerPosition) — the ' +
169
+ '"injected test input" backend named in F2.',
170
+ ),
171
+ sourceId: z
172
+ .string()
173
+ .describe(
174
+ 'Names the injected test source this binding reads (InputManager.injectPointerPosition).',
175
+ ),
176
+ });
177
+
178
+ // ---------------------------------------------------------------------------
179
+ // F2 (spec §12 "Complete Device Backends") — touch/virtual-control bindings.
180
+ // LIVE (real runtime readers in InputManager's isPressed/isJustPressed/
181
+ // isJustReleased/getDigitalSource and getVector2), driven by
182
+ // InputManager.setTouchButton/setTouchStick — the headless-testable
183
+ // injectable entry points a real on-screen touch-control UI's own
184
+ // touchstart/touchmove/touchend handlers would call (that DOM wiring is a
185
+ // thin adapter at the edge, out of scope here — see input-manager.ts).
186
+ // ---------------------------------------------------------------------------
187
+
188
+ const TouchButtonBindingSchema = z.object({
189
+ type: z
190
+ .literal('touch_button')
191
+ .describe(
192
+ "Binding kind: a virtual on-screen button for a 'digital' action " +
193
+ '(InputManager.setTouchButton/isPressed) — edge-tracked (isJustPressed/isJustReleased) ' +
194
+ 'the same way a real mouse/keyboard button is.',
195
+ ),
196
+ sourceId: z
197
+ .string()
198
+ .describe(
199
+ 'Names the virtual control zone this binding reads (InputManager.setTouchButton) — a ' +
200
+ 'touch-UI-chosen id, not a real device index.',
201
+ ),
202
+ });
203
+
204
+ const TouchStickBindingSchema = z.object({
205
+ type: z
206
+ .literal('touch_stick')
207
+ .describe(
208
+ "Binding kind: a virtual on-screen analog stick for a 'vector2' action " +
209
+ '(InputManager.setTouchStick/getVector2) — radial-deadzoned + unit-circle-clamped the ' +
210
+ 'same way a real gamepad stick is.',
211
+ ),
212
+ sourceId: z
213
+ .string()
214
+ .describe(
215
+ 'Names the virtual control zone this binding reads (InputManager.setTouchStick) — a ' +
216
+ 'touch-UI-chosen id, not a real device index.',
217
+ ),
218
+ deadzone: z
219
+ .number()
220
+ .optional()
221
+ .describe(
222
+ 'Radial deadzone magnitude applied to the raw {x,y} before combine (default 0 — no ' +
223
+ 'deadzone).',
224
+ ),
225
+ });
226
+
227
+ /** A single input binding. Every kind listed here is LIVE — F2 (spec §12) closed the last two
228
+ * dead stubs (`mouse_move`/`gamepad_axis_pair`) by giving both real `InputManager` readers. */
229
+ export const InputBindingSchema: z.ZodType<InputBinding> = z.discriminatedUnion('type', [
230
+ KeyBindingSchema,
231
+ MouseButtonBindingSchema,
232
+ MouseMoveBindingSchema,
233
+ GamepadButtonBindingSchema,
234
+ GamepadAxisBindingSchema,
235
+ GamepadAxisPairBindingSchema,
236
+ TestAxisBindingSchema,
237
+ TestVector2BindingSchema,
238
+ TestPointerDeltaBindingSchema,
239
+ TestPointerPositionBindingSchema,
240
+ TouchButtonBindingSchema,
241
+ TouchStickBindingSchema,
242
+ ]) as z.ZodType<InputBinding>;
109
243
 
110
244
  /** An action has a name (the record key) and one or more bindings. */
111
245
  export const InputActionSchema: z.ZodType<InputAction> = z.object({
246
+ valueType: z
247
+ .enum(['digital', 'scalar', 'vector2', 'pointerDelta', 'pointerPosition'])
248
+ .default('digital')
249
+ .describe(
250
+ "This action's declared value shape (F1, spec §12). 'digital' (boolean, with " +
251
+ 'pressed/justPressed/justReleased edges) is the default when omitted, matching every ' +
252
+ "action authored before F1. 'scalar' is a single deadzone-rescaled float axis. " +
253
+ "'vector2' is a radial-deadzoned, unit-circle-normalized {x,y}. 'pointerDelta' is a " +
254
+ "per-frame {x,y} movement delta, summed across sources. 'pointerPosition' is an " +
255
+ 'absolute {x,y}, last-write-wins across sources. InputManager enforces this at read ' +
256
+ 'time: reading an action through the wrong typed getter (e.g. getScalar on a ' +
257
+ "'digital' action) throws.",
258
+ ),
112
259
  bindings: z
113
260
  .array(InputBindingSchema)
114
- .describe('Bindings that all trigger this action (any one firing is enough)'),
261
+ .describe(
262
+ 'Bindings that all trigger/feed this action. Digital actions combine via OR (any one ' +
263
+ 'binding satisfied is enough). Scalar actions combine via max-magnitude across ' +
264
+ 'contributing bindings (ties keep the first-listed binding). Vector2 actions combine ' +
265
+ 'by summing contributing {x,y} values then clamping to the unit circle. PointerDelta ' +
266
+ 'actions sum contributing deltas. PointerPosition actions use the most-recently-' +
267
+ 'written contributing source (last-write-wins).',
268
+ ),
115
269
  });
116
270
 
117
271
  /**
@@ -1,21 +1,21 @@
1
1
  // Game manifest (vgai.game.json) — schema + loader (T3.1 slice 1).
2
2
  // See docs/GAME-MANIFEST-DESIGN.md.
3
3
 
4
- export type { ResolvedAdapter, ResolvedGameManifest, ResolvedWorldEntry } from './load';
4
+ export type { ResolvedAdapter, ResolvedAdapterRoot, ResolvedGameManifest } from './load';
5
5
  export { CAPABILITY_CEILINGS, DEFAULT_SERVER_MODULE, loadGameManifest } from './load';
6
6
  export type {
7
+ AdapterRoot,
7
8
  GameManifest,
8
9
  IngestStrategy,
10
+ RootAdapter,
9
11
  Tier,
10
- WorldAdapter,
11
- WorldEntry,
12
12
  } from './schema';
13
13
  export {
14
+ AdapterRootSchema,
14
15
  GameManifestSchema,
15
16
  IngestStrategySchema,
17
+ RootAdapterSchema,
16
18
  TierSchema,
17
- WorldAdapterSchema,
18
- WorldEntrySchema,
19
19
  } from './schema';
20
20
 
21
21
  // `loadGameManifestFile` (Node-only, reads `path` via `node:fs`) is