Medius - Native APITransform

Transform

Swap or remap a field on the wire

TRANSFORM moves a field the clone's descriptor declares into another one, clamped to the destination's declared range, so the clone still emits only values the real device could. It carries no imperfect-clone opt-in, unlike the rewrite/raw/patch layer.

  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 own interface
opNameEffect
0REMAPmove the source field into the destination and clear the source
1SWAPexchange two axes: read both, then write both

To weigh a field, or reverse it, use LOCK, whose percent is signed. An op above 1 is refused.

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.

TRANSFORM

Install, overwrite, or remove one entry

An entry is keyed by its (source, dest); setting one whose key exists overwrites its op. Opcode 0x1E.

TRANSFORM 0x1E · payload 8 bytes

Fire-and-forget

PAYLOAD
OffsetFieldTypeNotes
0opu8the operation, as the table above
1sclassu8source class: 0 button, 1 key, 2 media, 3 axis
2sidu16source id within the class, little-endian
4dclassu8destination class
5didu16destination id, little-endian
7stateu81 set (add or overwrite), 0 remove the keyed entry
WITH A SCALE

The weigh runs first and the transform moves what it kept. The rounding remainder is banked once, per axis and sign, by the lock.

  LOCK(X, both, -50)      keep half of X, reversed
  TRANSFORM(swap, X, Y)   and put what is left on Y

  physical X = +10  ->  weighed -5  ->  emitted on Y
REFUSALS
Refused whenWhy
op is above 1remap and swap are the whole set
the op does not admit that class paireach op names the shapes it can read and write
the source and the destination are the same fielda move needs two; to weigh a field in place, use the lock
a field this clone does not declarethe box will not store an address it cannot reach; re-send the entry after a re-clone
the table already holds 32 entriesnothing is evicted; the readback's full flag says an entry was turned away
EFFECT

An entry takes effect on the next report the device sends. The box walks the table in the order entries were installed, so a swap installed after a remap exchanges what the remap wrote.

TRANSFORM has no reply, so a refused entry shows up as its absence from RESP(TRANSFORMS). That reply also carries the full flag, and the TRANSFORM_ON health bit follows the table.

EXAMPLE

Swap X and Y: op = 1, source (axis 3, id 0), dest (axis 3, id 1), state = 1:

+--------+--------+--------+--------+--------+--------+
| A5     | 1E     | 00     | 08 00  | 01     | 03     |
+--------+--------+--------+--------+--------+--------+
| SOF    | TYPE   | SEQ    | LEN    | op     | sclass |
+--------+--------+--------+--------+--------+--------+

+--------+--------+--------+--------+--------+
| 00 00  | 03     | 01 00  | 01     | lo hi  |
+--------+--------+--------+--------+--------+
| sid    | dclass | did    | state  | CRC16  |
+--------+--------+--------+--------+--------+

The wheel drives vertical motion: op = 0 (remap), source (axis 3, id 2), dest (axis 3, id 1):

+--------+--------+--------+--------+--------+--------+
| A5     | 1E     | 01     | 08 00  | 00     | 03     |
+--------+--------+--------+--------+--------+--------+
| SOF    | TYPE   | SEQ    | LEN    | op     | sclass |
+--------+--------+--------+--------+--------+--------+

+--------+--------+--------+--------+--------+
| 02 00  | 03     | 01 00  | 01     | lo hi  |
+--------+--------+--------+--------+--------+
| sid    | dclass | did    | state  | CRC16  |
+--------+--------+--------+--------+--------+

Library bindings: transform, transform_swap, and transform_remap.

Field pairs

Which source and destination each op admits

Each op reads and writes 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
NameWhat the box does
swapReads both axes, then writes both. Two remaps would read the second after the first had overwritten it and leave the pair equal.
remapAdds the source onto the destination's own value, then zeroes the source. A button destination is OR'd the press instead, and the source bit is cleared.
WHAT A REMAP LEAVES

An axis remap adds, so a destination that was already moving keeps its own motion and picks up the source's on top. Only the source is zeroed. The sum is clamped to the destination's declared range like any other result.

Button to key or media

The one remap that crosses collections

A button source can drive a keyboard or Consumer destination. The key or media usage is emitted through that collection's own interface, exactly as INJECT emits one.

HELD, NOT LATCHED
  physical button    ____----------________-----____
  key on the clone   ____----------________-----____

  driven from the button mask the device just reported,
  re-read every report, with no edge latch to strand

Because it follows the level, a configuration switch, a missed release, or a destination that was briefly unbound all resolve on the next report instead of leaving a key held with nothing pressed.

EVERY MOUSE COLLECTION

A device may split its buttons across two mouse collections. The destination is held while any collection the box reports on has that button down, and a collection that does not declare the id holds nothing for it.

INERT WHEN UNBOUND

An entry naming a destination collection the box has not bound is refused outright. One whose collection goes away with a configuration switch turns inert: it does nothing and does not clear its source, so the button keeps reaching the game PC until that collection binds again.

EXAMPLE

Side button 3 drives the a key: op = 0 (remap), source (button 0, id 3), dest (key 1, id 0x04):

+--------+--------+--------+--------+--------+--------+
| A5     | 1E     | 02     | 08 00  | 00     | 00     |
+--------+--------+--------+--------+--------+--------+
| SOF    | TYPE   | SEQ    | LEN    | op     | sclass |
+--------+--------+--------+--------+--------+--------+

+--------+--------+--------+--------+--------+
| 03 00  | 01     | 04 00  | 01     | lo hi  |
+--------+--------+--------+--------+--------+
| sid    | dclass | did    | state  | CRC16  |
+--------+--------+--------+--------+--------+

The keycode is a HID keyboard usage; a media destination takes a 16-bit Consumer usage.

Where the pass sits

Against locks, rendering, and injection

The weigh comes first and the field pass moves what it left, so a transform carries a weighed value and everything downstream reads the field where the table put it.

  physical report
       |
       +-- 1  LOCK        weighs each field on its own 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
StageReadsActs on
LOCKthe physical fielda lock bites on the sign the device reported, not on where the value ends up, so a swap never moves a lock with it
renderingthe transformed, weighed cursor deltathe model is fed the same numbers the wire would have carried, so a scale changes what it renders rather than what it corrects
injectionnothing the table wroteinjected motion drains into the axis after the pass; a remap that zeroed that axis does not take it with it
WHAT CATCH SEES

The input classes of CATCH tap the physical report before either pass, so they still report the value the device sent. Its EMIT class is the mirror and carries what the clone actually put on the wire.

Lifecycle

A transform is PC-owned session state
CLEARS ON
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 holds the table open. The host library re-asserts the whole table after a device-side blip and across a control-link reconnect, exactly as it does a LOCK.

WHAT A CLEAR RELEASES

The cross-class holds go before the entries do, so no key or media usage is left down once the entry that drove it is gone. Removing one entry releases only what that entry was holding.

RESET, a detach and a link drop take the table through the same release, alongside injection and locks.