<!-- Source: https://medius.k4tech.net/dashboard/changelog -->
# Medius firmware changelog

## v3.4.6

16 Oct 2026

### Changes
- Devices now sleep and wake with the PC correctly
- Device STALLs now pass through to the PC correctly
- Devices with lots of interfaces, endpoints or alternate settings no longer require IMPERFECT
- Devices now connect ~200ms faster
- Improvements to query latency (status queries are now on-par with stock firmware)
- Improvements to native report latency (~0.05ms)
- More memory headroom on models without PSRAM
- Added a full comparison against the MAKCU stock firmware to the website
- Overhauled the website, with a new landing page, guide and help section
- Added search across the whole website
- Added link previews for pages, help answers, releases and devices
- Added copy-link buttons across the website and dashboard
- Added an Options guide explaining what every option does
- Added a Flash page to the dashboard (releases, files and MAKCU stock firmware, over USB2 or ROM download)
- The dashboard now offers the fix a connected device needs (ie, IMPERFECT, the Logitech fix)
- Boxes on older firmware (v3.2.0 to v3.4.1) can now use the Options tab in the dashboard
- Connection errors in the dashboard now say why, and Retry no longer needs the port picked again
- Added firmware updates to the C/C++ and Python bindings
- Streams can now be closed from another thread in the library and bindings (ie, to stop a thread waiting on input)
- The C ABI (medius-capi) is now published on crates.io, for Rust projects that offer a C API built on Medius
- Fixed an issue where typing a long string could drop characters
- Fixed an issue where a fast burst of commands could be lost (and leave a key held down)
- Fixed an issue where commands could be lost while a device was being plugged in or unplugged
- Fixed an issue where rendered movement may not add up to what was sent while the mouse was moving
- Fixed an issue where quick back-and-forth rendered movement could lose a few counts
- Fixed an issue where a CLIP's first click after a pause could land a frame late
- Fixed an issue where a 1khz keyboard could run at half rate beside slower endpoints (ie, Wooting keyboards)
- Fixed an issue where movement could arrive 1ms late under heavy load
- Fixed an issue where a single late report could leave a device a poll behind for up to 3 seconds
- Fixed an issue where IMPERFECT devices could lose bulk packets
- Fixed an issue where HEALTH could report the rate as confident with no mouse bound
- Fixed an issue where closing a device or stream in Python while another thread was using it could crash
- Several other smaller improvements to stability and performance

### Notes
Devices now go to sleep with the PC like they would plugged in directly, and only wake the PC the way they normally would (ie, a mouse that only wakes on click won't wake it on movement anymore)

The website also got a massive overhaul this update, including a full side-by-side comparison against the stock firmware: https://medius.k4tech.net/comparison

The C/C++ bindings have a new ABI version (10), so rebuild against the new header if you use them

### Commits

**Firmware**
- docs: a stock defect worded so site search still puts Help's answer first for a mouse that won't move (07d5c95)
- ci: the firmware zip goes on the GitHub release as well as the Discord post (87db760)
- docs: release notes for 3.4.6 (d6171e2)
- tools: gate docs, rig notes and test comments read on first pass (24db7a5)
- firmware: comments read on first pass (c349253)
- docs: the spec, write-ups and comparison read on first pass, with symbols for counts and limits (d89e5ac)
- tools: gate docstrings, rig notes and test comments in the project's words (77de5dd)
- firmware: comments in the project's words, and the ones that had the code wrong (86e8c59)
- docs: the protocol spec, architecture and comparison write-ups in the project's words, and the facts they had wrong (3ada641)
- docs: the suppression gate needs a device that sends nothing unprompted (ed20cb8)
- docs: the protocol doc, README, PSRAM page and detection notes follow 3.4.6 (cdfd496)
- docs: comparison rerun on the final build, with report latency, the lead and bulk data on Medius brought up to date (b6695e9)
- docs: the design record, release notes and gate list follow the bulk data fix (9daf47e)
- firmware: a device's bulk data reaches the PC whole, however late or slowly the PC reads it (7457c41)
- docs: the Discord foundation's rules post ends on rule 5 (3b5f48e)
- docs: the Discord rework's embeds and panels in the site's blue, and the comparison's benchmarks (9193c6d)
- tools: the release post's embed is in the site's blue, its title linking the release (97475a5)
- docs: the Discord rework's embeds follow the bot's look (bc4e32a)
- tools: the release post's text is its pings, the notes an embed titled with the release (967f32b)
- docs: the Discord rework's bot posts are embeds (503f9f0)
- tools: the release's Discord post is an embed, with the ping and the credits' thanks in its text (2b78491)
- docs: the design record, release notes and bench notes follow the 400 µs lead and the shorter wait after a late report (a302c08)
- firmware: every report the box relays is readied ahead of its other work, not only the mouse's (173dc14)
- docs: the Discord rework adds a comparison channel holding the site's summary (3bde844)
- firmware: one late report no longer leaves the reports after it a poll late for seconds (fdcab3f)
- firmware: the mouse's reports keep their poll under heavy load, and reach the PC 50 µs sooner (5044c5d)
- docs: comparison has no pending tests left; the three that need Windows are listed as not covered (47ca3c9)
- docs: comparison counts reports carrying only injected motion beside a varying mouse, and shows each clone's endpoint 0 reply time (df7ed4e)
- docs: comparison lists stock's 1 ms mouse interval as a defect on every row it wins (02a91e7)
- tools: comparison checks take a defect on every row it decides, and the device matrix has 2 ms and 4 ms mice (263e809)
- docs: comparison report latency rerun, p99 in place of the slowest report (f3746bc)
- tools: report latency script places the emulator's clock per window and follows its held-back intervals (5953efb)
- docs: comparison report latency on both boxes at 125 Hz to 1 kHz (4163514)
- tools: comparison script timing every native report from the emulator to the PC, on both boxes (401aab8)
- docs: comparison click timing rerun on the clip fix (0b2d552)
- firmware: a clip's clicks and keys reach the PC as far apart as they were played, the first after a pause included (328db02)
- docs: comparison rebuilt on one hub with Medius 3.4.6 final, the pending tests measured on both boxes (7751eb7)
- tools: comparison scripts give Medius the same fast and back-to-back legs as stock, and keep a clone that reconnects (eb32686)
- firmware: every interface number, setting and endpoint a device has is driven, and a stream polled every frame keeps its rate beside a slower one (43f9538)
- tools: comparison scripts for interface data, intake limits, typing, sleep and timing legs; same-hub evidence and its manifest (8ddbc03)
- docs: v3.4.6 sleep, halt, render debt and link fixes in the design record, protocol and release notes (2f353a4)
- tools: gates find this box's clone by its USB port as well as its ID, and the suspend and halt gates check more (1c7907a)
- firmware: the PC's sleep and a device's halts pass through, rendered debts are delivered, and a device change loses no link frame (aa833ee)
- docs: v3.4.6 faithfulness fixes design record and release notes (0d37dfd)
- tools: a clone lookup never takes another box's clone (25c7c2b)
- firmware: an endpoint the mouse stops reaches the PC, and the device chip keeps one snapshot (825a402)
- firmware: rendered moves deliver exactly what was sent, with no count against them (49edc1d)
- docs: comparison results rebuilt as direct like-for-like rows, with defects and pending tests (d03373e)
- docs: comparison results: relay reruns, Equal by overlapping ranges, defects that reproduce, pending tests for readers (8b70cef)
- docs: comparison smoothing group described in fewer words (b288474)
- docs: comparison results format: unmeasured chart points, defects on rows of results, stricter checks; seed refreshed (da7b336)
- docs: comparison results in one checked file, for the site (1b87cad)
- docs: comparison section design (dbdc893)
- docs: dashboard controls design record (f7f2945)
- docs: v3.4.6 render totals and query replies (36d86fc)
- tools: wire-level timing for the comparison, and the transform gate reads a reader overflow as blocked (8cb9f99)
- firmware: the control port answers queries sooner (a381d09)
- firmware: rendered motion keeps its totals with every report the model's (375710d)
- release v3.4.6 (0e0175b)
- docs: v3.4.6 design record, release tables and protocol change log (8ac0726)
- tools: the transform gate reads its own box's clone (ed59ffc)
- firmware: devices enumerate on the USB specification's reset timings (b93a834)
- firmware: typed strings arrive whole, and bursts of commands are kept (de854a4)
- firmware: rendered moves add up while the mouse moves (2cd6524)
- docs: comparison wording (4229ddc)
- docs: comparison refusals with the mouse moving, readers in their own processes, hub timing (c65622c)
- docs: comparison move sizes, text clumps and the reworked lock check on stock (b728b24)
- docs: comparison move acceptance by form and pacing, lock checks, native rate triggers (c055649)
- docs: comparison stock re-runs after power cycles, native rate while injecting, parser trigger (6875489)
- docs: comparison second review: trigger order, interval rule, render evidence, imperfect coverage (f0f1ff8)
- docs: comparison data path checks (fad19f0)
- docs: comparison report descriptor limits and endpoint 0 shapes (4a6c80c)
- docs: comparison corrections, Medius fastest-settings and cold-start runs, stock re-run script (ea6672b)
- docs: the stock firmware comparison: rig, measurements and research (bff4704)
- tools: quiet_clone and clone_guard cover two clones on one PC (9ee97ab)
- docs: the dashboard's Flash page and one-count flash meter (b6d4856)
- docs: the ROM client spec's erase before the write and its smaller blocks (970fa9c)
- docs: the device feed's first post and the changelog's history in the Discord design (e2c8c4e)
- docs: the ROM client spec's stock done screen, one line (ba5c3ae)
- release notes: a refused Discord post prints Discord's answer (0c4a7a2)
- docs: the dashboard's boot ROM client, spec and plan (b4a289d)
- docs: release posts by Clanker and the device feed in the Discord design (79bd102)
- release notes: the Discord post comes from the server's bot, not a webhook (60fb701)
- docs: the honeypot and bot settings in the Discord spec (8562eb9)
- docs: Home under Support in the Discord layout (e87073b)
- docs: the Server Tag in the Discord checklist (36866d9)
- docs: what the Discord spec holds now (08f9cad)
- docs: the Discord Apply checklist (3507bba)
- docs: options spec, the stock erase in regions (ffb0cfd)
- release notes: ping a release role in place of @everyone when one is set (414f00b)
- docs: Discord onboarding beside the captcha (d29730d)
- docs: place addresses spec and plan, as built (91c0a9c)
- docs: Discord layout after the second review (ddca522)
- docs: Discord rework after review (5a7d5c7)
- docs: options spec, project terms (fc385e5)
- docs: Discord rework design (0623ddf)
- docs: options spec, details as tables (31b5023)
- docs: rule 2 is the rule as written (2299430)
- docs: options spec, every option a section (6a04047)
- docs: options spec, links both ways and plain sections (ec9691d)
- docs: Discord foundation spec as built (b1d3e86)
- docs: options page and dashboard fixes spec and plan, as built (5b95e15)
- docs: Discord foundation spec, build changes (075af56)
- docs: Discord foundation plan; spec binds by name (1b9bfe9)
- docs: Discord foundation spec (7296017)
- docs: deferred minors spec and plan, as built (0de35d1)
- docs: copy link spec as built (1ae6b55)
- docs: copy link spec and plan (6431caa)
- release notes: no preview for any other link in the post (f2e8b1a)
- release notes: link the release's page, so its card shows (d349667)
- docs: plan wording (ef24270)
- docs: link preview plan; theme-color set before first paint (95bfe26)
- docs: link preview spec and the approved cards (975886b)
- docs: site loading spec as built; search spec follows it (5127cd9)
- docs: site search spec as built (049759f)
- docs: site search spec and plan (be54efa)
- docs: site surfaces implementation plan (ac49efd)
- docs: surfaces mockup, approved version (5a77bb8)
- docs: surfaces mockup, eighth version (4f21c4e)
- docs: surfaces mockup, seventh version (da90739)
- docs: surfaces mockup uses the full page width (0910fcc)
- docs: surfaces mockup, fifth version (cad1657)
- docs: surfaces mockup, fourth version (7a3f54c)
- docs: surfaces mockup, third version (21fe54e)
- docs: design mockup for the site's remaining surfaces (9951773)
- tools: closer injection bursts in the site feed capture, and no capture date (eb72838)
- tools: site capture rigs read the run exactly and restore every option they change (fc314f6)
- tools: descriptor and report capture for the docs site (d01f12c)
- docs: plan wording (0a95b59)
- docs: site theme and user pages plan (8c13329)
- docs: site theme and user pages spec (c4a6fe7)
- docs: the approved site design mockup (c213605)
- docs: every dashboard page is indexed (8edef13)
- docs: the search foundation spec as built (35d4814)
- tools: release notes refuse unknown sections and stray mentions; long posts split cleanly (42821b3)
- docs: wording in the search foundation spec and plan (371a670)
- docs: release notes for v2.2.1 to v3.4.5 (d8ea65d)
- tools, ci: release notes written once, posted to GitHub and Discord (a572a8e)
- docs: plan for the docs site's search foundation (304274c)
- docs: design for the docs site's search foundation (6b7013f)

**Library**
- docs: doc comments, the C header and Python docs read on first pass (c8dc355)
- docs: doc comments, the C header and the Python docstrings in the project's words, and the facts they had wrong (88f2073)
- library 3.4.6: close a stream from another thread, the C ABI on crates.io, and docs that match the code (5260727)
- docs: the library follows firmware 3.4.6: what relay_drops counts, sleep, STALLs, typing capacity and the RAW IN queue (042a40c)
- library: mock refuses an activate or reboots after one, tests for the abort and the confirm retry (390deeb)
- python: Device and ClipHandle close safe across threads (13e2dd3)
- python, capi: closer to the crate, safe stream close across threads, firmware update and confirm through the crate (881a342)
- capi: MediusDevice::into_raw, a handle over a Device that's already open (0d7e773)

**Docs**
- comparison: data synced; search for a mouse that won't move finds Help's answer again (017498a)
- comparison: a count out of a total reads 6/10 (a109fb7)
- site: every page reads on first pass, with symbols for counts and limits (30cfe39)
- docs: comparison wording synced from the firmware repo: native rate, emitted reports, relaying (6335da1)
- docs: every page and the dashboard in the project's words, and the facts they had wrong (d72fe58)
- docs: library 3.4.6: closing a stream from another thread, firmware update and confirm in every language, and the mock's update knobs (cf7e706)
- docs: the site follows firmware 3.4.6: sleep, STALLs, typing capacity, what relay_drops counts and when a transfer gets no answer (ec1cbbc)
- test: the stats page's dates are checked in the locale the tests run under (2df307e)
- docs: the comparison follows the final firmware build, and the RAW page describes how bulk IN fills its queue (066ed8b)
- feat: the comparison JSON carries a few benchmarks with both boxes' values, and every area's page (f8a3694)
- feat: the comparison JSON counts the results each box is ahead in, by area (487b38a)
- fix: the comparison JSON is built once per deploy (dba8df8)
- feat: the comparison's Overview as JSON, for the Discord bot (ae4be1a)
- comparison: the Method page drops its pending tests section (e4f07d6)
- comparison: injected-only reports beside a varying mouse, and endpoint 0 reply times (cdbcf84)
- site: links land clear of the bar and light the place they go to, and the settings table's headers wrap over their columns (0c03139)
- comparison: a defect marks every row it decides and links to its own place in the list (b521746)
- comparison: report latency rerun (ac08092)
- comparison: report latency section, led on the Overview (6c7aca3)
- comparison: click timing on the clip fix (86c7316)
- comparison: results from the one-hub final pass, the pending tests added, and the rig described as one hub (9fb2033)
- docs: comparison pages rebuilt as direct like-for-like results; headings and in-page links fixed (682bbeb)
- docs: comparison results refreshed; Method states the Equal rule and what interval sd measures (4412cf5)
- feat: a Comparison section: MAKCU's stock firmware against Medius, area by area (7c3acdc)
- docs: the privacy page carries what the law asks for, in the site's voice (5169636)
- fix: rate limits drop an address when its window ends, and count each visitor rather than the proxy (fbba2e2)
- feat: a Flash page that writes either chip through its ROM, older releases, and dashboard controls that match the site (d539a57)
- docs: update firmware version strings to 3.4.6 (5a216b0)
- test: comment wording in the Devices JSON test (405f9d9)
- test: each device's address in the Devices JSON is its own row's card (a0aabd5)
- fix: the privacy page dates a scam signature from the last scam post that added it (30c425e)
- feat: each device in the Devices JSON carries its card's address (c1dca68)
- feat: the Devices list as JSON, for the Discord bot (21eb8fa)
- fix: the privacy page keeps scam signatures for a year, as the bot does (161c3f2)
- fix: the privacy page names scam signatures as the bot keeps them (50b709d)
- feat: a copy of a box's stats on request; the privacy page covers copies, verifications, signatures and DMed transcripts (875c68e)
- fix: the stock firmware relay follows MAKCU's toolkit 1.15.20 (b9fe229)
- fix: the stock source reads as the release does, plainer fix offers, a stock erase as MAKCU's toolkit does it, and a fuller privacy page (4fdc7ba)
- feat: privacy page, a footer link to it, and a stats delete command (ed419c8)
- feat: an address and a link card for every section, tab and panel (c210347)
- docs: emit, ride and relay in place of paraphrases across the site (e28100b)
- docs: option details as tables with measured delays, not prose (da96a00)
- docs: Rendered motion and Wire rate as sections of the Options guide (2ebbb90)
- docs: plain option explanations with links back to the dashboard, and noun headings across the reference (13ba603)
- docs: plainer wording across the reference pages, comments and test names (24c9f54)
- fix: review findings for the Options page, the fix offers and the stock firmware relay and flash (d65f947)
- docs: Help sends owners to the dashboard for older boxes and MAKCU's stock firmware (63903b1)
- feat: Update's Manual tab can put MAKCU's stock firmware back on either chip (a08c74d)
- feat: the server relays MAKCU's signed stock firmware, checked as MAKCU's toolkit checks it (d3287ee)
- feat: the dashboard offers the fix a connected device needs, in place (bef0948)
- feat: boxes on firmware from v3.2.0 get the options their firmware has (b83f9fe)
- feat: an Options page in the Guide, linked from each option on the dashboard (864ed80)
- fix: review findings: a poll that stalls after a flash, the Devices twin's links, tab spacing per strip, and the feed on browsers that round (97be654)
- fix: no page scrolls sideways at 320 px, tab strips fit before they scroll, and Copied stays clear of tabs (8c92a13)
- fix: the landing's feed and descriptor are right before the app runs, pause under the pointer, and take the keyboard (2aa8ac3)
- fix: a link to an option says how to reach it before a box is connected (0de7f3c)
- test: crawlcheck fails a doubled or missing link-preview tag, and one naming another page (c7b11a7)
- fix: link cards draw every Latin letter off the main thread, item pages carry their own data, and drafts stay off the changelog (33762e7)
- fix: an unplugged box waits for its port without timers, and Reconnecting reads as a status (af7a5ea)
- fix: link icons take no room until shown, keep headings' names, and Copied never widens the page (b921cb9)
- docs: the link icon and Anchor in CLAUDE.md (e068230)
- fix: the open tab's Copied stays inside the tab bar (d50cf64)
- test: the build fails on a place search can land on with no link icon (a2880ea)
- feat: copy the link to the open dashboard tab (fc844d6)
- feat: copy the link to a Help answer, device or release (4d5c7ba)
- feat: a copy link icon on every section, panel and marked block (aa606cd)
- fix: one release date for every reader, page and card (d884d2a)
- docs: Pulsar ZywOo The Chosen Mouse reported working (675f071)
- docs: six more devices reported working, four confirmed on v3.4.5 (85a9319)
- fix: link cards kept by what they show, unknown items stay a 404, release lookups answer within 5 s (513e5fb)
- test: crawl every link card and item address (0cfbc07)
- fix: fill the changelog and stats blocks as the prerender now captures them (683aef8)
- feat: serve link cards and item addresses (c596cf4)
- feat: link card tags on every page, and addresses for one Help answer, release or device (6ab40f3)
- feat: draw link cards as PNGs on the server (bc87a87)
- feat: what each link card says, and addresses for single items (71ff1ec)
- feat: link card fonts and text layout (798032a)
- fix: one top bar and grid for the site, scroll kept per page, motion that never plays twice (da5197e)
- perf: split the site's code per page and fetch search on use (2653d39)
- feat: site search built from the pages themselves (514fb1c)
- feat: rebuilt site search (1b90eb8)
- fix: the box diagram matches the board (a298aa7)
- fix: the safety clear's caption is one short line (1845204)
- feat: one motion for every page (0fb5d52)
- fix: the dashboard's rare states, each rendered and measured (07ef510)
- fix: the 404's description names the links it carries (df1517d)
- fix: the 404 page prerenders again (237b076)
- fix: dashboard controls as designed: sliders, steppers, buttons and links (aace53a)
- fix: review findings across the dashboard and the Guide (432b849)
- docs: pages open on their title and first paragraph, with no subtitle (465420c)
- feat: the 404 names the address asked for and where to go instead (55e44e2)
- feat: the Guide in three pages: Start here, Devices and Help (e2ceb4c)
- feat: Changelog with each version beside its notes, and commits grouped by repo (418a637)
- feat: Stats on tabs, with charts that read out the column or row under the pointer (bade974)
- feat: Control on tabs, Update with a manual flash tab, and Set up as one flow (c05ee44)
- feat: the Device page on tabs: overview, options and a log to copy or save (8211222)
- feat: the box drawn from its board, for wiring and install steps (07b0009)
- feat: segmented choices, a filterable dropdown and a log for the dashboard (dd94a12)
- feat: page tabs and panels for the dashboard, and pages that use the full width (f035a3b)
- chore: a box that answers without hardware on the dev server (25bcb11)
- feat: the landing feed shows a burst of injected motion every few seconds, and no capture date (6a26bc8)
- feat: the landing feed recaptured with exact injected-byte marks (9278d64)
- fix: the landing intro carries on when the app takes over, and descriptor panel fixes (a6f6ce7)
- fix: landing and changelog answer within seconds while GitHub or Discord hang (0130bec)
- fix: plainer Troubleshooting lead and search description (a3fe643)
- fix: the custom scrollbar shows in Chrome, square and blue on hover (ba7b2c6)
- fix: the landing feed keeps every byte of a long report on screen at any width (bfec47c)
- test: crawlcheck holds every page to one h1 and a footer, and no hidden reveals (1fa1d71)
- feat: landing page (3967f7d)
- feat: captured descriptor and report samples for the landing page (4e62486)
- fix: phone panels, scrolling, prerendered reveals, contrast and search anchors (85f94ad)
- fix: phone page panel closes on a box pick or search, and dashboard tools get the full width (9765e4a)
- fix: guide corrections, and page leads without trailing periods (c1c3e36)
- fix: compatibility rows stay apart per device, and the Discord count retries after a failure (94c9e64)
- fix: prerender waits for the 404 page's header (3568738)
- docs: project notes for the new shell (f4613ce)
- feat: not-found page on the new shell (d049c04)
- feat: landing page blocks (1e3d911)
- feat: live figures on the landing and compatibility pages (d54320d)
- feat: guide pages for box owners (c6a3a05)
- feat: compatibility data (1d3f078)
- feat: FAQ data and structured data (95b7570)
- feat: docs content in the site theme (df74a74)
- feat: frame diagrams as byte strips (19b6a89)
- refactor: docs pages use page headers and sections (5f893f5)
- feat: dashboard in the site theme (6f16d46)
- feat: docs pages on the new shell (20b6382)
- feat: page header, sections and on-this-page rail (df5300b)
- feat: docs sidebar (1bdd0e5)
- feat: site nav and footer (4c7d086)
- feat: guide section routes (99dc575)
- feat: scroll reveal helpers (f5e97d9)
- feat: site theme tokens, fonts and base styles (f59b667)
- feat: the Control and Advanced control pages are indexed (b1a7aa7)
- fix: the server's shared modules ship in the image; live pages answer 503, not a placeholder (3050201)
- chore: wording in comments, a description and a test name (0a59366)
- feat: bold and code in release notes; crawl check typechecks (4905b9e)
- docs: the route registry, sidebar links and 404 in CLAUDE.md (d490c73)
- ci: sitemap dates from each page's last commit (0df48e2)
- feat: release notes on the changelog, commits behind Show commits, filled in for crawlers (02a5489)
- test: drop the removed unsupported-browser cards from the search exemptions (9e34f4e)
- feat: dashboard pages keep their content in a browser without Web Serial (b8349a0)
- feat: breadcrumbs and a site footer (452aeae)
- feat: a sidebar of real links (9598337)
- feat: a 404 page (67d43ae)
- feat: 301 to the canonical URL and a real 404 for unknown paths (d1e876d)
- feat: page head and structured data from the registry (ff7fb51)
- feat: prerender every route, the dashboard included, from the registry (6ab9ba9)
- feat: one route registry for page titles, descriptions and navigation (13c9fe7)

## v3.4.5

5 Oct 2026

### Changes
- Fixed an issue where certain models without PSRAM would fail to update
- Fixed an issue where certain models without PSRAM could get stuck when attempting to start
- Improved EP0 round-trip timing (~-30% reduction in EP0 latency)
- Improved timing stability for certain EP0 commands

### Notes
Sorry to those affected, this hotfix should address the issues you had with updating. Should be fully fixed now.

### Commits

**Firmware**
- docs, tools: v3.4.5's control request timing and RAM figures (6ad9805)
- firmware: control requests are answered from RAM (bf6e62f)
- docs: v3.4.5 in the release table and protocol change log (2dcc498)
- release v3.4.5 (95a4d53)
- docs: internal RAM on a box without PSRAM, and how the update gate checks it (977e03e)
- tools: the update gate runs the device image as a box without PSRAM does (2339868)
- firmware: the clone starts on a box without PSRAM (e8c1e2f)
- docs: the usage stats design and plan reach the page from the Stats tab alone (c2a431d)

**Docs**
- docs: update firmware version strings to 3.4.5 (116031c)
- dashboard: fewer words on the Update, Advanced and Device pages (8b0a097)
- dashboard: the connect card without the stats line (45c272b)

## v3.4.4

5 Oct 2026

### Changes
- Over-capacity IMPERFECT device's extra endpoints will now function correctly (ie, Wooting keyboards)
- Added support for multiple boxes in the dashboard
- Added flashing over USB2 from the Advanced page
- Boxes on newer firmware than the dashboard supports will now be able to connect (to upgrade/rollback)
- Added a public usage stats page
- Updates now catch a chip which fell back to its previous firmware during the process
- Fixed an issue where locks could take up-to 50ms to engage/disengage (now <2ms)
- Fixed an issue where some over-capacity devices would constantly re-enumerate on Windows
- Fixed an issue where devices with several interfaces could send reports in uneven gaps
- Fixed an issue where a 500hz device could read as 1khz if some reports arrived late
- Fixed an issue where the box could ignore a program's first commands if the previous was disconnected mid-command
- Fixed several issues when a cable is pulled mid-flash
- Fixed an issue where the dashboard couldn't read the box after a program had been using it
- Fixed an issue where the dropdowns could continue using the CPU after being closed (on the dashboard)
- Numerous other small improvements to performance and stability

### Notes
The over-capacity IMPERFECT changes allow devices which originally had dead endpoints to now function fully. It still requires IMPERFECT to be enabled, but DRAMATICALLY improves device-support and security in IMPERFECT mode. (IMO, there is **almost **no difference in security between a perfect and imperfect clone now)

The public usage stats page can be found here: https://medius.k4tech.net/dashboard/stats

### Commits

**Firmware**
- docs: the usage stats design's result names and totals (4020244)
- docs: the usage stats page down to its numbers (5fb9415)
- docs: the usage stats page is reached from the dashboard alone (14867a0)
- docs: the usage stats design's limits, active boxes and shared devices (7c2b211)
- docs: the public usage stats design and plan (628d511)
- docs, tools: plainer wording in the protocol doc, designs and gate notes (3dc6e29)
- tools: leave the control port readable by the browser dashboard (390ca7d)
- firmware: the control port drops a frame its sender abandoned (83fbd1b)
- docs: the USB2 flash design's rare paths and what they changed (4a65e0d)
- docs, tests: the stable update path names its resync, waits and the messages between the chips (8183489)
- docs: the USB2 flash design as built, and its hardware run (19410d8)
- docs: name the stable update path section, and plain punctuation in the plan (8df2fee)
- docs, tests: the update path keeps its shape from protocol 5 on (9d7e587)
- docs: plan for flashing over USB2 from the dashboard's Advanced page (dd5c6ce)
- docs: design for flashing over USB2 from the dashboard's Advanced page (d543602)
- docs: USB device box icon, and dropdown menus idle once closed (be31119)
- docs: box icons in the multi-box dashboard design (6dd0725)
- docs: multi-box dashboard design, Add box tab and card wording (aa5f821)
- docs: the multi-box dashboard design as built (dfe9a8c)
- docs: design and plan for managing several boxes from the dashboard (a7b7de2)
- firmware, tests, tools, docs: an imperfect clone keeps every IN endpoint live (d4eb9ec)
- firmware, tools, docs: a lock is in force before any command sent after it (ea2e07a)
- docs: v3.4.4 in the protocol change log (4d8f564)
- tools: the Logitech fix runs on Linux and Windows with nothing to install (c6d64e1)
- docs: v3.4.4 in the release tables (d2f2839)
- firmware, tools, docs: an endpoint polled every frame keeps one place in it beside slower ones (ff60509)
- firmware, tests, tools, docs: the native rate reads true through reports a poll late (74558cf)
- release v3.4.4 (c2417ef)

**Library**
- library: over_capacity says what it counts, now that the box serves every IN endpoint (610a5fe)

**Docs**
- dashboard: Waiting for injection and Move the mouse to start as neutral chips (292d018)
- dashboard: stats bar labels wide enough for the longest route (c56d2ae)
- dashboard: stats totals as tiles, Unconfirmed as a flash result, a nameless device named by its kind (d397ef4)
- dashboard: the stats page down to its numbers (c40b38f)
- dashboard: the stats page is reached from the dashboard alone (535fdf2)
- dashboard: the dev server refuses to serve the stats database (550b9c7)
- dashboard: the stats page says when it can't load, wraps long names, and tells flash results apart (5a33b27)
- dashboard: stats rate limits, a box active only when it connects, a device listed once two boxes share it (aeea4d5)
- dashboard: stats headings named as nouns, a peak on each chart, and New this week among the totals (848103a)
- dashboard: a volume for the usage stats database (f870b69)
- dashboard: a public stats page, and a note on the connect card that boxes are counted (926b4be)
- dashboard: report each ROM download flash to the public stats (1228dcc)
- dashboard: report each box, its cloned device and each update to the public stats (72686a7)
- dashboard: a usage stats sink, and the totals read for the page (d0c9d29)
- dashboard: an event route and a totals route for the usage stats (04f1ad4)
- dashboard: usage counts in SQLite, a box known only by a keyed hash of its MAC (add952c)
- dashboard: the stats server checks each reported event, field by field (653dde4)
- dashboard: name ROM download on Advanced, and plainer copy for the fixed update path (d4a5e73)
- dashboard: a box the browser can't read says to replug USB2, instead of an empty list (e6413eb)
- dashboard: say which firmware needs the resync before a handshake (77af84e)
- dashboard: a flash retried after a pulled cable clears what the box still held, and the box's busy is no longer worded as a held port (81471f1)
- dashboard: a box unplugged mid-flash reconnects when it comes back, and says the flash didn't finish (57d455e)
- dashboard: a newer box is named as newer on every page, and Update leaves Advanced's result alone (46e20f8)
- dashboard: Advanced keeps a flash set up when a box comes or goes, and spaces its fields like the other cards (3f6a169)
- dashboard: one update at a time, and a newer box found after an install is connected (cb6c47d)
- tests: pin this page's half of the stable update path (d25cd11)
- docs, dashboard: name the stable update path, and tighten two comments (1372324)
- dashboard: a refused file names no version to come, and Flash anyway has its own row (3078029)
- docs: the update path keeps its shape, and Advanced's search words (9aee6d0)
- dashboard: flash either chip or both over USB2 from Advanced (c563c96)
- dashboard: an update records which slot each chip came back on (f07950b)
- dashboard: say when a box is newer than the page, and where it can still be flashed (cfd434a)
- dashboard: a box on another protocol is kept connected without asking it for health (5713f0a)
- dashboard: a box on a newer protocol connects, for updating (bda9d27)
- dashboard: read a firmware file's chip and version before flashing it (0e44693)
- dashboard: a USB device icon for a box (64ffd79)
- dashboard: dropdowns no longer keep working in the background after they close (2c3bca1)
- dashboard: pick an icon for each box in the sidebar (3e36c5b)
- dashboard: long box names end in an ellipsis, and no Boxes section without Web Serial (130805b)
- dashboard: Add box as the last box tab, and box card wording to match its siblings (a5285c6)
- dashboard: box list as sidebar tabs, Identify on the box card (4c0664d)
- dashboard: one opener per port, and a box is held by one tab (b3f5522)
- dashboard: every box on the PC, listed and held at once (cc61111)
- docs: an imperfect clone serves every IN endpoint (2a7fdfd)
- docs: a lock is in force before anything sent after it (f4d5828)
- docs: update firmware version strings to 3.4.4 (0381ea2)

## v3.4.3

30 Sep 2026

### Changes
- Improved support for many more device types (devices with no HID interface, alternate settings, ect)
- Endpoint halts now pass in both directions correctly
- Improvements to native report latency (packets emit 1-tick after arrival instead of 2)
- Updated to ABCurves v2.0
- Added support for high-dpi while using rendering
- CATCH now reports zero-length packets correctly
- Improvements to latency for library replies on Windows
- Fixed an issue where writing to a device's OUT endpoints was capped at ~1 packet/ms
- Fixed an issue where reports could be lost when the PC paused reading the stream
- Fixed an issue where reports could be lost when a device switched settings
- Fixed an issue where zero-length packets from a device wouldn't be passed to the PC
- Fixed an issue where devices with a small control endpoint could take several seconds to connect
- Fixed an issue where some some strings and descriptors weren't cloned exactly as the device sent them
- Fixed an issue where a low-speed device's first report after a pause could land a frame late
- Fixed an issue where a CLIP could play 2 entries in 1 report
- Fixed an issue where MOVEs at poll rate could merge
- Several other smaller improvements to stability and performance

### Notes
This update should provide numerous improvements to stability, latency, and device support
I have some other changes in the works too, but I want to get this stuff out first

### Commits

**Firmware**
- firmware, tools, docs: relayed reports reach the PC on the poll after they arrive, and none are lost across setting switches (8ab665e)
- firmware, docs: a clip plays one entry to a report, every time (2974980)
- docs: v3.4.3 in the release tables (dae9d77)
- firmware, tests, docs: a low-speed device's first report after a pause keeps its place (557253f)
- firmware, tools, docs: a PC that stops reading a stream loses nothing, and reads it current after (d8f701e)
- firmware, tools, docs: zero-length packets on interrupt endpoints reach the PC as the device sends them (1f918af)
- firmware, tools, docs: relayed endpoints keep every packet through halts, resets and setting changes (ebe9ed7)
- firmware, tools, docs: reports reach the PC sooner, and slower endpoints wait on its polls (65aca8b)
- docs: the device shapes v3.4.3 clones, the OUT relay, endpoint halts and alternate settings (08376dd)
- firmware: HID interfaces with alternate settings are cloned, and a switch loses no report (7649b8a)
- firmware: an endpoint halt passes both ways between the PC and the device (e6d0427)
- firmware: every packet the PC writes to an OUT endpoint reaches the device, at the PC's rate (41fdcee)
- firmware: IN endpoints numbered 5 and 6 are cloned (72aaa87)
- firmware: a control request for more than 2048 bytes reaches the device as the PC wrote it (f1d0046)
- firmware: a patched string is readable on a device that lists no language (bb769ca)
- firmware: the device qualifier and other-speed configuration are cloned (7e160c8)
- firmware: a device with a small endpoint 0 enumerates at once (8f6a057)
- firmware: strings are cloned as the device answers them, in every language it lists (a1bf68b)
- firmware: a device with no HID interface is cloned (7b63fb9)
- firmware, docs: remove the unused capture-owner hooks (256946b)
- firmware: class and vendor requests to any interface reach the device (45efb0e)
- firmware: vendor relay buffers are sized to the endpoints that can be live at once (fadb88b)
- firmware: a device past the host's descriptor tables clones what fits and says what did not (e3fa3d0)
- firmware: a device that refuses its own report descriptor is cloned refusing it (cb82a3b)
- firmware: a slow report descriptor fetch is waited for, not left out of the clone (3144bee)
- firmware: a HID descriptor after the endpoints, or listing another descriptor first, no longer loses its interface (d154a9f)
- firmware: devices whose interface numbers skip a value or start at 1 are cloned (4f0ce37)
- firmware: an EP0 answer is relayed whole or refused (4511feb)
- firmware: tell an interrupt transfer a zero-length packet ended (abea180)
- firmware: count the IN endpoints a configuration can have live at once (2ef987d)
- firmware: name the host limit a configuration exceeds (f963527)
- firmware: one device is cloned from behind a hub, the first to configure (50d7fb7)
- firmware: OUT credit that lets a clone hold the PC the way a busy device does (82f7856)
- firmware: the report descriptor store keeps a device's refusal as an entry (1a9a03b)
- firmware: vendor endpoint buffers are sized to what can be live at once (93a9591)
- firmware: a table of string descriptors keyed by index and language (41193f0)
- firmware: find a HID interface's report descriptor wherever its HID descriptor sits (10e6ee1)
- tools: a descriptor gate and shared sim shape builders (08d473b)
- docs: design and plan for faithful clones of more device shapes (45a09a0)
- firmware, tools, docs: the box's reports reach the PC at one place in its frame (fad5327)
- firmware: moves at the poll rate stay one to a report beside a slower mouse (cffba9b)
- tools: the relay gate checks a feature report's bytes reach the device (8d8aff7)
- firmware, tools, docs: a HID interface without an interrupt IN is cloned (d88cfc3)
- firmware: rendered motion keeps pace with a flick (ABCurves 2.0) (5c24af8)
- release v3.4.3 (15efeb3)
- firmware: the host chip logs lost reports, not a heartbeat every 5 s (3c69470)

**Library**
- capi: format a test (31fd6ba)
- library, capi, python: relay_drops as the firmware counts it now that the box polls only as the PC reads (47f1aff)
- library, capi, python: the zero-length packet flag on every relayed traffic class (6516ce7)
- library, capi, python: relay_drops, RAW OUT and the device kind as the firmware has them (e861041)
- library, capi, python: a clone is up once its identity says so, and the counters and kinds read as the firmware has them (8d9ceb2)
- library, capi, python: a clone with no HID interface counts as cloned (47df70c)
- library, capi, python: 3.4.3 (d3f2e13)
- examples: inject_latency also times a query's round trip (4ce1787)
- library: a Windows read wakes on the box's reply, not on a 2 ms poll (466412c)

**Docs**
- docs: relay drops, and the room RAW reports have in an interrupt IN queue (15f09d3)
- dashboard, docs: zero-length packets on interrupt endpoints and the CATCH flag that marks them (5f69d13)
- docs: relay drops, RAW OUT waiting, the requests CATCH sees on EP0, the product string and the device kind (eb4619f)
- dashboard, docs: an attached device the box did not clone, relay drops, the IN endpoints a clone serves and the requests it passes on (361a7a8)
- docs: the native requests page on a clone with no HID interface, and which long requests the box refuses (9b2a754)
- dashboard, docs: a clone with no HID interface reads as cloned, and the IN endpoints a clone serves at once (ca384a0)
- docs: when a native report keeps its own poll beside an injection (c721340)
- docs: a rendered report is bounded by the mouse's own field, not 127 counts (b699bcf)
- docs: the library's unsafe code is its Windows comm-event wait (8e996ba)
- docs: update firmware and crate version strings to 3.4.3 (65632ec)

## v3.4.2

26 Sep 2026

### Changes
- Moved all motion processing to the host chip (rendering, riding, spreading, locks, ect)
- Added factory reset
- The library now restores locks, rules, subscriptions, ect after a box restart
- Injection now wakes a sleeping PC (where applicable)
- Fixed an issue where using CATCH could cause the devices movement to be dropped
- Fixed an issue where the library could bluescreen Windows PCs using the WCH CH343 driver
- Fixed an issue where a descriptor patch that failed a check could still override the device
- Fixed an issue where a device with reports larger than one packet would stop after its first report
- Fixed an issue where OUT reporting could be merged and lost on certain devices
- Fixed an issue where back-to-back injected clicks could be lost
- Fixed an issue where injected reports could delay a native report
- Fixed an issue where a devices reports could drop during an update
- Clearing descriptor patches no longer requires a reboot
- Several other smaller improvements to stability and performance

### Notes
With the motion processing being done on the host chip, it should eliminate any slowdown that occurred on certain devices whilst using Full Rendering and other high-intensity features

Overall, we are looking at a roughly 40% decrease in render latency across a number of different measurements. This also means native reports are delivered more consistently

### Commits

**Firmware**
- firmware: a render comment keeps the reason one report is outstanding (1517fe2)
- firmware, tools, docs: fewer words in every comment and doc, and the claims the pass found wrong (11752c1)
- docs: a flagged MOVE skips spreading and rendering with riding off (e8dd0ca)
- docs: the protocol reference and architecture match the corrected behaviour (f08625b)
- tools: gates for the advanced control fixes, session releases and OUT relay, all sim-driven (68b5ebd)
- firmware: advanced control corrections, OUT relay fixes and report scheduling (aafaf4f)
- docs: the version table names the flag RESET grew (2e623a4)
- tools: the client's opcode list says what RESET carries (f3ad3a6)
- firmware: RESET requires its flags byte, like every other command (c7a4dd2)
- firmware: RESET can take the box's stored settings with it (d69e608)
- firmware: a suspended PC is woken for injection, and hears it when it wakes (10f89dd)
- tools: the render gate reads a property the paced fill cannot have (1389ff4)
- firmware: the health bit answers for the control PC's motion, not the device's own (b39b53f)
- firmware, tools: comments say the one thing that is not obvious, in two lines (8895c70)
- firmware: injected commands cross the link a whole frame at a time (47cf5e8)
- tools: the wedge gate walks past an identity it has already spent (3a4943b)
- firmware: an abort that worked no longer leaves the session open behind it (ca82234)
- tools: three cadence checks now read the value their property is about (297b43d)
- firmware: what a departed control PC left behind is released whether or not a device is attached (8aefc06)
- tools: the wedge gate stops spending a device identity it cannot get back (91a3285)
- tools: a cadence gate was grading its own sender, not the box (4298d03)
- firmware: an update no longer loses its answer to the link's own recovery (3e1e82f)
- firmware: a relayed update says what happened to it (b567d1a)
- firmware: an OUT report lost on the link is the relay shedding, not input the game missed (fb52822)
- tools: the reference client's stats reply length follows the field it grew (9e4111d)
- firmware: a drop counter says which fault it is, or it says nothing useful (2af524b)
- tools: what a scale does to physical input no longer needs a hand (2fca234)
- firmware: injected motion leaves on arrival, not on a clock of its own (0ce9edb)
- tools: the transform gate measures the weigh and the transform together (4960cd1)
- firmware: nothing that carries motion blocks on the link, and the host watches its own work (fe925ec)
- firmware: the authority's answer crosses as the weigh and the release, apart (4f02b05)
- firmware: the box reports what the links dropped (c4bfad1)
- firmware: the motion authority releases the lock it takes (abe6356)
- firmware: the motion authority moves to the host chip (87f59c6)
- firmware: one weighing unit, on the delta rather than the report (efa315b)
- firmware: the motion primitives move to common (dea849e)
- docs: the motion authority moves to the host chip (9038058)
- release v3.4.2 (6cdbbfe)
- tools: the conservation arm puts the sim back when it is done (0154163)
- tools: the catch rig asks whether observing cost the player any movement (ac495eb)
- firmware: observing the traffic no longer costs the device's reports (ff0c296)

**Library**
- library, capi, python: fewer words in every doc, and the claims the pass found wrong (45468f3)
- library: a non-blocking control-port read avoids a WCH serial-driver crash on Windows (5b60d0b)
- library, capi, python: the library restores its state after a box restart or session release (37fdd95)
- library: RESET carries its flags byte on both paths (460c51c)
- library, capi, python: the box can be asked to erase what it keeps across a reboot (e84f1b3)
- library, capi, python: comments say the one thing that is not obvious, in two lines (6fc9f65)
- library, capi, python: back-pressure on a relayed stream is its own counter (b5782b0)
- library, capi, python: control protocol 9 and the box's link drop counters (57a56fd)

**Docs**
- dashboard: values that stand alone start with a capital (47cdd60)
- dashboard: DTR and RTS reset neither chip, measured (28c624a)
- docs, dashboard: fewer words on every page, and the wrong claims the pass turned up (60d5c89)
- dashboard: shorter performance row labels (33442a7)
- dashboard: the performance card shows every counter's count (a196c66)
- dashboard: the relayed-streams chip stands on its own (f862c43)
- dashboard: decode protocol 9 catch flags and stats, and show patch and rewrite state as reported (da3a75a)
- docs: the library and bindings pages cover session recovery and the corrected advanced control (bbd45cf)
- docs: native pages for RAW, TRANSFER, REWRITE and PATCH, and a sidebar that fits its pane (f9c68d5)
- docs, dashboard: RESET carries its flags byte on every frame (d678a45)
- docs, dashboard: RESET's erase-and-reboot flag (f231ce8)
- docs, dashboard: comments say the one thing that is not obvious, in two lines (b648d75)
- docs, dashboard: the stats counters tell lost input from relayed back-pressure (1cf6f21)
- docs: the stats offsets are what-inclusive, and the samples speak protocol 9 (6bbb5f7)
- docs, dashboard: control protocol 9 and the inter-chip link counters (37a8d9c)

## v3.4.1

19 Sep 2026

### Changes
- Added packet-based triggers to the CLIP system
- Added AC pan, raw reports and control transfers to CLIP system
- CLIPs now function correctly on all device types, not just mice
- Fixed an issue where injecting at the devices native report rate could blend moves together
- Fixed an issue where a descriptor patch wouldn't apply to a device without a mouse interface
- Fixed an issue where rewrite rules weren't cleared when the control link went silent
- Fixed an issue where rewrite rules on a vendor interface's OUT wouldn't match
- Improvements to latency when injecting RAW packets via the library
- Several other smaller improvements to stability and performance

### Notes
Packet triggers let a CLIP fire directly from the device's traffic. This allows for specific behaviour when a flag is seen (ie, conduct macro when DPI button is pressed): https://medius.k4tech.net/library/clip#packet-triggers

The protocol and ABI version has changed to `8`, so devs with their own libs will need to update accordingly

### Commits

**Firmware**
- docs: what protocol 8 changes, and the README's command reference (f9bea34)
- firmware: control protocol 8 (6388acc)
- docs: a packet trigger's read-back has its present bit clear (3fd6757)
- docs: clip packet triggers (44c550a)
- tools: the clip gate proves packet triggers on every surface (f219f0b)
- firmware: a matched packet is a clip trigger (9ea7e94)
- docs: whole commands at the report rate (14e9bf1)
- tools: the spread gate measures a report-rate loop, and a tool keeps the clone off the desktop (d5c7ed6)
- firmware: a command loop at the report rate lands one command on each report (807593e)
- docs: clip entries for pan, raw reports and transfers, and the clip transfer class (d35751d)
- tools: the catch gate presents its own streaming mouse for the clock-offset leg (9a73e43)
- tools: the reference client builds the new clip entries, and a clip gate proves them on the sim (9bfa856)
- firmware: clips carry pan, raw reports and control transfers (f2e6caf)
- release v3.4.1 (c09fa89)
- tools: the clone-policy gate waits out a multi-configuration capture (45dd27e)
- tools: the descriptor-patch gate covers a device the box finds no mouse in (66280e5)
- firmware: a descriptor patch is stored against the device, not the mouse in it (d4aef98)

**Library**
- library: the C header names its ABI, and a reconnect refuses a box on another protocol (a0740f2)
- library: control protocol 8 and C ABI 8 (dac754e)
- library: packet triggers follow the box through the opt-in, and the mock refuses as the box does (8208e81)
- capi, python: clip packet triggers (eb0a3a7)
- library: a matched packet is a clip trigger (7a93a44)
- docs: set_spread says what a loop at the report rate gets (a006979)
- examples: the hardware suite proves clip entries and a clip across a reconnect (901a1ec)
- library, capi, python: clip frames carry pan, raw reports and control transfers (1a54225)
- ci: the crate, the C ABI and the Python binding must carry one version (c066099)
- library, capi, python: the bindings reach the transfer timeout the crate has (9454600)
- release: bump the crate, C ABI, and Python binding to 3.4.1 (cecf373)
- library: raw sends without a round trip for the opt-in (50a4f8d)

**Docs**
- docs, dashboard: what protocol 8 changes, the C ABI constant, and a reconnect across protocols (f85d02b)
- docs, dashboard: control protocol 8 and C ABI 8 (9a0bf86)
- dashboard: number fields take whole numbers (07ae27d)
- docs: packet trigger ordering, placement, timing and the mock (b0b2f26)
- dashboard: the Clip card says when the box does not take a trigger (439682f)
- docs, dashboard: clip packet triggers (6bf5ab2)
- docs: what a loop at the native report rate gets from spreading (3e4bb4e)
- dashboard: clips build pan, raw report and control transfer ticks (8ac29c2)
- docs: clip frames with pan, raw reports and control transfers, and the clip transfer class (52dce04)
- docs: the C and Python transfer timeout calls (31a433c)
- docs: update crate version strings to 3.4.1 (0a96dd5)
- docs: update firmware version strings to 3.4.1 (d8d5931)
- docs: a patch's cfg is a configuration index, and raw does not read the opt-in (a0e2823)

## v3.4.0

16 Sep 2026

### Changes
- Added an advanced control layer (raw report injection, traffic rewrite rules, descriptor patches, ect)
- Added AC pan as first-class axis
- Added support for buttons above 5 (up to whatever the device declares)
- Added transform system: swap axis' and remap buttons with first-class support
- Fixed an issue where CLIP motion wouldn't be processed by the renderer
- Fixed an issue where CLIP + rendering may not advance at the correct rate for slower-reporting devices
- Fixed an issue where rendered motion may not be delivered in full on large movements
- Major improvements to injection throughput and reductions in latency
- Fixed an issue where a device may stop working after a host-pc restart
- Fixed an issue where a device may read as not connected when it just takes a while to connect
- Fixed an issue where an update may fail whilst the other chip was still erasing
- Several other smaller improvements to stability and performance

### Notes
The advanced control layer gives you low-level control over the device itself. Emit bytes on any endpoint, control transfers though to the real device, rules that can rewrite traffic in-flight, and patches to the descriptors being sent to the host device
It requires Imperfect Clone to be enabled, and can be found here: https://medius.k4tech.net/library/advanced/raw

The protocol version has changed, so for those with their own libs, you will need to update accordingly. The control port has also moved from 4MBaud to 6MBaud, so you will need to update your port-opening accordingly

### Commits

**Firmware**
- docs: the transform section says how the two commands compose, not what one lacks (4279cd1)
- firmware: a lock bites the field the device drove, and every one-bit class refuses a reversal (5641e52)
- firmware: the two comments that still described the old pass order (de0c479)
- docs, tools: the lock scale is signed, and a transform only moves a field (dcfd81f)
- firmware: weighing a field belongs to LOCK, and a transform only moves it (7af85dd)
- firmware: the field-transform pass is the first thing the renderer does, not the second (5a28e90)
- firmware: a scaled swap banks each direction, and a remapped key is one a keyboard reports (78b5873)
- firmware: the one injection path that reads the report map checks it is bound (ae091fd)
- tools: the transform gate reads the wire the game PC sees (2322322)
- tools, protocol: the reference client and the byte reference cover transforms (f90d0e7)
- firmware: a narrower configuration can still release what a wider one held (2685068)
- firmware: transforms hold their own arithmetic, and a remapped key follows the button (73941a4)
- firmware: shorter clip rendering notes and comments (67f58ba)
- firmware: clips play through the renderer at their own pace, and rendered motion arrives whole (74a6c6a)
- firmware: the mouse keeps working after the game PC restarts (99e546d)
- firmware: the tools open the port at the rate the box answers on (0bb18b2)
- firmware: a completed transmission is no longer thrown away before it is seen (cdcf9e0)
- firmware: a host image confirms itself only on a link that works (4247979)
- firmware: the link's rate recovery starts where the image expects it (52594cc)
- firmware: the inter-chip link runs at 20 Mbaud (b7a799e)
- firmware: revert the short frame form and CTRL_MOVE8 (39d5644)
- protocol: one version bump for the release, not two (e802ed3)
- firmware: the control link runs its own UART interrupt, and the link watchdog tolerates an erase (9fab234)
- tools: the recovery flash script takes the control rate from the client, not a literal (ac67ce4)
- firmware: the inter-chip link runs its own UART interrupt instead of IDF's driver (95d8c53)
- firmware: link frames go straight into the TX FIFO instead of through the driver's ring (299a0a4)
- firmware: the snapshot retry budget is spent only on attempts the link could carry (1bf86fd)
- protocol: the short frame form is v8, and the reference spells it out (4d14a07)
- tools: a control-rate rig, and the client stops charging the port timeout to every reply (1c5e7e6)
- firmware: the inter-chip link runs at 12 Mbaud and stops waiting out its receive timeout (ab9f62b)
- firmware: a shorter hot-path frame, and injection stops paying for a refused emit (b325bf4)
- firmware: the frame CRC reads a table instead of shifting eight times a byte (bc14de0)
- firmware: hold 32 rewrite rules, and count the matches the reply reports (5e96c48)
- tools: bearing and lock take the AC-Pan axis in their motion callback (f45dfef)
- firmware: descriptor-patch consistency read the wrong bytes, and a refusal stranded the clone (f7426a3)
- firmware: rename the developer layer to the advanced control layer (2e5b4a6)
- tools: catch subscribes to endpoints by number, and a raw injection gate (ecfc977)
- tools: reference client and protocol reference name an endpoint by number and direction (e9e99bf)
- firmware: name a cloned endpoint by number and direction, not a packed byte (51f0e97)
- firmware: transforms apply on a secondary mouse, and a cross-class remap releases its key on removal (3f136c8)
- tests: gate the field transforms and their compositions (1481d38)
- firmware: field transforms (invert, scale, swap, remap) (9c3a259)
- tools: reference client mirrors the widened input core (AC Pan, buttons past five) (27cab02)
- firmware: correct AC Pan's clip scope and the reported button count (f6741da)
- tests: gate AC Pan, buttons past five, and the spread/ride drain (7052133)
- firmware: widen the input core to AC Pan and buttons past five (5efc74d)
- firmware: the rewrite table's generation counter stays monotonic across a clear (855f239)
- firmware: set the milestone version to 3.4.0 (617997d)
- firmware: skip an unchanged patch-set write, and correct a transfer status comment (e4dc8dc)
- tools: reference client and protocol reference track the patch-length widening (2740192)
- firmware: correctness fixes to the developer layer's emit, observation, transfer and patch paths (163e0f4)
- firmware: a table overwrite that will not fit keeps the existing entry (7ee87de)
- docs: control-protocol reference for the developer layer (proto 7) (8f0d7cf)
- tools: reference client for the raw, transfer, rewrite and patch verbs (proto 7) (21817e3)
- firmware: dispatch the raw, transfer, rewrite and patch commands (6d91f35)
- firmware: persisted descriptor patches, applied under the clone-refusal ladder (50c48c2)
- firmware: health carries the rewrite state (health is now 16 bits) (389aa18)
- firmware: an on-box rule table that rewrites traffic at every surface (41e2c40)
- firmware: run a control transfer against the device on the control PC's behalf (d17be06)
- firmware: raw report injection on any cloned endpoint (ba45759)
- firmware: control-protocol wire for raw injection, transfers, rewrites and patches (proto 7) (dbd264f)
- firmware: a per-device descriptor-patch set with consistency checks (fc0a70f)
- firmware: a pure rule table for rewriting traffic in flight (8e3e1cf)
- docs: implementation plan for the generic control layer (d38d73c)
- docs: v3.4.0 design specs (generic control, widened input, transforms) (ebaf3ff)

**Library**
- library, capi, python: docs say where to go, not what the API lacks (a6f2520)
- library, capi, python: pin the signed scale's guards, and rank a block above a reversal (e2cbfde)
- library, capi, python: weighing a field is the lock's, and the percent is signed (8856654)
- library, capi, python: the C transform op byte, and the tests that never compiled (ee62bcd)
- library, capi, python: a transform table replays in the order it was built (8281209)
- library, capi: the clip ride readback fields keep their original wording (837c25a)
- library, capi, python: clip riding reaches only the wheel while rendering takes the cursor (d37c615)
- library: say what actually confirms a firmware image (7bfa7f4)
- library: the control link opens at 6 Mbaud (6abe68c)
- library, capi: the rewrite table holds 32 rules (38ca523)
- python: finish the advanced control layer rename in a test section header (35160bd)
- library, capi, python: rename the developer layer to the advanced control layer (2fa56be)
- library, capi, python: name a cloned endpoint by number and direction, not a packed byte (42510de)
- capi, python: AC Pan and first-class transforms (45d730e)
- library: field transforms (invert/scale/swap/remap) (c085db7)
- library: a button blanket re-asserts every declared button after a reconnect (7c4cebe)
- library: AC Pan axis and buttons past five in the input core (a76de9a)
- library: C API and Python bindings for the developer layer (0740836)
- library: serialize the developer-layer session state and harden its tests (ad99e91)
- library: cover the developer layer in tests and the hardware example (ab33cce)
- library: raw injection, control transfers, rewrite rules and descriptor patches (b492584)

**Docs**
- docs: a conformance gate, so shape drift is caught by the build and not by the reader (e7bf915)
- docs: an enum is documented on the enum page, not carded again on the method page (67936ae)
- docs: pages point somewhere, instead of naming calls that were never there (0bf11aa)
- docs, dashboard: the opcode table reaches 0x1E, and a block outranks a reversal (2dcbfb2)
- docs, dashboard: the lock scale is signed, and a transform only moves a field (412ff1d)
- docs: correct what the transform pages claim, and finish moving the types (0fa563c)
- docs, dashboard: the v3.4.0 types live on the type pages, and transforms match the wire (b822eb2)
- dashboard: the update choices are the ones they were (da23a7b)
- dashboard: reach a box on older firmware, and see its update through (a949be4)
- docs: shorter clip rendering copy (152ef34)
- docs: clips are rendered like live motion (0c594e1)
- docs: the pages that tell you which baud to open still said the old one (66182d3)
- docs: the control link is 6 Mbaud and the inter-chip link 20 (9650135)
- docs: the rewrite table holds 32 rules (02023b5)
- docs: rebuild the advanced control tab on the pattern the other tabs use (12b760d)
- docs: gate the rewrite remove/clear buttons on the imperfect opt-in (4fb4b97)
- docs: the advanced control dashboard tab is no longer blank without a device (d968e0e)
- docs: finish the advanced control layer rename (tab variable, a test comment) (20213f3)
- docs: rename the developer layer to the advanced control layer (cc5d672)
- docs: fix the dashboard RAW comments left on the old packed wire (ffa5a43)
- docs: name a cloned endpoint by number and direction, not a packed byte (b6988c9)
- docs: complete and de-slop the v3.4.0 reference (7fbc25b)
- docs: AC Pan and buttons past five as first-class input (22254a4)
- docs: widen the native HEALTH reference to the 16-bit word (da8f6eb)
- docs: developer layer reference and the 3.4.0 version bump (9be03ac)
- docs: firmware 3.4.0, protocol 7 on the native pages (f70735d)
- dashboard: add a Developer tab to edit rewrite rules and descriptor patches (aec3079)
- dashboard: mirror the developer layer on the control link (proto 7) (a4f09ec)

## v3.3.4

6 Sep 2026

### Changes
- Fixed an issue where the box could reboot while under heavy load
- Fixed an issue where data sent from the control pc could be silently dropped
- Fixed a rare crash on startup
- More small improvements to stability and crash reporting

### Notes
I've got a couple more features I want to do, but I have a TON of stuff coming up with work soon so just be aware of that

And just FYI, Unless a developer approaches me with the stuff to add proper console support (ie, the decryption process for PS5), it is unlikely that controller/console support will be implemented
This is simply because I do not have the hardware to test it on (A PS5 is >$1000 in Australia 💸)

### Commits

**Firmware**
- firmware: claim a deferred write before making it, not after (f4c4623)
- firmware: make a bulk sequence increment atomic now that it crosses cores (ea3e711)
- host: a control job refused rather than asserted before its queue exists (7cbd464)
- firmware: move the two starved tasks instead of working around them (e3be0b7)
- firmware: the record keeps the names worth reading, and a refused write is its own (80566b6)
- firmware: watch fewer tasks, and say which one stopped (e89e759)
- firmware: a busy box stays up, and a wedged one still names what stopped (9aa62e8)
- release v3.3.4 (87e90bf)
- firmware: a busy box no longer reboots itself under a heavy observation load (16459f3)
- docs: what a watchdog record says, and the boot line as it is actually emitted (6f56497)
- firmware: a task watchdog now says what was running when it fired (698b263)

**Docs**
- docs: tx_drops counts both directions, not just the report path (7318cb8)
- docs: update firmware version strings to 3.3.4 (ae7a32a)

## v3.3.3

4 Sep 2026

**Firmware**
- release v3.3.3 (4d5ad7f)
- firmware: act on the review of the PSRAM fix (2a0448a)
- docs: the two PSRAM variants of the board and what differs on each (17f749d)
- firmware: a box whose S3 has no PSRAM stops reverting its update (c01a089)

**Docs**
- docs: update firmware version strings to 3.3.3, and record that clip capacity varies (ea25c2a)

## v3.3.2

4 Sep 2026

### Changes
- Fixed an issue where PSRAM may not initiate correctly causing the update to fail

### Commits

**Firmware**
- firmware: ask for PSRAM a second time before giving up on it (d69deec)
- firmware: boot a box whose PSRAM does not answer, and say what it found (c2a6425)

**Docs**
- docs: the firmware is on 3.3.2 (6e79184)

## v3.3.1

3 Sep 2026

### Changes
- Added emission rendering, injected movement is pushed via a fitted model of your device
- Added "Full Rendering" where native movement ALSO passes through the model
- Added injection spreading to spread out added movement from sub-native injection rates
- Fixed an issue where after a pause, injected movement would wait an extra milisecond
- Fixed an issue where a device could get out-of-sync with its emitted state
- More improvements to stability and performance

### Notes
Rendering is probably the most important feature ever released. Injected movement now carries the texture of the device connected, not just a fixed, flat pattern
https://medius.k4tech.net/library/options#set-render

The protocol version has changed, so for those with their own libs, you will need to update accordingly.

### Commits

**Firmware**
- tools: let the relay arm allow an accumulation, and only an accumulation (881c34b)
- tools: let the mirror gate run itself (bcc50b7)
- firmware: keep the motion mirror true through a coalesced report (52dec74)
- tools: three gate defects, one of which inverted what a gate measured (d1e6c1b)
- firmware: hold the smoother's rounding order (f61dfd9)
- firmware: deposit a rendered report from the clock that emits it (9bcaf95)
- firmware: take a sixth off the model step and the observation pass (c52d7ef)
- firmware: learn the command period from the bucket's low edge (42c5811)
- firmware: cover a command rate that varies continuously, not in steps (ec76b3c)
- firmware: name the subject in three comments that opened on a pronoun (b2407d1)
- firmware: name what is being released instead of writing it (ee78ecd)
- firmware: keep the anchor a deposit has already earned (da65db8)
- firmware: drop the commentary the composing change made pointless (22c6678)
- tools: wait for the clone's event node instead of guessing one (a9d52c4)
- firmware: compose the release interval with movement riding (4f77770)
- spec: record where the build differs from the design (53697c9)
- firmware: fix the defects an adversarial audit of the release interval found (4f52577)
- tools: gate the release interval on density and magnitude, not the total (7b07f3c)
- firmware: spread an injected delta across the host's command interval (5d8a740)
- firmware: release an injected pot across an interval rather than on one drain (0eb0ee9)
- plan: the release-interval implementation (7f03864)
- spec: spread injected motion across the learnt command interval (bc5d559)
- tools: three gate defects the hardware run exposed (a31c648)
- firmware: say native for the operator's motion too, and drop two metaphors (9f1de70)
- firmware: say native in the host tests too (0f4bc36)
- firmware: say native (9577f8e)
- firmware: fix the defects an adversarial audit of the render change found (cb5107c)
- docs: name the box state a faithfulness capture requires (dab36b4)
- docs: mark the superseded render design records (1f57ef0)
- docs: correct the plan steps that contradicted their own constraints (1ea2e65)
- tools: have the client's selftest read every option back (125f3f1)
- firmware: dispatch and pack the box options in id order (e6588b4)
- tools: correct which personality the full-rendering gate runs on (5e6c796)
- docs: record the rendered texture in the detection register (6655022)
- tools: order the client's options by id, and name the bearing rather than the aim (dfd08d2)
- docs: say rendering adds latency, and stop there (250873a)
- firmware: credit ABCurves, and ship its licence with the images (395771e)
- docs: cut the render prose back to what the surrounding pages carry (f3b8c7e)
- docs: use the vocabulary and voice the rest of the stack already uses (684f625)
- firmware: keep the wire version this branch already introduced (fb83667)
- docs: record the soak result and what still needs a hand (ba47579)
- docs: record what the acceptance run measured and what it did not (a23b22e)
- device: keep the operator's motion out of every path that drops the host's (3169ec7)
- docs: describe the texture as its own setting, and what full renders (a74ee47)
- tools: address the texture settings on their own command (19da53d)
- device: render the attached device's motion with the same model (483084c)
- docs: plan the work for rendering native motion (7af1de1)
- docs: settle how native motion joins the model's stream (a4cc658)
- docs: the constraint set and open decisions for rendering native motion (58ad3d3)
- docs: design for spreading owed injection across the learnt command interval (61bfe18)
- tools: measure what a host commanding slower than the device costs the wire (f8fa8ec)
- docs: describe the kernel the box ships and the clock the model steps on (5ea77cd)
- docs: record every departure from the upstream render runtime and its measured cost (5f5d897)
- tools: verify the profile armed before the texture page claims a mode (6635fda)
- tools: apply the comparison kernels causally, at their own lag (bcc39ba)
- tools: resolve the clone's input node instead of assuming its index (6b571ae)
- firmware: begin a render phase on the tick it steps on, and hand back what it still owes (78545fa)
- firmware: render the intent in single precision, on a kernel that answers sooner (c0c16c4)
- tools: ignore the texture rig's output (cc46e41)
- tools: measure the render model against injection shapes a host actually sends (b2165e2)
- tools: score injected texture against the attached mouse (9a513ab)
- firmware: render on the same clock reports leave on (95e6e95)
- docs: record the measured result and why the render path deposits again (a43c4a8)
- tests: bound the assertions that passed on a dead pipeline (6925be7)
- firmware: drop the smoother's dead branches and answer flush misuse (78419a3)
- firmware: keep the device stack on its own core, and let the pace clock catch up (ed1d30b)
- docs: correct the renderer state size after the smoother change (4ee0cab)
- docs: what the render model reproduces, and why it paces off a timer (8286116)
- tools: gate the renderer on its texture and on whole delivery, not on idle fraction (f978169)
- firmware: stop the render pace clock on a clear, and only yield when a tick is already owed (43978f4)
- docs: record the measured injected-emit latency (41ee33c)
- tools: measure injected-emit latency per render mode (f3e1a14)
- firmware: the renderer keeps to its millisecond grid and starts a phase without folding the profile (94efd3c)
- firmware: injected motion emits as soon as its rate gate allows (8fb49d3)
- firmware: one place that builds a synthetic injected frame (ac55a88)
- tests: assert the render pipeline's latency without hardware (9eed0f9)
- firmware: the de-spiked smoother no longer drops the motion it starts with (383cbb2)
- docs: implementation plan for the injected-emit latency work (f0cb115)
- docs: design for cutting injected-emit latency (6d03dae)
- docs: use one spelling for the words that had two (b700ceb)
- firmware: apply a render mode change at the next phase, not mid-phase (03afab9)
- docs: correct stale protocol facts and tighten the writing (07fdee7)
- docs: split the render section and drop the em dashes (75735d7)
- docs: correct the protocol version and the emit section (df9d435)
- tools: assert the render profile is armed, and leave the box as found (6212b28)
- firmware: render only once the device has been seen to move (fcfbef5)
- firmware: render injected motion by default (092b7f7)
- firmware: give the render mode its own field in the emit option (ef63132)
- tools: show every render mode in the texture page and fix its idle metric (2ad2995)
- firmware: stock / de-spiked / off modes for the injected-motion smoother (2b5bfd2)
- firmware: no first-tick spike from the injected-motion smoother (e0fc07a)
- tools: ignore the generated render_viz.html (c3bffde)
- tools: visualise paced vs rendered injection (fd2550d)
- firmware: drop the stale mode-3 name from the render header (ab2e69c)
- firmware: make rendered a texture flag that composes with the pace (55622e2)
- firmware: vendor the ABCurves MIT license (2b8f7a4)
- firmware: cut comment noise in the render path (0f30828)
- release v3.3.1 (edcb98f)
- docs: list the rendered emit gate among the validators (3561e40)
- firmware: measure the rendered emit gate in isolation, off kernel time (482d940)
- firmware: harden the rendered emit path found by review (d6837e4)
- firmware: point the render gate at the trace mouse's own product id (fdbb35f)
- firmware: conformance gate for the rendered emit mode (7fa49cc)
- firmware: keep the rendered tick on-chip (IRAM code, internal-RAM tables) (183370b)
- firmware: rendered emit mode drives injected motion (36ec37a)
- firmware: move the clip ring to PSRAM to free internal RAM (70cec1c)
- firmware: 1ms grid reconstruction for the emit observer (a5c2961)
- firmware: bit-exact render runtime with SIMD-optimised tick and host gate (f9a10b3)
- docs: implementation plan for rendered emit mode (bbd6c1d)
- docs: design for rendered emit mode (e9407bb)

**Library**
- library: name what is being released instead of writing it (2cfd1df)
- library: say native for the report rate in set_spread's docs (4f86ba2)
- library: regenerate the C header, and widen two boundary tests past a byte (c8e0104)
- library: expose the release interval on the library and both bindings (ef0d6e6)
- library: say native (e047fee)
- library: cover the render option's async and C-ABI paths, and match the mock to the box (8a74375)
- library: expose the box options in id order across the library and both bindings (feadf82)
- docs: say rendering adds latency, and stop there (b76c1d0)
- docs: name the model the box renders with (bad84cd)
- docs: tighten the render option's doc comment (e64ec71)
- docs: use the vocabulary and voice the rest of the stack already uses (788be0b)
- library: keep the wire version this branch already introduced (b1b27dc)
- library: make the mock answer the pace the way an unarmed box does (11f7b86)
- library: address the texture settings on their own command (5f31ad4)
- library: one spelling in the Cargo.toml comment (6b1f0b5)
- library: take the render mode across the C ABI as a byte (e3a04f2)
- library: correct stale doc comments and tighten the writing (e2ff4f9)
- library: bump the C ABI, and require an explicit render mode (f997d6c)
- library: the render mode is its own emit field, and de-spiked is the default (860905c)
- library: a RenderMode of off / stock / de-spiked / unsmoothed (4fcb605)
- library: trim the emit-pace doc comments to one-liners (1971cf3)
- library: rendered is a composing flag, not a pace variant (9787636)
- release: bump the crate, C ABI, and Python binding to 3.3.1 (5dbfc7d)
- library: assert the rendered pace resolves to 1 kHz, and document it (59965e5)
- library: rendered emit pace across the crate, C API and Python (e5014c9)

**Docs**
- docs: name what is being released instead of writing it (23ad387)
- docs: drop the riding paragraphs the composing change made pointless (ce9cc43)
- docs: say that the release interval composes with movement riding (bc1453c)
- docs: correct what the release interval does, and say native (02d005b)
- docs: document the release interval and add its control (1dfa40e)
- dashboard: say what two more controls are, in fewer words (50db963)
- dashboard: say native for the wire rate too (b531d09)
- docs: say native (068eab6)
- dashboard: name the render setting, not what it does to the mouse (64cc05c)
- docs: fix the docs defects an adversarial audit found (611014b)
- dashboard: order the option code paths by id too (82b1bec)
- docs: list the box options in id order on every page (3bf7bb0)
- docs: put the option cards back in the order the other pages use (bcc5402)
- docs: say rendering adds latency, and stop there (e7b78ef)
- docs: credit ABCurves where the render option is described (861e177)
- docs: bring the render copy back to the shape of the cards around it (e57335d)
- docs: put the render option back with the other options (d24381c)
- docs: use the vocabulary and voice the rest of the stack already uses (8f43a93)
- docs: give the texture its own page instead of a footnote in the options list (9c60f5f)
- docs: keep the wire version this branch already introduced (53210af)
- docs: correct the texture's own cost and what it renders when it is off (5732204)
- docs: give the texture settings their own command and page (8145b5c)
- docs: shorten the labels that had grown into sentences (76b9167)
- dashboard: give every card a search anchor and every state its own card (6cffdd2)
- docs: correct stale references and tighten the writing (945600a)
- docs: cut the emit card down and stop tables repeating their own label (17cb075)
- dashboard: say what to do, not what the protocol is (b0b2132)
- dashboard: give the older-protocol state a card, not a loose callout (285ddfe)
- dashboard: connect a box a protocol version behind so it can be updated (491ed70)
- docs: correct the protocol version and the binding references (5051cc4)
- docs: rebuild the emit option page around its four fields (bbaa865)
- dashboard: four render modes in the selector; drop the Render Lab (7a483cf)
- dashboard: hint when rendered emits nothing on an unprofiled device (bc163c1)
- dashboard: a Render Lab to visualise rendered vs paced injection (a712679)
- docs: fix the rendered control's label case and reactive blurb (5317a5c)
- docs: strip the rendered prose to the terse house style (b5cb437)
- docs: match the bindings set_emit_pace signature and status field (dd7db68)
- docs: rendered is a composing flag, and credit ABCurves (34932ad)
- docs: order the option sections by id (8b77755)
- docs: tighten the rendered emit prose (3a70975)
- docs: rendered emit mode, and the 3.3.1 firmware version (a090bcc)

## v3.2.1

27 Aug 2026

### Changes
- Fixed an issue where a mouse would stop responding despite still showing as connected

### Commits

**Firmware**
- release v3.2.1 (c024681)
- docs: what the box does when a mouse stops answering, and what the delivery count means (c2c87e3)
- firmware: count a delivery stall only on an endpoint the PC reads (8c48083)
- firmware: bring a stream back when the mouse stops answering its own endpoint (aa409d1)

**Docs**
- docs: update firmware version strings to 3.2.1 (7e3ae55)

## v3.2.0

25 Aug 2026

### Changes
- Added firmware updating via USB2, both chips with no buttons or recabling
- LOCK is now a scale, you can keep a fraction of an input instead of only blocking it
- Added injection-based aim bearing, so LOCKs can apply with or against the direction of injection
- Added forcing of the rate the clone advertises
- Improved dashboard onboarding and update workflow
- Connection errors in dashboard now actually explain what is wrong
- Fixed some devices going unresponsive when the box attempts to seed certain information
- Fixed some wireless receivers reporting as a keyboard instead of a mouse
- Fixed clearing an aim bearing not actually fully clearing it
- Fixed a button LOCK reading back as the wrong thing
- Fixed SCALE affecting reports it shouldnt

### Notes
Updating is a big theme this release. Both chips can now take firmware over the control cable instead of performing the dance previously required. If an image won't run, the box will return to the previously working firmware automatically

However, you need to get TO v3.2.0 first though to enjoy this benefit. So follow the new setup page and that should hopefully be the last manual flash you'll need to do
https://medius.k4tech.net/dashboard/setup

LOCK got a big overhaul, it's a scale now instead of a binary on or off. 0 blocks everything, 100 allows everything through untouched. Above 100 amplifies the movement as well. Combined with the new aim bearing system and you can weigh native input by whether it's with or against the injection

Superstrike users, I will make a guide shortly on how to fix your device so it works correctly with this latest update. It will be found in #guides on the Discord

Keep it coming lads 🫡

### Commits

**Firmware**
- docs: correct the setup design to what the board and the reviews showed (8ff0f49)
- docs: the build order for getting a box set up the first time (05e0635)
- docs: the design for getting a box set up the first time (29e6813)
- tools: guard each restore, hand the sim back, and name the verb that exists (bd6bbde)
- docs: correct the forced-rate entry to what the wire actually does (16645c1)
- firmware: build the forced rate the way the files around it are built (346c220)
- firmware: say the poll interval changed only when it did (f4b8491)
- tools: run the rate gate against a real attached device, forcing away from what it declares (696c824)
- tools: let the riding gate run without a hand when the sim owns the only node (45e3456)
- tools: build the gate's mouse from the rig's own, and find its input node (77e7a57)
- firmware: what the review found in the forced-rate path (48d477d)
- tools: a gate for a clone running at a rate the device did not declare (25ff60e)
- docs: how a box is told to run at a rate the device did not declare (afbb2a0)
- tools: set and read the forced rate from the reference client (0527fda)
- firmware: ask the device for reports as often as the clone says it will be asked (94fe1f7)
- firmware: let a box advertise a rate the device did not declare (c99497e)
- firmware: carry a forced rate and what the clone advertises on the pacing option (ba558cc)
- firmware: work out the poll interval a forced rate needs, and write it into a descriptor (4c97cb3)
- docs: keep the dash sweep out of the file it sweeps (a9d859e)
- docs: the build order for a clone that runs at a forced rate (e9b5d91)
- docs: the design for running a clone at a rate the device does not declare (068a5a2)
- gate: hold each chip to the window it advertises (43ecf6e)
- firmware: let the mouse-side chip restart the way it always did (d36c59b)
- firmware: put the mouse-side USB controller down before that chip restarts (6165e95)
- firmware: ask for only as much as the chip can actually hold (8f1efa9)
- firmware: hold the whole window the box says it will take (c9e5a55)
- docs: say that abandoning a commit disarms both chips (4965827)
- firmware: abandoning one commit abandons both, and the client could not clear either (493bb1a)
- firmware: tell the other chip too, and settle before erasing (ce01bc4)
- firmware: only abandon an update while there is still something to abandon (898ff28)
- firmware: what the third review found in the second round of fixes (a1a3075)
- firmware: what the second review found in the first round of fixes (0bba54a)
- firmware: anchor every timer to the clock, not to zero (6f6728b)
- docs: match the renamed client call (c08bba0)
- firmware: read every elapsed-time check as signed (c07b370)
- tools: what the review found in the reference client (d03ca93)
- firmware: what the review found in the update path (ac065b0)
- tools: one command to push firmware to a connected box (211c39f)
- docs: how an image reaches each chip now (4711a91)
- tools: prove a broken image does not stick (20965e3)
- tools: a gate for taking an image over the control port (a9f26b8)
- firmware: ask the far chip for its version until it answers (1da1093)
- docs: record what the hardware corrected in the update design (9dbabfc)
- firmware: keep the clone down while the box rewrites itself (20a4807)
- firmware: serve an update on the control port and pass one along (0a1d078)
- firmware: let the mouse-side chip take an image over the internal link (a37c3b2)
- firmware: put the box down cleanly before it rewrites itself (161bbce)
- firmware: write a staged image to the spare slot (1f31d89)
- firmware: the chunk sequencing behind a staged image, with its tests (a139518)
- firmware: the wire shape for taking an image over the control port (1afe07e)
- firmware: give both chips a second app slot (a22f3c1)
- docs: the host chip gets one rom flash, and it has to be the right one (e14b65a)
- docs: the work order for updating over the control port (de9c107)
- docs: the design for updating both chips over the control port (edbd2ef)
- docs: the G502 answer is not random, it is before-first-motion (8944b4c)
- docs: point the work order at what actually shipped (b36bd88)
- docs: record what the review corrected in the at-rest rule (88c9289)
- docs: say which fields the spec pins and which only look pinned (cb4cd80)
- firmware: trust only what the descriptor pins, and ask once at a device that answers (bc6d4ee)
- docs: where an at-rest report comes from, in order of preference (80bef6d)
- firmware: ask a device once, and bring it back if the question stops it answering (e694922)
- tools: a gate for a device that stops answering when asked (fe39acb)
- firmware: reuse a report the box already has before asking for one (7f8e023)
- firmware: do not ask a device for a report its descriptor already spells out (f92d3b6)
- firmware: work out from the descriptor what a report reads at rest (a3c6f8e)
- docs: the build order for deciding when a device needs asking (d88e6e5)
- docs: decide from the descriptor whether a device needs asking at all (e0b4c26)
- docs: why a receiver falls into its bootloader behind the box (6cfccd4)
- fix: a full key bitmap made a receiver's paired mouse report as a keyboard (03f5d3e)
- docs: name the mechanism in the protocol reference and the comments (5fc4378)
- docs: correct what a discard, a refusal and a saturating gain actually do (ccf4f60)
- tools: validate the motion flags, and pin what the last round fixed (282386b)
- tools: stop a mutating verb defaulting to the destructive value (6ea1bb0)
- firmware: make a bearing clear actually clear, and keep the aim's angle (0e59085)
- docs: write down what the box applies, not what it was told (85adde4)
- tools: make a bearing gate that a plain sign lock cannot pass (252e352)
- tests: pin the weighing rules a mutation can still break (32603dc)
- firmware: weigh a report only when the scale actually changes it (13d1106)
- tools: stop the puller before closing the port it writes to (698a65f)
- tools: measure what the bearing does to a hand, not what it reports (125d7c2)
- firmware: let a button store the block it renders, not the number sent (0895364)
- firmware: re-read the version header when it changes, not once per build tree (6c56608)
- tools: validate a scale by hand, not only a lock (9a15813)
- firmware: let a lock keep a fraction of an input instead of all or none (19606fe)

**Library**
- ci: check the features this crate actually has, and do not ship wheels for a crate that did not publish (5ed374b)
- library: mirror the interval the box now advertises, and format (66f588e)
- library: what the review found in the forced-rate surfaces (ee75bd6)
- library: check the forced rate round-trips on a real box (06b2435)
- bindings: carry the forced rate through the C and Python surfaces (d31e06b)
- library: set and read the rate a box advertises (94d2cec)
- library: shorter refusal text (bc78c27)
- library: say what a refused update means and what to do about it (5cb91b6)
- library: derive the result-name guard instead of listing it (cdca871)
- library: clear what a refused commit left armed (0a6dc24)
- library: drop a reply the box has moved on from (74b6e09)
- library: look for a parked reply on every wake (5606b85)
- library: park a reply for the caller it belongs to (cc701a1)
- library: put back a reply that answers another caller (2df1b87)
- library: what the review found in the update client (0b7cafd)
- library: formatting (ee4567d)
- library: update box firmware over the control port (b2b43f6)
- docs: name the mechanism in the public API surface (f1fdc4b)
- capi: refuse a byte no constant names, everywhere one crosses (34747d2)
- hw_full: assert what only the firmware can satisfy (cb8f075)
- bindings(python): check a scalar before ctypes truncates it (8826d5a)
- capi: take a byte where the boundary cannot trust an enum (d80ce4b)
- crate: model the box's lock table so a reapply rebuilds what it found (2c98fac)
- crate: say what the scale readback actually promises (d2574c3)
- hw_full: prove the scale round trip and the bearing option on a box (78db22f)
- v3.2.0 (da8d355)
- bindings(python): follow the C ABI to the scale system (8bb2c5b)
- capi: carry the scale and the bearing across the C boundary (ed65e1d)
- crate: let a lock keep a fraction of an input instead of all or none (e179bcb)

**Docs**
- dashboard: let the manual flasher be manual, and size the secondary buttons like the rest (e643106)
- dashboard: keep the step chip, and stop the diagram forcing the gap above it (54d3a70)
- dashboard: put the step in the header instead of a row of its own (041af30)
- dashboard: even the gap either side of the step counter (c769401)
- dashboard: let the card header own the step counter's spacing (0a21b47)
- dashboard: give the step chip its own gap, and say the reason once (f8851e0)
- dashboard: set the device port apart from the two computers (86fa2a2)
- dashboard: one diagram per action, and cut the filler (4ed10e7)
- dashboard: judge each chip on its own version, and tests that can actually fail (324cc80)
- dashboard: a finish screen that could not finish, and the update path nothing tested (7996f79)
- dashboard: stop claiming a check that never ran, and cover the page that had no tests (5a29691)
- dashboard: two fixes that cancelled each other, and four more blind tests (1c4c470)
- dashboard: the wrong chip named, the slow read, and three blind tests (397baba)
- dashboard: the button beside the socket, and what round four found (d224f71)
- dashboard: the page freezes and the skipped gate that round three found (c0ec1ef)
- dashboard: what reviewing the fixes found in them (ce8f568)
- dashboard: what three reviews found in the setup path (de0efd7)
- dashboard: a guided first install, and a connect that says what is wrong (9992bc6)
- dashboard: turn a failed connect into one answer, and say which firmware gave it (0c952e7)
- dashboard: name the wire rate in the card's own heading style (cac69b8)
- dashboard: build the wire rate the way every other control on the card is built (c078168)
- dashboard: give the wire rate the card's own spacing, and name the two rate fields apart (260a4e9)
- dashboard: apply the forced rate the user actually asked for (55b2243)
- docs: the rate a box advertises is now something you set (fbfe5de)
- dashboard: refusal text that reads like the rest of the UI (a53ca34)
- dashboard: errors that say what to do (f40db50)
- dashboard: adopt a port that is already open, and say the window is not fixed (e108e85)
- docs: write the update pages the way every other page is written (7d6fd66)
- docs: put the update feature where the rest of the docs keep things (42efafd)
- docs: say that abandoning a commit disarms both chips (86c2a10)
- dashboard: make the typecheck gate able to fail (88f8787)
- docs: list the new result where the results are listed (8a7bb6c)
- dashboard: typecheck what the bundler never looked at (c835fea)
- dashboard: clear what a failed update left behind (aa668f7)
- dashboard: keep the real cause when a port goes away (24c8e42)
- dashboard: do not consume a reply from an abandoned transfer (a2f10f8)
- dashboard: do not lose an update reply that arrives early (b9d383a)
- docs: say which flashing path is the normal one now (009a9a4)
- docs: the library calls for updating a box (2209088)
- docs: a page for updating a box over the control port (b410928)
- dashboard: update both chips over the port you are already connected to (9cfdd68)
- dashboard: speak the firmware-update protocol (2f2deb1)
- docs: say where a lock readback can disagree with what the axis does (e30951b)
- dashboard: keep a readout's rows together (64f7075)
- dashboard: give the riding checkbox the margin its siblings have (33ccc30)
- Revert "dashboard: let the container own vertical rhythm, not the child" (ed9526e)
- dashboard: let the container own vertical rhythm, not the child (99c812d)
- dashboard: stop restating the reference in the control panel (2361d38)
- dashboard: pin that the release-on-silence fact is stated once (629aeba)
- dashboard: one sentence per control on the control page too (9ecc171)
- dashboard: put the bearing Turn off button back (d67a9e1)
- dashboard: one sentence per option, and one shape for every control (58977c9)
- dashboard: say why a connect failed on the control page (3690611)
- docs: name the mechanism, not the picture it makes (c5bfc00)
- docs: hold the rest of the reference to the bar the lock page set (dc0016f)
- docs: rebuild the lock page in the shape the rest of the reference uses (5a46100)
- docs: correct what a saturating gain does, and make the client claims true (bc998d7)
- docs: split the lock page, and say what the bearing actually does (660454e)
- docs: say each thing once, where the reader is looking for it (2aa0d86)
- docs: carry the scale, the aim and the version the pages describe (45169ab)
- dashboard: drop a lock entry the box would never have named (7ef46d8)
- docs: correct what Both does, and warn where the version cannot (ef59816)
- docs: document the scale and the bearing across every page they reach (fbf8811)
- dashboard: put the scale and the aim bearing on the control page (80bbf96)
- dashboard: follow the lock wire from a state to a scale (3bf110d)

## v3.1.0

17 Aug 2026

### Changes
- CATCH now observed every class of traffic, not just input
- Catch events carry the timestamp the report actually arrived
- Vendor specific interfaces are now relayed correctly, instead of being presented dead
- Alternate settings are now followed all the way to the real device
- Devices with multiple configurations are correctly described and can be switched to
- Added support for control endpoints above 0
- HID interfaces that declare extra endpoints are now driven (instead of being refused)
- Extra-large report descriptors are now correctly carried, via any interface
- Added a per-command movement riding override
- Fixed low-speed device support
- Fixed keyboards that split their keys across several reports
- Fixed key reports corrupting on an endpoint shared with a mouse report
- Fixed control frames being silently dropped whilst the box was emitting
- Fixed blanket lock axis doing nothing
- Fixed some devices reporting as the wrong KIND

### Notes
Security got a HUGE improvement this update, IMO this is pretty close to the limit of security for this hardware. Additionally, many more devices are now supported due to massive improvements in what can be cloned faithfully

CATCH is the other big one this release, it has received a massive overhaul, and now supports a wide range of functionality
https://medius.k4tech.net/library/catch

Keep the feedback coming lads 🫡

### Commits

**Firmware**
- docs: the design behind the per-command riding override (4aa8246)
- tools: a gate for what riding delivers, and what it holds back (4598c8b)
- tools: two tools had been sending move frames the box throws away (269decb)
- firmware: let one move say whether it waits for the user to move (d9da046)
- docs: say when a clip trigger takes its input lock, and when it does not (e3790a7)
- firmware: lock every axis when the blanket asks for it (c8598cd)
- docs: write the catch design doc without the dashes (3ce7a00)
- docs: the design behind the catch API rework (5b82fe9)
- firmware: charge a lost event to every subscription it belonged to (f691b7b)
- tools: leave the rig on a device the other gates can actually exercise (54750b7)
- firmware: make a snapshot say which class and which edge produced it (c2f4760)
- firmware: hold back only what can flood the link, and let the waits be short enough to matter (375267f)
- firmware: make the clock estimate say what it actually knows, and the tests able to tell (003518e)
- docs: say what the counter orders and what actually holds input early (e791ae4)
- firmware: keep the observation stream from making an input report late (f5ab3a7)
- tools: a conformance gate for the observation surface (6eee30a)
- firmware: keep the busiest observation work off the core the USB stack needs (1877297)
- firmware: carry the crash record's caller chain, and keep the rig off the desktop cursor (52c70ad)
- firmware: wake the drain directly instead of through a shared counter (bcdea80)
- firmware: read the observation table without locking the paths that read it most (cce63f1)
- firmware: pace the event stream to the link instead of spinning on it (8219d3d)
- firmware: keep the event stream from stalling the link it shares (255569e)
- docs: document what can be observed and which clock stamped it (0f132a6)
- tools: prove observing the traffic does not disturb it (39fb9c0)
- firmware: observe every class of traffic the box carries (bfbed83)
- firmware: hold the observation subscription as a table, and rank its event stream (cb46f50)
- firmware: stamp bulk where it arrives, like every other inbound stream (7c8ca4b)
- firmware: a four-stamp exchange so the two chips' clocks can be compared (619e26f)
- tools: teach the reference client the new observation surface (9694782)
- firmware: measure the two chips' clock difference instead of assuming it (380864b)
- firmware: address what to observe the way locks are addressed (c7c200c)
- docs: the implementation plan for observing everything the box carries (52d3182)
- docs: tap emitted reports where they complete, not where they are staged (59c8542)
- docs: a design for observing everything the box carries, not only physical input (96ee0f4)
- docs: a top-level guide so the repo carries its own bring-up knowledge (6bb87dc)
- tools: prove the descriptor half of the configuration cost, on hardware (11b67c1)
- firmware: cost the configurations the clone is not in, not just the one it is (b23f831)
- firmware: the faults only repeated switching could reach (bc626ca)
- firmware: rebind the roles a configuration switch moves, and hold every configuration's descriptors (8092fd6)
- firmware: a configuration switch rebuilds what it changes (3d72c39)
- firmware: the rest of what the reviews found (e346dc6)
- firmware: a marker that is a prefix of another could never be seen missing (3c5215e)
- firmware: defects a review found, including two the previous round introduced (368fb59)
- firmware: carry the report descriptors a configuration switch needs, with the switch (76e3a12)
- firmware: enter any configuration a device offers, not only the one it came up in (b262349)
- firmware: carry a control data stage larger than one link frame (62b41b1)
- tools: count the bytes on every endpoint a shape exists to exercise (7a7c042)
- tools: make the hardware oracles able to fail (2853b1d)
- firmware: defects found reviewing the rest of the interface-coverage work (fcfbd14)
- firmware: defects found reviewing the control-endpoint work (7050f85)
- firmware: carry a control endpoint a device declares beyond the first (df18d25)
- firmware: name the control endpoint above zero, and teach the port to hear it (a1c527d)
- firmware: describe every configuration a device has, not just the one it is in (cccaa2d)
- firmware: follow the alternate setting a host selects, all the way to the device (51bb6d0)
- docs: why the alt-setting relay is asynchronous (cd6ed02)
- firmware: drive the endpoints a HID interface declares and its driver ignores (f8a0d11)
- firmware: carry a report descriptor the device actually has, from an interface of any class (ab95091)
- docs: design for retiring every refusal the hardware does not force (b29edaa)
- firmware: a control transfer too big to buffer hung the clone instead of failing it (66580fc)
- firmware: the two chips disagreed about which interfaces they serve (eba56a3)
- tools: the refusal ladder had no test that ran it (c9f688a)
- firmware: an audio device cloned in silence, minus the half that mattered (ae6014e)
- firmware: a HID interface could declare an endpoint the clone never feeds (866ff13)
- firmware: a clone answered questions about configurations it does not have (90e3d13)
- docs: the vendor bulk ceiling is measured now, not estimated (7814fb3)
- docs: the vendor-interface pass-through design (0494a35)
- firmware: vendor-specific interfaces were declared to the PC but carried nothing (446b93f)
- firmware: a rejected hunk could still report itself applied (9ece3cc)
- firmware: defects found reviewing this branch (d0a5751)
- docs: the low-speed frame-interval fault, and finish a truncated sentence (5584bbe)
- firmware: low-speed devices were told the bus was idle on every frame (6d28b89)
- firmware: one keypress read as three on a keyboard that splits its keys across reports (c3c4aa3)
- firmware: the previous commit changed no behaviour; say so (82d9c02)
- firmware: log which firmware image is actually running (e5add6f)
- fix: device kind was decided by interface order, not by what the device sends (b91235b)
- fix: queue overflow dropped the newest report on one of two paths (5459acc)
- fix: report-ID budget counted Feature reports it never needed (a38482c)
- Revert "surface reports cut short mid-transfer" (bedda73)
- fix: key reports corrupted on an endpoint shared with a mouse report (cf55d44)
- docs: low-speed clones now match native bus speed (2d0077f)
- tools: screen HID devices for multi-packet reports (b0e1a84)
- fix: low-speed device support on both chips (ac5a647)
- docs: compress the protocol timestamp note (4195634)
- docs: spec matches the wire-width crate surface (7224a5d)
- docs: usage-event timestamp capture from the bench (694a185)
- tools: revive the lock validator and poll for ROM download (20f668d)
- docs: native jitter baseline for the timestamp accuracy oracle (3f73ce6)
- docs: record the bench device's measured report families (3d31dbb)
- firmware: catch events carry the report's arrival timestamp (0722057)
- docs: timestamped catch events design spec (644ea05)
- revert: drop the QUERY(LINK) selector (283e47c)
- fix: defects found reviewing the control-link work (4a2962f)
- firmware: drop the emit-path blocking histogram from RESP(LINK) (6307db2)
- firmware: RESP(LINK) reports only the window it can measure exactly (8974cf2)
- v3.1.0 (0b4e07e)
- tools: control-link loss rig, scripted host flash (5e0cc83)
- firmware: control frames survive a busy emit path (3a1a72b)

**Library**
- bindings: carry the move override through C and Python (e0975ce)
- crate: let one move say whether it waits for the user to move (35034c2)
- docs: strip the dashes and the padding out of the reference (ecfab01)
- crate: address an input the way a lock does, and hand back the edges (c6f5ca0)
- crate: refuse a subscription the box cannot hold, and send only what changed (b341abf)
- hw_full: measure the sustained rate, and drive the traffic a still device will not (39cc619)
- crate: route a held-usage snapshot as the class state it is, and mirror it in C and Python (ce3a72e)
- crate: expose the protocol version, and stop asserting quiet on a streaming clone (8ee86f3)
- hw_full: check the per-id observation path on hardware, not only the blankets (344230a)
- crate: route each observed input event by the address it actually carries (3cfb471)
- crate: give each observer only the traffic it asked for (c6ea88c)
- bindings(python): follow the C ABI to version 4 (6bbe0cf)
- capi: carry the observation surface across the C boundary (911eb2f)
- crate: observe every traffic class, not only physical input (cd50aca)
- capi: hold the ABI version at 3 (7ce742b)
- v3.1.0 (956d9c1)
- refactor: hand the catch timestamp over at its wire width (8b642b5)
- refactor: match the crate's idiom in the catch clock widener (72e76b1)
- feat: catch events carry the report's arrival timestamp (81118e6)
- v3.0.1 (f6b4dd1)
- fix: stop Windows injection queueing behind the reader thread (7ba7c4c)

**Docs**
- docs: check the rest of the pages this feature touched the same way (6f6d350)
- docs+dashboard: follow the patterns the reference already has, on the pages this feature touched (fdc3b21)
- docs: document what a move can say about waiting (9bb4f27)
- dashboard: drive the riding override from the control page (a3e52c4)
- docs: index the names the catch rework introduced (3e2ed8b)
- docs: index the dashboard cards search could not reach (737249f)
- dashboard: name the clip trigger flag in full on the checkbox (a59bf55)
- dashboard: call the clip trigger flag what the protocol calls it (a60a88e)
- dashboard: open the clip trigger picker on the class its own list leads with (96b1eca)
- docs: describe a clip trigger's input lock by what it does (5f03e36)
- dashboard: rebuild the control page around what the box can do (08e613c)
- docs: cut the documentation down to what it actually says (f4d9e68)
- docs: stop the reference justifying what the table just defined (b7a1698)
- docs: stop the tables saying their own column headings (2753832)
- docs+dashboard: document the catch surface as it is, not as it was (f43090c)
- docs: a table that is full, and who a lost event is charged to (a1a4b14)
- docs+dashboard: say which edge produced a snapshot, and how to route one (8f9f0e0)
- docs: document the protocol version a build speaks (5ee0d93)
- docs+dashboard: say which class a snapshot names, and what an unfitted drift rate means (ee104d3)
- docs: correct what the event counter means and how the stream is ranked (d43d7bb)
- docs+dashboard: document and display every traffic class the box can observe (1bad5f4)
- docs: repoint two search entries at the anchors that exist (e3b9ca8)
- docs: ABI displays back to 3 (81c9e65)
- docs: crate and ABI displays for v3.1.0 (55387ed)
- docs: firmware displays to 3.1.0 (4ef0046)
- dashboard: decode the catch timestamp instead of reading it as motion (90cc283)
- docs: ts_us belongs on the frame that carries it (37146b0)
- docs: trim the timestamp cards to reference-only (3026e95)
- docs: rebuild the timestamp cards as diagrams and tables (da0dc25)
- docs: correct the mock inbound-traffic card subtitle (1b2c016)
- docs: catch events carry the report's arrival timestamp (bd40044)
- docs: crate version displays to v3.0.1 (981a99e)

## v3.0.1

21 Jul 2026

### Changes
- Fixed a bug with the lock system where events could report through a lock if other conditions were met

### Notes
Keep it coming

### Commits

**Firmware**
- firmware: classify falls open on report shapes it can't model (73d0dbd)
- firmware: a locked input emits nothing instead of an empty report (v3.0.1) (7abc2a8)

**Docs**
- docs: firmware version displays to v3.0.1 (9f6257b)

## v3.0.0

18 Jul 2026

### Changes
- Unified mouse buttons, keyboard keys, and media into a single input model
- Added buffered clip playback
- Added on-device clip triggers
- Added retained clips with looping replay
- Added per-clip auto-lock
- Clip triggers can be consumed to never reach the target pc
- Added persistent box names
- Added AI support for documentation

### Notes
Buffered clips and the on-device trigger system is documented here: https://medius.k4tech.net/library/clip

Buttons, keys and media are now a unified "Usage" across the whole api, you will need to update your code if you are using any of the libraries

Keep the suggestions coming

### Commits

**Firmware**
- docs: update README control-surface to proto 3 (MOTION_EVENT/USAGE_EVENT, CLIP_SET/CLIP_TRIGGER, 10 query selectors) (d495261)
- ci: split the Discord release changelog across messages over the embed limit (b26bdea)
- docs: control-protocol clip trigger set + config + RESP(CLIP) rewrite (8d02390)
- tools: refclient clip trigger set + config, new RESP(CLIP) decode (3d2ca96)
- firmware: clip trigger set, retain/rewind engine, and queryable config (feefbe2)
- docs: clip trigger + config redesign spec and plan (c872c0c)
- cleanup: strip comments and AI-slop from host firmware, common, and docs (4bde84e)
- cleanup: strip AI-slop and comments (tools, host tests, protocol doc) (26a0a08)
- cleanup: strip firmware comments and AI-slop (9eaea0b)
- medius.py: one usage vocabulary for inject and lock (8027a85)
- docs+comments: proto 3 wire spec, RESP(CATCH) media bit, stale event-name comments (5f1f6dc)
- v3.0.0 (e939e75)
- medius.py: decode the clip held-usage snapshot in RESP(CLIP) (b1b60ca)
- firmware: report clip held state as one usage snapshot, not a button bitmask (0321ece)
- firmware: one catch event shape for every input class (9b79c45)
- firmware: uniform LOCK addressing and a full lock-state readback (proto v3) (d3d536b)
- firmware: one inject verb and one synthetic-emit path for every class (093d3f8)
- firmware: unify the HID injection core on one momentary-usage model (30e27fd)
- docs: input-taxonomy unification design (8dc8f7d)
- firmware: field-generic clip control with an autolock scope (f977fb5)
- firmware: clip STOP releases held input; auto-lock owns locks per direction (f76849e)
- firmware: clip auto-lock locks all input, drop the lock-mask (3ad676c)
- firmware: buffered clip playback drives keyboard and media, not just the mouse (255ebd3)
- docs: note buffered clip playback in README, ARCHITECTURE, tools (2c2a9c7)
- docs: correct clip overrun semantics + crate API reference (979db9b)
- firmware: harden buffered clip playback (adversarial review) (e8909fd)
- firmware: clip reference client + protocol doc (752fc28)
- firmware: clip playback engine + command dispatch (92fb50f)
- firmware: CLIP protocol constants + status packer (c99630c)
- firmware: clip SPSC byte ring (host-tested) (39d2651)
- firmware: clip entry codec (host-tested) (252df80)
- docs: buffered clip playback implementation plan (3e29317)
- docs: buffered clip playback design spec (17f8cd0)
- firmware: persistent, host-settable box name (671afbe)

**Library**
- docs: fix README to the v3 unified-usage API (0a34b65)
- fix: repair crate CI for v3 (9eaed71)
- hw_full: correct stale proto assertion and grab all composite clone nodes (e724e36)
- python: clip trigger set + queryable config bindings (59b2682)
- capi: clip trigger set + queryable config C ABI (0663193)
- clip: trigger set, retain/rewind, and queryable config surface (89c5521)
- cleanup: strip AI-slop from README and a stray doc-comment (5f5b08e)
- cleanup: strip unnecessary comments and AI-slop (crate, capi, python) (db05194)
- finish the builder unification + align the usage id field (dc0b940)
- bindings: rename MediusInput to MediusUsage (one Usage vocabulary) (9c9bbd7)
- review: LockScope sum type, shared usage codec, safer conversions (7657b2b)
- v3.0.0 (b2f684c)
- hw_full: v3 handshake + correct the clip/lock/name checks (65ec4a7)
- host: clip held state is a usage snapshot across crate, C ABI, and Python (cf2befe)
- python: unify bindings on the Input usage taxonomy (ABI v3) (b8eaab3)
- capi: unify the C ABI on the Usage taxonomy (ABI v3) (3659dee)
- crate: unify the input API on Usage and Axis (proto v3) (0676994)
- clip: ClipConfig options type + field-generic control API (6ac4187)
- clip: held is a per-button bitmask; enforce the edge cap; validate C edge actions (c47cfb1)
- crate + C ABI + Python: field-generic clip reuses existing types (a7158f3)
- docs: buffered clip playback section in the crate README (5a5f16f)
- crate + C ABI + Python: buffered clip playback (05728aa)
- crate + C ABI + Python: box name (19c9f17)

**Docs**
- docs: update README to the v3 API (opcode range 0x15, all 5 sections, input-passthrough, drop dead MidnightUI link) (7339e79)
- chore: shorten server.json description to the registry 100-char limit (8474b06)
- chore: MCP registry manifest (server.json) + publishing guide (3b74cc5)
- docs: demonstrate a move and an edge on one clip frame, all four surfaces (e9d7574)
- docs: move AI page to its own /ai section, add dropdown link (12a3ef9)
- docs: fix clip reconnect claim and clip-query section polish (bfc3014)
- docs: lay out the clip streaming/retained and trigger sections visually (42973bd)
- docs: add the AI & LLM access page (6ec7aea)
- feat: advertise MCP, cache headers, rel=alternate, prerender Home (b8c01e5)
- feat: ChatGPT-compatible search/fetch MCP tools (6600245)
- docs: fix the AI-actions menu button font (49f6ff3)
- fix: review findings (head defaults, search cap, route decode) (275e7f4)
- feat: serve the agent surface under vite dev and preview (fa14b49)
- ci: prerender stage in Docker, tests in CI, prerender scripts (8044820)
- feat: move AI-actions to a titlebar dropdown (portaled) (8fd103e)
- feat: per-route head + AI-actions row + SSG double-mount fix (b4a4dc7)
- fix: harden markdown extractor (review findings) (4b3e76d)
- feat: remote MCP docs server on /mcp (2db15e8)
- feat: agent content-negotiation in the Bun server (c58e47f)
- feat: build-time prerender + markdown extraction pipeline and agent artifacts (0e254db)
- docs: link the clip queries from the Clip page, like Lock and Catch (cdd428d)
- docs: agent-accessible docs design spec (3b67739)
- docs: move the clip queries onto the Requests page with the others (205b7de)
- docs: full page-by-page consistency pass across all 78 pages (79f41fc)
- docs: fix broken binding anchors + em-dash labels, mirror c/python clip API (5294fa5)
- docs: tighten the clip page to the sibling norm (82db2b0)
- docs: fix clip content + anchors from the migration review (9ecaf50)
- docs: clip trigger set + queryable config across the SPA (86a81be)
- docs: add the missing CLIP entry to feature-index enumerations (950e079)
- cleanup: strip AI-slop and narrating comments from the docs-SPA components (2a82ba0)
- docs: dashboard UI copy pass (terminology, labels, one diagram prop) (9555b82)
- docs: page-by-page quality pass (dedup, links, diagram redraws) (18fee2f)
- docs: finish the builder + field-rename propagation, ClipState meaning tables, reconnect badge (265cdff)
- docs: apply deep-review findings + propagate the Usage rename (52ab2c5)
- docs: v3.0.0 / proto 3 version references (6db69c3)
- docs: unify the input taxonomy across the reference and dashboard (39b49e5)
- docs: field-generic clip control and ClipConfig (89ae248)
- docs: correct clip status held (button bitmask) and ticks (content-only) (373f8f8)
- docs: rebuild the clip pages to match the rest of the docs, diagram-first (831879d)
- docs: rewrite the clip pages, field-generic and terse (be75744)
- docs: buffered clip playback (ea11667)
- docs + dashboard: box name (1ed73a2)

## v2.3.2

4 Jul 2026

### Changes
- Fixed an inconsistency in the locking behaviour

### Commits

**Firmware**
- firmware: soft-release clears a force-release, not just a press (v2.3.2) (da24f52)

**Docs**
- docs: soft-release defers to physical from any state; version strings to 2.3.2 (5e2d10a)

## v2.3.1

3 Jul 2026

### Changes
- Fixed lock and catch occasionally binding to the wrong interface on certain mice
- Lock and catch logic now applies across ALL applicable interfaces, not just the first
- Injection now prefers the boot interface for certain mice

### Notes
These changes should hopefully fix some inconsistencies some people were having with the injection/lock/catch logic
It may also improve device support for certain devices

Please keep the bug reports coming, I'm committed to fixing as much as a physically can

### Commits

**Firmware**
- firmware: catch and lock cover multi-interface mouse dongles (v2.3.1) (8a3c63f)

**Docs**
- docs: update firmware version strings to 2.3.1 (3d77801)
- docs: correct the stale crate version (2.0.0 -> 2.3.0) (1886e43)
- docs: bare-symbol card titles for discovery + a few consistency fixes (8c88a19)

## v2.3.0

1 Jul 2026

### Changes
- Added emission rate control (Learnt rate (default), Interval (bInterval based), Fixed)
- Improved HID capture on erratic enumeration
- Clone is now only emit after full enumeration (or timeout)
- Multi-box support, more detailed box descriptors
- Device info now carries more information about the connected device

### Notes
Emission pacing control can be found in the documentation: https://medius.k4tech.net/library/options#set-emit-pace
Discover by device type and similar multi-box steps can be found here: https://medius.k4tech.net/library/discovery

### Commits

**Firmware**
- firmware: self-heal a control decoder wedged by an abandoned partial frame (abb0c95)
- docs: sync the control-protocol reference + READMEs to the DEVICE_INFO rename (kind, product, box MAC) (64dd946)
- firmware: name the DEVICE_INFO product-tail cap (127) so host string buffers keep NUL room (dc36571)
- tools: multi-box discovery (list / open-by-id) + generic device_info in the reference client (bd283dd)
- firmware: VERSION carries the box MAC; MOUSE_INFO -> generic DEVICE_INFO (+kind, +product) (691aadb)
- docs: plan multi-box + generic device-info (80a07bd)
- docs: spec multi-box management + generic device-info (406a80a)
- host: robust HID report-descriptor fetch (blind-fetch + retry, verbatim) (1158d0a)
- host: emit the clone snapshot once all HID interfaces are captured (f2642eb)
- host: count HID interfaces in a config descriptor (for capture completion) (35ba281)
- docs: plan the capture-robustness implementation (065ba8d)
- docs: spec capture robustness for slow composite HID devices (7c1237b)
- Bump version to v2.3.0 (307c21e)
- tools: harden the emit-rate test + validation rig (998cd72)
- firmware: reset the interval emit-rate when the device detaches (faa0ee0)
- tools: emit-rate HW validation rig (02da0af)
- tools: emit-rate option in the reference client (8c3ed75)
- firmware: wire the emit-rate option command + query (fbda1e0)
- firmware: emit-rate pacing override (state + gate) (7351086)
- firmware: add the emit-rate option to the control protocol (7fcdb74)
- docs: plan the emit-rate option implementation (b5151ce)
- docs: spec a selectable emit-rate option (d347419)

**Library**
- style: cargo fmt (345ad1e)
- crate: discover example (enumerate boxes + open by kind/id); gate open_transport to the mock build (abcfe0e)
- capi + python: device_info (+kind +product) + multi-box discovery (list/open_by_id/find_*_box) + version MAC (f642a95)
- crate: multi-box discovery (list/open_by_id/find_*_box) + reconnect by identity; device_info replaces mouse_info (8480595)
- Bump version to 2.3.0 (d2f3a86)
- mock: match the firmware's emit-rate snapping (1eb5851)
- emit-rate pacing: C ABI + Python bindings (6d9ab05)
- emit-rate pacing API (93b19fc)
- bindings(python): add a README and use it as the PyPI long description (d304bea)

**Docs**
- docs: update example version strings from 2.2.1 to 2.3.0 (9eeb739)
- docs: bump docs site to v2.3.0 (97a25f3)
- dashboard: flush a wedged box decoder before retrying the handshake (7529503)
- dashboard: capitalize the device-kind chip, make the emit-rate radio vertical to match the others, and group Box id with the identity rows (95124f5)
- docs: add the mac field to mock Version examples; correct is_composite to n_hid>1 (c61e317)
- document multi-box discovery + generic device-info (kind, product, box MAC) (72f81a7)
- docs: rewrite the native Options page table-first (f78e1e5)
- document the emit-rate option (218cf4e)
- dashboard: add the emit-rate option control (39146b2)

## v2.2.1

30 Jun 2026

### Changes
- Added C/C++ bindings
- Added Python bindings
- Hardening of release binary
- Improvements to documentation

### Notes
Documentation for the new bindings can be found here: https://medius.k4tech.net/bindings
This was a required step so other developers can start utilising Medius, now I can start implementing more features/functionality

Make sure to keep the suggestions coming

### Commits

**Firmware**
- firmware: binary hardening (v2.2.1) (da48813)
- release: drop the parenthetical from the Library changelog header (50d2233)
- release: combine the 3-repo changelog and attach a bin zip to Discord (9157dd8)

**Library**
- bindings(python): add a README and use it as the PyPI long description (d304bea)
- ci: drop sccache (measured ~no gain) + the redundant host rust reinstall (47ab1dc)
- ci: cache the wheel Rust builds with sccache (GHA backend) (e1ad8b3)
- ci: aggressive caching to cut CI time (32fa151)
- ci: attach only wheels to the release, not the sdist (dc1fdde)
- ci: export MACOSX_DEPLOYMENT_TARGET=11.0 to cibuildwheel so delocate accepts the macOS wheels (772f1ce)
- ci: pin macOS deployment target so the x86_64 wheel passes delocate (6de7f4c)
- ci: wheels match the C tarball platforms (drop i686, add macOS x86_64) (5bab67e)
- ci: skip-existing on PyPI publish so a re-dispatch is idempotent (ff4fa90)
- ci: fix the crates.io version check (User-Agent) + attach wheels to the release (8940488)
- ci: build all 5 C ABI tarballs + add a manual backfill path (cc0dcac)
- ci: merge the C ABI + Python checks into ci.yml; gate the release on them (fc94810)
- ci: drop the unavailable macos-13 wheel runner (146fc21)
- release: bump the crate, C ABI, and Python binding to 2.2.1 (17f04ba)
- bindings: drop the C++ wrapper; the C header serves C and C++ (336b4d4)
- test: cut drift — src/tests covers only what hw_full can't reach (ab361dd)
- Drop the docs/ directory — docs live in the medius-docs repo (1c3d127)
- review: cut slop and dedupe across the bindings (1d2cd64)
- docs(bindings): drop DIFFERENCES.md for a short README section; trim prose (079731d)
- bindings: drop the Conan recipe / vcpkg plan (04e8cf8)
- ci: fold the bindings release into the crate publish flow (4ff3da7)
- ci(wheels): install libudev in the manylinux container; skip musllinux/win32 (5b6c99f)
- bindings: deviation doc + publishing (PyPI, C/C++ release assets, Conan) (eedbae9)
- bindings: restore the Clone capability (Device/streams/MockBox) (c78696e)
- docs: bindings README + main README pointer to the C/C++/Python bindings (d044b5f)
- ci: build/test/lint the C ABI + bindings; fix the publish version check (6a21cf9)
- bindings(python): self-contained sdist, py3-none wheel, API fixes (20beb94)
- bindings(python): ctypes package over the C ABI + wheel packaging (d3a652e)
- bindings(cpp): header-only RAII C++ wrapper over the C ABI (00b5c36)
- capi: cap catch-event key count at u8::MAX so it can never wrap (95f8113)
- capi: C ABI crate (medius-capi) covering the full medius surface (2eeadb8)
- docs: design spec for the C ABI + C++/Python bindings (cb6919b)

**Docs**
- docs: de-AI the binding prose and set version strings to 2.2.1 (478e9ec)
- docs: syntax-highlight code blocks with Prism (e009fb4)
- docs: bindings section for C/C++ and Python (49fede4)
- dashboard: render the grouped per-repo changelog headers (9123bc2)
- docs: add a Discord link (top-bar button + search) (52cfbc9)

## v2.2.0

29 Jun 2026

- firmware: enforce the no-radio guarantee with a build guard + document (1483d70)
- Bump version to v2.2.0 (22b2ced)

## v2.1.0

28 Jun 2026

- Feat/options movement riding (#13) (e657212)
- research: note report-density is sensitivity-dependent (no absolute human threshold) (0b4d928)
- research: movement-injection detectability rig + findings (781ff2d)
- Revert "ci: cache the esp-idf docker image" (2982ef5)
- ci: cache the esp-idf docker image (31ef02e)
- ci: cache ccache across firmware builds (de14333)

## v2.0.1

25 Jun 2026

- Gate verbose diagnostics behind the dev build flag (v2.0.1) (f4f6074)

## v2.0.0

25 Jun 2026

- Unified HID-input core (inject/move/lock/rate, faithful EP0 proxying) (#12) (da90ef9)
- Keyboard support P1 — injection state into a per-target table (#8) (70bbdd3)
- Keyboard support P0 — parser learns keyboard fields (#7) (9f949cf)
- fix(keyboard): follow the active interface for catch + inject (cb89c70)
- docs(protocol): keyboard + media (KEY/CONSUMER/KB_EVENT/CONS_EVENT, KBD_CAPS, KEYS catch) (fca4e0f)
- Make the mouse protocol names explicit, matching KBD_CAPS / KB_EVENT (dee2484)
- P5: note the variable-bitmap Consumer form is passthrough-only (review nit) (546ee25)
- P5: media (Consumer) key inject + catch (3bf90ba)
- P5: parse the Consumer (media-key) collection (a1ee76a)
- P5: bind the richest keyboard interface, not the boot 6KRO (96ccf5a)
- P4: review nits — widen NKRO run counter, fix stale catch comments (7f47dd9)
- P4: keyboard catch — generalize the catch pipeline in place (15c359a)
- P2: review hardening — reserved keycodes, bind alias, suspend-safe release (992b1e6)
- spec: drop send_text — library stays semantic, no keystroke automation (f54ff65)
- test: cover the keyboard control-protocol additions (3ff5aa9)
- P2: wire keyboard inject into usbdev + control dispatch + medius client (6bae488)
- P2 (wip): keyboard render core + protocol (host-tested) (57bfd90)
- P1: injection state into a per-target table (mouse byte-identical) (4a605f9)
- P0: keyboard field parsing (modifier / array / NKRO bitmap) (a0c24b4)
- Add keyboard support design spec (v1.7.0) (61b0773)

## v1.6.0

22 Jun 2026

- CATCH — physical-input event stream (v1.6.0) (#6) (42bf371)

## v1.5.0

22 Jun 2026

- LOCK — block physical input selectively (v1.5.0) (#4) (801b1ea)
- Post the release changelog to Discord (#5) (1a32ae0)

## v1.4.1

21 Jun 2026

- LED control (v1.4.1) (#3) (b85bb5b)

## v1.4.0

21 Jun 2026

- feat: device-info requests (MOUSE_INFO/CAPS/RATE/STATS + RATE_CONFIDENT) (#2) (01207b2)

## v1.3.1

18 Jun 2026

- rate: log/persist the realised emit rate, not the raw bucket centre (1c72990)
- docs: sync reference docs with v1.3.0 (link watchdog/safety machine, drop dead PONG) (455f837)

## v1.3.0

18 Jun 2026

- release v1.3.0 (94ccf57)
- docs: record the inter-chip link watchdog + safety machine (HW-validated) (2e6f224)
- tools: link-watchdog HW validation script (idle stability, no-false-trip, strand-trip) (57ad484)
- docs: sync link-watchdog spec/plan with review fixes (8f71df4)
- fw: adversarial-review fixes — dedicated lossy heartbeat task, drop dead PONG (dbd5303)
- docs: link-watchdog safety design + implementation plan (d9b9d0e)
- host: ~100ms inter-chip heartbeat so idle links stay warm (ea44acc)
- device: inter-chip link-loss watchdog, safety clear on loss/detach (4c07124)
- usbdev: unify the atomic clear as usbdev_safety_clear() (e9ef4fb)
- link: pure link-liveness watch helper + host test (920e674)
- docs: bring DETECTION-RESISTANCE + ARCHITECTURE up to v1.2.0 (6c2e39c)

## v1.2.0

17 Jun 2026

- release v1.2.0 (b73ae80)
- fw: strip comment slop from the Tier-2 detection-resistance work (7f342c3)
- tools: fix flash_device.sh for esptool v5 + target the device's 303a download port (3ea000d)
- fw: adversarial-review fixes (patch-harness silent-unpatched + ctrl-chunk race + drop-on-full) (d3dbbb2)
- fw: B6 — clone non-ASCII (raw UTF-16) string descriptors byte-true (e2fa27f)
- fw: interrupt-OUT endpoint forwarding (70a3804)
- fw: B8 — chunk proxied control transfers with a data stage >504 B (e96759f)
- fw: build-time CherryUSB patch harness + 3 core conformance fixes + host limits (f5ae094)
- fw: iConfiguration/iInterface caching, snapshot forward-compat, multi-IN guard (40ce42d)
- fw: enum-safe empty-string fallback + wIndex check + audit doc updates (36dd316)
- fw: EP0 device-feature conformance + capture stale-tail/empty-string fixes (a3a192f)
- docs: drop the "risky" framing from the host-chip flash (f5ffa35)
- docs: reflect v1.1.0 native-rate injection pacing (6803c0a)

## v1.1.0

16 Jun 2026

- fw: pace pure injection to the mouse's live report rate (44d26a8)

## v1.0.2

16 Jun 2026

- fw: clone the BOS descriptor for USB 2.01+ mice (8159f29)

## v1.0.1

16 Jun 2026

- release v1.0.1 (8f1bc9b)
- fw: robustness fixes — stream re-arm, snapshot re-send, enum recovery (dd34ba4)
- ci: release notes are a commit list, not GitHub PR notes (46d613a)

## v0.1.0

14 Jun 2026

Initial release of the Medius firmware.
