diff --git a/src/device/controller.ts b/src/device/controller.ts index 8fe6b50f..515b8a93 100644 --- a/src/device/controller.ts +++ b/src/device/controller.ts @@ -6178,6 +6178,17 @@ export function applyTeevolutionSensorMode(mode: NonNullable, code: number): number { + const listed = scale.millimetresByCode?.[code - scale.min]; + if (listed !== undefined) return listed; + return scale.minMillimetres + + ((code - scale.min) * (scale.maxMillimetres - scale.minMillimetres)) / Math.max(1, scale.max - scale.min); +} + /** * Lift-off for mice that tune it continuously rather than in three stops. The * value is a raw device code; the driver owns what it means in millimetres. @@ -6186,8 +6197,7 @@ export function applyLiftOffScale(code: number): void { const scale = latestDeviceStatus?.liftOffScale; if (!hasActiveClient() || !scale) return; if (!Number.isInteger(code) || code < scale.min || code > scale.max) return; - const millimetres = scale.minMillimetres - + ((code - scale.min) * (scale.maxMillimetres - scale.minMillimetres)) / Math.max(1, scale.max - scale.min); + const millimetres = liftOffScaleMillimetres(scale, code); const label = `Lift-off ${millimetres.toFixed(1)} mm`; stageChange({ key: "lift-off-scale", diff --git a/src/device/traits.ts b/src/device/traits.ts index 00159e46..b0ec3c80 100644 --- a/src/device/traits.ts +++ b/src/device/traits.ts @@ -45,6 +45,11 @@ const BY_FAMILY: Readonly>> = { vgn: SHARED_ADVANCED, wlmouse: DIRECT_MODE, lamzu: DIRECT_MODE, + // The Atlantis client (Lamzu Atlantis, Scyrox V8S) stores debounce and sleep + // in flash and publishes its own debounce ceiling (15 ms), which only the + // direct-mode path reads. Without the entry the sleep card still appeared + // through its option list, but the debounce card never did. + "lamzu-atlantis": DIRECT_MODE, lunafury: DIRECT_MODE, "attack-shark": DIRECT_MODE, bytech: DIRECT_MODE, diff --git a/src/features/devices/cards/PerformanceCards.tsx b/src/features/devices/cards/PerformanceCards.tsx index ec496516..2b983589 100644 --- a/src/features/devices/cards/PerformanceCards.tsx +++ b/src/features/devices/cards/PerformanceCards.tsx @@ -351,8 +351,7 @@ function LiftOffScale({ snapshot }: { snapshot: ControlSnapshot }): ReactNode { if (!scale) return null; const span = Math.max(1, scale.max - scale.min); - const millimetres = scale.minMillimetres - + ((code - scale.min) * (scale.maxMillimetres - scale.minMillimetres)) / span; + const millimetres = control.liftOffScaleMillimetres(scale, code); return (
diff --git a/src/features/devices/cards/availability.test.ts b/src/features/devices/cards/availability.test.ts index 6f051a62..068c5228 100644 --- a/src/features/devices/cards/availability.test.ts +++ b/src/features/devices/cards/availability.test.ts @@ -402,6 +402,14 @@ test("debounce is offered only by the families that store it", () => { })).debounce, false); }); +test("the Atlantis family (Lamzu Atlantis, Scyrox V8S) gets debounce and sleep", () => { + const status = { brand: "Scyrox", ui: { family: "lamzu-atlantis" }, debounceMs: 0, sleepTimeout: 60 } as const; + const has = cardAvailability(snapshot({ status })); + assert.equal(has.debounce, true); + assert.equal(has.sleep, true); + assert.equal(traitsFor({ ...STATUS, ...status }).directMode, true, "the debounce ceiling comes from the driver"); +}); + test("the Hitscan family alone gives the advanced section and debounce, not sleep or signal", () => { // No ui flags beyond the family, so the result comes from the traits entry. const status = { brand: "Hitscan", ui: { family: "hitscan" }, debounceMs: 4, sleepTimeout: 60, signalStrength: 80 } as const; diff --git a/src/features/hardware-test/supported-devices-data.ts b/src/features/hardware-test/supported-devices-data.ts index 307933e5..c2ff6487 100644 --- a/src/features/hardware-test/supported-devices-data.ts +++ b/src/features/hardware-test/supported-devices-data.ts @@ -22,6 +22,7 @@ export interface SupportedDeviceEntry { brand: string; model: string; status: SupportedDevicesStatus; + /** Known product ids from the page table (hex), when the driver pins them. */ pids?: readonly number[]; } @@ -420,6 +421,7 @@ export const SUPPORTED_DEVICES: readonly SupportedDeviceEntry[] = [ { brand: "Angry Miao", model: "Infinity .97", status: "unknown" }, { brand: "Dark Project", model: "Novus SE", status: "unknown" }, { brand: "Scyrox", model: "V8", status: "unknown" }, + { brand: "Scyrox", model: "V8S", status: "supported", pids: [0xf515, 0xf517] }, { brand: "Mchose", model: "AX5", status: "unknown" }, { brand: "AJAZZ", model: "AJ139 Pro", status: "unknown" }, { brand: "AJAZZ", model: "AJ199", status: "unknown" },