@kelnishi/satmouse-client 0.9.4 → 0.9.5

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.
@@ -119,6 +119,29 @@ type AxisMap = {
119
119
  rz: keyof Vec3;
120
120
  };
121
121
 
122
+ /** Input axis identifier */
123
+ type InputAxis = "tx" | "ty" | "tz" | "rx" | "ry" | "rz";
124
+ /** A single action binding — maps one input axis to a named output */
125
+ interface ActionBinding {
126
+ /** Which input axis drives this action */
127
+ source: InputAxis;
128
+ /** Scale multiplier (default: 1) */
129
+ scale?: number;
130
+ /** Invert the value (default: false) */
131
+ invert?: boolean;
132
+ }
133
+ /**
134
+ * ActionMap defines how raw 6DOF axes map to named output actions.
135
+ *
136
+ * Client apps declare the actions they support and how device axes
137
+ * feed into them. Users can reassign axes via the settings UI.
138
+ *
139
+ * Default: 6 actions matching the 6 input axes (passthrough).
140
+ */
141
+ type ActionMap = Record<string, ActionBinding>;
142
+ /** Result of applying an ActionMap to spatial data */
143
+ type ActionValues = Record<string, number>;
144
+
122
145
  /** Per-device transform overrides. Any field left undefined inherits from global defaults. */
123
146
  interface DeviceConfig {
124
147
  sensitivity?: Partial<SensitivityConfig>;
@@ -126,6 +149,7 @@ interface DeviceConfig {
126
149
  deadZone?: number;
127
150
  dominant?: boolean;
128
151
  axisRemap?: Partial<AxisMap>;
152
+ actionMap?: ActionMap;
129
153
  lockPosition?: boolean;
130
154
  lockRotation?: boolean;
131
155
  }
@@ -138,6 +162,8 @@ interface InputConfig {
138
162
  axisRemap: AxisMap;
139
163
  lockPosition: boolean;
140
164
  lockRotation: boolean;
165
+ /** Action map — maps input axes to named output actions. Default: passthrough. */
166
+ actionMap: ActionMap;
141
167
  /**
142
168
  * Per-device overrides, keyed by device ID (e.g., "spacemouse-c635")
143
169
  * or device family pattern (e.g., "spacemouse-*", "hid-054c-*").
@@ -152,8 +178,10 @@ interface StorageAdapter {
152
178
  }
153
179
 
154
180
  interface InputManagerEvents {
155
- /** Processed spatial data (after all transforms) */
181
+ /** Processed spatial data (after all transforms + action map) */
156
182
  spatialData: (data: SpatialData) => void;
183
+ /** Named action values from the action map */
184
+ actionValues: (values: ActionValues) => void;
157
185
  /** Raw spatial data (before transforms) */
158
186
  rawSpatialData: (data: SpatialData) => void;
159
187
  /** Button event (pass-through from connection) */
@@ -209,6 +237,8 @@ declare class InputManager extends TypedEmitter<InputManagerEvents> {
209
237
  onSpatialData(callback: (data: SpatialData) => void): () => void;
210
238
  /** Register a callback for button events. Returns unsubscribe function. */
211
239
  onButtonEvent(callback: (data: ButtonEvent) => void): () => void;
240
+ /** Register a callback for action values. Returns unsubscribe function. */
241
+ onActionValues(callback: (values: ActionValues) => void): () => void;
212
242
  private wireConnection;
213
243
  private processSpatialData;
214
244
  }
@@ -119,6 +119,29 @@ type AxisMap = {
119
119
  rz: keyof Vec3;
120
120
  };
121
121
 
122
+ /** Input axis identifier */
123
+ type InputAxis = "tx" | "ty" | "tz" | "rx" | "ry" | "rz";
124
+ /** A single action binding — maps one input axis to a named output */
125
+ interface ActionBinding {
126
+ /** Which input axis drives this action */
127
+ source: InputAxis;
128
+ /** Scale multiplier (default: 1) */
129
+ scale?: number;
130
+ /** Invert the value (default: false) */
131
+ invert?: boolean;
132
+ }
133
+ /**
134
+ * ActionMap defines how raw 6DOF axes map to named output actions.
135
+ *
136
+ * Client apps declare the actions they support and how device axes
137
+ * feed into them. Users can reassign axes via the settings UI.
138
+ *
139
+ * Default: 6 actions matching the 6 input axes (passthrough).
140
+ */
141
+ type ActionMap = Record<string, ActionBinding>;
142
+ /** Result of applying an ActionMap to spatial data */
143
+ type ActionValues = Record<string, number>;
144
+
122
145
  /** Per-device transform overrides. Any field left undefined inherits from global defaults. */
123
146
  interface DeviceConfig {
124
147
  sensitivity?: Partial<SensitivityConfig>;
@@ -126,6 +149,7 @@ interface DeviceConfig {
126
149
  deadZone?: number;
127
150
  dominant?: boolean;
128
151
  axisRemap?: Partial<AxisMap>;
152
+ actionMap?: ActionMap;
129
153
  lockPosition?: boolean;
130
154
  lockRotation?: boolean;
131
155
  }
@@ -138,6 +162,8 @@ interface InputConfig {
138
162
  axisRemap: AxisMap;
139
163
  lockPosition: boolean;
140
164
  lockRotation: boolean;
165
+ /** Action map — maps input axes to named output actions. Default: passthrough. */
166
+ actionMap: ActionMap;
141
167
  /**
142
168
  * Per-device overrides, keyed by device ID (e.g., "spacemouse-c635")
143
169
  * or device family pattern (e.g., "spacemouse-*", "hid-054c-*").
@@ -152,8 +178,10 @@ interface StorageAdapter {
152
178
  }
153
179
 
154
180
  interface InputManagerEvents {
155
- /** Processed spatial data (after all transforms) */
181
+ /** Processed spatial data (after all transforms + action map) */
156
182
  spatialData: (data: SpatialData) => void;
183
+ /** Named action values from the action map */
184
+ actionValues: (values: ActionValues) => void;
157
185
  /** Raw spatial data (before transforms) */
158
186
  rawSpatialData: (data: SpatialData) => void;
159
187
  /** Button event (pass-through from connection) */
@@ -209,6 +237,8 @@ declare class InputManager extends TypedEmitter<InputManagerEvents> {
209
237
  onSpatialData(callback: (data: SpatialData) => void): () => void;
210
238
  /** Register a callback for button events. Returns unsubscribe function. */
211
239
  onButtonEvent(callback: (data: ButtonEvent) => void): () => void;
240
+ /** Register a callback for action values. Returns unsubscribe function. */
241
+ onActionValues(callback: (values: ActionValues) => void): () => void;
212
242
  private wireConnection;
213
243
  private processSpatialData;
214
244
  }
@@ -425,6 +425,57 @@ function launchSatMouse(options) {
425
425
  });
426
426
  }
427
427
 
428
+ // src/utils/action-map.ts
429
+ var DEFAULT_ACTION_MAP = {
430
+ tx: { source: "tx" },
431
+ ty: { source: "ty" },
432
+ tz: { source: "tz" },
433
+ rx: { source: "rx" },
434
+ ry: { source: "ry" },
435
+ rz: { source: "rz" }
436
+ };
437
+ function readAxis(data, axis) {
438
+ switch (axis) {
439
+ case "tx":
440
+ return data.translation.x;
441
+ case "ty":
442
+ return data.translation.y;
443
+ case "tz":
444
+ return data.translation.z;
445
+ case "rx":
446
+ return data.rotation.x;
447
+ case "ry":
448
+ return data.rotation.y;
449
+ case "rz":
450
+ return data.rotation.z;
451
+ }
452
+ }
453
+ function applyActionMap(data, map) {
454
+ const result = {};
455
+ for (const [action, binding] of Object.entries(map)) {
456
+ let value = readAxis(data, binding.source);
457
+ if (binding.invert) value = -value;
458
+ value *= binding.scale ?? 1;
459
+ result[action] = value;
460
+ }
461
+ return result;
462
+ }
463
+ function actionValuesToSpatialData(values, timestamp) {
464
+ return {
465
+ translation: {
466
+ x: values.tx ?? 0,
467
+ y: values.ty ?? 0,
468
+ z: values.tz ?? 0
469
+ },
470
+ rotation: {
471
+ x: values.rx ?? 0,
472
+ y: values.ry ?? 0,
473
+ z: values.rz ?? 0
474
+ },
475
+ timestamp
476
+ };
477
+ }
478
+
428
479
  // src/utils/config.ts
429
480
  var DEFAULT_CONFIG = {
430
481
  sensitivity: { translation: 1e-3, rotation: 1e-3 },
@@ -434,6 +485,7 @@ var DEFAULT_CONFIG = {
434
485
  axisRemap: { tx: "x", ty: "y", tz: "z", rx: "x", ry: "y", rz: "z" },
435
486
  lockPosition: false,
436
487
  lockRotation: false,
488
+ actionMap: { ...DEFAULT_ACTION_MAP },
437
489
  devices: {}
438
490
  };
439
491
  function mergeConfig(base, partial) {
@@ -443,6 +495,7 @@ function mergeConfig(base, partial) {
443
495
  sensitivity: { ...base.sensitivity, ...partial.sensitivity },
444
496
  flip: { ...base.flip, ...partial.flip },
445
497
  axisRemap: { ...base.axisRemap, ...partial.axisRemap },
498
+ actionMap: partial.actionMap ? { ...base.actionMap, ...partial.actionMap } : { ...base.actionMap },
446
499
  devices: { ...base.devices }
447
500
  };
448
501
  if (partial.devices) {
@@ -482,6 +535,7 @@ function resolveDeviceConfig(config, deviceId) {
482
535
  deadZone: deviceOverride.deadZone ?? config.deadZone,
483
536
  dominant: deviceOverride.dominant ?? config.dominant,
484
537
  axisRemap: { ...config.axisRemap, ...deviceOverride.axisRemap },
538
+ actionMap: deviceOverride.actionMap ? { ...config.actionMap, ...deviceOverride.actionMap } : config.actionMap,
485
539
  lockPosition: deviceOverride.lockPosition ?? config.lockPosition,
486
540
  lockRotation: deviceOverride.lockRotation ?? config.lockRotation
487
541
  };
@@ -648,6 +702,7 @@ var InputManager = class extends TypedEmitter {
648
702
  deadZone: resolved.deadZone,
649
703
  dominant: resolved.dominant,
650
704
  axisRemap: resolved.axisRemap,
705
+ actionMap: resolved.actionMap,
651
706
  lockPosition: resolved.lockPosition,
652
707
  lockRotation: resolved.lockRotation
653
708
  };
@@ -677,11 +732,17 @@ var InputManager = class extends TypedEmitter {
677
732
  this.on("buttonEvent", callback);
678
733
  return () => this.off("buttonEvent", callback);
679
734
  }
735
+ /** Register a callback for action values. Returns unsubscribe function. */
736
+ onActionValues(callback) {
737
+ this.on("actionValues", callback);
738
+ return () => this.off("actionValues", callback);
739
+ }
680
740
  wireConnection(connection) {
681
741
  connection.on("spatialData", (raw) => {
682
742
  this.emit("rawSpatialData", raw);
683
- const processed = this.processSpatialData(raw);
684
- if (processed) this.emit("spatialData", processed);
743
+ const { spatial, actions } = this.processSpatialData(raw);
744
+ if (spatial) this.emit("spatialData", spatial);
745
+ if (actions) this.emit("actionValues", actions);
685
746
  });
686
747
  connection.on("buttonEvent", (event) => this.emit("buttonEvent", event));
687
748
  connection.on("stateChange", (state, proto) => this.emit("stateChange", state, proto));
@@ -705,7 +766,9 @@ var InputManager = class extends TypedEmitter {
705
766
  if (cfg.lockRotation) {
706
767
  data = { ...data, rotation: { x: 0, y: 0, z: 0 } };
707
768
  }
708
- return data;
769
+ const actions = applyActionMap(data, cfg.actionMap);
770
+ const spatial = actionValuesToSpatialData(actions, data.timestamp);
771
+ return { spatial, actions };
709
772
  }
710
773
  };
711
774
  var SatMouseContext = createContext(null);