Glass cockpit refit: one mode resolver, one button geometry, and a cockpit that scales

Replicates the RP412 cockpit line into BT411's own architecture -- porting the
geometry and keeping our renderers, so BT_SHOT single-frame verification stays
intact.

MODE RESOLVER.  Where the secondary displays go was decided in TWO places with
duplicated precedence, and the boot banner read NEITHER -- it announced
"per-display cockpit windows" for every glass boot, surround included.  The
split had also broken BT_COCKPIT=0: documented as the dock-bottom opt-out, the
profile block converted it to BT_GLASS_PANELS=1, so the docked strip was
UNREACHABLE under the glass profile.  One resolver now, consumed by the banner,
the pad-panel decision and the window sizing; BT_GLASS_PANELS is explicit-only;
dock/window modes auto-raise BT_PAD_PANEL so the button field always has a home.

L4RIOBANK -- one button geometry.  Both renderers carried their own copy and had
drifted: an MFD button was 156x138 reaching under the glass in the exploded
window and a 76x24 sliver entirely OUTSIDE the glass in the surround.  One
module owns it now, both are consumers, placement stays per-renderer.  The pod's
under-glass rule (RP412 L4MFDVIEW): reach half the glass in behind the display,
leave a lamp strip clearing the edge, paint buttons first and imagery over --
so the lamp reads as a bar and practically the whole display is the press
target.  The strip scales off the display's SHORT axis (the map is portrait)
with a readable floor.  Retired L4GLASSWIN's three local placers and seven
layout constants.

LAMP FLASH DECODE was wrong [T1].  BTLampBrightnessOf returned max(state1,
state2) and blanked on the alternate phase; RIO::LampState (L4RIO.h [T0]) says
solid shows state 1 and flashing ALTERNATES the two.  Agrees only when one state
is Off -- true for the Panic lamp, which is why it survived -- but L4LAMP.cpp:252
commands flashFast + state1Dim + state2Bright, a dim->bright pulse that rendered
as a hard bright->off blink.  Three copies existed (l4vb16.h, L4GLASSWIN,
L4PADPANEL), all three wrong; the two locals now forward to the one fixed inline.

THE COCKPIT SCALES.  The fixed canvas was stretched into whatever the client
area was, so a window dragged to a different shape squashed the instruments (the
projection was aspect-corrected in task #20; the panels never were).  Now one
uniform scale, centred, leftover black.  D3D9 applies it as a Present
destination rect, which DISCARD forbids -- so the WINDOWED swap effect becomes
COPY when the surround is up and multisampling is off.  The click mapping had to
follow (mapping against the full client drifts the hit test off every button by
the bar width), as did the world aspect (under a uniform scale it is the view
rect's own).  -fit / -windowed-fullscreen: borderless over the monitor.

 - ordering trap: the first WM_SIZE beats the device, so a -fit boot logged
   aspect=3.14 and applied it on frame 1.  The letterbox INTENT is decided in
   btl4main; L4VIDEO only confirms or withdraws it.

PLAYER-TUNABLE DISPLAYS.  BT_MFD_SCALE (+ _UL/_UC/_UR/_LL/_LR), BT_RADAR_SCALE,
BT_RADAR_POS (CENTER/LEFT/RIGHT/MIDLEFT/MIDRIGHT).  The surround BANDS derive
from the resolved sizes -- that is why the sizes could not stay constants: the
band a display hangs in has to grow with it or the canvas clips it.  100%
reproduces the historical L276 R276 T223 B336 exactly.  A corner map goes flush
to the CANVAS edge and the lower MFD slides beside it (measuring off the view
edge overlapped them by 232px).

MAP LEGEND GRID -- measured, not inherited.  scratchpad/measurelegend.py over a
native capture: top 3, cell 102, pitch 107 of 640.  RP412's map is 13 + 6x102 @
105 -- same cell height, different top and pitch, so its numbers do NOT
transfer.  Our old even division had the pitch right by luck and sat 3px high of
the labels.

environ.ini.  It was read ~300 lines into WinMain, AFTER the platform-profile
block had run its getenv()s -- so every setting the profile reads was silently
ignored FROM THE FILE and only worked as a real env var.  It also putenv()'d
comments verbatim.  Now loaded immediately after the first-breath line, comments
skipped, the real environment WINS over the file, and a fully documented default
is written on first run (the bindings.txt convention: untracked, so
extract-over-top never clobbers a player's settings).

VERIFICATION HARNESS (new, reusable): BT_RIOBANK_LOG=1 dumps every bank;
checkbank.py proves no address is SHADOWED (an address whose rect is covered by
earlier buttons is dead however big it looks -- the overlapping under-glass banks
make that a live hazard); clickbank.py posts a real click at every button centre.

Verified: 72/72 placed, 0 shadowed, 72/72 dispatched in BOTH modes, after a
resize, at 150%/135%, and at 75%+BOTTOMRIGHT; wide/tall drags and -fit
undistorted on a 3440x1440; exploded/dock/pod/dev un-regressed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-26 02:23:40 -05:00
co-authored by Claude Opus 5
parent 1cda880c6d
commit 61563c9efe
15 changed files with 1670 additions and 282 deletions
+318 -55
View File
@@ -142,10 +142,18 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
// new client aspect so the scene doesn't stretch fat/skinny (the D3D9
// backbuffer stays at the configured size and is stretched into the
// client area; rendering with the client aspect cancels the stretch).
// Under the cockpit LETTERBOX the canvas is scaled uniformly instead,
// so the aspect comes from the view rect -- BTWorldAspectOf handles it.
if (wParam != SIZE_MINIMIZED && LOWORD(lParam) > 0 && HIWORD(lParam) > 0)
{
extern void L4NotifyWindowResized(int client_w, int client_h);
L4NotifyWindowResized((int)LOWORD(lParam), (int)HIWORD(lParam));
// Repaint the letterbox bars: a COPY present only writes its dest
// rect, so whatever the old bars held would otherwise persist. The
// class brush is black, so an erase is all it takes.
extern int gBTCockpitLetterbox;
if (gBTCockpitLetterbox)
InvalidateRect(hWnd, NULL, TRUE);
}
return 0;
case WM_CLOSE:
@@ -215,6 +223,194 @@ static LONG WINAPI
return EXCEPTION_EXECUTE_HANDLER; // die (after the evidence is out)
}
//===========================================================================//
// environ.ini -- the player's settings file (cwd = content\). One KEY=VALUE
// per line; the real environment always WINS, so a launcher .bat or a shell
// export overrides the file rather than fighting it.
//
// ⚠ Loaded EARLY (2026-07-26). It used to be read ~300 lines into WinMain,
// after the platform-profile block had already 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
// turned a commented-out option into an environment variable literally named
// "#BT_MFD_SCALE" -- harmless, but it meant shipping a self-documenting file
// would have littered the environment with junk.
//===========================================================================//
//
// The shipped default, written on first run when the file is absent -- the
// bindings.txt convention ([[glass-cockpit]] §bindings.txt is a COMPATIBILITY
// SURFACE): the player customizes it, it stays UNTRACKED, and an
// extract-over-top upgrade therefore never clobbers their settings. Every
// option ships COMMENTED OUT, so a fresh install applies nothing and behaves
// exactly as it did before the file existed.
//
static const char *kEnvironIniDefault =
"# ============================================================================\n"
"# environ.ini -- BattleTech 4.11 settings\n"
"# ============================================================================\n"
"# One KEY=VALUE per line, read at game start. Lines starting with # or ;\n"
"# are comments. A real environment variable always WINS over this file, so\n"
"# a launcher .bat can override anything here. Delete a line to fall back to\n"
"# the built-in default.\n"
"#\n"
"# Input bindings live in bindings.txt beside this file (written with the\n"
"# full documented layout on first run; delete it to restore defaults).\n"
"\n"
"# ---- The cockpit ------------------------------------------------------------\n"
"\n"
"# Where the five MFDs and the map go:\n"
"# (unset) the COCKPIT SURROUND -- instruments composited around\n"
"# the viewscreen in one window (the default)\n"
"# BT_GLASS_PANELS=1 one desktop window PER display, arranged around the\n"
"# game window (the 'exploded' view -- handy for reading\n"
"# or screenshotting a display at full size)\n"
"# BT_COCKPIT=0 the plain docked gauge strip along the bottom\n"
"#BT_GLASS_PANELS=1\n"
"#BT_COCKPIT=0\n"
"\n"
"# Size of the secondary displays in the cockpit surround, as a percentage of\n"
"# their pod size. The pod bolted them down at one size; on a big panel there\n"
"# is room to trade viewscreen for instrument, so turn these up if you want to\n"
"# actually read the other displays while you fly. 100 = as the pod had them.\n"
"# Range 25-200 (out-of-range or unreadable values fall back to the group\n"
"# setting, then to 100). The surround grows to fit whatever you ask for.\n"
"#\n"
"# BT_MFD_SCALE sets all five MFDs at once...\n"
"#BT_MFD_SCALE=100\n"
"# ...and any single display can override it:\n"
"# UL upper left (heat) UC upper center (engineering)\n"
"# UR upper right (comm) LL lower left (left weapons)\n"
"# LR lower right (right weapons)\n"
"#BT_MFD_SCALE_UL=100\n"
"#BT_MFD_SCALE_UC=100\n"
"#BT_MFD_SCALE_UR=100\n"
"#BT_MFD_SCALE_LL=100\n"
"#BT_MFD_SCALE_LR=100\n"
"\n"
"# The portrait map/radar, sized on its own.\n"
"#BT_RADAR_SCALE=100\n"
"\n"
"# Where the map sits:\n"
"# CENTER bottom centre, under the viewscreen, as the pod had it\n"
"# (default; BOTTOM and CENTRE mean the same)\n"
"# LEFT bottom left corner (or BOTTOMLEFT)\n"
"# RIGHT bottom right corner (or BOTTOMRIGHT)\n"
"# MIDLEFT left edge, halfway up (or LEFTCENTER)\n"
"# MIDRIGHT right edge, halfway up (or RIGHTCENTER)\n"
"# Anywhere but CENTER stops it sitting where the road is. In a bottom corner\n"
"# the lower MFD whose corner it takes slides inboard beside it; halfway up a\n"
"# side it leaves the bottom row entirely.\n"
"#BT_RADAR_POS=CENTER\n"
"\n"
"# Phosphor colour of the mono MFDs, RRGGBB. Default is a green tube.\n"
"#BT_COCKPIT_TINT=21FF42\n"
"\n"
"# ---- Display ----------------------------------------------------------------\n"
"\n"
"# The cockpit is fitted into the window at ONE uniform scale, centred, with\n"
"# the leftover black -- drag the window to any shape and nothing distorts.\n"
"# Launch with -fit (or -windowed-fullscreen) for a borderless window over the\n"
"# whole monitor.\n"
"\n"
"# Simulation/render frame rate, integer frames per second. The desktop\n"
"# default is 60; the arcade pods shipped at 25.\n"
"#TARGETFPS=60\n"
"\n"
"# Anti-aliasing sample count, passed straight to Direct3D 9: 0 = off, else\n"
"# 2..16 as the GPU supports. NOTE: turning this on disables the letterbox\n"
"# fit above (the two use incompatible swap modes) and the cockpit stretches\n"
"# to the window instead.\n"
"#MULTISAMPLE=0\n"
"\n"
"# Particle budget, integer. Default 8192.\n"
"#MAXPARTICLES=8192\n"
"\n"
"# ---- Optional ---------------------------------------------------------------\n"
"\n"
"# RGB keyboard lamp mirror (Windows Dynamic Lighting): keys bound to a lamp\n"
"# button in bindings.txt glow with the panel -- yellow for the map's side\n"
"# columns, red for the rest -- and flash in step with the on-screen buttons.\n"
"# Per-key keyboards light each bound key; zone-lit boards mirror the\n"
"# strongest lamp board-wide. Unset or nonzero = on; 0 = off. A machine\n"
"# without Dynamic Lighting logs once and stays dormant.\n"
"#BT_KEYLIGHT=0\n"
"\n"
"# ---- Multiplayer ------------------------------------------------------------\n"
"\n"
"# 1 = Steam networking (lobbies, FakeIP mesh). Needs the Steam client\n"
"# running; without it the game logs the reason and falls back to Winsock.\n"
"#BT_STEAM_NET=1\n";
static void BTWriteDefaultEnvironIni(void)
{
FILE *probe;
if (fopen_s(&probe, "environ.ini", "r") == 0) // already there -- leave it alone
{
fclose(probe);
return;
}
FILE *out;
if (fopen_s(&out, "environ.ini", "w") != 0)
return;
fputs(kEnvironIniDefault, out);
fclose(out);
std::cout << "[boot] wrote a default content\\environ.ini (all options "
"commented out -- edit to taste)" << std::endl << std::flush;
}
static void BTLoadEnvironIni(void)
{
BTWriteDefaultEnvironIni();
FILE *file;
if (fopen_s(&file, "environ.ini", "r") != 0)
return;
char line[1024];
int applied = 0, skipped = 0;
while (fgets(line, sizeof(line), file))
{
// strip EOL + trailing blanks
int n = (int)strlen(line);
while (n > 0 && (line[n-1] == '\n' || line[n-1] == '\r' ||
line[n-1] == ' ' || line[n-1] == '\t'))
line[--n] = 0;
const char *p = line;
while (*p == ' ' || *p == '\t') ++p;
if (*p == 0 || *p == '#' || *p == ';') // blank or comment
continue;
const char *eq = strchr(p, '=');
if (eq == NULL || eq == p) // no key, or no '=' at all
{
++skipped;
continue;
}
// The real environment wins: only set what is not already set, so a
// launcher or a shell export beats the file.
char key[256];
size_t klen = (size_t)(eq - p);
if (klen >= sizeof(key)) { ++skipped; continue; }
memcpy(key, p, klen);
key[klen] = 0;
while (klen > 0 && (key[klen-1] == ' ' || key[klen-1] == '\t'))
key[--klen] = 0;
if (getenv(key) != NULL)
continue;
putenv(p);
++applied;
}
fclose(file);
std::cout << "[boot] environ.ini: " << applied << " setting(s) applied"
<< (skipped ? ", " : "") << (skipped ? std::to_string(skipped) : std::string())
<< (skipped ? " line(s) ignored" : "") << std::endl << std::flush;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
// Boot tick for the relaunch storm damper (btl4console.cpp, issue #33):
@@ -279,6 +475,10 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
<< (getenv("BT_FE_LOOP") ? " (relaunched generation)" : " (first process)")
<< std::endl << std::flush;
// The player's settings, before ANYTHING reads the environment (the
// platform profile, the joystick wizard, the layout resolver...).
BTLoadEnvironIni();
// BT_JOYCONFIG=1: the generic-joystick capture wizard (flight sticks /
// HOTAS / pedals -- L4JOY.h). Console prompts detect which device/axis
// the player moves for each pod control and write the joystick section
@@ -490,21 +690,12 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
if (getenv("BT_DEV_GAUGES") == NULL) putenv("BT_DEV_GAUGES=1");
if (getenv("L4PLASMA") == NULL) putenv("L4PLASMA=SCREEN");
// DEFAULT LAYOUT = the COCKPIT SURROUND (single window: the six gauge
// surfaces + clickable button lamps composited AROUND the centered 3D
// view). Opt out with BT_COCKPIT=0 (dock-bottom strip) or
// BT_GLASS_PANELS=1 (the per-display windows). When cockpit is the
// default the buttons live IN the main window, so no separate pad/panel
// window is created (leave BT_PAD_PANEL / BT_GLASS_PANELS unset).
int cockpitDefault = 1;
if (getenv("BT_COCKPIT") && getenv("BT_COCKPIT")[0] == '0') cockpitDefault = 0;
if (getenv("BT_GLASS_PANELS") && getenv("BT_GLASS_PANELS")[0] != '0') cockpitDefault = 0;
if (getenv("BT_DEV_GAUGES_WINDOW") || getenv("BT_DEV_GAUGES_DOCK")) cockpitDefault = 0;
if (!cockpitDefault)
{
if (getenv("BT_PAD_PANEL") == NULL) putenv("BT_PAD_PANEL=1");
if (getenv("BT_GLASS_PANELS") == NULL) putenv("BT_GLASS_PANELS=1");
}
// The SECONDARY-DISPLAY LAYOUT (surround / per-display windows / docked
// strip / separate window) is resolved once, below, after the shared
// defaults land -- it used to be half-decided here, which is how
// BT_COCKPIT=0 came to mean "per-display windows" despite being
// documented as the dock-bottom opt-out (the dock was unreachable under
// glass). This block now only sets the profile's env presets.
}
#endif
else
@@ -531,12 +722,64 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
if (getenv("BT_DEV_GAUGES") != NULL && getenv("L4GAUGE") == NULL)
putenv("L4GAUGE=640x480x16");
// -------------------------------------------------------------------------
// SECONDARY-DISPLAY LAYOUT -- resolved ONCE, here, now that every profile
// putenv has landed. It used to be decided TWICE (the glass profile block
// picked BT_PAD_PANEL/BT_GLASS_PANELS; the window-sizing block further down
// re-derived cockpit-vs-dock from the same env with its own copy of the
// precedence) and the boot banner read NEITHER -- it announced "per-display
// cockpit windows" for every glass boot, including the surround default.
// The split also broke BT_COCKPIT=0: documented as the dock-bottom opt-out,
// it actually landed on the per-display windows, leaving the docked strip
// unreachable under the glass profile.
//
// One resolver, one answer, consumed by the banner, the pad-panel decision
// and the sizing block. Precedence:
// BT_GLASS_PANELS!=0 per-display cockpit windows (explicit only)
// BT_DEV_GAUGES_WINDOW the legacy separate MFD window
// BT_DEV_GAUGES_DOCK the docked bottom strip
// BT_COCKPIT=0 ...also the docked strip (the documented opt-out)
// (nothing) COCKPIT SURROUND -- the glass default
// -------------------------------------------------------------------------
enum BTGlassLayout { GlassLayoutNone, GlassLayoutCockpit, GlassLayoutPanels,
GlassLayoutDock, GlassLayoutWindow };
int glassLayout = GlassLayoutNone;
if (!fe_menu_mode && getenv("BT_DEV_GAUGES") != NULL)
{
int panels = 0;
#ifdef BT_GLASS
{ extern int BTGlassPanelsActive(); panels = BTGlassPanelsActive(); }
#endif
const char *ck = getenv("BT_COCKPIT");
if (panels) glassLayout = GlassLayoutPanels;
else if (getenv("BT_DEV_GAUGES_WINDOW")) glassLayout = GlassLayoutWindow;
else if (getenv("BT_DEV_GAUGES_DOCK")) glassLayout = GlassLayoutDock;
else if (ck != NULL && ck[0] == '0') glassLayout = GlassLayoutDock;
else glassLayout = GlassLayoutCockpit;
// The surround and the per-display windows carry their own buttons; the
// dock strip and the separate MFD window don't, so the single combined
// pad panel supplies them (else the whole 72-button field is unclickable).
if ((glassLayout == GlassLayoutDock || glassLayout == GlassLayoutWindow)
&& getenv("BT_PAD_PANEL") == NULL)
putenv("BT_PAD_PANEL=1");
}
static const char *kGlassLayoutName[] = {
"no dev gauges",
"cockpit surround",
"per-display cockpit windows [BT_GLASS_PANELS]",
"docked gauge strip",
"separate MFD window"
};
std::cout << "[boot] platform profile: "
<< (fe_menu_mode ? "MENU (front end -- no profile applied)"
: gBTPlatformPod ? "POD (RIO cockpit input; multi-surface gauges/MFDs via pod hardware or explicit L4GAUGE)"
: gBTPlatformGlass ? "GLASS (PadRIO + per-display cockpit windows [BT_GLASS_PANELS] + plasma window)"
: "DEV (single window + keyboard)")
<< std::endl << std::flush;
: gBTPlatformGlass ? "GLASS (PadRIO + plasma window)"
: "DEV (single window + keyboard)");
if (!fe_menu_mode)
std::cout << " [secondary displays: " << kGlassLayoutName[glassLayout] << "]";
std::cout << std::endl << std::flush;
#ifdef BT_GLASS
//
@@ -656,22 +899,7 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
}
if (getenv("BT_FORCE_FIRE") != NULL) gBTDrive.fireForced = 1;
// Optional environ.ini overrides (same convention as RP).
FILE *file;
char line[1024];
if (fopen_s(&file, "environ.ini", "r") == 0)
{
while (!feof(file))
{
if (fgets(line, sizeof(line), file))
{
for (int i = (int)strlen(line); i >= 0; i--)
if (line[i] == '\n' || line[i] == '\r') line[i] = 0;
putenv(line);
}
}
fclose(file);
}
// (environ.ini is loaded much earlier now -- see BTLoadEnvironIni.)
// Version scheme: 4.10 = the 1995 arcade release; 4.11 = this win32
// reconstruction; build = git commit count, hash pins exact source
@@ -783,25 +1011,11 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
extern int gBTCockpitCanvasW, gBTCockpitCanvasH;
extern int BTGaugeStripHeightFor(int width);
extern void BTCockpitCanvasFor(int viewW, int viewH, int *cw, int *ch);
// BT_GLASS_PANELS breaks the gauges into their own per-display windows, so
// the world window keeps its normal size (no cockpit / bottom strip).
int glassOwnsGauges = 0;
#ifdef BT_GLASS
{ extern int BTGlassPanelsActive(); glassOwnsGauges = BTGlassPanelsActive(); }
#endif
if (!glassOwnsGauges && getenv("BT_DEV_GAUGES") != 0)
// The mode came from the ONE resolver above (glassLayout) -- BT_GLASS_PANELS
// keeps the world window at its normal size (the per-display windows own the
// gauges: no cockpit surround, no bottom strip).
{
// Mode resolution (precedence): explicit BT_COCKPIT > separate-window /
// legacy-inset opt-out > COCKPIT SURROUND default under BT_DEV_GAUGES.
int cockpitOn;
const char *ck = getenv("BT_COCKPIT");
if (ck != NULL)
cockpitOn = (ck[0] != '0');
else
cockpitOn = (getenv("BT_DEV_GAUGES_WINDOW") == 0 &&
getenv("BT_DEV_GAUGES_DOCK") == 0);
if (cockpitOn)
if (glassLayout == GlassLayoutCockpit)
{
gBTGaugeCockpit = 1;
// -res W H = the WORLD VIEW size in cockpit mode; default 900x500.
@@ -822,10 +1036,24 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
winW = cw; winH = ch;
gBTCockpitCanvasW = winW; // windowed backbuffer = client canvas
gBTCockpitCanvasH = winH;
//
// LETTERBOX INTENT, decided HERE rather than at device creation:
// the first WM_SIZE arrives before the device exists, and the
// world-aspect calc needs to know whether the canvas will be
// scaled uniformly (aspect = the view's own) or stretched to
// the client (aspect = the stretched one). Deciding it late
// gave a -fit boot aspect=3.14 on an ultrawide -- applied on
// the first frame, since nothing resizes the window again.
// L4VIDEO clears the flag if it cannot actually get COPY.
{
extern int gBTCockpitLetterbox;
const char *ms = getenv("MULTISAMPLE");
gBTCockpitLetterbox = (ms == NULL || atoi(ms) == 0) ? 1 : 0;
}
std::cout << "[cockpit] view " << viewW << "x" << viewH
<< " canvas " << winW << "x" << winH << std::endl << std::flush;
}
else if (getenv("BT_DEV_GAUGES_WINDOW") == 0)
else if (glassLayout == GlassLayoutDock)
{
gBTGaugeDockBottom = 1;
// Readability default (user-reported: the strip at 800 wide is a
@@ -843,9 +1071,44 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
}
}
}
//
// -fit (alias -windowed-fullscreen): borderless over the whole monitor.
// The cockpit canvas letterboxes inside it at one uniform scale, so a
// wider-than-canvas monitor gets black bars rather than a stretch -- the
// same deal a dragged window gets, just without the chrome. Only useful
// with a layout that HAS a canvas to fit; other modes keep their window.
//
int fitDisplay = 0;
if (lpCmdLine && (strstr(lpCmdLine, "-fit") != 0 ||
strstr(lpCmdLine, "-windowed-fullscreen") != 0))
fitDisplay = 1;
RECT wr = { 0, 0, winW, winH };
AdjustWindowRect(&wr, WS_OVERLAPPEDWINDOW, FALSE);
DWORD winStyle = WS_OVERLAPPEDWINDOW;
int winX = 0, winY = 0;
if (fitDisplay)
{
RECT mon = { 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN) };
MONITORINFO mi;
memset(&mi, 0, sizeof(mi));
mi.cbSize = sizeof(mi);
POINT origin = { 0, 0 };
HMONITOR hmon = MonitorFromPoint(origin, MONITOR_DEFAULTTOPRIMARY);
if (GetMonitorInfo(hmon, &mi))
mon = mi.rcMonitor;
winStyle = WS_POPUP | WS_CLIPCHILDREN;
winX = mon.left;
winY = mon.top;
wr.left = 0; wr.top = 0;
wr.right = mon.right - mon.left;
wr.bottom = mon.bottom - mon.top;
std::cout << "[cockpit] -fit: borderless " << (wr.right - wr.left) << "x"
<< (wr.bottom - wr.top) << " (canvas letterboxes inside)"
<< std::endl << std::flush;
}
//
// Window identity (MP dev): tag the title with the -net port so a player
// running two nodes side-by-side can tell the windows apart when
@@ -863,8 +1126,8 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
else
swprintf(winTitle, 64, L"BattleTech %S", BT_VERSION_STRING);
}
hWnd = CreateWindowEx(0, L"MainWndClass", winTitle, WS_OVERLAPPEDWINDOW,
0, 0, wr.right - wr.left, wr.bottom - wr.top,
hWnd = CreateWindowEx(0, L"MainWndClass", winTitle, winStyle,
winX, winY, wr.right - wr.left, wr.bottom - wr.top,
(HWND)NULL, (HMENU)NULL, hInstance, (LPVOID)NULL);
if (!hWnd) return FALSE;