POD MFD bring-up: BT_POD_SURFACES bare-panel mode + placement receipts + probe

Nick's crash cart is the first shot at driving the real MFD panels, so the
pieces the cab needs that the desktop glass path lacked:

- BT_POD_SURFACES=1 (L4GLASSWIN): crop every display window to its SURFACE
  (MFDs exactly 640x480, radar 480x640 portrait), drop the on-screen RIO
  button banks -- the cab's buttons are PHYSICAL, so drawing fake ones onto a
  real panel is exactly wrong -- go frameless, and skip the Flight Controls
  pad entirely (6 windows, not 7).  Per-window ",bare" in glass_layout.cfg
  for mixed rigs (idempotent with the global mode).
- Placement receipts: each window logs which PHYSICAL monitor it landed on
  ("[glasswin] 'Heat MFD' surface=Heat at X,Y 640x480 bare -> monitor
  \.\DISPLAYn (...)").  Nobody can see 7 surfaces at once on a cab, and over
  Chrome Remote Desktop you cannot see the panels at all -- the log is the
  only confirmation the map is right.
- tools/podprobe.ps1: run-on-the-pod topology probe (no install/admin) --
  GPUs, every monitor's virtual-desktop rect, EDID make/model, serial ports
  (the RIO board), session type, plus a PROPOSED glass_layout.cfg assigning
  the six surfaces to the non-primary monitors.  Self-tested here (correctly
  reports a single-monitor laptop and declines to map).

Local verify: 5 MFDs at 640x480 bare + portrait radar + Flight Controls
dropped, receipts printed.  KB: pod-hardware §MFD PANELS ON REAL HARDWARE
(incl. WHY the 1995 path is not the route -- NVIDIA Horizontal Span is gone
from post-XP drivers and per-adapter exclusive fullscreen is fragile over a
remote session) + glass-cockpit env table.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Joe DiPrima
2026-08-06 11:31:49 -05:00
co-authored by Claude Fable 5
parent bf39b81b9d
commit 67a4f09e86
4 changed files with 281 additions and 4 deletions
+1
View File
@@ -73,6 +73,7 @@ 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_POD_SURFACES`=1 | **POD MODE** (2026-08-06): crop each panel window to its SURFACE (MFD 640×480, radar 480×640), drop the on-screen button banks + the Flight Controls pad, frameless. For REAL pod glass, where the buttons are physical — see [[pod-hardware]] §MFD PANELS. Per-window: `,bare` in `glass_layout.cfg`. | 6 bare surfaces |
| `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 |
+38
View File
@@ -184,6 +184,44 @@ All cockpit surfaces are bit-plane MASKS over ONE shared `SVGA16` pixelBuffer: `
byte; `Heat`=0x4000, `Mfd2`=0x0400, `Comm`=0x8000, `Mfd1`=0x0100, `Mfd3`=0x1000; `Eng1-3` =
engineering-mode alt planes; `overlay`=0x00C0 (shares the sec surface). See [[gauges-hud]]. [T2]
## MFD PANELS ON REAL HARDWARE — the bring-up path (2026-08-06) [T2 local / T4 on-pod]
Nick's crash cart (pod hardware + Chrome Remote Desktop on a burner account) is the first chance
to drive the real panels. **The 1995 display path is NOT the way in.** That rig spanned the five
MFDs as ONE 1280×480 surface via **NVIDIA Horizontal Span, which every driver after XP dropped**;
`SVGA16::BuildWindows` also wants an exclusive-fullscreen D3D device per adapter, which is
fragile on modern drivers and over a remote session. Both are still in-tree and still the
authentic reference — they are just not the bring-up route.
**The route is the glass per-display windows** ([[glass-cockpit]]): one window per surface, which
maps 1:1 onto one physical panel per MFD, needs no special driver, and is already field-proven on
desktops. Two pieces were added for the cab:
- **`BT_POD_SURFACES=1` (pod surface mode, L4GLASSWIN)** — crops every window to its SURFACE
(MFDs exactly 640×480, radar 480×640 portrait), drops the on-screen RIO button banks (the cab's
buttons are PHYSICAL — drawing fake ones over a real panel is precisely wrong), goes frameless,
and does not create the Flight Controls pad at all (6 windows, not 7). Per-window equivalent:
append `,bare` to a line in `glass_layout.cfg` (mixed rigs).
- **Placement receipts** — every window logs `[glasswin] '<title>' surface=<port> at X,Y WxH
bare -> monitor \.\DISPLAYn (origin WxH, PRIMARY)`. On a cab nobody can see seven surfaces at
once, and over CRD you cannot see the panels at all: the log IS the confirmation that a picture
landed on the right glass.
**Runbook** (`tools/podprobe.ps1`, PowerShell, no install/admin — run it ON the pod PC):
1. Probe: GPUs, every monitor's virtual-desktop rect, EDID make/model (identifies the original
panels), serial ports (the RIO board), session type, and a PROPOSED `glass_layout.cfg` that
assigns the six surfaces to the non-primary monitors top-to-bottom/left-to-right, centred.
2. Drop the cfg in the game's working directory; run with `BT_GLASS=1 BT_GLASS_PANELS=1
BT_POD_SURFACES=1 BT_GLASS_LAYOUT=load` (`=save` to persist drags instead).
3. Read the receipts; re-assign titles to monitors in the cfg until each picture is on its panel.
Surface→panel roles: Heat MFD = upper left (coolant), Engineering = upper centre, Comm MFD =
upper right (hot box), Left/Right Weapons = lower left/right, Secondary/Radar = the secondary
screen (portrait). Main 3D view stays the game's own window on the main-view monitor.
**Known constraints / open on-pod questions:** whether the panels are attached to this PC at all
(the probe answers it); whether Windows offers a 640×480 mode on them (if not, the surface renders
at native size CENTRED, not scaled — a scale-to-fit option is the obvious follow-up); whether
Chrome Remote Desktop holds the CONSOLE session (it should — an RDP session would get a virtual
display and light nothing); and the RIO serial input, which is a separate task from the displays.
## The 1995 player manual — alignment audit (2026-07-18) [T1, primary source]
`reference/manual/Tesla40_BT_manual.pdf` (34pp, from Nick). CONFIRMS the reconstruction on
every checked control behavior:
+92 -4
View File
@@ -407,6 +407,52 @@ enum { LayoutOff = 0, LayoutLoad = 1, LayoutSave = 2 };
static const char *layoutFileName = "glass_layout.cfg";
// POD SURFACE MODE (BT_POD_SURFACES=1, 2026-08-06) -- the REAL pod bring-up
// shape. On the actual cab each MFD is its own 640x480 panel with PHYSICAL
// buttons and lamps around it, so the desktop chrome the glass windows draw
// (the RIO button bank + the frame) is exactly wrong there: it would paint
// fake buttons onto a panel that has real ones behind glass. In pod mode a
// window is cropped to its SURFACE, frameless, and the bank is dropped:
// one clean picture per physical panel, positioned by glass_layout.cfg.
// The Flight Controls window (no surface at all) is not created.
// BT_POD_SURFACES=1 -> every display window goes bare
// glass_layout.cfg "<title>=x,y,bare" -> just that one (mixed rigs)
static int
PodSurfaceMode()
{
static int m = -1;
if (m < 0)
{
const char *e = getenv("BT_POD_SURFACES");
m = (e != NULL && *e != '\0' && *e != '0') ? 1 : 0;
if (m)
DEBUG_STREAM << "[glasswin] POD SURFACE MODE: bare surfaces, no "
"button banks (physical pod buttons assumed)\n" << std::flush;
}
return m;
}
// Crop a built window to its surface: drop the bank, move the picture to the
// client origin, go frameless. Called at the end of each Build* when pod mode
// is on (and from LoadLayout for a per-window ",bare").
static void
MakeBareSurface(GWin &w)
{
if (w.portPrimary == NULL) // Flight Controls -- nothing to show
{
w.clientW = w.clientH = 0;
w.buttonCount = 0;
return;
}
const int sw = w.surfaceRect.right - w.surfaceRect.left;
const int sh = w.surfaceRect.bottom - w.surfaceRect.top;
w.buttonCount = 0; // the pod's buttons are real
SetRect(&w.surfaceRect, 0, 0, sw, sh);
w.clientW = sw;
w.clientH = sh;
w.noFrame = 1;
}
// A framed window is a normal tool window; ",noframe" makes it a bare
// WS_POPUP -- no caption, no border, nothing but the display and its buttons.
static DWORD
@@ -580,16 +626,15 @@ static void
// Trailing options after the numbers, comma-separated. Unknown ones
// are ignored (so an older build reading a newer file just loses the
// option, never the line) -- the bindings.txt grammar rule.
int noframe = 0;
int noframe = 0, bare = 0;
for (const char *opt = strchr(eq + 1, ','); opt != NULL; opt = strchr(opt + 1, ','))
{
const char *t = opt + 1;
while (*t == ' ' || *t == '\t') ++t;
if (_strnicmp(t, "noframe", 7) == 0)
{
noframe = 1;
break;
}
else if (_strnicmp(t, "bare", 4) == 0)
bare = 1; // pod panel: surface only (see PodSurfaceMode)
}
GWin *gw = FindGWinByTitle(s);
@@ -598,6 +643,8 @@ static void
gw->wantX = x;
gw->wantY = y;
gw->noFrame = noframe;
if (bare && gw->buttonCount > 0)
MakeBareSurface(*gw); // per-window pod panel (mixed rigs)
gw->restored = 1;
++restored;
}
@@ -1090,6 +1137,16 @@ void
BuildFlight(gWins[6]); // 0x38-0x47
gWinCount = 7;
// POD SURFACE MODE: crop every display to its picture and drop the Flight
// Controls pad (it is pure desktop chrome -- the cab has a real stick).
if (PodSurfaceMode())
{
for (int i = 0; i < gWinCount; ++i)
MakeBareSurface(gWins[i]);
if (gWins[6].portPrimary == NULL)
gWinCount = 6; // drop Flight Controls
}
titleFont = CreateFontW(-11, 0, 0, 0, FW_BOLD, 0, 0, 0,
DEFAULT_CHARSET, 0, 0, CLEARTYPE_QUALITY, 0, L"Segoe UI");
buttonFont = CreateFontW(-10, 0, 0, 0, FW_NORMAL, 0, 0, 0,
@@ -1145,6 +1202,37 @@ void
SetLayeredWindowAttributes(w.hwnd, 0, 255, LWA_ALPHA);
SetTimer(w.hwnd, RepaintTimerId, RepaintMilliseconds, NULL);
ShowWindow(w.hwnd, SW_SHOWNOACTIVATE);
// PLACEMENT RECEIPT (pod bring-up, 2026-08-06): which PHYSICAL monitor
// each display actually landed on. On a real cab -- or any remote
// session -- nobody can see all seven surfaces at once, so the log is
// the only way to confirm the map without walking around the pod.
{
RECT wr = { 0, 0, 0, 0 };
GetWindowRect(w.hwnd, &wr);
HMONITOR mon = MonitorFromWindow(w.hwnd, MONITOR_DEFAULTTONEAREST);
MONITORINFOEXA mi;
memset(&mi, 0, sizeof(mi));
mi.cbSize = sizeof(mi);
const char *devName = "?";
int mx = 0, my = 0, mw = 0, mh = 0, primary = 0;
if (mon != NULL && GetMonitorInfoA(mon, (MONITORINFO *)&mi))
{
devName = mi.szDevice;
mx = mi.rcMonitor.left; my = mi.rcMonitor.top;
mw = mi.rcMonitor.right - mi.rcMonitor.left;
mh = mi.rcMonitor.bottom - mi.rcMonitor.top;
primary = (mi.dwFlags & MONITORINFOF_PRIMARY) ? 1 : 0;
}
DEBUG_STREAM << "[glasswin] '" << w.title << "' surface="
<< (w.portPrimary ? w.portPrimary : "-")
<< " at " << wr.left << "," << wr.top
<< " " << (wr.right - wr.left) << "x" << (wr.bottom - wr.top)
<< (w.noFrame ? " bare" : " framed")
<< " -> monitor " << devName
<< " (" << mx << "," << my << " " << mw << "x" << mh
<< (primary ? ", PRIMARY)" : ")") << "\n" << std::flush;
}
}
DEBUG_STREAM << "[glasswin] per-display cockpit up (" << gWinCount
+150
View File
@@ -0,0 +1,150 @@
# podprobe.ps1 -- pod / crash-cart display + I/O topology probe.
#
# Run ON THE POD PC (PowerShell, no install, no admin):
# powershell -ExecutionPolicy Bypass -File podprobe.ps1
#
# Prints and writes podprobe.txt: GPUs, every monitor with its VIRTUAL DESKTOP
# rect (the coordinates glass_layout.cfg needs), EDID make/model (to identify
# the original pod panels), serial ports (the RIO board), and a PROPOSED
# glass_layout.cfg mapping the six pod surfaces onto the non-primary monitors.
#
# Then, in the game's working directory:
# copy the proposed glass_layout.cfg in
# set BT_GLASS=1 & set BT_GLASS_PANELS=1 & set BT_POD_SURFACES=1
# set BT_GLASS_LAYOUT=load (add =save to persist drags instead)
# btl4.exe -platform pod
# The log prints one "[glasswin] '<title>' ... -> monitor \\.\DISPLAYn" line per
# surface: that is the receipt that a picture landed on the right panel.
$ErrorActionPreference = 'Continue'
$out = New-Object System.Collections.ArrayList
function W($s) { [void]$out.Add($s); Write-Host $s }
W "==================== BT411 POD PROBE ===================="
W ("host : {0}" -f $env:COMPUTERNAME)
W ("when : {0}" -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'))
W ("windows : {0}" -f (Get-CimInstance Win32_OperatingSystem).Caption)
W ""
W "---- GPUs ----"
try {
Get-CimInstance Win32_VideoController | ForEach-Object {
W (" {0}" -f $_.Name)
W (" driver {0} ({1}) mode {2}x{3} ram {4} MB" -f `
$_.DriverVersion, $_.DriverDate, $_.CurrentHorizontalResolution,
$_.CurrentVerticalResolution, [int]($_.AdapterRAM / 1MB))
}
} catch { W " (video controller query failed: $_)" }
W ""
W "---- MONITORS (virtual-desktop rects -- these are the layout coordinates) ----"
Add-Type -AssemblyName System.Windows.Forms | Out-Null
$screens = [System.Windows.Forms.Screen]::AllScreens
$i = 0
foreach ($s in $screens) {
$b = $s.Bounds
W (" [{0}] {1}{2} x={3} y={4} {5}x{6} bpp={7}" -f `
$i, $s.DeviceName, $(if ($s.Primary) { " *PRIMARY*" } else { "" }),
$b.X, $b.Y, $b.Width, $b.Height, $s.BitsPerPixel)
$i++
}
W (" total screens: {0}" -f $screens.Count)
W ""
W "---- EDID identity (which physical panel is which) ----"
try {
Get-CimInstance -Namespace root\wmi -ClassName WmiMonitorID | ForEach-Object {
$mk = -join ($_.ManufacturerName | Where-Object { $_ -ne 0 } | ForEach-Object { [char]$_ })
$nm = -join ($_.UserFriendlyName | Where-Object { $_ -ne 0 } | ForEach-Object { [char]$_ })
$sn = -join ($_.SerialNumberID | Where-Object { $_ -ne 0 } | ForEach-Object { [char]$_ })
W (" {0} | make={1} model='{2}' serial={3} year={4}" -f `
$_.InstanceName, $mk, $nm, $sn, $_.YearOfManufacture)
}
} catch { W " (EDID query failed -- normal over some remote sessions: $_)" }
W ""
W "---- SERIAL PORTS (the RIO board lives on one of these) ----"
try {
$sp = Get-CimInstance Win32_SerialPort
if ($sp) { $sp | ForEach-Object { W (" {0} {1}" -f $_.DeviceID, $_.Name) } }
else {
$reg = 'HKLM:\HARDWARE\DEVICEMAP\SERIALCOMM'
if (Test-Path $reg) {
(Get-ItemProperty $reg).PSObject.Properties |
Where-Object { $_.Name -notlike 'PS*' } |
ForEach-Object { W (" {0} -> {1}" -f $_.Name, $_.Value) }
} else { W " (none found)" }
}
} catch { W " (serial query failed: $_)" }
W ""
W "---- REMOTE SESSION ----"
W (" SESSIONNAME={0} (a console session drives the real panels; an RDP" -f $env:SESSIONNAME)
W " session gets a VIRTUAL display and will NOT light the pod monitors."
W " Chrome Remote Desktop attaches to the CONSOLE -- that is what we want."
W ""
# ---------------- proposed layout ----------------
# Pod surface roles, in the order the cab reads them. Sizes are the native
# surface sizes the game creates in BT_POD_SURFACES mode.
$roles = @(
@{ title = 'Heat MFD'; w = 640; h = 480; note = 'upper LEFT (coolant)' },
@{ title = 'Engineering'; w = 640; h = 480; note = 'upper CENTER (engineering)' },
@{ title = 'Comm MFD'; w = 640; h = 480; note = 'upper RIGHT (hot box / comm)' },
@{ title = 'Left Weapons'; w = 640; h = 480; note = 'lower LEFT (weapons)' },
@{ title = 'Right Weapons'; w = 640; h = 480; note = 'lower RIGHT (weapons)' },
@{ title = 'Secondary / Radar'; w = 480; h = 640; note = 'secondary screen (radar, portrait)' }
)
$targets = @($screens | Where-Object { -not $_.Primary } | Sort-Object { $_.Bounds.Y }, { $_.Bounds.X })
W "---- PROPOSED glass_layout.cfg ----"
if ($targets.Count -eq 0) {
W " ! No non-primary monitors found. Either the pod panels are not attached to"
W " this PC, or this is a remote/virtual display session. Nothing to map yet."
} else {
W (" {0} non-primary monitor(s) available; assigning in top-to-bottom, left-to-right order." -f $targets.Count)
if ($targets.Count -lt $roles.Count) {
W (" ! Fewer panels than surfaces -- the last {0} surface(s) stay on the primary display." -f ($roles.Count - $targets.Count))
}
}
$cfg = New-Object System.Collections.ArrayList
[void]$cfg.Add('# BT411 glass cockpit window layout -- generated by tools/podprobe.ps1')
[void]$cfg.Add('# "<title>=x,y,w,h,noframe,bare" (bare = surface only, no desktop buttons)')
[void]$cfg.Add('# Coordinates are VIRTUAL DESKTOP pixels; run the probe again after any')
[void]$cfg.Add('# display rearrangement, because Windows renumbers the desktop origin.')
for ($r = 0; $r -lt $roles.Count; $r++) {
$role = $roles[$r]
if ($r -lt $targets.Count) {
$b = $targets[$r].Bounds
# centre the surface on the panel (exact fit when the panel is 640x480)
$x = $b.X + [int](($b.Width - $role.w) / 2)
$y = $b.Y + [int](($b.Height - $role.h) / 2)
$line = "{0}={1},{2},{3},{4},noframe,bare" -f $role.title, $x, $y, $role.w, $role.h
[void]$cfg.Add($line)
W (" {0,-18} -> {1} ({2}x{3}) at {4},{5} [{6}]" -f `
$role.title, $targets[$r].DeviceName, $b.Width, $b.Height, $x, $y, $role.note)
} else {
[void]$cfg.Add(("# {0}=<x>,<y>,{1},{2},noframe,bare # no panel assigned" -f $role.title, $role.w, $role.h))
}
}
$cfgPath = Join-Path (Get-Location) 'glass_layout.cfg.proposed'
$cfg | Set-Content -Path $cfgPath -Encoding ASCII
W ""
W (" wrote {0}" -f $cfgPath)
W " -> review it, rename to glass_layout.cfg in the game's working directory."
W ""
W "---- NOTES ----"
W " * The pod's main 3D view is the GAME's own window (not a glass panel); put it"
W " on the main-view monitor with the normal window/fullscreen controls."
W " * 640x480 panels: if Windows does not offer that mode, the surface still"
W " renders 640x480 inside whatever mode the panel runs -- centred, not scaled."
W " * The 1995 rig spanned the five MFDs as ONE 1280x480 surface via NVIDIA"
W " Horizontal Span, which modern drivers removed. This per-panel window path"
W " replaces it and needs no special driver."
$txt = Join-Path (Get-Location) 'podprobe.txt'
$out | Set-Content -Path $txt -Encoding ASCII
Write-Host ""
Write-Host ("full report written to {0}" -f $txt)