USBJoystick - settings load/save (the config store)
=======================================================

This documents how USBJoystick persists settings across sessions: what is
saved, where, in what format, how it is keyed to a physical device, and
how it is (re)loaded - including the awkward case of the module running
from ROM before the desktop and its Choices$ paths exist.



What gets saved
------------------
Two kinds of setting:

  - Per-device mappings: the device's binding table (every source->target
    binding) plus its legacy stick number. Keyed by the device fingerprint
    (below) so it re-applies to that same physical device whenever it is
    plugged in again.

  - Global module settings: things that are not per-device - currently
    the three emulation toggles (SerialPort/Joy/ADC). Future non-mapping
    settings (ADC sampling curves, force-feedback defaults, etc) are
    expected to land here too, or as extra per-device keys, without a
    format change - see "Forward compatibility" below.

Deliberately NOT versioned for cross-release migration: if the file's
format marker does not match what this build understands, the whole file
is ignored (module reverts to live auto-mapping) and the next save
overwrites it. There are few enough settings that silently rebuilding
them is acceptable, and it keeps the parser simple.


Device fingerprint (the per-device key)
------------------------------------------
Produced by config_fingerprint() (c.config). Two tiers, matching what SDL
and Windows both settle for:

  Tier 1 - device has a genuine non-empty USB iSerialNumber:
      vvvv:pppp:serial=<serial>
  Tier 2 - no serial string:
      vvvv:pppp

vvvv / pppp are the lower-case 4-hex-digit USB vendor and product ids.
The serial is taken verbatim from joy_data[slot].serial (populated in
c.joydev set_up_joystick only when iSerialNumber > 0, so serial[0]=='\0'
is one of the Tier-1/Tier-2 discriminators), except that any character
that would break the [section] line of the config file - anything <= ' ',
or one of []; = - is replaced with '_'. This is a one-way sanitisation
for use as a stable key; it is not meant to reproduce the original serial.

Untrustworthy serials (JOY_QUIRK_NO_SERIAL): some devices report a serial
that is NOT a stable per-unit identifier. Cheap Xbox360 clones (which all
borrow Microsoft's VID:PID 045e:028e) synthesise a fresh value on almost
every enumeration - confirmed on real hardware, and visible through RISC
OS's own *USBDevInfo too, so it is the device lying, not a decode bug -
and even genuine Xbox360 pads have no useful serial (SDL keys the whole
family by VID:PID alone for this reason). Trusting such a serial would be
worse than useless: the fingerprint would change every replug and a saved
mapping would never re-match. So the device table (c.device) carries a
JOY_QUIRK_NO_SERIAL bit per driver, set on both Xbox360 entries, and
config_fingerprint() forces Tier 2 (drops the serial) whenever the matched
driver has it. Two such pads then share one VID:PID config entry - the same
ceiling SDL/Windows accept for serial-less devices, not a regression.

Explicitly rejected as a fingerprint component: the internal
usb_path/device_name (e.g. "...:USB5"). That is a sequential DeviceFS
registration counter, not stable across replug (unplug/replug typically
turns USB5 into USB6), so no part of it may be used.


File location and access
---------------------------
  Read:   Choices:USBJoystick.Devices
          The Choices: path variable already searches Choices$Path, so
          this reads the user's writable copy if present and falls back
          through the path otherwise. A missing file is not an error -
          the module simply keeps today's live auto-mapping behaviour.

  Write:  <Choices$Write>.USBJoystick.Devices
          The USBJoystick directory is created first (OS_File 8) since
          Choices$Write's target may not contain it yet.

If Choices$Path / Choices$Write are not set at all (module running from
ROM before !Boot has set them up), read and write both no-op gracefully:
nothing to load, nowhere to save yet. See "Boot-time loading" below for
how the saved config still gets applied in that case.


File format
--------------
A plain, hand-editable key=value text file, one section per fingerprint
plus one [global] section. Example:

    # USBJoystick config - hand-editable   Format:2
    [global]
    emulate_serialport=yes
    emulate_joy=yes
    emulate_adc=no
    mouse_control=no
    keyboard_control=no

    [045e:028e]
    profile=Default
    stick=0
    bind=axis:X,stick:16bitX
    bind=axis:Y,stick:16bitY,invert
    bind=btn:0,stickbtn:0
    bind=axis:X,mouse:X
    bind=btn:0,mouse:S
    bind=btn:3,key:57
    bind=axis:DPADX,adc:0

Rules:
  - '#' or '|' at line start, and blank lines, are comments/ignored.
  - '; ...' trailing text is a comment.
  - stick=<n> sets the device's legacy Joystick_Read stick number. Omitted
    when the device has no stick number.
  - bind=<source>,<target>[,invert] is one binding, in the *USBJoystick_Map
    grammar: source is axis:<name|n> or btn:<n>; target is one of
    stick:{8bitX,8bitY,16bitX,16bitY}, stickbtn:<n>, mouse:{X,Y,S,M,A},
    key:<keyno>, adc:<n> or adcbtn:<n>. One line per binding; together the
    bind= lines are the device's whole mapping. A section with no bind= lines
    (and no stick=) leaves the device on its live auto-mapping.
  - The [global] section holds module-wide (not per-device) settings:
    emulate_serialport / emulate_joy / emulate_adc (yes/no, Joystick_Emulate -
    impersonates a classic pre-USB joystick API), and mouse_control /
    keyboard_control (yes/no, default no; Joystick_Control - the module's own
    native mouse/keyboard output, gated independently of any device's
    bindings; see "output control toggles" in doc.APIs section 6 for why
    these are a separate concept from Emulate). Regenerated from the live
    flags on every save (either a per-device save/revert, or the
    global-only Joystick_SaveSettings), and applied by Joystick_ReloadConfig
    (see below) - NOT at per-device attach, since none of them are per-device.
  - profile=<name> is written and ignored on read for now. It reserves the
    file shape for a future "same device, different mapping per game" feature.
  - Unknown keys are ignored, not errors.


Forward compatibility
-------------------------
Because the format is open-ended key=value with unknown-key-ignore, new
settings (per-device ADC curves, force-feedback strength, per-profile
mappings, new global toggles) are added simply as new keys/sections.
Older builds ignore keys they do not recognise; the Format: marker is
only bumped for a genuinely incompatible change, at which point the
ignore-and-overwrite policy above applies.


Module-side integration
---------------------------
The module owns the file end to end - reads, parses, writes, applies it.
JoySetup (and any other client) never touches the file; it only calls the
SWIs below. This split is forced by the ROM/pre-desktop case: the module
is live before JoySetup (a desktop app) could possibly run, so it must be
self-sufficient.

  - auto_map() (c.map) consults the store by fingerprint at the end of every
    device attach, via config_apply(). It computes the default mapping and
    captures it into the binding table first, then - if a saved section of the
    current format exists - REPLACES the table with the saved bindings (and
    stick number). This makes "reload after replug", "reload after reboot" and
    "reload after ROM boot" one code path. [global] settings are applied
    separately (see Joystick_ReloadConfig, and the [global] note in "File
    format").

  - Joystick_SaveMapping <slot> [R1=profile, 0=default]: computes the slot's
    fingerprint and writes its current binding table (and stick number) under
    that fingerprint, persisting the file.

  - Joystick_SaveSettings: persists just the live Emulate + Control flags into
    [global], touching no device's section - the save path for those two
    gates, kept deliberately separate from Joystick_SaveMapping so a global
    settings save and a per-device mapping save can never clash over the same
    part of the file.

  - Joystick_RevertToDefaultMap <slot>: removes the saved section for that
    fingerprint, then calls auto_map() again live so the change shows
    immediately, not just on next attach.

  - Joystick_ReloadConfig: re-reads the whole store and re-applies it to every
    currently-attached slot. Idempotent and safe to call repeatedly. This is
    the hook the boot sequence uses (below), and what JoySetup calls to refresh
    after external edits. Store-wide (all slots, and the [global] settings),
    not the inverse of a per-device save - that inverse is
    Joystick_RevertToDefaultMap.

Each of these SWIs has a `*USBJoystick_*` command twin for interactive use.


Boot-time loading (ROM-baked / pre-desktop)
----------------------------------------------
Problem: if USBJoystick is baked into a ROM image it initialises during
ROM module init, long before !Boot sets Choices$Path/Choices$Write, and a
joystick present at power-on could attach (auto_map) before any Choices
path exists.

Solution - do not read Choices at module-init time. Load lazily:
  1. auto_map() reads the store at each attach. If Choices is not yet set
     (pre-!Boot), it finds nothing and falls back to live auto-mapping.
  2. A single re-runnable Joystick_ReloadConfig / *USBJoystick_ReloadConfig
     re-reads the store and re-applies it to all attached slots.
  3. The boot sequence invokes that command once Choices$ paths exist.

So a one-line PreDesk obey fragment (the same mechanism ThemeSetup and
SetUpNet already use) is probably the way to action this.


Source layout
----------------
  h.config, c.config   The config subsystem: config_fingerprint() (device
                       key), the file reader/parser and writer, and the
                       auto_map() lookup entry point. Hand-rolled parser,
                       no external library - same level of complexity as
                       the existing *-command argument parsing.
