From cbe8db60738e51a08373140b0c23b50e82490c3b Mon Sep 17 00:00:00 2001 From: awangelo Date: Thu, 22 Oct 2026 20:18:12 -0300 Subject: [PATCH 1/2] feat(devices): show debounce for the Atlantis family and list the Scyrox V8S - traits: register "lamzu-atlantis" as a direct-mode family, so the debounce card appears for the Lamzu Atlantis and the Scyrox V8S with the driver's own 15 ms ceiling. Only the sleep card showed before, through its option list. - The lift-off slider and its labels use the driver's per-code heights (liftOffScale.millimetresByCode) when present, so the V8S's uneven 3955 steps read 0.7/0.9/1.2/1.4/1.6 mm instead of being interpolated. - Sync the supported-devices table from the landing page (Scyrox V8S). Needs the @openmouse/protocol release that adds the Scyrox client and liftOffScale.millimetresByCode. --- src/device/controller.ts | 14 ++++++++++++-- src/device/traits.ts | 5 +++++ src/features/devices/cards/PerformanceCards.tsx | 3 +-- src/features/devices/cards/availability.test.ts | 8 ++++++++ .../hardware-test/supported-devices-data.ts | 2 ++ 5 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/device/controller.ts b/src/device/controller.ts index 35cb8aaf..86459406 100644 --- a/src/device/controller.ts +++ b/src/device/controller.ts @@ -5818,6 +5818,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. @@ -5826,8 +5837,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 15052eea..4e18427f 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 2d65ebc2..84f3f039 100644 --- a/src/features/devices/cards/PerformanceCards.tsx +++ b/src/features/devices/cards/PerformanceCards.tsx @@ -320,8 +320,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 e3b08b60..d6649632 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("low power needs the Razer capability, not merely a reported threshold", () => { // selectableValues treats an empty option list as unbounded, so a mouse that // reports a threshold without the Razer driver must not show the card. 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" }, From 58341352bd198b78e4593cd8ba39fc5a23c76e6c Mon Sep 17 00:00:00 2001 From: awangelo Date: Sun, 25 Oct 2026 19:05:47 -0300 Subject: [PATCH 2/2] chore(protocol): bump @openmouse/protocol to 0.47.0 for liftOffScale.millimetresByCode The Atlantis-family lift-off scale carries uneven step heights, and 0.45.2's MouseStatus["liftOffScale"] does not declare millimetresByCode, so tsc failed on liftOffScaleMillimetres in the controller on a lockfile install. 0.47.0 declares the field and carries the Scyrox V8S ids the mirrored supported-devices table crosschecks. --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 43e1251d..47c50347 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "AGPL-3.0-or-later", "dependencies": { "@openmouse/keyboard-protocol": "1.1.0", - "@openmouse/protocol": "0.45.2", + "@openmouse/protocol": "0.47.0", "lucide-react": "^1.43.0", "posthog-js": "^1.438.17", "preact": "^10.29.8" @@ -492,9 +492,9 @@ } }, "node_modules/@openmouse/protocol": { - "version": "0.45.2", - "resolved": "https://registry.npmjs.org/@openmouse/protocol/-/protocol-0.45.2.tgz", - "integrity": "sha512-971Aihm8fJOuRHxNvhEPKYpWe5BM4v9GO8bahHKTxGa4nMrtKYd46A9gPKDqIZk0T+mnyjfBloH1T4vum5Qzag==", + "version": "0.47.0", + "resolved": "https://registry.npmjs.org/@openmouse/protocol/-/protocol-0.47.0.tgz", + "integrity": "sha512-vNEddWvUl47OmaMtSlxMR20FmmcX0G1cH8edZE0o4QTXKLTdbSeWcMqZGlZ2f2e0yfdTNa4QGKTk5D1+eoABzg==", "license": "AGPL-3.0-or-later", "engines": { "node": ">=20" diff --git a/package.json b/package.json index 52889f6e..eb224800 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ }, "dependencies": { "@openmouse/keyboard-protocol": "1.1.0", - "@openmouse/protocol": "0.45.2", + "@openmouse/protocol": "0.47.0", "lucide-react": "^1.43.0", "posthog-js": "^1.438.17", "preact": "^10.29.8"