Injection model
A device is a set of fields, each an Axis (relative motion: X, Y, wheel) or a Usage (a momentary button, key, or media control).
| Device | Axes (MOVE) | Momentary (INJECT) |
|---|---|---|
| mouse | cursor X/Y, wheel | buttons |
| keyboard | none | keys, modifiers |
| media | none | volume, play/pause, ... |
Injected input is added to native input:
native input (real device) --+
+--> one combined report --> game PC
injected input (control PC) --+| Sent | Clone emits |
|---|---|
a MOVE while the mouse moves | The sum of both. |
an INJECT press with nothing held on the real device | The injected press. |
| nothing | Native input only. |
Fire-and-forgetNo per-command acknowledgement
Command frames get no echo or acknowledgement, so input streams at up to ~1 command/ms. QUERY and TRANSFER each return a reply frame. UPDATE replies to every op except DATA. DATA chunks get one acknowledgement per window, plus one reply per refused chunk.
Correctness comes from:
| Mechanism | Effect |
|---|---|
| frame checksum | Drops corrupted frames. |
| safety rules | Keep a dropped command from leaving the box stuck. |
HEALTH | Reads the box's actual state. |
A lost MOVE frame costs one millisecond of motion.
Tracked state Pending motion and held usages
Pending values the box carries between reports, separate from native input.
| State | Holds |
|---|---|
| riding accumulator | Injected motion and scroll not yet emitted. An ordinary MOVE (cursor or wheel) adds to it. Drains into emitted reports. With movement riding on, it waits to ride a native motion report instead. |
| immediate accumulator | The same, for motion that never waits: a MOVE carrying NOW or FLUSH, and unrendered clip motion with ride off. Both accumulators always exist. Movement riding decides whether the riding accumulator drains, not which accumulator a MOVE lands in. |
| usage override | Per usage (button, key, or media): forced active, forced inactive, or left native. Set by INJECT actions: press forces active, force-release forces inactive, soft-release clears both. |
A large injected move emits what fits the native field width. The rest stays in its accumulator and is emitted over as many reports as it takes. Nothing is clipped (total seen = total sent).
Report emission At native report rate, only on activity
| When | Emits |
|---|---|
| the mouse reported | Native motion plus the drained accumulator. Buttons combine native state and overrides. |
| the mouse was still, with motion pending | Only the drained accumulator, paced to native report rate (not every millisecond). With movement riding on, only motion that bypassed riding is emitted this way. |
an INJECT or RESET changed a usage | One report with the new state. |
Otherwise the box emits nothing. A held usage is a single report (the edge), then silence until it changes.
The box emits its report on the first poll no native report can be ready for, so no native report moves to a later poll. If a native report reaches the box first, it carries the box's motion and button changes.
If that native report already carries injected motion, it goes first instead. The box's report then takes a separate poll, so two injections don't share one report.
The separate poll needs the real device to leave polls free (it reports less often than every poll, no other report ID has reported in the last 12 ms, and the report fits one packet). The box's waiting report must also match the native one in everything but motion. If two of the box's reports are already waiting, the native report carries them.
A multi-packet report from the box is emitted once the real device has been still for 12 ms.
Native reports under another report ID on the same endpoint go ahead of the box's report for ≤2 polls. While they fill every poll, the box's report takes the next poll, and they keep 2 polls in 3.
A change waits until the PC collects the report carrying the previous changes. A press and its release never cancel, and changes arrive in the order sent.
Up to 160 key changes wait (80 characters sent at once, 2 changes each; a capital typed with Shift takes 4), and 16 button or media changes. Past that, the oldest change goes ahead of the report it waits for. Order and final state still hold.
While the game PC has the clone suspended, an injection wakes the PC if the PC armed remote wakeup. wakeups counts each wake.
OPTION(EMIT) paces the middle row (mouse still, motion pending). Default: the learnt native report rate. OPTION(RENDER) (on by default) shapes motion through a live per-device model. It can render native motion too.
SafetyInjected state never sticks
A force-release always writes 0: it clears an injected hold and masks a native press.
The box clears all injection (every override and pending move) and returns to passthrough on any of these:
| Trigger | Condition |
|---|---|
| silence timeout | No valid frame within the timeout (default 1000 ms). If a program crashes holding a button, the button releases a second later. |
| link drop | The host-chip link drops. |
| mouse unplugged | The mouse detaches. |
| re-clone | The box clones again: another device attaches, or a patch presentation or an update takes the clone down. |
RESET | An explicit reset command. |
To hold an injected button, keep the link busy. Any valid frame resets the timer, so a periodic QUERY(HEALTH) is enough.
Each release moves the session count, so a polling host knows when to reapply its state.
The medius library automates this: keepalives while something is held, and reconnect plus state re-apply if the link drops.