Glass cockpit: per-display windows (BT_GLASS_PANELS)

Break the desktop glass cockpit's secondary displays out of the single
combined pad panel + D3D gauge strip into ONE window per pod display, each
carrying that display's surface with its RIO button bank, arranged
pod-faithfully around the main view.  New TU engine/MUNGA_L4/L4GLASSWIN.*
(BT_GLASS-only), selected by the -platform glass preset via the new runtime
gate BT_GLASS_PANELS (=0 falls back to the legacy single panel + dock).

- Surfaces are CPU-expanded from the shared gauge buffer
  (SVGA16::ExpandPlaneToBGRA, no D3D) and StretchDIBits'd in -- the D3D
  dev-composite path (dock / window / overlay) stands down while active.
- Buttons sit UNDER the imagery: big hidden click targets that reach into
  the display, with only a small protruding edge showing as a lamp light.
  Red MFD banks tile the top/bottom; radar rails run the sides + a bottom
  row; a Flight Controls window hosts the no-display banks.
- Windows: Heat / Engineering / Comm / Left Weapons / Right Weapons / radar
  + Flight Controls; edge-to-edge (no in-client margin/title; OS caption
  labels each).
- Fix blank surfaces: application/ghWnd are duplicate-defined and bind
  non-deterministically under /FORCE, so the fresh L4GLASSWIN TU read NULL.
  Reach the renderer/window via BTResolveGaugeRenderer/BTResolveMainWindow
  defined in btl4main.cpp off the real btl4App/hWnd pointers.  New gotcha:
  reconstruction-gotchas.md S6 duplicate-GLOBAL corollary.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-20 14:43:40 -05:00
co-authored by Claude Opus 4.8
parent 157da70ec9
commit f338595685
10 changed files with 1175 additions and 10 deletions
+90
View File
@@ -285,3 +285,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).