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:
@@ -39,6 +39,26 @@ run\run.cmd [EGG] # default DEV.EGG; cd's to content\ and runs btl4.exe -eg
|
||||
- **cwd MUST be `content\`** — the engine resolves `BTL4.RES`, `VIDEO\`, `BTDPL.INI`, eggs relative
|
||||
to cwd (the `loadTables` gotcha: `L4VIDEO.cpp:849` `fopen("VIDEO\\REPLACEMATS.tbl")` is relative +
|
||||
unchecked → fread on NULL if cwd is wrong). Logs to `btl4.log` in `content\` (or `BT_LOG=<file>`). [T2]
|
||||
**Since 2026-07-26 the exe enforces this itself** (`BTEnsureContentDirectory`, btl4main.cpp): if
|
||||
cwd has no `BTL4.RES` it probes from the exe's own directory (`..\..\content` for the shipped
|
||||
layout, plus flattened / in-content cases) and `SetCurrentDirectory`s there, before the log file
|
||||
opens. The boot line says so when it had to look. ⚠ **The landmine it defuses** (field report):
|
||||
a bare `btl4.exe` launch from `build\Release` found no resources ("Resource file btl4.res
|
||||
v1.0.0.0 is obsolete!"), wrote a stray `bindings.txt`/`environ.ini`/`btl4.log` NEXT TO THE EXE —
|
||||
so the player's real ones in `content\` looked like they were never created — and killed the
|
||||
mission generation the menu launched (the child inherits the parent's cwd). Invisible for years
|
||||
because every launcher `cd`s to `content\` first; it became reachable once glass became the
|
||||
desktop default and a zero-arg launch started opening the menu.
|
||||
- **Why this is a BT411-only hazard [T1].** The 1995 pod shipped ONE folder — `BTL4OPT.EXE` sits
|
||||
next to `BTL4.RES`/`VIDEO\`/`GAUGE\`/`AUDIO\` (it is still there in `content\`) — and RP411/RP412
|
||||
keep that shape (`pack-dist.ps1` copies the exe and every asset dir into one dist root). BT411's
|
||||
two-folder split (`build\Release\btl4.exe` + `content\`) is not a design decision: `mkdist.py`
|
||||
zips TRACKED REPO PATHS verbatim, so the dist inherited the repo's developer layout. In the
|
||||
authentic single-folder shape cwd is right by construction and a double-click just works, which
|
||||
is why the other games never hit this. **A proposal to ship the authentic shape is written up
|
||||
in `docs/DIST_LAYOUT_PLAN.md` — PROPOSED, not implemented, awaiting a decision between the
|
||||
authors.** The cwd guard stays either way (it protects the developer tree, where the split is
|
||||
real and permanent).
|
||||
- Interactive: **WASD** drive; weapon groups (keyboard, task #43) **1/Space** = lasers, **2** =
|
||||
PPCs, **3/Ctrl** = missiles; **X** all-stop; **V** cockpit/chase view. Default egg = `DEV.EGG`
|
||||
(map=grass, time=day). Swap mech via the egg's `vehicle=` — **ALL 18 ModelList names
|
||||
|
||||
+12
-7
@@ -92,17 +92,22 @@ viewport), and `gWindowAspect` = the view rect's on-screen aspect (`BTWorldAspec
|
||||
sizing (`btl4main.cpp`), the world viewport, the panel/button draw, the aspect, and the mouse
|
||||
hit-test. Constants: `SCALE=0.5, OVL=44 (corner overlap), LAMP=16 (protruding lamp edge),
|
||||
REDCELL=64 (hidden hit depth), RAILW=26`; `canvas = view + (552, 548)`.
|
||||
- **Buttons** = the L4GLASSWIN geometry ×0.5 with the same address banks (Heat 0x2F, Mfd2 0x27,
|
||||
Comm 0x37, Mfd1 0x0F, Mfd3 0x07 red 8-btn; radar rails 0x10-0x15/0x18-0x1D + bottom
|
||||
- **Buttons** = `L4RIOBANK` (2026-07-26: the ONE geometry, shared with the exploded windows — see
|
||||
[[glass-cockpit]] §ONE button-bank geometry) at half scale, same address banks (Heat 0x2F,
|
||||
Mfd2 0x27, Comm 0x37, Mfd1 0x0F, Mfd3 0x07 red 8-btn; radar columns 0x10-0x15/0x18-0x1D + foot
|
||||
{0x16,0x17,0x1F,0x1E} yellow; flight 0x38-0x3F/0x40-0x47 blue, labeled). Full rect = hit target;
|
||||
the surface draws OVER it so only the lamp edge shows (the PaintGlass painter trick). Mouse:
|
||||
the surface draws OVER it so only the lamp strip shows (the PaintGlass painter trick). ⚠ Before
|
||||
that date the surround's MFD lamps sat ENTIRELY outside the glass on a 24px band — a 76×24
|
||||
target where the exploded window gave 156×138; both now reach half the glass. Mouse:
|
||||
main WndProc `WM_L/RBUTTON` → `BTCockpitMouseDown/Up` (client→bb map, glass press/release/right-
|
||||
latch contract) → `PadRIO::SetScreenButton` (`#ifdef BT_GLASS`; dim/no-op in pod builds). Lamp
|
||||
brightness = `BTLampBrightnessOf` (shared inline in l4vb16.h) over `PadRIO::GetLampState`.
|
||||
- **Env / precedence** (resolved once in btl4main → `gBTGaugeCockpit`): `BT_GLASS_PANELS=1`
|
||||
(Cyd's per-display windows) stands cockpit down > explicit `BT_COCKPIT=1` > `BT_DEV_GAUGES_WINDOW`
|
||||
(separate window) / `BT_DEV_GAUGES_DOCK` (legacy inset) opt-out > **cockpit default**.
|
||||
`BT_COCKPIT=0` forces the dock-bottom strip. `-res W H` = the WORLD VIEW size (canvas clamped to
|
||||
- **Env / precedence** (resolved ONCE in btl4main → `glassLayout` → `gBTGaugeCockpit`; the full
|
||||
table lives in [[glass-cockpit]] §Layout modes): `BT_GLASS_PANELS`≠0 (the per-display windows)
|
||||
stands cockpit down > `BT_DEV_GAUGES_WINDOW` (separate window) > `BT_DEV_GAUGES_DOCK` (legacy
|
||||
inset) > `BT_COCKPIT=0` (also the dock strip) > **cockpit surround default**. ⚠ Before
|
||||
2026-07-26 `BT_COCKPIT=0` actually landed on the per-display windows — the dock strip was
|
||||
unreachable under glass. `-res W H` = the WORLD VIEW size (canvas clamped to
|
||||
the work area). Green tint tunable via `BT_COCKPIT_TINT=RRGGBB` (default `0x27E8`). Labels are a
|
||||
lazy GDI-baked MANAGED atlas (survives device reset). Renders in ALL builds; only the PadRIO
|
||||
click/lamp seam is BT_GLASS-gated. Full detail: `docs/GAUGE_COMPOSITE.md`.
|
||||
|
||||
+258
-4
@@ -63,6 +63,258 @@ version skew (a stale steam exe shipped 2 days behind), tripled build time, and
|
||||
([[reconstruction-gotchas]]); everything links always, so this class of trap no longer varies
|
||||
by build flavor.
|
||||
|
||||
## Layout modes — ONE resolver (2026-07-26) [T2 four-mode boot-verified]
|
||||
|
||||
Where the five MFDs + radar go is a SEPARATE axis from the platform profile. Under a glass boot
|
||||
(`BT_DEV_GAUGES` set) `btl4main.cpp` resolves it **once**, right after the profile putenvs land,
|
||||
into `glassLayout`; the boot banner, the pad-panel decision and the window-sizing block all read
|
||||
that one answer. Precedence, highest first:
|
||||
|
||||
| Env | Layout | Buttons live in |
|
||||
|---|---|---|
|
||||
| `BT_GLASS_PANELS`≠0 | per-display cockpit windows (the "exploded" view, `L4GLASSWIN`) | each display's own window |
|
||||
| `BT_DEV_GAUGES_WINDOW` | the legacy separate MFD window | the single combined pad panel |
|
||||
| `BT_DEV_GAUGES_DOCK` | the docked bottom gauge strip | the single combined pad panel |
|
||||
| `BT_COCKPIT=0` | ...also the docked strip (the documented opt-out) | the single combined pad panel |
|
||||
| (nothing) | **COCKPIT SURROUND — the glass default** | the main window (D3D-drawn lamps) |
|
||||
|
||||
The banner now names the live one: `[boot] platform profile: GLASS (PadRIO + plasma window)
|
||||
[secondary displays: cockpit surround]`.
|
||||
|
||||
⚠ **Two bugs this replaced** (both were live until 2026-07-26): the mode was decided in TWO places
|
||||
with duplicated precedence (the glass profile block picked `BT_PAD_PANEL`/`BT_GLASS_PANELS`; the
|
||||
sizing block re-derived cockpit-vs-dock), and the banner read NEITHER — it announced "per-display
|
||||
cockpit windows [BT_GLASS_PANELS]" for every glass boot, surround included. Worse, the profile
|
||||
block turned `BT_COCKPIT=0` into `BT_GLASS_PANELS=1`, so the docked strip it is documented to
|
||||
select was **unreachable under the glass profile**. `BT_GLASS_PANELS` is now explicit-only, and
|
||||
dock/window modes auto-raise `BT_PAD_PANEL` so the 72-button field always has a home.
|
||||
|
||||
## ONE button-bank geometry — L4RIOBANK (2026-07-26) [T2 click-verified both modes]
|
||||
|
||||
The RIO button field around a pod display is drawn TWICE — composited into the main window
|
||||
(surround, `L4VB16.cpp`) and one window per display (exploded, `L4GLASSWIN.cpp`) — and each
|
||||
renderer used to carry its own copy of the geometry. They drifted badly: the same MFD button was
|
||||
a 156×138 under-glass target in the exploded window and a **76×24 sliver entirely outside the
|
||||
glass** in the surround. `engine/MUNGA_L4/L4RIOBANK.cpp` is now that geometry, once; both
|
||||
renderers are consumers, and placement (WHERE each display goes) stays with each renderer because
|
||||
that genuinely differs.
|
||||
|
||||
**The under-glass rule** (ported from RP412 `L4MFDVIEW`, which took it from the pod): a button
|
||||
reaches **half the glass** in BEHIND the display and only a **lamp strip** clears the edge. The
|
||||
renderer paints buttons first, imagery over them — so the lamp reads as a slim bar, practically
|
||||
the whole display is the press target, and nothing is occluded (the picture wins every pixel it
|
||||
covers). The strip scales off the display's SHORT axis against the native 480 (an MFD is 640×480
|
||||
landscape, the map 480×640 portrait — both short-axis 480) with a 6px floor, so a half-scale
|
||||
surround lamp is still a bar and not a smudge. The MFD per-column nudge against the unevenly
|
||||
spaced DISPLAY/PROGRAM legends (`mfdColumnDX`) moved here too, so both renderers inherit the
|
||||
same alignment.
|
||||
|
||||
⚠ **The map's foot row is pushed FIRST**, deliberately: the side columns now reach half the map
|
||||
each, so they cover the foot band too, and the hit test takes the first match — foot-first is
|
||||
what keeps 0x16/0x17/0x1F/0x1E reachable. That class of bug is why the verification below tests
|
||||
first-hit reachability, not mere presence.
|
||||
|
||||
**Verified 2026-07-26 [T2]:** `BT_RIOBANK_LOG=1` dumps every bank's rects;
|
||||
`scratchpad/checkbank.py` reports the per-bank census and proves no address is SHADOWED (has a
|
||||
point no earlier button covers); `scratchpad/clickbank.py` then posts a real click at every
|
||||
button's centre. Both modes: **72/72 addresses placed, zero shadowed, zero duplicated, 72/72
|
||||
dispatched** (`[cockpit] CLICK` / `[glasswin] CLICK`), process survived 144 posted messages per
|
||||
mode. Pod + dev profiles boot and simulate un-regressed. Surround BT_SHOT confirms the legends
|
||||
are clear and the lamps read as edge strips.
|
||||
|
||||
## The cockpit SCALES — uniform-fit letterbox + `-fit` (2026-07-26) [T2 live]
|
||||
|
||||
The canvas is a fixed size and D3D9 stretched it into whatever the client area was, so a window
|
||||
dragged to a different shape **squashed the instruments** — the projection was aspect-corrected
|
||||
(task #20) but the panels, lamps and MFD glass were not. Now the canvas is fitted at ONE uniform
|
||||
scale, centred, leftover black: the cockpit scales both ways and never distorts, whatever the
|
||||
window.
|
||||
|
||||
- `BTCockpitFitRect(clientW, clientH, RECT*)` — integer math throughout, so the rect is stable
|
||||
frame to frame (a float scale wobbles a pixel and the bars shimmer during a drag).
|
||||
- D3D9 does the scaling at Present via a **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 whether that
|
||||
happened; when 0 the old full-client stretch stands, so an MSAA run still works.
|
||||
- Both present sites take the rect (scene + the wait-screen overlay, which paints into the same
|
||||
backbuffer). `WM_SIZE` invalidates so the bars repaint black — a COPY present only writes its
|
||||
dest rect, so stale bar content would otherwise persist.
|
||||
- **The click transform had to follow.** `BTCockpitMouseDown` mapped client→canvas against the
|
||||
full client; under the letterbox that drifts the hit test off every button by the bar width.
|
||||
It now runs the identical fit (and a click on a bar hits nothing).
|
||||
- **World aspect** under the letterbox is the view rect's OWN aspect — the client no longer
|
||||
enters into it.
|
||||
- **`-fit`** (alias `-windowed-fullscreen`): borderless `WS_POPUP` over the monitor, canvas
|
||||
letterboxed inside. Verified on a 3440×1440 ultrawide.
|
||||
|
||||
⚠ **Ordering trap found live:** the letterbox flag was first set at device creation, but the
|
||||
first `WM_SIZE` arrives BEFORE the device exists — a `-fit` boot logged `aspect=3.14` (the
|
||||
stretch formula) and applied it on frame 1, because nothing resizes the window again. The INTENT
|
||||
is now decided in `btl4main` alongside `gBTGaugeCockpit`; `L4VIDEO` only confirms or withdraws it.
|
||||
|
||||
⚠ **Capture note:** `PrintWindow` returns an all-black frame for the borderless `-fit` window
|
||||
(it works for the normal chrome'd one). Use a screen-region grab (`scratchpad/shot.ps1`) for
|
||||
`-fit`, and don't read a black PrintWindow there as a render failure — it isn't.
|
||||
|
||||
Verified: wide (1684×661) and tall (744×961) drags both letterbox with zero distortion; **72/72
|
||||
buttons still dispatch through the transform at 0.62× scale with a 389px bar**; exploded / dock /
|
||||
pod / dev all keep DISCARD and boot un-regressed.
|
||||
|
||||
## Player-tunable displays — scale, map placement, environ.ini (2026-07-26) [T2 live]
|
||||
|
||||
The pod bolted its instruments down at one size; a desktop panel has room to trade viewscreen for
|
||||
instrument, so the player scales them. All resolved ONCE (`BTCkResolvedSizes`, L4VB16.cpp) because
|
||||
the surround recomputes its layout every frame.
|
||||
|
||||
| Env | Effect |
|
||||
|---|---|
|
||||
| `BT_MFD_SCALE` | all five MFDs, % of pod size (25-200, default 100) |
|
||||
| `BT_MFD_SCALE_UL/_UC/_UR/_LL/_LR` | one display, overriding the group |
|
||||
| `BT_RADAR_SCALE` | the portrait map |
|
||||
| `BT_RADAR_POS` | `CENTER` (default) / `LEFT` / `RIGHT` / `MIDLEFT` / `MIDRIGHT` (+ `BOTTOM*`, `*CENTER` spellings) |
|
||||
|
||||
**The bands derive from the resolved sizes** — that is why the sizes could not stay constants: the
|
||||
surround 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`, since it overlaps the view), and
|
||||
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` exactly; 150% gives `L436 R436 T343 B448`;
|
||||
80% + MIDLEFT gives `L256 R212 T175 B148`.
|
||||
|
||||
⚠ **A corner map goes flush to the CANVAS edge**, not the view edge, and the lower MFD whose corner
|
||||
it took sits beside it. Measuring off the view edge (first cut) overlapped them by the band width —
|
||||
232px of map drawn over the Right Weapons MFD.
|
||||
|
||||
**Map legend grid — MEASURED, not inherited [T2].** The side columns must line up with the six
|
||||
cells the map paints beside them (⊕MAP / ⊖MAP / IR / crouch / searchlight / display-mode =
|
||||
0x10-0x15 — the imagery independently confirms our address map). `scratchpad/measurelegend.py` over
|
||||
a native 480×640 capture gives **top 3, cell 102, pitch 107** on the 640 span. RP412's map is
|
||||
`13 + 6×102 @ pitch 105` — same cell height, different top and pitch, so **its numbers do not
|
||||
transfer**; ours are ours. Our old even division (`displayH/6 + 1 = 107`) had the pitch right by
|
||||
luck and sat 3px high of the labels. Top and bottom are now scaled separately and subtracted, so
|
||||
rounding cannot drift the buttons out of step with the labels down the column.
|
||||
|
||||
**`environ.ini` (the settings file) [T2].** ⚠ It was read ~300 lines into `WinMain`, AFTER the
|
||||
platform-profile block had 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 worked as a real env var. It also `putenv`'d comment lines verbatim, which would
|
||||
have turned a shipped commented-out option into a variable literally named `#BT_MFD_SCALE`. Now:
|
||||
loaded immediately after the first-breath boot line, comments and blanks skipped, and **the real
|
||||
environment WINS** so a launcher `.bat` overrides the file rather than fighting it. A documented
|
||||
default is written on first run when absent — the `bindings.txt` convention: untracked, so an
|
||||
extract-over-top upgrade never clobbers a player's settings, and every option ships commented out
|
||||
so a fresh install behaves exactly as before. Verified all three: first run writes it and applies
|
||||
0 settings; uncommented file settings drive the layout (130% group + 60% UC + MIDRIGHT); a real
|
||||
`BT_MFD_SCALE=90` overrides the file's 130 while the file's other 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 un-regressed.
|
||||
|
||||
## Dead-button backlog CLOSED — DuckRequest was the last (2026-07-26) [T2 live]
|
||||
|
||||
The 2026-07-20 audit's "8 buttons dispatch a streamed message with no reconstructed handler" is
|
||||
finished. ⚠ Only ONE of the eight was actually still missing when this pass started — generator
|
||||
on/off, ToggleSeekVoltage, EjectAmmo, ToggleCooling and BalanceCoolant had all landed between
|
||||
07-20 and 07-25 while `pod-hardware.md` / `open-questions.md` still called them dead
|
||||
(`docs/INPUT_PATH_AUDIT.md` had already flagged the census as "stale in both directions").
|
||||
**Check the code, not the census.**
|
||||
|
||||
**`Mech::DuckRequest` @0049fa00 (id 0x1a, RIO 0x13 — the manual's CROUCH button).** The binary's
|
||||
entire body is two lines: press-only (`msg+0xc > 0`), then `duckState`(`mech+0x398`) = 1. It is a
|
||||
one-shot REQUEST flag, not a posture toggle — the handler never clears it and the only other
|
||||
writer in the whole binary is the mech reset (part_012.c:9439, the same reset that zeroes
|
||||
`incomingLock`). Registered in `Mech::MessageHandlerEntries`; `[duck]` log line on press.
|
||||
|
||||
**Its consumer is a DATABINDING, not code [T1] — which is why the flag looked pointless.**
|
||||
`duckState` has ZERO readers anywhere in the decomp, because it is published as attribute 0x37 and
|
||||
consumed through the gauge system: `content/GAUGE/L4GAUGE.CFG` runs a 3-frame `bduck.pcc`
|
||||
`oneOfSeveralPixInt` widget bound to `DuckState` on the map's legend column ("crouch mode: button
|
||||
4"). Verified live: pressing 0x13 turns that legend icon grey→orange. **Do not invent a crouch
|
||||
pose to "finish" this** — the flag plus the indicator IS the code side.
|
||||
|
||||
Bonus confirmation: those legend widgets sit at gauge offsets 537/430/322/215/108 — a **107
|
||||
pitch**, independently corroborating the map legend grid measured from pixels in §Player-tunable
|
||||
displays.
|
||||
|
||||
## THE KEYBOARD IS THE BUTTON BOARD — new default keymap (2026-07-26) [T2]
|
||||
|
||||
Ported from RP412's bindings design (user decision: make it the DEFAULT, not an alternate). The
|
||||
letter and number rows are the MFD button banks **laid out where they sit on the panel**, and
|
||||
flight moves to the NUMPAD so the board stays free:
|
||||
|
||||
| Keys | Bank |
|
||||
|---|---|
|
||||
| `1 2 3 4` / `Q W E R` | Heat / coolant 0x2F-0x2C / 0x2B-0x28 |
|
||||
| `5 6 7 8` / `T Y U I` | Engineering (Mfd2) 0x27-0x24 / 0x23-0x20 |
|
||||
| `9 0 - =` / `O P [ ]` | Comm / target hotbox 0x37-0x34 / 0x33-0x30 |
|
||||
| `A S D F` / `Z X C V` | Left Weapons (Mfd1) 0x0F-0x0C / 0x0B-0x08 |
|
||||
| `H J K L` / `N M , .` | Right Weapons (Mfd3) 0x07-0x04 / 0x03-0x00 |
|
||||
| `F1`-`F6` / `F7`-`F12` | the map's two columns 0x10-0x15 / 0x18-0x1D |
|
||||
| numpad | 8/2/4/6 stick, 7/9 pedals, 5 all-stop, 0/1/3/. the four fire buttons |
|
||||
| `Shift`/`Ctrl` · `Alt` · arrows · `` ` `` | throttle lever · reverse · look/torso-centre · view |
|
||||
|
||||
`G` and `B` stay unbound — the physical gap between the lower clusters. **Coverage: 61 of 72
|
||||
addresses on the keyboard, and the 11 absent ones are exactly those with no authored function**
|
||||
(0x16/0x17/0x1E/0x1F column gaps, 0x38-0x3E intercom/door). All 72 remain clickable.
|
||||
|
||||
It lands on BT's addresses better than it deserves to: `1-4`+`QWER` are the ENTIRE coolant system
|
||||
(Condensers 1-6, flush, balance), `F6`/`F7` the display/control-mode cycles, `F9`-`F12` Generators
|
||||
A-D, `F4` the crouch button.
|
||||
|
||||
⚠ **What it costs, deliberately.** A bound key is removed from the authentic 1995 typed-hotkey
|
||||
channel to stop double-dispatch, and this board binds nearly everything — so `5` (Quad page), `z`
|
||||
(Eng1), `t/y/u/i/o` (pilot select) and `+`/`-` (target zoom) are given up. Unbind a key to get its
|
||||
1995 meaning back.
|
||||
|
||||
⚠ **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 and the view toggle lives on `` ` `` alone. Without this they would have fired BOTH.
|
||||
|
||||
`content/CONTROLS.MAP` was rewritten to mirror the board (90 bindings, 0 parse complaints) so the
|
||||
glass/pod/dev boots still feel identical — the 2026-07-21 settlement. An existing `bindings.txt`
|
||||
is never overwritten, so a player only meets this board by deleting theirs.
|
||||
|
||||
## Hat labels corrected — 0x42 is TORSO CENTER [T1, streamed mapping]
|
||||
|
||||
`docs/INPUT_PATH_AUDIT.md` flagged the 0x41-0x44 labels as wrong and it was right. Settled from
|
||||
`BT_CTRLMAP_LOG`: **0x42 → subsys 17 (Torso), attrID 14** — not a look at all; the other three go
|
||||
to the mapper's look trio (0x44 → attrID 10 LookLeft, 0x43 → 11 LookRight, 0x41 → 12 LookBehind).
|
||||
The shipped `.RES` has no "TORSO CENTER" string (its names are LookBehind/Down/Forward/Left/Right),
|
||||
so the audit's phrasing was loose but its substance correct. Swept the three UI tables
|
||||
(`L4GLASSWIN`, `L4PADPANEL`, `L4VB16`): 0x42 is now "Torso Ctr", 0x41/0x43/0x44 "Look Bk/R/L".
|
||||
|
||||
## RGB keyboard lamp mirror — L4KEYLIGHT (2026-07-26) [T2 live, claimed a real board]
|
||||
|
||||
Ported from RP412 (itself vRIO's `KeyboardLampMirror`). Keys bound to a lamp address in
|
||||
`bindings.txt` glow with the panel palette through **Windows Dynamic Lighting** — yellow for the
|
||||
map's side columns (0x10-0x1F), red for the rest — flashing in step with the on-screen buttons
|
||||
(its `LampLevel` copy matches the FIXED `BTLampBrightnessOf`). Per-key boards light each bound
|
||||
key; zone-lit boards mirror the strongest lamp board-wide. All WinRT runs on a private worker
|
||||
thread. Gate: `BT_KEYLIGHT=0` opts out; no Dynamic Lighting = one log line, then dormant.
|
||||
|
||||
⚠ **RP412's packing hazard does NOT apply here.** It compiles that 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
|
||||
(`/std:c++17 /permissive-`, per-file in CMakeLists, since the project otherwise builds C++14
|
||||
/permissive). The scalars-only interface is kept anyway so the isolation survives if packing is
|
||||
ever added.
|
||||
|
||||
Wired in `PadRIO`: map from `bindings.keyBindings` (ActionButton binds only, first-binding-wins
|
||||
per key), fed from `PadRIO::SetLamp`, stopped in the dtor (which hands the LEDs back to Windows).
|
||||
Verified: `[keylight] mirroring 25 bound key(s)` + claimed this machine's 24-zone keyboard;
|
||||
`BT_KEYLIGHT=0` and the pod profile produce zero keylight lines and run clean.
|
||||
|
||||
## Lamp flash decode was wrong — fixed (2026-07-26) [T1, from the L4RIO.h enum]
|
||||
|
||||
`BTLampBrightnessOf` (l4vb16.h) returned `max(state1, state2)` and blanked to 0 on the alternate
|
||||
phase. The engine's own enum [T0] says otherwise: bits 0-1 = flash mode, bits 2-3 = **state 1**
|
||||
brightness, bits 4-5 = **state 2** brightness (0 off / 1 dim / 3 bright) — solid shows state 1,
|
||||
flashing ALTERNATES the two at 500/250/125 ms. The old formula agrees only when one state is Off,
|
||||
which is why it survived: the Panic lamp is `flashFast+state1Off+state2Bright` and L4LAMP's other
|
||||
pulse is off/dim. But **`L4LAMP.cpp:252` commands `flashFast + state1Dim + state2Bright`** — a
|
||||
dim→bright pulse that rendered as a hard bright→off blink. RP412's `L4MFDVIEW::LampLevel` has the
|
||||
faithful formula; that is now ours. THREE copies of the decode existed (l4vb16.h, L4GLASSWIN,
|
||||
L4PADPANEL), all three with the bug; the two locals now forward to the one inline.
|
||||
|
||||
## The pre-mission WAITING SCREEN (2026-07-22) [T2]
|
||||
Every pre-run state (seat wait, roster full, WaitingForEgg, LoadingMission, WaitingForLaunch)
|
||||
paints an animated overlay -- Consolas green text + a 12-segment spinner phased on
|
||||
@@ -199,8 +451,9 @@ copied from them.
|
||||
dead panel: `MechRIOMapper`'s message ids were chained one enum too high (0x19-0x2a) — the
|
||||
binary RIO table @0051dd30 re-registers the BASE aux/zoom ids 3..0x13 (Hotbox 0x1a); fixed
|
||||
ungated + static_assert-locked (`btl4mppr.hpp`) → all 24 MFD-bank + 2 zoom buttons live
|
||||
(52/72 panel buttons now work; 8 dead await handler reconstruction — Gitea backlog issue;
|
||||
12 have no streamed mapping authored, authentically inert). **Keyboard = the ~20 core
|
||||
(52/72 panel buttons worked at the time; the 8 dead ones are ALL wired as of 2026-07-26 —
|
||||
`DuckRequest` was the last, see §Dead-button backlog closed; 12 have no streamed mapping
|
||||
authored, authentically inert). **Keyboard = the ~20 core
|
||||
gameplay actions on the CONTROLS.MAP keys; the PANEL covers every pod address by click**
|
||||
(right-click = hold latch) — the merged default profile lives in `L4PADBINDINGS.cpp`
|
||||
(auto-writes `content\bindings.txt`; W/S/A/D/Q/E/R/F/X drive, 1-4/Space/Ctrl fire, M/N/H/C =
|
||||
@@ -243,8 +496,9 @@ copied from them.
|
||||
(`SVGA16::ExpandPlaneToBGRA`, no D3D) and `StretchDIBits`'d in, so the **whole D3D
|
||||
dev-composite path (dock / separate window / overlay) stands down** while these are up
|
||||
(`BTDrawGaugeInset`/`BTGaugeWindowRenderAndPresent` early-return; no `gBTGaugeDockBottom`
|
||||
strip). Runtime gate **`BT_GLASS_PANELS`** (default ON under `-platform glass`, `=0` falls
|
||||
back to the single pad panel + docked gauges) — read by `L4PADRIO` (which panel to create),
|
||||
strip). Runtime gate **`BT_GLASS_PANELS`** (⚠ was "default ON under `-platform glass`" — NO
|
||||
LONGER TRUE; the surround is the glass default and panels are explicit-only, see §Layout modes
|
||||
below) — read by `L4PADRIO` (which panel to create),
|
||||
`L4VB16` (suppress compositing), and `btl4main` (skip the world-window dock strip). The
|
||||
display↔bank map (spine of the feature): Heat 0x28-2F, Mfd2 0x20-27, Comm 0x30-37,
|
||||
Mfd1 0x08-0F, Mfd3 0x00-07, sec 0x10-1B ([[pod-hardware]] §Bank→MFD). Verified: build +
|
||||
|
||||
+23
-11
@@ -169,17 +169,29 @@ register. ⚠ The audit also flags the damage-economy item as SELF-CONTRADICTOR
|
||||
exactly is the all-roster-hosts-connected launch wait, and can absent seats stay `Opening`
|
||||
without stalling? Go/no-go = the 2-node late-attach spike in the plan doc. Phase 1 (round
|
||||
carousel) is console-only and has no engine unknowns. [[multiplayer]]
|
||||
- **Glass panel dead-button backlog (input-coverage audit 2026-07-20) [T1 census].** 8 of the 72
|
||||
panel buttons dispatch a streamed message with NO reconstructed handler (silently ignored):
|
||||
0x13 → Mech `DuckRequest` @0049fa00 (CROUCH — `duckState` attr + SQUAT clips exist!), 0x28 →
|
||||
Mech `BalanceCoolant` @0049f728, 0x1A-0x1D → Generator A-D `ToggleGeneratorOnOff` @004b1ed0,
|
||||
0x12 → ThermalSight toggle (pvision unported), 0x14 → Searchlight `ToggleLamp` (body
|
||||
reconstructed, handler-set unwired — note the 1995 latent bug below: even wired, the light
|
||||
never turns on in the original). Plus page-gated Eng-page meanings: `ToggleCooling` @004ad6f8
|
||||
(msg 3), `ToggleSeekVoltage` @004ba478/@004b8a48 (msg 0xb energy / 9 Myomers), `EjectAmmo`
|
||||
@004bb9b8 (msg 0xb ammo weapons), and MechRIOMapper's own Keypress @004d2514 (id 0x19).
|
||||
Filed as the Gitea "glass panel: unreconstructed button handlers backlog" issue; tables in
|
||||
[[decomp-reference]] §2026-07-20; census in `docs/GLASS_COCKPIT.md`.
|
||||
- **Glass panel dead-button backlog (input-coverage audit 2026-07-20) — ✅ CLOSED 2026-07-26.**
|
||||
The 8 buttons that dispatched a streamed message with NO reconstructed handler are all wired
|
||||
now; this entry stays as the index of where each one landed:
|
||||
0x13 → Mech `DuckRequest` @0049fa00 (**CROUCH — wired 2026-07-26**, the last one; see below),
|
||||
0x28 → Mech `BalanceCoolant` @0049f728 (2026-07-21, issue #20),
|
||||
0x1A-0x1D → Generator A-D `ToggleGeneratorOnOff` @004b1ed0 (`powersub.cpp`),
|
||||
0x12 → ThermalSight / 0x14 → Searchlight `ToggleLamp` (both 2026-07-25, #61),
|
||||
`ToggleCooling` @004ad6f8 msg 3 (2026-07-20, `heatfamily_reslice.cpp`),
|
||||
`ToggleSeekVoltage` @004ba478 msg 0xb energy (`emitter.cpp`) / @004b8a48 msg 9 Myomers,
|
||||
`EjectAmmo` @004bb9b8 msg 0xb ammo (`projweap.cpp`).
|
||||
Still open from that census: **MechRIOMapper's own Keypress @004d2514 (id 0x19)**.
|
||||
Tables in [[decomp-reference]] §2026-07-20; census in `docs/GLASS_COCKPIT.md`.
|
||||
⚠ This entry was stale in BOTH directions for weeks (`docs/INPUT_PATH_AUDIT.md` flagged it) —
|
||||
most of the list had been reconstructed while the prose still called it dead. Check the code,
|
||||
not the census, before reconstructing anything here.
|
||||
- **`DuckState` has no CODE consumer, and that is authentic [T1].** `Mech::DuckRequest` sets
|
||||
`duckState`(attr 0x37, binary `mech+0x398`) to 1 and nothing else: the flag has exactly two
|
||||
writers in the whole binary (that handler and the mech reset) and ZERO readers. Its consumer is
|
||||
a DATABINDING — `content/GAUGE/L4GAUGE.CFG` drives a 3-frame `bduck.pcc` widget off `DuckState`
|
||||
on the map's legend column, verified live (the crouch icon lights grey→orange on a press). So
|
||||
the button is COMPLETE as a request flag + indicator. What is NOT known: whether the 1995 game
|
||||
ever consumed it for posture/collision (no SQUAT clip name survives in the decomp or in
|
||||
`content/`, only `DuckServo01.wav` in AUDIO1.RES). Do not invent a crouch pose to "finish" it.
|
||||
- **Searchlight-driven fog swap — STILL DEFERRED, but the "ORIGINAL 1995 LATENT BUG" premise is
|
||||
❌ RETRACTED (2026-07-25, #61).** The arcade swaps fog between `fog=` (lights on) and
|
||||
`nosearchlightfog=` (off) via `PullFogRenderable` watching the Searchlight's `lightState`.
|
||||
|
||||
@@ -52,10 +52,12 @@ handler chain, so the Eng-page "Coolant" button now toggles the shown weapon's c
|
||||
sub 3 = Reservoir, 4-9 = Condenser1-6, 10-13 = GeneratorA-D): **0x2C = the COOLANT FLUSH
|
||||
button** (Reservoir InjectCoolant, hold-to-flush — works), **0x2F/0x2E/0x2D/0x2B/0x2A/0x29 =
|
||||
the per-condenser VALVE buttons** (MoveValve, Cond1-6 — work), **0x1A-0x1D = Generator A-D
|
||||
ON/OFF** (`ToggleGeneratorOnOff` id 4, binary table @0050fb90 fn @004b1ed0 — DEAD until
|
||||
reconstructed). Newly decoded from the binary message tables: **0x13 → Mech `DuckRequest`
|
||||
(0x1a @0049fa00 — the manual's CROUCH button)**, **0x28 → Mech `BalanceCoolant` (0x16
|
||||
@0049f728)**, **0x12 → ThermalSight `ToggleLamp` (id 3, table @0x51120C fn @004b860c)** and **0x14 →
|
||||
ON/OFF** (`ToggleGeneratorOnOff` id 4, binary table @0050fb90 fn @004b1ed0 — ✅ **WIRED**,
|
||||
`powersub.cpp`). Newly decoded from the binary message tables: **0x13 → Mech `DuckRequest`
|
||||
(0x1a @0049fa00 — the manual's CROUCH button)** — ✅ **WIRED 2026-07-26**, the last handler in
|
||||
this census; press-only, sets `duckState` and the map legend's `bduck.pcc` widget lights (see
|
||||
[[open-questions]] for why it has no code consumer) —, **0x28 → Mech `BalanceCoolant` (0x16
|
||||
@0049f728)** ✅ **WIRED 2026-07-21 (#20)**, **0x12 → ThermalSight `ToggleLamp` (id 3, table @0x51120C fn @004b860c)** and **0x14 →
|
||||
Searchlight + Searchlight2 `ToggleLamp` (id 3, table @0x51117C fn @004b838c)** — ✅ **BOTH WIRED
|
||||
2026-07-25 (#61)**, previously default-constructed blackholes; verified live (0x14 →
|
||||
`lightState 0→1`, 0x12 → `thermalActive 0→1`). ⚠ The fn addresses were **swapped** in this file
|
||||
|
||||
Reference in New Issue
Block a user