Merge glass-cockpit-refit: cockpit scaling + one button geometry, the keyboard button board, RGB keylight, crouch, the cwd guard
Cyd's 9-commit branch, reviewed before merge (clean merge-tree, zero overlap with the console/relay work that landed after his fork point; his L4VB16 refit preserves the #48 plane-audit tripwires, and his lamp-decode fix corrects the #47 flash rendering). Four review findings are fixed in the follow-up commit: the stale README controls table, the unguarded backtick view-toggle, the environ.ini one-shot loophole, and volume-key documentation (the -/= -> PgUp/ PgDn move itself landed pre-merge so this branch's Comm-bank -/= bindings are collision-free). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+1062
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,114 @@
|
||||
# BattleTech 4.11 — controls reference
|
||||
|
||||
Two ways to fly: the **keyboard/pad** (the ~25 core actions) and the **cockpit panel**, where
|
||||
every one of the pod's 72 illuminated buttons is mouse-clickable exactly where it sat in the
|
||||
cabinet. Left-click presses; **right-click latches** a button held.
|
||||
|
||||
Rebind keys in `content\bindings.txt` (delete it to regenerate the defaults).
|
||||
Cockpit layout and display sizes live in `content\environ.ini` (likewise self-documenting).
|
||||
|
||||
---
|
||||
|
||||
## Keyboard — the keyboard IS the button board
|
||||
|
||||
The letter and number rows are the MFD button banks, laid out where they sit on the panel.
|
||||
Flight lives on the number pad so the board stays free.
|
||||
|
||||
### Flight — the number pad
|
||||
|
||||
| Key | Action | | Key | Action |
|
||||
|---|---|---|---|---|
|
||||
| NumPad 8 / 2 | torso aim up / down | | NumPad 7 / 9 | left / right pedal |
|
||||
| NumPad 4 / 6 | torso twist left / right | | NumPad 5 | all stop |
|
||||
| NumPad 0, Space | main trigger | | NumPad 1 / 3 / . | pinky / middle / upper |
|
||||
| Shift / Ctrl | throttle up / down (the lever sticks) | | Alt | reverse thrust |
|
||||
| ← ↓ → | look left / behind / right | | ↑ | torso centre |
|
||||
| ` | cockpit / chase camera | | − / = *(see below)* | — |
|
||||
|
||||
**You spawn in BASIC control mode.** In BASIC the stick steers and the pedals do nothing.
|
||||
Press **F7** once for MID, where the pedals steer and the stick twists the torso — the full
|
||||
pod experience. If "turning doesn't work", this is why.
|
||||
|
||||
### The panel — letter and number rows
|
||||
|
||||
```
|
||||
UPPER DISPLAYS Heat / coolant Engineering Comm / hotbox
|
||||
top row 1 2 3 4 | 5 6 7 8 | 9 0 - =
|
||||
bottom row Q W E R | T Y U I | O P [ ]
|
||||
0x2F..0x2C 0x27..0x24 0x37..0x34
|
||||
0x2B..0x28 0x23..0x20 0x33..0x30
|
||||
|
||||
LOWER DISPLAYS Left Weapons Right Weapons
|
||||
top row A S D F (G unbound) H J K L
|
||||
bottom row Z X C V (B unbound) N M , .
|
||||
0x0F..0x0C 0x07..0x04
|
||||
0x0B..0x08 0x03..0x00
|
||||
|
||||
COLUMNS beside the map
|
||||
left F1 zoom+ F2 zoom- F3 thermal F4 CROUCH F5 lamp F6 display
|
||||
right F7 control mode F8 - F9..F12 Generators A-D
|
||||
```
|
||||
|
||||
G and B are deliberately unbound: they are the physical gap between the lower clusters.
|
||||
|
||||
**1 2 3 Q W E are the coolant valves** and the detents run 1 - 5 - 50 (max) - **0 (CLOSED)** -
|
||||
back to 1. One press past max shuts that loop off and everything on it overheats; boosting one
|
||||
loop starves the others, since the supply is shared. **4** is the flush (hold it) and **R**
|
||||
re-balances every valve.
|
||||
|
||||
The weapon and engineering displays are **page-gated**: on the Quad page the outer buttons jump
|
||||
to a populated Eng page; on an Eng page they drive generator select A-D, bus mode, coolant
|
||||
toggle, trigger configure, and **eject** (tap to clear a jam, hold ~3 s to jettison a burning
|
||||
ammo bay before it detonates).
|
||||
|
||||
### What a bound key gives up
|
||||
|
||||
Binding a key removes it from the authentic 1995 typed-hotkey channel so it cannot
|
||||
double-dispatch. This board binds nearly everything, so those hotkeys are given up by design:
|
||||
`5` = MFD1 Quad page, `z` = MFD3 Eng1, `t/y/u/i/o` = pilot select, `+`/`-` = target zoom.
|
||||
Unbind a key in `bindings.txt` to get its 1995 meaning back. Same rule for the built-ins:
|
||||
**`** is the view toggle (V is a board button now), and J/K/L cycle the MFD preset pages only
|
||||
while unbound.
|
||||
|
||||
Addresses with no function at all — the pod never wired them — are `0x16` `0x17` `0x19`
|
||||
`0x1E` `0x1F` and `0x38`-`0x3E`.
|
||||
|
||||
## Controllers
|
||||
|
||||
An **Xbox controller** works out of the box.
|
||||
|
||||
**Flight sticks, HOTAS and pedals:** run `joyconfig.bat` once. A wizard asks you to move each
|
||||
control in turn — stick, twist, throttle, fire buttons — writes the bindings, and drops you into
|
||||
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`.
|
||||
|
||||
---
|
||||
|
||||
## Cockpit layout
|
||||
|
||||
Set these in `content\environ.ini`:
|
||||
|
||||
- **`BT_MFD_SCALE`** (and `_UL`/`_UC`/`_UR`/`_LL`/`_LR`) — size of the five MFDs, as a percentage
|
||||
of their pod size. Turn them up if you want to actually read them while you fly.
|
||||
- **`BT_RADAR_SCALE`**, **`BT_RADAR_POS`** — size the map, and move it out of the middle of the
|
||||
road: `CENTER`, `LEFT`, `RIGHT`, `MIDLEFT`, `MIDRIGHT`.
|
||||
- **`BT_GLASS_PANELS=1`** — break every display out into its own desktop window instead of the
|
||||
composited cockpit. Good for reading one display at full size.
|
||||
|
||||
Run with **`-fit`** for a borderless window over the whole monitor. The cockpit scales to any
|
||||
window shape at one uniform scale, so nothing ever stretches — a window that isn't the cockpit's
|
||||
shape just gets black bars.
|
||||
@@ -0,0 +1,162 @@
|
||||
# DIST_LAYOUT_PLAN — proposal: ship the authentic single-folder layout
|
||||
|
||||
**Status: PROPOSED, NOT IMPLEMENTED.** Written 2026-07-26 for discussion between the authors.
|
||||
Nothing in this document has been built. The seatbelt described in §7 *has* shipped (4.11.560);
|
||||
this proposal is about removing the need for it.
|
||||
|
||||
---
|
||||
|
||||
## 1. What prompted this
|
||||
|
||||
Field report, 4.11.559:
|
||||
|
||||
```
|
||||
PS C:\Games\BT411_4.11.559\build\Release> .\btl4.exe -fit
|
||||
```
|
||||
|
||||
→ the mission console opens, then the game **crashes on Launch**, and the player's
|
||||
`environ.ini` / `bindings.txt` appear to have never been created.
|
||||
|
||||
Root cause: the engine resolves `BTL4.RES`, `VIDEO\`, `BTDPL.INI`, the eggs **and** both player
|
||||
config files relative to **cwd**. Launched from `build\Release`, it found no resources
|
||||
(`Resource file btl4.res v1.0.0.0 is obsolete!`), wrote stray copies of `bindings.txt` /
|
||||
`environ.ini` / `btl4.log` next to the exe, and killed the mission generation the menu launched
|
||||
(the child inherits the parent's cwd).
|
||||
|
||||
The observation that matters more than the bug: **RP411, RP412 and the 1995 pod cannot hit this,
|
||||
because they ship one folder.**
|
||||
|
||||
## 2. Evidence that one folder is the authentic shape
|
||||
|
||||
- **The 1995 pod.** `content\` in this repo *is* an original pod game directory:
|
||||
`BTL4OPT.EXE`, `BTL4.RES`, `SETENV.BAT`, `32RTM.EXE`, `DPMI32VM.OVL`, `VIDEO\`, `GAUGE\`,
|
||||
`AUDIO\` all sit together. The exe lived beside its resources. [T1]
|
||||
- **RP411 / RP412.** `pack-dist.ps1` copies `rpl4opt.exe`, `AUDIO/`, `GAUGE/`, `VIDEO/`,
|
||||
`RPL4.RES`, `RPDPL.INI`, the eggs, the DLLs, `environ.ini` and `bindings.txt` into ONE dist
|
||||
root. A dist listing confirms it. [T1]
|
||||
- **BT411.** `mkdist.py` zips **tracked repo paths verbatim** (`git ls-files content/` written as
|
||||
`<name>/content/...`, the exe as `<name>/build/Release/btl4.exe`). The player therefore receives
|
||||
the *developer tree*. [T1]
|
||||
|
||||
So the split is not a decision anyone made about how the game should be installed — it is an
|
||||
artifact of how the packer was written. In the authentic shape cwd is correct by construction and
|
||||
a double-click just works.
|
||||
|
||||
## 3. Proposed dist tree
|
||||
|
||||
```
|
||||
BT411_4.11.NNN/
|
||||
btl4.exe <- was build/Release/btl4.exe
|
||||
OpenAL32.dll d3dx9_43.dll msvcp140.dll vcruntime140.dll steam_api.dll
|
||||
BTL4.RES BTDPL.INI CONTROLS.MAP *.EGG ... <- was content/*
|
||||
AUDIO/ GAUGE/ VIDEO/ <- was content/*/
|
||||
play_solo.bat join.bat join_lan.bat joyconfig.bat play_steam.bat
|
||||
README.txt CONTROLS.txt
|
||||
(bindings.txt, environ.ini, volume.cfg, *.log appear here on first run)
|
||||
```
|
||||
|
||||
Identical in shape to the pod's own directory and to RP's dist.
|
||||
|
||||
## 4. What changes
|
||||
|
||||
| File | Change | Size |
|
||||
|---|---|---|
|
||||
| `tools/mkdist.py` | rewrite archive paths: `content/X` → `X`, `build/Release/btl4.exe` → `btl4.exe`, DLLs → root. The redist/PDB/steam logic is unaffected. | ~15 lines |
|
||||
| `players/*.bat` (5) | drop `cd content`; drop the `build\Release\` prefix; the `badpath` guard tests `BTL4.RES` instead of `content\OPERATOR.EGG` | ~4 lines each |
|
||||
| `players/README.txt` | "next to the content and build folders" → "in the game folder"; upgrade section (see §6) | ~10 lines |
|
||||
| `docs/CONTROLS.md` | `content\bindings.txt` → `bindings.txt` (3 mentions) | trivial |
|
||||
| game code | **none required.** `BTEnsureContentDirectory` already probes the exe's own directory, so a single-folder install satisfies it on the first candidate. Log strings that say `content\environ.ini` become slightly wrong — cosmetic, worth a sweep. | ~5 strings |
|
||||
|
||||
**The repo layout does NOT change.** `build/` is generated and `content/` stays where it is; only
|
||||
the packer's output shape moves. Developers keep running `run\run.cmd` exactly as now.
|
||||
|
||||
## 5. What it buys
|
||||
|
||||
- The reported crash class disappears at the source rather than being caught by a guard.
|
||||
- A double-clicked `btl4.exe` works — which is what a player tries first, and what the menu
|
||||
front end was built to support.
|
||||
- `bindings.txt` / `environ.ini` / `volume.cfg` / `btl4.log` land where the player can see them,
|
||||
next to the game, instead of one folder down.
|
||||
- One fewer way to run a **stale exe**: today `build\Release\btl4.exe` can survive an upgrade and
|
||||
be launched by an old shortcut. (That has already cost us one debugging session — see
|
||||
`docs/GLASS_COCKPIT.md` §2026-07-20 "STALE EXE, not code".)
|
||||
- We stop shipping a directory named `build\` to players, which invites exactly the "run the exe
|
||||
directly" instinct that caused this.
|
||||
|
||||
## 6. The hard part — upgrades ⚠
|
||||
|
||||
The README tells players to extract the new zip **over** the existing folder and overwrite, so
|
||||
their `bindings.txt` and `volume.cfg` survive. A layout change collides with that:
|
||||
|
||||
| Hazard | Effect |
|
||||
|---|---|
|
||||
| Player's `content\bindings.txt` stays in the now-unused `content\` | Their key/joystick setup is silently ignored — and still visible on disk, so it looks like the game lost it |
|
||||
| Old `build\Release\btl4.exe` remains | A shortcut or habit runs a **stale build** |
|
||||
| Old `content\` tree remains | ~130 MB duplicated, and two copies of every asset |
|
||||
|
||||
**But note the wrinkle worth checking first:** the zip root is *versioned*
|
||||
(`BT411_4.11.560/…`), so extracting into `C:\Games\` produces a **new sibling folder**, not an
|
||||
overlay — the reporter's own install is `C:\Games\BT411_4.11.559\`. The documented
|
||||
extract-over-top only works if the player extracts the *inner folder's contents* onto the old
|
||||
install, which is a fiddly manual step. **If in practice every version already lands in its own
|
||||
folder, then bindings.txt already does not carry over today**, and the migration cost of this
|
||||
change is close to zero. That should be confirmed before weighing the options below.
|
||||
|
||||
### Migration options
|
||||
|
||||
- **A. Versioned folder, no migration.** Rely on each version extracting fresh; README tells
|
||||
players to copy `bindings.txt` (+ `volume.cfg`, `environ.ini`) from the old folder's `content\`
|
||||
into the new folder's root. Simple, honest, one paragraph of README — but a manual step, and
|
||||
wrong if anyone really is overlaying.
|
||||
- **B. One-shot in-game migration.** On boot, if `<exe dir>\content\bindings.txt` exists and
|
||||
`<exe dir>\bindings.txt` does not, copy the player files up and log it loudly. ~30 lines,
|
||||
covers the overlay case invisibly. Does not solve the stale exe or the duplicated tree.
|
||||
- **C. B + a loud stale-install warning.** Additionally, if `<exe dir>\build\Release\btl4.exe`
|
||||
exists, log a warning and show it once in the front end ("an old copy of the game is still in
|
||||
this folder — delete build\ and content\"). Most protective, most code.
|
||||
- **D. Stable zip root** (`BT411/` instead of `BT411_<version>/`) so extract-over-top genuinely
|
||||
works, combined with B. Fixes the upgrade story properly but changes how testers file reports
|
||||
(the folder no longer names the version — though the title bar and log head still do).
|
||||
|
||||
**Recommendation: A if §6's wrinkle confirms versioned folders are the reality, else B+C.**
|
||||
Either way the change is small; the risk is entirely in the migration, not in the layout.
|
||||
|
||||
## 7. Relationship to the shipped seatbelt (4.11.560)
|
||||
|
||||
`BTEnsureContentDirectory` (btl4main.cpp) already makes both layouts work: if cwd has no
|
||||
`BTL4.RES` it probes `..\..\content`, `..\content`, `content`, and the exe dir itself. **It stays
|
||||
either way** — it costs nothing, it protects the developer tree (where the split is real and
|
||||
permanent), and it means this proposal can be adopted or rejected without any risk of regression.
|
||||
If the layout changes, the first probe candidate simply becomes the hit.
|
||||
|
||||
## 8. Verification plan (if adopted)
|
||||
|
||||
1. `mkdist.py` → extract clean → **double-click `btl4.exe`** (no bat, no cwd help): menu opens,
|
||||
Launch runs a mission, `bindings.txt`/`environ.ini`/`btl4.log` appear at the root.
|
||||
2. Each of the five bats from a clean extract.
|
||||
3. `btl4.exe -fit` bare, and from a shell sitting anywhere else on disk.
|
||||
4. 2-node LAN via `join_lan.bat` (path assumptions in the join path are the least exercised).
|
||||
5. Migration path per the chosen option: install 4.11.559 old-shape, customize `bindings.txt`,
|
||||
upgrade, confirm the setup survives or that the README's step recovers it.
|
||||
6. Pod profile boot (`SETENV.BAT` now sits at the root — confirm the cabinet contract in
|
||||
`context/glass-cockpit.md` §ONE unified build still reads correctly).
|
||||
|
||||
## 9. Open questions for the other author
|
||||
|
||||
1. Do we actually have players *overlaying* installs, or does everyone get a fresh folder per
|
||||
version? This decides §6 entirely.
|
||||
2. Do the `.bat` launchers survive at all in a single-folder world? They still earn their keep
|
||||
(launch forensics `#41`, the handoff wait, `BT_FE_SOLO`, `BT_LOG` naming) — but a player who
|
||||
can now just double-click the exe may never touch them.
|
||||
3. Should `environ.ini` / `bindings.txt` be **shipped** in the zip rather than written on first
|
||||
run? RP ships both. We deliberately do not, so extract-over-top cannot clobber a player's
|
||||
settings — but that reasoning weakens under option A/D.
|
||||
4. Does the real pod cabinet care? Its `SETENV.BAT` + `BT_PLATFORM=pod` contract is
|
||||
path-independent, but the pod's own install shape should be confirmed against Nick's notes
|
||||
before we call the single folder "authentic" for the cabinet as well as the 1995 build.
|
||||
|
||||
## Key Relationships
|
||||
|
||||
- Caused by: `context/build-and-run.md` §cwd guard (the landmine + why BT411 differs)
|
||||
- Touches: `tools/mkdist.py`, `players/*.bat`, `players/README.txt`, `docs/CONTROLS.md`
|
||||
- Prior art: `C:\VWE\RP412\pack-dist.ps1` (single-folder dist), `content\BTL4OPT.EXE` (the pod's own)
|
||||
@@ -843,9 +843,13 @@ below (view bottom clean). Buttons: the full rect is the click target, the surfa
|
||||
only the LAMP edge shows (PaintGlass painter trick). Address banks = L4GLASSWIN
|
||||
BuildMfd/BuildRadar/BuildFlight.
|
||||
|
||||
**Gating precedence** (resolved once → `gBTGaugeCockpit`): `BTGlassPanelsActive` (BT_GLASS_PANELS)
|
||||
> `BT_COCKPIT=1` > `BT_DEV_GAUGES_WINDOW` / `BT_DEV_GAUGES_DOCK` > **cockpit default**. `BT_COCKPIT=0`
|
||||
= dock-bottom. `DevGaugeDocked()` includes cockpit so the separate window stands down. Green tint
|
||||
**Gating precedence** (2026-07-26: resolved ONCE in btl4main → `glassLayout` → `gBTGaugeCockpit`):
|
||||
`BTGlassPanelsActive` (BT_GLASS_PANELS) > `BT_DEV_GAUGES_WINDOW` > `BT_DEV_GAUGES_DOCK` >
|
||||
`BT_COCKPIT=0` > **cockpit surround default**. `BT_COCKPIT=0` = dock-bottom — ⚠ which it did NOT
|
||||
do before that date: the glass profile block converted it to `BT_GLASS_PANELS=1`, so the dock
|
||||
strip was unreachable under glass. Dock/window modes now auto-raise `BT_PAD_PANEL` (those layouts
|
||||
carry no buttons of their own).
|
||||
`DevGaugeDocked()` includes cockpit so the separate window stands down. Green tint
|
||||
`BT_COCKPIT_TINT=RRGGBB` (default 0x27E8 ≈ rgb(33,255,66)); radar stays palette amber.
|
||||
|
||||
**Pod-build seam.** Panels + dim lamps render in ALL builds; only `PadRIO::SetScreenButton/
|
||||
|
||||
+296
-3
@@ -385,9 +385,10 @@ on the pod monitor. This reuses the plasma-window CPU→window pattern and sides
|
||||
D3D dev-composite bug family (additional-swap-chain lifetime, device-state save/restore,
|
||||
depth-stencil mismatch, layered strobing).
|
||||
|
||||
**Gate: `BT_GLASS_PANELS`** (runtime env; `BTGlassPanelsActive()`). Default ON under
|
||||
`-platform glass` (preset putenv in `btl4main.cpp`); `=0` restores the legacy single pad panel
|
||||
+ docked gauge strip. Wired at three seams:
|
||||
**Gate: `BT_GLASS_PANELS`** (runtime env; `BTGlassPanelsActive()`). ⚠ **Was** default-ON under
|
||||
`-platform glass` (preset putenv in `btl4main.cpp`) — as of 2026-07-26 the COCKPIT SURROUND is the
|
||||
glass default and this gate is **explicit-only**; the one resolver + the full precedence table are
|
||||
in `context/glass-cockpit.md` §Layout modes. Wired at three seams:
|
||||
- `L4PADRIO` ctor: `BTGlassPanelsActive()` → `BTGlassPanels_Create()` instead of
|
||||
`BTPadPanel_Create()`; dtor destroys both (safe no-ops).
|
||||
- `L4VB16`: `BTDrawGaugeInset` / `BTGaugeWindowRenderAndPresent` early-return when
|
||||
@@ -439,3 +440,295 @@ Verified after the fix (`BT_GLASS_LOG`): `[glass] 'Heat MFD' port=Heat mask=0x40
|
||||
enable=2 ow=640 oh=480 nonzero=22227/307200`, `[glass] 'Secondary / Radar' port=sec mask=0x3f
|
||||
pal=1 enable=3 ow=480 oh=640 nonzero=26206/307200` — surfaces resolve and carry live pixels.
|
||||
`BT_GLASS_LOG` left in as a permanent per-surface resolve/pixel diagnostic (throttled).
|
||||
|
||||
## 2026-07-26 — Layout modes get ONE resolver; button geometry gets ONE owner (L4RIOBANK)
|
||||
|
||||
The RP412 replication pass, step 1 of the agreed plan (port the geometry, keep our renderers).
|
||||
|
||||
### The mode resolver (btl4main.cpp)
|
||||
Where the secondary displays go was decided in TWO places with duplicated precedence -- the glass
|
||||
profile block picked `BT_PAD_PANEL`/`BT_GLASS_PANELS`, the window-sizing block re-derived
|
||||
cockpit-vs-dock -- and the boot banner read NEITHER, announcing "per-display cockpit windows
|
||||
[BT_GLASS_PANELS]" for every glass boot including the surround default. The split had also
|
||||
broken `BT_COCKPIT=0`: documented as the dock-bottom opt-out, the profile block converted it to
|
||||
`BT_GLASS_PANELS=1`, so **the docked strip was unreachable under the glass profile**.
|
||||
|
||||
Now: one `glassLayout` resolved after the profile putenvs land, consumed by the banner, the
|
||||
pad-panel decision and the sizing block. Precedence `BT_GLASS_PANELS` > `BT_DEV_GAUGES_WINDOW` >
|
||||
`BT_DEV_GAUGES_DOCK` > `BT_COCKPIT=0` > surround default; `BT_GLASS_PANELS` is explicit-only now
|
||||
(it was auto-set); dock/window auto-raise `BT_PAD_PANEL` so the 72-button field always has a home.
|
||||
Banner: `[boot] platform profile: GLASS (PadRIO + plasma window) [secondary displays: <mode>]`.
|
||||
Verified all four modes boot to the mode they name, plus pod/dev.
|
||||
|
||||
### L4RIOBANK -- the shared field
|
||||
`engine/MUNGA_L4/L4RIOBANK.cpp/.h` (new TU). Both renderers were carrying their own copy of the
|
||||
button geometry and had drifted: an MFD button was 156x138 reaching under the glass in the
|
||||
exploded window, and a **76x24 sliver entirely OUTSIDE the glass** in the surround. One module
|
||||
now owns it, both are consumers, placement stays per-renderer.
|
||||
|
||||
The under-glass rule, from RP412 `L4MFDVIEW` (which took it from the pod): reach half the glass
|
||||
in behind the display, leave a lamp strip clearing the edge, paint buttons first and imagery
|
||||
over. Strip scales off the display's SHORT axis vs the native 480 (MFD 640x480 landscape, map
|
||||
480x640 portrait) with a 6px floor. The MFD per-column nudge against the DISPLAY/PROGRAM legends
|
||||
moved here so both renderers inherit one alignment. The map's foot row is pushed FIRST because
|
||||
the side columns now cover that band and the hit test takes the first match.
|
||||
|
||||
Retired: `L4GLASSWIN`'s `PlaceCellAt`/`PlaceLine`/`PlaceRect` and its `RedCellH`/`EdgeProtrude`/
|
||||
`RailW`/`RailSlotAdd`/`RailGap`/`RedOffsetX`/`kRedColDX` constants; the surround's inline MFD/
|
||||
radar/flight button loops. `kCkRedH`/`kCkLamp` survive only as band reserves for the canvas math.
|
||||
|
||||
### Lamp flash decode [T1 -> fixed]
|
||||
`BTLampBrightnessOf` returned `max(state1,state2)` and blanked on the alternate phase. Per the
|
||||
engine's own `RIO::LampState` enum [T0] solid shows state 1 and flashing ALTERNATES state 1 and
|
||||
state 2. Agrees only when one state is Off -- true for Panic (`flashFast+state1Off+state2Bright`)
|
||||
which is why it survived -- but `L4LAMP.cpp:252` commands `flashFast+state1Dim+state2Bright`, a
|
||||
dim->bright pulse that rendered as bright->off. Three copies existed (l4vb16.h, L4GLASSWIN,
|
||||
L4PADPANEL), all three wrong; the two locals now forward to the one fixed inline.
|
||||
|
||||
### Verification harness (new, reusable)
|
||||
- `BT_RIOBANK_LOG=1` -- one-shot dump of every bank's buttons + bounds (capped at 16 banks so the
|
||||
per-frame surround caller prints one pass).
|
||||
- `scratchpad/checkbank.py <log>` -- per-bank census + **first-hit reachability**: an address
|
||||
whose rect is entirely covered by earlier buttons in its bank is dead however big it looks, and
|
||||
the overlapping under-glass banks make that a live hazard. Reports duplicates and shadowing.
|
||||
- `scratchpad/clickbank.py <log> <title> --mode surround|exploded` -- posts a real
|
||||
WM_LBUTTONDOWN/UP at every button's centre; the game's own CLICK forensics prove dispatch.
|
||||
|
||||
Result both modes: **72/72 placed, 0 shadowed, 0 duplicated, 72/72 dispatched**, process survived
|
||||
144 posted messages each. Pod + dev profiles boot and simulate un-regressed. Surround `BT_SHOT`
|
||||
confirms the legends are clear and the lamps read as edge strips; exploded `PrintWindow` captures
|
||||
confirm surfaces still land under their banks.
|
||||
|
||||
## 2026-07-26 (later) — Phase 2: the cockpit scales (uniform-fit letterbox + -fit)
|
||||
|
||||
RP412 replication step 2. The canvas is a FIXED size and D3D9 stretched it into the client, so a
|
||||
window dragged to a different shape squashed the instruments -- the projection was aspect-corrected
|
||||
back in task #20, but the panels, lamps and MFD glass never were. Now: one uniform scale, centred,
|
||||
leftover black.
|
||||
|
||||
**Mechanism.** `BTCockpitFitRect()` (L4VB16.cpp) computes the centred uniform-scale rect in integer
|
||||
math -- a float scale wobbles by a pixel between frames and the bars shimmer during a drag. D3D9
|
||||
applies it as a Present **destination rect**, which `D3DSWAPEFFECT_DISCARD` forbids, so the
|
||||
WINDOWED swap effect becomes `D3DSWAPEFFECT_COPY` when the surround is up and multisampling is off
|
||||
(COPY cannot multisample). `gBTCockpitLetterbox` records the outcome; 0 = the old full-client
|
||||
stretch, which is what every non-cockpit mode and any MSAA run still gets.
|
||||
|
||||
Wired at five seams:
|
||||
- `L4VIDEO` device setup: swap effect + confirm/withdraw the flag.
|
||||
- Both Present sites: the scene present AND the wait-screen overlay present (it paints into the
|
||||
same backbuffer, so it has to land in the same rect).
|
||||
- `btl4main` WM_SIZE: `InvalidateRect` -- a COPY present only writes its dest rect, so stale bar
|
||||
content persists without an erase (the class brush is black).
|
||||
- `BTCockpitMouseDown`: client->canvas through the SAME fit. Mapping against the full client
|
||||
drifts the hit test off every button by the bar width; a click on a bar now hits nothing.
|
||||
- `BTWorldAspectOf`: under the letterbox the view's on-screen aspect is its OWN -- the client
|
||||
drops out of the formula entirely.
|
||||
|
||||
**`-fit` / `-windowed-fullscreen`** (btl4main): borderless `WS_POPUP` over the monitor rect, canvas
|
||||
letterboxed inside. Unknown tokens fall through `L4Application::ParseCommandLine`'s ladder to
|
||||
`return True`, so no arg-parser change was needed.
|
||||
|
||||
### Ordering trap (found live, fixed)
|
||||
The letterbox flag was originally set at device creation -- but **the first WM_SIZE beats the
|
||||
device**. A `-fit` boot on the 3440x1440 ultrawide logged `[resize] client 3440x1440 aspect=3.14`
|
||||
(the stretch formula), and since nothing resizes the window again, the per-frame
|
||||
`gWindowAspect != appliedAspect` check applied it on frame 1. The INTENT is now decided in
|
||||
btl4main alongside `gBTGaugeCockpit` (windowed cockpit + MULTISAMPLE unset/0); L4VIDEO only
|
||||
confirms it or withdraws it. Re-verified: the boot line reads `aspect=1.8`.
|
||||
|
||||
### Capture note (for whoever verifies this next)
|
||||
`PrintWindow` returns an all-black frame for the borderless `-fit` window, while working fine for
|
||||
the normal chrome'd window. Use a screen-region grab (`scratchpad/shot.ps1`) for `-fit`. The
|
||||
all-black PrintWindow is NOT a render failure -- the log showed 18 sim ticks and the screen grab
|
||||
showed the full cockpit.
|
||||
|
||||
### Verified
|
||||
- Wide drag 1684x661 and tall drag 744x961: canvas uniformly scaled and centred, bars on the long
|
||||
axis, ZERO distortion (captures `fit_wide.png` / `fit_tall.png`).
|
||||
- `-fit` on 3440x1440: borderless, centred, bars left/right, undistorted.
|
||||
- **72/72 buttons still dispatch after a resize** -- clickbank.py now maps canvas->client through
|
||||
its own copy of the fit (an independent check that the two transforms agree): at fit 906x661
|
||||
with a 389px left bar, all 72 `[cockpit] CLICK` lines land.
|
||||
- exploded / dock / pod / dev: DISCARD retained, boot + simulate un-regressed.
|
||||
|
||||
## 2026-07-26 (later still) — Phase 3: player-tunable displays + the settings file
|
||||
|
||||
RP412 replication step 3.
|
||||
|
||||
### Scaling (BTCkResolvedSizes, L4VB16.cpp)
|
||||
`BT_MFD_SCALE` (all five), `BT_MFD_SCALE_UL/_UC/_UR/_LL/_LR` (per display, overriding the group),
|
||||
`BT_RADAR_SCALE` -- percentages of the pod size, 25-200, resolved ONCE (the surround recomputes its
|
||||
layout every frame, so reading the env per frame would be silly).
|
||||
|
||||
The load-bearing consequence: **the surround BANDS derive from the resolved sizes**. That is why
|
||||
the sizes could not stay compile-time constants -- the band a display hangs in has to grow with it
|
||||
or the canvas clips it. Each band is what its largest occupant needs; a corner MFD only claims
|
||||
`size - kCkOVL` because it overlaps the view; the bottom band drops to just the lower MFDs when the
|
||||
map leaves that row for a side. Verified 100% reproduces the historical `L276 R276 T223 B336`
|
||||
byte-for-byte, 150% gives `L436 R436 T343 B448`, 80%+MIDLEFT gives `L256 R212 T175 B148`.
|
||||
|
||||
The flight label blocks now hang off their OWN lower MFD's scaled height + lamp strip, instead of
|
||||
the constant -- a shrunken MFD used to leave the block floating in the gap and an enlarged one to
|
||||
overlap it.
|
||||
|
||||
### Map placement (BT_RADAR_POS)
|
||||
CENTER (default) / LEFT / RIGHT / MIDLEFT / MIDRIGHT, with the BOTTOM*/`*CENTER` spellings RP
|
||||
accepts. Bottom corner: the map goes flush to the canvas edge and the lower MFD whose corner it
|
||||
took slides beside it. Halfway up a side: it leaves the bottom row entirely and the bottom band
|
||||
shrinks accordingly.
|
||||
|
||||
⚠ **First cut was wrong and the capture caught it:** the corner map was placed relative to the VIEW
|
||||
edge, which overlapped the neighbouring MFD by the band width -- 232px of map drawn over the Right
|
||||
Weapons display. Corner placement is measured off the CANVAS edge.
|
||||
|
||||
### The map legend grid -- measured, not inherited
|
||||
The side columns have to line up with the six cells the map imagery paints beside them. Zooming a
|
||||
native Secondary/Radar capture shows those cells are exactly our address map: MAP+ 0x10, MAP- 0x11,
|
||||
IR 0x12 (ThermalSight lamp), crouch 0x13 (DuckRequest), searchlight 0x14, display-mode 0x15 -- the
|
||||
art independently corroborating [[pod-hardware]].
|
||||
|
||||
`scratchpad/measurelegend.py` scans the capture for the legend's vertical rule and for our lamp
|
||||
bars, and reports both. Ours: **top 3, cell 102, pitch 107** of the 640 span. RP412's map:
|
||||
`13 + 6x102 @ 105`. Same cell height, different top and pitch -- confirming the deferral call in
|
||||
Phase 1 was right, the numbers do NOT transfer. Our old even division (`displayH/6 + 1`) had the
|
||||
pitch right by luck and sat 3px high of the labels; the grid is now pinned to the measurement, with
|
||||
top and bottom scaled SEPARATELY and subtracted (RP's trick) so rounding cannot walk the buttons
|
||||
out of step with the labels down a scaled column.
|
||||
|
||||
### environ.ini
|
||||
Two defects, both fixed:
|
||||
1. **Read far too late.** It sat ~300 lines into WinMain, after the platform-profile block had
|
||||
already run its getenv()s -- so every setting the profile reads (BT_PLATFORM, BT_COCKPIT,
|
||||
BT_GLASS_PANELS, BT_DEV_GAUGES, L4CONTROLS...) was silently ignored FROM THE FILE and only ever
|
||||
worked as a real environment variable. Now loaded immediately after the first-breath boot line.
|
||||
2. **putenv()'d comments verbatim**, so a commented-out option would have become an environment
|
||||
variable literally named `#BT_MFD_SCALE`. Comments (`#`, `;`) and blanks are skipped, and lines
|
||||
with no `=` are counted and ignored.
|
||||
|
||||
Added: the real environment WINS over the file (a launcher .bat overrides rather than fights), and
|
||||
a fully documented default is written on first run when the file is absent -- the bindings.txt
|
||||
convention (untracked via .gitignore, so extract-over-top never clobbers a player's settings; every
|
||||
option ships commented out so a fresh install behaves exactly as before).
|
||||
|
||||
Verified live, all three paths: first run writes 3886 bytes and applies `0 setting(s)`; uncommenting
|
||||
`BT_MFD_SCALE=130` + `BT_MFD_SCALE_UC=60` + `BT_RADAR_POS=MIDRIGHT` in the FILE drives the layout
|
||||
(`displays UL 130% UC 60% ... radar on the right side, centred`); and `set BT_MFD_SCALE=90` beats
|
||||
the file's 130 while the file's other two settings still apply.
|
||||
|
||||
### Verified
|
||||
- 72/72 placed, 0 shadowed, 72/72 dispatched at baseline, at 150%/135%, and at 75% + BOTTOMRIGHT.
|
||||
- exploded / dock / pod / dev boot and simulate un-regressed.
|
||||
- Captures: p3_big2 (150%), p3_radleft2 (bottom-left + slid MFD), p3_radmidl (side-mounted map at
|
||||
80%), p3_fromini (settings taken from the file).
|
||||
|
||||
## 2026-07-26 (final) — Phases 4-6: the dead-button backlog, the lamp mirror, the shipped doc
|
||||
|
||||
### Phase 4 — Mech::DuckRequest, and a census that lied in both directions
|
||||
The 2026-07-20 audit listed 8 buttons with no reconstructed handler. **Only ONE was still
|
||||
missing.** Generator on/off (`powersub.cpp`), ToggleSeekVoltage (`emitter.cpp`), EjectAmmo
|
||||
(`projweap.cpp`), ToggleCooling (`heatfamily_reslice.cpp`) and BalanceCoolant (`mech.cpp`) had all
|
||||
landed between 07-20 and 07-25 while `pod-hardware.md` and `open-questions.md` still called them
|
||||
dead. `docs/INPUT_PATH_AUDIT.md:190` had already flagged this ("census stale in both directions")
|
||||
and was right. Rule for next time: **grep the reconstruction, not the prose.**
|
||||
|
||||
`Mech::DuckRequest` @0049fa00 (id 0x1a, RIO 0x13 -- the manual's CROUCH button). Binary body, in
|
||||
full:
|
||||
|
||||
if (0 < *(int *)(param_2 + 0xc)) { *(undefined4 *)(param_1 + 0x398) = 1; }
|
||||
|
||||
Press-only; sets `duckState` (`mech+0x398`, attribute 0x37). A one-shot REQUEST flag -- the
|
||||
handler never clears it and the only other writer in the entire binary is the mech reset
|
||||
(part_012.c:9439, the same reset that zeroes `incomingLock`, which is how that region was already
|
||||
mapped). Registered in `Mech::MessageHandlerEntries`; `[duck]` log on press.
|
||||
|
||||
**Why the flag has no reader, and why that is correct.** `duckState` has ZERO readers anywhere in
|
||||
the decomp. It is published as an ATTRIBUTE, so its consumer is a databinding:
|
||||
`content/GAUGE/L4GAUGE.CFG` runs `oneOfSeveralPixInt(E, ModeAlwaysActive, bduck.pcc, 3, 1,
|
||||
DuckState)` -- "crouch mode: button 4" on the map's legend column. Verified live by capturing the
|
||||
Secondary/Radar window before and after a 0x13 press: the crouch icon goes grey -> orange. So the
|
||||
handler is COMPLETE; a crouch pose invented here would be a stand-in for data we have not found
|
||||
(no SQUAT clip name survives in the decomp or in content/, only DuckServo01.wav in AUDIO1.RES).
|
||||
|
||||
Bonus: those gauge widgets sit at offsets 537/430/322/215/108 -- a **107 pitch**, independently
|
||||
corroborating the map legend grid measured from pixels in Phase 3.
|
||||
|
||||
Still open from that census: MechRIOMapper's own Keypress @004d2514 (id 0x19).
|
||||
|
||||
### Phase 5 — L4KEYLIGHT (RGB keyboard lamp mirror)
|
||||
Ported from RP412 (itself a port of vRIO's KeyboardLampMirror). Keys bound to a lamp address in
|
||||
bindings.txt glow with the panel palette -- yellow for the map's side columns (0x10-0x1F), red for
|
||||
the rest -- flashing in step with the on-screen buttons (the LampLevel copy matches the FIXED
|
||||
BTLampBrightnessOf from Phase 1). Per-key boards light each bound key; zone-lit boards mirror the
|
||||
strongest lamp board-wide. All WinRT runs on a private worker thread (watcher, claim, 100 ms paint
|
||||
loop, repaint only on change).
|
||||
|
||||
**The packing hazard did NOT transfer.** RP412 compiles this file with forced default struct
|
||||
packing because its engine is `/Zp1`, which would break the WinRT ABI. BT411's `BT_OPTS` is
|
||||
`/permissive /W0 /wd4996 /EHsc /bigobj /MP` -- no `/Zp` -- so only the dialect flags are needed:
|
||||
`set_source_files_properties(... COMPILE_OPTIONS "/std:c++17;/permissive-")`, since the project
|
||||
otherwise builds C++14 /permissive. The scalars-only interface is kept regardless.
|
||||
|
||||
Wired in `PadRIO`: map built from `bindings.keyBindings` (ActionButton binds only, first binding
|
||||
per key wins, addresses < LampCount), fed from `PadRIO::SetLamp`, stopped in the dtor (which hands
|
||||
the LEDs back to Windows). Gate `BT_KEYLIGHT=0`.
|
||||
|
||||
Verified live: `[keylight] mirroring 25 bound key(s)` + `+ LAPTOP-767CF84B (24 zones - board-wide
|
||||
mirror)` -- it claimed this machine's real keyboard. `BT_KEYLIGHT=0` -> zero keylight lines, game
|
||||
fine. Pod profile (no PadRIO) -> zero lines. Exploded -> works.
|
||||
|
||||
### Phase 6 — the shipped controls reference
|
||||
`docs/CONTROLS.md`: the keyboard table plus **the full 72-button pod map**, grouped by the display
|
||||
each bank surrounds, with the coolant-valve detent warning and the eject/jam procedure. mkdist
|
||||
flattens it to ASCII as `CONTROLS.txt` at the zip root (the README's own flattening idiom).
|
||||
`players/README.txt` gained pointers to it, to environ.ini, to -fit, and to the RGB mirror.
|
||||
Verified: `mkdist.py` writes a 5149-byte pure-ASCII CONTROLS.txt into the zip.
|
||||
|
||||
## 2026-07-26 (last) — the keyboard becomes the button board (RP412 bindings port)
|
||||
|
||||
User decision: port RP412's bindings design and make it the DEFAULT (not an opt-in alternate),
|
||||
plus a BT-flavoured interactive controls page.
|
||||
|
||||
**The layout.** Letter and number rows = the MFD banks laid out where they sit on the panel;
|
||||
flight to the numpad so the board stays free; F1-F12 = the map's two columns; G and B unbound as
|
||||
the physical gap between the lower clusters. Expressed in BT's OWN grammar (slew/deflect/set,
|
||||
the Turn channel) -- the bindings.txt format is a compatibility surface and was not touched.
|
||||
|
||||
Coverage audit: **61 of 72 addresses on the keyboard**, and the 11 absent ones are exactly the
|
||||
addresses with no authored function (0x16/0x17/0x1E/0x1F, 0x38-0x3E). 0x19 is bound (F8) purely
|
||||
for column completeness. All 72 stay clickable on the panel.
|
||||
|
||||
The scheme lands on BT's addresses unreasonably well: 1-4 + QWER are the whole coolant system
|
||||
(Condensers 1-6, flush, balance), F6/F7 the display and control-mode cycles, F9-F12 Generators
|
||||
A-D, F4 the crouch button reconstructed earlier today.
|
||||
|
||||
**The deliberate cost.** A bound key is removed from the 1995 typed-hotkey channel, and this board
|
||||
binds nearly everything -- so 5 (Quad), z (Eng1), t/y/u/i/o (pilot select) and +/- (zoom) go.
|
||||
Documented in the file header, the markdown and the page.
|
||||
|
||||
**New rule: a bindings row WINS over a built-in convenience key.** `PadRIO::KeyHasBinding` --
|
||||
V and J/K/L are board buttons now, so the view-toggle and preset-cycle polls stand down for them
|
||||
(they poll GetAsyncKeyState directly and would otherwise have fired BOTH). View toggle lives on
|
||||
BACKTICK alone.
|
||||
|
||||
**CONTROLS.MAP mirrored** so glass/pod/dev still feel identical (the 2026-07-21 settlement):
|
||||
90 bindings, 0 parse complaints under BT_INPUT_LOG.
|
||||
|
||||
### Hat labels: 0x42 is TORSO CENTER [T1]
|
||||
INPUT_PATH_AUDIT flagged 0x41-0x44 as mislabelled. Settled from the streamed mapping
|
||||
(BT_CTRLMAP_LOG): `elem 66 -> subsys 17 attrID 14` = the TORSO subsystem, while 0x44/0x43/0x41 go
|
||||
to the mapper's look trio (attrID 10/11/12 = LookLeft/LookRight/LookBehind). The .RES itself has
|
||||
no "TORSO CENTER" string -- its names are LookBehind/Down/Forward/Left/Right -- so the audit's
|
||||
wording was loose but its substance right. Swept L4GLASSWIN / L4PADPANEL / L4VB16.
|
||||
|
||||
### The controls page
|
||||
`docs/CONTROLS.html` -- interactive keyboard (hover any key for its address and meaning, with the
|
||||
MFD clusters outlined), pad diagram, panel map, the under-glass press-target figure, the radar
|
||||
placement thumbnails, and the environ.ini table. Modelled on RP412's page, rewritten for BT's
|
||||
addresses and hazards (the coolant-valve detent warning, jams and ammo-bay fires, BASIC control
|
||||
mode). mkdist wraps the fragment in a doctype/charset shell and ships it beside CONTROLS.txt.
|
||||
|
||||
### Verified
|
||||
- bindings.txt regenerates and loads: 74 keys, 0 parse errors; CONTROLS.MAP 90 bindings, 0 errors.
|
||||
- 72/72 panel addresses still dispatch on click.
|
||||
- surround / exploded / dock / pod / dev: boot + simulate, zero faults.
|
||||
- Dist carries CONTROLS.txt + a 46 KB CONTROLS.html with doctype and charset.
|
||||
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user