LOCKBlock, pass, or amplify

A momentary usage shares INJECT's (class, id) space, so a button locks like a key. Opcode 0x0A.

LOCK 0x0A · payload 6 bytes

Fire-and-forget

PAYLOAD
OffsetFieldTypeNotes
0classu8input class (table above)
1idu16input within the class, little-endian; 0xFFFF = a blanket
3directionu8sign or edge, 0-4
4scalei16percent of the native value kept, little-endian, -255 to 255
SCALE
NameValueEffect
min-2552.55×, reversed. Anything negative reverses what it keeps.
invert-100All of the native value, reversed: an exact inversion.
block0None of the native value is emitted.
pass100All of the native value, byte for byte.
max2552.55×. Anything above 100 amplifies.
RULES
NameBox action
combineA delta picks up one fixed-sign scale and one relative scale, multiplied. A 0 in either blocks.
carryA native delta at 1 kHz is almost always +/-1, so the dropped fraction is banked per axis and sign: 40 on a run of -1 emits 0 0 -1 0 -1.
saturateA weighed value clamps to the field's declared range, never wraps, and forfeits the fraction it could not carry.
one bitA button, key or media usage locks below 100 and passes at 100. The box stores that, not the number sent.
signA negative reverses what it keeps: the magnitude is weighed and the result flipped, so -100 is an inversion with no rounding. A negative on a momentary usage (one bit, nothing to reverse) is refused.
directionThe slot is picked from the sign of the delta before the weigh, so a directional negative does not loop: -100 on positive sends rightward motion left and leaves leftward motion alone.
orderA scale takes effect before anything sent after it, a clip's auto-lock included. Once a later command's effect is emitted, no native input passes at the old scale.

Weighing runs before injected motion drains. A gain that fills an axis leaves injected motion no room. The injected motion is held, not dropped, and is emitted in one report once room opens.

The threshold is the field's declared maximum divided by the gain. At 255 on an 8-bit axis, the field fills at a native delta of 50 and clamps from 51.

MOVE_RIDE doesn't bound the hold: a moved report re-opens the ride window, and riding never gates the immediate accumulator.

DIRECTION
NameValueAxisButton, key, media
both0Both signs.Press and release.
positive1Positive sign only (+).Press only (0 to 1).
negative2Negative sign only (-).Release only (1 to 0).
with3The sign the box is injecting.Refused on a button or key. Media locks the whole usage, as 0 would.
against4The sign opposing the injected one.Refused on a button or key. Media locks the whole usage, as 0 would.

Media has no sign and no edge, so RESP(LOCKS) always reports its direction as 0. Every shipped client refuses 3 and 4 on all three momentary classes.

On an axis, 0 writes the scale to the two fixed-sign slots and a pass to the relative pair. A both of 50 then means 50%, bearing or not. An unlock clears all four.

BLANKET

An id of 0xFFFF addresses the whole class in one command.

ClassCoversDirectionReads back as
buttonEvery button the real device declares.As a named button.One entry per button and edge, under the button's id.
keyEvery keyboard usage.Honoured: 1 blocks press edges, 2 release edges, 0 both.One entry per blocked edge, id 0xFFFF.
mediaEvery Consumer usage.Ignored.One entry, id 0xFFFF, direction 0.
axisX, Y, the wheel, and pan.As a named axis.One entry per axis and direction, under the axis's id.
BlanketStorage
button, axisExpanded on arrival, so a later command on one member overwrites only that member.
key, mediaOne flag. A per-usage unlock doesn't lift it.
CLEARS ON
unlock      the matching unlock (scale = 100); direction 0 clears
            all four slots of that target
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

A keepalive holds a lock past 1 s of quiet. Injection auto-clears on the same events.

Every clear here except the unlock moves the session count.

EFFECT

Scales are PC-owned and invisible to the game PC. QUERY(LOCKS) reads the active set. The HEALTH LOCK_ON bit is set while any scale differs from a full pass.

Library bindings: scale, lock, unlock, and scale_all. scale_all sends this frame for buttons, keys and media, and per-axis frames for X, Y, the wheel and pan.

EXAMPLE

Block the wheel's negative (scroll-down) sign: class = 3 (axis), id = 2 (wheel), direction = 2, scale = 0:

A5SOF
0ATYPE
00SEQ
06 00LEN
03class
02 00id
02dir
00 00scale
lo hiCRC16

Keep 40% of leftward motion while the bearing on X is positive: direction = 4 (against), scale = 40. A native -10 is then emitted as -4, and as -10 again once the bearing lapses:

A5SOF
0ATYPE
01SEQ
06 00LEN
03class
00 00id
04dir
28 00scale
lo hiCRC16

Invert X on both signs: scale = -100, which is 0xFF9C little-endian.

A5SOF
0ATYPE
02SEQ
06 00LEN
03class
00 00id
00dir
9C FFscale
lo hiCRC16

BearingCurrent injected direction

with and against weigh a native delta by its sign relative to the bearing. The box reads the bearing at the merge point, where pending injection and the arriving report are both present.

  MOVE(+10)        MOVE(+10)                          idle
      |                |
      v                v
  ----+----------------+---------------------------------------> t
      |<-- restarted ->|<------ window ------->|
       bearing +X       bearing +X               no bearing
LIFETIME

Each axis has a separate bearing and deadline, set by OPTION(BEARING). A window of 0 holds no bearing, so with and against stop weighing without being cleared.

EventBearing
An injected delta on that axis, from a MOVE on either accumulator or from a clipSet, and the deadline restarted. A zero component leaves that axis unchanged.
Injected motion still owed on that axis, held for a ride or queued behind a slow emit gateKeeps restarting the deadline, and points at the net pending delta rather than the last one sent.
The window elapsesThat axis has no bearing. Both relative directions stop applying, and the axis passes at its fixed-sign scale alone.
MOVE with DISCARDCleared, then set again by the command's delta. Only a zero-delta discard leaves the axis without a bearing.
Motion held for a ride goes staleCleared with the held motion, on the next native motion report that would have carried it.
A change to OPTION(MOVE_RIDE)Cleared, along with the held motion.
A change to OPTION(BEARING)Cleared, and the banked carry with it.
RESET, ~1 s of control-PC silence, link loss, detachCleared with the rest of the PC-owned state.

GeometryPer axis, or projected onto the bearing

Set by OPTION(BEARING). The two modes agree while only one bearing component is nonzero, and differ once both are.

ModeValueWeighs
per axis0Each axis against its bearing, independently.
vector1Only the motion along the injected direction.
the bearing is down-right at 45 degrees, the real device moves straight right

        o----------->  h   (+12, 0)
         \         /   across b  (+6, -6)  untouched by with / against
          \       /
           \     /
            \   /
             \ /
              +        along b   (+6, +6)  weighed by with / against

In vector mode, the relative pair addresses the XY bearing as a whole. The box applies the lower of the X and Y scales to both.

RESP(LOCKS) reports that effective scale on both axes. Replaying a readback as commands lowers the higher stored byte to match.

The wheel and pan are never projected. Each weighs against its bearing.

TWO STAGES
StageReadsActs on
1. projectThe relative pair, one number for both axes.The part along the bearing. What survives is written back to both axes.
2. weighEach axis's fixed pair, chosen by the sign now in the field.What stage 1 left, not the delta the report carried.

A fixed-sign scale governs what is emitted, so it covers the across part as well as the native delta. Only the relative pair is redistributed. The fixed pair is per axis in both modes.

EXAMPLE
  bearing +X +Y
  LOCK(axis X, with, 0)  LOCK(axis Y, with, 0)   the bearing's relative scale is 0
  LOCK(axis Y, negative, 0)                      Y's negative sign is blocked

                          X     Y
  native                +12     0   straight right, nothing on Y
  stage 1  along b       +6    +6   scaled by 0, so it goes
           across b      +6    -6   left alone, and is all that remains
           leaves        +6    -6
  stage 2  scale        100     0   on the sign now in each field
  emitted                +6     0

In per-axis mode, stage 2 would leave Y alone, since the report carried nothing there. With a scale of 200 instead of the block, the same -6 is emitted as -12.