@vgai/engine 0.5.42 → 0.5.45
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.
- package/dist/adapter/adapter-module.d.ts +28 -29
- package/dist/adapter/adapter-module.d.ts.map +1 -1
- package/dist/adapter/adapter-module.js +11 -34
- package/dist/adapter/authoring-seam-contract.d.ts +5 -0
- package/dist/adapter/authoring-seam-contract.d.ts.map +1 -1
- package/dist/adapter/authoring-seam-contract.js +1 -0
- package/dist/adapter/authoring.d.ts +16 -0
- package/dist/adapter/authoring.d.ts.map +1 -1
- package/dist/adapter/finders/finder-result.d.ts +2 -2
- package/dist/adapter/finders/finder-result.d.ts.map +1 -1
- package/dist/adapter/finders/index.d.ts +15 -2
- package/dist/adapter/finders/index.d.ts.map +1 -1
- package/dist/adapter/finders/index.js +38 -10
- package/dist/adapter/finders/prefabs-from-stories.d.ts.map +1 -1
- package/dist/adapter/finders/registry.d.ts +45 -0
- package/dist/adapter/finders/registry.d.ts.map +1 -0
- package/dist/adapter/finders/registry.js +56 -0
- package/dist/adapter/ingest/scene-capture.d.ts.map +1 -1
- package/dist/adapter/ingest/scene-capture.js +50 -20
- package/dist/adapter/system-adapter.d.ts +3 -1
- package/dist/adapter/system-adapter.d.ts.map +1 -1
- package/dist/canvas-react/pixi-react-root-factory.d.ts.map +1 -1
- package/dist/canvas-react/pixi-react-root-factory.js +21 -11
- package/dist/data/vite-plugin-data.d.ts +1 -1
- package/dist/data/vite-plugin-data.d.ts.map +1 -1
- package/dist/data/vite-plugin-data.js +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/input/host-pointer.d.ts +18 -0
- package/dist/input/host-pointer.d.ts.map +1 -1
- package/dist/input/host-pointer.js +54 -5
- package/dist/input/input-manager.d.ts +83 -1
- package/dist/input/input-manager.d.ts.map +1 -1
- package/dist/input/input-manager.js +357 -8
- package/dist/manifest/configuration-kinds.d.ts +97 -0
- package/dist/manifest/configuration-kinds.d.ts.map +1 -0
- package/dist/manifest/configuration-kinds.js +148 -0
- package/dist/manifest/index.d.ts +2 -1
- package/dist/manifest/index.d.ts.map +1 -1
- package/dist/manifest/index.js +4 -3
- package/dist/manifest/kind-modules.d.ts +18 -0
- package/dist/manifest/kind-modules.d.ts.map +1 -0
- package/dist/manifest/kind-modules.js +58 -0
- package/dist/manifest/load-file.d.ts +3 -3
- package/dist/manifest/load-file.d.ts.map +1 -1
- package/dist/manifest/load-file.js +4 -4
- package/dist/manifest/load.d.ts +17 -12
- package/dist/manifest/load.d.ts.map +1 -1
- package/dist/manifest/load.js +29 -19
- package/dist/manifest/runtime-environment.d.ts +11 -0
- package/dist/manifest/runtime-environment.d.ts.map +1 -0
- package/dist/manifest/runtime-environment.js +44 -0
- package/dist/manifest/schema.d.ts +10 -9
- package/dist/manifest/schema.d.ts.map +1 -1
- package/dist/manifest/schema.js +32 -27
- package/dist/react/use-data.d.ts +1 -1
- package/dist/react/use-data.js +1 -1
- package/dist/render/matcap-texture.d.ts +5 -0
- package/dist/render/matcap-texture.d.ts.map +1 -0
- package/dist/render/matcap-texture.js +82 -0
- package/dist/render/soft-particle-depth.d.ts +14 -8
- package/dist/render/soft-particle-depth.d.ts.map +1 -1
- package/dist/render/soft-particle-depth.js +37 -14
- package/dist/render/viewport-shading.d.ts +11 -1
- package/dist/render/viewport-shading.d.ts.map +1 -1
- package/dist/render/viewport-shading.js +25 -0
- package/dist/runtime/create-runtime.js +6 -5
- package/dist/runtime/mount-manifest.d.ts.map +1 -1
- package/dist/runtime/mount-manifest.js +11 -2
- package/dist/settings/schema.d.ts +99 -0
- package/dist/settings/schema.d.ts.map +1 -0
- package/dist/settings/schema.js +110 -0
- package/dist/world3d-react/r3f-root-factory.d.ts.map +1 -1
- package/dist/world3d-react/r3f-root-factory.js +34 -36
- package/dist/world3d-react/rapier-physics-bridge.d.ts.map +1 -1
- package/dist/world3d-react/rapier-physics-bridge.js +21 -6
- package/dist-config/config.js +1 -1
- package/dist-config/config.js.map +2 -2
- package/package.json +2 -1
- package/schemas/engine-capabilities.json +4 -4
- package/schemas/vgai-project.schema.json +41 -36
- package/schemas/vgai-settings.schema.json +112 -0
- package/src/adapter/adapter-module.ts +33 -51
- package/src/adapter/authoring-seam-contract.ts +1 -0
- package/src/adapter/authoring.ts +16 -0
- package/src/adapter/finders/finder-result.ts +2 -2
- package/src/adapter/finders/index.ts +68 -12
- package/src/adapter/finders/prefabs-from-stories.ts +2 -2
- package/src/adapter/finders/registry.ts +95 -0
- package/src/adapter/finders/scenes-from-entrypoint-selection.ts +4 -4
- package/src/adapter/ingest/scene-capture.ts +48 -19
- package/src/adapter/system-adapter.ts +3 -1
- package/src/canvas-react/pixi-react-root-factory.tsx +21 -11
- package/src/data/vite-plugin-data.ts +4 -2
- package/src/index.ts +1 -1
- package/src/input/host-pointer.ts +84 -5
- package/src/input/input-manager.ts +404 -7
- package/src/manifest/configuration-kinds.ts +241 -0
- package/src/manifest/index.ts +4 -3
- package/src/manifest/kind-modules.ts +58 -0
- package/src/manifest/load-file.ts +11 -5
- package/src/manifest/load.ts +53 -22
- package/src/manifest/runtime-environment.ts +48 -0
- package/src/manifest/schema.ts +39 -33
- package/src/react/use-data.ts +1 -1
- package/src/render/matcap-texture.ts +92 -0
- package/src/render/soft-particle-depth.ts +43 -14
- package/src/render/viewport-shading.ts +33 -1
- package/src/runtime/create-runtime.ts +6 -5
- package/src/runtime/mount-manifest.ts +13 -2
- package/src/settings/schema.ts +127 -0
- package/src/world3d-react/r3f-root-factory.tsx +45 -39
- 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
|
-
|
|
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
|
-
/**
|
|
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
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
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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"}
|