sol-r1-linux-game-mappings/space-engineers/mapping-brief.md

5.0 KiB

Thrustmaster Sol-R 1 -> Space Engineers input mapping (Linux/Arch/Hyprland)

Goal

Fix "throttle behaves like a trigger" bug and build a full custom input scheme for the Sol-R1 (grip + base) in Space Engineers, on Arch/Hyprland, without Windows/T.A.R.G.E.T.

Stack decided on

  • joyful (AUR: yay -S joyful) - Go-based Linux joystick remapper, creates a virtual gamepad device from physical Sol-R inputs via YAML rules in ~/.config/joyful/. Chosen over input-remapper because it natively supports split-axis and segmented-axis-to-button mappings, which input-remapper doesn't (see upstream issue #900).
  • switch_sync.py (custom, in this repo/outputs) - a separate stateful daemon for switches that need to reconcile physical position against actual in-game state (bypasses joyful, which can't do this since it has no game-state feedback).
  • Steam launch options wrapper script - starts/stops joyful and switch_sync around the actual game session via %command%, instead of a Hyprland-IPC window-watcher (Proton WM_CLASS is unreliable for this). NOTE: Steam launch options are per-machine (stored in userdata/<id>/config/localconfig.vdf, not synced) - the wrapper script path should be identical across machines (keep it in dotfiles), but the launch-options string itself must be re-pasted into Steam on each machine.

Diagnostic tools (already written, in outputs/)

  • find_codes.py - run this first on every physical control to get real evdev BTN_/ABS_ codes. Nothing below has real codes yet - they're all TODO: placeholders until this is run.
  • switch_sync.py - screen-reads HUD state via grim region capture + color sampling, compares against physical switch position (evdev), and taps a virtual key only when they disagree. Has --sample X,Y for pixel-color calibration and --dump-example-config.

Current mapping plan

Physical control SE function Mechanism
Throttle (base) Move Forward/Backward joyful axis passthrough (was original bug: SE read raw HID axis like a trigger)
Main stick (grip) X/Y View/look/rotate joyful axis passthrough
Ministick (grip) Strafe horizontal/vertical joyful axis passthrough
Dual-position trigger, stage 1/2 Primary/secondary click (use/fire, alt-fire) joyful button passthrough
2 orange buttons (top of stick) Dampeners on/off (Z) + Relative dampers (Ctrl+Z) joyful, KEY_Z / [KEY_LEFTCTRL, KEY_Z] key emulation (not gamepad buttons - Relative Dampers isn't confirmed as a standalone bindable action, it's a modifier on the toggle key)
Rotary knob 1 (base) Switch toolbar (next/prev, default ,/.) joyful, detent pulses -> KEY_COMMA/KEY_DOT - ASSUMES knob reports as BTN pulses, not an ABS axis; confirm with find_codes.py
Rotary knob 2 (base) Hotbar slot select joyful, same pulse assumption as above; if it's an ABS axis instead, needs joyful's segmented axis-to-button feature
4-button group (base, left side) Jetpack / Helmet / Flashlight / Parking toggles joyful button passthrough to virtual gamepad buttons, bound in SE's own control menu
4 tilt switches (base, top), 2 buttons each Same 4 toggles as above, but as REAL on/off switches switch_sync.py - NOT joyful, because this needs game-state feedback to avoid desync

Known caveats / open items

  • SE's native joystick support only got real per-binding customization in update 1.209; before that (and possibly still, depending on version) it treated joysticks through an Xbox-controller-shaped abstraction. Check Options -> Controls -> Controllers on the current version before assuming full flexibility.
  • Dampeners are a stateless toggle (Z) + contextual modifier (Ctrl+Z, only works if dampers are off AND you're looking at a moving target in range). No true 3-state action exists in SE.
  • switch_sync.py's HUD detection is pixel-color based and fragile: breaks on HUD detail mode changes (Tab), resolution/UI scale changes, or camera view changes. Needs recalibration after any of those.
  • joyful's exact per-rule-type YAML field names (beyond the top-level devices/modes/rules structure and evdev keycode naming) were not fully verified against examples/ruletypes.yml in the actual installed version - check that file before trusting rule blocks verbatim.
  • None of the joyful config blocks have real BTN_/ABS_ codes yet - all placeholders pending find_codes.py output on the actual hardware.

Next steps

  1. Run find_codes.py against every physical control, replace all TODOs.
  2. Confirm rotary knobs are pulse (BTN) vs continuous (ABS) - determines which joyful rule type to use.
  3. Verify SE's actual keybinds for helmet/flashlight/parking in Options before finalizing switch_sync.py's toggle_key values.
  4. Calibrate switch_sync.py's HUD regions/colors for jetpack, helmet, light, parking indicators.
  5. Write the Steam launch-options wrapper script, add to dotfiles repo.
  6. Repeat launch-options paste on each machine (desktop, Framework 12, ThinkPad T440p) - not synced by Steam.