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:
arcattack
2026-07-26 13:13:08 -05:00
co-authored by Claude Opus 5
36 changed files with 4848 additions and 431 deletions
+20
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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),
0x28Mech `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`.
+6 -4
View File
@@ -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 @004b1ed0DEAD 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