Skip to content

corsair: phase 2 — live DPI stage, lift-off, and angle-snap writes - #69

Merged
snekxs merged 4 commits into
OpenMouse-Project:mainfrom
kfrancis:feat/corsair-phase2
Sep 7, 2026
Merged

corsair: phase 2 — live DPI stage, lift-off, and angle-snap writes#69
snekxs merged 4 commits into
OpenMouse-Project:mainfrom
kfrancis:feat/corsair-phase2

Conversation

@kfrancis

@kfranciskfrancis commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Follows #66 (read-only NIGHTSWORD RGB driver). Turns CorsairHidClient into a settings client. Every write addresses the live profile (byte 3 = 0), gets no reply, and is confirmed by the matching GET.

Setters, matching the shared stage-editor contract:

  • setDpi(x, y?) rewrites whichever slot is selected, reading its colour first so the RGB-required stage write does not clear it.
  • setDpiStageValue, setDpiStageColor, setActiveDpiStage index iCUE's numbered stages (slots d1–d5). The Sniper slot d0 is never written. A stage whose Y already differs keeps it.
  • setDpiStageCount writes the mask first, confirms it, then seeds any newly enabled slot that holds 0 DPI from the last previously enabled stage. Preserves the Sniper bit; moves the selection back inside the enabled range when shrinking.
  • setLiftOffDistance maps Low/Medium/High onto the raw 1–5 scale (1/3/5 on write; 1–2/3/4–5 on read).
  • setAngleSnapping sends ckb-next's trailing 0x05, which the mouse accepts with or without.

Status now reports dpiStageColors, liftOffDistance with the three stops, settingsReady/valuesVerified true, a dpiStageEditor hint (5 stages, 100–18,000 in 50-DPI steps to keep the option list usable), and showAdvancedSection so the processing card appears. Polling rate stays read-only because that write re-enumerates the device. The identity-only fallback is unchanged.

The driver test's fake device is stateful: GETs answer from the iCUE-loaded fixture big-endian, SETs mutate it little-endian and leave the feature buffer alone, and writes to disabled slots are dropped, so every setter is checked against both the bytes on the wire and the read-back.

Tested on hardware

NIGHTSWORD RGB fw 3.41, Windows 11, Chrome 152, OpenMouse control-panel, iCUE app + service running:

  • Write probe (in captures/corsair-nightsword/write-probe.txt): stage select is live (cursor speed changes), snap works with and without the trailing byte, lift accepts 1–5.
  • App run: DPI 400 / 1,600 / 3,200 on the selected stage, lift Low/Medium/High, angle snap on/off — all read back and took effect.
  • First "stage count 3 → 4" attempt failed: the seed write went to slot d4 while the mask still had it disabled and the mouse dropped it. Fixed by writing the mask first (second commit) and re-run on hardware: 3 → 4 adds a fourth 5,700 stage and reads back, 4 → 3 removes it.
  • One stale GET reply occurred in the wild (asked for d3, got d2's buffer); the echo check caught it and the retry succeeded.

Known limits

  • Lift-off: iCUE has no manual lift-off control for this mouse (only its spiral surface calibration), so there are no vendor labels. The raw 1–5 scale is ckb-next's slider range; Low/Medium/High = 1/3/5 is this driver's own mapping. Whether the calibration pass writes the same byte is still unknown.
  • iCUE does not read live state back, so its panel keeps showing its own profile after a write here, and it re-pushes that profile on its own triggers (profile switch, reconnect). Same volatility as a power cycle; onboard persistence would need Corsair's file-based profile format.
  • Polling rate and the Sniper stage are not exposed.

Checks

npm run check passes (1099 tests).

🤖 Generated with Claude Code

kfrancisand others added 2 commits September 6, 2026 21:18
Turns the NIGHTSWORD RGB driver from read-only into a settings client.
Every write addresses the live profile (byte 3 = 0), gets no reply, and
is confirmed by the matching GET, following the ckb-next layout that
the write probe in captures/corsair-nightsword verified on fw 3.41.
Setters, matching the shared stage-editor contract:
- setDpi(x, y?) rewrites whichever slot is selected, reading its colour
first so the RGB-required stage write does not clear it.
- setDpiStageValue / setDpiStageColor / setActiveDpiStage index the
numbered stages (slots d1–d5); the Sniper slot d0 is never touched.
A stage whose Y already differs keeps it.
- setDpiStageCount seeds never-populated slots from the last enabled
stage before enabling them, preserves the Sniper bit in the mask, and
moves the selection back inside the enabled range when shrinking.
- setLiftOffDistance maps Low/Medium/High onto the raw 1–5 scale
(1/3/5 on write; 1–2/3/4–5 on read) until iCUE's Surface Calibration
labels are captured against it.
- setAngleSnapping sends ckb-next's trailing 0x05, which the mouse
accepts with or without.
Status now reports dpiStageColors, liftOffDistance and the three
supported stops, settingsReady/valuesVerified true, a dpiStageEditor
hint (5 stages, 100–18,000 in 50-DPI steps to keep the option list
usable), and showAdvancedSection so the processing card appears.
Polling rate stays read-only because the device re-enumerates on that
write. The identity-only fallback is unchanged.
The driver test's fake device is now stateful: GETs answer from the
iCUE-loaded fixture state big-endian, SETs mutate it little-endian and
leave the feature buffer alone, so every setter is checked against
both the bytes on the wire and the read-back.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The first phase-2 hardware run failed "Set DPI stage count to 4" with
"kept 0 DPI on stage 4": the driver wrote slot d4 while the mask still
had it disabled, and the mouse dropped the write. Write the mask first,
confirm it, then seed any newly enabled slot that holds 0 DPI. The test
fake now drops writes to disabled slots so the old order fails there
too, and the run's other findings (a stale reply caught by the echo
check, iCUE not reading live state back) are recorded in PROTOCOL.md.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@kfrancis
kfrancis marked this pull request as ready for review September 7, 2026 01:23
@kfrancis

Copy link
Copy Markdown
ContributorAuthor

Re-ran the stage-count change on hardware with the mask-first ordering (fw 3.41, iCUE running): 3 → 4 adds a fourth 5,700 stage and reads back, 4 → 3 removes it. Marking ready.

kfrancisand others added 2 commits September 6, 2026 21:30
…iCUE
iCUE has no manual lift-off control for the NIGHTSWORD, only the spiral
surface-calibration pass, so there are no vendor labels to map the raw
1–5 byte against. The scale is ckb-next's slider range; Low/Medium/High
= 1/3/5 is this driver's definition. Comment and notes only.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Polled 0e 13 03 00 twice a second through a complete spiral calibration
pass in iCUE (fw 3.41); the lift byte stayed at 5 throughout. Surface
tuning is therefore a separate command, still to be captured.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@snekxs
snekxs merged commit 6c9df95 into OpenMouse-Project:mainSep 7, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@kfrancis@snekxs