Files
RP412/docs/CONTROLS.md
T
CydandClaude Opus 5 91420b5cb2 Flight sticks, HOTAS and pedals, with a setup wizard
Ported from BT411, which needed the same thing for its glass cockpit.

PadRIO reads XInput, which covers Xbox-class pads and nothing else. A
flight stick, a HOTAS throttle, a twist grip, rudder pedals or a wheel
arrive through DirectInput instead, and until now the game could not see
any of them - the only generic-joystick path left was the 1995 single-
device DIJoystick behind L4CONTROLS=DIJOYSTICK, which is untouched here.

L4JOY is the reader: up to four devices as normalized state blocks, hot-
plug re-enumeration on the same ~3 s cadence PadRIO uses to look for a
pad, and a device lost mid-race zeroed rather than left holding whatever
was pressed when it went. XInput-class devices are excluded by VID/PID
against the RawInput paths carrying the "IG_" marker - without that an
Xbox pad arrives through both APIs and every button counts twice.

bindings.txt gains four rows in the grammar it already had, using its own
vocabulary (deadzone/rate) rather than BT411's:

  joydev <slot> [product-name substring]
  joyaxis <src> axis <axis> [invert] [deadzone <d>] [rate <n>]
  joybutton <n> button <addr> [toggle]
  joyhat <n> <up|down|left|right> button <addr>

Slots resolve to a live device every poll, by name substring or ordinal,
so unplugging and replugging does not rewrite anyone's file.

Two things the pod's shape forced that BT411 solved differently:

  Pedals - a signed composite axis that decomposes into the pod's two
  pedals, positive right and negative left. The pod has a pedal each
  side; a twist grip or rudder bar is one signed control, and pressing
  one or the other but never both is exactly what it wants to say. It
  is a channel name like any other, so a pad stick can drive the turn
  too.

  A joyaxis on Throttle with no rate is a real lever and OWNS the
  channel - full travel maps onto the 0..1 the pod runs on, instead of
  nudging the accumulator that a spring-centred pad stick has to use.

RP412JOYCONFIG=1 (joyconfig.bat) runs the capture wizard before the
console screen: it asks the player to move each control, and derives the
sign convention from the DIRECTION of the move. That is the point of it -
a stick that reads positive pushed right and one that reads negative are
equally common, and no amount of documentation gets a player to work out
which they own. It writes only its own section, between marker lines, so
hand-edited keyboard and pad rows survive re-running it.

The wizard also prints every axis at rest before it starts. A driver that
refuses the +-32767 range we ask for reports its own, and an axis then
sits hard over instead of near zero; seeing "X +1.00" on an untouched
stick is the difference between a five-minute fix and a bug report that
says it configured itself. Each capture reports the move it saw for the
same reason.

Verified on the Logitech Extreme 3D on this machine. Enumeration finds
it and excludes the Xbox pad, which still arrives separately through
XInput. Every row shape parses - 7 axes, 2 buttons, 4 hat directions -
and three deliberately malformed rows (a bad axis name, button 99, a
"sideways" hat) are each rejected by line number rather than silently
dropped. The wizard lists the device with its axes at rest reading
X +0.00 Y -0.01 RZ -0.04 SL0 +1.00, waits on the first prompt without
self-triggering, and with a hand on the stick captures X to steering,
Y to pitch, RZ to the pedals and SL0 to the throttle, inverting the ones
that read backwards.

Running the captures through to a written file needs a hand on the
stick, so that part is the machine's to confirm, not this build's.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 10:06:30 -05:00

8.5 KiB
Raw Blame History

Red Planet 4.12 — controls map

Generated from the default bindings.txt (the file the game writes beside the exe on first run). Everything here is rebindable: edit that file and restart, or delete it to get these defaults back.

Grammar reminder, from the file's own header:

key <name>      button <addr> [toggle]
key <name>      axis <axis> deflect <n>      # held, springs back
key <name>      axis <axis> rate <n>         # walks per second, sticks
pad <button>    button <addr> [toggle]
padaxis <src>   axis <axis> [invert] [deadzone <d>] [rate <n>]

Xbox controller

              ( LB )  Panic                Throttle / reverse  ( RB )
             ( LT )  Left pedal              Right pedal  ( RT )
        ______________________________________________________
       /                                                      \
      /    [Back]                                  [Start]     \
     |     unbound                                  unbound      |
     |                                                           |
     |        ,--.                                    (Y)        |
     |       ( L  )  stick X / Y                   Upper         |
     |        `--'                            (X)       (B)      |
     |                    +---+              Pinky     Middle    |
     |     D-pad  -->     |hat|                  (A)             |
     |                    +---+                 Main             |
     |                                                           |
     |                              ,--.                         |
     |                             ( R  )  throttle (Y axis)     |
      \                             `--'                        /
       \____________________________________________________ _/
Control Does RIO address
Left stick Joystick X / Y (inverted, deadzone 0.24 — pod convention) axes
Right stick Y Throttle, as a rate: hold to wind it up or down, position sticks axis
Left / right trigger Left / right pedal (deadzone 0.12) axes
A Main trigger 0x40
B Middle 0x46
X Pinky 0x45
Y Upper 0x47
LB Panic 0x3D
RB Throttle button — reverse thrust 0x3F
D-pad Hat: up / back / left / right (look) 0x42 0x41 0x44 0x43
Start, Back unbound — yours to map

The config buttons (0x37/0x36) are the first two buttons of the Upper Right MFD's top row, reachable from the keyboard on 9 and 0. Start and Back ship unbound so you can put anything there; the commented lines in bindings.txt show how.


Keyboard

Flight cluster — the number pad

                    +-----+-----+-----+
                    |  7  |  8  |  9  |     7 / 9  left / right pedal
                    | Lped| fwd |Rped |     8 / 2  stick forward / back
                    +-----+-----+-----+     4 / 6  stick left / right
                    |  4  |  5  |  6  |     0      main trigger
                    | left|     |right|
                    +-----+-----+-----+
                    |  1  |  2  |  3  |
                    |     | back|     |
                    +-----+-----+-----+
                    |     0     |  .  |
                    |  trigger  |     |
                    +-----------+-----+

   Shift  throttle up      Ctrl  throttle down      Alt  reverse thrust
   (both throttle keys walk the axis and it holds where you leave it)
Key Does
NumPad 8 / 2 Stick forward / back
NumPad 4 / 6 Stick left / right
NumPad 7 / 9 Left / right pedal
NumPad 0, Space Main trigger (0x40)
Shift / Ctrl Throttle up / down (position holds)
Alt Reverse thrust (0x3F)
Arrow keys Hat — look up / back / left / right
Alt + Q Abort the mission (score banked, back to menu or lobby)

Panel buttons — the letter and number rows

The rest of the keyboard is the pod's button board, laid out as it is printed on the panel. Each MFD has 8 buttons (a 4-wide top row and a 4-wide bottom row); the keyboard rows run left to right across the three upper MFDs, then the two lower ones.

  UPPER MFDs      Left MFD          Middle MFD         Right MFD
   top row     1   2   3   4  |  5   6   7   8  |  9   0   -   =
   bottom row  Q   W   E   R  |  T   Y   U   I  |  O   P   [   ]
                 0x2F..0x2C       0x27..0x24        0x37..0x34
                 0x2B..0x28       0x23..0x20        0x33..0x30

  LOWER MFDs      Left MFD                            Right MFD
   top row     A   S   D   F        (G unbound)     H   J   K   L
   bottom row  Z   X   C   V        (B unbound)     N   M   ,   .
                 0x0F..0x0C                            0x07..0x04
                 0x0B..0x08                            0x03..0x00

  COLUMNS beside the map
   Secondary   F1  F2  F3  F4  F5  F6        0x10..0x15
   Screen      F7  F8  F9  F10 F11 F12       0x18..0x1D

G and B are deliberately unbound: they are the physical gap between the Lower Left and Lower Right MFD clusters on the real board.

Every one of these buttons is also clickable on screen — the red strips around each MFD and the amber strips beside the map are the same addresses, and they light up when the game commands their lamps.

Not bound by default

The pilot keypad (0x500x5F) and external operator keypad (0x600x6F) have no keys assigned — the game never reads them. They remain bindable if you want them (they arrive as arcade RIO key events).


RGB keyboard lighting

If the machine has a Dynamic Lighting keyboard, bound panel keys mirror the pod's lamps: red for the MFD banks, amber/yellow for the Secondary and Screen columns, blinking in step with the on-screen buttons. Unbound keys go dark. Set RP412KEYLIGHT=0 in environ.ini to switch it off.


Flight sticks, HOTAS and rudder pedals

Anything that is not an Xbox-class pad — a flight stick, a HOTAS throttle, a twist grip, rudder pedals, a wheel — comes in through DirectInput rather than XInput, and needs to be told which axis is which. Run joyconfig.bat once. It asks you to move each control in turn, works out which device and axis you moved and which way round it reads, and writes the joystick rows of bindings.txt. Then it carries on into the setup screen so you can try them straight away.

It only writes its own section, between two marker lines, so anything you have edited yourself survives re-running it. Xbox-class controllers need none of this — they are the pad rows above and work out of the box.

By hand, the rows look like this:

joydev 0 T.16000M                          # or a bare slot number
joyaxis X   axis JoystickX invert deadzone 0.08
joyaxis Y   axis JoystickY invert deadzone 0.08
joyaxis RZ  axis Pedals deadzone 0.08      # twist grip works both pedals
joyaxis SL0 axis Throttle deadzone 0       # a real lever owns the channel
joybutton 0 button 0x40                    # trigger
joyhat 0 up button 0x42

Axis names are DirectInput's — X Y Z RX RY RZ SL0 SL1 — where a twist grip is usually RZ and a HOTAS throttle usually Z or SL0.

Two things are worth knowing. Pedals is a signed axis that drives the pedal pair, positive for the right pedal and negative for the left, so one rudder bar or twist grip works both the way a foot never could. And a joyaxis on Throttle with no rate is treated as a real lever: its full travel is the throttle range, rather than nudging a position the way a spring-centred pad stick has to.

Rebinding

bindings.txt sits beside the exe, written with the full default layout and its own documentation on first run. Change a line, restart the game. Delete the file to restore these defaults — worth doing after an update, since an existing file is never overwritten.

Examples:

key J       button 0x40        # main trigger on J as well
pad X       button 0x40        # move the trigger to the X button
key NumPadAdd    axis Throttle rate 0.75    # throttle on numpad +/-
key NumPadSubtract axis Throttle rate -0.75
padaxis LeftStickX axis JoystickX deadzone 0.15   # drop 'invert' to flip X

Addresses: 0x000x47 are the lamp buttons above, 0x500x6F the keypads. Axis names: Throttle, LeftPedal, RightPedal, JoystickX, JoystickY, Pedals. Key names follow the .NET Keys naming (AZ, D0D9 for the digit row, F1F12, NumPad0NumPad9, Up, Down, Left, Right, Space, Return, Shift, Ctrl, Alt, OemMinus, Oemplus, Oemcomma, OemPeriod, OemOpenBrackets, OemCloseBrackets, …).