Files
riojoy/docs/INPUT-INTEGRATION.md
T
CydandClaude Fable 5 9a792193f9 docs: shipping-a-profile section in the input integration guide
Documents the install-time handoff contract that previously lived only in
Program.cs comments: single-profile document + --import-profile, exit codes
(0/1/2/3), the tray-must-not-be-running rule and installer sequencing,
per-user/per-session scope, idempotent replace-by-name, and the game-repo
reference-copy convention (dxx-rebirth pattern).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 21:09:02 -05:00

10 KiB
Raw Blame History

Input integration guide (cockpit → RIOJoy → game)

How a game receives the cockpit's inputs — the 72 lighted buttons, two 16-key keypads, and 5 analog axes — and how to build the profile that maps them. This is the mirror of OUTPUT-INTEGRATION.md (game → cockpit); the wire protocol lives in PROTOCOL.md, the profile/auto-switch model in PLAN.md.

What a game sees

RIOJoy translates cockpit events into ordinary Windows input, per profile, through three surfaces (all can be active at once — each button picks its route):

Surface What the game sees When
Virtual Xbox 360 pad (ViGEm) a normal XInput controller: 11 buttons, D-pad, 2 sticks, 2 triggers default on Windows 10/11 when ViGEmBus is installed
Keyboard / mouse (SendInput) scancode keystrokes with modifiers; relative mouse moves + clicks any button routed to a key/mouse action
RioGamepad HID a native 6-axis, 96-button, 1-hat joystick fallback when ViGEm is absent; the XP flavor

The sink is chosen at activation: ViGEm → RioGamepad feeder → none (keyboard and mouse always work). Most games — XInput and DirectInput alike — see the Xbox 360 pad as a standard controller; the practical limit is its 11 mappable buttons, so keyboard routing carries everything beyond that.

The input inventory

  • 72 lighted buttons, RIO addresses 0x000x47, grouped into five MFD clusters and four columns — the physical map is in OUTPUT-INTEGRATION.md.
  • Two 4×4 keypads: internal 0x500x5F, external 0x600x6F (key label → address = base + hex digit; no lamps).
  • 5 analog inputs — joystick X/Y, throttle, left pedal, right pedal — calibrated into 6 virtual axes (X, Y, Z, Rx, Ry, Rz), each 0..32766 with center 16383.

Per-button routing

Every mapped address carries one action (the iRIO word, PROTOCOL.md §5). The editor exposes these as the Action kinds:

Kind What happens on press/release
Keyboard key down/up by scancode (so DOS-era and raw-input games see it), with optional Shift/Ctrl/Alt held around it and the extended-key flag for nav keys
Joystick virtual pad button 111 (or 196 on the RioGamepad HID)
Hat the POV hat / D-pad direction (up/right/down/left; release = centered)
Mouse relative move in clean 50-px steps (up/right/down/left) or left/right click — the legacy build's mixed-up move deltas are fixed in the port
RIO command internal: axis recalibrations/resets, version/check request, diagnostic toggles — useful on a spare cockpit button so recalibration never needs the desktop
Lit flag lamp follows the button (dim idle, bright pressed). Also marks the lamp as profile-owned, which shields it from the feedback endpoint (see OUTPUT-INTEGRATION.md)

The Xbox 360 button map

RIO joystick buttons are assigned in this fixed order — pick low numbers for the game's most important actions:

RIO joy button 1 2 3 4 5 6 7 8 9 10 11
Pad button A B X Y LB RB Back Start L3 R3 Guide

The hat maps to the D-pad. Buttons past 11 are dropped by the pad — route those to the keyboard instead. (On the RioGamepad HID all 96 buttons exist natively and no mapping table applies.)

Axes

Calibration (per profile)

Calibration holds per-axis invert flags and EnableZR:

  • Joystick X/Y auto-range from observed travel with a small deadzone.
  • Throttle (Z) is the ratcheted lever; calibrated so the detent rest position reads 0, full forward 32766 — i.e. it is naturally unipolar.
  • Pedals feed Rx/Ry directly, or — with EnableZR — mix into a single rudder axis: Rz = 16383 left/2 + right/2 (Rx/Ry then idle).

Routing onto the pad (AxisRouting, JSON-only)

Each of the six axes picks a Target and Mode (src/RioJoy.Core/Output/AxisRoutingConfig.cs; null section = the legacy default routing):

Axis Default target Conversion
X LeftThumbX Centered
Y LeftThumbY Centered
Z LeftTrigger trigger byte value×255/32766
Rx RightThumbX Centered
Ry RightThumbY Centered
Rz RightTrigger trigger byte

Modes for thumb targets: Centered ((value16383)×2 → stick range) for axes that rest mid-travel, UnipolarPositive (rest 0 = stick center, 32766 = stick max — only the upper half is used) for the ratcheted throttle. Target: "None" suppresses an axis entirely.

The triggers-are-buttons trap: many games hard-bind the pad triggers to fire/actions. If the throttle rides LeftTrigger (the default), advancing the throttle fires. The shipped Descent profile (profiles/descent-d1x.json) is the worked example: throttle → RightThumbY UnipolarPositive, rudder mix → RightThumbX, pedals None, keeping both triggers free for the game.

Choosing a strategy per game

  • Modern XInput game — pad buttons + axes for the flight controls, keyboard routing for the long tail (MFD pages, systems). Check the game's own binding UI to see the pad.
  • DOS / emulated game (DOSBox, source ports) — mostly keyboard routing (it arrives as scancodes, which DOSBox maps cleanly); axes via the pad if the emulator supports a controller, else map coarse throttle steps to keys.
  • Legacy DirectInput sim — the x360 pad appears as a DirectInput device too; if the game needs more than 11 buttons on the stick itself, prefer keyboard routing or run the RioGamepad HID (96 native buttons).
  • Anything with a clickable cockpit — mouse routing gives you cursor nudges and clicks from cockpit buttons.

Building the profile

  1. Create/edit from the tray: Edit profile. The editor shows the cockpit panel in its functional groups; click a button, set its label, action, modifiers, and Lit, then Apply. Save writes the config.
  2. Live check: with the RIO (or vRIO) connected, physical presses light the panel and the axis gauges move — before any game is involved. The "Send button output to the PC" toggle turns real keystroke injection on when you want to test into an editor/notepad.
  3. Triggers: comma-separated executable names that auto-activate the profile when their window is foreground (d1x-rebirth, descent). Matching is basename, case-insensitive, .exe optional. First matching profile wins; DOSBox-hosted games all share the DOSBox exe name (rename per game or switch manually); native games (Firestorm, Red Planet) go in NativeGameExecutables instead — RIOJoy releases the ports for them.
  4. RIO port: leave (app default), or a COM name, or pipe:vrio for the emulator.
  5. JSON-only settings (edit %APPDATA%\RIOJoy\config.json): AxisRouting, Calibration fine points, PlasmaComPort/PlasmaGreeting, and the Feedback section (see FEEDBACK.md).
  6. Legacy import: the Import .ini tray menu converts an original RIO.ini (buttons, inverts, greeting). For programmatic install-time handoff, see the next section.

Shipping a profile with your game

A game (or its installer/launcher) hands its profile to RIOJoy as a single-profile JSON document plus one command:

RioJoy.Tray.exe --import-profile <your-game-riojoy-profile.json>

The document is one RioProfile object — the shipped profiles/descent-d1x.json is the reference example. It must carry a "Name" (imports without one are rejected), and it bundles everything in one payload: Buttons, MatchExecutables (the triggers), Calibration, AxisRouting, PlasmaGreeting, Feedback, overlay labels. Author it in the profile editor, then lift the profile object out of %APPDATA%\RIOJoy\config.json into your distribution.

The import merges into the user's %APPDATA%\RIOJoy\config.json — created with defaults if absent, all other content preserved. A profile with the same name (case-insensitive) is replaced in place, so re-running the import on a game update is idempotent and never disturbs other games' profiles.

Contract for installers:

  • Check the exit code, not stdout (the tray is a GUI-subsystem exe; console output only appears when redirected): 0 imported, 1 failed, 2 usage, 3 RIOJoy is running.
  • The tray must not be running during import — a running tray holds the config in memory and its own saves would silently discard the merge, so the import refuses (exit 3) instead. Sequence: quit/skip the tray → import → (re)launch. On launcher-managed cabinets (TeslaConsole owns the RIOJoy lifecycle) a game's install step can import safely before the next boot.
  • Per-user, per-session: the config lives under the user's %APPDATA% and the running-instance check is per-session — run the import in the user's session, not as an elevated SYSTEM step.

Nothing else needs registering: once the tray starts with the merged config, the profile's MatchExecutables auto-activates it whenever the game's window is foreground (tray in Auto mode).

For games you control end-to-end, keep the profile document in the game's repo as the source of truth — dxx-rebirth does this, and a RIOJoy test (ShippedDescentProfile_MatchesDxxRebirthReferenceCopy) asserts the two checkouts stay byte-identical so drift is caught in CI.

Testing without hardware or game

  • vRIO (pipe:vrio): click buttons on the emulator's panel and watch them arrive — the editor lights up, the pad reacts.
  • joy.cpl (Game Controllers): shows the virtual pad's axes/buttons moving.
  • Keyboard routes: open Notepad, enable the editor's output toggle, press cockpit buttons.

Checklist for a new game

  1. Find the game's real executable name (foreground window process) → Triggers.
  2. Decide the axis story first: does the game hard-use triggers? If yes, route the throttle to a thumb axis UnipolarPositive (copy the Descent pattern).
  3. Map the few primary actions to pad buttons 111, everything else to keyboard; mark cockpit-lit buttons Lit.
  4. Set EnableZR if the game wants one rudder axis rather than two pedals.
  5. Live-check in the editor, then in-game; bind a spare cockpit button to RIO command → recalibrate for the cabinet.
  6. Add the Feedback section if the game will drive lamps/plasma back (OUTPUT-INTEGRATION.md).