pod: freeze the ALPHA-MR rig in environ.ini (BT_FIT, L4PLASMA=NONE)
The cart's display config was carried by a launcher .bat, so it only came up
right if the game was started one particular way. Move it into the file the
engine already reads before anything touches the environment.
content/environ.ini <- scratchpad/pod/podprofile.ini, merged idempotently
between markers by scratchpad/pod/mergeprofile.ps1
Two gates were missing for that to be enough:
BT_FIT=1 the env spelling of -fit, so the borderless main view does not
depend on one launcher's command line (shortcut, scheduled
task and autostart all have to produce the same rig)
L4PLASMA= NONE / OFF / 0 -> no marquee at all. Leaving it unset does
NOT work: the GLASS profile force-defaults it to SCREEN, which
drops a desktop plasma window on the cab's glass. The boot
banner now reports the live state instead of always claiming
"plasma window".
Also lands the bring-up engine work this depended on: monitor:<name|index>
layout binding (device-bound, not pixel-bound -- desktop rects move when a
display re-enumerates), ",bare" implying frameless, rotation-aware radar
surface sizing, BT_GAUGE_SEC_ROT accepting 0-3 (it silently forced 3 for
anything but 1), 180-degree ExpandPlaneToBGRA, and the BT_POD_CHANMAP /
BT_POD_IDENT / BT_POD_CHANTEST identification gates.
Verified on the cart, build 4.11.813, launcher carrying none of it:
[boot] environ.ini: 9 setting(s) applied
[boot] platform profile: GLASS (PadRIO; plasma off [L4PLASMA])
[cockpit] -fit: borderless 800x600
[glasswin] radar rotation 0 (none)
... all three surfaces on their intended \.\DISPLAYn
No [plasmawin] line in an otherwise-logging run = the ctor never ran.
KB: pod-hardware.md gains the ALPHA-MR section (mapping, the two wiring
deviations, the frozen profile, the session-0 remote-work traps) and its RGB
SPLIT "OPEN: which way is the cart wired" is now SETTLED -- it is splitter
wired, the composite is what lit it. glass-cockpit.md documents monitor:
binding, BT_FIT and L4PLASMA=NONE.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rw7No5wLTpkaUgA3ANbtZZ
This commit is contained in:
co-authored by
Claude Opus 5
parent
bef051e837
commit
654277bb7b
@@ -156,6 +156,26 @@ want bare. From then on those windows are frameless **and pinned** — with no c
|
||||
nothing to drag them by, which is the point: the arrangement is finished. Delete the flag to get
|
||||
the frame back.
|
||||
|
||||
### `monitor:<name|index>` — bind a window to a DISPLAY, not to pixels (2026-08-06) [T2 live]
|
||||
|
||||
Instead of an `x,y,w,h` rect, a line may name a display device:
|
||||
|
||||
```
|
||||
Secondary / Radar=monitor:DISPLAY4,bare
|
||||
VGA Port A=monitor:DISPLAY2,bare
|
||||
```
|
||||
|
||||
The resolver (`ResolveMonitorSpec` + `MonScanProc`, `L4GLASSWIN`) matches the full device name OR
|
||||
its tail, so `monitor:DISPLAY4` works without typing `\\.\`; a bare integer is a 0-based index in
|
||||
enumeration order. The surface is CENTRED on that monitor. This is what makes a fixed rig
|
||||
survivable: **desktop rects move** when a display is re-detected, a USB graphics adapter
|
||||
re-enumerates or someone changes a resolution, and a pixel rect then silently puts a panel on the
|
||||
wrong glass — a device binding does not. Required for [[pod-hardware]] §ALPHA-MR, where a
|
||||
mis-bound window means a picture on the wrong CRT with no error anywhere.
|
||||
|
||||
`,bare` **implies frameless** (it used to need `,noframe` too, and a "bare" window came back
|
||||
656×519 WITH a caption — a real bug found on the cab).
|
||||
|
||||
Options are comma-separated after the numbers and **unknown ones are ignored**, so an older build
|
||||
reading a newer file loses the option but never the line — the `bindings.txt` grammar rule
|
||||
applied here.
|
||||
@@ -183,6 +203,15 @@ appears in the cfg like any panel and honours `,noframe`. Verified: a drag wrote
|
||||
at 321,222. NB the plasma window blits directly every frame (`GetDC`+`StretchDIBits`), so unlike
|
||||
the panels it has no `WM_TIMER` focus-throttle to worry about.
|
||||
|
||||
**Turning it OFF: `L4PLASMA=NONE` (also `OFF`/`0`, 2026-08-06) [T2].** `L4GREND` creates a
|
||||
marquee whenever `L4PLASMA` is set at all (`SCREEN` → the desktop window, anything else → a real
|
||||
`PlasmaDisplay` on that serial port), and the GLASS profile force-defaults it to `SCREEN` — so on
|
||||
a rig with no marquee wired, leaving the variable unset is NOT enough; the profile fills it back
|
||||
in and a plasma window lands on the cab's glass. The `NONE`/`OFF`/`0` spelling nulls the string
|
||||
before the branch, so no external display is constructed at all. The boot banner reads
|
||||
`GLASS (PadRIO; plasma off [L4PLASMA])` when it takes, and the absence of the
|
||||
`[plasmawin] desktop plasma display up` line is the runtime proof.
|
||||
|
||||
**Verified 2026-07-26 [T2]:** `BT_RIOBANK_LOG=1` dumps every bank's rects;
|
||||
`scratchpad/checkbank.py` reports the per-bank census and proves no address is SHADOWED (has a
|
||||
point no earlier button covers); `scratchpad/clickbank.py` then posts a real click at every
|
||||
@@ -214,7 +243,10 @@ window.
|
||||
- **World aspect** under the letterbox is the view rect's OWN aspect — the client no longer
|
||||
enters into it.
|
||||
- **`-fit`** (alias `-windowed-fullscreen`): borderless `WS_POPUP` over the monitor, canvas
|
||||
letterboxed inside. Verified on a 3440×1440 ultrawide.
|
||||
letterboxed inside. Verified on a 3440×1440 ultrawide. **`BT_FIT=1` is the env spelling**
|
||||
(2026-08-06) — same flag, settable from `environ.ini`, so a fixed rig keeps its borderless main
|
||||
view however it is launched instead of depending on one launcher's command line. Targets
|
||||
`MONITOR_DEFAULTTOPRIMARY`, i.e. the PRIMARY display. [[pod-hardware]] §ALPHA-MR
|
||||
|
||||
⚠ **Ordering trap found live:** the letterbox flag was first set at device creation, but the
|
||||
first `WM_SIZE` arrives BEFORE the device exists — a `-fit` boot logged `aspect=3.14` (the
|
||||
|
||||
+68
-6
@@ -217,12 +217,21 @@ colour channel of one shared palettized framebuffer. Mechanism, end to end:
|
||||
mono-tinted window on its own Windows display (see §MFD PANELS ON REAL HARDWARE), which is right
|
||||
when every panel has its own output. **On splitter-wired glass it is wrong** — three monitors
|
||||
would share one Windows display and each would show only its channel's share of a single tinted
|
||||
image. Driving original splitter hardware needs a CHANNEL-COMPOSITE mode: extract three planes
|
||||
into ONE 640x480 RGB image with pure (255,0,0)/(0,255,0)/(0,0,255) tints, one window per VGA
|
||||
output. `SVGA16::ExpandPlaneToBGRA` already does the per-plane extraction with a tint, so the
|
||||
composite is a small addition. OPEN: which way Nick's crash cart is wired (Windows shows three
|
||||
separate 640x480 displays there, which suggests per-panel outputs via the Trigger 6 USB adapter,
|
||||
not a splitter) — settle it by eye before building.
|
||||
image.
|
||||
|
||||
**`BT_POD_RGB=1` — the CHANNEL-COMPOSITE mode (built + field-verified 2026-08-06) [T2].** It
|
||||
collapses the six panel windows into **three**: two VGA-port windows plus the radar. Each port
|
||||
window composites its group's planes into ONE 640×480 BGRA image, each member ORed into its own
|
||||
channel mask (`0x00FF0000` / `0x0000FF00` / `0x000000FF`), so the analog splitter downstream hands
|
||||
each mono CRT exactly its picture. Grouping follows `L4GAUGE.CFG`: *VGA Port A* = Heat + Comm +
|
||||
Mfd2, *VGA Port B* = Mfd1 + Mfd3; the radar keeps real colour on its own port.
|
||||
`SVGA16::ExpandPlaneToBGRA` does the per-plane extraction, and `BT_POD_RGB` implies
|
||||
`BT_POD_SURFACES`. Two companion gates came out of the bring-up: **`BT_POD_CHANMAP=<Port>=<colour>,…`**
|
||||
remaps a surface's colour line when a cab's harness disagrees with the 1995 config (it did — see
|
||||
§ALPHA-MR), and **`BT_POD_IDENT`** / **`BT_POD_CHANTEST`** paint identifying/cycling test patterns
|
||||
so you can tell which physical CRT is which without trusting the desktop layout.
|
||||
**SETTLED:** Nick's cart IS splitter-wired — its three 640×480 "displays" are VGA outputs, each
|
||||
feeding a three-monitor splitter, and the composite is what lit the glass correctly.
|
||||
|
||||
## 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
|
||||
@@ -262,6 +271,59 @@ at native size CENTRED, not scaled — a scale-to-fit option is the obvious foll
|
||||
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.
|
||||
|
||||
## ALPHA-MR — the verified cart config, FROZEN (2026-08-06) [T2, field-verified by eye]
|
||||
The bring-up above landed: real pictures on real pod glass, then user-confirmed correct
|
||||
("ok this is correct"). What that took, and where it now lives permanently.
|
||||
|
||||
**The rig.** Win10 IoT Enterprise LTSC on the cart; Intel HD630 + a "Trigger 6 External Graphics"
|
||||
USB adapter. Four console displays: **DISPLAY3 800×600 PRIMARY at 0,0** (main view), plus
|
||||
**DISPLAY4 @800,122**, **DISPLAY2 @1440,123**, **DISPLAY1 @2080,124** (each 640×480). Physical
|
||||
cab: five mono MFD CRTs (two left stacked, two right stacked, one centre loose on the desk) plus
|
||||
the colour radar LCD in the yellow-button frame.
|
||||
|
||||
**The mapping** (`glass_layout.cfg`, `monitor:<name>` binding — see [[glass-cockpit]]):
|
||||
```
|
||||
Secondary / Radar = monitor:DISPLAY4,bare # colour LCD, yellow-button frame
|
||||
VGA Port A = monitor:DISPLAY2,bare # RGB triple: Heat + Comm + Mfd2
|
||||
VGA Port B = monitor:DISPLAY1,bare # RGB triple: Mfd1 + Mfd3
|
||||
```
|
||||
Three windows, not six — under `BT_POD_RGB` each VGA-port window carries three MFDs on its three
|
||||
colour planes (§THE RGB SPLIT). Desktop-coordinate order says NOTHING about physical cab position:
|
||||
the first assignment, made by sorting monitors left-to-right, was wrong. Only eyes on the glass
|
||||
settle it.
|
||||
|
||||
**Two deviations from the 1995 config, both real, both this cab's wiring:**
|
||||
- **`BT_POD_CHANMAP=Comm=blue,Heat=red`** — kills/deaths and coolant loops came up on each
|
||||
other's panel. `L4GAUGE.CFG` has Comm on red and Heat on blue; THIS cab's splitter harness
|
||||
wires them the other way. The env override remaps a port's colour channel without touching the
|
||||
authentic config file. [T2]
|
||||
- **`BT_GAUGE_SEC_ROT=0`** — the radar here is a LANDSCAPE LCD, so the 640×480 source needs no
|
||||
rotation. The 1995 pod's 270° rotation existed because its secondary CRT was mounted PORTRAIT;
|
||||
it is a mounting fact, not a format fact. (En route: 180° looked plausible and was upside down.)
|
||||
|
||||
**Frozen in `content/environ.ini`**, not in a launcher — `scratchpad/pod/podprofile.ini` is the
|
||||
block, `scratchpad/pod/mergeprofile.ps1` merges it idempotently between markers. environ.ini is
|
||||
read before anything touches the environment ([[build-and-run]]), and the real environment still
|
||||
WINS, so a .bat can override any of it for a one-off. The cart's `runpod.bat` now sets only
|
||||
`BT_LOG`/`BT_GLASS_LOG` and the `-egg`; `runpod_env.bat` keeps the all-inline version as the
|
||||
fallback. Two gates were added for this:
|
||||
- **`BT_FIT=1`** — the env spelling of `-fit`, so the borderless main view survives however the
|
||||
game is started (shortcut, scheduled task, autostart) instead of riding on one launcher's
|
||||
command line. It fits `MONITOR_DEFAULTTOPRIMARY` = DISPLAY3, which is the main-view panel.
|
||||
- **`L4PLASMA=NONE`** (also `OFF`/`0`) — no marquee at all. Needed because the GLASS profile
|
||||
force-defaults `L4PLASMA=SCREEN`, so simply leaving it unset still puts a desktop plasma window
|
||||
on the pod's glass. The boot banner reports `plasma off [L4PLASMA]` when it takes.
|
||||
Verified on build 4.11.813 with the launcher carrying none of it: `[boot] environ.ini: 9
|
||||
setting(s) applied`, `[cockpit] -fit: borderless 800x600`, `[glasswin] radar rotation 0 (none)`,
|
||||
and all three surfaces on their intended `\\.\DISPLAYn`.
|
||||
|
||||
**Working on the cart, remotely.** SSH over Tailscale lands in **session 0**, which has a dummy
|
||||
"WinDisc" display and CANNOT see or enumerate session 1's windows — GUI work must go through
|
||||
`schtasks /run /tn BT411Run` (task registered `/IT`), and window enumeration over SSH silently
|
||||
returns nothing rather than failing. Kill `btl4.exe` BEFORE scp'ing a new exe or the file is
|
||||
locked. The receipts in `podrun.log` are the remote eyes; a missing `DEBUG_STREAM` line in an
|
||||
otherwise-logging run is real evidence that code path did not execute.
|
||||
|
||||
## 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:
|
||||
|
||||
+382
-12
@@ -123,6 +123,10 @@ struct GWin
|
||||
const char *groupPort[3];
|
||||
const char *groupAlt[3]; // the Eng<n> twin, or NULL
|
||||
int groupCount;
|
||||
|
||||
char monitorName[40]; // the PHYSICAL monitor this window landed on
|
||||
// (\.\DISPLAYn), stamped at creation; shown
|
||||
// by BT_POD_IDENT so the cab can be mapped.
|
||||
};
|
||||
|
||||
static GWin gWins[8];
|
||||
@@ -253,17 +257,26 @@ static void
|
||||
// (0x15 sits on the left column -- Cyd listed 0x10-0x14; move it if wanted.)
|
||||
static const int bottomAddrs[4] = { 0x16, 0x17, 0x1F, 0x1E };
|
||||
|
||||
// The SURFACE SIZE follows the rotation: a quarter turn (1 or 3) transposes
|
||||
// the 640x480 source to 480x640 portrait -- the pod's rotated CRT -- while
|
||||
// 0 (none) and 2 (180) keep it LANDSCAPE. Sizing this independently of the
|
||||
// rotation squashed the picture into the wrong aspect on a landscape
|
||||
// replacement panel (crash cart, 2026-08-06).
|
||||
const int quarterTurn = (gRadarRot == 1 || gRadarRot == 3);
|
||||
const int surfW = quarterTurn ? RadarSurfW : RadarSurfH; // 480 : 640
|
||||
const int surfH = quarterTurn ? RadarSurfH : RadarSurfW; // 640 : 480
|
||||
|
||||
BTRioBankMetrics metrics;
|
||||
BTRioBankMetricsFor(RadarSurfW, RadarSurfH, &metrics);
|
||||
BTRioBankMetricsFor(surfW, surfH, &metrics);
|
||||
|
||||
BTRioBank bank;
|
||||
BTRioBankLayout(BTRioBankRadar, 0, 0, RadarSurfW, RadarSurfH,
|
||||
BTRioBankLayout(BTRioBankRadar, 0, 0, surfW, surfH,
|
||||
0x10, 0x18, &metrics, bottomAddrs, &bank);
|
||||
|
||||
BTRioBankDump("Secondary / Radar", &bank);
|
||||
int dx = -bank.boundsX, dy = -bank.boundsY;
|
||||
AdoptBank(w, bank, ClrYellow, dx, dy);
|
||||
SetRect(&w.surfaceRect, dx, dy, dx + RadarSurfW, dy + RadarSurfH);
|
||||
SetRect(&w.surfaceRect, dx, dy, dx + surfW, dy + surfH);
|
||||
w.clientW = bank.boundsW;
|
||||
w.clientH = bank.boundsH;
|
||||
}
|
||||
@@ -426,6 +439,116 @@ static const char *layoutFileName = "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)
|
||||
// BT_POD_CHANMAP -- per-cart channel override, e.g.
|
||||
// BT_POD_CHANMAP=Comm=blue,Heat=red
|
||||
// The 1995 config authors which colour line each surface rides (L4GAUGE.CFG),
|
||||
// but a rebuilt cab can have its RGB splitter wired to different panels than
|
||||
// the original -- on Nick's cart the coolant (Heat) and hot-box (Comm) screens
|
||||
// came out swapped, which is a WIRING difference, not a software bug. Rather
|
||||
// than edit authentic config, override the channel here, per rig.
|
||||
// Returns 0/1/2 (red/green/blue) for a named port, or -1 if unmapped.
|
||||
static int
|
||||
PodChannelOverride(const char *portName)
|
||||
{
|
||||
static int loaded = 0;
|
||||
static char names[8][20];
|
||||
static int chans[8];
|
||||
static int count = 0;
|
||||
if (!loaded)
|
||||
{
|
||||
loaded = 1;
|
||||
const char *e = getenv("BT_POD_CHANMAP");
|
||||
while (e != NULL && *e && count < 8)
|
||||
{
|
||||
while (*e == ' ' || *e == ',') ++e;
|
||||
const char *eq = strchr(e, '=');
|
||||
if (eq == NULL) break;
|
||||
int n = 0;
|
||||
for (const char *p = e; p < eq && n < 19; ++p) names[count][n++] = *p;
|
||||
names[count][n] = 0;
|
||||
const char *v = eq + 1;
|
||||
int ch = -1;
|
||||
if (_strnicmp(v, "red", 3) == 0) ch = L4GraphicsPort::RedChannel;
|
||||
else if (_strnicmp(v, "green", 5) == 0) ch = L4GraphicsPort::GreenChannel;
|
||||
else if (_strnicmp(v, "blue", 4) == 0) ch = L4GraphicsPort::BlueChannel;
|
||||
if (ch >= 0)
|
||||
{
|
||||
chans[count] = ch;
|
||||
DEBUG_STREAM << "[glasswin] channel override: " << names[count]
|
||||
<< " -> " << (ch == L4GraphicsPort::RedChannel ? "red"
|
||||
: ch == L4GraphicsPort::GreenChannel ? "green" : "blue")
|
||||
<< std::endl << std::flush;
|
||||
++count;
|
||||
}
|
||||
const char *nx = strchr(v, ',');
|
||||
if (nx == NULL) break;
|
||||
e = nx + 1;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < count; ++i)
|
||||
if (portName != NULL && _stricmp(names[i], portName) == 0)
|
||||
return chans[i];
|
||||
return -1;
|
||||
}
|
||||
|
||||
// The channel a surface actually rides on THIS rig: the override if given,
|
||||
// else what the pod config authored.
|
||||
static int
|
||||
ResolvedChannelOf(const char *portName, L4GraphicsPort *port)
|
||||
{
|
||||
int ov = PodChannelOverride(portName);
|
||||
if (ov >= 0)
|
||||
return ov;
|
||||
return (port != NULL) ? (port->GetEnableID() & 0x7F) : -1;
|
||||
}
|
||||
|
||||
// BT_POD_IDENT=1 -- draw each surface's identity ON the glass (title, monitor,
|
||||
// and in RGB mode the channel->surface map). The only reliable way to learn
|
||||
// which physical cab panel a Windows display drives.
|
||||
static int
|
||||
PodIdentMode()
|
||||
{
|
||||
static int m = -1;
|
||||
if (m < 0)
|
||||
{
|
||||
const char *e = getenv("BT_POD_IDENT");
|
||||
m = (e != NULL && e[0] != 0 && e[0] != '0') ? 1 : 0;
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
// BT_POD_CHANTEST=<seconds> -- CHANNEL WALK, the instrument for mapping
|
||||
// splitter-wired glass. On a cab, three mono CRTs share one VGA port through
|
||||
// its R/G/B lines, so "which panel is which" cannot be read off Windows: the
|
||||
// desktop sees ONE display for three monitors. This drives ONE channel at a
|
||||
// time -- all-red, then all-green, then all-blue, N seconds each, with the
|
||||
// channel name drawn huge -- so the answer is simply: watch which panel lights
|
||||
// up, and when. Readable even over a rough livestream.
|
||||
// Returns the dwell in seconds (0 = off).
|
||||
static int
|
||||
PodChanTestSeconds()
|
||||
{
|
||||
static int s = -1;
|
||||
if (s < 0)
|
||||
{
|
||||
const char *e = getenv("BT_POD_CHANTEST");
|
||||
s = (e != NULL && e[0] != 0) ? atoi(e) : 0;
|
||||
if (s < 0) s = 0;
|
||||
if (e != NULL && e[0] != 0 && s == 0) s = 3; // "1"/"on" -> 3s default
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
// Which channel is lit right now (0=red 1=green 2=blue), from the wall clock.
|
||||
static int
|
||||
PodChanTestPhase()
|
||||
{
|
||||
int dwell = PodChanTestSeconds();
|
||||
if (dwell <= 0)
|
||||
return -1;
|
||||
return (int)((GetTickCount() / (DWORD)(dwell * 1000)) % 3);
|
||||
}
|
||||
|
||||
// BT_POD_RGB=1 -- the pod's REAL video wiring (see pod-hardware.md §THE RGB
|
||||
// SPLIT): one VGA port per window, its R/G/B analog lines split to three
|
||||
// monochrome MFD monitors. Implies pod surface mode.
|
||||
@@ -609,6 +732,73 @@ void
|
||||
}
|
||||
|
||||
// Restore saved positions over the just-computed pod-faithful defaults. Called
|
||||
//---------------------------------------------------------------------------
|
||||
// MONITOR BINDING (2026-08-06) -- "monitor:<name-or-index>" instead of x,y.
|
||||
//
|
||||
// Absolute desktop coordinates do not survive a reboot on a pod: Windows
|
||||
// renumbers the virtual desktop whenever adapters enumerate differently (a USB
|
||||
// display adapter is especially prone), so a layout pinned to 800,122 can land
|
||||
// on the wrong glass -- or off-screen -- after a power cycle. Binding to the
|
||||
// MONITOR instead and resolving its rect at startup is stable across boots.
|
||||
// Heat MFD=monitor:\.\DISPLAY4,bare (device name -- what the receipts print)
|
||||
// Heat MFD=monitor:2,bare (index into the enumeration order)
|
||||
// The surface is centred on that monitor; an exact-size panel fills it.
|
||||
//---------------------------------------------------------------------------
|
||||
struct MonScan { int index; const char *want; int wantIndex; RECT rect; int found; };
|
||||
|
||||
static BOOL CALLBACK
|
||||
MonScanProc(HMONITOR mon, HDC, LPRECT, LPARAM param)
|
||||
{
|
||||
MonScan *sc = (MonScan *)param;
|
||||
MONITORINFOEXA mi;
|
||||
memset(&mi, 0, sizeof(mi));
|
||||
mi.cbSize = sizeof(mi);
|
||||
if (GetMonitorInfoA(mon, (MONITORINFO *)&mi))
|
||||
{
|
||||
int hit = 0;
|
||||
if (sc->want != NULL)
|
||||
{
|
||||
// Match the full device name OR just its tail, so a hand-written
|
||||
// cfg can say `monitor:DISPLAY4` and skip the \\.\ prefix entirely
|
||||
// (backslashes in a config file are a foot-gun -- every shell,
|
||||
// editor and copy-paste path mangles them differently).
|
||||
size_t dl = strlen(mi.szDevice), wl = strlen(sc->want);
|
||||
if (_stricmp(mi.szDevice, sc->want) == 0)
|
||||
hit = 1;
|
||||
else if (wl > 0 && wl <= dl
|
||||
&& _stricmp(mi.szDevice + (dl - wl), sc->want) == 0)
|
||||
hit = 1;
|
||||
}
|
||||
if (!hit && sc->wantIndex >= 0 && sc->index == sc->wantIndex)
|
||||
hit = 1;
|
||||
if (hit && !sc->found)
|
||||
{
|
||||
sc->rect = mi.rcMonitor;
|
||||
sc->found = 1;
|
||||
}
|
||||
}
|
||||
++sc->index;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Resolve "monitor:<spec>" to a rect. Returns 1 on success.
|
||||
static int
|
||||
ResolveMonitorSpec(const char *spec, RECT *out)
|
||||
{
|
||||
while (*spec == ' ' || *spec == ' ') ++spec;
|
||||
MonScan sc;
|
||||
memset(&sc, 0, sizeof(sc));
|
||||
sc.wantIndex = -1;
|
||||
if (spec[0] >= '0' && spec[0] <= '9')
|
||||
sc.wantIndex = atoi(spec);
|
||||
else
|
||||
sc.want = spec;
|
||||
EnumDisplayMonitors(NULL, NULL, MonScanProc, (LPARAM)&sc);
|
||||
if (sc.found)
|
||||
*out = sc.rect;
|
||||
return sc.found;
|
||||
}
|
||||
|
||||
// from Create AFTER ComputeLayout, so any window not named in the file keeps its
|
||||
// computed spot. Restored windows are flagged so the WM_TIMER re-snap (which
|
||||
// re-runs ComputeLayout once the main window appears) leaves them alone.
|
||||
@@ -646,7 +836,34 @@ static void
|
||||
*end = '\0';
|
||||
|
||||
int x = 0, y = 0, w = 0, h = 0;
|
||||
if (sscanf(eq + 1, "%d,%d,%d,%d", &x, &y, &w, &h) < 2)
|
||||
int haveXY = (sscanf(eq + 1, "%d,%d,%d,%d", &x, &y, &w, &h) >= 2);
|
||||
int viaMonitor = 0;
|
||||
if (!haveXY)
|
||||
{
|
||||
// "monitor:<name|index>" -- boot-stable binding (see the resolver).
|
||||
const char *v = eq + 1;
|
||||
while (*v == ' ' || *v == '\t') ++v;
|
||||
if (_strnicmp(v, "monitor:", 8) == 0 || _strnicmp(v, "mon:", 4) == 0)
|
||||
{
|
||||
char spec[64];
|
||||
const char *p2 = strchr(v, ':') + 1;
|
||||
int n2 = 0;
|
||||
while (*p2 && *p2 != ',' && n2 < (int)sizeof(spec) - 1) spec[n2++] = *p2++;
|
||||
spec[n2] = 0;
|
||||
RECT mr;
|
||||
if (ResolveMonitorSpec(spec, &mr))
|
||||
{
|
||||
x = mr.left; y = mr.top;
|
||||
w = mr.right - mr.left; h = mr.bottom - mr.top;
|
||||
haveXY = 1;
|
||||
viaMonitor = 1;
|
||||
}
|
||||
else
|
||||
DEBUG_STREAM << "[glasswin] monitor '" << spec
|
||||
<< "' not found -- leaving computed placement\n" << std::flush;
|
||||
}
|
||||
}
|
||||
if (!haveXY)
|
||||
continue;
|
||||
|
||||
// Trailing options after the numbers, comma-separated. Unknown ones
|
||||
@@ -666,11 +883,30 @@ static void
|
||||
GWin *gw = FindGWinByTitle(s);
|
||||
if (gw == NULL)
|
||||
continue;
|
||||
gw->wantX = x;
|
||||
gw->wantY = y;
|
||||
gw->noFrame = noframe;
|
||||
// "bare" is a pod panel: it always implies frameless. (Without this a
|
||||
// cfg line saying only ",bare" silently re-framed a window that pod
|
||||
// surface mode had already cropped -- caught on the bench 2026-08-06.)
|
||||
gw->noFrame = (noframe || bare) ? 1 : 0;
|
||||
if (bare && gw->buttonCount > 0)
|
||||
MakeBareSurface(*gw); // per-window pod panel (mixed rigs)
|
||||
if (viaMonitor)
|
||||
{
|
||||
// x,y,w,h is the MONITOR rect -- centre this surface on it (an
|
||||
// exact-size pod panel fills it edge to edge).
|
||||
int sw = gw->clientW > 0 ? gw->clientW : (gw->surfaceRect.right - gw->surfaceRect.left);
|
||||
int sh = gw->clientH > 0 ? gw->clientH : (gw->surfaceRect.bottom - gw->surfaceRect.top);
|
||||
gw->wantX = x + ((w - sw) / 2);
|
||||
gw->wantY = y + ((h - sh) / 2);
|
||||
DEBUG_STREAM << "[glasswin] '" << gw->title
|
||||
<< "' bound to monitor " << x << "," << y << " " << w << "x" << h
|
||||
<< " -> window at " << gw->wantX << "," << gw->wantY
|
||||
<< std::endl << std::flush;
|
||||
}
|
||||
else
|
||||
{
|
||||
gw->wantX = x;
|
||||
gw->wantY = y;
|
||||
}
|
||||
gw->restored = 1;
|
||||
++restored;
|
||||
}
|
||||
@@ -841,6 +1077,19 @@ static void
|
||||
mask = 0xFF; // index the low (palette) byte
|
||||
palId = SVGA16::SecondaryPalette;
|
||||
}
|
||||
// RGB mode: the base surface rides a CHANNEL, so its tint is that pure
|
||||
// primary -- resolved live (override first), never the build-time guess.
|
||||
if (w->groupCount > 0 && tint >= 0)
|
||||
{
|
||||
switch (ResolvedChannelOf(w->portPrimary, port))
|
||||
{
|
||||
case L4GraphicsPort::RedChannel: tint = (int)0x00FF0000; break;
|
||||
case L4GraphicsPort::GreenChannel: tint = (int)0x0000FF00; break;
|
||||
case L4GraphicsPort::BlueChannel: tint = (int)0x000000FF; break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
int ow = 0, oh = 0;
|
||||
svga->ExpandPlaneToBGRA(mask, palId, tint, w->rotate, gStage, &ow, &oh);
|
||||
|
||||
@@ -880,9 +1129,9 @@ static void
|
||||
if (ga != NULL && ga->GetEnableID() != L4GraphicsPort::BlankColor)
|
||||
gp = ga;
|
||||
}
|
||||
const int ch = gp->GetEnableID();
|
||||
const int ch = ResolvedChannelOf(w->groupPort[gi], gp);
|
||||
unsigned long chMask;
|
||||
switch (ch & 0x7F)
|
||||
switch (ch)
|
||||
{
|
||||
case L4GraphicsPort::RedChannel: chMask = 0x00FF0000ul; break;
|
||||
case L4GraphicsPort::GreenChannel: chMask = 0x0000FF00ul; break;
|
||||
@@ -1069,6 +1318,113 @@ static void
|
||||
// (NO full-face flash overlay here either -- same wrongness as the
|
||||
// L4VB16 one, removed 2026-08-03: the buttons are big rects tucked
|
||||
// under the surface by design, and the protruding edge is the lamp.)
|
||||
|
||||
// ---- CHANNEL WALK (BT_POD_CHANTEST) ---------------------------------
|
||||
// Overwrite the whole surface with ONE channel at full intensity, so
|
||||
// exactly one monitor of a splitter-fed trio lights. Cycles R->G->B.
|
||||
{
|
||||
const int phase = PodChanTestPhase();
|
||||
if (phase >= 0)
|
||||
{
|
||||
static const COLORREF chColor[3] =
|
||||
{ RGB(255, 0, 0), RGB(0, 255, 0), RGB(0, 0, 255) };
|
||||
static const char *chName[3] = { "RED", "GREEN", "BLUE" };
|
||||
HBRUSH fill = CreateSolidBrush(chColor[phase]);
|
||||
FillRect(dc, &w->surfaceRect, fill);
|
||||
DeleteObject(fill);
|
||||
|
||||
// Name the channel AND what the pod authors onto it here, so a
|
||||
// lit panel identifies itself completely: "GREEN = Mfd2".
|
||||
const char *who = "-";
|
||||
GaugeRenderer *cgr = BTResolveGaugeRenderer();
|
||||
if (cgr != NULL)
|
||||
{
|
||||
const char *cand[4]; int nc = 0;
|
||||
cand[nc++] = w->portPrimary;
|
||||
for (int gi = 0; gi < w->groupCount && nc < 4; ++gi)
|
||||
cand[nc++] = w->groupPort[gi];
|
||||
for (int i = 0; i < nc; ++i)
|
||||
{
|
||||
L4GraphicsPort *pp =
|
||||
static_cast<L4GraphicsPort*>(cgr->GetGraphicsPort(cand[i]));
|
||||
if (pp != NULL && (pp->GetEnableID() & 0x7F) == phase)
|
||||
{ who = cand[i]; break; }
|
||||
}
|
||||
}
|
||||
char big[96];
|
||||
sprintf(big, "%s %s = %s", w->monitorName, chName[phase], who);
|
||||
RECT br = w->surfaceRect;
|
||||
br.top += (br.bottom - br.top) / 3;
|
||||
SetBkMode(dc, TRANSPARENT);
|
||||
DrawLabelText(dc, big, titleFont, &br, RGB(0, 0, 0),
|
||||
DT_CENTER | DT_SINGLELINE | DT_VCENTER);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- PANEL IDENTIFY (BT_POD_IDENT=1) --------------------------------
|
||||
// Pod bring-up: nobody can tell which PHYSICAL panel a Windows display
|
||||
// is, and desktop coordinate order says nothing about where a panel
|
||||
// sits in the cab -- guessing that mapping is exactly how it came out
|
||||
// wrong. So label each surface ON ITS OWN GLASS with what it is, which
|
||||
// monitor it came from, and (in RGB mode) the channel->surface map.
|
||||
// Walk the pod, read the panels, write the layout from what you SAW.
|
||||
if (PodIdentMode())
|
||||
{
|
||||
char line1[96], line2[160];
|
||||
sprintf(line1, "%s", w->title);
|
||||
if (w->groupCount > 0)
|
||||
{
|
||||
// name the channels so a splitter-fed trio is self-describing
|
||||
char parts[128];
|
||||
parts[0] = 0;
|
||||
GaugeRenderer *igr = BTResolveGaugeRenderer();
|
||||
const char *names[4]; int chans[4]; int nn = 0;
|
||||
names[nn] = w->portPrimary; chans[nn] = -1; ++nn;
|
||||
for (int gi = 0; gi < w->groupCount && nn < 4; ++gi)
|
||||
{ names[nn] = w->groupPort[gi]; chans[nn] = -1; ++nn; }
|
||||
for (int i = 0; i < nn; ++i)
|
||||
{
|
||||
const char *chName = "?";
|
||||
if (igr != NULL)
|
||||
{
|
||||
L4GraphicsPort *pp =
|
||||
static_cast<L4GraphicsPort*>(igr->GetGraphicsPort(names[i]));
|
||||
if (pp != NULL)
|
||||
switch (pp->GetEnableID() & 0x7F)
|
||||
{
|
||||
case L4GraphicsPort::RedChannel: chName = "RED"; break;
|
||||
case L4GraphicsPort::GreenChannel: chName = "GREEN"; break;
|
||||
case L4GraphicsPort::BlueChannel: chName = "BLUE"; break;
|
||||
case L4GraphicsPort::AllChannels: chName = "RGB"; break;
|
||||
default: chName = "blank"; break;
|
||||
}
|
||||
}
|
||||
char one[48];
|
||||
sprintf(one, "%s%s=%s", (i ? " " : ""), chName, names[i]);
|
||||
if (strlen(parts) + strlen(one) < sizeof(parts) - 1)
|
||||
strcat(parts, one);
|
||||
}
|
||||
sprintf(line2, "%s %s", w->monitorName, parts);
|
||||
}
|
||||
else
|
||||
{
|
||||
sprintf(line2, "%s surface=%s", w->monitorName,
|
||||
w->portPrimary ? w->portPrimary : "-");
|
||||
}
|
||||
|
||||
RECT lr = w->surfaceRect;
|
||||
lr.bottom = lr.top + 64;
|
||||
HBRUSH back = CreateSolidBrush(RGB(0, 0, 0));
|
||||
FillRect(dc, &lr, back);
|
||||
DeleteObject(back);
|
||||
SetBkMode(dc, TRANSPARENT);
|
||||
RECT t1 = lr; t1.top += 4; t1.bottom = t1.top + 30;
|
||||
DrawLabelText(dc, line1, titleFont, &t1, RGB(255, 255, 0),
|
||||
DT_LEFT | DT_SINGLELINE | DT_VCENTER);
|
||||
RECT t2 = lr; t2.top += 32; t2.bottom = t2.top + 28;
|
||||
DrawLabelText(dc, line2, buttonFont, &t2, RGB(0, 255, 255),
|
||||
DT_LEFT | DT_SINGLELINE | DT_VCENTER);
|
||||
}
|
||||
}
|
||||
|
||||
BitBlt(winDC, 0, 0, client.right - client.left, client.bottom - client.top,
|
||||
@@ -1205,11 +1561,23 @@ void
|
||||
if (gWinCount != 0)
|
||||
return; // already up
|
||||
|
||||
// BT_GAUGE_SEC_ROT: the portrait-CRT unrotation direction (shared with the
|
||||
// D3D dev path; default 3 = CW = user-verified upright).
|
||||
// BT_GAUGE_SEC_ROT: how far to turn the secondary/radar surface.
|
||||
// 0 = none, 1 = 90 CCW, 2 = 180, 3 = 90 CW (default -- the pod's
|
||||
// portrait CRT, user-verified upright).
|
||||
// 0 and 2 keep the picture LANDSCAPE, which a modern replacement panel
|
||||
// needs. (Until 2026-08-06 this read only '1' and forced everything else
|
||||
// to 3, so BT_GAUGE_SEC_ROT=2 was silently ignored -- caught on the cart
|
||||
// when a 180 request kept coming back portrait.)
|
||||
{
|
||||
const char *rv = getenv("BT_GAUGE_SEC_ROT");
|
||||
gRadarRot = (rv != NULL && rv[0] == '1') ? 1 : 3;
|
||||
if (rv != NULL && rv[0] >= '0' && rv[0] <= '3')
|
||||
gRadarRot = rv[0] - '0';
|
||||
else
|
||||
gRadarRot = 3;
|
||||
DEBUG_STREAM << "[glasswin] radar rotation " << gRadarRot
|
||||
<< (gRadarRot == 3 ? " (90 CW)" : gRadarRot == 1 ? " (90 CCW)"
|
||||
: gRadarRot == 2 ? " (180)" : " (none)")
|
||||
<< std::endl << std::flush;
|
||||
}
|
||||
|
||||
memset(latched, 0, sizeof(latched));
|
||||
@@ -1348,6 +1716,8 @@ void
|
||||
if (mon != NULL && GetMonitorInfoA(mon, (MONITORINFO *)&mi))
|
||||
{
|
||||
devName = mi.szDevice;
|
||||
strncpy(w.monitorName, mi.szDevice, sizeof(w.monitorName) - 1);
|
||||
w.monitorName[sizeof(w.monitorName) - 1] = 0;
|
||||
mx = mi.rcMonitor.left; my = mi.rcMonitor.top;
|
||||
mw = mi.rcMonitor.right - mi.rcMonitor.left;
|
||||
mh = mi.rcMonitor.bottom - mi.rcMonitor.top;
|
||||
|
||||
@@ -90,6 +90,23 @@ L4GaugeRenderer::L4GaugeRenderer(bool windowed, int *secondaryIndex, int *aux1In
|
||||
char
|
||||
*plasma_string = getenv("L4PLASMA");
|
||||
|
||||
//
|
||||
// L4PLASMA=NONE / OFF / 0 -- no plasma marquee at all (2026-08-06).
|
||||
// A real cab drives the 128x32 marquee over serial; a rebuilt cart may
|
||||
// not have one wired, and the GLASS profile force-defaults L4PLASMA to
|
||||
// SCREEN, which puts a desktop plasma window on the pod's glass where
|
||||
// nothing should be. Leaving the variable UNSET is not an option
|
||||
// there (the profile fills it in), so give "off" an explicit spelling.
|
||||
//
|
||||
if (plasma_string != NULL
|
||||
&& (_stricmp(plasma_string, "NONE") == 0
|
||||
|| _stricmp(plasma_string, "OFF") == 0
|
||||
|| strcmp(plasma_string, "0") == 0))
|
||||
{
|
||||
Tell("Plasma display disabled (L4PLASMA=none)\n");
|
||||
plasma_string = NULL;
|
||||
}
|
||||
|
||||
if (plasma_string != NULL)
|
||||
{
|
||||
//Win32 Serial support: ADB 06/30/07
|
||||
|
||||
@@ -739,6 +739,34 @@ void SVGA16::ExpandPlaneToBGRA(int mask, int paletteID, int monoTint, int rotate
|
||||
return;
|
||||
}
|
||||
|
||||
// 180 degrees (rotate 2): dimensions UNCHANGED -- the case a landscape
|
||||
// replacement panel needs when the 1995 glass was a portrait CRT mounted
|
||||
// the other way up. Added 2026-08-06 from the crash cart, whose radar is a
|
||||
// landscape LCD: 90 CW left the picture portrait and overhanging its
|
||||
// monitor, and a further 90 CW is this.
|
||||
if (rotateQuadrant == 2)
|
||||
{
|
||||
for (int oy = 0; oy < h; oy++)
|
||||
{
|
||||
unsigned long *d = dst + oy * w;
|
||||
for (int ox = 0; ox < w; ox++)
|
||||
{
|
||||
Word s = base[(h - 1 - oy) * w + (w - 1 - ox)];
|
||||
if (monoTint < 0)
|
||||
{
|
||||
PaletteTriplet *pe = &(pal->paletteData.Color[s & mask]);
|
||||
d[ox] = ((unsigned long)pe->Red << 16) |
|
||||
((unsigned long)pe->Green << 8) | (unsigned long)pe->Blue;
|
||||
}
|
||||
else
|
||||
d[ox] = (s & mask) ? (unsigned long)monoTint : 0UL;
|
||||
}
|
||||
}
|
||||
if (outW) *outW = w;
|
||||
if (outH) *outH = h;
|
||||
return;
|
||||
}
|
||||
|
||||
// 90-degree rotation: output is transposed (ow = h, oh = w). rotate 3 = CW,
|
||||
// rotate 1 = CCW (the DrawDevSurface convention; BT_GAUGE_SEC_ROT picks it).
|
||||
int ow = h, oh = w;
|
||||
|
||||
+27
-1
@@ -407,6 +407,18 @@ static const char *gBTCwdFixNote = NULL;
|
||||
//
|
||||
int gBTFitDisplay = 0;
|
||||
|
||||
//
|
||||
// L4PLASMA=NONE/OFF/0 means "no marquee at all" (L4GREND.cpp reads the same
|
||||
// spellings). Only used to keep the boot banner honest.
|
||||
//
|
||||
static bool BTPlasmaDisabled(void)
|
||||
{
|
||||
const char *p = getenv("L4PLASMA");
|
||||
return p != NULL && (_stricmp(p, "NONE") == 0 ||
|
||||
_stricmp(p, "OFF") == 0 ||
|
||||
strcmp(p, "0") == 0);
|
||||
}
|
||||
|
||||
static void BTEnsureContentDirectory(void)
|
||||
{
|
||||
if (GetFileAttributesA("BTL4.RES") != INVALID_FILE_ATTRIBUTES)
|
||||
@@ -848,6 +860,18 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
|
||||
{
|
||||
gBTFitDisplay = 1;
|
||||
}
|
||||
//
|
||||
// BT_FIT=1 -- the env spelling of -fit, so a POD can freeze it in
|
||||
// content\environ.ini alongside the rest of its rig config instead of
|
||||
// depending on every launcher passing the flag (2026-08-06, crash cart:
|
||||
// the main view has to fill its 800x600 panel on every boot, however the
|
||||
// game gets started -- shortcut, autostart or scheduled task).
|
||||
//
|
||||
{
|
||||
const char *fitEnv = getenv("BT_FIT");
|
||||
if (fitEnv != NULL && fitEnv[0] != '\0' && fitEnv[0] != '0')
|
||||
gBTFitDisplay = 1;
|
||||
}
|
||||
|
||||
// BT_JOYCONFIG=1: the generic-joystick capture wizard (flight sticks /
|
||||
// HOTAS / pedals -- L4JOY.h). Console prompts detect which device/axis
|
||||
@@ -1145,7 +1169,9 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
|
||||
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 + plasma window)"
|
||||
: gBTPlatformGlass ? (BTPlasmaDisabled()
|
||||
? "GLASS (PadRIO; plasma off [L4PLASMA])"
|
||||
: "GLASS (PadRIO + plasma window)")
|
||||
: "DEV (single window + keyboard)");
|
||||
if (!fe_menu_mode)
|
||||
std::cout << " [secondary displays: " << kGlassLayoutName[glassLayout] << "]";
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
# IDENTIFY PASS -- assignment here is arbitrary on purpose. Each panel prints
|
||||
# its own name + monitor, so we map the cab from what the GLASS says, not from
|
||||
# desktop coordinate order (which is what got it wrong the first time).
|
||||
#
|
||||
# Bound by MONITOR IDENTITY, not x,y: survives reboots and adapter renumbering.
|
||||
VGA Port A=monitor:DISPLAY4,bare
|
||||
VGA Port B=monitor:DISPLAY2,bare
|
||||
Secondary / Radar=monitor:DISPLAY1,bare
|
||||
@@ -0,0 +1,8 @@
|
||||
# Corrected from the cab photo (2026-08-06): DISPLAY4 feeds the COLOUR RADAR
|
||||
# LCD -- the RGB composite was landing there and showing all three channels
|
||||
# overlaid. Colour surface to the colour panel; the RGB composites to the
|
||||
# displays that feed the mono CRT groups. Which CRT group is DISPLAY2 vs
|
||||
# DISPLAY1 is still unknown -- the channel test settles it.
|
||||
Secondary / Radar=monitor:DISPLAY4,bare
|
||||
VGA Port A=monitor:DISPLAY2,bare
|
||||
VGA Port B=monitor:DISPLAY1,bare
|
||||
@@ -0,0 +1,25 @@
|
||||
Add-Type @"
|
||||
using System;using System.Text;using System.Runtime.InteropServices;
|
||||
public class W{
|
||||
[DllImport("user32.dll")] public static extern bool EnumWindows(EnumWindowsProc cb,IntPtr p);
|
||||
public delegate bool EnumWindowsProc(IntPtr h,IntPtr p);
|
||||
[DllImport("user32.dll")] public static extern int GetWindowText(IntPtr h,StringBuilder s,int c);
|
||||
[DllImport("user32.dll")] public static extern bool IsWindowVisible(IntPtr h);
|
||||
[DllImport("user32.dll")] public static extern uint GetWindowThreadProcessId(IntPtr h,out uint p);
|
||||
[DllImport("user32.dll")] public static extern bool GetWindowRect(IntPtr h,out R r);
|
||||
public struct R{public int L,T,Rr,B;}
|
||||
}
|
||||
"@
|
||||
$target = (Get-Process btl4 -ErrorAction SilentlyContinue).Id
|
||||
if (-not $target) { Write-Output "btl4 NOT RUNNING"; exit }
|
||||
[W]::EnumWindows({ param($h,$l)
|
||||
$o = 0
|
||||
[W]::GetWindowThreadProcessId($h, [ref]$o) | Out-Null
|
||||
if ($o -eq $target -and [W]::IsWindowVisible($h)) {
|
||||
$sb = New-Object System.Text.StringBuilder 256
|
||||
[W]::GetWindowText($h, $sb, 256) | Out-Null
|
||||
$r = New-Object 'W+R'
|
||||
[W]::GetWindowRect($h, [ref]$r) | Out-Null
|
||||
Write-Output ("WIN '" + $sb.ToString() + "' at " + $r.L + "," + $r.T + " " + ($r.Rr-$r.L) + "x" + ($r.B-$r.T))
|
||||
}
|
||||
return $true }, [IntPtr]::Zero) | Out-Null
|
||||
@@ -0,0 +1,21 @@
|
||||
# Merge the POD PROFILE block into the cart's content\environ.ini, idempotently.
|
||||
# Any previous block between the two markers is replaced, so re-running is safe.
|
||||
$ini = 'C:\bt411\BT411_4.11.801\content\environ.ini'
|
||||
$prof = 'C:\bt411\BT411_4.11.801\content\podprofile.ini'
|
||||
|
||||
$block = Get-Content $prof
|
||||
$keep = @()
|
||||
if (Test-Path $ini) {
|
||||
$inBlock = $false
|
||||
foreach ($line in (Get-Content $ini)) {
|
||||
if ($line -match '^# ==== BT411 POD PROFILE') { $inBlock = $true; continue }
|
||||
if ($line -match '^# ==== END BT411 POD PROFILE') { $inBlock = $false; continue }
|
||||
if (-not $inBlock) { $keep += $line }
|
||||
}
|
||||
}
|
||||
# Trim trailing blanks so repeated merges do not grow the file.
|
||||
while ($keep.Count -gt 0 -and $keep[-1].Trim() -eq '') { $keep = $keep[0..($keep.Count-2)] }
|
||||
|
||||
($keep + @('') + $block) | Set-Content $ini -Encoding ascii
|
||||
Write-Output "environ.ini: $((Get-Content $ini).Count) lines, profile keys:"
|
||||
Get-Content $ini | Select-String '^(BT_|L4)' | ForEach-Object { $_.Line }
|
||||
@@ -0,0 +1,36 @@
|
||||
# ==== BT411 POD PROFILE (ALPHA-MR crash cart) ================================
|
||||
# Frozen 2026-08-06 from the verified-good bring-up. Everything a launcher
|
||||
# used to have to pass now lives here, so the rig comes up correct however
|
||||
# the game is started -- shortcut, scheduled task or autostart.
|
||||
# Physical mapping + the RGB-split decode: context/pod-hardware.md.
|
||||
#
|
||||
# The layout itself (which surface sits on which monitor) is in
|
||||
# glass_layout.cfg beside this file, loaded by BT_GLASS_LAYOUT=load:
|
||||
# Secondary / Radar = monitor:DISPLAY4 (colour LCD in the yellow frame)
|
||||
# VGA Port A = monitor:DISPLAY2 (RGB triple: Heat + Comm + Mfd2)
|
||||
# VGA Port B = monitor:DISPLAY1 (RGB triple: Mfd1 + Mfd3)
|
||||
|
||||
BT_GLASS=1
|
||||
BT_GLASS_PANELS=1
|
||||
|
||||
# ONE VGA port's R/G/B lines drive THREE mono MFDs -- composite the planes.
|
||||
BT_POD_RGB=1
|
||||
|
||||
# THIS cab's splitter wires Comm and Heat to different colour lines than the
|
||||
# 1995 L4GAUGE.CFG assumed (kills/deaths and coolant loops came up swapped).
|
||||
BT_POD_CHANMAP=Comm=blue,Heat=red
|
||||
|
||||
# The radar panel here is a LANDSCAPE LCD; the 1995 pod rotated only because
|
||||
# its CRT was mounted portrait. 0 = leave the 640x480 source alone.
|
||||
BT_GAUGE_SEC_ROT=0
|
||||
|
||||
BT_GLASS_LAYOUT=load
|
||||
BT_START_INSIDE=1
|
||||
|
||||
# Main view fills its 800x600 panel (DISPLAY3, the primary) with no border.
|
||||
BT_FIT=1
|
||||
|
||||
# No 128x32 marquee wired on this cart -- and the glass profile would
|
||||
# otherwise default L4PLASMA to SCREEN and drop a plasma window on the glass.
|
||||
L4PLASMA=NONE
|
||||
# ==== END BT411 POD PROFILE ==================================================
|
||||
@@ -1,12 +1,9 @@
|
||||
@echo off
|
||||
REM BT411 pod launch -- console session (started via schtasks /IT).
|
||||
REM BT411 pod -- ALPHA-MR. The rig config is FROZEN in content\environ.ini
|
||||
REM (glass panels, RGB split, channel map, radar rotation, -fit, no plasma),
|
||||
REM so this launcher carries nothing but the log switches and the mission.
|
||||
REM Fallback if environ.ini is ever lost: runpod_env.bat sets it all inline.
|
||||
cd /d C:\bt411\BT411_4.11.801\content
|
||||
set BT_GLASS=1
|
||||
set BT_GLASS_PANELS=1
|
||||
set BT_POD_RGB=1
|
||||
set BT_GLASS_LAYOUT=load
|
||||
set BT_GLASS_LOG=1
|
||||
set BT_START_INSIDE=1
|
||||
set BT_FE_SOLO=1
|
||||
set BT_LOG=podrun.log
|
||||
start "" ..\build\Release\btl4.exe
|
||||
start "" ..\build\Release\btl4.exe -egg PODTEST.EGG
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
@echo off
|
||||
REM BT411 pod -- ALPHA-MR working config.
|
||||
REM layout : radar on DISPLAY4 (the colour LCD), RGB composites on 2 and 1
|
||||
REM fix 1 : Comm/Heat ride swapped colour lines on THIS cab's splitter
|
||||
REM fix 2 : radar panel is landscape -> UNROTATED landscape, not the pod portrait CRT
|
||||
cd /d C:\bt411\BT411_4.11.801\content
|
||||
set BT_GLASS=1
|
||||
set BT_GLASS_PANELS=1
|
||||
set BT_POD_RGB=1
|
||||
set BT_POD_CHANMAP=Comm=blue,Heat=red
|
||||
set BT_GAUGE_SEC_ROT=0
|
||||
set BT_GLASS_LAYOUT=load
|
||||
set BT_GLASS_LOG=1
|
||||
set BT_START_INSIDE=1
|
||||
set BT_LOG=podrun.log
|
||||
start "" ..\build\Release\btl4.exe -egg PODTEST.EGG
|
||||
@@ -0,0 +1,101 @@
|
||||
@echo off
|
||||
REM ===========================================================================
|
||||
REM podbringup.bat -- ONE double-click pod bring-up. BT411.
|
||||
REM
|
||||
REM 1. self-elevates (accept the UAC prompt -- that is the only interaction)
|
||||
REM 2. installs the built-in Windows OpenSSH SERVER
|
||||
REM 3. authorizes ONE key (the laptop's bt411 key) and locks its ACL
|
||||
REM 4. opens SSH on private/domain profiles ONLY (never the public internet)
|
||||
REM 5. probes the display/GPU/monitor/serial topology
|
||||
REM 6. sends the results BACK over Tailscale -- no copying by hand
|
||||
REM
|
||||
REM Undo (admin cmd):
|
||||
REM sc stop sshd & sc config sshd start= disabled
|
||||
REM del C:\ProgramData\ssh\administrators_authorized_keys
|
||||
REM netsh advfirewall firewall delete rule name="BT411 SSH"
|
||||
REM ===========================================================================
|
||||
|
||||
REM ---- 1. elevate -----------------------------------------------------------
|
||||
net session >nul 2>&1
|
||||
if %errorlevel% neq 0 (
|
||||
echo Requesting administrator rights -- click YES on the prompt...
|
||||
powershell -Command "Start-Process -FilePath '%~f0' -Verb RunAs"
|
||||
exit /b
|
||||
)
|
||||
|
||||
setlocal enabledelayedexpansion
|
||||
set LAPTOP=desktop-ae1su9u
|
||||
set TS=C:\PROGRA~1\Tailscale\tailscale.exe
|
||||
set REPORT=%~dp0podbringup_report.txt
|
||||
set DXOUT=%~dp0podbringup_dxdiag.txt
|
||||
|
||||
echo ==================== BT411 POD BRING-UP ====================> "%REPORT%"
|
||||
echo host=%COMPUTERNAME% user=%USERNAME% session=%SESSIONNAME%>> "%REPORT%"
|
||||
ver >> "%REPORT%"
|
||||
echo.>> "%REPORT%"
|
||||
|
||||
REM ---- 2. OpenSSH server ----------------------------------------------------
|
||||
echo [1/5] installing OpenSSH server (this can take a minute)...
|
||||
echo ---- OpenSSH install ---->> "%REPORT%"
|
||||
dism /online /quiet /norestart /add-capability /capabilityname:OpenSSH.Server~~~~0.0.1.0 >> "%REPORT%" 2>&1
|
||||
sc config sshd start= auto >> "%REPORT%" 2>&1
|
||||
net start sshd >> "%REPORT%" 2>&1
|
||||
sc query sshd | findstr /i "STATE" >> "%REPORT%" 2>&1
|
||||
|
||||
REM ---- 3. authorize the laptop key -----------------------------------------
|
||||
echo [2/5] authorizing the BT411 key...
|
||||
if not exist C:\ProgramData\ssh mkdir C:\ProgramData\ssh
|
||||
set AKF=C:\ProgramData\ssh\administrators_authorized_keys
|
||||
findstr /c:"bt411-claude-code@laptop" "%AKF%" >nul 2>&1
|
||||
if errorlevel 1 (
|
||||
echo ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICmGkinDbZMwQ/UhwqU/oECUIeAN+pBjLgsekj41di7W bt411-claude-code@laptop>> "%AKF%"
|
||||
)
|
||||
icacls "%AKF%" /inheritance:r /grant "SYSTEM:F" /grant "BUILTIN\Administrators:F" >> "%REPORT%" 2>&1
|
||||
echo ---- authorized_keys ---->> "%REPORT%"
|
||||
type "%AKF%" >> "%REPORT%" 2>&1
|
||||
|
||||
REM ---- 4. firewall (private/domain only) ------------------------------------
|
||||
echo [3/5] firewall rule (private/tailnet only)...
|
||||
netsh advfirewall firewall show rule name="BT411 SSH" >nul 2>&1
|
||||
if errorlevel 1 (
|
||||
netsh advfirewall firewall add rule name="BT411 SSH" dir=in action=allow protocol=TCP localport=22 profile=private,domain >> "%REPORT%" 2>&1
|
||||
)
|
||||
|
||||
REM ---- 5. topology probe ----------------------------------------------------
|
||||
echo [4/5] probing displays / GPUs / monitors / serial ports...
|
||||
echo.>> "%REPORT%"
|
||||
echo ---- OS ---->> "%REPORT%"
|
||||
wmic os get Caption,Version,BuildNumber,OSArchitecture /format:list >> "%REPORT%" 2>&1
|
||||
echo ---- GPUs ---->> "%REPORT%"
|
||||
wmic path Win32_VideoController get Name,DriverVersion,DriverDate,CurrentHorizontalResolution,CurrentVerticalResolution /format:list >> "%REPORT%" 2>&1
|
||||
echo ---- Monitors ---->> "%REPORT%"
|
||||
wmic path Win32_DesktopMonitor get DeviceID,Name,ScreenWidth,ScreenHeight,Availability /format:list >> "%REPORT%" 2>&1
|
||||
echo ---- Serial ports (RIO) ---->> "%REPORT%"
|
||||
wmic path Win32_SerialPort get DeviceID,Name /format:list >> "%REPORT%" 2>&1
|
||||
echo ---- Tailscale ---->> "%REPORT%"
|
||||
"%TS%" ip -4 >> "%REPORT%" 2>&1
|
||||
echo ---- CONNECT WITH ---->> "%REPORT%"
|
||||
echo ssh -i ~/.ssh/bt411_pod %USERNAME%@bt411-pod>> "%REPORT%"
|
||||
|
||||
echo [5/5] dxdiag (monitor rects + D3D caps, ~30s)...
|
||||
dxdiag /whql:off /t "%DXOUT%"
|
||||
|
||||
REM ---- 6. send it all back --------------------------------------------------
|
||||
echo Sending results back to %LAPTOP% over Tailscale...
|
||||
"%TS%" file cp "%REPORT%" "%DXOUT%" %LAPTOP%:
|
||||
if errorlevel 1 (
|
||||
echo.
|
||||
echo ! Taildrop send failed -- right-click these two files and use
|
||||
echo "Send with Tailscale" instead:
|
||||
echo %REPORT%
|
||||
echo %DXOUT%
|
||||
) else (
|
||||
echo ...sent.
|
||||
)
|
||||
|
||||
echo.
|
||||
echo ================= DONE =================
|
||||
echo ssh user: %USERNAME% host: bt411-pod
|
||||
type "%REPORT%" | findstr /i "STATE ssh-ed25519 Caption"
|
||||
echo.
|
||||
pause
|
||||
Reference in New Issue
Block a user