Generic joystick / HOTAS / pedals support: DirectInput 8 layer + capture wizard

Tester ask: configure non-Xbox controllers.  New L4JOY layer (DI8):
enumerates every non-XInput game device (up to 4), DIJOYSTATE2 polling
with range normalization, reacquire-on-loss, 3s hot-plug re-enum.
XInput devices are excluded via the documented RawInput IG_ VID/PID
check (live-verified skipping a real XBOX360 pad -- no double-feed).

bindings.txt grows joydev/joyaxis/joybutton/joyhat rows (device slots
by name substring or ordinal; axes X..RZ/SL0/SL1 with invert/slew/
deadzone; hats as 4-direction buttons with diagonal chords).  PadRIO
runs them through the existing channel/event machinery -- per-binding
edge arrays release automatically on detach (the #24 rule), and a
direct throttle axis maps full lever travel onto the 0..1 channel
while the gait detent still snaps a lever parked in the walk/run dead
band.

BT_JOYCONFIG=1 (joyconfig.bat) runs a console capture wizard: move
each control when prompted (stick/twist/throttle/fire), invert derived
from the move direction, hat look cluster auto-added, output written
between markers in bindings.txt with the rest of the file preserved.

Legacy L4DINPUT DIJoystick (L4CONTROLS=DIJOYSTICK) untouched.
Verified: grammar accept/reject per line, XInput-exclusion live,
30s in-mission no-device polling clean.  Awaiting a tester with real
stick hardware for axis verification.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-23 16:36:32 -05:00
co-authored by Claude Opus 4.8
parent 86e387b6c7
commit c2ee7299b2
12 changed files with 1531 additions and 6 deletions
+25
View File
@@ -88,6 +88,31 @@ recovery; keyboard-only run clean). Fix: on the connected->disconnected transit
`EmitButton(address, 0)` for every binding whose mask was held, THEN forget. Not reproducible
headless (needs real hardware unplug) -- logic fix, awaiting the reporter's controller.
## Generic joysticks / HOTAS / pedals -- DirectInput 8 layer (2026-07-23) [T2 rig-verified; awaiting stick hardware]
Tester ask: configure non-Xbox controllers. **`L4JOY.cpp/.h`** (all builds, lazy-init): DI8
enumeration of every non-XInput game device (up to 4), `c_dfDIJoystick2` polling with
`DIPROP_RANGE` ±32767 + reacquire-on-loss + 3 s hot-plug re-enum, normalized
`BTJoyDeviceState` {8 axes -1..1, 32 buttons, 4 POV hats}. **XInput exclusion** = the
documented wbem-free RawInput check (device path contains `IG_` → VID/PID blacklist vs
`guidProduct.Data1`) — LIVE-verified skipping a real XBOX360 pad. **Bindings**
(L4PADBINDINGS): `joydev <slot> [name-substr]` + `joyaxis <X|Y|Z|RX|RY|RZ|SL0|SL1> axis
<channel> [invert] [slew r] [deadzone f]` + `joybutton <0-31>` + `joyhat <0-3> <dir>` — rows
attach to the last joydev slot; unnamed slot binds attached device #slot. **PadRIO poll**
runs them through the SAME channel/event machinery (per-binding prev arrays → detach releases
automatically, the #24 rule); a direct joyaxis on Throttle maps full travel [-1,1]→[0,1] and
writes every frame (a lever OWNS the channel; slewHeld stays clear so the GAIT DETENT still
snaps a lever parked in the walk/run dead band). **Wizard** `BT_JOYCONFIG=1`
(`BTJoyConfigWizard`, console UI, `joyconfig.bat`): baselines all axes (a HOTAS lever rests
anywhere), captures the dominant mover per prompt (0.45 threshold, 250 ms hold, already-
assigned axes excluded), derives invert from the move direction (throttle from FINAL
position), auto-adds the hat look cluster, and rewrites bindings.txt between
`# >>> BT_JOYCONFIG` markers (rest of the file preserved). Sign conventions: JoystickX + =
twist right, JoystickY + = aim up (wizard binds flight-style forward=down via invert), Turn +
= right. Legacy `L4DINPUT.cpp DIJoystick` (L4CONTROLS=DIJOYSTICK, 1995 `Joystick` iface)
left untouched. Verified: grammar accept/reject by line, no-device + XInput-only polling
clean 30 s in-mission, zero DI cost with no joy rows. NOT yet verified: real stick axes
(no hardware here) — needs a tester with a stick.
## Plan of record (2026-07-17)
Working branch `glass-cockpit` (from master `e2c21c4`). Steps: (1) gates+scaffolding [THIS],