feat(lamzu): add the Atlantis generation under CompX vendor id 0x3554 (resolves #85) - #91
Merged
Conversation
The Atlantis family speaks CompX's report-8 interrupt protocol, not the feature-report page/command protocol the 0x373e and 0x37b0 Lamzu models use — the same protocol this package already implements for Pulsar's 4K receiver, so the framing, checksum, command ids and 50-step DPI encoding are imported from there rather than restated. Verified on a Lamzu Atlantis Mini 4K, firmware 1.24, wired 0x3554:0xf50f. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWUM7JqBNz5bjMfs3ZVQKn
- A late reply to a timed-out attempt could satisfy the next request: every flash access shares command 0x08/0x07, so a read of one address could return another's bytes and a setter could verify against the wrong field. Flash replies are now matched on the echoed address. - open() is memoized; two concurrent reads attached two listeners, of which close() removed one. - setDpiStageCount shrinks the cached stage list with the device. - setProfile validates against the four profiles the mouse accepts (0-3 write and read back; 4 and above are rejected with status 1). - A corrupt DPI stage read throws instead of reporting 50 DPI. - Dropped the redundant per-product picker filters: 0x3554 is already requested vendor-wide for the Pulsar 4K receiver. - Discovery tests, and the flash-field evidence table now says which fields the vendor UI confirmed and which were only round-tripped. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWUM7JqBNz5bjMfs3ZVQKn
The Atlantis config collection carries only report 8, the request/reply channel, so startNotifications answers false and the app keeps polling. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWUM7JqBNz5bjMfs3ZVQKn
isSupported gates on an output report 8 on the 0xff02/usage-2 collection, and Chrome's collection shape is not the platform's — so what navigator.hid actually reports is now in the notes rather than assumed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWUM7JqBNz5bjMfs3ZVQKn
setDpiStageColor scaled an unchecked stage index straight into a flash address: stage -8 landed on the DPI stages at 12 and stage 13 on the button actions at 96, with the colour reading back cleanly from wherever it wrote. Every stage index is now bounded before it becomes an address. Also: - Setters hold the queue across write and read-back, so two concurrent calls for one field can no longer verify against each other's writes. - close() cancels: it settles the waiting exchange and stops queued work from reopening the device. - Switching profiles or changing the stage count drops the cached status, which described the old profile or the wrong stage list. - DPI stages decode per axis, so a stage Lamzu's app set to separate x and y no longer reads as corrupt. - The polling-rate family is declared per product instead of inferred from the rate ceiling, which had quietly decided it for the 1K receiver nobody has tested. - Timers come from globalThis, so the client is testable off-browser. - A fake HID device with real flash state now covers the request lifecycle, the setters, and each of the above. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWUM7JqBNz5bjMfs3ZVQKn
The reentrancy flag it replaced could not tell a nested call from an unrelated one: a second caller arriving while an operation awaited a reply read the flag as nesting, ran inline, and overwrote the in-flight exchange's reply callback. Public methods now take the lock exactly once and work through unqueued helpers, so there is nothing to detect. Also: - open() and close() take turns. Overlapping them let a reopen memoize a resolved open while the close was still about to remove the listener, wedging every later open. - setProfile and setDpiStageCount drop the cache before the write rather than after the verification read, which could otherwise fail and leave a cache describing a device that had already changed. - dpiY follows dpi through a stage write and a stage switch instead of keeping the previous stage's value. - The concurrency and close tests now reach the failure modes they name: the second caller starts only once the device has a report in hand and its reply is withheld. Reintroducing the old flag fails the first; restoring it passes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWUM7JqBNz5bjMfs3ZVQKn
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Resolves conflicts in #85, supersedes it.
Original PR by the contributor added a new Lamzu Atlantis driver under CompX's shared vendor id 0x3554 (also used by Teevolution and VGN). The driver's
isSupportedand the touchedsrc/drivers/pulsar/pulsar-hid.tscorrectly exclude Atlantis product IDs by subtraction (LAMZU_ATLANTIS_PRODUCTS.keys()), following the same safe per-vendorId-scoping pattern used elsewhere in this repo (and specifically the pattern that fixes the historical Pulsar/VGN codec-overwrite bug) — no shared codec object is mutated or reused across vendor branches. The only conflict was an additive change tosrc/drivers/registry.tsfrom other PRs merged in the meantime (Incott, HyperX, MCHOSE V3). Resolved by combining both sides' additions; no logic was altered.Verified:
npm run check(build + full test suite + pack dry-run) passes, 1516/1516 tests green.Claude-Session: https://claude.ai/code/session_01JfTcrhgBuFh8QiLvbtdJCX