@forgeax/engine-input 0.1.2

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 (107) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +312 -0
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/__tests__/browser-backend-dual-instance.test.d.ts +2 -0
  5. package/dist/__tests__/browser-backend-dual-instance.test.d.ts.map +1 -0
  6. package/dist/__tests__/browser-backend-focus-loss.test.d.ts +2 -0
  7. package/dist/__tests__/browser-backend-focus-loss.test.d.ts.map +1 -0
  8. package/dist/__tests__/browser-backend-lock-error.test.d.ts +2 -0
  9. package/dist/__tests__/browser-backend-lock-error.test.d.ts.map +1 -0
  10. package/dist/__tests__/browser-backend-lock-gate.test.d.ts +2 -0
  11. package/dist/__tests__/browser-backend-lock-gate.test.d.ts.map +1 -0
  12. package/dist/__tests__/browser-backend-lock-release.test.d.ts +2 -0
  13. package/dist/__tests__/browser-backend-lock-release.test.d.ts.map +1 -0
  14. package/dist/__tests__/browser-backend-lock-state.test.d.ts +2 -0
  15. package/dist/__tests__/browser-backend-lock-state.test.d.ts.map +1 -0
  16. package/dist/__tests__/canvas-input-boundary.test.d.ts +2 -0
  17. package/dist/__tests__/canvas-input-boundary.test.d.ts.map +1 -0
  18. package/dist/__tests__/canvas-input-owner.test-d.d.ts +2 -0
  19. package/dist/__tests__/canvas-input-owner.test-d.d.ts.map +1 -0
  20. package/dist/__tests__/composite-backend.test.d.ts +2 -0
  21. package/dist/__tests__/composite-backend.test.d.ts.map +1 -0
  22. package/dist/__tests__/controller-platform-owner.test-d.d.ts +2 -0
  23. package/dist/__tests__/controller-platform-owner.test-d.d.ts.map +1 -0
  24. package/dist/__tests__/input-snapshot.test-d.d.ts +2 -0
  25. package/dist/__tests__/input-snapshot.test-d.d.ts.map +1 -0
  26. package/dist/__tests__/input-snapshot.test.d.ts +2 -0
  27. package/dist/__tests__/input-snapshot.test.d.ts.map +1 -0
  28. package/dist/__tests__/swipe-direction-owner.test-d.d.ts +2 -0
  29. package/dist/__tests__/swipe-direction-owner.test-d.d.ts.map +1 -0
  30. package/dist/__tests__/ui-input-matrix.test.d.ts +2 -0
  31. package/dist/__tests__/ui-input-matrix.test.d.ts.map +1 -0
  32. package/dist/__tests__/ui-ownership-edge.test.d.ts +2 -0
  33. package/dist/__tests__/ui-ownership-edge.test.d.ts.map +1 -0
  34. package/dist/__tests__/ui-ownership.test.d.ts +2 -0
  35. package/dist/__tests__/ui-ownership.test.d.ts.map +1 -0
  36. package/dist/__tests__/ui-reset-idempotence.test.d.ts +2 -0
  37. package/dist/__tests__/ui-reset-idempotence.test.d.ts.map +1 -0
  38. package/dist/__tests__/ui-reset.test.d.ts +2 -0
  39. package/dist/__tests__/ui-reset.test.d.ts.map +1 -0
  40. package/dist/action-state.d.ts +146 -0
  41. package/dist/action-state.d.ts.map +1 -0
  42. package/dist/browser-backend.d.ts +121 -0
  43. package/dist/browser-backend.d.ts.map +1 -0
  44. package/dist/canvas-input-boundary.d.ts +13 -0
  45. package/dist/canvas-input-boundary.d.ts.map +1 -0
  46. package/dist/composite-backend.d.ts +34 -0
  47. package/dist/composite-backend.d.ts.map +1 -0
  48. package/dist/controller-db-data.d.ts +5 -0
  49. package/dist/controller-db-data.d.ts.map +1 -0
  50. package/dist/controller-db-data.mjs +12 -0
  51. package/dist/controller-db-data.mjs.map +1 -0
  52. package/dist/controller-db.d.ts +70 -0
  53. package/dist/controller-db.d.ts.map +1 -0
  54. package/dist/controller-db.mjs +119 -0
  55. package/dist/controller-db.mjs.map +1 -0
  56. package/dist/frame-start-scan-system.d.ts +37 -0
  57. package/dist/frame-start-scan-system.d.ts.map +1 -0
  58. package/dist/gamepad-frame.d.ts +42 -0
  59. package/dist/gamepad-frame.d.ts.map +1 -0
  60. package/dist/gesture-recognizer.d.ts +145 -0
  61. package/dist/gesture-recognizer.d.ts.map +1 -0
  62. package/dist/index.d.ts +13 -0
  63. package/dist/index.d.ts.map +1 -0
  64. package/dist/index.mjs +1502 -0
  65. package/dist/index.mjs.map +1 -0
  66. package/dist/input-snapshot.d.ts +420 -0
  67. package/dist/input-snapshot.d.ts.map +1 -0
  68. package/dist/plugin-service.d.ts +11 -0
  69. package/dist/plugin-service.d.ts.map +1 -0
  70. package/dist/ui-ownership.d.ts +17 -0
  71. package/dist/ui-ownership.d.ts.map +1 -0
  72. package/dist/virtual-joystick.d.ts +39 -0
  73. package/dist/virtual-joystick.d.ts.map +1 -0
  74. package/package.json +72 -0
  75. package/src/__tests__/browser-backend-dual-instance.test.ts +379 -0
  76. package/src/__tests__/browser-backend-focus-loss.test.ts +239 -0
  77. package/src/__tests__/browser-backend-lock-error.test.ts +425 -0
  78. package/src/__tests__/browser-backend-lock-gate.test.ts +283 -0
  79. package/src/__tests__/browser-backend-lock-release.test.ts +641 -0
  80. package/src/__tests__/browser-backend-lock-state.test.ts +604 -0
  81. package/src/__tests__/canvas-input-boundary.test.ts +65 -0
  82. package/src/__tests__/canvas-input-owner.test-d.ts +39 -0
  83. package/src/__tests__/composite-backend.test.ts +299 -0
  84. package/src/__tests__/controller-platform-owner.test-d.ts +46 -0
  85. package/src/__tests__/input-snapshot.test-d.ts +78 -0
  86. package/src/__tests__/input-snapshot.test.ts +96 -0
  87. package/src/__tests__/swipe-direction-owner.test-d.ts +45 -0
  88. package/src/__tests__/ui-input-matrix.test.ts +31 -0
  89. package/src/__tests__/ui-ownership-edge.test.ts +33 -0
  90. package/src/__tests__/ui-ownership.test.ts +35 -0
  91. package/src/__tests__/ui-reset-idempotence.test.ts +17 -0
  92. package/src/__tests__/ui-reset.test.ts +25 -0
  93. package/src/action-state.ts +380 -0
  94. package/src/browser-backend.ts +884 -0
  95. package/src/canvas-input-boundary.ts +81 -0
  96. package/src/composite-backend.ts +189 -0
  97. package/src/controller-db-data.ts +25 -0
  98. package/src/controller-db.ts +218 -0
  99. package/src/frame-start-scan-system.ts +93 -0
  100. package/src/gamepad-frame.ts +259 -0
  101. package/src/gesture-recognizer.ts +495 -0
  102. package/src/index.ts +101 -0
  103. package/src/input-snapshot.ts +732 -0
  104. package/src/plugin-service.ts +31 -0
  105. package/src/ui-ownership.ts +61 -0
  106. package/src/virtual-joystick.ts +103 -0
  107. package/vendor/gamecontrollerdb.txt +2114 -0
@@ -0,0 +1,31 @@
1
+ import type { Plugin } from '@forgeax/engine-plugin';
2
+
3
+ import type { InputBackend } from './input-snapshot';
4
+
5
+ declare module '@forgeax/engine-plugin' {
6
+ interface EngineContextServices {
7
+ input?: InputBackend;
8
+ }
9
+ }
10
+
11
+ export function inputBackendPlugin(backend: InputBackend): Plugin {
12
+ return {
13
+ name: 'input-backend',
14
+ provide: 'input',
15
+ apply(ctx) {
16
+ ctx.provide('input', backend);
17
+ },
18
+ };
19
+ }
20
+
21
+ /** Provide an App-acquired input backend and release its Host listeners with the Fiber. */
22
+ export function ownedInputBackendPlugin(backend: InputBackend, dispose: () => void): Plugin {
23
+ return {
24
+ name: 'input-backend',
25
+ provide: 'input',
26
+ apply(ctx) {
27
+ ctx.provide('input', backend);
28
+ ctx.effect(() => dispose, 'input/backend');
29
+ },
30
+ };
31
+ }
@@ -0,0 +1,61 @@
1
+ /** Event-path ownership and lifecycle reset primitives for browser hosts. */
2
+
3
+ export interface UiOwnershipResult {
4
+ readonly owned: boolean;
5
+ readonly source: 'composed-path' | 'target-fallback' | 'unknown';
6
+ }
7
+
8
+ export interface UiInputResetBoundaryOptions {
9
+ readonly clear: () => void;
10
+ readonly signal?: AbortSignal;
11
+ }
12
+
13
+ export interface UiInputResetBoundary {
14
+ readonly reset: () => void;
15
+ readonly dispose: () => void;
16
+ }
17
+
18
+ function contains(root: Node, value: EventTarget | null): boolean {
19
+ const NodeCtor = globalThis.Node;
20
+ return NodeCtor && value instanceof NodeCtor ? value === root || root.contains(value) : false;
21
+ }
22
+
23
+ export function resolveUiOwnership(event: Event, host: Node): UiOwnershipResult {
24
+ const path = event.composedPath;
25
+ if (typeof path === 'function') {
26
+ const composed = path.call(event);
27
+ return {
28
+ owned:
29
+ composed.some((item) => contains(host, item)) ||
30
+ (composed.length === 0 && contains(host, event.target)),
31
+ source: composed.length === 0 ? 'target-fallback' : 'composed-path',
32
+ };
33
+ }
34
+ if ('target' in event) {
35
+ return { owned: contains(host, event.target), source: 'target-fallback' };
36
+ }
37
+ return { owned: false, source: 'unknown' };
38
+ }
39
+
40
+ export function isUiOwnedEvent(event: Event, host: Node): boolean {
41
+ return resolveUiOwnership(event, host).owned;
42
+ }
43
+
44
+ export function createUiInputResetBoundary(
45
+ options: UiInputResetBoundaryOptions,
46
+ ): UiInputResetBoundary {
47
+ let disposed = false;
48
+ const reset = (): void => {
49
+ if (!disposed) options.clear();
50
+ };
51
+ const onAbort = (): void => reset();
52
+ options.signal?.addEventListener('abort', onAbort, { once: true });
53
+ return {
54
+ reset,
55
+ dispose: () => {
56
+ if (disposed) return;
57
+ disposed = true;
58
+ options.signal?.removeEventListener('abort', onAbort);
59
+ },
60
+ };
61
+ }
@@ -0,0 +1,103 @@
1
+ // virtual-joystick.ts -- pure-function virtual joystick derivation (M3).
2
+ //
3
+ // No DOM dependency; backend closure holds per-joystick bindState
4
+ // and calls deriveVirtualAxes each sample() frame.
5
+ //
6
+ // @internal -- only the browser backend calls these functions; the
7
+ // barrel does not re-export them.
8
+
9
+ import type { VirtualAxisSample, VirtualJoystickConfig } from './input-snapshot';
10
+
11
+ /**
12
+ * Per-joystick binding state. The backend closure owns a
13
+ * Map<string, BindState> keyed by joystick config name.
14
+ */
15
+ export interface BindState {
16
+ /** bound pointerId, or undefined if unbound */
17
+ pointerId?: number | undefined;
18
+ /** origin in canvas pixels (anchor for fixed, first-touch for floating) */
19
+ originX: number;
20
+ originY: number;
21
+ }
22
+
23
+ function computeVec(
24
+ ptrX: number,
25
+ ptrY: number,
26
+ originX: number,
27
+ originY: number,
28
+ radius: number,
29
+ deadzone: number,
30
+ ): { x: number; y: number } {
31
+ const rawX = (ptrX - originX) / radius;
32
+ const rawY = (ptrY - originY) / radius;
33
+ const mag = Math.sqrt(rawX * rawX + rawY * rawY);
34
+ if (mag < deadzone) return { x: 0, y: 0 };
35
+ if (mag > 1) {
36
+ // Clamp to unit vector: normalize direction, magnitude = 1.
37
+ return { x: rawX / mag, y: rawY / mag };
38
+ }
39
+ return { x: rawX, y: rawY };
40
+ }
41
+
42
+ /**
43
+ * Derive one frame of virtual axis outputs from live pointer state and
44
+ * per-joystick binding state.
45
+ *
46
+ * Pure function: no DOM access. The backend closure is the SSOT for
47
+ * per-joystick state; binding + origin selection happens in
48
+ * browser-backend.ts onPointerDown. This function only reads bindState
49
+ * and computes per-frame vectors.
50
+ *
51
+ * Unbound joysticks always produce the zero vector.
52
+ *
53
+ * @param configs - configured virtual joysticks
54
+ * @param pointerMap - live pointer positions keyed by pointerId
55
+ * @param bindState - per-joystick binding state (read-only)
56
+ * @returns one VirtualAxisSample per configured joystick
57
+ */
58
+ export function deriveVirtualAxes(
59
+ configs: readonly VirtualJoystickConfig[],
60
+ pointerMap: Map<number, { readonly x: number; readonly y: number }>,
61
+ bindState: Map<string, BindState>,
62
+ ): VirtualAxisSample[] {
63
+ const results: VirtualAxisSample[] = [];
64
+
65
+ for (const cfg of configs) {
66
+ const bs = bindState.get(cfg.name);
67
+
68
+ if (bs?.pointerId !== undefined) {
69
+ // Bound: compute vector from current pointer position.
70
+ const ptr = pointerMap.get(bs.pointerId);
71
+ if (ptr) {
72
+ const vec = computeVec(ptr.x, ptr.y, bs.originX, bs.originY, cfg.radius, cfg.deadzone);
73
+ results.push({ name: cfg.name, x: vec.x, y: vec.y });
74
+ } else {
75
+ // Pointer disappeared (up/cancel handled by backend unbind,
76
+ // but this guards against pointer removal without unbind).
77
+ bs.pointerId = undefined;
78
+ results.push({ name: cfg.name, x: 0, y: 0 });
79
+ }
80
+ } else {
81
+ // Unbound: zero vector. Binding is the backend's responsibility.
82
+ results.push({ name: cfg.name, x: 0, y: 0 });
83
+ }
84
+ }
85
+
86
+ return results;
87
+ }
88
+
89
+ /**
90
+ * Unbind a joystick when its bound pointer goes up or is cancelled.
91
+ * Sets pointerId to undefined; the next deriveVirtualAxes call will
92
+ * produce a zero vector for the unbound joystick.
93
+ */
94
+ export function handleVirtualJoystickUnbind(
95
+ bindState: Map<string, BindState>,
96
+ pointerId: number,
97
+ ): void {
98
+ for (const bs of bindState.values()) {
99
+ if (bs.pointerId === pointerId) {
100
+ bs.pointerId = undefined;
101
+ }
102
+ }
103
+ }