gauges: secondary/radar MFD composites live on a dev box (Milestone B)

The pod's secondary/radar cockpit surface now renders as an inset in the
800x600 dev window under BT_DEV_GAUGES: radar/tactical grid, SPEED/HEADING/
MAGNETIC/PROP dials, and the color-coded ARMOR DAMAGE mech schematic -- real
gauge content (nzSec=27247), composited from the shared CPU pixelBuffer.

Composite pass (engine): SVGA16::DrawDevInset palette-expands the secondary
plane into a MANAGED texture on the MAIN device and draws an XYZRHW inset quad;
BTDrawGaugeInset() reaches the gauge renderer's 'sec' port and is called from
DPLRenderer::ExecuteImplementation as the last draw before EndScene.

Three reconstruction bugs fixed to get real content:
1. BTL4Application::MakeGaugeRenderer signature mismatch (REAL fix): the
   reconstructed no-arg override only HID the 2007-engine's widened 3-arg
   virtual MakeGaugeRenderer(int*,int*,int*), so the engine built a base
   L4GaugeRenderer whose ctor never parsed gauge/l4gauge.cfg -> empty symbol
   table -> "undefined label 'bhk1Init'" -> no ports/gauges. Matched the 3-arg
   signature so it truly overrides (args ignored; BT is fullscreen). Same bug
   class as the BTL4GaugeRenderer(false,NULL,NULL,NULL) ctor fix.
2. Parse hung on undefined primitives (gated dev accommodation): the BT gauge
   primitive table (BTL4MethodDescription) is still a stub, so an unknown
   primitive -> ReportParsingError -> Fail() -> a MODAL dialog freezing the
   parse. Under BT_DEV_GAUGES, skip the unknown primitive's params so labels
   register and the base "configure" primitive builds the ports.
3. Gauge widgets AV on NULL data bindings (gated): NumericDisplayScalar's NULL
   value_pointer -> GaugeConnectionDirectOf ctor deref (bind NULL->static zero);
   RankAndScore::Execute derefs unreconstructed game state (Gauge::GuardedExecute
   SEH wrapper -> Disable(True) on first fault).

All guards gated on BT_DEV_GAUGES: default DEV un-regressed (TARGET DESTROYED,
0 crashes) and the pod path is byte-unchanged (strict Fail, no guards).

Remaining (docs/GAUGE_COMPOSITE.md): the real gauge WIDGET reconstruction
(BTL4MethodDescription method table + gauge->game-state data bindings), then
Step 2 (RP<->BT MFD port-name reconcile) + Step 3 (2-window layout).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-06 10:59:45 -05:00
co-authored by Claude Opus 4.8
parent c13e72d0ba
commit a256813395
10 changed files with 308 additions and 10 deletions
+39 -1
View File
@@ -568,7 +568,24 @@ Logical
//-----------------------------------------
// Execute gauge code
//-----------------------------------------
Execute();
{
// BT DEV-GAUGES bring-up: run Execute() under SEH so a gauge whose
// game-state binding isn't reconstructed yet is DISABLED (rate=0)
// after its first fault instead of AV'ing every frame. Gated on
// BT_DEV_GAUGES; the pod build calls Execute() directly.
static int s_devGaugesUpd = -1;
if (s_devGaugesUpd < 0)
s_devGaugesUpd = (getenv("BT_DEV_GAUGES") != NULL) ? 1 : 0;
if (s_devGaugesUpd)
{
if (!GuardedExecute())
Disable(True);
}
else
{
Execute();
}
}
actually_ran = True;
}
Check_Fpu();
@@ -581,6 +598,27 @@ void
Fail("Gauge::Execute not overridden");
}
//
// GuardedExecute -- BT DEV-GAUGES bring-up. Run Execute() under a structured
// exception handler so a gauge whose game-state data binding is not yet
// reconstructed is skipped instead of AV'ing the whole game. (A separate
// function because __try/__except may not share a frame with C++ objects that
// require unwinding, which Gauge::Update does.) Returns False on fault.
//
Logical
Gauge::GuardedExecute()
{
__try
{
Execute();
return True;
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
return False;
}
}
struct TierLookup
{
GaugeRate rate;
+26
View File
@@ -193,6 +193,26 @@ template <class T> GaugeConnectionDirectOf<T>::GaugeConnectionDirectOf(
{
Check_Pointer(this);
Check_Pointer(data_destination);
//
// BT DEV-GAUGES bring-up: some gauge widgets bind to mech data sources that
// are not reconstructed yet, so the attribute resolves to a NULL data_source
// (see the "(Scalar *) is VERY dangerous!" note in L4GAUGE.cpp). Rather than
// AV here (and later in Update()), bind to a static zero so the gauge simply
// reads 0. Gated on BT_DEV_GAUGES so the POD path is byte-unchanged.
//
if (data_source == NULL)
{
static int s_devGauges = -1;
if (s_devGauges < 0)
s_devGauges = (getenv("BT_DEV_GAUGES") != NULL) ? 1 : 0;
if (s_devGauges)
{
static T s_gaugeNullSource = T();
dataSource = &s_gaugeNullSource;
data_source = &s_gaugeNullSource;
}
}
Check_Pointer(data_source);
*dataDestination = *dataSource;
@@ -344,6 +364,12 @@ public:
protected:
virtual void
Execute(); // Execute internal processes to display the gauge
// BT DEV-GAUGES bring-up: SEH wrapper around Execute() so a gauge whose
// game-state data binding isn't reconstructed yet is skipped (not crashed).
// Returns False if Execute() raised a structured exception (e.g. an AV).
Logical
GuardedExecute();
//----------------------------------------------------------------------
// Protected data
//----------------------------------------------------------------------
+36 -1
View File
@@ -1332,7 +1332,42 @@ void
) == False
)
{
ReportParsingError("Undefined primitive");
//
// BT DEV-GAUGES bring-up: the BT-specific gauge primitive
// table (BTL4MethodDescription, btl4grnd.cpp) is still a stub
// -- the BT gauge widget classes (PlayerStatus, ...) aren't
// reconstructed yet, so they don't resolve here. Rather than
// Fail() -- which pops a modal assert dialog and FREEZES the
// game mid-parse -- SKIP the unknown primitive's parameter
// list so the parse can continue: it still registers every
// label (e.g. Bhk1Init -> MechInit) and runs the base
// "configure" primitives that build the gauge PORTS/surfaces
// (the beachhead needs the 'sec'/radar surface, not the
// widgets). Gated on BT_DEV_GAUGES so the POD path keeps the
// strict Fail (it needs a complete, real method table).
//
if (getenv("BT_DEV_GAUGES") != NULL)
{
const char
*skip_token;
int
paren_depth = 1; // the '(' already consumed above
while (
(paren_depth > 0) &&
((skip_token = GetToken()) != NULL)
)
{
if (stricmp(skip_token, "(") == 0)
++paren_depth;
else if (stricmp(skip_token, ")") == 0)
--paren_depth;
}
UngetPreviousToken(); // leave ')' for the check below
}
else
{
ReportParsingError("Undefined primitive");
}
}
if (stricmp(GetToken(), ")") != 0)
{