Merge glass-per-display-windows: per-display cockpit windows (Cyd, BT_GLASS_PANELS)

One window per pod display (Heat/Engineering/Comm/Weapons x2/radar +
Flight Controls), each carrying its surface with its RIO button bank
arranged pod-faithfully; CPU-expanded surfaces, buttons under the
imagery.  Runtime gate BT_GLASS_PANELS (=0 = the legacy single panel).
Merged on top of today's landed fix pile (audio-crash, experience,
parallax); the L4PADRIO overlap with the gait-detent commit resolved
preserving BOTH (his window refactor + our at-rest band snap).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

# Conflicts:
#	context/glass-cockpit.md
This commit is contained in:
arcattack
2026-07-20 14:52:38 -05:00
10 changed files with 1176 additions and 10 deletions
+90
View File
@@ -348,3 +348,93 @@ cavern.egg`. This is every option the original console exposed for a BattleTech
- dropzone (5, the `dropzone=` field): one, two, three, four, five ← was `one`
- advancedDamage (0/1): the advanced damage model toggle ← was hardcoded `1`
- vehicleValue: 1000 (const), loadzones=1 (const), bitmapindex (per-pilot ordinal)
---
## 2026-07-20 — Per-display cockpit windows (BT_GLASS_PANELS)
**Goal (Cyd):** stop collapsing the desktop cockpit into combined surfaces. Break each
SECONDARY display out into its OWN window carrying that display's surface WITH its RIO button
bank placed around it, arranged pod-faithfully around the main 3D view — a dev cockpit that
reads like the pod, so button/lamp/display behavior can be validated in position.
**New TU: `engine/MUNGA_L4/L4GLASSWIN.{h,cpp}`** (BT_GLASS-only; added to the CMake BT_GLASS
`target_sources`). Builds SEVEN GDI windows and positions them around the main window:
| Window | Surface (port) | RIO bank | Buttons |
|---|---|---|---|
| Heat MFD (UL) | `Heat` (0x4000) | 0x28-0x2F | 8 red, 4 above / 4 below |
| MFD 2 (UC) | `Mfd2`/`Eng2` (0x0400) | 0x20-0x27 | 8 red, 4 above / 4 below |
| Comm MFD (UR) | `Comm` (0x8000) | 0x30-0x37 | 8 red, 4 above / 4 below |
| MFD 1 (LL) | `Mfd1`/`Eng1` (0x0100) | 0x08-0x0F | 8 red, 4 above / 4 below |
| MFD 3 (LR) | `Mfd3`/`Eng3` (0x1000) | 0x00-0x07 | 8 red, 4 above / 4 below |
| Secondary/Radar | `sec` (palette, portrait) | 0x10-0x1B Secondary | 12 yellow, 6 left / 6 right |
| Flight Controls | (none) | 0x38-0x47 (+ panic/icom) | neutral, 2 columns |
**Architecture — GDI + CPU blit, NO D3D for the secondaries.** Each window is a layered,
topmost GDI window (the L4PADPANEL pattern: double-buffered paint, ~62 ms repaint timer,
left-click = momentary / right-click = latch → `PadRIO::SetScreenButton`; lamps from
`PadRIO::GetLampState`). The surface is CPU-expanded from the shared gauge pixelBuffer by the
new **`SVGA16::ExpandPlaneToBGRA(mask, paletteID, monoTint, rotateQuadrant, dst, &ow, &oh)`**
(L4VB16.cpp) — the no-D3D analog of `DrawDevSurface`: palette-LUT expand for `sec` (monoTint
< 0), mono bit-plane → tint for the MFDs; the portrait secondary CRT is unrotated by
transposing into `dst` (rotate 1/3, `BT_GAUGE_SEC_ROT`) — then `StretchDIBits` (HALFTONE) into
the surface rect. An Mfd/Eng pair shows whichever plane is the live (non-`BlankColor`) one, as
on the pod monitor. This reuses the plasma-window CPU→window pattern and sidesteps the whole
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:
- `L4PADRIO` ctor: `BTGlassPanelsActive()``BTGlassPanels_Create()` instead of
`BTPadPanel_Create()`; dtor destroys both (safe no-ops).
- `L4VB16`: `BTDrawGaugeInset` / `BTGaugeWindowRenderAndPresent` early-return when
`GlassPanelsOwnSurfaces()` (the per-display windows own the surfaces).
- `btl4main`: skip the `gBTGaugeDockBottom` + 1100×600 world-window forcing (world stays
normal size — no bottom strip).
**Placement.** `ComputeLayout()` rings the windows around `ghWnd` (the main game window):
top row Heat/Mfd2/Comm above it, Mfd1/Mfd3 at the lower corners, radar below-center, Flight
Controls left of the seat; clamped to the work area, with an 800×600-centered fallback before
the main window exists + a one-shot reposition once `ghWnd` comes up (in the repaint timer).
**Verified [T2 startup].** `-DBT_GLASS=ON` Debug build links (the usual `/FORCE` mech3
subsystem set is pre-existing, unrelated). Bounded `-platform glass -egg DEV.EGG` launch:
`[glasswin] per-display cockpit up (7 windows: 5 MFD + radar + flight)`, `[plasmawin] … up`,
`[padrio] XInput pad found`, no failure/abort markers, full mission loop un-regressed (killed
after 18 s). Visual layout / surface content / click-through NOT yet human-verified.
**Tunable (Cyd flagged refinements):** surface display sizes, the exact button sub-positions
around each screen, the yellow left/right split (first cut 0x10-0x15 / 0x16-0x1B), and the MFD
mono phosphor tint — all named constants at the top of `L4GLASSWIN.cpp`. Deferred: folding the
plasma window into the same arrangement; per-window draggable/saved positions; moving/resizing
the main window into a true center cell for a tighter ring.
### Blank-surface bug (found + fixed same day) — /FORCE duplicate-global race
First glass-panels run showed the buttons but BLANK display surfaces. `BT_GLASS_LOG`
diagnostics in `BlitSurface` traced it stage by stage: `application == 0x00000000` in
`L4GLASSWIN.cpp` for the ENTIRE run (call 1 → 481), so `application->GetGaugeRenderer()` was
never even reached. Yet the legacy dock path (`BTDrawGaugeSurfaces`, `L4VB16.cpp`, SAME engine
lib) logged `application=047BA9C8 gr=0C57FF78` — non-NULL.
Root cause: the 1995 headers declare free globals (`application`, `ghWnd`, …) without
`inline`/`extern`, so they are DEFINED in both `munga_engine` and `bt410_l4`. Under
`/FORCE:MULTIPLE` the linker binds each `.obj`'s reference to one copy or the other
**non-deterministically per link** — the newly-added `L4GLASSWIN.obj` bound the copy the game
never assigns (stays NULL), and a relink even flipped `L4VB16.obj`. Routing through an engine
helper is therefore unreliable (its binding is equally random).
Fix: `BTResolveGaugeRenderer()` / `BTResolveMainWindow()` (declared in `l4vb16.h`) are DEFINED
in `game/btl4main.cpp` off the launcher's OWN file-scope pointers — `btl4App` (set to `new_app`
at btl4main.cpp:631) and the global `hWnd` (the real main window). Those live in `btl4main.obj`
and are the actual assigned pointers, so the resolvers are always correct regardless of the
duplicate-symbol binding. `BlitSurface` and the placement code call the resolvers, never the
`application`/`ghWnd` globals. New durable gotcha: `context/reconstruction-gotchas.md` §6
duplicate-GLOBAL corollary.
Verified after the fix (`BT_GLASS_LOG`): `[glass] 'Heat MFD' port=Heat mask=0x4000 pal=3
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).