platform profile + gauge dev-composite (Milestone A: gauge renderer woken)

PLATFORM PROFILE (-platform pod|dev / BT_PLATFORM / run.cmd pod; default dev):
an env-preset selector, NOT a code fork -- it picks which environment the
existing video/gauge/input code reads.  dev = single 800x600 window + keyboard
(the working build); pod = RIO input + (on real hardware) the multi-surface
gauges/MFDs, mirroring content/SETENV.BAT (the pod path -- FindBestAdapterIndices
/ SVGA16 -- is left untouched).  The multi-surface needs the pod's 2 video cards,
so -platform pod does NOT auto-enable L4GAUGE on a dev box (stays bootable,
single-window).  Also fixes a real engine bug: SVGA16::BuildWindows
PostQuitMessage'd on a CreateDevice failure but fell through to a null-device
deref (segfault) -- now breaks (inert on the pod).

GAUGE DEV-COMPOSITE, Milestone A (option B; opt-in BT_DEV_GAUGES, default OFF):
wake the (dormant) gauge renderer so its CPU-rastered pixelBuffer can later be
composited into the dev window, WITHOUT touching the pod SVGA16 output path.
SVGA16 does no per-surface D3D in this mode (a DevGaugeComposite() gate forces
the no-surface path + short-circuits Update/Refresh).  Waking the never-exercised
gauge subsystem exposed 4 latent reconstruction bugs, each guarded:
SVGA16::Update / Refresh (empty mSurfaces[]), LBE4ControlsManager::MakeLinkedLamp
(NULL lamp manager), L4GaugeRenderer::NotifyOfNewInterestingEntity (garbage
warehouse chain).  It now boots STABLY; the gauge reconstruction is incomplete
(shadowed lamp-manager + warehouse members) -- Milestone B (the composite pass)
will reveal whether the content is real.  See docs/GAUGE_COMPOSITE.md.

Verified: default DEV un-regressed (combat DESTROYED, 0 crashes); pod path
untouched; BT_DEV_GAUGES boots stable.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-06 10:18:40 -05:00
co-authored by Claude Opus 4.8
parent 8b36440d05
commit c13e72d0ba
7 changed files with 314 additions and 11 deletions
+7
View File
@@ -2054,6 +2054,13 @@ Lamp *
L4LampManager
*lamp_manager = (L4LampManager *) application->
GetGaugeRenderer()->GetLampManager();
// DEV-COMPOSITE GUARD: when the gauge renderer is woken (BT_DEV_GAUGES) but its
// reconstructed lamp manager isn't wired (GetLampManager returns NULL -- a recon
// offset gap in BTL4GaugeRenderer), skip the linked cockpit lamp rather than
// constructing an L4Lamp with a NULL manager (crashes in LampManager::AddLamp).
// The lamp is a secondary cockpit indicator; inert on the pod (manager valid).
if (lamp_manager == NULL)
return NULL;
Check(lamp_manager);
Lamp
+12 -3
View File
@@ -437,9 +437,18 @@ void
//----------------------------------------------
Check(warehousePointer);
((L4Warehouse*)warehousePointer)->gaugeImageBin.Get(
interesting_entity->GetResourceID()
);
// DEV-COMPOSITE GUARD: the reconstructed BTL4GaugeRenderer's warehouse / gaugeImageBin
// is not fully wired (its internal chain reads garbage -> WarehouseBin::Find AVs).
// Skip the per-entity gauge-IMAGE caching when the gauge renderer is woken on a dev
// box (BT_DEV_GAUGES); the radar/secondary composite doesn't need per-mech images.
// Inert on the pod (the real warehouse is valid). FAITHFUL FOLLOW-UP: fix the
// BTL4GaugeRenderer warehouse reconstruction (likely a shadowed warehousePointer).
if (getenv("BT_DEV_GAUGES") == NULL)
{
((L4Warehouse*)warehousePointer)->gaugeImageBin.Get(
interesting_entity->GetResourceID()
);
}
//----------------------------------------------
// Update gaugeRenderer
//----------------------------------------------
+47
View File
@@ -98,6 +98,17 @@
// );
// DEV-COMPOSITE (option B): opt-in via BT_DEV_GAUGES. When set, SVGA16 does NO
// per-surface D3D output (no per-adapter devices/windows/present) -- it only holds
// the CPU pixelBuffer the gauge widgets raster into; the MAIN renderer composites
// that buffer as a window inset. The pod multi-surface path is left untouched.
static bool DevGaugeComposite()
{
static int v = -1;
if (v < 0) v = (getenv("BT_DEV_GAUGES") != NULL) ? 1 : 0;
return v != 0;
}
void SVGA16::BuildWindows(unsigned int width, unsigned int height, bool windowed, int *secondaryIndex, int *aux1Index, int *aux2Index)
{
NUMGAUGEWINDOWS = 0;
@@ -117,8 +128,22 @@ void SVGA16::BuildWindows(unsigned int width, unsigned int height, bool windowed
NUMGAUGEWINDOWS++;
}
if (DevGaugeComposite())
{
// DEV COMPOSITE (option B): force the "no D3D output surfaces" path. Do NOT
// create per-surface devices/windows -- FindBestAdapterIndices hands us the
// primary adapter (up to 3x) on a dev box, but a 2nd/3rd fullscreen device
// can't be created there. The base Video16BitBuffered pixelBuffer still
// exists and the widgets raster into it; the main renderer composites it.
NUMGAUGEWINDOWS = 0;
}
if (NUMGAUGEWINDOWS == 0)
{
// (defensive) NULL the per-surface arrays so ~SVGA16 delete[] is safe on the
// no-surface path (they are not allocated below).
gaugeWindows = NULL; mDevice = NULL; mVertBuffers = NULL;
mSurfaces = NULL; mLockFlags = NULL; mPresentParams = NULL; mSurfaceRects = NULL;
return;
}
@@ -202,6 +227,15 @@ void SVGA16::BuildWindows(unsigned int width, unsigned int height, bool windowed
{
DEBUG_STREAM<<"Couldn't create Direct3D device!"<<std::endl<<std::flush;
PostQuitMessage(1);
// GUARD (platform profile): mDevice[j] is null/uninitialised on
// failure, and the code below (GetDeviceCaps @next line, CreateTexture,
// CreateVertexBuffer) derefs it -> segfault. This is hit when the POD
// profile runs on a box WITHOUT the pod's extra adapters/monitors (a
// second fullscreen gauge device can't be created). PostQuitMessage
// already asked to bail; break so we exit the surface loop cleanly
// instead of crashing. INERT on real pod hardware (CreateDevice
// succeeds there) -- does not change the pod multi-surface behavior.
break;
}
D3DCAPS9 caps;
@@ -4040,6 +4074,13 @@ Logical SVGA16::Update(Logical forceAll)
return False; // Do no more!
}
// DEV-COMPOSITE GUARD (option B): dev-composite SVGA16 does NO per-surface D3D
// (BuildWindows forced NUMGAUGEWINDOWS=0 + allocated nothing); the mSurfaces[0]
// path below would index an absent array. The main renderer composites the shared
// CPU pixelBuffer as a window inset instead. Inert on the pod.
if (DevGaugeComposite() || NUMGAUGEWINDOWS <= 0)
return False;
if (++mDisplayToUpdate >= NUMGAUGEWINDOWS)
mDisplayToUpdate = 0;
@@ -4221,6 +4262,12 @@ Logical SVGA16::Update(Logical forceAll)
void SVGA16::Refresh()
{
// DEV-COMPOSITE GUARD (option B): dev-composite does NO per-surface D3D; skip the
// per-surface clear/present (the main renderer composites the CPU pixelBuffer
// inset). Inert on the pod.
if (DevGaugeComposite() || NUMGAUGEWINDOWS <= 0)
return;
HRESULT hr;
V( mDevice[mDisplayToUpdate]->Clear(0, NULL, D3DCLEAR_TARGET, 0xFF000000, 1.0f, 0) );