Assemble the whole cockpit in a single window

The main game window becomes the cockpit shell (enlarged, clipping
children); every display folds in as a chrome-less child pane in the
pod interior arrangement:

  [ MFD UL ] [ MFD UC ] [ MFD UR ]
  [ plasma (reduced) ][ viewscreen (centered) ]
  [ MFD LL ] [   Map  ] [ MFD LR ]

The 3D scene presents into a black STATIC viewscreen child via
Present's hDestWindowOverride (new gMainPresentWindow global) - no
swap-chain changes, and STATIC's transparent hit-testing keeps mouse
input over the 3D view flowing to the game window. MFDSplitView gains a
parent/child mode; PlasmaScreen::Position reparents the glass into the
shell. Main window class background goes black for the cockpit gaps.

Verified by screenshot: live green gauges (LIFT CUT / BOOST / CHUTE /
trigger-program screens) with their red button strips, the 3D canyon in
the centered viewscreen, plasma score glass at its left, map with lit
amber preset lamps - one window, 976x1132 client at 50% scale.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-12 14:52:26 -05:00
co-authored by Claude Fable 5
parent 775ee130a7
commit aa24968c3d
12 changed files with 222 additions and 96 deletions
+21 -11
View File
@@ -114,17 +114,27 @@ Two new environment options remove the hardware dependency entirely:
desktop window ("Plasma Display", plasma orange, `L4PLASMASCALE` sets the
pixel size, default 4). It opens directly below the main view;
`L4PLASMAPOS=x,y` overrides. No COM port. Closing the window hides it.
- **`L4MFDSPLIT=1`** — un-packs the 7-display cockpit. The pod hardware
drove five monochrome MFDs from the color channels of two video outputs
(window 3 = upper MFDs in R/G/B, window 4 = lower MFDs in R/G) and
mounted the map display portrait. With split mode the game itself opens
seven proper windows — main view, five green-screen MFDs ("MFD upper
left/center/right", "MFD lower left/right") and the 90°-rotated "Map" —
rendered CPU-side from the shared gauge canvas; the packed windows stay
hidden. `L4MFDSCALE` sets the view size in percent of the gauge canvas
(default 50). Default layout is the pod grid to the right of the main
view; all windows are draggable. This replaces the external
BitBlt-mirror launcher wrapper.
- **`L4MFDSPLIT=1`** — assembles the **whole pod interior in one window**.
The pod hardware drove five monochrome MFDs from the color channels of
two video outputs (window 3 = upper MFDs in R/G/B, window 4 = lower
MFDs in R/G) and mounted the map display portrait. In split mode the
main game window becomes the cockpit shell and every display is a
chrome-less child pane, arranged as in the pod:
```
[ MFD UL ] [ MFD UC ] [ MFD UR ]
[ plasma (reduced) ][ viewscreen (centered) ]
[ MFD LL ] [ Map ] [ MFD LR ]
```
MFDs render green-screen and the map full-color/rotated, CPU-side from
the shared gauge canvas (the packed D3D windows stay hidden); the 3D
scene presents into the viewscreen pane via `Present`'s
`hDestWindowOverride` (`gMainPresentWindow`), so no swap-chain changes.
Mouse clicks over the viewscreen fall through to the game window
(STATIC pane hit-test transparency). `L4MFDSCALE` sets the MFD/map pane
size in percent of the gauge canvas (default 50). This replaces the
external BitBlt-mirror launcher wrapper.
Each split window also carries its display's **physical button bank**
(geometry per vRIO's `CockpitLayout`): 4 red buttons above and below
+35 -12
View File
@@ -61,6 +61,7 @@ namespace
view->Paint();
return 0;
}
// no instance (e.g. the plain viewscreen child): default paint
break;
case WM_LBUTTONDOWN:
@@ -86,7 +87,11 @@ namespace
return 0;
case WM_ERASEBKGND:
return 1;
if (view != NULL)
{
return 1;
}
break; // viewscreen child: erase with the class black brush
}
return DefWindowProcA(hwnd, message, wParam, lParam);
}
@@ -106,7 +111,8 @@ MFDSplitView::MFDSplitView(
int y,
ButtonStyle button_style,
int anchorA,
int anchorB
int anchorB,
void *parent
)
{
Check_Pointer(this);
@@ -133,6 +139,8 @@ MFDSplitView::MFDSplitView(
int client_height = display_height;
LayoutButtons(button_style, anchorA, anchorB,
display_width, display_height, &client_width, &client_height);
clientWidth = client_width;
clientHeight = client_height;
HINSTANCE instance = GetModuleHandleA(NULL);
@@ -152,13 +160,28 @@ MFDSplitView::MFDSplitView(
RegisterClassA(&window_class);
}
DWORD style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX;
RECT bounds;
bounds.left = 0;
bounds.top = 0;
bounds.right = client_width;
bounds.bottom = client_height;
AdjustWindowRect(&bounds, style, FALSE);
DWORD style;
int window_w, window_h;
if (parent != NULL)
{
// chrome-less pane inside the cockpit window
style = WS_CHILD | WS_VISIBLE;
window_w = client_width;
window_h = client_height;
}
else
{
style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX;
RECT bounds;
bounds.left = 0;
bounds.top = 0;
bounds.right = client_width;
bounds.bottom = client_height;
AdjustWindowRect(&bounds, style, FALSE);
window_w = bounds.right - bounds.left;
window_h = bounds.bottom - bounds.top;
}
window = CreateWindowExA(
0,
@@ -166,9 +189,9 @@ MFDSplitView::MFDSplitView(
title,
style,
x, y,
bounds.right - bounds.left,
bounds.bottom - bounds.top,
NULL, NULL, instance, NULL
window_w,
window_h,
(HWND) parent, NULL, instance, NULL
);
if (window != NULL)
+14 -1
View File
@@ -35,6 +35,9 @@ public:
SideColumns // 6 per side; anchorA = left base, anchorB = right base
};
// With a parent, the view is created as a chrome-less child window
// inside the single cockpit window (x/y in parent client coords);
// without one it is its own draggable desktop window.
MFDSplitView(
const char *title,
int source_width,
@@ -45,9 +48,17 @@ public:
int y,
ButtonStyle button_style = NoButtons,
int anchorA = 0,
int anchorB = 0
int anchorB = 0,
void *parent = 0
);
int
ClientWidth() const
{ return clientWidth; }
int
ClientHeight() const
{ return clientHeight; }
~MFDSplitView();
Logical
@@ -102,6 +113,8 @@ protected:
int
displayX, displayY,
displayW, displayH;
int
clientWidth, clientHeight;
ScreenButton
buttons[maxButtons];
+27 -12
View File
@@ -64,27 +64,42 @@ namespace
PlasmaScreen *PlasmaScreen::activeInstance = NULL;
void
PlasmaScreen::Position(int x, int y, int client_width, int client_height)
PlasmaScreen::Position(void *parent, int x, int y, int client_width, int client_height)
{
if (activeInstance == NULL || activeInstance->window == NULL)
{
return;
}
DWORD style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX;
RECT bounds;
bounds.left = 0;
bounds.top = 0;
bounds.right = client_width;
bounds.bottom = client_height;
AdjustWindowRect(&bounds, style, FALSE);
HWND glass = (HWND) activeInstance->window;
int window_w = client_width;
int window_h = client_height;
if (parent != NULL)
{
// fold the glass into the cockpit window as a chrome-less pane
SetWindowLongPtrA(glass, GWL_STYLE, WS_CHILD | WS_VISIBLE);
SetParent(glass, (HWND) parent);
}
else
{
DWORD style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX;
RECT bounds;
bounds.left = 0;
bounds.top = 0;
bounds.right = client_width;
bounds.bottom = client_height;
AdjustWindowRect(&bounds, style, FALSE);
window_w = bounds.right - bounds.left;
window_h = bounds.bottom - bounds.top;
}
SetWindowPos(
(HWND) activeInstance->window, NULL,
glass, NULL,
x, y,
bounds.right - bounds.left,
bounds.bottom - bounds.top,
SWP_NOZORDER | SWP_NOACTIVATE
window_w,
window_h,
SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED | SWP_SHOWWINDOW
);
}
+4 -3
View File
@@ -38,10 +38,11 @@ public:
WaitForUpdate();
// Reposition/resize the live glass (client-area size; the paint
// stretches to fit, so this also rescales it). Used by SVGA16's
// split-view layout; no-op when no plasma screen exists.
// stretches to fit, so this also rescales it). With a parent the
// glass becomes a chrome-less child pane of the cockpit window.
// Used by SVGA16's split-view layout; no-op when no plasma exists.
static void
Position(int x, int y, int client_width, int client_height);
Position(void *parent, int x, int y, int client_width, int client_height);
protected:
static PlasmaScreen
+92 -42
View File
@@ -3821,6 +3821,7 @@ SVGA16::SVGA16(
// windows can stay hidden.
//------------------------------------------------------------------
splitViews = False;
cockpitViewscreen = NULL;
{
const char *split_string = getenv("L4MFDSPLIT");
if (split_string != NULL && atoi(split_string) != 0)
@@ -3857,51 +3858,68 @@ SVGA16::SVGA16(
int cell_w = (init_width * scale_percent) / 100;
int cell_h = (init_height * scale_percent) / 100;
int frame_h = GetSystemMetrics(SM_CYCAPTION) + 2 * GetSystemMetrics(SM_CYFIXEDFRAME);
int frame_w = 2 * GetSystemMetrics(SM_CXFIXEDFRAME);
//---------------------------------------------------------------
// Emulate the pod interior:
// Assemble the pod interior in ONE window (the main game window
// becomes the cockpit shell; every display is a chrome-less
// child pane):
// [ MFD UL ] [ MFD UC ] [ MFD UR ]
// [ plasma (reduced) ][ main screen (centered) ]
// [ plasma (reduced) ][ viewscreen (centered) ]
// [ MFD LL ] [ Map ] [ MFD LR ]
// The main game window is moved into the middle band; the plasma
// glass shrinks to fill the space at its left.
// The 3D scene presents into the viewscreen child via
// gMainPresentWindow.
//---------------------------------------------------------------
int gap = 8;
// pane sizes mirror MFDSplitView::LayoutButtons (buttonGap = 4)
int strip_h = cell_h / 8;
if (strip_h < 18) strip_h = 18;
if (strip_h > 40) strip_h = 40;
int mfd_extra_h = 2 * (strip_h + 8);
int block_h = cell_h + 2 * (strip_h + 8);
int col_span = cell_w + frame_w;
int row_w = 3 * col_span;
int band_y = cell_h + mfd_extra_h + frame_h; // below the upper row
int map_col_w = cell_h / 8;
if (map_col_w < 20) map_col_w = 20;
if (map_col_w > 40) map_col_w = 40;
int map_block_w = cell_h + 2 * (map_col_w + 8);
int map_block_h = cell_w;
int col_span = cell_w + gap;
int row_w = 3 * cell_w + 2 * gap;
int band_y = block_h + gap;
int main_w = (int) L4Application::GetScreenWidth();
int main_h = (int) L4Application::GetScreenHeight();
// center the viewscreen under the columns, but always leave the
// plasma glass at least 128px of space at its left
int main_x = (row_w - main_w) / 2;
if (main_x < 0) main_x = 0;
int plasma_min = 128 + 2 * gap;
if (main_x < plasma_min) main_x = plasma_min;
HWND main_window = ApplicationManager::GetCurrentManager()->GetHWnd();
if (main_window != NULL)
int lower_y = band_y + main_h + gap;
int lower_h = (block_h > map_block_h) ? block_h : map_block_h;
int client_w = (row_w > main_x + main_w) ? row_w : (main_x + main_w);
int client_h = lower_y + lower_h;
//---------------------------------------------------------------
// Grow the main window into the cockpit shell
//---------------------------------------------------------------
HWND cockpit = ApplicationManager::GetCurrentManager()->GetHWnd();
if (cockpit != NULL)
{
SetWindowPos(main_window, NULL, main_x, band_y, 0, 0,
SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
}
SetWindowLongPtrA(cockpit, GWL_STYLE,
GetWindowLongPtrA(cockpit, GWL_STYLE) | WS_CLIPCHILDREN);
// plasma: reduced to fit left of the main screen, centered
// vertically in the band (128x32 glass aspect)
int plasma_avail = main_x - 12;
if (plasma_avail >= 128)
{
int glass_w = plasma_avail;
int glass_h = glass_w / 4;
PlasmaScreen::Position(
4, band_y + (main_h - glass_h - frame_h) / 2,
glass_w, glass_h);
}
RECT outer, inner;
GetWindowRect(cockpit, &outer);
GetClientRect(cockpit, &inner);
int chrome_w = (outer.right - outer.left) - inner.right;
int chrome_h = (outer.bottom - outer.top) - inner.bottom;
int lower_y = band_y + main_h;
SetWindowPos(cockpit, NULL, 0, 0,
client_w + chrome_w, client_h + chrome_h,
SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
}
//---------------------------------------------------------------
// Button banks per display (addresses per vRIO CockpitLayout,
@@ -3913,36 +3931,59 @@ SVGA16::SVGA16(
splitView[SplitMFDUpperLeft] = new MFDSplitView(
"MFD upper left", init_width, init_height,
cell_w, cell_h, 0, 0,
MFDSplitView::MFDStrips, 0x2F);
MFDSplitView::MFDStrips, 0x2F, 0, cockpit);
splitView[SplitMFDUpperCenter] = new MFDSplitView(
"MFD upper center", init_width, init_height,
cell_w, cell_h, col_span, 0,
MFDSplitView::MFDStrips, 0x27);
MFDSplitView::MFDStrips, 0x27, 0, cockpit);
splitView[SplitMFDUpperRight] = new MFDSplitView(
"MFD upper right", init_width, init_height,
cell_w, cell_h, 2 * col_span, 0,
MFDSplitView::MFDStrips, 0x37);
MFDSplitView::MFDStrips, 0x37, 0, cockpit);
splitView[SplitMFDLowerLeft] = new MFDSplitView(
"MFD lower left", init_width, init_height,
cell_w, cell_h, 0, lower_y,
MFDSplitView::MFDStrips, 0x0F);
MFDSplitView::MFDStrips, 0x0F, 0, cockpit);
splitView[SplitMFDLowerRight] = new MFDSplitView(
"MFD lower right", init_width, init_height,
cell_w, cell_h, 2 * col_span, lower_y,
MFDSplitView::MFDStrips, 0x07);
// map is portrait: source rotated 90 degrees clockwise; shift left
// by one side-button column so the GLASS (not the window) centers
// under the upper-center MFD
int map_col_w = cell_h / 8;
if (map_col_w < 20) map_col_w = 20;
if (map_col_w > 40) map_col_w = 40;
MFDSplitView::MFDStrips, 0x07, 0, cockpit);
// map is portrait: source rotated 90 degrees clockwise, its pane
// centered under the middle column
splitView[SplitMap] = new MFDSplitView(
"Map", init_height, init_width,
cell_h, cell_w,
col_span + (cell_w - cell_h) / 2 - (map_col_w + 8), lower_y,
MFDSplitView::SideColumns, 0x10, 0x18);
col_span + (cell_w - map_block_w) / 2, lower_y,
MFDSplitView::SideColumns, 0x10, 0x18, cockpit);
DEBUG_STREAM << "SVGA16: split views active - 5 MFD windows + rotated map, cockpit buttons on (scale "
//---------------------------------------------------------------
// The viewscreen: a black STATIC pane the 3D scene presents
// into. STATIC hit-tests transparent, so mouse input over the
// 3D view still reaches the game window as before.
//---------------------------------------------------------------
if (cockpit != NULL)
{
cockpitViewscreen = CreateWindowExA(
0, "STATIC", "",
WS_CHILD | WS_VISIBLE | SS_BLACKRECT,
main_x, band_y, main_w, main_h,
cockpit, NULL, GetModuleHandleA(NULL), NULL);
if (cockpitViewscreen != NULL)
{
gMainPresentWindow = cockpitViewscreen;
}
// plasma glass, reduced to fill the space left of the
// viewscreen, vertically centered in the band
int glass_w = main_x - 2 * gap;
int glass_h = glass_w / 4;
PlasmaScreen::Position(cockpit,
gap, band_y + (main_h - glass_h) / 2,
glass_w, glass_h);
}
DEBUG_STREAM << "SVGA16: single-window cockpit assembled ("
<< client_w << "x" << client_h << ", scale "
<< scale_percent << "%)\n" << std::flush;
}
//STUBBED: VIDEO RB 1/15/07
@@ -4134,6 +4175,15 @@ SVGA16::~SVGA16()
delete splitView[view];
splitView[view] = NULL;
}
if (cockpitViewscreen != NULL)
{
if (gMainPresentWindow == cockpitViewscreen)
{
gMainPresentWindow = NULL;
}
DestroyWindow(cockpitViewscreen);
cockpitViewscreen = NULL;
}
Check_Fpu();
}
+2
View File
@@ -319,6 +319,8 @@ private:
splitViews;
MFDSplitView
*splitView[SplitViewCount];
HWND
cockpitViewscreen; // child pane the 3D scene presents into
};
//########################################################################
+6 -2
View File
@@ -27,6 +27,10 @@ using namespace std;
LPDIRECT3D9 gD3D = NULL;
// Single-window cockpit: viewscreen child window the scene presents into
// (NULL = present to the device window as always).
HWND gMainPresentWindow = NULL;
//STUBBED: DPL RB 1/14/07
// when this is resolved it can be removed
#include "..\DPLSTUB.h"
@@ -6152,7 +6156,7 @@ void DPLRenderer::ExecuteImplementation(RendererComplexity, RendererOrigin::Inte
hr = mDevice->EndScene();
hr = mDevice->Present(NULL, NULL, NULL, NULL);
hr = mDevice->Present(NULL, NULL, gMainPresentWindow, NULL);
if (hr == D3DERR_DEVICELOST)
{
int bbCount = mPresentParams.BackBufferCount;
@@ -6187,7 +6191,7 @@ void DPLRenderer::ExecuteIdle()
hr = mDevice->EndScene();
if (mDevice->Present(NULL, NULL, NULL, NULL) == D3DERR_DEVICELOST)
if (mDevice->Present(NULL, NULL, gMainPresentWindow, NULL) == D3DERR_DEVICELOST)
{
int bbCount = mPresentParams.BackBufferCount;
int bbWidth = mPresentParams.BackBufferWidth;
+5
View File
@@ -28,6 +28,11 @@ using namespace stdext;
class NotationFile;
// Single-window cockpit (L4MFDSPLIT): when non-NULL, the main 3D scene
// presents into this child "viewscreen" window instead of stretching to
// the device window. Set by SVGA16 when it assembles the cockpit.
extern HWND gMainPresentWindow;
#define INTERSECT_ALL (0xFFFFFFFF)
//##########################################################################
+1 -1
View File
@@ -164,7 +164,7 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
wc.hInstance = hInstance;
wc.hIcon = LoadIcon((HINSTANCE) NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor((HINSTANCE) NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
wc.lpszMenuName = NULL;
wc.lpszClassName = L"MainWndClass";
+9 -6
View File
@@ -50,12 +50,15 @@ so press-feedback works like the real button field.
`Video8BitBuffered` surface into a desktop "Plasma Display" window in
plasma orange — verified drawing live game content (score readout).
See BUILD.md §4 for bindings and the desktop `environ.ini`.
-**7-display cockpit in-engine** (`L4MFDSPLIT=1`): the five MFDs the pod
packed into the color channels of two video outputs, plus the
portrait-mounted map, now open as their own windows (green-screen MFDs,
90°-rotated map), rendered CPU-side from the shared gauge canvas — the
external BitBlt-mirror wrapper is obsolete. Verified visually (MFD score
readout, full tactical map).
-**7-display cockpit in-engine, single window** (`L4MFDSPLIT=1`): the
five channel-packed MFDs, the portrait map, the plasma glass and the 3D
viewscreen all assemble inside one cockpit window in the pod interior
arrangement (MFD row / plasma+viewscreen band / MFD-map row), with each
display's physical button bank around it. The 3D scene presents into
the viewscreen child pane; MFDs/map render CPU-side from the gauge
canvas. The external BitBlt-mirror wrapper is obsolete. Verified
visually: live gauges (LIFT CUT/BOOST/CHUTE), tactical map with lit
preset lamps, 3D scene in the viewscreen.
-**Cockpit buttons on the displays** (2026-07-12): each split window
carries its physical button bank — 4+4 red buttons per MFD, 6 amber per
map side (Secondary/Screen columns; addresses per vRIO `CockpitLayout`,
+6 -6
View File
@@ -95,12 +95,12 @@ Controls (XInput controller and/or keyboard):
DPad / arrow keys joystick hat (look)
Start,Back / F1,F2 config buttons
The full pod cockpit comes up as separate windows: the main view, five
green MFD screens, the portrait map, and the orange plasma glass below
the main view. Drag them wherever you like; closing one just hides it.
The red buttons around each MFD and the amber buttons flanking the map
are the pod's real button banks: click them with the mouse, and they
light up as the game commands their lamps.
The full pod cockpit comes up in a single window: three green MFDs
across the top, the 3D viewscreen centered with the orange plasma glass
at its left, and the lower MFDs flanking the portrait map. The red
buttons around each MFD and the amber buttons beside the map are the
pod's real button banks: click them with the mouse, and they light up
as the game commands their lamps.
environ.ini options: L4PADFLIP=XY inverts the stick axes, L4MFDSCALE=n
sizes the MFD/map windows (percent, default 50), L4PLASMASCALE=n sets
the plasma pixel size (default 4), L4PLASMAPOS=x,y places the plasma.