Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ checklist.
| Incott | `@openmouse/protocol/incott` |
| K-snake | `@openmouse/protocol/ksnake` |
| Keychron | `@openmouse/protocol/keychron` |
| Lamzu / CRDRAKO / Attack Shark | `@openmouse/protocol/lamzu` |
| Lamzu / CRDRAKO / Attack Shark / LunaFury | `@openmouse/protocol/lamzu` |
| Logitech | `@openmouse/protocol/logitech` |
| MCHOSE | `@openmouse/protocol/mchose` |
| Microsoft | `@openmouse/protocol/microsoft` |
Expand All @@ -84,6 +84,11 @@ An exported protocol means OpenMouse implements that wire format. It does not
claim every mouse from that brand works. When a catalog provides a `verified`
field, use it to distinguish hardware-tested support from USB recognition.

LunaFury uses the shared Lamzu/CompX driver at `@openmouse/protocol/lamzu`.
LUNA33 firmware `0.0.26.0` has owner-confirmed cable/8K coverage; TYPE33 and
bottom-button behavior are not hardware-verified. See the
[support and testing notes](docs/lunafury-testing.md) for the verified scope.

The Ninjutso catalog and packet layouts are derived from the JavaScript shipped
by the official NinjaForce WebHID panel. They have automated transport and
codec coverage, but are not marked as hardware-verified until tested on the
Expand Down
180 changes: 41 additions & 139 deletions docs/lunafury-testing.md
Original file line number Diff line number Diff line change
@@ -1,155 +1,57 @@
# LunaFury LUNA33 / TYPE33 protocol evidence
# LunaFury support and testing

This integration is based on the public LunaFury WebHID configurator at
<https://mouse.lunafury.games/>. The site and its source map were inspected on
2026-10-05:
Packet definitions follow the public [LunaFury configurator](https://mouse.lunafury.games/)
and its `XviUpdater` implementation. Vendor scripts are not included.

- `/js/app.14b76f96.js` and `/js/app.14b76f96.js.map` contain the device table
and UI-side command calls.
- `/js/output.xvi3.min.js` contains the `XviUpdater` transport and the concrete
feature-report packets.
## Devices

The owner completed full-function testing on LUNA33 with firmware
`0.0.26.0`, over both cable and 8K receiver connections. This verifies the
LUNA33 runtime identities `0x0032` and `0x0033` on that firmware. TYPE33
identities and packet layouts are source-verified but have not been tested
on hardware.
Runtime VID: `0x373e`. Bootloader identities are not supported here.

The owner has not supplied separate results for power-cycle persistence,
other firmware versions, or native-Bridge end-to-end testing. The stated
Corded/20 kHz behavior has not been independently measured.

## Runtime identities

All devices use the shared CompX VID `0x373e`.

| Model | Connection | PID | Polling rates exposed by LunaFury |
| Model | Cable PID | 8K receiver PID | DPI stages |
| --- | --- | --- | --- |
| LUNA33 | cable | `0x0032` | 125, 250, 500, 1000 Hz |
| LUNA33 | 8K receiver | `0x0033` | 125, 250, 500, 1000, 2000, 4000, 8000 Hz |
| TYPE33 | cable | `0x0054` | 125, 250, 500, 1000, 2000, 4000, 8000 Hz |
| TYPE33 | 8K receiver | `0x0084` | 125, 250, 500, 1000, 2000, 4000, 8000 Hz |

The configurator also lists `0xb032`, `0xb033`, `0xb054`, and `0xb084` as
bootloader PIDs. They are intentionally excluded: firmware-update identities
must not be claimed by the normal settings driver.

Both models advertise a 30,000 DPI ceiling. LUNA33 exposes up to six DPI
stages and TYPE33 up to five. The stage-read request carries that model limit,
so each product profile supplies the correct maximum while the reply still
determines the active stage list at runtime.

## Protocol match

The configurator sends report ID 0 with a 64-byte payload. That is the same
CompX/XVI envelope implemented by `src/compx/codec.ts` and
`src/drivers/lamzu/hid.ts`.

| Setting | Read | Write / encoding |
| --- | --- | --- |
| Firmware | page `0x00`, command `0x81` | read only |
| Battery | page `0x00`, command `0x83` | read only |
| Active profile | page `0x00`, command `0x85` | read only |
| Polling rate | page `0x01`, command `0x80` | command `0x00`; `08/04/02/01/20/40/80` = 125 through 8000 Hz |
| Sleep | page `0x00`, command `0x87` | command `0x07` |
| Debounce | page `0x00`, command `0x88` | command `0x08` |
| Lift-off distance | page `0x01`, command `0x88` | command `0x08`; `87/01/02` = low/medium/high |
| DPI stages | page `0x01`, command `0x81` | command `0x01` |
| Angle snap | page `0x01`, command `0x84` | command `0x04` |
| Motion sync | page `0x01`, command `0x89` | command `0x09` |
| Ripple control | page `0x01`, command `0x8a` | command `0x0a` |
| Competitive Mode / 竞技模式 | page `0x01`, command `0x8b` | command `0x0b`; vendor `setHyperMode`, shared field `hyperMode` |
| Tracking Mode / 追踪模式 | page `0x01`, command `0x93` | command `0x13`; vendor `setTrackingMode`, shared field `performanceMode` |
| Sensor angle | page `0x01`, command `0x94`, length 2 | command `0x14`; signed byte, −30° through +30° |
| Lightning Trigger | page `0x00`, command `0x98`, length 4 | command `0x18`; 0 = off, 1 = left priority, 2 = right priority |
| Per-button latency | page `0x00`, command `0x92`, length 19 | command `0x12`; button IDs 1/2/3 = left/right/middle |
| Wheel anti-mistouch | page `0x00`, command `0x99`, length 4 | command `0x19`; enable byte and big-endian window in milliseconds |

The cable transport rewrites mouse target `0x02` to target `0x00`. The 2.4 GHz
receiver keeps mouse commands on target `0x02`, while receiver firmware remains
on target `0x00`. These are represented by the product profiles rather than a
forked LunaFury protocol implementation.

### LunaFury-specific controls
| LUNA33 | `0x0032` | `0x0033` | 1-6 |
| TYPE33 | `0x0054` | `0x0084` | 1-5 |

LunaFury labels follow its official `language.js` and `patternModule.vue`:
`angleSnapping` is 直线修正, `hyperMode` is 竞技模式, and `performanceMode`
is 追踪模式. Motion Sync and Ripple Control use 运动同步 and 波纹控制 in
Chinese. These are display overrides for LunaFury only; commands and labels
for other brands remain unchanged.
The shared Lamzu/CompX driver provides button polling, three onboard profiles,
DPI stage/count/X-Y controls, button mappings, sleep, 8K receiver lighting
and bottom-button power/DPI modes. Button polling is separate from USB polling.
Custom DPI colors are not exposed.

The owner clarified the hardware semantics on 2026-10-05: Competitive Mode
is Corded mode and Tracking Mode locks sensor sampling at 20 kHz. This
description is owner-provided, not an independently measured result.
The UI mirrors the vendor's `v-if="competition"`: tracking is shown only
while competitive mode is confirmed on. Hiding it preserves its stored
value. For staged changes, competitive mode enables before tracking edits
and disables after them; other brands retain their existing ordering.
Bank operations are serialized. Writes verify readback and preserve dormant
DPI slots and reserved bytes. Complete DPI storage supports Games restoration;
it is not a saved or importable Games setting.

`XviUpdater.hidIndex` is 1: received WebHID buffers do not include the report
ID. Decoders use the payload after the six-byte CompX header. Lightning mode
and sensor angle are payload byte 1; button ID is byte 2 and button latency
is bytes 3–4; wheel enable is byte 1 and its window is bytes 2–3.
## Hardware verification

Left/right latency uses 0–15 ms in 1 ms steps; middle-button debounce uses
1–30 ms. The 19-byte latency write payload is
`[profile, 0, buttonId, hi, lo, 0, 0, hi, lo, 0, 0, 0, 20, 0, 0, 0, 20, 0, 0]`.
Wheel guard uses 20–200 ms in 20 ms steps. A disabled zero window is a valid
read, not a guessed default; enabling it from that state proposes 100 ms.
Writing a disabled zero window is supported so restoring a Games profile
does not invent a different prior value.
The owner verified LUNA33 firmware `0.0.26.0` over cable and 8K:

All added setters read back the value and reject a mismatch. Optional reads
that fail or return invalid values leave their controls absent, while basic
device settings remain available. Only the LunaFury product profiles issue
these extra commands; other CompX brands are unchanged.
- Button polling, all three onboard profiles and DPI stage/count/X-Y controls.
- Mouse, scroll, DPI, media and keyboard mappings.
- Sleep/Never, reconnect readback and all four 8K receiver light modes.
Cable connections do not expose receiver lighting.

LunaFury extension reads accept a reply only when its payload echoes the
requested profile; button-latency reads also match the button ID. A successful
reply for another profile or button is ignored while the same request waits
for a matching reply, within its existing attempt limit. This applies to both
initial reads and write readbacks. Generic CompX requests and write
acknowledgments retain their existing matching rules.
TYPE33 and bottom-button behavior are not hardware-verified.
Games apply/restore has automated coverage only.

The UI places Lightning Trigger, independent left/right latency, middle
debounce, and wheel guard on the Buttons tab. Sensor angle is in Processing
on the Performance tab. As in the vendor UI, left/right priority disables
the corresponding latency slider. Turning priority off is applied before
staged latency edits, and enabling priority is applied after them. These
settings can also be saved in Games profiles as a partial LunaFury namespace.
Each profile restores only the controls it changed, preserving unrelated
settings and valid zero values.
## Automated checks

## Required hardware check
Build, package checks and all 2,474 protocol tests pass, including both
exhaustive registry checks. The 162 Lamzu driver/codec tests also pass with
real timers. Coverage includes the four runtime PIDs, bank isolation, DPI storage,
write/read failures and retry recovery. Runtime audit reports no vulnerabilities;
development and release dependencies still have audit findings.

Connect each available cable/receiver identity in Chromium and confirm:
The paired app passes its build, 511 tests, bundle checks and audit with the local
protocol package. Games and manual Apply tests cover complete restoration,
untrusted status, write ordering and retained edits after failure.
Synthetic HID and Bridge callbacks do not replace hardware testing.

1. OpenMouse labels it `LunaFury LUNA33` or `LunaFury TYPE33`, never Attack
Shark.
2. Firmware, battery, DPI stages, polling, LOD, sleep, debounce, motion sync,
angle snap, and ripple control read successfully.
3. Change one reversible setting at a time, read it back, then restore the
original value.
4. Confirm only the rates in the table are offered for that connection.
5. Confirm the driver refuses bootloader/DFU identities. The shared broad
VID filter may still let Chromium show them in its native picker; do not
select or send settings commands to an upgrade-mode interface.
6. With Lightning Trigger off, test independent left/right latencies and
middle debounce, refresh/reconnect, and compare their values with the
vendor configurator. Test each priority mode separately, restoring the
initial mode afterward.
7. Test sensor angles −1°, 0°, and +1° first. Confirm direction and persistence
on hardware, then restore the original angle.
8. Test wheel guard off and on at 20/100/200 ms. Confirm the actual firmware
behavior, then restore both the original enable state and window.
9. Save a Games profile with a single LunaFury control, apply it, and restore
the prior settings. Confirm unrelated controls remain unchanged, including
the originally disabled zero wheel window if reported by this firmware.
10. Confirm Competitive Mode controls Tracking Mode's visibility, including
staged on/off changes, without silently resetting its stored value.
## Integration

Automated coverage includes all four runtime IDs, cable/receiver targeting,
profile 3, full packet fixtures, signed angles, invalid ranges, unsupported
reads, ignored writes, and the other-brand guard. LUNA33 hardware testing
is recorded above; TYPE33 still needs a real-device pass. The owner's
full-function report does not include item-by-item results for this checklist.
Merge and publish the protocol before upgrading the app's exact npm dependency.
After pinning the published version, the app's `CI / check` job runs `npm ci`,
`npm run build`, `npm test`, `npm run size` and `npm audit --audit-level=high`
against registry-only dependencies. Local paired-package checks do not replace
this gate.
Both branches require upstream CI before merge.
See the [application setup](https://github.com/sekiranunn/openmouse/blob/codex/lunafury-onboard-controls/docs/lunafury-development.md).
4 changes: 3 additions & 1 deletion src/drivers/lamzu/hid.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,9 @@ test("LunaFury LUNA33 and TYPE33 use the CompX driver with their own brand", asy
} else {
const dongleFirmware = sent.filter((packet) =>
packet[2] === 0x00 && packet[4] === 0x00 && packet[5] === 0x81);
const mouseRequests = sent.filter((packet) => !dongleFirmware.includes(packet));
const lightReads = sent.filter((packet) => packet[4] === 2 && packet[5] === 0x80);
assert.ok(lightReads.every((packet) => packet[2] === (productId === 0x0084 ? 1 : 0)));
const mouseRequests = sent.filter((packet) => !dongleFirmware.includes(packet) && !lightReads.includes(packet));
assert.equal(dongleFirmware.length, 1);
assert.ok(mouseRequests.length > 0);
assert.ok(mouseRequests.every((packet) => packet[2] === 0x02));
Expand Down
Loading
Loading