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
+63 -4
View File
@@ -116,7 +116,66 @@ So the beachhead's real question shifted from "composite a working buffer" to "h
recon." FAITHFUL FOLLOW-UPS: fix the `BTL4GaugeRenderer` lamp-manager + warehouse reconstruction (shadowed
members) so per-mech gauges + lamps work (needed for real MFD content anyway).
**Milestone B (NEXT) — the composite pass:** a new `SVGA16` method (has access to `pixelBuffer` + the `sec`
port + palette) creates a texture on the MAIN device (`l4_application->GetVideoRenderer()->GetDevice()`),
runs the secondary palette-expand (L4VB16.cpp:4124-4147) into it, and the main renderer draws it as an inset
quad (state save/restore) after the 3D pass. This is what reveals whether the content is real or garbage.
**Milestone B DONE (2026-07) — THE SECONDARY/RADAR SURFACE COMPOSITES LIVE ON A DEV BOX** (opt-in
`BT_DEV_GAUGES`; default DEV + pod paths verified un-regressed). The inset in the bottom-left of the 800×600
window renders the authentic BT cockpit secondary MFD: the **radar/tactical grid** (SCALE/SECTOR + crosshair),
the **SPEED / HEADING / MAGNETIC / PROP dials**, and the color-coded **ARMOR DAMAGE mech schematic**
(green intact / red damaged) — real content (`nzSec=27247` non-zero secondary-plane pixels), not garbage.
The composite chain: `SVGA16::DrawDevInset(device, secMask, paletteID)` (L4VB16.cpp) lazily creates a MANAGED
R5G6B5 640×480 texture on the MAIN device, palette-expands the secondary plane into it (mirrors
`SVGA16::Update` case 0), and draws an `XYZRHW|TEX1` inset quad; the free entry `BTDrawGaugeInset(mDevice)`
reaches `application->GetGaugeRenderer()->GetGraphicsPort("sec")->graphicsDisplay` and is called from
`DPLRenderer::ExecuteImplementation` as the LAST draw before `EndScene` (no state save/restore needed).
**THREE reconstruction bugs had to be fixed to get from "woken but empty" to "real content" (each a genuine
faithful fix or a clearly-gated dev accommodation):**
1. **`MakeGaugeRenderer` OVERRIDE SIGNATURE MISMATCH (real bug — FIXED).** The reconstructed
`BTL4Application::MakeGaugeRenderer()` took NO args, but the 2007 WinTesla engine had WIDENED the base
virtual to `MakeGaugeRenderer(int* secondaryIndex, int* aux1, int* aux2)` (called from `Application::
Initialize`, APP.cpp:382). A no-arg method only HIDES the virtual — it never overrides it — so the engine
ran `L4Application::MakeGaugeRenderer` instead and built a **base `L4GaugeRenderer`** whose ctor never
calls `BuildConfigurationFile``gauge\l4gauge.cfg` was NEVER PARSED → empty symbol table → the observed
`GaugeInterpreter: undefined label 'bhk1Init'` → no ports, no gauges. FIX: match the 3-arg signature so
it truly overrides (args ignored — BT is fullscreen/no-aux, the `BTL4GaugeRenderer` ctor hardcodes
`false,NULL,NULL,NULL`). Same class of bug as the `BTL4GaugeRenderer(false,NULL,NULL,NULL)` ctor fix:
reconstruction written against the ORIGINAL signature, compiled against the WIDER 2007 engine virtual.
(btl4app.cpp / btl4app.hpp.) **Label facts:** the label = `mission->GetGameModel()` + `"Init"` = `bhk1Init`
(lowercase, no capitalization — the binary `FUN_0046ff64` does a plain copy + `Str_Cat`); the CFG defines
`Bhk1Init` but the symbol-table lookup is `stricmp` (case-insensitive), so the case doesn't matter — the
only reason it was "undefined" was the empty table. `MechInit` (CFG:4390) is the shared macro every
`<Mech>Init` calls that builds the `sec`/overlay/Mfd*/Heat/Comm PORTS via the base `configure` primitive.
2. **Gauge-config parse HUNG on undefined primitives (dev accommodation, gated).** The BT-specific gauge
primitive table `BTL4MethodDescription` (btl4grnd.cpp) is still a STUB (only the chain-to-base link; the
BT gauge widget classes like `PlayerStatus` aren't reconstructed), so `GaugeInterpreter::GetProcedureBody`
hits an unknown primitive → `ReportParsingError``Fail()` → a MODAL assert dialog that FREEZES the game
mid-parse (cdb confirmed: `MessageBoxW``abort``ReportParsingError``GetProcedureBody`). FIX
(GAUGREND.cpp, gated `BT_DEV_GAUGES`): when a primitive doesn't resolve, SKIP its parameter list (paren
depth walk + `UngetPreviousToken`) and continue — the parse then registers ALL labels (`Bhk1Init`
`MechInit`) and runs the base `configure` primitives that build the PORTS. The base `configure`
(`MakeConfigMethodDescription`) IS in `L4MethodDescription`, so the `sec`/radar SURFACE builds even though
the BT widgets are skipped. Pod keeps the strict `Fail` (it needs a complete real table).
3. **Gauge widgets AV on NULL data bindings (dev accommodation, gated).** Two flavors: (a) value-bound gauges
(`NumericDisplayScalar`) get `value_pointer = parameterList[8].data.attributePointer` = NULL (the mech data
binding isn't reconstructed; the source even comments "(Scalar *) is VERY dangerous!") → the
`GaugeConnectionDirectOf<float>` ctor derefs NULL (gauge.h:198). FIX: the ctor binds a NULL source to a
static zero (gated). (b) game-state gauges (`RankAndScore::Execute`) directly deref unreconstructed
mission/player data. FIX: `Gauge::Update` runs `Execute()` under a SEH wrapper `Gauge::GuardedExecute()`
(a separate fn — `__try/__except` can't share a frame with the `ChainIteratorOf` unwinding local); a gauge
that faults once is `Disable(True)`d (rate=0) so it never retries. Both gated `BT_DEV_GAUGES`.
**⚠ KEY TAKEAWAY:** the gauge subsystem is now PROVEN end-to-end on dev (renderer→parse→port config→raster→
palette-expand→composite), and the gauges whose data binds correctly (radar/armor/dials) show REAL content.
The remaining work is the **BT gauge WIDGET reconstruction** — the `BTL4MethodDescription` method table (the
BT gauge classes: `PlayerStatus` + the ~16 recovered `methodDescription` entries) AND the gauge→game-state
DATA BINDINGS (why `RankAndScore` / some `NumericDisplayScalar` resolve NULL). Those are what the dev
accommodations (skip/guard) stand in for. Files touched: `L4VB16.{h,cpp}` (DrawDevInset/BTDrawGaugeInset),
`L4VIDEO.cpp` (the EndScene hook), `btl4app.{cpp,hpp}` (the override fix), `GAUGREND.cpp` (tolerant skip),
`gauge.h` + `GAUGE.{h,cpp}` (NULL-source + SEH guards).
## Follow-ups (after Milestone B)
- **Widget recon (the real fix):** reconstruct `BTL4MethodDescription` (BT gauge classes) + the gauge→mech
data bindings so the skipped/guarded gauges show real data and the POD path (strict `Fail`, no guards) works.
- **Step 2** — reconcile the MFD port names (`auxUL2/auxC/...` in `SVGA16::Update` vs `Heat/Comm/Mfd1-3` in
`L4GAUGE.CFG`) → the 5 MFD surfaces render (only `sec`/radar works today).
- **Step 3** — the 2-window layout (3D-view window + an instrument-panel window tiling radar + the 5 MFDs).