Mouse buttons are bindable; mouse LOOK scoped (not built)
BUTTONS (shipped). Win32 hands mouse buttons out as virtual keys, and the
binding path already stores VKs and polls GetAsyncKeyState -- so this needed no
verb, no parser change and no new machinery, just names:
key MOUSE4 button 0x40 # side buttons
key MOUSE5 button 0x46
key MOUSEMIDDLE button 0x41
MOUSELEFT/MOUSERIGHT are named too, with a warning in the file header and the
docs: they are how the player presses cockpit buttons (left = press, right =
latch), and the poll is focus-guarded but not click-aware, so binding either
ALSO fires on every panel click. The side and middle buttons are the free
ones. Axes work as well (`key MOUSE4 axis Throttle slew + 0.7`).
One guard: the RGB lamp mirror skips mouse VKs when building its map -- a
keyboard lamp array has no mouse buttons to paint (BTPadBindingIsMouseKey).
Verified live: a bindings.txt carrying two mouse rows loads 76 keys (74 + 2),
and a real injected XBUTTON1 press produced `[emitter] FIRED #1`. Clean A/B --
the earlier run whose SendInput failed the struct-size check logged no fire at
all. Shipped commented-out examples in the default profile so the capability
is discoverable.
LOOK (scoped only). docs/MOUSELOOK_PLAN.md. Mouse MOVEMENT is a real feature,
not a table entry, and the hard part is not reading the device:
- The cursor is already spoken for. The glass cockpit's premise is that all
72 buttons are mouse targets; mouse-look wants a captured, hidden, recentred
cursor. Both cannot be true, so this is a MODE question first -- four
models compared, recommending hold-to-look (costs nothing when unbound,
cannot strand a player, behaves the same in surround and exploded layouts).
- The channels are POSITIONAL (JoystickX/Y are -1..1 deflections the mapper
reads per control mode); a mouse gives deltas. Accumulate-into-a-virtual-
stick reuses every existing rule; a rate model would need new mapper
semantics.
- Control mode changes what it MEANS: BASIC steers with the stick, MID/ADV
twist the torso -- so mouse-look steers in one and aims in the other.
Proposed grammar (new row type, so old builds skip it with a warning),
where the code goes, ~250 lines, and a verification plan whose load-bearing
item is "panel clicks still work when a mouselook binding exists but is not
engaged". Also flagged: the pod had NO mouse, so nothing here is
reconstruction -- every choice is design, judged on feel, and it must stay out
of the pod build's path.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -85,7 +85,7 @@ precise than anything you can infer.
|
||||
| Term / acronym definitions | `reference/glossary.yaml` |
|
||||
| The ORIGINAL 1995 player manual (controls, per-mech stats, coolant loops) | `reference/manual/Tesla40_BT_manual.pdf` (+ alignment audit in `context/pod-hardware.md` §Manual) |
|
||||
| The complete verbatim detail (fallback) | `docs/PROGRESS_LOG.md` (the old 2236-line CLAUDE.md) |
|
||||
| Detailed running ledgers | `docs/RECONCILE.md`, `docs/GAUGE_COMPOSITE.md`, `docs/HARD_PROBLEMS.md`, `docs/SUBSYS_PLAN.md`, `docs/P3_LOCOMOTION.md`, `docs/RESOURCE_AUDIT.md`, `docs/VEHICLE_SUBSYSTEMS.md`, `docs/BGF_FORMAT.md`, `docs/ASSET_PIPELINE.md`, `docs/BT_SOURCE_STATUS.md`, `docs/WAVE_PLAN.md`, `docs/GLASS_COCKPIT.md`, `docs/REVOLVING_DOOR_PLAN.md`, `docs/INPUT_PATH_AUDIT.md`, `docs/RESPAWN_REARM_PLAN.md`, `docs/KD_SCOREBOARD_PLAN.md`, `docs/DIST_LAYOUT_PLAN.md` |
|
||||
| Detailed running ledgers | `docs/RECONCILE.md`, `docs/GAUGE_COMPOSITE.md`, `docs/HARD_PROBLEMS.md`, `docs/SUBSYS_PLAN.md`, `docs/P3_LOCOMOTION.md`, `docs/RESOURCE_AUDIT.md`, `docs/VEHICLE_SUBSYSTEMS.md`, `docs/BGF_FORMAT.md`, `docs/ASSET_PIPELINE.md`, `docs/BT_SOURCE_STATUS.md`, `docs/WAVE_PLAN.md`, `docs/GLASS_COCKPIT.md`, `docs/REVOLVING_DOOR_PLAN.md`, `docs/INPUT_PATH_AUDIT.md`, `docs/RESPAWN_REARM_PLAN.md`, `docs/KD_SCOREBOARD_PLAN.md`, `docs/DIST_LAYOUT_PLAN.md`, `docs/MOUSELOOK_PLAN.md` |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -743,6 +743,7 @@
|
||||
<tr><th>Buttons</th><td class="addr">0x00 – 0x47</td></tr>
|
||||
<tr><th>No function</th><td class="addr">0x16 0x17 0x19 0x1E 0x1F 0x38–0x3E <span style="color:var(--ink-quiet);font-family:var(--sans)">— the pod never wired these</span></td></tr>
|
||||
<tr><th>Key names</th><td class="mono">A–Z · 0–9 · F1–F12 · NUMPAD0–9 · NUMPADDOT · UP DOWN LEFT RIGHT · SPACE · LSHIFT LCTRL LALT · MINUS EQUALS COMMA PERIOD LBRACKET RBRACKET BACKTICK</td></tr>
|
||||
<tr><th>Mouse</th><td class="mono">MOUSE4 · MOUSE5 · MOUSEMIDDLE <span style="color:var(--ink-quiet);font-family:var(--sans)">— buttons only; left/right also press cockpit buttons</span></td></tr>
|
||||
<tr><th>Pad</th><td class="mono">A B X Y · DPAD_UP/DOWN/LEFT/RIGHT · LB RB · BACK START · LS RS</td></tr>
|
||||
<tr><th>Pad axes</th><td class="mono">LX LY RX RY LT RT</td></tr>
|
||||
</tbody>
|
||||
|
||||
@@ -82,6 +82,16 @@ control in turn — stick, twist, throttle, fire buttons — writes the bindings
|
||||
the solo menu to try them. It only rewrites its own section of `bindings.txt`, so hand edits
|
||||
elsewhere in the file survive.
|
||||
|
||||
**Mouse buttons** are bindable like any key — `MOUSE4`, `MOUSE5` (the side buttons),
|
||||
`MOUSEMIDDLE`, and `MOUSELEFT`/`MOUSERIGHT`. The side and middle buttons are the free ones; left
|
||||
and right are how you press cockpit buttons, so binding those fires on every panel click too.
|
||||
Mouse *movement* is not bindable — see `docs/MOUSELOOK_PLAN.md`.
|
||||
|
||||
```
|
||||
key MOUSE4 button 0x40 # side button = main trigger
|
||||
key MOUSE5 button 0x46 # side button = middle thumb
|
||||
```
|
||||
|
||||
**RGB keyboards** with Windows Dynamic Lighting mirror the panel: any key you have bound to a
|
||||
lamp button glows in the panel's own colours and flashes in step with the on-screen buttons. Turn
|
||||
it off with `BT_KEYLIGHT=0` in `environ.ini`.
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
# MOUSELOOK_PLAN — scope: mouse movement as a cockpit axis
|
||||
|
||||
**Status: SCOPED, NOT IMPLEMENTED.** Written 2026-07-26 at the author's request, alongside the
|
||||
mouse-BUTTON support that did ship (4.11.565). Read §2 before agreeing to build it — the hard
|
||||
part is not reading the mouse.
|
||||
|
||||
---
|
||||
|
||||
## 1. What shipped, and what this is instead
|
||||
|
||||
**Shipped:** mouse *buttons*, as key names (`MOUSE4`, `MOUSE5`, `MOUSEMIDDLE`, `MOUSELEFT`,
|
||||
`MOUSERIGHT`). Zero new machinery — Win32 hands mouse buttons out as virtual keys and the binding
|
||||
path already stores VKs and polls `GetAsyncKeyState`, so five table entries were the whole change.
|
||||
|
||||
**This document:** mouse *movement* driving `JoystickX`/`JoystickY` (torso twist and aim) — the
|
||||
thing a modern player means by "mouse look". That is a real feature, not a table entry, for three
|
||||
reasons that have nothing to do with reading the device.
|
||||
|
||||
## 2. The three real problems
|
||||
|
||||
### 2.1 The cursor is already spoken for ⚠ (the blocking one)
|
||||
|
||||
The glass cockpit's whole premise is that **every one of the 72 pod buttons is a mouse target** —
|
||||
left-click presses, right-click latches, and under the exploded layout there are seven separate
|
||||
windows to click in. Mouse-look wants the opposite: a captured, hidden, recentred cursor that
|
||||
never touches a button.
|
||||
|
||||
These cannot both be true at once, so the feature is really a **mode question**, and the answer
|
||||
decides the whole design:
|
||||
|
||||
| Model | Feel | Cost |
|
||||
|---|---|---|
|
||||
| **A. Hold-to-look** (`key MOUSE5 mouselook` or a modifier) | Cursor stays free; press to steer, release to click buttons | No mode confusion, discoverable, but you cannot look and hold a trigger comfortably |
|
||||
| **B. Toggle** (a key flips captured/free) | Full mouse-look while engaged | Needs a visible on-screen indicator or players get stuck "unable to click anything" |
|
||||
| **C. Region** (look while the pointer is over the 3D view, click when over a display) | No mode at all | Fails in the exploded layout (displays are separate windows) and near the surround's corner MFDs, which overlap the view by design |
|
||||
| **D. Always captured**, panel clicks via a "release" key | Cleanest for a shooter | Throws away the cockpit's defining interaction; **not recommended** |
|
||||
|
||||
**Recommendation: A, with B as an option.** A costs nothing when unbound and cannot strand a
|
||||
player. It is also the only one that behaves identically in the surround and exploded layouts.
|
||||
|
||||
### 2.2 The pod's channels are POSITIONAL; a mouse is not
|
||||
|
||||
`JoystickX`/`JoystickY` are absolute stick deflections in −1..1 that `MechControlsMapper`
|
||||
interprets per control mode. A mouse produces *deltas*. Bridging that is a design choice, not a
|
||||
conversion:
|
||||
|
||||
- **Accumulate** (recommended): integrate deltas into the channel, clamp to ±1 — a "virtual
|
||||
stick". Reuses every existing rule (composition, the #36 release edge, the gait detent).
|
||||
- **Rate**: treat the delta as a velocity of torso angle. Feels better for aiming, but the mapper
|
||||
has no rate input — it would mean a new channel semantic and a new path through the mapper.
|
||||
Bigger and less authentic.
|
||||
|
||||
With Accumulate, the stick does not spring back (a mouse has no centre). Options, all cheap:
|
||||
a `spring <rate>` trailing option that decays toward 0, and/or a recentre key (0x42 torso centre
|
||||
is already bound to the Up arrow).
|
||||
|
||||
### 2.3 Control mode changes what it means
|
||||
|
||||
BASIC steers with the stick; MID/ADV twist the torso with it and steer with the pedals
|
||||
([[experience-levels]], [[pod-hardware]]). So mouse-look **steers the mech in BASIC and aims the
|
||||
torso in MID/ADV** — authentic, but it will read as a bug to anyone who hasn't been told. The
|
||||
docs must say so, and the feature is most useful in MID/ADV.
|
||||
|
||||
## 3. Proposed shape (if we build it)
|
||||
|
||||
New row type — the sanctioned way to extend the grammar (old builds skip unknown rows with a
|
||||
logged warning, so a player's file stays forward-compatible):
|
||||
|
||||
```
|
||||
mouseaxis <X|Y> axis <channel> [invert] [sensitivity <n>] [spring <rate>] [deadzone <f>]
|
||||
key <KEY> mouselook # hold-to-look (model A)
|
||||
```
|
||||
|
||||
Example default (shipped commented out):
|
||||
|
||||
```
|
||||
#mouseaxis X axis JoystickX sensitivity 1.0 spring 0
|
||||
#mouseaxis Y axis JoystickY sensitivity 1.0 spring 0 invert
|
||||
#key MOUSE5 mouselook
|
||||
```
|
||||
|
||||
### Where the code goes
|
||||
|
||||
| Piece | Where | Notes |
|
||||
|---|---|---|
|
||||
| Raw delta capture | `L4PADRIO` (new), fed from the window proc | **`WM_INPUT` (RawInput)**, not `WM_MOUSEMOVE`: relative motion, no cursor clamping at screen edges, no pointer acceleration. Register `HID_USAGE_GENERIC_MOUSE` with `RIDEV_INPUTSINK` off (foreground only). |
|
||||
| Capture/hide | same | `SetCapture` + `ShowCursor(FALSE)` + recentre per frame while engaged; release on focus loss |
|
||||
| Bindings | `L4PADBINDINGS` | `mouseAxisBindings[]` + the `mouselook` action, parallel to `padAxisBindings` |
|
||||
| Channel write | `PadRIO::Poll` | Beside the pad/joy axis writes, with the SAME per-binding previous-value array so it inherits the #36 release edge and the #24 disconnect rule |
|
||||
| Docs | `bindings.txt` header, `docs/CONTROLS.md`, `docs/CONTROLS.html` | including the control-mode caveat |
|
||||
|
||||
Estimated size: ~250 lines plus docs. No engine or reconstruction changes — this is entirely in
|
||||
the glass desktop layer, like PadRIO itself.
|
||||
|
||||
## 4. Authenticity note
|
||||
|
||||
**The pod had no mouse.** There is nothing to reconstruct here and no binary behaviour to be
|
||||
faithful to — this is a desktop-convenience layer, exactly like PadRIO and the glass cockpit
|
||||
itself. That is fine (the layer exists precisely so developers and players can fly without a
|
||||
cabinet), but it means every decision above is a *design* decision and should be judged on feel,
|
||||
not fidelity. It also means it must stay out of the pod build's path: gate it the way PadRIO is
|
||||
gated, so `BT_PLATFORM=pod` never sees it.
|
||||
|
||||
## 5. Verification plan
|
||||
|
||||
1. `mouseaxis` rows parse; unknown-option rows warn and skip (grammar-compatibility rule).
|
||||
2. Hold-to-look engages/releases; cursor visible again on release AND on focus loss (the #24
|
||||
disconnect rule, mouse edition).
|
||||
3. Channel values track motion, clamp at ±1, and release cleanly to 0 (the #36 latch bug is
|
||||
exactly the hazard here).
|
||||
4. **Panel clicks still work** while a mouselook binding exists but is not engaged — in BOTH the
|
||||
surround and exploded layouts. This is the regression that matters.
|
||||
5. Control-mode sweep: BASIC steers, MID/ADV twists — confirmed against `[mppr]` trace.
|
||||
6. Pod + dev profiles: no mouse path constructed at all.
|
||||
|
||||
## 6. Open questions
|
||||
|
||||
1. Model A or B (§2.1)? Everything else follows from it.
|
||||
2. Should mouse-look imply a crosshair/reticle change, so the player can see it is engaged?
|
||||
3. Does the exploded layout need its own answer, given the seven windows each have their own
|
||||
client area and the main window may not have focus?
|
||||
4. Is this wanted for the tester builds at all, or only for the operator/dev seat? It is not
|
||||
pod-authentic, and the cockpit's mouse-click identity is a feature people have praised.
|
||||
|
||||
## Key Relationships
|
||||
|
||||
- Extends: [[glass-cockpit]] (PadRIO, the desktop input layer) · Constrained by: [[pod-hardware]]
|
||||
(the RIO channel model), [[experience-levels]] (control modes)
|
||||
- Prior art in-tree: `L4JOY` (DirectInput layer + the wizard) is the closest analogue for adding
|
||||
a whole new input device cleanly.
|
||||
@@ -61,8 +61,42 @@ static const NamedValue keyNames[] =
|
||||
{ "BACKTICK", VK_OEM_3 }, { "LBRACKET", VK_OEM_4 },
|
||||
{ "BACKSLASH", VK_OEM_5 }, { "RBRACKET", VK_OEM_6 },
|
||||
{ "QUOTE", VK_OEM_7 },
|
||||
//
|
||||
// MOUSE BUTTONS (2026-07-26). Win32 hands these out as virtual keys, and
|
||||
// the whole binding path already stores VKs and reads them with
|
||||
// GetAsyncKeyState -- so they need no verb, no parser change and no new
|
||||
// machinery, just a name. `key MOUSE4 button 0x40` works exactly like any
|
||||
// other key row, axes included.
|
||||
//
|
||||
// ⚠ MOUSELEFT/MOUSERIGHT are how the player presses cockpit buttons (left
|
||||
// = press, right = latch). The poll is focus-guarded but not click-aware,
|
||||
// so binding either ALSO fires on every panel click. They are named here
|
||||
// for completeness; the middle and side buttons are the free ones.
|
||||
//
|
||||
{ "MOUSELEFT", VK_LBUTTON }, { "MOUSERIGHT", VK_RBUTTON },
|
||||
{ "MOUSEMIDDLE", VK_MBUTTON },
|
||||
{ "MOUSE4", VK_XBUTTON1 }, { "MOUSE5", VK_XBUTTON2 },
|
||||
{ "MOUSEX1", VK_XBUTTON1 }, { "MOUSEX2", VK_XBUTTON2 },
|
||||
};
|
||||
|
||||
//
|
||||
// A mouse button rather than a keyboard key? (Used where a binding only
|
||||
// makes sense for a real key -- the RGB lamp mirror, which paints keycaps.)
|
||||
//
|
||||
static int
|
||||
IsMouseVirtualKey(int virtualKey)
|
||||
{
|
||||
return (virtualKey == VK_LBUTTON || virtualKey == VK_RBUTTON ||
|
||||
virtualKey == VK_MBUTTON || virtualKey == VK_XBUTTON1 ||
|
||||
virtualKey == VK_XBUTTON2) ? 1 : 0;
|
||||
}
|
||||
|
||||
int
|
||||
BTPadBindingIsMouseKey(int virtualKey)
|
||||
{
|
||||
return IsMouseVirtualKey(virtualKey);
|
||||
}
|
||||
|
||||
static const NamedValue padButtonNames[] =
|
||||
{
|
||||
{ "A", XINPUT_GAMEPAD_A },
|
||||
@@ -202,6 +236,10 @@ static const char *defaultProfileText =
|
||||
"# key <KEYNAME> axis <channel> set <value> jump/detent\n"
|
||||
"# pad <PADBTN> button <addr>\n"
|
||||
"# padaxis <PADAXIS> axis <channel> [invert] [slew <rate>]\n"
|
||||
"# <KEYNAME> may be a MOUSE BUTTON: MOUSEMIDDLE, MOUSE4, MOUSE5 (the two\n"
|
||||
"# side buttons; MOUSEX1/MOUSEX2 are aliases). MOUSELEFT and MOUSERIGHT\n"
|
||||
"# also work but are how you press cockpit buttons -- bind them and they\n"
|
||||
"# fire on every panel click too. Mouse MOVEMENT is not bindable.\n"
|
||||
"# Channels: Throttle JoystickX JoystickY LeftPedal RightPedal Turn\n"
|
||||
"# (Turn = signed composite: + drives the right pedal, - the left --\n"
|
||||
"# made for mapping a gamepad stick to turning)\n"
|
||||
@@ -240,6 +278,10 @@ static const char *defaultProfileText =
|
||||
"key RALT button 0x3F\n"
|
||||
"\n"
|
||||
"# --- the four mappable fire buttons ------------------------------------\n"
|
||||
"# The mouse's side buttons are free if you want them on the trigger:\n"
|
||||
"# key MOUSE4 button 0x40\n"
|
||||
"# key MOUSE5 button 0x46\n"
|
||||
"# key MOUSEMIDDLE button 0x41\n"
|
||||
"key SPACE button 0x40 # main trigger\n"
|
||||
"key NUMPAD0 button 0x40 # main trigger\n"
|
||||
"key NUMPAD1 button 0x45 # pinky\n"
|
||||
|
||||
@@ -230,6 +230,14 @@ PadRIO::PadRIO():
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// A keyboard lamp array has no mouse buttons to paint.
|
||||
{
|
||||
extern int BTPadBindingIsMouseKey(int virtualKey);
|
||||
if (BTPadBindingIsMouseKey(binding.virtualKey))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Logical duplicate = False;
|
||||
for (int j = 0; j < count; ++j)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user