@kelnishi/satmouse-client 0.9.9 → 0.9.13

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.
@@ -1,79 +1,22 @@
1
1
  'use strict';
2
2
 
3
3
  var chunkI5MEZZOT_cjs = require('../chunk-I5MEZZOT.cjs');
4
-
5
- // src/utils/action-map.ts
6
- var DEFAULT_ACTION_MAP = {
7
- tx: { source: "tx" },
8
- ty: { source: "ty" },
9
- tz: { source: "tz" },
10
- rx: { source: "rx" },
11
- ry: { source: "ry" },
12
- rz: { source: "rz" }
13
- };
14
- function readAxis(data, axis) {
15
- switch (axis) {
16
- case "tx":
17
- return data.translation.x;
18
- case "ty":
19
- return data.translation.y;
20
- case "tz":
21
- return data.translation.z;
22
- case "rx":
23
- return data.rotation.x;
24
- case "ry":
25
- return data.rotation.y;
26
- case "rz":
27
- return data.rotation.z;
28
- }
29
- }
30
- function applyActionMap(data, map) {
31
- const result = {};
32
- for (const [action, binding] of Object.entries(map)) {
33
- let value = readAxis(data, binding.source);
34
- if (binding.invert) value = -value;
35
- value *= binding.scale ?? 1;
36
- result[action] = value;
37
- }
38
- return result;
39
- }
40
- function actionValuesToSpatialData(values, timestamp) {
41
- return {
42
- translation: {
43
- x: values.tx ?? 0,
44
- y: values.ty ?? 0,
45
- z: values.tz ?? 0
46
- },
47
- rotation: {
48
- x: values.rx ?? 0,
49
- y: values.ry ?? 0,
50
- z: values.rz ?? 0
51
- },
52
- timestamp
53
- };
54
- }
55
- function swapActions(map, actionA, actionB) {
56
- const result = { ...map };
57
- const a = result[actionA];
58
- const b = result[actionB];
59
- if (a && b) {
60
- result[actionA] = b;
61
- result[actionB] = a;
62
- }
63
- return result;
64
- }
4
+ var chunkLDSDVYCV_cjs = require('../chunk-LDSDVYCV.cjs');
65
5
 
66
6
  // src/utils/config.ts
67
7
  var DEFAULT_CONFIG = {
68
8
  sensitivity: { translation: 1e-3, rotation: 1e-3 },
69
- flip: { tx: false, ty: true, tz: true, rx: false, ry: true, rz: true },
9
+ flip: { tx: false, ty: false, tz: false, rx: false, ry: false, rz: false },
70
10
  deadZone: 0,
71
11
  dominant: false,
72
12
  axisRemap: { tx: "x", ty: "y", tz: "z", rx: "x", ry: "y", rz: "z" },
73
13
  lockPosition: false,
74
14
  lockRotation: false,
75
- actionMap: { ...DEFAULT_ACTION_MAP },
76
- devices: {}
15
+ actionMap: { ...chunkLDSDVYCV_cjs.DEFAULT_ACTION_MAP },
16
+ devices: {
17
+ // SpaceMouse Z-up → Three.js Y-up axis correction
18
+ "cnx-*": { flip: { ty: true, tz: true, ry: true, rz: true } }
19
+ }
77
20
  };
78
21
  function mergeConfig(base, partial) {
79
22
  const merged = {
@@ -245,6 +188,10 @@ var InputManager = class extends chunkI5MEZZOT_cjs.TypedEmitter {
245
188
  connections = [];
246
189
  storage;
247
190
  knownDevices = /* @__PURE__ */ new Map();
191
+ // Per-device accumulators: latest value from each device per frame tick
192
+ deviceAccumulators = /* @__PURE__ */ new Map();
193
+ accDirty = false;
194
+ flushTimer = null;
248
195
  _config;
249
196
  get config() {
250
197
  return this._config;
@@ -254,6 +201,7 @@ var InputManager = class extends chunkI5MEZZOT_cjs.TypedEmitter {
254
201
  this.storage = storage;
255
202
  const persisted = loadSettings(storage);
256
203
  this._config = mergeConfig(DEFAULT_CONFIG, { ...config, ...persisted });
204
+ this.flushTimer = setInterval(() => this.flushAccumulator(), 16);
257
205
  }
258
206
  /** Add a connection to the managed set */
259
207
  addConnection(connection) {
@@ -273,6 +221,10 @@ var InputManager = class extends chunkI5MEZZOT_cjs.TypedEmitter {
273
221
  /** Disconnect all managed connections */
274
222
  disconnect() {
275
223
  for (const c of this.connections) c.disconnect();
224
+ if (this.flushTimer) {
225
+ clearInterval(this.flushTimer);
226
+ this.flushTimer = null;
227
+ }
276
228
  }
277
229
  /** Fetch device info from all connections */
278
230
  async fetchDeviceInfo() {
@@ -335,9 +287,17 @@ var InputManager = class extends chunkI5MEZZOT_cjs.TypedEmitter {
335
287
  wireConnection(connection) {
336
288
  connection.on("spatialData", (raw) => {
337
289
  this.emit("rawSpatialData", raw);
338
- const { spatial, actions } = this.processSpatialData(raw);
339
- if (spatial) this.emit("spatialData", spatial);
340
- if (actions) this.emit("actionValues", actions);
290
+ const id = raw.deviceId ?? "_default";
291
+ const processed = this.processPerDevice(raw, id);
292
+ this.deviceAccumulators.set(id, {
293
+ tx: processed.translation.x,
294
+ ty: processed.translation.y,
295
+ tz: processed.translation.z,
296
+ rx: processed.rotation.x,
297
+ ry: processed.rotation.y,
298
+ rz: processed.rotation.z
299
+ });
300
+ this.accDirty = true;
341
301
  });
342
302
  connection.on("buttonEvent", (event) => this.emit("buttonEvent", event));
343
303
  connection.on("stateChange", (state, proto) => this.emit("stateChange", state, proto));
@@ -347,32 +307,73 @@ var InputManager = class extends chunkI5MEZZOT_cjs.TypedEmitter {
347
307
  this.emit("deviceStatus", event, device);
348
308
  });
349
309
  }
350
- processSpatialData(raw) {
351
- const cfg = this._config;
310
+ flushAccumulator() {
311
+ if (!this.accDirty) return;
312
+ const merged = { tx: 0, ty: 0, tz: 0, rx: 0, ry: 0, rz: 0 };
313
+ for (const acc of this.deviceAccumulators.values()) {
314
+ merged.tx += acc.tx;
315
+ merged.ty += acc.ty;
316
+ merged.tz += acc.tz;
317
+ merged.rx += acc.rx;
318
+ merged.ry += acc.ry;
319
+ merged.rz += acc.rz;
320
+ }
321
+ this.deviceAccumulators.clear();
322
+ this.accDirty = false;
323
+ const data = {
324
+ translation: { x: merged.tx, y: merged.ty, z: merged.tz },
325
+ rotation: { x: merged.rx, y: merged.ry, z: merged.rz },
326
+ timestamp: performance.now() * 1e3
327
+ };
328
+ const { spatial, actions } = this.applyGlobalTransforms(data);
329
+ if (spatial) this.emit("spatialData", spatial);
330
+ if (actions) this.emit("actionValues", actions);
331
+ }
332
+ /** Per-device transforms: flip, sensitivity, dead zone, dominant, axis remap */
333
+ processPerDevice(raw, deviceId) {
334
+ const cfg = resolveDeviceConfig(this._config, deviceId);
352
335
  let data = raw;
353
336
  if (cfg.deadZone > 0) data = applyDeadZone(data, cfg.deadZone);
354
337
  if (cfg.dominant) data = applyDominant(data);
355
338
  data = applyFlip(data, cfg.flip);
356
339
  data = applyAxisRemap(data, cfg.axisRemap);
357
340
  data = applySensitivity(data, cfg.sensitivity);
341
+ return data;
342
+ }
343
+ /** Global transforms applied after per-device merge: locks + action map */
344
+ applyGlobalTransforms(data) {
345
+ const cfg = this._config;
358
346
  if (cfg.lockPosition) {
359
347
  data = { ...data, translation: { x: 0, y: 0, z: 0 } };
360
348
  }
361
349
  if (cfg.lockRotation) {
362
350
  data = { ...data, rotation: { x: 0, y: 0, z: 0 } };
363
351
  }
364
- const actions = applyActionMap(data, cfg.actionMap);
365
- const spatial = actionValuesToSpatialData(actions, data.timestamp);
352
+ const actions = chunkLDSDVYCV_cjs.applyActionMap(data, cfg.actionMap);
353
+ const spatial = chunkLDSDVYCV_cjs.actionValuesToSpatialData(actions, data.timestamp);
366
354
  return { spatial, actions };
367
355
  }
368
356
  };
369
357
 
370
- exports.DEFAULT_ACTION_MAP = DEFAULT_ACTION_MAP;
358
+ Object.defineProperty(exports, "DEFAULT_ACTION_MAP", {
359
+ enumerable: true,
360
+ get: function () { return chunkLDSDVYCV_cjs.DEFAULT_ACTION_MAP; }
361
+ });
362
+ Object.defineProperty(exports, "actionValuesToSpatialData", {
363
+ enumerable: true,
364
+ get: function () { return chunkLDSDVYCV_cjs.actionValuesToSpatialData; }
365
+ });
366
+ Object.defineProperty(exports, "applyActionMap", {
367
+ enumerable: true,
368
+ get: function () { return chunkLDSDVYCV_cjs.applyActionMap; }
369
+ });
370
+ Object.defineProperty(exports, "swapActions", {
371
+ enumerable: true,
372
+ get: function () { return chunkLDSDVYCV_cjs.swapActions; }
373
+ });
371
374
  exports.DEFAULT_AXIS_MAP = DEFAULT_AXIS_MAP;
372
375
  exports.DEFAULT_CONFIG = DEFAULT_CONFIG;
373
376
  exports.InputManager = InputManager;
374
- exports.actionValuesToSpatialData = actionValuesToSpatialData;
375
- exports.applyActionMap = applyActionMap;
376
377
  exports.applyAxisRemap = applyAxisRemap;
377
378
  exports.applyDeadZone = applyDeadZone;
378
379
  exports.applyDominant = applyDominant;
@@ -382,6 +383,5 @@ exports.loadSettings = loadSettings;
382
383
  exports.mergeConfig = mergeConfig;
383
384
  exports.resolveDeviceConfig = resolveDeviceConfig;
384
385
  exports.saveSettings = saveSettings;
385
- exports.swapActions = swapActions;
386
386
  //# sourceMappingURL=index.cjs.map
387
387
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/action-map.ts","../../src/utils/config.ts","../../src/utils/persistence.ts","../../src/utils/transforms.ts","../../src/utils/input-manager.ts"],"names":["TypedEmitter"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { SpatialData } from \"../core/types.js\";\n\n/** Input axis identifier */\nexport type InputAxis = \"tx\" | \"ty\" | \"tz\" | \"rx\" | \"ry\" | \"rz\";\n\n/** A single action binding — maps one input axis to a named output */\nexport interface ActionBinding {\n /** Which input axis drives this action */\n source: InputAxis;\n /** Scale multiplier (default: 1) */\n scale?: number;\n /** Invert the value (default: false) */\n invert?: boolean;\n}\n\n/**\n * ActionMap defines how raw 6DOF axes map to named output actions.\n *\n * Client apps declare the actions they support and how device axes\n * feed into them. Users can reassign axes via the settings UI.\n *\n * Default: 6 actions matching the 6 input axes (passthrough).\n */\nexport type ActionMap = Record<string, ActionBinding>;\n\n/** Default passthrough — each axis maps to itself */\nexport const DEFAULT_ACTION_MAP: ActionMap = {\n tx: { source: \"tx\" },\n ty: { source: \"ty\" },\n tz: { source: \"tz\" },\n rx: { source: \"rx\" },\n ry: { source: \"ry\" },\n rz: { source: \"rz\" },\n};\n\n/** Result of applying an ActionMap to spatial data */\nexport type ActionValues = Record<string, number>;\n\n/** Read a raw axis value from SpatialData */\nfunction readAxis(data: SpatialData, axis: InputAxis): number {\n switch (axis) {\n case \"tx\": return data.translation.x;\n case \"ty\": return data.translation.y;\n case \"tz\": return data.translation.z;\n case \"rx\": return data.rotation.x;\n case \"ry\": return data.rotation.y;\n case \"rz\": return data.rotation.z;\n }\n}\n\n/** Apply an ActionMap to SpatialData, producing named action values */\nexport function applyActionMap(data: SpatialData, map: ActionMap): ActionValues {\n const result: ActionValues = {};\n for (const [action, binding] of Object.entries(map)) {\n let value = readAxis(data, binding.source);\n if (binding.invert) value = -value;\n value *= binding.scale ?? 1;\n result[action] = value;\n }\n return result;\n}\n\n/**\n * Convert ActionValues back to SpatialData.\n * Only populates tx/ty/tz/rx/ry/rz keys if they exist as actions.\n */\nexport function actionValuesToSpatialData(values: ActionValues, timestamp: number): SpatialData {\n return {\n translation: {\n x: values.tx ?? 0,\n y: values.ty ?? 0,\n z: values.tz ?? 0,\n },\n rotation: {\n x: values.rx ?? 0,\n y: values.ry ?? 0,\n z: values.rz ?? 0,\n },\n timestamp,\n };\n}\n\n/** Swap two action bindings */\nexport function swapActions(map: ActionMap, actionA: string, actionB: string): ActionMap {\n const result = { ...map };\n const a = result[actionA];\n const b = result[actionB];\n if (a && b) {\n result[actionA] = b;\n result[actionB] = a;\n }\n return result;\n}\n","import type { FlipConfig, SensitivityConfig, AxisMap } from \"./transforms.js\";\nimport { DEFAULT_ACTION_MAP, type ActionMap } from \"./action-map.js\";\n\n/** Per-device transform overrides. Any field left undefined inherits from global defaults. */\nexport interface DeviceConfig {\n sensitivity?: Partial<SensitivityConfig>;\n flip?: Partial<FlipConfig>;\n deadZone?: number;\n dominant?: boolean;\n axisRemap?: Partial<AxisMap>;\n actionMap?: ActionMap;\n lockPosition?: boolean;\n lockRotation?: boolean;\n}\n\nexport interface InputConfig {\n /** Global defaults applied to all devices */\n sensitivity: SensitivityConfig;\n flip: FlipConfig;\n deadZone: number;\n dominant: boolean;\n axisRemap: AxisMap;\n lockPosition: boolean;\n lockRotation: boolean;\n\n /** Action map — maps input axes to named output actions. Default: passthrough. */\n actionMap: ActionMap;\n\n /**\n * Per-device overrides, keyed by device ID (e.g., \"spacemouse-c635\")\n * or device family pattern (e.g., \"spacemouse-*\", \"hid-054c-*\").\n * Values override global defaults for matching devices.\n */\n devices: Record<string, DeviceConfig>;\n}\n\nexport const DEFAULT_CONFIG: InputConfig = {\n sensitivity: { translation: 0.001, rotation: 0.001 },\n flip: { tx: false, ty: true, tz: true, rx: false, ry: true, rz: true },\n deadZone: 0,\n dominant: false,\n axisRemap: { tx: \"x\", ty: \"y\", tz: \"z\", rx: \"x\", ry: \"y\", rz: \"z\" },\n lockPosition: false,\n lockRotation: false,\n actionMap: { ...DEFAULT_ACTION_MAP },\n devices: {},\n};\n\nexport function mergeConfig(base: InputConfig, partial: Partial<InputConfig>): InputConfig {\n const merged = {\n ...base,\n ...partial,\n sensitivity: { ...base.sensitivity, ...partial.sensitivity },\n flip: { ...base.flip, ...partial.flip },\n axisRemap: { ...base.axisRemap, ...partial.axisRemap },\n actionMap: partial.actionMap ? { ...base.actionMap, ...partial.actionMap } : { ...base.actionMap },\n devices: { ...base.devices },\n };\n\n // Merge per-device configs\n if (partial.devices) {\n for (const [key, devCfg] of Object.entries(partial.devices)) {\n merged.devices[key] = mergeDeviceConfig(merged.devices[key], devCfg);\n }\n }\n\n return merged;\n}\n\nexport function mergeDeviceConfig(base: DeviceConfig | undefined, partial: DeviceConfig): DeviceConfig {\n if (!base) return partial;\n return {\n ...base,\n ...partial,\n sensitivity: partial.sensitivity ? { ...base.sensitivity, ...partial.sensitivity } : base.sensitivity,\n flip: partial.flip ? { ...base.flip, ...partial.flip } : base.flip,\n axisRemap: partial.axisRemap ? { ...base.axisRemap, ...partial.axisRemap } : base.axisRemap,\n };\n}\n\n/** Resolve the effective config for a specific device by merging global + device overrides */\nexport function resolveDeviceConfig(config: InputConfig, deviceId: string): InputConfig {\n // Find matching device config: exact match first, then pattern match\n let deviceOverride: DeviceConfig | undefined;\n\n if (config.devices[deviceId]) {\n deviceOverride = config.devices[deviceId];\n } else {\n // Try pattern match (e.g., \"spacemouse-*\" matches \"spacemouse-c635\")\n for (const [pattern, cfg] of Object.entries(config.devices)) {\n if (pattern.endsWith(\"*\") && deviceId.startsWith(pattern.slice(0, -1))) {\n deviceOverride = cfg;\n break;\n }\n }\n }\n\n if (!deviceOverride) return config;\n\n return {\n ...config,\n sensitivity: { ...config.sensitivity, ...deviceOverride.sensitivity },\n flip: { ...config.flip, ...deviceOverride.flip },\n deadZone: deviceOverride.deadZone ?? config.deadZone,\n dominant: deviceOverride.dominant ?? config.dominant,\n axisRemap: { ...config.axisRemap, ...deviceOverride.axisRemap },\n actionMap: deviceOverride.actionMap ? { ...config.actionMap, ...deviceOverride.actionMap } : config.actionMap,\n lockPosition: deviceOverride.lockPosition ?? config.lockPosition,\n lockRotation: deviceOverride.lockRotation ?? config.lockRotation,\n };\n}\n","import type { InputConfig } from \"./config.js\";\n\nexport interface StorageAdapter {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n}\n\nconst STORAGE_KEY = \"satmouse:settings\";\n\nfunction getStorage(storage?: StorageAdapter): StorageAdapter | null {\n if (storage) return storage;\n try {\n return globalThis.localStorage ?? null;\n } catch {\n return null;\n }\n}\n\nexport function saveSettings(config: InputConfig, storage?: StorageAdapter): void {\n const s = getStorage(storage);\n if (!s) return;\n s.setItem(STORAGE_KEY, JSON.stringify(config));\n}\n\nexport function loadSettings(storage?: StorageAdapter): Partial<InputConfig> | null {\n const s = getStorage(storage);\n if (!s) return null;\n const raw = s.getItem(STORAGE_KEY);\n if (!raw) return null;\n try {\n return JSON.parse(raw) as Partial<InputConfig>;\n } catch {\n return null;\n }\n}\n","import type { SpatialData, Vec3 } from \"../core/types.js\";\n\nexport interface FlipConfig {\n tx: boolean;\n ty: boolean;\n tz: boolean;\n rx: boolean;\n ry: boolean;\n rz: boolean;\n}\n\nexport interface SensitivityConfig {\n translation: number;\n rotation: number;\n}\n\n/** Maps each input axis to an output axis. E.g., { tx: \"tz\", tz: \"tx\" } swaps X and Z translation. */\nexport type AxisMap = {\n tx: keyof Vec3;\n ty: keyof Vec3;\n tz: keyof Vec3;\n rx: keyof Vec3;\n ry: keyof Vec3;\n rz: keyof Vec3;\n};\n\nexport const DEFAULT_AXIS_MAP: AxisMap = {\n tx: \"x\", ty: \"y\", tz: \"z\",\n rx: \"x\", ry: \"y\", rz: \"z\",\n};\n\nexport function applyFlip(data: SpatialData, flip: FlipConfig): SpatialData {\n return {\n ...data,\n translation: {\n x: flip.tx ? -data.translation.x : data.translation.x,\n y: flip.ty ? -data.translation.y : data.translation.y,\n z: flip.tz ? -data.translation.z : data.translation.z,\n },\n rotation: {\n x: flip.rx ? -data.rotation.x : data.rotation.x,\n y: flip.ry ? -data.rotation.y : data.rotation.y,\n z: flip.rz ? -data.rotation.z : data.rotation.z,\n },\n };\n}\n\nexport function applySensitivity(data: SpatialData, sens: SensitivityConfig): SpatialData {\n return {\n ...data,\n translation: {\n x: data.translation.x * sens.translation,\n y: data.translation.y * sens.translation,\n z: data.translation.z * sens.translation,\n },\n rotation: {\n x: data.rotation.x * sens.rotation,\n y: data.rotation.y * sens.rotation,\n z: data.rotation.z * sens.rotation,\n },\n };\n}\n\nexport function applyDominant(data: SpatialData): SpatialData {\n const axes = [\n { group: \"t\" as const, key: \"x\" as const, v: Math.abs(data.translation.x) },\n { group: \"t\" as const, key: \"y\" as const, v: Math.abs(data.translation.y) },\n { group: \"t\" as const, key: \"z\" as const, v: Math.abs(data.translation.z) },\n { group: \"r\" as const, key: \"x\" as const, v: Math.abs(data.rotation.x) },\n { group: \"r\" as const, key: \"y\" as const, v: Math.abs(data.rotation.y) },\n { group: \"r\" as const, key: \"z\" as const, v: Math.abs(data.rotation.z) },\n ];\n const max = axes.reduce((a, b) => (b.v > a.v ? b : a));\n\n const t: Vec3 = { x: 0, y: 0, z: 0 };\n const r: Vec3 = { x: 0, y: 0, z: 0 };\n\n if (max.group === \"t\") t[max.key] = data.translation[max.key];\n else r[max.key] = data.rotation[max.key];\n\n return { ...data, translation: t, rotation: r };\n}\n\nexport function applyDeadZone(data: SpatialData, threshold: number): SpatialData {\n const dz = (v: number) => (Math.abs(v) < threshold ? 0 : v);\n return {\n ...data,\n translation: { x: dz(data.translation.x), y: dz(data.translation.y), z: dz(data.translation.z) },\n rotation: { x: dz(data.rotation.x), y: dz(data.rotation.y), z: dz(data.rotation.z) },\n };\n}\n\nexport function applyAxisRemap(data: SpatialData, map: AxisMap): SpatialData {\n return {\n ...data,\n translation: {\n x: 0, y: 0, z: 0,\n [map.tx]: data.translation.x,\n [map.ty]: data.translation.y,\n [map.tz]: data.translation.z,\n } as Vec3,\n rotation: {\n x: 0, y: 0, z: 0,\n [map.rx]: data.rotation.x,\n [map.ry]: data.rotation.y,\n [map.rz]: data.rotation.z,\n } as Vec3,\n };\n}\n","import { TypedEmitter } from \"../core/emitter.js\";\nimport type { SatMouseConnection } from \"../core/connection.js\";\nimport type { SpatialData, ButtonEvent, DeviceInfo, ConnectionState, TransportProtocol } from \"../core/types.js\";\nimport type { InputConfig, DeviceConfig } from \"./config.js\";\nimport { DEFAULT_CONFIG, mergeConfig, resolveDeviceConfig } from \"./config.js\";\nimport { loadSettings, saveSettings, type StorageAdapter } from \"./persistence.js\";\nimport {\n applyFlip,\n applySensitivity,\n applyDominant,\n applyDeadZone,\n applyAxisRemap,\n} from \"./transforms.js\";\nimport { applyActionMap, actionValuesToSpatialData, type ActionValues } from \"./action-map.js\";\n\nexport interface InputManagerEvents {\n /** Processed spatial data (after all transforms + action map) */\n spatialData: (data: SpatialData) => void;\n /** Named action values from the action map */\n actionValues: (values: ActionValues) => void;\n /** Raw spatial data (before transforms) */\n rawSpatialData: (data: SpatialData) => void;\n /** Button event (pass-through from connection) */\n buttonEvent: (data: ButtonEvent) => void;\n /** Connection state changed */\n stateChange: (state: ConnectionState, protocol: TransportProtocol) => void;\n /** Device connected/disconnected */\n deviceStatus: (event: \"connected\" | \"disconnected\", device: DeviceInfo) => void;\n /** Configuration changed */\n configChange: (config: InputConfig) => void;\n}\n\n/** A connected device paired with its resolved configuration */\nexport interface DeviceWithConfig {\n device: DeviceInfo;\n config: DeviceConfig;\n}\n\n/**\n * Unified device service that wraps one or more SatMouseConnections\n * and provides a single processed event stream.\n *\n * Applies a configurable transform pipeline per-device:\n * deadZone → dominant → flip → axisRemap → sensitivity → lock\n *\n * Per-device overrides are resolved from InputConfig.devices using\n * device ID matching (exact or pattern with wildcard \"*\").\n */\nexport class InputManager extends TypedEmitter<InputManagerEvents> {\n private connections: SatMouseConnection[] = [];\n private storage?: StorageAdapter;\n private knownDevices = new Map<string, DeviceInfo>();\n\n private _config: InputConfig;\n\n get config(): InputConfig {\n return this._config;\n }\n\n constructor(config?: Partial<InputConfig>, storage?: StorageAdapter) {\n super();\n this.storage = storage;\n const persisted = loadSettings(storage);\n this._config = mergeConfig(DEFAULT_CONFIG, { ...config, ...persisted });\n }\n\n /** Add a connection to the managed set */\n addConnection(connection: SatMouseConnection): void {\n this.connections.push(connection);\n this.wireConnection(connection);\n }\n\n /** Remove a connection */\n removeConnection(connection: SatMouseConnection): void {\n const idx = this.connections.indexOf(connection);\n if (idx !== -1) this.connections.splice(idx, 1);\n connection.removeAllListeners();\n }\n\n /** Connect all managed connections */\n async connect(): Promise<void> {\n await Promise.all(this.connections.map((c) => c.connect()));\n }\n\n /** Disconnect all managed connections */\n disconnect(): void {\n for (const c of this.connections) c.disconnect();\n }\n\n /** Fetch device info from all connections */\n async fetchDeviceInfo(): Promise<DeviceInfo[]> {\n const results = await Promise.all(this.connections.map((c) => c.fetchDeviceInfo()));\n const devices = results.flat();\n // Track known devices\n for (const d of devices) this.knownDevices.set(d.id, d);\n return devices;\n }\n\n /** Get all known connected devices paired with their resolved config */\n getDevicesWithConfig(): DeviceWithConfig[] {\n return Array.from(this.knownDevices.values()).map((device) => ({\n device,\n config: this.getDeviceConfig(device.id),\n }));\n }\n\n /** Get the resolved per-device config (global defaults + device overrides) */\n getDeviceConfig(deviceId: string): DeviceConfig {\n const resolved = resolveDeviceConfig(this._config, deviceId);\n return {\n sensitivity: resolved.sensitivity,\n flip: resolved.flip,\n deadZone: resolved.deadZone,\n dominant: resolved.dominant,\n axisRemap: resolved.axisRemap,\n actionMap: resolved.actionMap,\n lockPosition: resolved.lockPosition,\n lockRotation: resolved.lockRotation,\n };\n }\n\n /** Update global configuration. Persists by default. */\n updateConfig(partial: Partial<InputConfig>, persist = true): void {\n this._config = mergeConfig(this._config, partial);\n if (persist) saveSettings(this._config, this.storage);\n this.emit(\"configChange\", this._config);\n }\n\n /** Update configuration for a specific device. Persists by default. */\n updateDeviceConfig(deviceId: string, partial: DeviceConfig, persist = true): void {\n const existing = this._config.devices[deviceId] ?? {};\n this._config = mergeConfig(this._config, {\n devices: { [deviceId]: { ...existing, ...partial } },\n });\n if (persist) saveSettings(this._config, this.storage);\n this.emit(\"configChange\", this._config);\n }\n\n /** Register a callback for processed spatial data. Returns unsubscribe function. */\n onSpatialData(callback: (data: SpatialData) => void): () => void {\n this.on(\"spatialData\", callback);\n return () => this.off(\"spatialData\", callback);\n }\n\n /** Register a callback for button events. Returns unsubscribe function. */\n onButtonEvent(callback: (data: ButtonEvent) => void): () => void {\n this.on(\"buttonEvent\", callback);\n return () => this.off(\"buttonEvent\", callback);\n }\n\n /** Register a callback for action values. Returns unsubscribe function. */\n onActionValues(callback: (values: ActionValues) => void): () => void {\n this.on(\"actionValues\", callback);\n return () => this.off(\"actionValues\", callback);\n }\n\n private wireConnection(connection: SatMouseConnection): void {\n connection.on(\"spatialData\", (raw) => {\n this.emit(\"rawSpatialData\", raw);\n const { spatial, actions } = this.processSpatialData(raw);\n if (spatial) this.emit(\"spatialData\", spatial);\n if (actions) this.emit(\"actionValues\", actions);\n });\n\n connection.on(\"buttonEvent\", (event) => this.emit(\"buttonEvent\", event));\n connection.on(\"stateChange\", (state, proto) => this.emit(\"stateChange\", state, proto));\n connection.on(\"deviceStatus\", (event, device) => {\n if (event === \"connected\") this.knownDevices.set(device.id, device);\n else this.knownDevices.delete(device.id);\n this.emit(\"deviceStatus\", event, device);\n });\n }\n\n private processSpatialData(raw: SpatialData): { spatial: SpatialData | null; actions: ActionValues | null } {\n const cfg = this._config;\n let data = raw;\n\n // Transform pipeline\n if (cfg.deadZone > 0) data = applyDeadZone(data, cfg.deadZone);\n if (cfg.dominant) data = applyDominant(data);\n data = applyFlip(data, cfg.flip);\n data = applyAxisRemap(data, cfg.axisRemap);\n data = applySensitivity(data, cfg.sensitivity);\n\n if (cfg.lockPosition) {\n data = { ...data, translation: { x: 0, y: 0, z: 0 } };\n }\n if (cfg.lockRotation) {\n data = { ...data, rotation: { x: 0, y: 0, z: 0 } };\n }\n\n // Apply action map: remap transformed axes to named actions\n const actions = applyActionMap(data, cfg.actionMap);\n\n // Convert action values back to spatial data for consumers that\n // expect the standard tx/ty/tz/rx/ry/rz format\n const spatial = actionValuesToSpatialData(actions, data.timestamp);\n\n return { spatial, actions };\n }\n}\n"]}
1
+ {"version":3,"sources":["../../src/utils/config.ts","../../src/utils/persistence.ts","../../src/utils/transforms.ts","../../src/utils/input-manager.ts"],"names":["DEFAULT_ACTION_MAP","TypedEmitter","applyActionMap","actionValuesToSpatialData"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { FlipConfig, SensitivityConfig, AxisMap } from \"./transforms.js\";\nimport { DEFAULT_ACTION_MAP, type ActionMap } from \"./action-map.js\";\n\n/** Per-device transform overrides. Any field left undefined inherits from global defaults. */\nexport interface DeviceConfig {\n sensitivity?: Partial<SensitivityConfig>;\n flip?: Partial<FlipConfig>;\n deadZone?: number;\n dominant?: boolean;\n axisRemap?: Partial<AxisMap>;\n actionMap?: ActionMap;\n lockPosition?: boolean;\n lockRotation?: boolean;\n}\n\nexport interface InputConfig {\n /** Global defaults applied to all devices */\n sensitivity: SensitivityConfig;\n flip: FlipConfig;\n deadZone: number;\n dominant: boolean;\n axisRemap: AxisMap;\n lockPosition: boolean;\n lockRotation: boolean;\n\n /** Action map — maps input axes to named output actions. Default: passthrough. */\n actionMap: ActionMap;\n\n /**\n * Per-device overrides, keyed by device ID (e.g., \"spacemouse-c635\")\n * or device family pattern (e.g., \"spacemouse-*\", \"hid-054c-*\").\n * Values override global defaults for matching devices.\n */\n devices: Record<string, DeviceConfig>;\n}\n\nexport const DEFAULT_CONFIG: InputConfig = {\n sensitivity: { translation: 0.001, rotation: 0.001 },\n flip: { tx: false, ty: false, tz: false, rx: false, ry: false, rz: false },\n deadZone: 0,\n dominant: false,\n axisRemap: { tx: \"x\", ty: \"y\", tz: \"z\", rx: \"x\", ry: \"y\", rz: \"z\" },\n lockPosition: false,\n lockRotation: false,\n actionMap: { ...DEFAULT_ACTION_MAP },\n devices: {\n // SpaceMouse Z-up → Three.js Y-up axis correction\n \"cnx-*\": { flip: { ty: true, tz: true, ry: true, rz: true } },\n },\n};\n\nexport function mergeConfig(base: InputConfig, partial: Partial<InputConfig>): InputConfig {\n const merged = {\n ...base,\n ...partial,\n sensitivity: { ...base.sensitivity, ...partial.sensitivity },\n flip: { ...base.flip, ...partial.flip },\n axisRemap: { ...base.axisRemap, ...partial.axisRemap },\n actionMap: partial.actionMap ? { ...base.actionMap, ...partial.actionMap } : { ...base.actionMap },\n devices: { ...base.devices },\n };\n\n // Merge per-device configs\n if (partial.devices) {\n for (const [key, devCfg] of Object.entries(partial.devices)) {\n merged.devices[key] = mergeDeviceConfig(merged.devices[key], devCfg);\n }\n }\n\n return merged;\n}\n\nexport function mergeDeviceConfig(base: DeviceConfig | undefined, partial: DeviceConfig): DeviceConfig {\n if (!base) return partial;\n return {\n ...base,\n ...partial,\n sensitivity: partial.sensitivity ? { ...base.sensitivity, ...partial.sensitivity } : base.sensitivity,\n flip: partial.flip ? { ...base.flip, ...partial.flip } : base.flip,\n axisRemap: partial.axisRemap ? { ...base.axisRemap, ...partial.axisRemap } : base.axisRemap,\n };\n}\n\n/** Resolve the effective config for a specific device by merging global + device overrides */\nexport function resolveDeviceConfig(config: InputConfig, deviceId: string): InputConfig {\n // Find matching device config: exact match first, then pattern match\n let deviceOverride: DeviceConfig | undefined;\n\n if (config.devices[deviceId]) {\n deviceOverride = config.devices[deviceId];\n } else {\n // Try pattern match (e.g., \"spacemouse-*\" matches \"spacemouse-c635\")\n for (const [pattern, cfg] of Object.entries(config.devices)) {\n if (pattern.endsWith(\"*\") && deviceId.startsWith(pattern.slice(0, -1))) {\n deviceOverride = cfg;\n break;\n }\n }\n }\n\n if (!deviceOverride) return config;\n\n return {\n ...config,\n sensitivity: { ...config.sensitivity, ...deviceOverride.sensitivity },\n flip: { ...config.flip, ...deviceOverride.flip },\n deadZone: deviceOverride.deadZone ?? config.deadZone,\n dominant: deviceOverride.dominant ?? config.dominant,\n axisRemap: { ...config.axisRemap, ...deviceOverride.axisRemap },\n actionMap: deviceOverride.actionMap ? { ...config.actionMap, ...deviceOverride.actionMap } : config.actionMap,\n lockPosition: deviceOverride.lockPosition ?? config.lockPosition,\n lockRotation: deviceOverride.lockRotation ?? config.lockRotation,\n };\n}\n","import type { InputConfig } from \"./config.js\";\n\nexport interface StorageAdapter {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n}\n\nconst STORAGE_KEY = \"satmouse:settings\";\n\nfunction getStorage(storage?: StorageAdapter): StorageAdapter | null {\n if (storage) return storage;\n try {\n return globalThis.localStorage ?? null;\n } catch {\n return null;\n }\n}\n\nexport function saveSettings(config: InputConfig, storage?: StorageAdapter): void {\n const s = getStorage(storage);\n if (!s) return;\n s.setItem(STORAGE_KEY, JSON.stringify(config));\n}\n\nexport function loadSettings(storage?: StorageAdapter): Partial<InputConfig> | null {\n const s = getStorage(storage);\n if (!s) return null;\n const raw = s.getItem(STORAGE_KEY);\n if (!raw) return null;\n try {\n return JSON.parse(raw) as Partial<InputConfig>;\n } catch {\n return null;\n }\n}\n","import type { SpatialData, Vec3 } from \"../core/types.js\";\n\nexport interface FlipConfig {\n tx: boolean;\n ty: boolean;\n tz: boolean;\n rx: boolean;\n ry: boolean;\n rz: boolean;\n}\n\nexport interface SensitivityConfig {\n translation: number;\n rotation: number;\n}\n\n/** Maps each input axis to an output axis. E.g., { tx: \"tz\", tz: \"tx\" } swaps X and Z translation. */\nexport type AxisMap = {\n tx: keyof Vec3;\n ty: keyof Vec3;\n tz: keyof Vec3;\n rx: keyof Vec3;\n ry: keyof Vec3;\n rz: keyof Vec3;\n};\n\nexport const DEFAULT_AXIS_MAP: AxisMap = {\n tx: \"x\", ty: \"y\", tz: \"z\",\n rx: \"x\", ry: \"y\", rz: \"z\",\n};\n\nexport function applyFlip(data: SpatialData, flip: FlipConfig): SpatialData {\n return {\n ...data,\n translation: {\n x: flip.tx ? -data.translation.x : data.translation.x,\n y: flip.ty ? -data.translation.y : data.translation.y,\n z: flip.tz ? -data.translation.z : data.translation.z,\n },\n rotation: {\n x: flip.rx ? -data.rotation.x : data.rotation.x,\n y: flip.ry ? -data.rotation.y : data.rotation.y,\n z: flip.rz ? -data.rotation.z : data.rotation.z,\n },\n };\n}\n\nexport function applySensitivity(data: SpatialData, sens: SensitivityConfig): SpatialData {\n return {\n ...data,\n translation: {\n x: data.translation.x * sens.translation,\n y: data.translation.y * sens.translation,\n z: data.translation.z * sens.translation,\n },\n rotation: {\n x: data.rotation.x * sens.rotation,\n y: data.rotation.y * sens.rotation,\n z: data.rotation.z * sens.rotation,\n },\n };\n}\n\nexport function applyDominant(data: SpatialData): SpatialData {\n const axes = [\n { group: \"t\" as const, key: \"x\" as const, v: Math.abs(data.translation.x) },\n { group: \"t\" as const, key: \"y\" as const, v: Math.abs(data.translation.y) },\n { group: \"t\" as const, key: \"z\" as const, v: Math.abs(data.translation.z) },\n { group: \"r\" as const, key: \"x\" as const, v: Math.abs(data.rotation.x) },\n { group: \"r\" as const, key: \"y\" as const, v: Math.abs(data.rotation.y) },\n { group: \"r\" as const, key: \"z\" as const, v: Math.abs(data.rotation.z) },\n ];\n const max = axes.reduce((a, b) => (b.v > a.v ? b : a));\n\n const t: Vec3 = { x: 0, y: 0, z: 0 };\n const r: Vec3 = { x: 0, y: 0, z: 0 };\n\n if (max.group === \"t\") t[max.key] = data.translation[max.key];\n else r[max.key] = data.rotation[max.key];\n\n return { ...data, translation: t, rotation: r };\n}\n\nexport function applyDeadZone(data: SpatialData, threshold: number): SpatialData {\n const dz = (v: number) => (Math.abs(v) < threshold ? 0 : v);\n return {\n ...data,\n translation: { x: dz(data.translation.x), y: dz(data.translation.y), z: dz(data.translation.z) },\n rotation: { x: dz(data.rotation.x), y: dz(data.rotation.y), z: dz(data.rotation.z) },\n };\n}\n\nexport function applyAxisRemap(data: SpatialData, map: AxisMap): SpatialData {\n return {\n ...data,\n translation: {\n x: 0, y: 0, z: 0,\n [map.tx]: data.translation.x,\n [map.ty]: data.translation.y,\n [map.tz]: data.translation.z,\n } as Vec3,\n rotation: {\n x: 0, y: 0, z: 0,\n [map.rx]: data.rotation.x,\n [map.ry]: data.rotation.y,\n [map.rz]: data.rotation.z,\n } as Vec3,\n };\n}\n","import { TypedEmitter } from \"../core/emitter.js\";\nimport type { SatMouseConnection } from \"../core/connection.js\";\nimport type { SpatialData, ButtonEvent, DeviceInfo, ConnectionState, TransportProtocol } from \"../core/types.js\";\nimport type { InputConfig, DeviceConfig } from \"./config.js\";\nimport { DEFAULT_CONFIG, mergeConfig, resolveDeviceConfig } from \"./config.js\";\nimport { loadSettings, saveSettings, type StorageAdapter } from \"./persistence.js\";\nimport {\n applyFlip,\n applySensitivity,\n applyDominant,\n applyDeadZone,\n applyAxisRemap,\n} from \"./transforms.js\";\nimport { applyActionMap, actionValuesToSpatialData, type ActionValues } from \"./action-map.js\";\n\nexport interface InputManagerEvents {\n /** Processed spatial data (after all transforms + action map) */\n spatialData: (data: SpatialData) => void;\n /** Named action values from the action map */\n actionValues: (values: ActionValues) => void;\n /** Raw spatial data (before transforms) */\n rawSpatialData: (data: SpatialData) => void;\n /** Button event (pass-through from connection) */\n buttonEvent: (data: ButtonEvent) => void;\n /** Connection state changed */\n stateChange: (state: ConnectionState, protocol: TransportProtocol) => void;\n /** Device connected/disconnected */\n deviceStatus: (event: \"connected\" | \"disconnected\", device: DeviceInfo) => void;\n /** Configuration changed */\n configChange: (config: InputConfig) => void;\n}\n\n/** A connected device paired with its resolved configuration */\nexport interface DeviceWithConfig {\n device: DeviceInfo;\n config: DeviceConfig;\n}\n\n/**\n * Unified device service that wraps one or more SatMouseConnections\n * and provides a single processed event stream.\n *\n * Applies a configurable transform pipeline per-device:\n * deadZone → dominant → flip → axisRemap → sensitivity → lock\n *\n * Per-device overrides are resolved from InputConfig.devices using\n * device ID matching (exact or pattern with wildcard \"*\").\n */\nexport class InputManager extends TypedEmitter<InputManagerEvents> {\n private connections: SatMouseConnection[] = [];\n private storage?: StorageAdapter;\n private knownDevices = new Map<string, DeviceInfo>();\n\n // Per-device accumulators: latest value from each device per frame tick\n private deviceAccumulators = new Map<string, { tx: number; ty: number; tz: number; rx: number; ry: number; rz: number }>();\n private accDirty = false;\n private flushTimer: ReturnType<typeof setInterval> | null = null;\n\n private _config: InputConfig;\n\n get config(): InputConfig {\n return this._config;\n }\n\n constructor(config?: Partial<InputConfig>, storage?: StorageAdapter) {\n super();\n this.storage = storage;\n const persisted = loadSettings(storage);\n this._config = mergeConfig(DEFAULT_CONFIG, { ...config, ...persisted });\n\n // Flush accumulated inputs at ~60Hz\n this.flushTimer = setInterval(() => this.flushAccumulator(), 16);\n }\n\n /** Add a connection to the managed set */\n addConnection(connection: SatMouseConnection): void {\n this.connections.push(connection);\n this.wireConnection(connection);\n }\n\n /** Remove a connection */\n removeConnection(connection: SatMouseConnection): void {\n const idx = this.connections.indexOf(connection);\n if (idx !== -1) this.connections.splice(idx, 1);\n connection.removeAllListeners();\n }\n\n /** Connect all managed connections */\n async connect(): Promise<void> {\n await Promise.all(this.connections.map((c) => c.connect()));\n }\n\n /** Disconnect all managed connections */\n disconnect(): void {\n for (const c of this.connections) c.disconnect();\n if (this.flushTimer) { clearInterval(this.flushTimer); this.flushTimer = null; }\n }\n\n /** Fetch device info from all connections */\n async fetchDeviceInfo(): Promise<DeviceInfo[]> {\n const results = await Promise.all(this.connections.map((c) => c.fetchDeviceInfo()));\n const devices = results.flat();\n // Track known devices\n for (const d of devices) this.knownDevices.set(d.id, d);\n return devices;\n }\n\n /** Get all known connected devices paired with their resolved config */\n getDevicesWithConfig(): DeviceWithConfig[] {\n return Array.from(this.knownDevices.values()).map((device) => ({\n device,\n config: this.getDeviceConfig(device.id),\n }));\n }\n\n /** Get the resolved per-device config (global defaults + device overrides) */\n getDeviceConfig(deviceId: string): DeviceConfig {\n const resolved = resolveDeviceConfig(this._config, deviceId);\n return {\n sensitivity: resolved.sensitivity,\n flip: resolved.flip,\n deadZone: resolved.deadZone,\n dominant: resolved.dominant,\n axisRemap: resolved.axisRemap,\n actionMap: resolved.actionMap,\n lockPosition: resolved.lockPosition,\n lockRotation: resolved.lockRotation,\n };\n }\n\n /** Update global configuration. Persists by default. */\n updateConfig(partial: Partial<InputConfig>, persist = true): void {\n this._config = mergeConfig(this._config, partial);\n if (persist) saveSettings(this._config, this.storage);\n this.emit(\"configChange\", this._config);\n }\n\n /** Update configuration for a specific device. Persists by default. */\n updateDeviceConfig(deviceId: string, partial: DeviceConfig, persist = true): void {\n const existing = this._config.devices[deviceId] ?? {};\n this._config = mergeConfig(this._config, {\n devices: { [deviceId]: { ...existing, ...partial } },\n });\n if (persist) saveSettings(this._config, this.storage);\n this.emit(\"configChange\", this._config);\n }\n\n /** Register a callback for processed spatial data. Returns unsubscribe function. */\n onSpatialData(callback: (data: SpatialData) => void): () => void {\n this.on(\"spatialData\", callback);\n return () => this.off(\"spatialData\", callback);\n }\n\n /** Register a callback for button events. Returns unsubscribe function. */\n onButtonEvent(callback: (data: ButtonEvent) => void): () => void {\n this.on(\"buttonEvent\", callback);\n return () => this.off(\"buttonEvent\", callback);\n }\n\n /** Register a callback for action values. Returns unsubscribe function. */\n onActionValues(callback: (values: ActionValues) => void): () => void {\n this.on(\"actionValues\", callback);\n return () => this.off(\"actionValues\", callback);\n }\n\n private wireConnection(connection: SatMouseConnection): void {\n connection.on(\"spatialData\", (raw) => {\n this.emit(\"rawSpatialData\", raw);\n\n const id = raw.deviceId ?? \"_default\";\n\n // Apply per-device transforms BEFORE accumulating\n const processed = this.processPerDevice(raw, id);\n\n // Store latest per-device processed values\n this.deviceAccumulators.set(id, {\n tx: processed.translation.x,\n ty: processed.translation.y,\n tz: processed.translation.z,\n rx: processed.rotation.x,\n ry: processed.rotation.y,\n rz: processed.rotation.z,\n });\n this.accDirty = true;\n });\n\n connection.on(\"buttonEvent\", (event) => this.emit(\"buttonEvent\", event));\n connection.on(\"stateChange\", (state, proto) => this.emit(\"stateChange\", state, proto));\n connection.on(\"deviceStatus\", (event, device) => {\n if (event === \"connected\") this.knownDevices.set(device.id, device);\n else this.knownDevices.delete(device.id);\n this.emit(\"deviceStatus\", event, device);\n });\n }\n\n private flushAccumulator(): void {\n if (!this.accDirty) return;\n\n // Merge all device accumulators: sum contributions from each device\n const merged = { tx: 0, ty: 0, tz: 0, rx: 0, ry: 0, rz: 0 };\n for (const acc of this.deviceAccumulators.values()) {\n merged.tx += acc.tx;\n merged.ty += acc.ty;\n merged.tz += acc.tz;\n merged.rx += acc.rx;\n merged.ry += acc.ry;\n merged.rz += acc.rz;\n }\n\n // Reset all device accumulators\n this.deviceAccumulators.clear();\n this.accDirty = false;\n\n const data: SpatialData = {\n translation: { x: merged.tx, y: merged.ty, z: merged.tz },\n rotation: { x: merged.rx, y: merged.ry, z: merged.rz },\n timestamp: performance.now() * 1000,\n };\n\n // Apply global transforms (action map, locks)\n const { spatial, actions } = this.applyGlobalTransforms(data);\n if (spatial) this.emit(\"spatialData\", spatial);\n if (actions) this.emit(\"actionValues\", actions);\n }\n\n /** Per-device transforms: flip, sensitivity, dead zone, dominant, axis remap */\n private processPerDevice(raw: SpatialData, deviceId: string): SpatialData {\n const cfg = resolveDeviceConfig(this._config, deviceId);\n let data = raw;\n\n if (cfg.deadZone > 0) data = applyDeadZone(data, cfg.deadZone);\n if (cfg.dominant) data = applyDominant(data);\n data = applyFlip(data, cfg.flip);\n data = applyAxisRemap(data, cfg.axisRemap);\n data = applySensitivity(data, cfg.sensitivity);\n\n return data;\n }\n\n /** Global transforms applied after per-device merge: locks + action map */\n private applyGlobalTransforms(data: SpatialData): { spatial: SpatialData | null; actions: ActionValues | null } {\n const cfg = this._config;\n\n if (cfg.lockPosition) {\n data = { ...data, translation: { x: 0, y: 0, z: 0 } };\n }\n if (cfg.lockRotation) {\n data = { ...data, rotation: { x: 0, y: 0, z: 0 } };\n }\n\n const actions = applyActionMap(data, cfg.actionMap);\n const spatial = actionValuesToSpatialData(actions, data.timestamp);\n\n return { spatial, actions };\n }\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { V as Vec3, S as SpatialData, D as DeviceInfo, e as TypedEmitter, B as ButtonEvent, a as ConnectionState, d as TransportProtocol, b as SatMouseConnection } from '../connection-CH2YsPn1.cjs';
1
+ import { V as Vec3, S as SpatialData, D as DeviceInfo, e as TypedEmitter, B as ButtonEvent, a as ConnectionState, d as TransportProtocol, b as SatMouseConnection } from '../connection-5KQFvHoJ.cjs';
2
2
 
3
3
  interface FlipConfig {
4
4
  tx: boolean;
@@ -138,6 +138,9 @@ declare class InputManager extends TypedEmitter<InputManagerEvents> {
138
138
  private connections;
139
139
  private storage?;
140
140
  private knownDevices;
141
+ private deviceAccumulators;
142
+ private accDirty;
143
+ private flushTimer;
141
144
  private _config;
142
145
  get config(): InputConfig;
143
146
  constructor(config?: Partial<InputConfig>, storage?: StorageAdapter);
@@ -166,7 +169,11 @@ declare class InputManager extends TypedEmitter<InputManagerEvents> {
166
169
  /** Register a callback for action values. Returns unsubscribe function. */
167
170
  onActionValues(callback: (values: ActionValues) => void): () => void;
168
171
  private wireConnection;
169
- private processSpatialData;
172
+ private flushAccumulator;
173
+ /** Per-device transforms: flip, sensitivity, dead zone, dominant, axis remap */
174
+ private processPerDevice;
175
+ /** Global transforms applied after per-device merge: locks + action map */
176
+ private applyGlobalTransforms;
170
177
  }
171
178
 
172
179
  export { type ActionBinding, type ActionMap, type ActionValues, type AxisMap, DEFAULT_ACTION_MAP, DEFAULT_AXIS_MAP, DEFAULT_CONFIG, type DeviceConfig, type DeviceWithConfig, type FlipConfig, type InputAxis, type InputConfig, InputManager, type InputManagerEvents, type SensitivityConfig, type StorageAdapter, actionValuesToSpatialData, applyActionMap, applyAxisRemap, applyDeadZone, applyDominant, applyFlip, applySensitivity, loadSettings, mergeConfig, resolveDeviceConfig, saveSettings, swapActions };
@@ -1,4 +1,4 @@
1
- import { V as Vec3, S as SpatialData, D as DeviceInfo, e as TypedEmitter, B as ButtonEvent, a as ConnectionState, d as TransportProtocol, b as SatMouseConnection } from '../connection-CH2YsPn1.js';
1
+ import { V as Vec3, S as SpatialData, D as DeviceInfo, e as TypedEmitter, B as ButtonEvent, a as ConnectionState, d as TransportProtocol, b as SatMouseConnection } from '../connection-5KQFvHoJ.js';
2
2
 
3
3
  interface FlipConfig {
4
4
  tx: boolean;
@@ -138,6 +138,9 @@ declare class InputManager extends TypedEmitter<InputManagerEvents> {
138
138
  private connections;
139
139
  private storage?;
140
140
  private knownDevices;
141
+ private deviceAccumulators;
142
+ private accDirty;
143
+ private flushTimer;
141
144
  private _config;
142
145
  get config(): InputConfig;
143
146
  constructor(config?: Partial<InputConfig>, storage?: StorageAdapter);
@@ -166,7 +169,11 @@ declare class InputManager extends TypedEmitter<InputManagerEvents> {
166
169
  /** Register a callback for action values. Returns unsubscribe function. */
167
170
  onActionValues(callback: (values: ActionValues) => void): () => void;
168
171
  private wireConnection;
169
- private processSpatialData;
172
+ private flushAccumulator;
173
+ /** Per-device transforms: flip, sensitivity, dead zone, dominant, axis remap */
174
+ private processPerDevice;
175
+ /** Global transforms applied after per-device merge: locks + action map */
176
+ private applyGlobalTransforms;
170
177
  }
171
178
 
172
179
  export { type ActionBinding, type ActionMap, type ActionValues, type AxisMap, DEFAULT_ACTION_MAP, DEFAULT_AXIS_MAP, DEFAULT_CONFIG, type DeviceConfig, type DeviceWithConfig, type FlipConfig, type InputAxis, type InputConfig, InputManager, type InputManagerEvents, type SensitivityConfig, type StorageAdapter, actionValuesToSpatialData, applyActionMap, applyAxisRemap, applyDeadZone, applyDominant, applyFlip, applySensitivity, loadSettings, mergeConfig, resolveDeviceConfig, saveSettings, swapActions };
@@ -1,77 +1,21 @@
1
1
  import { TypedEmitter } from '../chunk-X6NIARXW.js';
2
-
3
- // src/utils/action-map.ts
4
- var DEFAULT_ACTION_MAP = {
5
- tx: { source: "tx" },
6
- ty: { source: "ty" },
7
- tz: { source: "tz" },
8
- rx: { source: "rx" },
9
- ry: { source: "ry" },
10
- rz: { source: "rz" }
11
- };
12
- function readAxis(data, axis) {
13
- switch (axis) {
14
- case "tx":
15
- return data.translation.x;
16
- case "ty":
17
- return data.translation.y;
18
- case "tz":
19
- return data.translation.z;
20
- case "rx":
21
- return data.rotation.x;
22
- case "ry":
23
- return data.rotation.y;
24
- case "rz":
25
- return data.rotation.z;
26
- }
27
- }
28
- function applyActionMap(data, map) {
29
- const result = {};
30
- for (const [action, binding] of Object.entries(map)) {
31
- let value = readAxis(data, binding.source);
32
- if (binding.invert) value = -value;
33
- value *= binding.scale ?? 1;
34
- result[action] = value;
35
- }
36
- return result;
37
- }
38
- function actionValuesToSpatialData(values, timestamp) {
39
- return {
40
- translation: {
41
- x: values.tx ?? 0,
42
- y: values.ty ?? 0,
43
- z: values.tz ?? 0
44
- },
45
- rotation: {
46
- x: values.rx ?? 0,
47
- y: values.ry ?? 0,
48
- z: values.rz ?? 0
49
- },
50
- timestamp
51
- };
52
- }
53
- function swapActions(map, actionA, actionB) {
54
- const result = { ...map };
55
- const a = result[actionA];
56
- const b = result[actionB];
57
- if (a && b) {
58
- result[actionA] = b;
59
- result[actionB] = a;
60
- }
61
- return result;
62
- }
2
+ import { DEFAULT_ACTION_MAP, applyActionMap, actionValuesToSpatialData } from '../chunk-I47ZOX3M.js';
3
+ export { DEFAULT_ACTION_MAP, actionValuesToSpatialData, applyActionMap, swapActions } from '../chunk-I47ZOX3M.js';
63
4
 
64
5
  // src/utils/config.ts
65
6
  var DEFAULT_CONFIG = {
66
7
  sensitivity: { translation: 1e-3, rotation: 1e-3 },
67
- flip: { tx: false, ty: true, tz: true, rx: false, ry: true, rz: true },
8
+ flip: { tx: false, ty: false, tz: false, rx: false, ry: false, rz: false },
68
9
  deadZone: 0,
69
10
  dominant: false,
70
11
  axisRemap: { tx: "x", ty: "y", tz: "z", rx: "x", ry: "y", rz: "z" },
71
12
  lockPosition: false,
72
13
  lockRotation: false,
73
14
  actionMap: { ...DEFAULT_ACTION_MAP },
74
- devices: {}
15
+ devices: {
16
+ // SpaceMouse Z-up → Three.js Y-up axis correction
17
+ "cnx-*": { flip: { ty: true, tz: true, ry: true, rz: true } }
18
+ }
75
19
  };
76
20
  function mergeConfig(base, partial) {
77
21
  const merged = {
@@ -243,6 +187,10 @@ var InputManager = class extends TypedEmitter {
243
187
  connections = [];
244
188
  storage;
245
189
  knownDevices = /* @__PURE__ */ new Map();
190
+ // Per-device accumulators: latest value from each device per frame tick
191
+ deviceAccumulators = /* @__PURE__ */ new Map();
192
+ accDirty = false;
193
+ flushTimer = null;
246
194
  _config;
247
195
  get config() {
248
196
  return this._config;
@@ -252,6 +200,7 @@ var InputManager = class extends TypedEmitter {
252
200
  this.storage = storage;
253
201
  const persisted = loadSettings(storage);
254
202
  this._config = mergeConfig(DEFAULT_CONFIG, { ...config, ...persisted });
203
+ this.flushTimer = setInterval(() => this.flushAccumulator(), 16);
255
204
  }
256
205
  /** Add a connection to the managed set */
257
206
  addConnection(connection) {
@@ -271,6 +220,10 @@ var InputManager = class extends TypedEmitter {
271
220
  /** Disconnect all managed connections */
272
221
  disconnect() {
273
222
  for (const c of this.connections) c.disconnect();
223
+ if (this.flushTimer) {
224
+ clearInterval(this.flushTimer);
225
+ this.flushTimer = null;
226
+ }
274
227
  }
275
228
  /** Fetch device info from all connections */
276
229
  async fetchDeviceInfo() {
@@ -333,9 +286,17 @@ var InputManager = class extends TypedEmitter {
333
286
  wireConnection(connection) {
334
287
  connection.on("spatialData", (raw) => {
335
288
  this.emit("rawSpatialData", raw);
336
- const { spatial, actions } = this.processSpatialData(raw);
337
- if (spatial) this.emit("spatialData", spatial);
338
- if (actions) this.emit("actionValues", actions);
289
+ const id = raw.deviceId ?? "_default";
290
+ const processed = this.processPerDevice(raw, id);
291
+ this.deviceAccumulators.set(id, {
292
+ tx: processed.translation.x,
293
+ ty: processed.translation.y,
294
+ tz: processed.translation.z,
295
+ rx: processed.rotation.x,
296
+ ry: processed.rotation.y,
297
+ rz: processed.rotation.z
298
+ });
299
+ this.accDirty = true;
339
300
  });
340
301
  connection.on("buttonEvent", (event) => this.emit("buttonEvent", event));
341
302
  connection.on("stateChange", (state, proto) => this.emit("stateChange", state, proto));
@@ -345,14 +306,42 @@ var InputManager = class extends TypedEmitter {
345
306
  this.emit("deviceStatus", event, device);
346
307
  });
347
308
  }
348
- processSpatialData(raw) {
349
- const cfg = this._config;
309
+ flushAccumulator() {
310
+ if (!this.accDirty) return;
311
+ const merged = { tx: 0, ty: 0, tz: 0, rx: 0, ry: 0, rz: 0 };
312
+ for (const acc of this.deviceAccumulators.values()) {
313
+ merged.tx += acc.tx;
314
+ merged.ty += acc.ty;
315
+ merged.tz += acc.tz;
316
+ merged.rx += acc.rx;
317
+ merged.ry += acc.ry;
318
+ merged.rz += acc.rz;
319
+ }
320
+ this.deviceAccumulators.clear();
321
+ this.accDirty = false;
322
+ const data = {
323
+ translation: { x: merged.tx, y: merged.ty, z: merged.tz },
324
+ rotation: { x: merged.rx, y: merged.ry, z: merged.rz },
325
+ timestamp: performance.now() * 1e3
326
+ };
327
+ const { spatial, actions } = this.applyGlobalTransforms(data);
328
+ if (spatial) this.emit("spatialData", spatial);
329
+ if (actions) this.emit("actionValues", actions);
330
+ }
331
+ /** Per-device transforms: flip, sensitivity, dead zone, dominant, axis remap */
332
+ processPerDevice(raw, deviceId) {
333
+ const cfg = resolveDeviceConfig(this._config, deviceId);
350
334
  let data = raw;
351
335
  if (cfg.deadZone > 0) data = applyDeadZone(data, cfg.deadZone);
352
336
  if (cfg.dominant) data = applyDominant(data);
353
337
  data = applyFlip(data, cfg.flip);
354
338
  data = applyAxisRemap(data, cfg.axisRemap);
355
339
  data = applySensitivity(data, cfg.sensitivity);
340
+ return data;
341
+ }
342
+ /** Global transforms applied after per-device merge: locks + action map */
343
+ applyGlobalTransforms(data) {
344
+ const cfg = this._config;
356
345
  if (cfg.lockPosition) {
357
346
  data = { ...data, translation: { x: 0, y: 0, z: 0 } };
358
347
  }
@@ -365,6 +354,6 @@ var InputManager = class extends TypedEmitter {
365
354
  }
366
355
  };
367
356
 
368
- export { DEFAULT_ACTION_MAP, DEFAULT_AXIS_MAP, DEFAULT_CONFIG, InputManager, actionValuesToSpatialData, applyActionMap, applyAxisRemap, applyDeadZone, applyDominant, applyFlip, applySensitivity, loadSettings, mergeConfig, resolveDeviceConfig, saveSettings, swapActions };
357
+ export { DEFAULT_AXIS_MAP, DEFAULT_CONFIG, InputManager, applyAxisRemap, applyDeadZone, applyDominant, applyFlip, applySensitivity, loadSettings, mergeConfig, resolveDeviceConfig, saveSettings };
369
358
  //# sourceMappingURL=index.js.map
370
359
  //# sourceMappingURL=index.js.map