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

PAYLOAD
OffsetFieldTypeNotes
0opu8operation (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
SCALED

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
REFUSALS
Refused whenWhy
op above 1only remap and swap exist
a class pair the op doesn't admiteach op names the shapes it can read and write
source and destination are the same fielda move needs two; to weigh a field in place, use the lock
a field the clone doesn't declarethe box stores no unreachable address; re-send after a re-clone
table already at 32 entriesnothing is evicted; the readback's full flag marks the refusal
EFFECT

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.

EXAMPLE

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

A5SOF
1ETYPE
00SEQ
08 00LEN
01op
03sclass
00 00sid
03dclass
01 00did
01state
lo hiCRC16

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

A5SOF
1ETYPE
01SEQ
08 00LEN
00op
03sclass
02 00sid
03dclass
01 00did
01state
lo hiCRC16

Library bindings: transform, transform_swap, and transform_remap.

Field pairsSource 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
NameBehaviour
swapReads both axes, then writes both. Two remaps would leave the pair equal.
remapAdds 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 mediaRemap across collections

A button source can drive a keyboard or Consumer destination. The box emits it through that collection's interface, as for INJECT.

HELD, NOT LATCHED
  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.

EVERY MOUSE COLLECTION

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.

INERT WHEN UNBOUND

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.

EXAMPLE

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

A5SOF
1ETYPE
02SEQ
08 00LEN
00op
00sclass
03 00sid
01dclass
04 00did
01state
lo hiCRC16

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

Pipeline orderLocks, 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
StageReadsActs on
LOCKthe native fielda lock applies on the sign the real device reported, not where the value ends up, so a swap never moves a lock with it
renderingthe transformed, weighed cursor deltathe model gets the numbers the clone would have emitted, so a scale changes what it renders, not what it corrects
injectionnothing the table wroteinjected motion drains into the axis after the pass, so a remap that zeroed that axis doesn't remove it
CATCH

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

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 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.

RELEASE

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.