diff --git a/CLAUDE.md b/CLAUDE.md index ede7e2e..1b834f9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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` | --- diff --git a/docs/CONTROLS.html b/docs/CONTROLS.html index 3ef800c..b593689 100644 --- a/docs/CONTROLS.html +++ b/docs/CONTROLS.html @@ -743,6 +743,7 @@ Buttons0x00 – 0x47 No function0x16 0x17 0x19 0x1E 0x1F 0x38–0x3E — the pod never wired these Key namesA–Z · 0–9 · F1–F12 · NUMPAD0–9 · NUMPADDOT · UP DOWN LEFT RIGHT · SPACE · LSHIFT LCTRL LALT · MINUS EQUALS COMMA PERIOD LBRACKET RBRACKET BACKTICK + MouseMOUSE4 · MOUSE5 · MOUSEMIDDLE — buttons only; left/right also press cockpit buttons PadA B X Y · DPAD_UP/DOWN/LEFT/RIGHT · LB RB · BACK START · LS RS Pad axesLX LY RX RY LT RT diff --git a/docs/CONTROLS.md b/docs/CONTROLS.md index 136a058..ece9804 100644 --- a/docs/CONTROLS.md +++ b/docs/CONTROLS.md @@ -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`. diff --git a/docs/MOUSELOOK_PLAN.md b/docs/MOUSELOOK_PLAN.md new file mode 100644 index 0000000..009b8c8 --- /dev/null +++ b/docs/MOUSELOOK_PLAN.md @@ -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 ` 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 axis [invert] [sensitivity ] [spring ] [deadzone ] +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. diff --git a/engine/MUNGA_L4/L4PADBINDINGS.cpp b/engine/MUNGA_L4/L4PADBINDINGS.cpp index 08cf9ce..af68103 100644 --- a/engine/MUNGA_L4/L4PADBINDINGS.cpp +++ b/engine/MUNGA_L4/L4PADBINDINGS.cpp @@ -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 axis set jump/detent\n" "# pad button \n" "# padaxis axis [invert] [slew ]\n" +"# 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" diff --git a/engine/MUNGA_L4/L4PADRIO.cpp b/engine/MUNGA_L4/L4PADRIO.cpp index 13b2c00..40229a0 100644 --- a/engine/MUNGA_L4/L4PADRIO.cpp +++ b/engine/MUNGA_L4/L4PADRIO.cpp @@ -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) {