Cockpit buttons on the split displays, lamp-lit and clickable

Each MFDSplitView window now carries its display's physical button bank,
placed as in the pod (addresses per vRIO CockpitLayout): a 4x2 red
cluster around each MFD glass (anchors 0x2F/0x27/0x37 upper, 0x0F/0x07
lower, addresses descending row-major) and 6 amber buttons down each
side of the map - Secondary 0x10-0x15 left, Screen 0x18-0x1D right; the
remaining column addresses are Tesla relays, per the pod wiring, so they
get no buttons.

Buttons light from the lamp state the game commands: PadRIO grows a
static active-instance hook (SetScreenButton/GetLampState); mouse
press/release feeds PadRIO's desired-state sampling alongside pad and
keyboard, and paint decodes the lamp byte (state1/state2 brightness,
solid/slow/med/fast flash animated by tick). With real serial hardware
(no PadRIO) the buttons draw dark and inert.

Verified: map flank buttons light per the game's preset lamps, aligned
with the labels the glass draws at its edges; MFD clusters render 4+4.
Roadmap: queued the vRIO Dynamic Lighting RGB-keyboard lamp mirror as a
polish-pass item. dist repacked.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-12 14:22:38 -05:00
co-authored by Claude Fable 5
parent 214a8e079c
commit 1058de326d
8 changed files with 432 additions and 62 deletions
+11
View File
@@ -126,6 +126,17 @@ Two new environment options remove the hardware dependency entirely:
view; all windows are draggable. This replaces the external view; all windows are draggable. This replaces the external
BitBlt-mirror launcher wrapper. 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
each MFD glass (RIO addresses descending from the cluster anchor —
upper left 0x2F, upper center 0x27, upper right 0x37, lower left 0x0F,
lower right 0x07), and 6 amber buttons down each side of the map
(Secondary 0x100x15 left, Screen 0x180x1D right; the columns' other
addresses are Tesla relays, not buttons). They **light from the lamp
states the game commands** (dim/bright, flash modes animate) and press
the corresponding RIO unit with the mouse — active when `PadRIO` is the
control device, dark and inert with real serial hardware.
Bindings (vRIO's default profile, condensed): Bindings (vRIO's default profile, condensed):
| Input | Pod control | | Input | Pod control |
+257 -45
View File
@@ -2,47 +2,83 @@
#pragma hdrstop #pragma hdrstop
#include "l4mfdview.h" #include "l4mfdview.h"
#include "l4padrio.h"
namespace namespace
{ {
const char mfdViewClass[] = "RPMFDView"; const char mfdViewClass[] = "RPMFDView";
const char mfdViewProp[] = "RPMFDViewBlit";
struct MFDViewBlit const int buttonGap = 4;
//---------------------------------------------------------------
// Button fill colors by lamp brightness (0 off / 1-2 dim / 3
// bright), red family for MFD strips, amber for the side columns.
//---------------------------------------------------------------
COLORREF ButtonFill(int amber, int level)
{ {
BITMAPINFOHEADER header; if (amber)
void *pixels; {
int sourceWidth; if (level >= 3) return RGB(255, 210, 48);
int sourceHeight; if (level >= 1) return RGB(150, 124, 26);
}; return RGB(64, 52, 10);
}
if (level >= 3) return RGB(255, 72, 44);
if (level >= 1) return RGB(150, 44, 28);
return RGB(64, 18, 12);
}
//---------------------------------------------------------------
// Lamp byte -> current brightness level, animating flash modes.
// Low 2 bits: solid/slow/med/fast; bits 2-3: state 1 brightness;
// bits 4-5: state 2 brightness (the flash alternate).
//---------------------------------------------------------------
int LampLevel(int lamp_state)
{
int mode = lamp_state & 0x03;
int level1 = (lamp_state >> 2) & 0x03;
int level2 = (lamp_state >> 4) & 0x03;
if (mode == 0)
{
return level1;
}
static const int half_period[4] = { 0, 500, 250, 125 };
return ((GetTickCount() / half_period[mode]) & 1) ? level2 : level1;
}
LRESULT CALLBACK LRESULT CALLBACK
MFDViewWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) MFDViewWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{ {
MFDSplitView *view =
(MFDSplitView *) GetWindowLongPtrA(hwnd, GWLP_USERDATA);
switch (message) switch (message)
{ {
case WM_PAINT: case WM_PAINT:
if (view != NULL)
{ {
PAINTSTRUCT ps; view->Paint();
HDC hdc = BeginPaint(hwnd, &ps); return 0;
const MFDViewBlit *blit =
(const MFDViewBlit *) GetPropA(hwnd, mfdViewProp);
if (blit != NULL && blit->pixels != NULL)
{
RECT client;
GetClientRect(hwnd, &client);
SetStretchBltMode(hdc, COLORONCOLOR);
StretchDIBits(
hdc,
0, 0, client.right, client.bottom,
0, 0, blit->sourceWidth, blit->sourceHeight,
blit->pixels, (const BITMAPINFO *) &blit->header,
DIB_RGB_COLORS, SRCCOPY
);
}
EndPaint(hwnd, &ps);
} }
return 0; break;
case WM_LBUTTONDOWN:
if (view != NULL)
{
view->MouseDown((int)(short) LOWORD(lParam), (int)(short) HIWORD(lParam));
return 0;
}
break;
case WM_LBUTTONUP:
case WM_CAPTURECHANGED:
if (view != NULL)
{
view->MouseUp();
return 0;
}
break;
case WM_CLOSE: case WM_CLOSE:
// Part of the cockpit; just hide it. // Part of the cockpit; just hide it.
@@ -56,36 +92,47 @@ namespace
} }
} }
//########################################################################
//############################ MFDSplitView ##############################
//########################################################################
MFDSplitView::MFDSplitView( MFDSplitView::MFDSplitView(
const char *title, const char *title,
int source_width, int source_width,
int source_height, int source_height,
int client_width, int display_width,
int client_height, int display_height,
int x, int x,
int y int y,
ButtonStyle button_style,
int anchorA,
int anchorB
) )
{ {
Check_Pointer(this); Check_Pointer(this);
sourceWidth = source_width; sourceWidth = source_width;
sourceHeight = source_height; sourceHeight = source_height;
buttonCount = 0;
pressedIndex = -1;
pixels = new unsigned long[source_width * source_height]; pixels = new unsigned long[source_width * source_height];
memset(pixels, 0, source_width * source_height * sizeof(unsigned long)); memset(pixels, 0, source_width * source_height * sizeof(unsigned long));
MFDViewBlit *blit = new MFDViewBlit; BITMAPINFOHEADER *header = new BITMAPINFOHEADER;
memset(blit, 0, sizeof(MFDViewBlit)); memset(header, 0, sizeof(BITMAPINFOHEADER));
blit->header.biSize = sizeof(BITMAPINFOHEADER); header->biSize = sizeof(BITMAPINFOHEADER);
blit->header.biWidth = source_width; header->biWidth = source_width;
blit->header.biHeight = -source_height; // top-down header->biHeight = -source_height; // top-down
blit->header.biPlanes = 1; header->biPlanes = 1;
blit->header.biBitCount = 32; header->biBitCount = 32;
blit->header.biCompression = BI_RGB; header->biCompression = BI_RGB;
blit->pixels = pixels; blitHeader = header;
blit->sourceWidth = source_width;
blit->sourceHeight = source_height; int client_width = display_width;
blitInfo = blit; int client_height = display_height;
LayoutButtons(button_style, anchorA, anchorB,
display_width, display_height, &client_width, &client_height);
HINSTANCE instance = GetModuleHandleA(NULL); HINSTANCE instance = GetModuleHandleA(NULL);
@@ -126,7 +173,7 @@ MFDSplitView::MFDSplitView(
if (window != NULL) if (window != NULL)
{ {
SetPropA((HWND) window, mfdViewProp, blitInfo); SetWindowLongPtrA((HWND) window, GWLP_USERDATA, (LONG_PTR) this);
ShowWindow((HWND) window, SW_SHOWNOACTIVATE); ShowWindow((HWND) window, SW_SHOWNOACTIVATE);
} }
else else
@@ -141,12 +188,12 @@ MFDSplitView::~MFDSplitView()
if (window != NULL) if (window != NULL)
{ {
RemovePropA((HWND) window, mfdViewProp); SetWindowLongPtrA((HWND) window, GWLP_USERDATA, 0);
DestroyWindow((HWND) window); DestroyWindow((HWND) window);
window = NULL; window = NULL;
} }
delete (MFDViewBlit *) blitInfo; delete (BITMAPINFOHEADER *) blitHeader;
blitInfo = NULL; blitHeader = NULL;
delete [] pixels; delete [] pixels;
pixels = NULL; pixels = NULL;
} }
@@ -157,6 +204,171 @@ Logical
return pixels != NULL; return pixels != NULL;
} }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Compute the display rectangle and the button rectangles, growing the
// client area to make room for the strips.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void
MFDSplitView::LayoutButtons(
ButtonStyle button_style,
int anchorA,
int anchorB,
int display_width,
int display_height,
int *client_width,
int *client_height
)
{
displayX = 0;
displayY = 0;
displayW = display_width;
displayH = display_height;
if (button_style == MFDStrips)
{
//-----------------------------------------------------------
// 4 buttons above the glass, 4 below; RIO addresses descend
// from the anchor, row-major (vRIO CockpitLayout::Mfd).
//-----------------------------------------------------------
int strip_h = display_height / 8;
if (strip_h < 18) strip_h = 18;
if (strip_h > 40) strip_h = 40;
int button_w = (display_width - 5 * buttonGap) / 4;
displayY = strip_h + 2 * buttonGap;
*client_height = display_height + 2 * (strip_h + 2 * buttonGap);
for (int i = 0; i < 8; ++i)
{
int row = i / 4; // 0 = top strip, 1 = bottom strip
int col = i % 4;
ScreenButton *button = &buttons[buttonCount++];
button->unit = anchorA - i;
button->amber = 0;
button->x = buttonGap + col * (button_w + buttonGap);
button->y = row ? (displayY + displayH + buttonGap) : buttonGap;
button->w = button_w;
button->h = strip_h;
}
}
else if (button_style == SideColumns)
{
//-----------------------------------------------------------
// 6 buttons down each side of the glass (the pod wires only
// 6 of each column's 8 addresses to buttons; the rest are
// Tesla relays). Left = anchorA ascending, right = anchorB.
//-----------------------------------------------------------
int col_w = display_width / 8;
if (col_w < 20) col_w = 20;
if (col_w > 40) col_w = 40;
int button_h = (display_height - 7 * buttonGap) / 6;
displayX = col_w + 2 * buttonGap;
*client_width = display_width + 2 * (col_w + 2 * buttonGap);
for (int i = 0; i < 6; ++i)
{
ScreenButton *left = &buttons[buttonCount++];
left->unit = anchorA + i;
left->amber = 1;
left->x = buttonGap;
left->y = buttonGap + i * (button_h + buttonGap);
left->w = col_w;
left->h = button_h;
ScreenButton *right = &buttons[buttonCount++];
right->unit = anchorB + i;
right->amber = 1;
right->x = displayX + displayW + buttonGap;
right->y = left->y;
right->w = col_w;
right->h = button_h;
}
}
}
void
MFDSplitView::Paint()
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint((HWND) window, &ps);
RECT client;
GetClientRect((HWND) window, &client);
FillRect(hdc, &client, (HBRUSH) GetStockObject(BLACK_BRUSH));
SetStretchBltMode(hdc, COLORONCOLOR);
StretchDIBits(
hdc,
displayX, displayY, displayW, displayH,
0, 0, sourceWidth, sourceHeight,
pixels, (const BITMAPINFO *) blitHeader,
DIB_RGB_COLORS, SRCCOPY
);
for (int i = 0; i < buttonCount; ++i)
{
const ScreenButton *button = &buttons[i];
int level = PadRIO::IsActive()
? LampLevel(PadRIO::GetLampState(button->unit))
: 0;
RECT rect;
rect.left = button->x;
rect.top = button->y;
rect.right = button->x + button->w;
rect.bottom = button->y + button->h;
HBRUSH fill = CreateSolidBrush(ButtonFill(button->amber, level));
FillRect(hdc, &rect, fill);
DeleteObject(fill);
HBRUSH frame = CreateSolidBrush(
(i == pressedIndex) ? RGB(255, 255, 255) : RGB(48, 48, 48));
FrameRect(hdc, &rect, frame);
DeleteObject(frame);
}
EndPaint((HWND) window, &ps);
}
void
MFDSplitView::MouseDown(int x, int y)
{
for (int i = 0; i < buttonCount; ++i)
{
const ScreenButton *button = &buttons[i];
if (x >= button->x && x < button->x + button->w &&
y >= button->y && y < button->y + button->h)
{
pressedIndex = i;
SetCapture((HWND) window);
PadRIO::SetScreenButton(button->unit, True);
InvalidateRect((HWND) window, NULL, FALSE);
return;
}
}
}
void
MFDSplitView::MouseUp()
{
if (pressedIndex >= 0)
{
PadRIO::SetScreenButton(buttons[pressedIndex].unit, False);
pressedIndex = -1;
if (GetCapture() == (HWND) window)
{
ReleaseCapture();
}
InvalidateRect((HWND) window, NULL, FALSE);
}
}
void void
MFDSplitView::Repaint() MFDSplitView::Repaint()
{ {
+63 -4
View File
@@ -11,6 +11,16 @@
// MFDs into the color channels of two video outputs and mounted the map // MFDs into the color channels of two video outputs and mounted the map
// display rotated; on a desktop each display gets its own window instead. // display rotated; on a desktop each display gets its own window instead.
// //
// Each window also carries its display's physical button bank, exactly
// as mounted in the pod (geometry per vRIO's CockpitLayout):
// - MFDs: a 4x2 cluster - 4 red buttons above the glass, 4 below,
// RIO addresses descending from the cluster anchor (top-left = hi).
// - The map ("secondary screen"): 6 amber buttons down each side -
// Secondary 0x10-0x15 on the left, Screen 0x18-0x1D on the right
// (the remaining column addresses are Tesla relays, not buttons).
// Buttons light from the lamp state the game commands (via PadRIO) and
// press/release RIO units with the mouse.
//
// SVGA16 fills Pixels() (32bpp top-down, sourceWidth x sourceHeight as // SVGA16 fills Pixels() (32bpp top-down, sourceWidth x sourceHeight as
// passed - already swapped by the caller for the rotated map) and calls // passed - already swapped by the caller for the rotated map) and calls
// Repaint(). Closing a view window just hides it. // Repaint(). Closing a view window just hides it.
@@ -18,14 +28,24 @@
class MFDSplitView class MFDSplitView
{ {
public: public:
enum ButtonStyle
{
NoButtons = 0,
MFDStrips, // 4 above + 4 below, addresses descend from anchorA
SideColumns // 6 per side; anchorA = left base, anchorB = right base
};
MFDSplitView( MFDSplitView(
const char *title, const char *title,
int source_width, int source_width,
int source_height, int source_height,
int client_width, int display_width,
int client_height, int display_height,
int x, int x,
int y int y,
ButtonStyle button_style = NoButtons,
int anchorA = 0,
int anchorB = 0
); );
~MFDSplitView(); ~MFDSplitView();
@@ -40,14 +60,53 @@ public:
void void
Repaint(); Repaint();
// Window-procedure callbacks (public for the registered WndProc)
void
Paint();
void
MouseDown(int x, int y);
void
MouseUp();
protected: protected:
void
LayoutButtons(
ButtonStyle button_style,
int anchorA,
int anchorB,
int display_width,
int display_height,
int *client_width,
int *client_height
);
struct ScreenButton
{
int unit;
int x, y, w, h;
int amber; // 0 = red family, 1 = amber family
};
enum { maxButtons = 20 };
void void
*window; // HWND *window; // HWND
void void
*blitInfo; // MFDViewBlit (header + source dims), see .cpp *blitHeader; // BITMAPINFOHEADER for StretchDIBits
unsigned long unsigned long
*pixels; *pixels;
int int
sourceWidth, sourceWidth,
sourceHeight; sourceHeight;
int
displayX, displayY,
displayW, displayH;
ScreenButton
buttons[maxButtons];
int
buttonCount;
int
pressedIndex;
}; };
+35
View File
@@ -87,6 +87,27 @@ namespace
//############################### PadRIO ################################# //############################### PadRIO #################################
//######################################################################## //########################################################################
PadRIO *PadRIO::activeInstance = NULL;
void
PadRIO::SetScreenButton(int unit, Logical pressed)
{
if (activeInstance != NULL && unit >= 0 && unit < buttonUnits)
{
activeInstance->screenButton[unit] = pressed ? 1 : 0;
}
}
int
PadRIO::GetLampState(int unit)
{
if (activeInstance != NULL && unit >= 0 && unit < lampCount)
{
return activeInstance->lampState[unit];
}
return 0;
}
PadRIO::PadRIO() PadRIO::PadRIO()
{ {
Check_Pointer(this); Check_Pointer(this);
@@ -105,6 +126,7 @@ PadRIO::PadRIO()
memset(buttonDown, 0, sizeof(buttonDown)); memset(buttonDown, 0, sizeof(buttonDown));
memset(lampState, 0, sizeof(lampState)); memset(lampState, 0, sizeof(lampState));
memset(screenButton, 0, sizeof(screenButton));
invertX = False; invertX = False;
invertY = False; invertY = False;
@@ -125,12 +147,18 @@ PadRIO::PadRIO()
MajorRevision = 4; MajorRevision = 4;
MinorRevision = 2; MinorRevision = 2;
activeInstance = this;
DEBUG_STREAM << "PadRIO: virtual RIO active (XInput pad + keyboard)\n" << std::flush; DEBUG_STREAM << "PadRIO: virtual RIO active (XInput pad + keyboard)\n" << std::flush;
} }
PadRIO::~PadRIO() PadRIO::~PadRIO()
{ {
Check_Pointer(this); Check_Pointer(this);
if (activeInstance == this)
{
activeInstance = NULL;
}
} }
Logical Logical
@@ -290,6 +318,13 @@ void
desired[keyboardButtonMap[i].rioUnit] = 1; desired[keyboardButtonMap[i].rioUnit] = 1;
} }
} }
for (int i = 0; i < buttonUnits; ++i)
{
if (screenButton[i])
{
desired[i] = 1;
}
}
for (int unit = 0; unit < buttonUnits; ++unit) for (int unit = 0; unit < buttonUnits; ++unit)
{ {
+21 -1
View File
@@ -50,11 +50,24 @@ public:
SetLamp(int lampNumber, int state); SetLamp(int lampNumber, int state);
// Lamp state the game has commanded, indexed by RIO lamp number. // Lamp state the game has commanded, indexed by RIO lamp number.
// A future on-screen panel reads this to light its buttons. // The on-screen cockpit buttons read this to light themselves.
enum { lampCount = 64 }; enum { lampCount = 64 };
unsigned char unsigned char
lampState[lampCount]; lampState[lampCount];
//---------------------------------------------------------------
// On-screen cockpit buttons (MFDSplitView strips) press RIO units
// through these; they are no-ops when no PadRIO is active (e.g.
// real serial hardware selected).
//---------------------------------------------------------------
static void
SetScreenButton(int unit, Logical pressed);
static int
GetLampState(int unit);
static Logical
IsActive()
{ return activeInstance != NULL; }
protected: protected:
void void
PollInputs(); PollInputs();
@@ -69,6 +82,13 @@ protected:
int int
queueHead, queueTail; queueHead, queueTail;
static PadRIO
*activeInstance;
// pressed state driven by the on-screen cockpit buttons
unsigned char
screenButton[0x48];
unsigned long unsigned long
lastPollTick, lastPollTick,
lastPadCheckTick; lastPadCheckTick;
+28 -8
View File
@@ -3859,30 +3859,50 @@ SVGA16::SVGA16(
int frame_h = GetSystemMetrics(SM_CYCAPTION) + 2 * GetSystemMetrics(SM_CYFIXEDFRAME); int frame_h = GetSystemMetrics(SM_CYCAPTION) + 2 * GetSystemMetrics(SM_CYFIXEDFRAME);
int frame_w = 2 * GetSystemMetrics(SM_CXFIXEDFRAME); int frame_w = 2 * GetSystemMetrics(SM_CXFIXEDFRAME);
int grid_x = init_width + frame_w; int grid_x = init_width + frame_w;
int row2_y = cell_h + frame_h;
// the MFD button strips grow each window (mirror of
// MFDSplitView::LayoutButtons so the grid rows still tile)
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 row2_y = cell_h + mfd_extra_h + frame_h;
//---------------------------------------------------------------
// Button banks per display (addresses per vRIO CockpitLayout,
// bank-to-display placement per the pod cockpit):
// upper left MFD 0x2F.. / upper center 0x27.. / upper right
// 0x37.. / lower left 0x0F.. / lower right 0x07.. and the map
// flanked by Secondary 0x10-0x15 / Screen 0x18-0x1D.
//---------------------------------------------------------------
splitView[SplitMFDUpperLeft] = new MFDSplitView( splitView[SplitMFDUpperLeft] = new MFDSplitView(
"MFD upper left", init_width, init_height, "MFD upper left", init_width, init_height,
cell_w, cell_h, grid_x, 0); cell_w, cell_h, grid_x, 0,
MFDSplitView::MFDStrips, 0x2F);
splitView[SplitMFDUpperCenter] = new MFDSplitView( splitView[SplitMFDUpperCenter] = new MFDSplitView(
"MFD upper center", init_width, init_height, "MFD upper center", init_width, init_height,
cell_w, cell_h, grid_x + (cell_w + frame_w), 0); cell_w, cell_h, grid_x + (cell_w + frame_w), 0,
MFDSplitView::MFDStrips, 0x27);
splitView[SplitMFDUpperRight] = new MFDSplitView( splitView[SplitMFDUpperRight] = new MFDSplitView(
"MFD upper right", init_width, init_height, "MFD upper right", init_width, init_height,
cell_w, cell_h, grid_x + 2 * (cell_w + frame_w), 0); cell_w, cell_h, grid_x + 2 * (cell_w + frame_w), 0,
MFDSplitView::MFDStrips, 0x37);
splitView[SplitMFDLowerLeft] = new MFDSplitView( splitView[SplitMFDLowerLeft] = new MFDSplitView(
"MFD lower left", init_width, init_height, "MFD lower left", init_width, init_height,
cell_w, cell_h, grid_x, row2_y); cell_w, cell_h, grid_x, row2_y,
MFDSplitView::MFDStrips, 0x0F);
splitView[SplitMFDLowerRight] = new MFDSplitView( splitView[SplitMFDLowerRight] = new MFDSplitView(
"MFD lower right", init_width, init_height, "MFD lower right", init_width, init_height,
cell_w, cell_h, grid_x + 2 * (cell_w + frame_w), row2_y); cell_w, cell_h, grid_x + 2 * (cell_w + frame_w), row2_y,
MFDSplitView::MFDStrips, 0x07);
// map is portrait: source rotated 90 degrees clockwise // map is portrait: source rotated 90 degrees clockwise
splitView[SplitMap] = new MFDSplitView( splitView[SplitMap] = new MFDSplitView(
"Map", init_height, init_width, "Map", init_height, init_width,
cell_h, cell_w, cell_h, cell_w,
grid_x + (cell_w + frame_w) + (cell_w - cell_h) / 2, row2_y); grid_x + (cell_w + frame_w) + (cell_w - cell_h) / 2, row2_y,
MFDSplitView::SideColumns, 0x10, 0x18);
DEBUG_STREAM << "SVGA16: split views active - 5 MFD windows + rotated map (scale " DEBUG_STREAM << "SVGA16: split views active - 5 MFD windows + rotated map, cockpit buttons on (scale "
<< scale_percent << "%)\n" << std::flush; << scale_percent << "%)\n" << std::flush;
} }
//STUBBED: VIDEO RB 1/15/07 //STUBBED: VIDEO RB 1/15/07
+14 -4
View File
@@ -56,10 +56,20 @@ so press-feedback works like the real button field.
90°-rotated map), rendered CPU-side from the shared gauge canvas — the 90°-rotated map), rendered CPU-side from the shared gauge canvas — the
external BitBlt-mirror wrapper is obsolete. Verified visually (MFD score external BitBlt-mirror wrapper is obsolete. Verified visually (MFD score
readout, full tactical map). readout, full tactical map).
- Next in A: on-screen RIO panel fed by `PadRIO::lampState[]` (the - **Cockpit buttons on the displays** (2026-07-12): each split window
cockpit-feel layout — vRIO button clusters around the displays); port the carries its physical button bank — 4+4 red buttons per MFD, 6 amber per
full vRIO bindings-file model (deflect/rate/deadzone per axis, rebinding); map side (Secondary/Screen columns; addresses per vRIO `CockpitLayout`,
pilot keypad (numpad → KeyEvent); Steam Input once C starts. placement per the pod). Mouse presses inject into `PadRIO`; the game's
lamp commands light them (verified: PRESET lamps lit on the map flank,
aligned with the glass's own edge labels).
- Next in A: port the full vRIO bindings-file model (deflect/rate/deadzone
per axis, rebinding); pilot keypad (numpad / on-screen 4×4 → KeyEvent);
Steam Input once C starts.
- Polish pass (queued): **vRIO's Dynamic Lighting RGB-keyboard mirror**
lamp states glow on the physical keyboard's keys (per-key or zone-lit),
blinking with the flash modes. Needs package identity for background
light control (vRIO's `pkg\Register-vRIO.ps1` pattern; see the vRIO
README for the Settings → Dynamic Lighting steps).
- vRIO itself stays useful as a dev harness against unmodified builds. - vRIO itself stays useful as a dev harness against unmodified builds.
## Workstream B — In-game sessions (from TeslaConsole) ## Workstream B — In-game sessions (from TeslaConsole)
+3
View File
@@ -98,6 +98,9 @@ Controls (XInput controller and/or keyboard):
The full pod cockpit comes up as separate windows: the main view, five 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 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 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.
environ.ini options: L4PADFLIP=XY inverts the stick axes, L4MFDSCALE=n environ.ini options: L4PADFLIP=XY inverts the stick axes, L4MFDSCALE=n
sizes the MFD/map windows (percent, default 50), L4PLASMASCALE=n sets sizes the MFD/map windows (percent, default 50), L4PLASMASCALE=n sets
the plasma pixel size (default 4), L4PLASMAPOS=x,y places the plasma. the plasma pixel size (default 4), L4PLASMAPOS=x,y places the plasma.