diff --git a/context/glass-cockpit.md b/context/glass-cockpit.md
index 943ec10..5dff570 100644
--- a/context/glass-cockpit.md
+++ b/context/glass-cockpit.md
@@ -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 |
diff --git a/context/pod-hardware.md b/context/pod-hardware.md
index 768b859..f22f418 100644
--- a/context/pod-hardware.md
+++ b/context/pod-hardware.md
@@ -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] '
' surface= 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:
diff --git a/engine/MUNGA_L4/L4GLASSWIN.cpp b/engine/MUNGA_L4/L4GLASSWIN.cpp
index 7d7a7ee..806594f 100644
--- a/engine/MUNGA_L4/L4GLASSWIN.cpp
+++ b/engine/MUNGA_L4/L4GLASSWIN.cpp
@@ -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 "=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
diff --git a/tools/podprobe.ps1 b/tools/podprobe.ps1
new file mode 100644
index 0000000..46161eb
--- /dev/null
+++ b/tools/podprobe.ps1
@@ -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] '' ... -> 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('# "=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}=,,{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)