Transform
TRANSFORM moves one declared field into another. The value is clamped to the destination's declared range, so the clone emits only values the real device could. Unlike the rewrite/raw/patch layer, it needs no imperfect-clone opt-in.
parsed report --> LOCK --> [ TRANSFORM ] --> render --> inject --> emit
|
+-- swap two axes, read both then write both
+-- remap source -> destination, source cleared
|
+-- button -> key / media, held on the
destination's interface| op | Name | Effect |
|---|---|---|
0 | REMAP | move the source field into the destination and clear the source |
1 | SWAP | exchange two axes: read both, then write both |
To weigh a field, or reverse it, use LOCK, whose percent is signed.
A field is a (class, id) in the same space INJECT and LOCK use: 0 button, 1 key, 2 media, 3 axis (id 0=X, 1=Y, 2=wheel, 3=pan).
QUERY(TRANSFORMS) reads the installed table back.
TRANSFORMInstall, overwrite, or remove one entry
An entry is keyed by its (source, dest). Setting an entry whose key exists overwrites its op. Opcode 0x1E.
TRANSFORM 0x1E · payload 8 bytes
Fire-and-forget
| Offset | Field | Type | Notes |
|---|---|---|---|
| 0 | op | u8 | operation (table above) |
| 1 | sclass | u8 | source class: 0 button, 1 key, 2 media, 3 axis |
| 2 | sid | u16 | source id within the class, little-endian |
| 4 | dclass | u8 | destination class |
| 5 | did | u16 | destination id, little-endian |
| 7 | state | u8 | 1 set (add or overwrite), 0 remove the keyed entry |
The weigh runs first, and the transform moves what the weigh kept. The lock banks the rounding remainder once, per axis and sign.
LOCK(X, both, -50) keep half of X, reversed TRANSFORM(swap, X, Y) and put what is left on Y native X = +10 -> weighed -5 -> emitted on Y
| Refused when | Why |
|---|---|
op above 1 | only remap and swap exist |
| a class pair the op doesn't admit | each op names the shapes it can read and write |
| source and destination are the same field | a move needs two; to weigh a field in place, use the lock |
| a field the clone doesn't declare | the box stores no unreachable address; re-send after a re-clone |
| table already at 32 entries | nothing is evicted; the readback's full flag marks the refusal |
An entry applies from the next native report. The box walks the table in install order, so a swap installed after a remap exchanges what the remap wrote.
No reply. A refused entry is absent from RESP(TRANSFORMS), which also carries the full flag. The TRANSFORM_ON health bit follows the table.
Swap X and Y: op = 1, source (axis 3, id 0), dest (axis 3, id 1), state = 1:
The wheel drives vertical motion: op = 0 (remap), source (axis 3, id 2), dest (axis 3, id 1):
Library bindings: transform, transform_swap, and transform_remap.
Field pairs Source and destination per op
Each op admits a fixed set of shapes. Anything else is refused.
swap axis a <---------> axis b two axes
remap axis a ----------> axis b one report, source zeroed
button i ----------> button j one report, source bit cleared
button i ----------> key the keyboard collection
button i ----------> media the consumer collection
every pair is two different fields| Name | Behaviour |
|---|---|
swap | Reads both axes, then writes both. Two remaps would leave the pair equal. |
remap | Adds the source to the destination's value, then zeroes the source. For a button destination, the press is OR'd in instead, and the source bit cleared. |
Button to key or media Remap across collections
A button source can drive a keyboard or Consumer destination. The box emits it through that collection's interface, as for INJECT.
native button ____----------________-----____ key on the clone ____----------________-----____ driven from the button mask the real device just reported, re-read every report, with no edge latch to strand
Because the key follows the level, a configuration switch, missed release or briefly unbound destination resolves on the next report. No key is left held with nothing pressed.
Buttons may span two mouse collections. The destination is held while any collection the box reports on has that button down. A collection not declaring the id holds nothing.
An entry naming an unbound destination collection is refused. If an entry's collection goes away with a configuration switch, the entry turns inert and leaves its source uncleared. The button keeps reaching the game PC until that collection binds again.
Side button 3 drives the a key: op = 0 (remap), source (button 0, id 3), dest (key 1, id 0x04):
The keycode is a HID keyboard usage. A media destination takes a 16-bit Consumer usage.
Pipeline order Locks, rendering, injection
The weigh runs first, and the field pass moves what the weigh left. Everything downstream reads the field where the table put it.
native report
|
+-- 1 LOCK weighs each field on its sign and bearing
|
+-- 2 TRANSFORM moves the weighed fields, in table order
|
+-- 3 render the model is handed the transformed, weighed delta
|
+-- 4 inject drains onto the field a remap just cleared
|
v
emitted report| Stage | Reads | Acts on |
|---|---|---|
LOCK | the native field | a lock applies on the sign the real device reported, not where the value ends up, so a swap never moves a lock with it |
| rendering | the transformed, weighed cursor delta | the model gets the numbers the clone would have emitted, so a scale changes what it renders, not what it corrects |
| injection | nothing the table wrote | injected motion drains into the axis after the pass, so a remap that zeroed that axis doesn't remove it |
CATCH input classes tap the native report before either pass, so they report the native value. EMIT carries what the clone emitted.
LifecyclePC-owned session state
remove a TRANSFORM with state = 0 and the entry's (source, dest) clear state 0 with both classes 0xFF and both ids 0xFFFF, a blanket silence ~1 s with no control-PC frame RESET a RESET command link loss the inter-chip link drops detach the real device goes away re-clone the box binds a device again
Any valid frame resets the silence timer, so a keepalive keeps the table. The library re-asserts the whole table after the box clears it and across a control-link reconnect, as for LOCK.
Every clear here except remove and clear moves the session count.
Cross-class holds release before their entries go, so no key or media usage stays down after its entry. Removing one entry releases only that entry's holds.
RESET, a detach and a link drop take the table through the same release, alongside injection and locks.