USBJoystick - API reference
=============================

This module lets a modern USB HID joystick/gamepad stand in for four
different RISC OS joystick interfaces, and also provides some new SWIs
(section 5) for new software - to enumerate devices, inspect their
axes/buttons, and control mapping directly, without touching any of the
classic APIs at all. This note documents all of them: the classic APIs
being emulated, the native SWI API, and the `*USBJoystick_*` command set
(a debugging/interactive convenience layer sharing its implementation
with the native API, not a separately-committed API of its own).

Three of the four classic APIs - Serial Port, Joy, and ADC - can each be
turned on/off independently at run time via `Joystick_Emulate` in
section 5 (or the equivalent `*USBJoystick_Emulate` command in section
6). The Acorn `Joystick_Read` API cannot be turned off, and never
answers "SWI not known" - see section 1 for why. A physical joystick
only answers a given classic API once it has been mapped onto it (via the
binding table and `Joystick_SetStick`, section 5) - `auto_map()` does this
automatically with sensible defaults as soon as a device is recognised, so in
practice most joysticks "just work" without anything needing to run any mapping
commands by hand.


1. Native Acorn `Joystick_Read` API
-------------------------------------
SWI chunk: `Joystick_Read` = &043F40, `Joystick_CalibrateTopRight` =
&043F41, `Joystick_CalibrateBottomLeft` = &043F42 (both accepted as
no-ops - there's nothing to calibrate against an already-scaled digital
axis range).

`Joystick_Read` packs a "reason" and a legacy stick number into R0 on
entry:
    R0 bits 0-7   = stick number (set by *USBJoystick_SetStick)
    R0 bits 8-15  = reason code (0 or 1, see below)

  Reason 0 - read 8-bit position/buttons for one stick:
    Out  R0 bits 0-7   = X position, -127..127 (0 if no stick mapped)
         R0 bits 8-15  = Y position, -127..127
         R0 bits 16+   = one bit per button

  Reason 1 - read 16-bit position/buttons for one stick:
    Out  R0 bits 0-15  = Y position, 0..65535 (32768 = centred/no stick)
         R0 bits 16-31 = X position, 0..65535
         R1             = one bit per button

These two reasons are the existing Acorn API, and they are not "legacy"
in the sense of being superseded - to date, they're the only interface
an actual RISC OS game can use to read a joystick, and that isn't
changing. Unlike the other three classic APIs, this one cannot be
turned off via `Joystick_Emulate`/`*USBJoystick_Emulate*` - it's this
module's own registered SWI chunk (`&43F40`), not a shared vector grab
that some other real hardware or module might need this module to get
out of the way for, so there's no legitimate reason for it to ever
answer "SWI not known".

The newer SWIs described in section 5 are a complementary, lower-level
tier (full axis/button access, real device info) for software that
wants more than two pre-scaled sticks - e.g. a fancy in-game
controller-setup screen. They do not have to replace the old
Joystick_Read for simple use cases.

8-bit and 16-bit values are produced by linearly rescaling the joystick's
native HID axis range into the Acorn range (pre-computed slope, not a
per-read float calculation), then clamped. A binding's Invert flag reverses an
axis's sense (see the command reference). A HAT switch or D-pad, if present, is
exposed as two extra synthetic
digital axes (values -1/0/1) alongside any real analogue axes, and can be
mapped onto the 8/16-bit APIs exactly like a real axis.


2. "Serial Port" joystick emulation (`Joystick_Status`)
----------------------------------------------------------
Reachable two ways - as a proper module SWI (`Joystick_Status` = &043F43),
or via raw SWI number &81540 for older software that calls it directly
rather than by name (both do exactly the same thing).

    Out  R0 bits 0-4   = stick 1 status (legacy stick number 0)
         R0 bits 8-12  = stick 2 status (legacy stick number 1)

Each 5-bit status field, bit meaning:
    bit 0 = Right
    bit 1 = Left
    bit 2 = Down
    bit 3 = Up
    bit 4 = any fire button pressed

This is a digital (on/off) view of the mapped X/Y axis: each axis is
compared against a dead-zone (an eighth of its range either side of
centre) to decide whether it currently counts as "pushed" in that
direction. A binding's Invert flag swaps left/right or up/down as
appropriate. If no stick is mapped to a given legacy number, that
status field reads as 0 (centred, no buttons).


3. Teque/Krisalis "Joy" module API
-------------------------------------
Reachable only via raw SWI numbers (there is no named SWI for these):
    &CFFC0  Joy_Read0      - read legacy stick 0
    &CFFC1  Joy_Read1      - read legacy stick 1
    &CFFC2  Joy_Test       - is an RTFM-style interface present?
    &CFFC3  Joy_ReadPipe   - not implemented (returns without error, does
                             nothing - unknown original behaviour)

`Joy_Read0`/`Joy_Read1`:
    Out  R0 = %000FUDLR (same 5-bit encoding as the Serial Port API,
              bit0=Right, bit1=Left, bit2=Down, bit3=Up, bit4=fire),
              or &1F ("all bits set") if that stick number isn't mapped

`Joy_Test`:
    Out  R0 = 255 if neither stick 0 nor stick 1 is mapped (reported as
                  "nothing present")
         R0 = 0   if either is mapped (reported as "RTFM present" - this
                  module never reports 128/"Econet present", since it
                  isn't emulating Econet hardware)

Differs from the Serial Port API only in packing (one stick per call
here, both packed into one word there) and in how "nothing mapped" is
signalled (&1F here, 0 there).


4. I/O podule ADC emulation
-------------------------------
Intercepts the classic `OSBYTE` calls BASIC's `ADVAL()` function (and any
direct `OS_Byte` caller) uses to talk to an I/O podule's analogue-to-
digital converter:

    OSBYTE 16  (channel count)     - remembers how many channels to sample
    OSBYTE 17  (convert channel)   - remembers which channel is "current"
                                     (channel must be 1-4)
    OSBYTE 128 sub-op 0  = ADVAL(0)  - fire-button status:
                              R1 bits 0-1 = mapped fire buttons
                              R2 = 0
    OSBYTE 128 sub-op 1-4 = ADVAL(1..4) - read one ADC channel:
                              R1 = low 8 bits of the value
                              R2 = remaining high bits
                              value range 0-65520 (or snapped to a HAT's
                              min/mid/max if that channel maps to a
                              HAT-derived axis)
    OSBYTE 188 - read back the "current channel" set by OSBYTE 17
    OSBYTE 189 - read back the "channel count" set by OSBYTE 16
    OSBYTE 190 - conversion resolution, hardcoded to 12 bits

Pointer-position ADVAL calls (`ADVAL(-1)`/`ADVAL(-2)`) are NOT
implemented and pass straight through to the real OS.

A HID axis or button only becomes readable through this API once a binding
targets an ADC channel (`adc:<n>`) or fire button (`adcbtn:<n>`) - unlike the
other three classic APIs, ADC channels are not auto-mapped, since there's no
way to guess which of up to 4 podule channels an application expects a given
axis on.


5. Native slot-based API (`Joystick_Enumerate` etc.)
----------------------------------------------------------
This is the module's own, new API - the proposed way to enumerate
connected joysticks, inspect their axes/buttons, and control mapping,
without needing any of the classic APIs above turned on.

Every SWI here addresses a device by its raw **slot** (0..JOY_MAX-1,
the order USB attached it in - see h.usbjoystick for the current
JOY_MAX) - never confuse this with an **index**, which means an axis or
button's position within one device's own arrays. A device's axis/button
index assignment is a deterministic function of its HID report
descriptor's own byte content (confirmed by reading the parser) - stable
across replug and reboot for a given model/firmware, though a device
with a hardware mode switch that changes its report descriptor (some
old analogue pads had exactly this) could show a different index
assignment per mode despite an identical vendor/product id.

Every SWI below has one fixed response shape - the one exception,
`Joystick_Emulate`, uses a selector register to choose *which* classic
API to toggle, but every value of that selector gets exactly the same
request/response shape back, which is a genuinely different (and fine)
use of a selector register from dispatching on it to change what shape
comes back.

  `Joystick_Enumerate`
    Out  R0 = bitmask of in-use slots (bit N set = slot N is in use)
         R1 = generation counter, bumped on every attach/detach - lets a
              poller (a Configure plug-in, an SDL backend) cheaply notice
              the device list changed without re-enumerating every call

  `Joystick_DeviceInfo <slot>`
    Out  R0 = 0, or an error if the slot isn't a currently open joystick
         R1 = size of the device-info record (forward compatibility)
         R2 = pointer to one record: manufacturer, product, serial
              ("" if the device has no real iSerialNumber), vendor_id,
              product_id, axis/button/hat counts, a capabilities bitfield
              (bit 0 = rumble, bit 1 = LED, bit 2 = battery, bit 3 =
              touchpad, bit 4 = sensors - all reserved/0 until future work
              populates them), and a 16-byte SDL-inspired GUID
              (bus type + vendor/product/version, not byte-identical to
              SDL's own encoding - there's no need for an exact 1:1 fit)

  `Joystick_AxisInfo <slot>`
    Out  R0 = 0, or an error
         R1 = size of one axis-info record
         R2 = number of axes
         R3 = pointer to an array of records: raw HID usage page/usage
              (transport-agnostic HID vocabulary - not a USB-specific
              concept, and reused verbatim by e.g. Bluetooth HID, so this
              stays meaningful if a non-USB transport is ever added),
              this module's own semantic type (handles multiple axes
              sharing one HID usage, e.g. dual analogue sticks both being
              "X"), name, min/mid/max, and whether it's a synthesised
              HAT/D-pad digital axis

  `Joystick_AxisValues <slot>`
    Out  R0 = 0, or an error
         R1 = size of one value (4 bytes)
         R2 = number of axes
         R3 = pointer to an array of current int32_t values, one per axis
         R4 = full button bitmask

  `Joystick_GetMapping <slot>`
    Out  R0 = 0, or an error
         R1 = size of the mapping record
         R2 = pointer to a full snapshot, in resolved form, of everything this
              device is currently mapped to: legacy stick number, 8/16-bit axis
              mapping, button mapping, ADC channel/button mapping, mouse
              mapping, key mapping. This is the read-only "get everything"
              companion to the editable binding table (`Joystick_ReadBindings`),
              which returns the same information as individual source->target
              records.

The whole mapping - which classic-read axes/buttons, mouse movement/buttons,
keys and ADC channels each physical axis/button drives - is one flat **binding
table** per slot. A binding is a fixed-size "this source drives that target"
record (source = an axis or button; target = a stick 8/16-bit axis, a stick
button, mouse move X/Y, a mouse button, a key, or an ADC channel/fire button),
with an invert flag. Analogue targets (stick axis, mouse move, ADC channel)
take one source each; digital targets (stick button, mouse button, key, ADC
fire) may be driven by several. A button, being an on/off source, may only
drive a digital target - a button->analogue binding is rejected, since the
engine records the driving source as an axis index. See h.binding for the
record layout.

  `Joystick_SetStick <slot> R1=<stick number>`
      Assign a device to a legacy Joystick_Read stick number (0..JOY_MAX-1),
      used by all three classic read APIs. A device attribute, not a binding.

  `Joystick_ReadBindings <slot>`
    Out  R0 = number of bindings
         R1 = size of one binding record (bytes)
         R2 = pointer to the slot's live binding table
      Points at the module's own table; valid until the next mapping change.

  `Joystick_WriteBindings <slot> R1=<pointer to records> R2=<count>`
      Validate the whole table, then replace the slot's mapping with it -
      all-or-nothing, so a bad record leaves the current mapping untouched.

  `Joystick_AddBinding <slot> R1=<pointer to one binding record>`
      Validate one binding against the current table (range checks and the
      singular-target rule) and append it.

  `Joystick_ReadDefaultBindings <slot>`
    Out  R0 = number of bindings
         R1 = size of one binding record (bytes)
         R2 = pointer to a computed (not stored) binding table
         R3 = legacy stick number auto-mapping would assign right now
      Read-only preview of what `Joystick_RevertToDefaultMap` would produce -
      unlike it, this never touches live state, the config store, or any
      other slot, so a caller can use it to reset a not-yet-applied draft
      mapping and still let the user Cancel out of it. The returned pointer
      is valid until the next `Joystick_ReadDefaultBindings` call.

  `Joystick_Emulate R0=<which> R1=<-1 query, 0 off, 1 on>`
      `which`: 0=SerialPort, 1=Joy, 2=ADC. No Acorn value - see section 1
      for why `Joystick_Read` can't be turned off.
    Out  R0 = resulting current state (0 or 1) for that API
      One SWI covering these three - see h.joyswis for the
      `JOYSTICK_EMULATE_*` selector values. `*USBJoystick_Emulate` below
      mirrors this same selector as its first argument rather than having
      three separate command names.

  `Joystick_Control R0=<which> R1=<-1 query, 0 off, 1 on>`
      `which`: 0=Mouse, 1=Keyboard (`JOYSTICK_CONTROL_*`, h.joyswis - a
      separate enum from `JOYSTICK_EMULATE_*` even though the numbers
      overlap, since the two SWIs have independent R0 namespaces).
    Out  R0 = resulting current state (0 or 1)
      Global output gates, independent of the binding table and OFF by
      default: a device can have mouse/key bindings configured (ready to go)
      without them driving anything until the matching gate here is on.
      Deliberately a separate SWI from `Joystick_Emulate` - Emulate is about
      impersonating a classic pre-USB joystick API for old software; this is
      about the module's own native mouse/keyboard output, which isn't
      emulating any RISC OS API, just directly driving the pointer/keyboard
      the way a real mouse or keyboard does. Turning a gate off cleanly
      releases anything it was currently holding (see c.mouse, c.keys)
      rather than leaving it stuck.

  `Joystick_SaveSettings`
      Persist the module-wide settings (the Emulate + Control gates above)
      to the config store's `[global]` section - see doc.Config. Touches no
      device's own section, so it can never clash with `Joystick_SaveMapping`.


6. `*USBJoystick_*` command reference
------------------------------------------
The module's own commands, mainly for interactive inspection and editing.
Every command that changes state shares its implementation with the equivalent
SWI in section 5.

Inspection:

  *USBJoystick_List [<slot>]
      List all connected joysticks, or details for just one.

  *USBJoystick_Read <slot>
      Show one joystick's current live axis values, buttons, and flip state.

  *USBJoystick_Mappings <slot>
      List a slot's binding table - each "source -> target" pair, marked
      (inverted) where set.

  *USBJoystick_Debug [<slot>]
      Detailed internal diagnostics: USB path, buffer/file handles, raw report
      bytes, upcall and good/bad-read counters (reading resets the "delta"
      counters), per-axis scaling detail.

  *USBJoystick_Fingerprint <slot>
      Print the config-store fingerprint key for one slot.

Mapping:

  *USBJoystick_SetStick <slot> <stick number>
      Assign a device to legacy stick number 0..JOY_MAX-1, used by all three
      classic read APIs. A device attribute, not a binding.

  *USBJoystick_Map <slot> <source> <target> [Invert]
      Add one binding (a physical input driving an output).
        source: axis:<name|n> | btn:<n>
        target: stick:{8bitX,8bitY,16bitX,16bitY} | stickbtn:<n> |
                mouse:{X,Y,S,M,A} | key:<keyno> | adc:<n> | adcbtn:<n>
      "stick" is the classic Joystick_Read output (the "Stick N" screen);
      mouse buttons S/M/A are Select/Menu/Adjust; keycodes are numeric RISC OS
      internal key numbers. Invert reverses direction. A device drives the
      mouse once it has mouse-move bindings; ADC channels/fire buttons feed
      the ADC API.

  *USBJoystick_Unmap <slot> [<source> [<target>]]
      No source: clear the whole device. Source only: clear all of that
      source's bindings. Source and target: clear just that one binding.

Config store:

  *USBJoystick_SaveMappings <slot>
      Save the slot's binding table to the config store, so it re-applies when
      the device next attaches.

  *USBJoystick_RevertToDefaultMap <slot>
      Discard the slot's saved mapping and return it to the default,
      automatically-generated mapping.

  *USBJoystick_ReloadConfig
      Re-read the config store and apply saved mappings to all attached devices.

  *USBJoystick_SaveSettings
      Save the module-wide settings (Emulate + Control below) to the config
      store, independent of any device's own saved mapping - see
      Joystick_SaveSettings in section 5.

Emulation toggles:

  *USBJoystick_Emulate <SerialPort|Joy|ADC> [<on|off>]
      Turn emulation of one classic API on/off globally (an unrecognised
      SWI/OSBYTE passes through as if this module weren't present when its
      emulation is off) - mirrors `Joystick_Emulate`'s R0 selector 1:1. With
      no on/off argument, reports that API's current state. No equivalent for
      the Acorn API - `Joystick_Read` is always active, see section 1.

Output control toggles:

  *USBJoystick_Control <Mouse|Keyboard> [<on|off>]
      Turn a joystick's native mouse or keyboard output on/off globally,
      independent of any device's bindings - mirrors `Joystick_Control`'s R0
      selector 1:1. Off by default. With no on/off argument, reports the
      current state. Not the same thing as Emulate above: this doesn't
      impersonate a classic API, it gates the module's own direct
      pointer/keyboard output.
