Testers taking notes in another window were flying the pod while they
typed. RP412INPUTFOCUS=1 is the new default; 0 restores the old
behaviour.
The pod was the only thing running on its cabinet, so the virtual RIO
reads the key state directly rather than waiting on the message pump.
That is the right call for latency and it is why the pedals feel like
pedals - but a direct read is a read of the WHOLE keyboard, whatever has
focus. On a cabinet that distinction did not exist. On a desktop it is
the difference between writing a bug report and steering into a wall
while you write it.
One choke point does the whole job: PadRIO::PollInputs is where the
keyboard, the XInput pad and the DirectInput stick are all read, so a
single flag covers the three of them. The joystick needs no change of
its own - unfocused the resolve block is skipped, every device slot
stays at -1, and the button, hat and axis loops find no device and read
released on their own. It is opened DISCL_BACKGROUND on purpose, or it
would stop answering the moment a cockpit pane took focus, so declining
to poll it is what makes it go quiet.
Each source reads as RELEASED rather than the poll returning early, and
that is the part worth keeping: bail out instead and whatever was held
at the moment you switched away stays held until you come back, which is
the stuck throttle this is meant to prevent rather than cause. Reading
released lets the diffs already in there turn it into proper release
events.
The throttle accumulator is the deliberate exception. It is the pod's
one sticky axis and it integrates what the controls ask for, so controls
asking for nothing simply stop moving it - you come back to the speed
you left rather than to a dead stop.
Focus is tested per PROCESS, not against one window handle. The cockpit
is a shell full of child panes, the exploded view is six windows of its
own and the plasma glass another; matching a single HWND would drop the
controls the moment somebody clicked an MFD.
Real RIO cockpit hardware is untouched - this is the keyboard, pad and
joystick path only. The volume and bass keys in L4CTRL were already
gated this way, unconditionally, which is where the idiom comes from.
On by default because the alternative is every tester editing a file
before the fix reaches them: an environ.ini written by an older build
does not carry the line, so the built-in default is what they get. The
log says which way it is set, and the option-mention check names it as
one they have not heard of.
Verified against the built exe both ways: a fresh run writes the
documented default and applies 14 settings where it applied 13, and a
file with the line removed reports exactly one unknown option and falls
back to focus-gated.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
You are never asked what you own. Two controls cannot simply be watched,
so they are asked for differently.
Yaw is asked for twice, right then left, and which axis answers is the
measurement. The same axis both times is one control covering both
directions - a twist grip, a rudder bar, pedals the driver has already
mixed - and binds to the signed Pedals axis. Two different axes are two
real pedals, one per foot, which is what the pod had, so they bind to the
pod's own LeftPedal/RightPedal pair and the game does the mixing: both at
once then does what both at once did in the pod.
The throttle is zeroed first. A lever sits wherever it was last left,
possibly hard against the stop that reads +1, so watching it move says
nothing about which end means power. Close it, press SPACE, then open it,
and the direction it travels from a known idle is the direction that
means throttle.
CONTROLS.md, the handbook and the packaged README say all of this, and
joyconfig.bat's own header no longer promises "rudder-pedal setup" when
racing pedals work too.
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>
The polish-backlog item, implemented from vRIO KeyboardLampMirror:
game-commanded RIO lamp states paint per-key RGB keyboards through
Windows Dynamic Lighting (WinRT LampArray). Keys bound to lamp
addresses in the active bindings profile glow with the panel palette
(red banks, yellow Secondary/Screen columns), flash modes use the
exact L4MFDVIEW formula so keyboard and on-screen buttons blink in
step, unbound keys are blacked out so the board reads as the button
field, and zone-lit keyboards fall back to a board-wide mirror of the
strongest lamp. Advantage over vRIO: Dynamic Lighting grants LEDs to
the FOREGROUND app - which is the game - so no Windows settings
dance.
Isolation: L4KEYLIGHT.cpp compiles /std:c++17 + DEFAULT packing +
conformance (per-file vcxproj settings; the engine /Zp1 would break
the WinRT ABI) with a scalars-only interface, and all WinRT work runs
on a private worker thread (watcher, claiming, 100ms paint loop).
On by default with a bindings map present; RP412KEYLIGHT=0 opts out;
missing Dynamic Lighting logs once and stays dormant.
Verified live on the dev laptop: claimed its 24-zone keyboard
(board-wide mirror) during a race; race cycling with per-race
start/stop of the mirror thread stays green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PadRIO now loads bindings.txt (vRIO profile grammar: key/pad/padaxis
lines with toggle, deflect/rate, invert/deadzone options) written
self-documenting with the full default layout on first run. The
default is vRIO board-complete map - number and letter rows are the
MFD banks as printed on the panel, F-keys the secondary/screen
columns, numpad the pilot keypad (0x50-0x5F delivered as arcade RIO
KeyEvents, a new PadRIO capability), Space/arrows the joystick
column - with the desktop driving keys carved out: WASD stick, Q/E
pedals, PgUp/PgDn throttle, B reverse (vRIO gap key; R returns to
its bank). Pad bindings unchanged in spirit, plus Panic on LB and
config on Start/Back; axis signs are encoded in the profile now, so
L4PADFLIP flips on top of it.
Default profile parses with zero rejected lines (68 key buttons, 8
key axes, 12 pad buttons, 5 pad axes); single-player cycle and the
key-bomb tests stay green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Each MFDSplitView window now carries its display's physical button bank,
placed as in the pod (addresses per vRIO CockpitLayout): a 4x2 red
cluster around each MFD glass (anchors 0x2F/0x27/0x37 upper, 0x0F/0x07
lower, addresses descending row-major) and 6 amber buttons down each
side of the map - Secondary 0x10-0x15 left, Screen 0x18-0x1D right; the
remaining column addresses are Tesla relays, per the pod wiring, so they
get no buttons.
Buttons light from the lamp state the game commands: PadRIO grows a
static active-instance hook (SetScreenButton/GetLampState); mouse
press/release feeds PadRIO's desired-state sampling alongside pad and
keyboard, and paint decodes the lamp byte (state1/state2 brightness,
solid/slow/med/fast flash animated by tick). With real serial hardware
(no PadRIO) the buttons draw dark and inert.
Verified: map flank buttons light per the game's preset lamps, aligned
with the labels the glass draws at its edges; MFD clusters render 4+4.
Roadmap: queued the vRIO Dynamic Lighting RGB-keyboard lamp mirror as a
polish-pass item. dist repacked.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Playtest feedback: X and Y from the Xbox controller were both inverted.
Negate both by default (pad and WASD go through the same assignment, so
they stay consistent); L4PADFLIP now flips back toward raw XInput
orientation per axis.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Splits the control surface the game consumes from the RIO board into
RIOBase (8 virtuals + the five analog scalars); the serial RIO is now one
implementation of it. Adds two new ones:
- PadRIO (L4CONTROLS=PAD): in-process RIO speaking the full surface from
an XInput controller + PC keyboard using vRIO's default profile (left
stick/WASD = stick, triggers/Q,E = pedals, right stick Y/PgUp,PgDn =
rate throttle that holds position, A/Space = trigger, B/R = reverse,
dpad/arrows = hat, Start,Back/F1,F2 = config). Samples in GetNextEvent
so button latency does not depend on the 15 s menu-time analog cadence;
hot-plugs pads; L4PADFLIP=XY inverts stick axes; lamp commands land in
lampState[] for the planned on-screen cockpit panel. The stock
VTVRIOMapper/lamp/button path runs unchanged.
- PlasmaScreen (L4PLASMA=SCREEN): the 128x32 plasma glass as a desktop
window in plasma orange (L4PLASMASCALE, default x4), rendering the same
Video8BitBuffered surface the gauge system always drew; no COM port.
Verified in the sandbox with vRIO off and no serial devices: boots to a
running mission, controller hot-detected, plasma window drawing live game
content (score readout). BUILD.md 4 documents the desktop environ.ini and
bindings; roadmap updated.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>