@kelnishi/satmouse-client 0.9.15 → 0.10.0

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,49 +1,53 @@
1
1
  import { TypedEmitter } from '../chunk-X6NIARXW.js';
2
- import { DEFAULT_ACTION_MAP, applyActionMap, actionValuesToSpatialData } from '../chunk-I47ZOX3M.js';
3
- export { DEFAULT_ACTION_MAP, actionValuesToSpatialData, applyActionMap, swapActions } from '../chunk-I47ZOX3M.js';
2
+ import { DEFAULT_ROUTES, applyRoutes, buildRoutes } from '../chunk-RNM322RZ.js';
3
+ export { DEFAULT_ROUTES, FULL_AXES, applyRoutes, buildRoutes } from '../chunk-RNM322RZ.js';
4
4
 
5
5
  // src/utils/config.ts
6
6
  var DEFAULT_CONFIG = {
7
- sensitivity: { translation: 1e-3, rotation: 1e-3 },
8
- flip: { tx: false, ty: false, tz: false, rx: false, ry: false, rz: false },
7
+ routes: DEFAULT_ROUTES,
8
+ scale: 1e-3,
9
9
  deadZone: 0,
10
10
  dominant: false,
11
- axisRemap: { tx: "x", ty: "y", tz: "z", rx: "x", ry: "y", rz: "z" },
12
11
  lockPosition: false,
13
12
  lockRotation: false,
14
- actionMap: { ...DEFAULT_ACTION_MAP },
15
13
  devices: {
16
- // SpaceMouse Z-up → Three.js Y-up axis correction
17
- "cnx-*": { flip: { ty: true, tz: true, ry: true, rz: true } }
14
+ "cnx-*": {
15
+ routes: [
16
+ { source: "tx", target: "tx" },
17
+ { source: "ty", target: "ty", flip: true },
18
+ { source: "tz", target: "tz", flip: true },
19
+ { source: "rx", target: "rx" },
20
+ { source: "ry", target: "ry", flip: true },
21
+ { source: "rz", target: "rz", flip: true }
22
+ ]
23
+ },
24
+ // PlayStation: L2 (ty) → TY, R2 (ry) → TY flipped (push-pull)
25
+ "hid-54c-*": {
26
+ routes: [
27
+ { source: "tx", target: "tx" },
28
+ { source: "tz", target: "tz" },
29
+ { source: "rz", target: "rz" },
30
+ { source: "rx", target: "rx" },
31
+ { source: "ty", target: "ty" },
32
+ { source: "ry", target: "ty", flip: true }
33
+ ]
34
+ }
18
35
  }
19
36
  };
20
37
  function mergeConfig(base, partial) {
21
38
  const merged = {
22
39
  ...base,
23
40
  ...partial,
24
- sensitivity: { ...base.sensitivity, ...partial.sensitivity },
25
- flip: { ...base.flip, ...partial.flip },
26
- axisRemap: { ...base.axisRemap, ...partial.axisRemap },
27
- actionMap: partial.actionMap ? { ...base.actionMap, ...partial.actionMap } : { ...base.actionMap },
41
+ routes: partial.routes ?? [...base.routes],
28
42
  devices: { ...base.devices }
29
43
  };
30
44
  if (partial.devices) {
31
45
  for (const [key, devCfg] of Object.entries(partial.devices)) {
32
- merged.devices[key] = mergeDeviceConfig(merged.devices[key], devCfg);
46
+ merged.devices[key] = { ...merged.devices[key], ...devCfg };
33
47
  }
34
48
  }
35
49
  return merged;
36
50
  }
37
- function mergeDeviceConfig(base, partial) {
38
- if (!base) return partial;
39
- return {
40
- ...base,
41
- ...partial,
42
- sensitivity: partial.sensitivity ? { ...base.sensitivity, ...partial.sensitivity } : base.sensitivity,
43
- flip: partial.flip ? { ...base.flip, ...partial.flip } : base.flip,
44
- axisRemap: partial.axisRemap ? { ...base.axisRemap, ...partial.axisRemap } : base.axisRemap
45
- };
46
- }
47
51
  function resolveDeviceConfig(config, deviceId) {
48
52
  let deviceOverride;
49
53
  if (config.devices[deviceId]) {
@@ -59,14 +63,10 @@ function resolveDeviceConfig(config, deviceId) {
59
63
  if (!deviceOverride) return config;
60
64
  return {
61
65
  ...config,
62
- sensitivity: { ...config.sensitivity, ...deviceOverride.sensitivity },
63
- flip: { ...config.flip, ...deviceOverride.flip },
66
+ routes: deviceOverride.routes ?? config.routes,
67
+ scale: deviceOverride.scale ?? config.scale,
64
68
  deadZone: deviceOverride.deadZone ?? config.deadZone,
65
- dominant: deviceOverride.dominant ?? config.dominant,
66
- axisRemap: { ...config.axisRemap, ...deviceOverride.axisRemap },
67
- actionMap: deviceOverride.actionMap ? { ...config.actionMap, ...deviceOverride.actionMap } : config.actionMap,
68
- lockPosition: deviceOverride.lockPosition ?? config.lockPosition,
69
- lockRotation: deviceOverride.lockRotation ?? config.lockRotation
69
+ dominant: deviceOverride.dominant ?? config.dominant
70
70
  };
71
71
  }
72
72
 
@@ -85,6 +85,11 @@ function saveSettings(config, storage) {
85
85
  if (!s) return;
86
86
  s.setItem(STORAGE_KEY, JSON.stringify(config));
87
87
  }
88
+ function clearSettings(storage) {
89
+ const s = getStorage(storage);
90
+ if (!s) return;
91
+ s.setItem(STORAGE_KEY, "{}");
92
+ }
88
93
  function loadSettings(storage) {
89
94
  const s = getStorage(storage);
90
95
  if (!s) return null;
@@ -97,97 +102,11 @@ function loadSettings(storage) {
97
102
  }
98
103
  }
99
104
 
100
- // src/utils/transforms.ts
101
- var DEFAULT_AXIS_MAP = {
102
- tx: "x",
103
- ty: "y",
104
- tz: "z",
105
- rx: "x",
106
- ry: "y",
107
- rz: "z"
108
- };
109
- function applyFlip(data, flip) {
110
- return {
111
- ...data,
112
- translation: {
113
- x: flip.tx ? -data.translation.x : data.translation.x,
114
- y: flip.ty ? -data.translation.y : data.translation.y,
115
- z: flip.tz ? -data.translation.z : data.translation.z
116
- },
117
- rotation: {
118
- x: flip.rx ? -data.rotation.x : data.rotation.x,
119
- y: flip.ry ? -data.rotation.y : data.rotation.y,
120
- z: flip.rz ? -data.rotation.z : data.rotation.z
121
- }
122
- };
123
- }
124
- function applySensitivity(data, sens) {
125
- return {
126
- ...data,
127
- translation: {
128
- x: data.translation.x * sens.translation,
129
- y: data.translation.y * sens.translation,
130
- z: data.translation.z * sens.translation
131
- },
132
- rotation: {
133
- x: data.rotation.x * sens.rotation,
134
- y: data.rotation.y * sens.rotation,
135
- z: data.rotation.z * sens.rotation
136
- }
137
- };
138
- }
139
- function applyDominant(data) {
140
- const axes = [
141
- { group: "t", key: "x", v: Math.abs(data.translation.x) },
142
- { group: "t", key: "y", v: Math.abs(data.translation.y) },
143
- { group: "t", key: "z", v: Math.abs(data.translation.z) },
144
- { group: "r", key: "x", v: Math.abs(data.rotation.x) },
145
- { group: "r", key: "y", v: Math.abs(data.rotation.y) },
146
- { group: "r", key: "z", v: Math.abs(data.rotation.z) }
147
- ];
148
- const max = axes.reduce((a, b) => b.v > a.v ? b : a);
149
- const t = { x: 0, y: 0, z: 0 };
150
- const r = { x: 0, y: 0, z: 0 };
151
- if (max.group === "t") t[max.key] = data.translation[max.key];
152
- else r[max.key] = data.rotation[max.key];
153
- return { ...data, translation: t, rotation: r };
154
- }
155
- function applyDeadZone(data, threshold) {
156
- const dz = (v) => Math.abs(v) < threshold ? 0 : v;
157
- return {
158
- ...data,
159
- translation: { x: dz(data.translation.x), y: dz(data.translation.y), z: dz(data.translation.z) },
160
- rotation: { x: dz(data.rotation.x), y: dz(data.rotation.y), z: dz(data.rotation.z) }
161
- };
162
- }
163
- function applyAxisRemap(data, map) {
164
- return {
165
- ...data,
166
- translation: {
167
- x: 0,
168
- y: 0,
169
- z: 0,
170
- [map.tx]: data.translation.x,
171
- [map.ty]: data.translation.y,
172
- [map.tz]: data.translation.z
173
- },
174
- rotation: {
175
- x: 0,
176
- y: 0,
177
- z: 0,
178
- [map.rx]: data.rotation.x,
179
- [map.ry]: data.rotation.y,
180
- [map.rz]: data.rotation.z
181
- }
182
- };
183
- }
184
-
185
105
  // src/utils/input-manager.ts
186
106
  var InputManager = class extends TypedEmitter {
187
107
  connections = [];
188
108
  storage;
189
109
  knownDevices = /* @__PURE__ */ new Map();
190
- // Per-device accumulators: latest value from each device per frame tick
191
110
  deviceAccumulators = /* @__PURE__ */ new Map();
192
111
  accDirty = false;
193
112
  flushTimer = null;
@@ -202,22 +121,18 @@ var InputManager = class extends TypedEmitter {
202
121
  this._config = mergeConfig(DEFAULT_CONFIG, { ...config, ...persisted });
203
122
  this.flushTimer = setInterval(() => this.flushAccumulator(), 16);
204
123
  }
205
- /** Add a connection to the managed set */
206
124
  addConnection(connection) {
207
125
  this.connections.push(connection);
208
126
  this.wireConnection(connection);
209
127
  }
210
- /** Remove a connection */
211
128
  removeConnection(connection) {
212
129
  const idx = this.connections.indexOf(connection);
213
130
  if (idx !== -1) this.connections.splice(idx, 1);
214
131
  connection.removeAllListeners();
215
132
  }
216
- /** Connect all managed connections */
217
133
  async connect() {
218
134
  await Promise.all(this.connections.map((c) => c.connect()));
219
135
  }
220
- /** Disconnect all managed connections */
221
136
  disconnect() {
222
137
  for (const c of this.connections) c.disconnect();
223
138
  if (this.flushTimer) {
@@ -225,41 +140,32 @@ var InputManager = class extends TypedEmitter {
225
140
  this.flushTimer = null;
226
141
  }
227
142
  }
228
- /** Fetch device info from all connections */
229
143
  async fetchDeviceInfo() {
230
144
  const results = await Promise.all(this.connections.map((c) => c.fetchDeviceInfo()));
231
145
  const devices = results.flat();
232
146
  for (const d of devices) this.knownDevices.set(d.id, d);
233
147
  return devices;
234
148
  }
235
- /** Get all known connected devices paired with their resolved config */
236
149
  getDevicesWithConfig() {
237
150
  return Array.from(this.knownDevices.values()).map((device) => ({
238
151
  device,
239
152
  config: this.getDeviceConfig(device.id)
240
153
  }));
241
154
  }
242
- /** Get the resolved per-device config (global defaults + device overrides) */
243
155
  getDeviceConfig(deviceId) {
244
156
  const resolved = resolveDeviceConfig(this._config, deviceId);
245
157
  return {
246
- sensitivity: resolved.sensitivity,
247
- flip: resolved.flip,
158
+ routes: resolved.routes,
159
+ scale: resolved.scale,
248
160
  deadZone: resolved.deadZone,
249
- dominant: resolved.dominant,
250
- axisRemap: resolved.axisRemap,
251
- actionMap: resolved.actionMap,
252
- lockPosition: resolved.lockPosition,
253
- lockRotation: resolved.lockRotation
161
+ dominant: resolved.dominant
254
162
  };
255
163
  }
256
- /** Update global configuration. Persists by default. */
257
164
  updateConfig(partial, persist = true) {
258
165
  this._config = mergeConfig(this._config, partial);
259
166
  if (persist) saveSettings(this._config, this.storage);
260
167
  this.emit("configChange", this._config);
261
168
  }
262
- /** Update configuration for a specific device. Persists by default. */
263
169
  updateDeviceConfig(deviceId, partial, persist = true) {
264
170
  const existing = this._config.devices[deviceId] ?? {};
265
171
  this._config = mergeConfig(this._config, {
@@ -268,21 +174,25 @@ var InputManager = class extends TypedEmitter {
268
174
  if (persist) saveSettings(this._config, this.storage);
269
175
  this.emit("configChange", this._config);
270
176
  }
271
- /** Register a callback for processed spatial data. Returns unsubscribe function. */
177
+ resetDeviceConfig(deviceId, persist = true) {
178
+ const { [deviceId]: _, ...rest } = this._config.devices;
179
+ this._config = { ...this._config, devices: rest };
180
+ if (persist) saveSettings(this._config, this.storage);
181
+ this.emit("configChange", this._config);
182
+ }
183
+ resetAllConfig() {
184
+ clearSettings(this.storage);
185
+ this._config = { ...DEFAULT_CONFIG };
186
+ this.emit("configChange", this._config);
187
+ }
272
188
  onSpatialData(callback) {
273
189
  this.on("spatialData", callback);
274
190
  return () => this.off("spatialData", callback);
275
191
  }
276
- /** Register a callback for button events. Returns unsubscribe function. */
277
192
  onButtonEvent(callback) {
278
193
  this.on("buttonEvent", callback);
279
194
  return () => this.off("buttonEvent", callback);
280
195
  }
281
- /** Register a callback for action values. Returns unsubscribe function. */
282
- onActionValues(callback) {
283
- this.on("actionValues", callback);
284
- return () => this.off("actionValues", callback);
285
- }
286
196
  wireConnection(connection) {
287
197
  connection.on("spatialData", (raw) => {
288
198
  this.emit("rawSpatialData", raw);
@@ -319,41 +229,66 @@ var InputManager = class extends TypedEmitter {
319
229
  }
320
230
  this.deviceAccumulators.clear();
321
231
  this.accDirty = false;
322
- const data = {
232
+ let data = {
323
233
  translation: { x: merged.tx, y: merged.ty, z: merged.tz },
324
234
  rotation: { x: merged.rx, y: merged.ry, z: merged.rz },
325
235
  timestamp: performance.now() * 1e3
326
236
  };
327
- const { spatial, actions } = this.applyGlobalTransforms(data);
328
- if (spatial) this.emit("spatialData", spatial);
329
- if (actions) this.emit("actionValues", actions);
237
+ if (this._config.lockPosition) {
238
+ data = { ...data, translation: { x: 0, y: 0, z: 0 } };
239
+ }
240
+ if (this._config.lockRotation) {
241
+ data = { ...data, rotation: { x: 0, y: 0, z: 0 } };
242
+ }
243
+ this.emit("spatialData", data);
330
244
  }
331
- /** Per-device transforms: flip, sensitivity, dead zone, dominant, axis remap */
245
+ /** Per-device: deadZone dominant routes (flip + scale + remap in one pass) */
332
246
  processPerDevice(raw, deviceId) {
333
247
  const cfg = resolveDeviceConfig(this._config, deviceId);
334
248
  let data = raw;
335
- if (cfg.deadZone > 0) data = applyDeadZone(data, cfg.deadZone);
336
- if (cfg.dominant) data = applyDominant(data);
337
- data = applyFlip(data, cfg.flip);
338
- data = applyAxisRemap(data, cfg.axisRemap);
339
- data = applySensitivity(data, cfg.sensitivity);
249
+ if (cfg.deadZone > 0) {
250
+ const dz = (v) => Math.abs(v) < cfg.deadZone ? 0 : v;
251
+ data = {
252
+ ...data,
253
+ translation: { x: dz(data.translation.x), y: dz(data.translation.y), z: dz(data.translation.z) },
254
+ rotation: { x: dz(data.rotation.x), y: dz(data.rotation.y), z: dz(data.rotation.z) }
255
+ };
256
+ }
257
+ if (cfg.dominant) {
258
+ const axes = [
259
+ { g: "t", k: "x", v: Math.abs(data.translation.x) },
260
+ { g: "t", k: "y", v: Math.abs(data.translation.y) },
261
+ { g: "t", k: "z", v: Math.abs(data.translation.z) },
262
+ { g: "r", k: "x", v: Math.abs(data.rotation.x) },
263
+ { g: "r", k: "y", v: Math.abs(data.rotation.y) },
264
+ { g: "r", k: "z", v: Math.abs(data.rotation.z) }
265
+ ];
266
+ const max = axes.reduce((a, b) => b.v > a.v ? b : a);
267
+ const t = { x: 0, y: 0, z: 0 };
268
+ const r = { x: 0, y: 0, z: 0 };
269
+ if (max.g === "t") t[max.k] = data.translation[max.k];
270
+ else r[max.k] = data.rotation[max.k];
271
+ data = { ...data, translation: t, rotation: r };
272
+ }
273
+ const device = this.knownDevices.get(deviceId);
274
+ const deviceRoutes = this.resolveRoutes(deviceId, device);
275
+ data = applyRoutes(data, deviceRoutes, cfg.scale);
340
276
  return data;
341
277
  }
342
- /** Global transforms applied after per-device merge: locks + action map */
343
- applyGlobalTransforms(data) {
344
- const cfg = this._config;
345
- if (cfg.lockPosition) {
346
- data = { ...data, translation: { x: 0, y: 0, z: 0 } };
347
- }
348
- if (cfg.lockRotation) {
349
- data = { ...data, rotation: { x: 0, y: 0, z: 0 } };
278
+ /** Get the effective routes for a device: device config override > device axes metadata > global default */
279
+ resolveRoutes(deviceId, device) {
280
+ const devCfg = this._config.devices[deviceId];
281
+ if (devCfg?.routes && Array.isArray(devCfg.routes)) return devCfg.routes;
282
+ for (const [pattern, cfg] of Object.entries(this._config.devices)) {
283
+ if (pattern.endsWith("*") && deviceId.startsWith(pattern.slice(0, -1))) {
284
+ if (cfg.routes && Array.isArray(cfg.routes)) return cfg.routes;
285
+ }
350
286
  }
351
- const actions = applyActionMap(data, cfg.actionMap);
352
- const spatial = actionValuesToSpatialData(actions, data.timestamp);
353
- return { spatial, actions };
287
+ if (device?.axes) return buildRoutes(device.axes);
288
+ return DEFAULT_ROUTES;
354
289
  }
355
290
  };
356
291
 
357
- export { DEFAULT_AXIS_MAP, DEFAULT_CONFIG, InputManager, applyAxisRemap, applyDeadZone, applyDominant, applyFlip, applySensitivity, loadSettings, mergeConfig, resolveDeviceConfig, saveSettings };
292
+ export { DEFAULT_CONFIG, InputManager, loadSettings, mergeConfig, resolveDeviceConfig, saveSettings };
358
293
  //# sourceMappingURL=index.js.map
359
294
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/config.ts","../../src/utils/persistence.ts","../../src/utils/transforms.ts","../../src/utils/input-manager.ts"],"names":[],"mappings":"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","file":"index.js","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
+ {"version":3,"sources":["../../src/utils/config.ts","../../src/utils/persistence.ts","../../src/utils/input-manager.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import { DEFAULT_ROUTES, type AxisRoute } from \"./action-map.js\";\n\n/** Per-device configuration */\nexport interface DeviceConfig {\n /** Axis routing — each entry maps a device input to an output with optional flip */\n routes?: AxisRoute[];\n /** Scale multiplier applied to all axes (default: 1) */\n scale?: number;\n /** Dead zone threshold (0-1). Values below this are zeroed. */\n deadZone?: number;\n /** Only pass the strongest axis, zero all others */\n dominant?: boolean;\n}\n\n/** Global configuration */\nexport interface InputConfig {\n /** Default axis routes (used when device has no override) */\n routes: AxisRoute[];\n /** Default scale */\n scale: number;\n /** Dead zone threshold */\n deadZone: number;\n /** Dominant axis mode */\n dominant: boolean;\n /** Lock translation to zero */\n lockPosition: boolean;\n /** Lock rotation to zero */\n lockRotation: boolean;\n /** Per-device overrides keyed by device ID or pattern (e.g., \"cnx-*\") */\n devices: Record<string, DeviceConfig>;\n}\n\nexport const DEFAULT_CONFIG: InputConfig = {\n routes: DEFAULT_ROUTES,\n scale: 0.001,\n deadZone: 0,\n dominant: false,\n lockPosition: false,\n lockRotation: false,\n devices: {\n \"cnx-*\": {\n routes: [\n { source: \"tx\", target: \"tx\" },\n { source: \"ty\", target: \"ty\", flip: true },\n { source: \"tz\", target: \"tz\", flip: true },\n { source: \"rx\", target: \"rx\" },\n { source: \"ry\", target: \"ry\", flip: true },\n { source: \"rz\", target: \"rz\", flip: true },\n ],\n },\n // PlayStation: L2 (ty) → TY, R2 (ry) → TY flipped (push-pull)\n \"hid-54c-*\": {\n routes: [\n { source: \"tx\", target: \"tx\" },\n { source: \"tz\", target: \"tz\" },\n { source: \"rz\", target: \"rz\" },\n { source: \"rx\", target: \"rx\" },\n { source: \"ty\", target: \"ty\" },\n { source: \"ry\", target: \"ty\", flip: true },\n ],\n },\n },\n};\n\nexport function mergeConfig(base: InputConfig, partial: Partial<InputConfig>): InputConfig {\n const merged = {\n ...base,\n ...partial,\n routes: partial.routes ?? [...base.routes],\n devices: { ...base.devices },\n };\n\n if (partial.devices) {\n for (const [key, devCfg] of Object.entries(partial.devices)) {\n merged.devices[key] = { ...merged.devices[key], ...devCfg };\n }\n }\n\n return merged;\n}\n\n/** Resolve the effective config for a specific device */\nexport function resolveDeviceConfig(config: InputConfig, deviceId: string): InputConfig {\n let deviceOverride: DeviceConfig | undefined;\n\n if (config.devices[deviceId]) {\n deviceOverride = config.devices[deviceId];\n } else {\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 routes: deviceOverride.routes ?? config.routes,\n scale: deviceOverride.scale ?? config.scale,\n deadZone: deviceOverride.deadZone ?? config.deadZone,\n dominant: deviceOverride.dominant ?? config.dominant,\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 clearSettings(storage?: StorageAdapter): void {\n const s = getStorage(storage);\n if (!s) return;\n s.setItem(STORAGE_KEY, \"{}\");\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 { 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, clearSettings, type StorageAdapter } from \"./persistence.js\";\nimport { applyRoutes, buildRoutes, DEFAULT_ROUTES, type AxisRoute } from \"./action-map.js\";\n\nexport interface InputManagerEvents {\n spatialData: (data: SpatialData) => void;\n rawSpatialData: (data: SpatialData) => void;\n buttonEvent: (data: ButtonEvent) => void;\n stateChange: (state: ConnectionState, protocol: TransportProtocol) => void;\n deviceStatus: (event: \"connected\" | \"disconnected\", device: DeviceInfo) => void;\n configChange: (config: InputConfig) => void;\n}\n\nexport interface DeviceWithConfig {\n device: DeviceInfo;\n config: DeviceConfig;\n}\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 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 this.flushTimer = setInterval(() => this.flushAccumulator(), 16);\n }\n\n addConnection(connection: SatMouseConnection): void {\n this.connections.push(connection);\n this.wireConnection(connection);\n }\n\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 async connect(): Promise<void> {\n await Promise.all(this.connections.map((c) => c.connect()));\n }\n\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 async fetchDeviceInfo(): Promise<DeviceInfo[]> {\n const results = await Promise.all(this.connections.map((c) => c.fetchDeviceInfo()));\n const devices = results.flat();\n for (const d of devices) this.knownDevices.set(d.id, d);\n return devices;\n }\n\n getDevicesWithConfig(): DeviceWithConfig[] {\n return Array.from(this.knownDevices.values()).map((device) => ({\n device,\n config: this.getDeviceConfig(device.id),\n }));\n }\n\n getDeviceConfig(deviceId: string): DeviceConfig {\n const resolved = resolveDeviceConfig(this._config, deviceId);\n return {\n routes: resolved.routes,\n scale: resolved.scale,\n deadZone: resolved.deadZone,\n dominant: resolved.dominant,\n };\n }\n\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 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 resetDeviceConfig(deviceId: string, persist = true): void {\n const { [deviceId]: _, ...rest } = this._config.devices;\n this._config = { ...this._config, devices: rest };\n if (persist) saveSettings(this._config, this.storage);\n this.emit(\"configChange\", this._config);\n }\n\n resetAllConfig(): void {\n clearSettings(this.storage);\n this._config = { ...DEFAULT_CONFIG };\n this.emit(\"configChange\", this._config);\n }\n\n onSpatialData(callback: (data: SpatialData) => void): () => void {\n this.on(\"spatialData\", callback);\n return () => this.off(\"spatialData\", callback);\n }\n\n onButtonEvent(callback: (data: ButtonEvent) => void): () => void {\n this.on(\"buttonEvent\", callback);\n return () => this.off(\"buttonEvent\", 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 const processed = this.processPerDevice(raw, id);\n\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 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 this.deviceAccumulators.clear();\n this.accDirty = false;\n\n let 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 if (this._config.lockPosition) {\n data = { ...data, translation: { x: 0, y: 0, z: 0 } };\n }\n if (this._config.lockRotation) {\n data = { ...data, rotation: { x: 0, y: 0, z: 0 } };\n }\n\n this.emit(\"spatialData\", data);\n }\n\n /** Per-device: deadZone → dominant → routes (flip + scale + remap in one pass) */\n private processPerDevice(raw: SpatialData, deviceId: string): SpatialData {\n const cfg = resolveDeviceConfig(this._config, deviceId);\n let data = raw;\n\n // Dead zone\n if (cfg.deadZone > 0) {\n const dz = (v: number) => (Math.abs(v) < cfg.deadZone ? 0 : v);\n data = {\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\n // Dominant axis\n if (cfg.dominant) {\n const axes = [\n { g: \"t\" as const, k: \"x\" as const, v: Math.abs(data.translation.x) },\n { g: \"t\" as const, k: \"y\" as const, v: Math.abs(data.translation.y) },\n { g: \"t\" as const, k: \"z\" as const, v: Math.abs(data.translation.z) },\n { g: \"r\" as const, k: \"x\" as const, v: Math.abs(data.rotation.x) },\n { g: \"r\" as const, k: \"y\" as const, v: Math.abs(data.rotation.y) },\n { g: \"r\" as const, k: \"z\" as const, v: Math.abs(data.rotation.z) },\n ];\n const max = axes.reduce((a, b) => (b.v > a.v ? b : a));\n const t = { x: 0, y: 0, z: 0 };\n const r = { x: 0, y: 0, z: 0 };\n if (max.g === \"t\") t[max.k] = data.translation[max.k];\n else r[max.k] = data.rotation[max.k];\n data = { ...data, translation: t, rotation: r };\n }\n\n // Routes: flip + scale + remap in one pass\n // Use device-specific routes if configured, otherwise build from device axes metadata\n const device = this.knownDevices.get(deviceId);\n const deviceRoutes = this.resolveRoutes(deviceId, device);\n data = applyRoutes(data, deviceRoutes, cfg.scale);\n\n return data;\n }\n\n /** Get the effective routes for a device: device config override > device axes metadata > global default */\n private resolveRoutes(deviceId: string, device?: DeviceInfo): AxisRoute[] {\n // Check for explicit device config (exact match or pattern)\n const devCfg = this._config.devices[deviceId];\n if (devCfg?.routes && Array.isArray(devCfg.routes)) return devCfg.routes;\n\n // Check pattern matches\n for (const [pattern, cfg] of Object.entries(this._config.devices)) {\n if (pattern.endsWith(\"*\") && deviceId.startsWith(pattern.slice(0, -1))) {\n if (cfg.routes && Array.isArray(cfg.routes)) return cfg.routes;\n }\n }\n\n // Build from device axes metadata\n if (device?.axes) return buildRoutes(device.axes);\n\n // Global fallback\n return DEFAULT_ROUTES;\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kelnishi/satmouse-client",
3
- "version": "0.9.15",
3
+ "version": "0.10.0",
4
4
  "description": "Client SDK for SatMouse 6DOF spatial input bridge",
5
5
  "type": "module",
6
6
  "sideEffects": ["./src/elements/*.ts", "./dist/elements/*"],
@@ -1,64 +0,0 @@
1
- // src/utils/action-map.ts
2
- var DEFAULT_ACTION_MAP = {
3
- tx: { source: "tx" },
4
- ty: { source: "ty" },
5
- tz: { source: "tz" },
6
- rx: { source: "rx" },
7
- ry: { source: "ry" },
8
- rz: { source: "rz" }
9
- };
10
- function readAxis(data, axis) {
11
- switch (axis) {
12
- case "tx":
13
- return data.translation.x;
14
- case "ty":
15
- return data.translation.y;
16
- case "tz":
17
- return data.translation.z;
18
- case "rx":
19
- return data.rotation.x;
20
- case "ry":
21
- return data.rotation.y;
22
- case "rz":
23
- return data.rotation.z;
24
- }
25
- }
26
- function applyActionMap(data, map) {
27
- const result = {};
28
- for (const [action, binding] of Object.entries(map)) {
29
- let value = readAxis(data, binding.source);
30
- if (binding.invert) value = -value;
31
- value *= binding.scale ?? 1;
32
- result[action] = value;
33
- }
34
- return result;
35
- }
36
- function actionValuesToSpatialData(values, timestamp) {
37
- return {
38
- translation: {
39
- x: values.tx ?? 0,
40
- y: values.ty ?? 0,
41
- z: values.tz ?? 0
42
- },
43
- rotation: {
44
- x: values.rx ?? 0,
45
- y: values.ry ?? 0,
46
- z: values.rz ?? 0
47
- },
48
- timestamp
49
- };
50
- }
51
- function swapActions(map, actionA, actionB) {
52
- const result = { ...map };
53
- const a = result[actionA];
54
- const b = result[actionB];
55
- if (a && b) {
56
- result[actionA] = b;
57
- result[actionB] = a;
58
- }
59
- return result;
60
- }
61
-
62
- export { DEFAULT_ACTION_MAP, actionValuesToSpatialData, applyActionMap, swapActions };
63
- //# sourceMappingURL=chunk-I47ZOX3M.js.map
64
- //# sourceMappingURL=chunk-I47ZOX3M.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/utils/action-map.ts"],"names":[],"mappings":";AA0BO,IAAM,kBAAA,GAAgC;AAAA,EAC3C,EAAA,EAAI,EAAE,MAAA,EAAQ,IAAA,EAAK;AAAA,EACnB,EAAA,EAAI,EAAE,MAAA,EAAQ,IAAA,EAAK;AAAA,EACnB,EAAA,EAAI,EAAE,MAAA,EAAQ,IAAA,EAAK;AAAA,EACnB,EAAA,EAAI,EAAE,MAAA,EAAQ,IAAA,EAAK;AAAA,EACnB,EAAA,EAAI,EAAE,MAAA,EAAQ,IAAA,EAAK;AAAA,EACnB,EAAA,EAAI,EAAE,MAAA,EAAQ,IAAA;AAChB;AAMA,SAAS,QAAA,CAAS,MAAmB,IAAA,EAAyB;AAC5D,EAAA,QAAQ,IAAA;AAAM,IACZ,KAAK,IAAA;AAAM,MAAA,OAAO,KAAK,WAAA,CAAY,CAAA;AAAA,IACnC,KAAK,IAAA;AAAM,MAAA,OAAO,KAAK,WAAA,CAAY,CAAA;AAAA,IACnC,KAAK,IAAA;AAAM,MAAA,OAAO,KAAK,WAAA,CAAY,CAAA;AAAA,IACnC,KAAK,IAAA;AAAM,MAAA,OAAO,KAAK,QAAA,CAAS,CAAA;AAAA,IAChC,KAAK,IAAA;AAAM,MAAA,OAAO,KAAK,QAAA,CAAS,CAAA;AAAA,IAChC,KAAK,IAAA;AAAM,MAAA,OAAO,KAAK,QAAA,CAAS,CAAA;AAAA;AAEpC;AAGO,SAAS,cAAA,CAAe,MAAmB,GAAA,EAA8B;AAC9E,EAAA,MAAM,SAAuB,EAAC;AAC9B,EAAA,KAAA,MAAW,CAAC,MAAA,EAAQ,OAAO,KAAK,MAAA,CAAO,OAAA,CAAQ,GAAG,CAAA,EAAG;AACnD,IAAA,IAAI,KAAA,GAAQ,QAAA,CAAS,IAAA,EAAM,OAAA,CAAQ,MAAM,CAAA;AACzC,IAAA,IAAI,OAAA,CAAQ,MAAA,EAAQ,KAAA,GAAQ,CAAC,KAAA;AAC7B,IAAA,KAAA,IAAS,QAAQ,KAAA,IAAS,CAAA;AAC1B,IAAA,MAAA,CAAO,MAAM,CAAA,GAAI,KAAA;AAAA,EACnB;AACA,EAAA,OAAO,MAAA;AACT;AAMO,SAAS,yBAAA,CAA0B,QAAsB,SAAA,EAAgC;AAC9F,EAAA,OAAO;AAAA,IACL,WAAA,EAAa;AAAA,MACX,CAAA,EAAG,OAAO,EAAA,IAAM,CAAA;AAAA,MAChB,CAAA,EAAG,OAAO,EAAA,IAAM,CAAA;AAAA,MAChB,CAAA,EAAG,OAAO,EAAA,IAAM;AAAA,KAClB;AAAA,IACA,QAAA,EAAU;AAAA,MACR,CAAA,EAAG,OAAO,EAAA,IAAM,CAAA;AAAA,MAChB,CAAA,EAAG,OAAO,EAAA,IAAM,CAAA;AAAA,MAChB,CAAA,EAAG,OAAO,EAAA,IAAM;AAAA,KAClB;AAAA,IACA;AAAA,GACF;AACF;AAGO,SAAS,WAAA,CAAY,GAAA,EAAgB,OAAA,EAAiB,OAAA,EAA4B;AACvF,EAAA,MAAM,MAAA,GAAS,EAAE,GAAG,GAAA,EAAI;AACxB,EAAA,MAAM,CAAA,GAAI,OAAO,OAAO,CAAA;AACxB,EAAA,MAAM,CAAA,GAAI,OAAO,OAAO,CAAA;AACxB,EAAA,IAAI,KAAK,CAAA,EAAG;AACV,IAAA,MAAA,CAAO,OAAO,CAAA,GAAI,CAAA;AAClB,IAAA,MAAA,CAAO,OAAO,CAAA,GAAI,CAAA;AAAA,EACpB;AACA,EAAA,OAAO,MAAA;AACT","file":"chunk-I47ZOX3M.js","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"]}