@vgai/engine 0.5.42 → 0.5.44

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 (114) hide show
  1. package/dist/adapter/adapter-module.d.ts +28 -29
  2. package/dist/adapter/adapter-module.d.ts.map +1 -1
  3. package/dist/adapter/adapter-module.js +11 -34
  4. package/dist/adapter/authoring-seam-contract.d.ts +5 -0
  5. package/dist/adapter/authoring-seam-contract.d.ts.map +1 -1
  6. package/dist/adapter/authoring-seam-contract.js +1 -0
  7. package/dist/adapter/authoring.d.ts +16 -0
  8. package/dist/adapter/authoring.d.ts.map +1 -1
  9. package/dist/adapter/finders/finder-result.d.ts +2 -2
  10. package/dist/adapter/finders/finder-result.d.ts.map +1 -1
  11. package/dist/adapter/finders/index.d.ts +15 -2
  12. package/dist/adapter/finders/index.d.ts.map +1 -1
  13. package/dist/adapter/finders/index.js +38 -10
  14. package/dist/adapter/finders/prefabs-from-stories.d.ts.map +1 -1
  15. package/dist/adapter/finders/registry.d.ts +45 -0
  16. package/dist/adapter/finders/registry.d.ts.map +1 -0
  17. package/dist/adapter/finders/registry.js +56 -0
  18. package/dist/adapter/ingest/scene-capture.d.ts.map +1 -1
  19. package/dist/adapter/ingest/scene-capture.js +50 -20
  20. package/dist/adapter/system-adapter.d.ts +3 -1
  21. package/dist/adapter/system-adapter.d.ts.map +1 -1
  22. package/dist/canvas-react/pixi-react-root-factory.d.ts.map +1 -1
  23. package/dist/canvas-react/pixi-react-root-factory.js +21 -11
  24. package/dist/data/vite-plugin-data.d.ts +1 -1
  25. package/dist/data/vite-plugin-data.d.ts.map +1 -1
  26. package/dist/data/vite-plugin-data.js +2 -2
  27. package/dist/index.d.ts +1 -1
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +1 -1
  30. package/dist/input/host-pointer.d.ts +18 -0
  31. package/dist/input/host-pointer.d.ts.map +1 -1
  32. package/dist/input/host-pointer.js +54 -5
  33. package/dist/input/input-manager.d.ts +83 -1
  34. package/dist/input/input-manager.d.ts.map +1 -1
  35. package/dist/input/input-manager.js +357 -8
  36. package/dist/manifest/configuration-kinds.d.ts +97 -0
  37. package/dist/manifest/configuration-kinds.d.ts.map +1 -0
  38. package/dist/manifest/configuration-kinds.js +148 -0
  39. package/dist/manifest/index.d.ts +2 -1
  40. package/dist/manifest/index.d.ts.map +1 -1
  41. package/dist/manifest/index.js +4 -3
  42. package/dist/manifest/kind-modules.d.ts +18 -0
  43. package/dist/manifest/kind-modules.d.ts.map +1 -0
  44. package/dist/manifest/kind-modules.js +58 -0
  45. package/dist/manifest/load-file.d.ts +3 -3
  46. package/dist/manifest/load-file.d.ts.map +1 -1
  47. package/dist/manifest/load-file.js +4 -4
  48. package/dist/manifest/load.d.ts +17 -12
  49. package/dist/manifest/load.d.ts.map +1 -1
  50. package/dist/manifest/load.js +29 -19
  51. package/dist/manifest/runtime-environment.d.ts +11 -0
  52. package/dist/manifest/runtime-environment.d.ts.map +1 -0
  53. package/dist/manifest/runtime-environment.js +44 -0
  54. package/dist/manifest/schema.d.ts +10 -9
  55. package/dist/manifest/schema.d.ts.map +1 -1
  56. package/dist/manifest/schema.js +32 -27
  57. package/dist/react/use-data.d.ts +1 -1
  58. package/dist/react/use-data.js +1 -1
  59. package/dist/render/matcap-texture.d.ts +5 -0
  60. package/dist/render/matcap-texture.d.ts.map +1 -0
  61. package/dist/render/matcap-texture.js +82 -0
  62. package/dist/render/soft-particle-depth.d.ts +14 -8
  63. package/dist/render/soft-particle-depth.d.ts.map +1 -1
  64. package/dist/render/soft-particle-depth.js +37 -14
  65. package/dist/render/viewport-shading.d.ts +11 -1
  66. package/dist/render/viewport-shading.d.ts.map +1 -1
  67. package/dist/render/viewport-shading.js +25 -0
  68. package/dist/runtime/create-runtime.js +6 -5
  69. package/dist/runtime/mount-manifest.d.ts.map +1 -1
  70. package/dist/runtime/mount-manifest.js +11 -2
  71. package/dist/settings/schema.d.ts +99 -0
  72. package/dist/settings/schema.d.ts.map +1 -0
  73. package/dist/settings/schema.js +110 -0
  74. package/dist/world3d-react/r3f-root-factory.d.ts.map +1 -1
  75. package/dist/world3d-react/r3f-root-factory.js +34 -36
  76. package/dist/world3d-react/rapier-physics-bridge.d.ts.map +1 -1
  77. package/dist/world3d-react/rapier-physics-bridge.js +21 -6
  78. package/dist-config/config.js +1 -1
  79. package/dist-config/config.js.map +2 -2
  80. package/package.json +2 -1
  81. package/schemas/engine-capabilities.json +4 -4
  82. package/schemas/vgai-project.schema.json +41 -36
  83. package/schemas/vgai-settings.schema.json +112 -0
  84. package/src/adapter/adapter-module.ts +33 -51
  85. package/src/adapter/authoring-seam-contract.ts +1 -0
  86. package/src/adapter/authoring.ts +16 -0
  87. package/src/adapter/finders/finder-result.ts +2 -2
  88. package/src/adapter/finders/index.ts +68 -12
  89. package/src/adapter/finders/prefabs-from-stories.ts +2 -2
  90. package/src/adapter/finders/registry.ts +95 -0
  91. package/src/adapter/finders/scenes-from-entrypoint-selection.ts +4 -4
  92. package/src/adapter/ingest/scene-capture.ts +48 -19
  93. package/src/adapter/system-adapter.ts +3 -1
  94. package/src/canvas-react/pixi-react-root-factory.tsx +21 -11
  95. package/src/data/vite-plugin-data.ts +4 -2
  96. package/src/index.ts +1 -1
  97. package/src/input/host-pointer.ts +84 -5
  98. package/src/input/input-manager.ts +404 -7
  99. package/src/manifest/configuration-kinds.ts +241 -0
  100. package/src/manifest/index.ts +4 -3
  101. package/src/manifest/kind-modules.ts +58 -0
  102. package/src/manifest/load-file.ts +11 -5
  103. package/src/manifest/load.ts +53 -22
  104. package/src/manifest/runtime-environment.ts +48 -0
  105. package/src/manifest/schema.ts +39 -33
  106. package/src/react/use-data.ts +1 -1
  107. package/src/render/matcap-texture.ts +92 -0
  108. package/src/render/soft-particle-depth.ts +43 -14
  109. package/src/render/viewport-shading.ts +33 -1
  110. package/src/runtime/create-runtime.ts +6 -5
  111. package/src/runtime/mount-manifest.ts +13 -2
  112. package/src/settings/schema.ts +127 -0
  113. package/src/world3d-react/r3f-root-factory.tsx +45 -39
  114. package/src/world3d-react/rapier-physics-bridge.tsx +22 -8
@@ -379,6 +379,8 @@ export class InputManager {
379
379
  // level values feeding the same max-magnitude/sum combine every real
380
380
  // binding uses (collectScalarContributions/collectVector2Contributions).
381
381
  virtualDigitalHeld = new Set();
382
+ /** Processed strength attached to a held virtual digital action. */
383
+ virtualDigitalStrength = new Map();
382
384
  virtualDigitalTapQueued = new Set();
383
385
  virtualDigitalTapActive = new Set();
384
386
  /** pulse-runner friction #4 — the release-edge mirror of
@@ -392,6 +394,9 @@ export class InputManager {
392
394
  virtualDigitalJustReleased = new Set();
393
395
  virtualScalarValues = new Map();
394
396
  virtualVector2Values = new Map();
397
+ /** Derived digital-pair axes owned by mounted foreign runtimes. Their public GetAxis-shaped
398
+ * calls remain in compat; only interpolation state and action reads live beside their source. */
399
+ smoothedAxisSets = new Set();
395
400
  // D15/T-D15.5 — tick-indexed input scheduling + post-gate recording.
396
401
  //
397
402
  // `currentTick` is a FALLBACK, self-incrementing counter, used only when a
@@ -482,8 +487,60 @@ export class InputManager {
482
487
  pointerLocked = false;
483
488
  /** Element currently wired for click-to-pointer-lock, and its listener. */
484
489
  pointerLockElement = null;
490
+ /** One report per refusal reason: a refused lock is the whole story of
491
+ * "the mouse doesn't aim", and it was silent. */
492
+ reportedPointerLockRefusals = new Set();
493
+ reportPointerLockRefused(reason) {
494
+ if (this.reportedPointerLockRefusals.has(reason))
495
+ return;
496
+ this.reportedPointerLockRefusals.add(reason);
497
+ // biome-ignore lint/suspicious/noConsole: the game's own input intent failed in the browser — an error the editor console and a tester's evidence must both carry
498
+ console.error(`Pointer lock was refused, so mouse look and mouse aim cannot engage: ${reason}. ` +
499
+ 'The browser grants it only to a user gesture on a page it trusts — a page inside a ' +
500
+ 'frame needs allow="pointer-lock", and some extensions block it.');
501
+ }
502
+ /** A retry already scheduled for Chrome's post-exit cooldown, if any. */
503
+ pointerLockRetry = null;
485
504
  onPointerLockClick = () => {
486
- this.pointerLockElement?.requestPointerLock();
505
+ // Chrome answers with a promise that REJECTS when the lock is refused (a
506
+ // sandboxed/automated document, a policy, a frame without the permission).
507
+ // Three testers in a row reported "the mouse doesn't aim" on the hosted
508
+ // editor while automation could not lock at all — and the refusal was
509
+ // caught silently (runhuman passes 122–126). It is reported now, once
510
+ // per reason, as the error it is.
511
+ const request = this.pointerLockElement?.requestPointerLock();
512
+ if (request instanceof Promise) {
513
+ request.catch((error) => {
514
+ const reason = error instanceof Error ? error.message : String(error);
515
+ // CHROME'S COOLDOWN IS NOT A REFUSAL. After Escape ends a lock, a
516
+ // request in the next ~1.25 s is rejected with "cannot be acquired
517
+ // immediately after the user has exited the lock" — and a player who
518
+ // clicks back into the game right away gets no aim and no second
519
+ // chance (runhuman pass 130: "I can't move the camera with the
520
+ // mouse"). The click's activation still stands for a few seconds, so
521
+ // the request is retried once when the cooldown has passed.
522
+ if (/exited the lock/i.test(reason) && this.pointerLockRetry === null) {
523
+ this.pointerLockRetry = setTimeout(() => {
524
+ this.pointerLockRetry = null;
525
+ const doc = typeof document !== 'undefined' ? document : null;
526
+ const activation = typeof navigator !== 'undefined' ? navigator.userActivation : undefined;
527
+ if (!this.pointerLockElement || doc?.pointerLockElement === this.pointerLockElement) {
528
+ return;
529
+ }
530
+ if (activation?.isActive) {
531
+ this.onPointerLockClick();
532
+ }
533
+ else {
534
+ // The cooldown outlived the click's activation: the retry
535
+ // cannot run, and a silent end here is "mouse dead, no error".
536
+ this.reportPointerLockRefused(`${reason} (the retry found no user activation left)`);
537
+ }
538
+ }, 1_300);
539
+ return;
540
+ }
541
+ this.reportPointerLockRefused(reason);
542
+ });
543
+ }
487
544
  };
488
545
  onPointerLockChange = () => {
489
546
  const doc = typeof document !== 'undefined' ? document : null;
@@ -608,6 +665,7 @@ export class InputManager {
608
665
  }
609
666
  }
610
667
  this.virtualDigitalHeld.clear();
668
+ this.virtualDigitalStrength.clear();
611
669
  this.virtualDigitalTapQueued.clear();
612
670
  this.virtualDigitalTapActive.clear();
613
671
  this.virtualScalarValues.clear();
@@ -742,6 +800,107 @@ export class InputManager {
742
800
  actionNames() {
743
801
  return [...this.actions.keys()];
744
802
  }
803
+ /**
804
+ * Create a retained, action-backed set of digital axes. Foreign runtimes keep their public
805
+ * GetAxis-shaped API in compat, while interpolation and the live action reads stay in the input
806
+ * owner instead of becoming a parallel compat input store.
807
+ */
808
+ createSmoothedAxisSet(definitions) {
809
+ const byName = new Map();
810
+ for (const source of definitions) {
811
+ const definition = { ...source };
812
+ if (definition.name.length === 0) {
813
+ throw new Error('InputManager.createSmoothedAxisSet: axis names must not be empty');
814
+ }
815
+ if (byName.has(definition.name)) {
816
+ throw new Error(`InputManager.createSmoothedAxisSet: duplicate axis ${JSON.stringify(definition.name)}`);
817
+ }
818
+ if (definition.negativeAction.length === 0 || definition.positiveAction.length === 0) {
819
+ throw new Error(`InputManager.createSmoothedAxisSet: axis ${JSON.stringify(definition.name)} requires ` +
820
+ 'both negative and positive actions');
821
+ }
822
+ for (const [field, value] of [
823
+ ['gravity', definition.gravity],
824
+ ['dead', definition.dead],
825
+ ['sensitivity', definition.sensitivity],
826
+ ]) {
827
+ if (!Number.isFinite(value) || value < 0) {
828
+ throw new Error(`InputManager.createSmoothedAxisSet: ${definition.name}.${field} must be a finite ` +
829
+ `non-negative number, received ${String(value)}`);
830
+ }
831
+ }
832
+ byName.set(definition.name, Object.freeze(definition));
833
+ }
834
+ const state = {
835
+ definitions: byName,
836
+ values: new Map(),
837
+ disposed: false,
838
+ };
839
+ this.smoothedAxisSets.add(state);
840
+ const assertLive = () => {
841
+ if (state.disposed || this.disposed) {
842
+ throw new Error('InputManager smoothed axis set has been disposed');
843
+ }
844
+ };
845
+ const definitionOf = (name) => {
846
+ assertLive();
847
+ const definition = state.definitions.get(name);
848
+ if (definition === undefined) {
849
+ throw new Error(`InputManager smoothed axis set has no axis ${JSON.stringify(name)}`);
850
+ }
851
+ return definition;
852
+ };
853
+ const rawValue = (definition) => {
854
+ const negative = this.isPressed(definition.negativeAction) ? -1 : 0;
855
+ const positive = this.isPressed(definition.positiveAction) ? 1 : 0;
856
+ return (negative + positive) * (definition.invert ? -1 : 1);
857
+ };
858
+ const getAxis = (name) => {
859
+ const definition = definitionOf(name);
860
+ const value = state.values.get(name) ?? 0;
861
+ return Math.abs(value) < definition.dead ? 0 : value;
862
+ };
863
+ return {
864
+ advance: (deltaTime) => {
865
+ assertLive();
866
+ if (!Number.isFinite(deltaTime) || deltaTime < 0) {
867
+ throw new Error(`InputManager smoothed axis deltaTime must be finite and non-negative, received ` +
868
+ String(deltaTime));
869
+ }
870
+ for (const definition of state.definitions.values()) {
871
+ const target = rawValue(definition);
872
+ const current = state.values.get(definition.name) ?? 0;
873
+ let next;
874
+ if (target === 0) {
875
+ const step = definition.gravity * deltaTime;
876
+ next = current > 0 ? Math.max(0, current - step) : Math.min(0, current + step);
877
+ }
878
+ else {
879
+ const from = definition.snap && current * target < 0 ? 0 : current;
880
+ const step = definition.sensitivity * deltaTime;
881
+ next = target > 0 ? Math.min(target, from + step) : Math.max(target, from - step);
882
+ }
883
+ state.values.set(definition.name, next);
884
+ }
885
+ },
886
+ getAxis,
887
+ getAxisRaw: (name) => rawValue(definitionOf(name)),
888
+ snapshot: () => {
889
+ assertLive();
890
+ const snapshot = {};
891
+ for (const name of state.definitions.keys())
892
+ snapshot[name] = getAxis(name);
893
+ return snapshot;
894
+ },
895
+ dispose: () => {
896
+ if (state.disposed)
897
+ return;
898
+ state.disposed = true;
899
+ state.values.clear();
900
+ this.smoothedAxisSets.delete(state);
901
+ },
902
+ };
903
+ }
745
904
  /**
746
905
  * Poll gamepads and virtual look stick (call at start of frame).
747
906
  *
@@ -955,10 +1114,12 @@ export class InputManager {
955
1114
  if (typeof entry.value === 'boolean') {
956
1115
  if (entry.value) {
957
1116
  this.virtualDigitalHeld.add(entry.action);
1117
+ this.virtualDigitalStrength.set(entry.action, 1);
958
1118
  this.virtualDigitalTapActive.add(entry.action);
959
1119
  }
960
1120
  else {
961
1121
  this.virtualDigitalHeld.delete(entry.action);
1122
+ this.virtualDigitalStrength.delete(entry.action);
962
1123
  }
963
1124
  }
964
1125
  else {
@@ -1060,6 +1221,111 @@ export class InputManager {
1060
1221
  }
1061
1222
  return false;
1062
1223
  }
1224
+ /**
1225
+ * Processed strength for a declared digital action without creating a
1226
+ * second action-state store. Digital keys/buttons/touches contribute 1,
1227
+ * while a directional gamepad axis contributes Godot's deadzone-rescaled
1228
+ * magnitude. Multiple bindings combine by greatest strength, matching the
1229
+ * action state that {@link isPressed} reads as a boolean.
1230
+ */
1231
+ getDigitalStrength(actionName) {
1232
+ this.assertValueType(actionName, 'digital', 'getDigitalStrength');
1233
+ if (this.machineInputActive &&
1234
+ (this.virtualDigitalHeld.has(actionName) || this.virtualDigitalTapActive.has(actionName))) {
1235
+ return this.virtualDigitalStrength.get(actionName) ?? 1;
1236
+ }
1237
+ if (!this.inputActive)
1238
+ return 0;
1239
+ const bindings = this.actions.get(actionName);
1240
+ if (!bindings)
1241
+ return 0;
1242
+ let strength = 0;
1243
+ for (const binding of bindings) {
1244
+ switch (binding.type) {
1245
+ case 'key':
1246
+ if (this.keysDown.has(binding.code))
1247
+ return 1;
1248
+ break;
1249
+ case 'mouse_button':
1250
+ if (this.mouseButtons.has(binding.button))
1251
+ return 1;
1252
+ break;
1253
+ case 'gamepad_button':
1254
+ for (const gp of this.gamepads) {
1255
+ const button = gp?.buttons[binding.button];
1256
+ // Pinned InputEventJoypadButton::action_match reports 1 for a
1257
+ // pressed button and 0 otherwise; GamepadButton.value is not its
1258
+ // action strength.
1259
+ if (button?.pressed)
1260
+ return 1;
1261
+ }
1262
+ break;
1263
+ case 'gamepad_axis': {
1264
+ const deadzone = binding.deadzone ?? GAMEPAD_AXIS_DEFAULT_DEADZONE;
1265
+ for (const gp of this.gamepads) {
1266
+ if (!gp)
1267
+ continue;
1268
+ const raw = gp.axes[binding.axis] ?? 0;
1269
+ const directional = binding.direction === 'positive' ? raw : -raw;
1270
+ if (directional < deadzone)
1271
+ continue;
1272
+ strength = Math.max(strength, deadzone === 1
1273
+ ? 1
1274
+ : Math.min(1, Math.max(0, (directional - deadzone) / (1 - deadzone))));
1275
+ }
1276
+ break;
1277
+ }
1278
+ case 'touch_button':
1279
+ if (this.touchButtonsDown.has(binding.sourceId))
1280
+ return 1;
1281
+ break;
1282
+ }
1283
+ }
1284
+ return strength;
1285
+ }
1286
+ /** Raw (pre-deadzone) strength for a declared digital action. */
1287
+ getDigitalRawStrength(actionName) {
1288
+ this.assertValueType(actionName, 'digital', 'getDigitalRawStrength');
1289
+ if (this.machineInputActive &&
1290
+ (this.virtualDigitalHeld.has(actionName) || this.virtualDigitalTapActive.has(actionName))) {
1291
+ return this.virtualDigitalStrength.get(actionName) ?? 1;
1292
+ }
1293
+ if (!this.inputActive)
1294
+ return 0;
1295
+ const bindings = this.actions.get(actionName);
1296
+ if (!bindings)
1297
+ return 0;
1298
+ let strength = 0;
1299
+ for (const binding of bindings) {
1300
+ switch (binding.type) {
1301
+ case 'key':
1302
+ if (this.keysDown.has(binding.code))
1303
+ return 1;
1304
+ break;
1305
+ case 'mouse_button':
1306
+ if (this.mouseButtons.has(binding.button))
1307
+ return 1;
1308
+ break;
1309
+ case 'gamepad_button':
1310
+ if (this.gamepads.some((gp) => gp?.buttons[binding.button]?.pressed))
1311
+ return 1;
1312
+ break;
1313
+ case 'gamepad_axis':
1314
+ for (const gp of this.gamepads) {
1315
+ if (!gp)
1316
+ continue;
1317
+ const raw = gp.axes[binding.axis] ?? 0;
1318
+ strength = Math.max(strength, Math.min(1, Math.max(0, binding.direction === 'positive' ? raw : -raw)));
1319
+ }
1320
+ break;
1321
+ case 'touch_button':
1322
+ if (this.touchButtonsDown.has(binding.sourceId))
1323
+ return 1;
1324
+ break;
1325
+ }
1326
+ }
1327
+ return strength;
1328
+ }
1063
1329
  /** Was an action pressed this frame (not held from previous)? */
1064
1330
  isJustPressed(actionName) {
1065
1331
  this.assertValueType(actionName, 'digital', 'isJustPressed');
@@ -1724,6 +1990,18 @@ export class InputManager {
1724
1990
  return false;
1725
1991
  return this.gamepads.some((pad) => pad !== null);
1726
1992
  }
1993
+ /** Connected W3C Gamepad indices in native slot order, gated by the same play/focus state as
1994
+ * every other raw device poll. Foreign-engine adapters use their own collection value type. */
1995
+ getConnectedGamepadIndices() {
1996
+ if (!this.inputActive)
1997
+ return [];
1998
+ const indices = [];
1999
+ for (let index = 0; index < this.gamepads.length; index += 1) {
2000
+ if (this.gamepads[index] !== null)
2001
+ indices.push(index);
2002
+ }
2003
+ return indices;
2004
+ }
1727
2005
  isDeviceGamepadButtonPressed(button) {
1728
2006
  if (!this.inputActive)
1729
2007
  return false;
@@ -1732,6 +2010,15 @@ export class InputManager {
1732
2010
  return false;
1733
2011
  return this.gamepadButtonsDown.has(`${index}:${button}`);
1734
2012
  }
2013
+ /** Raw button state for one explicit native Gamepad slot. */
2014
+ isDeviceGamepadButtonPressedAt(index, button) {
2015
+ if (!this.inputActive)
2016
+ return false;
2017
+ if (!Number.isSafeInteger(index) || index < 0 || !Number.isSafeInteger(button) || button < 0) {
2018
+ return false;
2019
+ }
2020
+ return this.gamepads[index]?.buttons[button]?.pressed ?? false;
2021
+ }
1735
2022
  isDeviceGamepadButtonJustPressed(button) {
1736
2023
  if (!this.inputActive)
1737
2024
  return false;
@@ -1771,6 +2058,15 @@ export class InputManager {
1771
2058
  return 0;
1772
2059
  return this.gamepads[index]?.axes[axis] ?? 0;
1773
2060
  }
2061
+ /** Raw axis value for one explicit native Gamepad slot. */
2062
+ getDeviceGamepadAxisAt(index, axis) {
2063
+ if (!this.inputActive)
2064
+ return 0;
2065
+ if (!Number.isSafeInteger(index) || index < 0 || !Number.isSafeInteger(axis) || axis < 0) {
2066
+ return 0;
2067
+ }
2068
+ return this.gamepads[index]?.axes[axis] ?? 0;
2069
+ }
1774
2070
  firstConnectedGamepadIndex() {
1775
2071
  for (let i = 0; i < this.gamepads.length; i++) {
1776
2072
  if (this.gamepads[i] !== null)
@@ -2181,6 +2477,27 @@ export class InputManager {
2181
2477
  this.applyVirtualActionValue(action, value);
2182
2478
  return { delivered: true };
2183
2479
  }
2480
+ /**
2481
+ * Godot's `Input.action_press(action, strength)` door for a digital action.
2482
+ * This extends the existing virtual held/edge state with its clamped
2483
+ * processed strength instead of maintaining a parallel Godot action map.
2484
+ */
2485
+ setVirtualDigitalStrength(action, strength) {
2486
+ this.assertActionExists(action, 'setVirtualDigitalStrength');
2487
+ this.assertValueType(action, 'digital', 'setVirtualDigitalStrength');
2488
+ if (!Number.isFinite(strength)) {
2489
+ throw new Error('InputManager.setVirtualDigitalStrength: strength must be finite.');
2490
+ }
2491
+ if (!this.machineInputActive) {
2492
+ return { delivered: false, reason: 'input-gated (input disabled by the editor)' };
2493
+ }
2494
+ const clamped = Math.min(1, Math.max(0, strength));
2495
+ // `Input::action_press` sets api_pressed=true even when its clamped
2496
+ // strength is zero; only `action_release` clears the pressed state.
2497
+ this.applyVirtualActionValue(action, true);
2498
+ this.virtualDigitalStrength.set(action, clamped);
2499
+ return { delivered: true };
2500
+ }
2184
2501
  /**
2185
2502
  * The write half of {@link setVirtualAction} — extracted so D15/T-D15.5's
2186
2503
  * `scheduleActionAtTick` can apply a non-boolean (scalar/vector2) queued
@@ -2230,6 +2547,7 @@ export class InputManager {
2230
2547
  if (this.virtualDigitalHeld.has(action))
2231
2548
  this.virtualDigitalJustReleased.add(action);
2232
2549
  this.virtualDigitalHeld.delete(action);
2550
+ this.virtualDigitalStrength.delete(action);
2233
2551
  }
2234
2552
  break;
2235
2553
  case 'scalar':
@@ -2341,17 +2659,34 @@ export class InputManager {
2341
2659
  getInputTrace() {
2342
2660
  return { version: 1, ticks: this.inputTrace.slice() };
2343
2661
  }
2344
- /** Request pointer lock for FPS-style mouse control */
2662
+ /**
2663
+ * Request pointer lock for FPS-style mouse control.
2664
+ *
2665
+ * Wires `element` for click-to-lock (so a lock released by Escape comes back
2666
+ * on the next click) AND, when the call itself is inside a user gesture,
2667
+ * requests the lock right now. Games call this FROM a click handler
2668
+ * (third-person's Player: canvas click → `requestPointerLock(canvas)`), and
2669
+ * with only the listener wired that first click did nothing — the lock
2670
+ * arrived one click late, with no feedback, which every hosted-editor tester
2671
+ * read as "the mouse doesn't work" (runhuman passes 113–116). Outside a
2672
+ * gesture the browser would refuse and log, so the immediate request is
2673
+ * gated on `navigator.userActivation`.
2674
+ */
2345
2675
  requestPointerLock(element) {
2346
2676
  // Idempotent: never stack duplicate click listeners. Rewire if the element
2347
2677
  // changed, and track it so dispose() can remove the listener.
2348
- if (this.pointerLockElement === element)
2349
- return;
2350
- if (this.pointerLockElement) {
2351
- this.pointerLockElement.removeEventListener('click', this.onPointerLockClick);
2678
+ if (this.pointerLockElement !== element) {
2679
+ if (this.pointerLockElement) {
2680
+ this.pointerLockElement.removeEventListener('click', this.onPointerLockClick);
2681
+ }
2682
+ this.pointerLockElement = element;
2683
+ element.addEventListener('click', this.onPointerLockClick);
2684
+ }
2685
+ const doc = typeof document !== 'undefined' ? document : null;
2686
+ const activation = typeof navigator !== 'undefined' ? navigator.userActivation : undefined;
2687
+ if (doc && doc.pointerLockElement !== element && activation?.isActive) {
2688
+ this.onPointerLockClick();
2352
2689
  }
2353
- this.pointerLockElement = element;
2354
- element.addEventListener('click', this.onPointerLockClick);
2355
2690
  }
2356
2691
  /**
2357
2692
  * Release pointer lock on the element {@link requestPointerLock} wired.
@@ -2366,6 +2701,10 @@ export class InputManager {
2366
2701
  * holding lock on a different element is not ours to drop.
2367
2702
  */
2368
2703
  exitPointerLock() {
2704
+ if (this.pointerLockRetry !== null) {
2705
+ clearTimeout(this.pointerLockRetry);
2706
+ this.pointerLockRetry = null;
2707
+ }
2369
2708
  const el = this.pointerLockElement;
2370
2709
  if (el) {
2371
2710
  el.removeEventListener('click', this.onPointerLockClick);
@@ -2383,6 +2722,16 @@ export class InputManager {
2383
2722
  if (this.disposed)
2384
2723
  return;
2385
2724
  this.disposed = true;
2725
+ // A manager torn down with a key under the hand (Stop while W is held)
2726
+ // kept that key in `keysDown` for good — its listeners went away before
2727
+ // the release arrived, and nothing else clears it. A disposed manager
2728
+ // holds nothing.
2729
+ this.flushHeldState();
2730
+ for (const axes of this.smoothedAxisSets) {
2731
+ axes.disposed = true;
2732
+ axes.values.clear();
2733
+ }
2734
+ this.smoothedAxisSets.clear();
2386
2735
  if (typeof window !== 'undefined') {
2387
2736
  window.removeEventListener('keydown', this.onKeyDown);
2388
2737
  window.removeEventListener('keyup', this.onKeyUp);
@@ -0,0 +1,97 @@
1
+ /**
2
+ * THE RUN-CONFIGURATION KIND REGISTRY — the open set of kinds a manifest's
3
+ * `configurations[]` may declare (ARCHITECTURE-CORE §The project model). A run
4
+ * configuration is one of the project's ENTRYPOINTS: something the editor,
5
+ * the CLI or a harness can start by id. The engine registers two kinds
6
+ * here; a capability registers more through the same door; the manifest
7
+ * validates each declaration against its kind's own schema at load, and an
8
+ * unregistered kind refuses loudly naming the registered ones.
9
+ *
10
+ * The implicit configuration every project with roots has is `play` — mount
11
+ * every root in the host — which is never declared and may be named by a
12
+ * `compound`.
13
+ */
14
+ import { type ZodType } from 'zod';
15
+ /** The envelope every declaration carries; the rest is the kind's. */
16
+ export interface ConfigurationEnvelope {
17
+ readonly id: string;
18
+ readonly kind: string;
19
+ }
20
+ export type ConfigurationRole = 'run' | 'build';
21
+ export interface ConfigurationKindRegistration<T extends ConfigurationEnvelope = ConfigurationEnvelope> {
22
+ readonly kind: T['kind'];
23
+ /** Where a configuration of this kind surfaces and which verb starts it:
24
+ * `run` (the transport, `vgai run`) or `build` (Export, `vgai build`). */
25
+ readonly role: ConfigurationRole;
26
+ /** The declaration's full shape, envelope included; `.strict()`. */
27
+ readonly schema: ZodType<T>;
28
+ /** One line for schema output and refusals. */
29
+ readonly describe: string;
30
+ /** A build-role kind backed by a project TOOL (the operation catalog):
31
+ * the host runs `tool` with `input(configuration)` and reports its
32
+ * outcome as the build's. This is how a capability's bake becomes a
33
+ * build without the engine knowing what a bake is. */
34
+ readonly build?: {
35
+ readonly tool: string;
36
+ readonly input: (configuration: T) => unknown;
37
+ };
38
+ }
39
+ /**
40
+ * What a CONTRIBUTION exports to register a kind — a plain object, so a
41
+ * project module (evaluated with the project's own copy of the engine)
42
+ * hands it to the HOST, which registers it in the host's registry. A module
43
+ * `src/contributions/<name>.kind.ts` exports it as `kind`.
44
+ */
45
+ export type ConfigurationKindContribution = ConfigurationKindRegistration;
46
+ /** Register a contribution's kind after checking its shape by name. */
47
+ export declare function registerContributedConfigurationKind(value: unknown, source: string): () => void;
48
+ /** Register a kind. A second registration replaces the first (a module
49
+ * re-evaluated on save); never throws. */
50
+ export declare function registerConfigurationKind<T extends ConfigurationEnvelope>(registration: ConfigurationKindRegistration<T>): () => void;
51
+ export declare function registeredConfigurationKinds(): readonly string[];
52
+ export declare function configurationKind(kind: string): ConfigurationKindRegistration | undefined;
53
+ /** The id of the implicit configuration: mount every root in the host. */
54
+ export declare const PLAY_CONFIGURATION_ID = "play";
55
+ /** A process the project runs beside the host: an entry module started
56
+ * with the project's own toolchain, listening on a port when it has one. */
57
+ export interface ProcessConfiguration extends ConfigurationEnvelope {
58
+ readonly kind: 'process';
59
+ /** Project-relative entry module (`server/main.ts`). */
60
+ readonly entry: string;
61
+ /** The command that starts it, run in a shell at `cwd`; `npx tsx <entry>`
62
+ * when omitted. Declare it when the entry needs its own flags. */
63
+ readonly command?: string;
64
+ /** The port it listens on, when readiness means a port. */
65
+ readonly port?: number;
66
+ /** Project-relative working directory; the project root when omitted. */
67
+ readonly cwd?: string;
68
+ readonly env?: Readonly<Record<string, string>>;
69
+ /** How a runner knows it is up: the port accepting connections (the
70
+ * default when `port` is set), or an HTTP path answering 2xx on it. */
71
+ readonly ready?: {
72
+ readonly http?: string;
73
+ };
74
+ }
75
+ /** Several configurations started together, `play` included by name. */
76
+ export interface CompoundConfiguration extends ConfigurationEnvelope {
77
+ readonly kind: 'compound';
78
+ readonly run: readonly string[];
79
+ /** How many instances of the host mount to run against the rest (the
80
+ * editor's Instances picker is this parameter). */
81
+ readonly instances?: number;
82
+ }
83
+ /** A packaged build of the project's own web bundle: the project's build
84
+ * script, then `dist/` packaged as the web artifact. The first build-role
85
+ * kind; a capability registers more (a bake, a static site). */
86
+ export interface BundleConfiguration extends ConfigurationEnvelope {
87
+ readonly kind: 'bundle';
88
+ /** The package.json script that builds (`build`). */
89
+ readonly script?: string;
90
+ /** Extra arguments after `--` (`['--base', './']` for an itch-style relative bundle). */
91
+ readonly args?: readonly string[];
92
+ }
93
+ export type DeclaredConfiguration = ConfigurationEnvelope & Readonly<Record<string, unknown>>;
94
+ /** Validate `configurations[]` against the registry: every kind registered, every
95
+ * declaration its kind's shape, ids unique, compound members declared. */
96
+ export declare function configurationIssues(configurations: readonly ConfigurationEnvelope[]): string[];
97
+ //# sourceMappingURL=configuration-kinds.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configuration-kinds.d.ts","sourceRoot":"","sources":["../../src/manifest/configuration-kinds.ts"],"names":[],"mappings":"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"}