diff --git a/CLAUDE.md b/CLAUDE.md index c9d594e..eccbff5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -645,6 +645,36 @@ so a forced L4GAUGE on non-pod hardware degrades cleanly; (3) **pod main-view fu pod's 800×600 main monitor, but pod fullscreen-on-a-chosen-adapter is a Phase-8 detail). The Phase-4 HUD/MFD render work now targets the profile's LOGICAL surfaces, not a hardcoded single window. +**✅✅ GAUGE DEV-COMPOSITE — the pod's SECONDARY/RADAR MFD renders LIVE on a dev box (opt-in `BT_DEV_GAUGES`; +full map + findings in `docs/GAUGE_COMPOSITE.md`).** The FOLLOW-UP (1) above is DONE for the secondary surface: +an inset in the bottom-left of the 800×600 window shows the authentic BT cockpit secondary MFD — **radar/ +tactical grid** (SCALE/SECTOR + crosshair), **SPEED/HEADING/MAGNETIC/PROP dials**, and the color-coded +**ARMOR DAMAGE mech schematic** (green intact / red damaged) — REAL content (`nzSec=27247` non-zero pixels). +The draw model is CPU-raster→texture-upload, DEVICE-INDEPENDENT: gauge widgets raster 16-bit R5G6B5 into ONE +shared `Video16BitBuffered::pixelBuffer`; `SVGA16::DrawDevInset` (L4VB16.cpp) palette-expands the secondary +plane into a MANAGED texture on the MAIN device + draws an `XYZRHW|TEX1` inset quad; `BTDrawGaugeInset(mDevice)` +is called from `DPLRenderer::ExecuteImplementation` as the last draw before `EndScene`. **THREE recon bugs +fixed to get real content (1 genuine fix + 2 gated dev accommodations):** (a) **⭐ `BTL4Application:: +MakeGaugeRenderer` SIGNATURE MISMATCH** — the reconstructed no-arg override only HID the 2007-engine's widened +3-arg virtual `MakeGaugeRenderer(int*,int*,int*)` (APP.cpp:382), so the engine built a BASE `L4GaugeRenderer` +whose ctor never calls `BuildConfigurationFile` → `gauge\l4gauge.cfg` was NEVER PARSED → `undefined label +'bhk1Init'` → no ports. FIXED to the 3-arg signature (args ignored; BT is fullscreen). Same bug class as the +`BTL4GaugeRenderer(false,NULL,NULL,NULL)` ctor fix — recon written to the ORIGINAL signature vs the WIDER 2007 +virtual. (The gauge label = `GetGameModel()`+`"Init"`=`bhk1Init`; CFG has `Bhk1Init` but the symbol lookup is +`stricmp`, so case is irrelevant — the table was just empty. `MechInit`, CFG:4390, is the shared macro that +builds the sec/overlay/Mfd/Heat/Comm PORTS via the base `configure` primitive.) (b) **parse HUNG on undefined +primitives** — `BTL4MethodDescription` (btl4grnd.cpp) is a STUB (BT gauge widget classes unreconstructed), so +an unknown primitive → `ReportParsingError`→`Fail()`→a MODAL dialog freezing mid-parse; gated skip +(GAUGREND.cpp) walks past the unknown primitive's params so labels register + base `configure` builds the +ports. (c) **gauge widgets AV on NULL data bindings** — `NumericDisplayScalar` value_pointer NULL → +`GaugeConnectionDirectOf` ctor deref (bind NULL→static zero, gauge.h); `RankAndScore::Execute` derefs +unreconstructed game state (`Gauge::GuardedExecute` SEH wrapper → `Disable(True)` on first fault). All gated +`BT_DEV_GAUGES`; **default DEV (no gauges) un-regressed (TARGET DESTROYED, 0 crashes) + pod path byte-unchanged** +(strict `Fail`, no guards). **REMAINING = the real gauge WIDGET reconstruction:** the `BTL4MethodDescription` +method table (BT gauge classes: `PlayerStatus` + ~16 recovered `methodDescription` entries) + the gauge→ +game-state DATA BINDINGS (why some gauges resolve NULL) — that is what the dev skip/guards stand in for, and +what the POD needs. Then Step 2 (RP↔BT MFD port-name reconcile → the 5 MFDs) + Step 3 (2-window layout). + **Alternate route considered:** run original `btrel410.exe` under DOSBox — **blocked**: it needs the VPX/IG board DOSBox can't emulate; bypassing it = the shim work anyway. diff --git a/docs/GAUGE_COMPOSITE.md b/docs/GAUGE_COMPOSITE.md index 3ca1bb0..50fa734 100644 --- a/docs/GAUGE_COMPOSITE.md +++ b/docs/GAUGE_COMPOSITE.md @@ -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 + `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` 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). diff --git a/engine/MUNGA/GAUGE.cpp b/engine/MUNGA/GAUGE.cpp index 6c71a20..309b194 100644 --- a/engine/MUNGA/GAUGE.cpp +++ b/engine/MUNGA/GAUGE.cpp @@ -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; diff --git a/engine/MUNGA/GAUGE.h b/engine/MUNGA/GAUGE.h index 49ae18f..63d2cba 100644 --- a/engine/MUNGA/GAUGE.h +++ b/engine/MUNGA/GAUGE.h @@ -193,6 +193,26 @@ template GaugeConnectionDirectOf::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 //---------------------------------------------------------------------- diff --git a/engine/MUNGA/GAUGREND.cpp b/engine/MUNGA/GAUGREND.cpp index 1daa82e..cdfac22 100644 --- a/engine/MUNGA/GAUGREND.cpp +++ b/engine/MUNGA/GAUGREND.cpp @@ -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) { diff --git a/engine/MUNGA_L4/L4VB16.cpp b/engine/MUNGA_L4/L4VB16.cpp index e64c4af..c401255 100644 --- a/engine/MUNGA_L4/L4VB16.cpp +++ b/engine/MUNGA_L4/L4VB16.cpp @@ -109,6 +109,89 @@ static bool DevGaugeComposite() return v != 0; } +//===========================================================================// +// DEV-COMPOSITE (option B) -- draw the woken gauge renderer's SECONDARY/radar +// surface as an inset in the MAIN window. The gauge widgets have already +// rastered into the shared CPU pixelBuffer; here we palette-expand the secondary +// plane into a texture on the MAIN device (mirrors SVGA16::Update case 0) and blit +// a quad. Called by the main renderer as the LAST draw before EndScene, so no +// render-state save/restore is needed (the next frame resets device state). +//===========================================================================// +void SVGA16::DrawDevInset(LPDIRECT3DDEVICE9 device, int secMask, int paletteID) +{ + int w = pixelBuffer.Data.Size.x; // 640 + int h = pixelBuffer.Data.Size.y; // 480 + if (device == NULL || w <= 0 || h <= 0) return; + + // Lazily create the inset texture on the MAIN device (MANAGED -> lockable + + // survives device reset). + if (mDevInsetTex == NULL) + device->CreateTexture(w, h, 1, 0, D3DFMT_R5G6B5, D3DPOOL_MANAGED, &mDevInsetTex, NULL); + if (mDevInsetTex == NULL) return; + + // Palette-expand the secondary plane into the texture (== SVGA16::Update case 0). + SVGA16Palette *pal = &palette[paletteID]; + D3DLOCKED_RECT rect; + if (SUCCEEDED(mDevInsetTex->LockRect(0, &rect, NULL, 0))) + { + Word *source = pixelBuffer.Data.MapPointer; + Word *dest = (Word*)rect.pBits; + int postRowIncrement = (rect.Pitch / 2) - w; + for (int y = 0; y < h; y++) + { + for (int x = 0; x < w; x++) + { + PaletteTriplet *pe = &(pal->paletteData.Color[*source & secMask]); + *dest = ((pe->Red >> 3) << 11) | ((pe->Green >> 2) << 5) | (pe->Blue >> 3); + dest++; source++; + } + dest += postRowIncrement; + } + mDevInsetTex->UnlockRect(0); + } + + // Draw the inset: a 320x240 panel in the bottom-left of the (800x600) backbuffer. + const float iw = 320.0f, ih = 240.0f, ix = 8.0f, iy = 600.0f - ih - 8.0f; + struct InsetVert { float x, y, z, rhw, u, v; }; + InsetVert quad[4] = + { + { ix, iy, 0.0f, 1.0f, 0.0f, 0.0f }, + { ix + iw, iy, 0.0f, 1.0f, 1.0f, 0.0f }, + { ix + iw, iy + ih, 0.0f, 1.0f, 1.0f, 1.0f }, + { ix, iy + ih, 0.0f, 1.0f, 0.0f, 1.0f }, + }; + device->SetTexture(0, mDevInsetTex); + device->SetFVF(D3DFVF_XYZRHW | D3DFVF_TEX1); + device->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1); + device->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE); + device->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); + device->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + device->SetRenderState(D3DRS_ZENABLE, FALSE); + device->SetRenderState(D3DRS_LIGHTING, FALSE); + device->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE); + device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); + device->DrawPrimitiveUP(D3DPT_TRIANGLEFAN, 2, quad, sizeof(InsetVert)); +} + +// +// Free entry point the MAIN renderer calls (L4VIDEO DPLRenderer::ExecuteImplementation, +// just before EndScene). No-op unless BT_DEV_GAUGES is set + the gauge renderer + its +// 'sec' port exist. Reaches the gauge renderer's SVGA16 and blits the secondary inset. +// +void BTDrawGaugeInset(LPDIRECT3DDEVICE9 device) +{ + if (!DevGaugeComposite() || device == NULL) return; + GaugeRenderer *gr = application ? application->GetGaugeRenderer() : NULL; + if (gr == NULL) return; + // The secondary/radar surface ('sec') is the one port that resolves without + // the RP<->BT MFD port-name reconciliation (Step 2), so it is the beachhead. + L4GraphicsPort *secPort = static_cast(gr->GetGraphicsPort("sec")); + if (secPort == NULL) return; + SVGA16 *svga = (SVGA16*)secPort->graphicsDisplay; + if (svga == NULL) return; + svga->DrawDevInset(device, secPort->GetBitMask(), secPort->paletteID); +} + void SVGA16::BuildWindows(unsigned int width, unsigned int height, bool windowed, int *secondaryIndex, int *aux1Index, int *aux2Index) { NUMGAUGEWINDOWS = 0; @@ -3844,6 +3927,7 @@ SVGA16::SVGA16( ):Video16BitBuffered(init_width, init_height) { BuildWindows(init_width,init_height,windowed, secondaryIndex, aux1Index, aux2Index); + mDevInsetTex = NULL; // DEV-COMPOSITE (option B): lazily created on first inset draw //STUBBED: VIDEO RB 1/15/07 # if defined(DEBUG) Tell("SVGA16::SVGA16()\n"); diff --git a/engine/MUNGA_L4/L4VB16.h b/engine/MUNGA_L4/L4VB16.h index bc1b987..2e33371 100644 --- a/engine/MUNGA_L4/L4VB16.h +++ b/engine/MUNGA_L4/L4VB16.h @@ -291,6 +291,13 @@ private: RECT *mSurfaceRects; int mDisplayToUpdate; + + LPDIRECT3DTEXTURE9 mDevInsetTex; // DEV-COMPOSITE (option B): the secondary/radar + // plane uploaded to a texture on the MAIN device. +public: + // DEV-COMPOSITE (option B): palette-expand the secondary plane into mDevInsetTex on + // the given (main) device + draw it as an inset quad. See BTDrawGaugeInset (L4VB16.cpp). + void DrawDevInset(LPDIRECT3DDEVICE9 device, int secMask, int paletteID); }; //######################################################################## diff --git a/engine/MUNGA_L4/L4VIDEO.cpp b/engine/MUNGA_L4/L4VIDEO.cpp index c973b2b..08182e6 100644 --- a/engine/MUNGA_L4/L4VIDEO.cpp +++ b/engine/MUNGA_L4/L4VIDEO.cpp @@ -6963,6 +6963,11 @@ void DPLRenderer::ExecuteImplementation(RendererComplexity, RendererOrigin::Inte if (mCamShipHUD) mCamShipHUD->Render(0, &viewTransform); + // DEV-COMPOSITE (option B): blit the woken gauge renderer's secondary/radar surface + // as an inset in this window (LAST draw before EndScene; no-op unless BT_DEV_GAUGES). + extern void BTDrawGaugeInset(LPDIRECT3DDEVICE9 device); + BTDrawGaugeInset(mDevice); + hr = mDevice->EndScene(); // DIAG (turn-hitch hunt): draw CPU is _rt0..here; Present blocks on the GPU. diff --git a/game/reconstructed/btl4app.cpp b/game/reconstructed/btl4app.cpp index fc1e14c..0a2439d 100644 --- a/game/reconstructed/btl4app.cpp +++ b/game/reconstructed/btl4app.cpp @@ -343,13 +343,27 @@ BTL4Application::SharedData //############################################################################# // GaugeRenderer* - BTL4Application::MakeGaugeRenderer() + BTL4Application::MakeGaugeRenderer(int *secondaryIndex, int *aux1Index, int *aux2Index) { // // Only build the gauge renderer when the L4GAUGE environment variable - // is set -- matches RPL4Application's getenv("L4GAUGE") gate. Note - // the BT override takes no arguments (unlike RP's secondary/aux idx). + // is set -- matches RPL4Application's getenv("L4GAUGE") gate. // + // SIGNATURE NOTE: the shipped BT override (@004d368c) took no arguments, + // but the 2007 WinTesla engine WIDENED the base virtual to + // Application::MakeGaugeRenderer(int* secondaryIndex, int* aux1, int* aux2) + // (the secondary/aux surface indices for windowed mode) and calls THAT + // 3-arg virtual from Application::Initialize (APP.cpp:382). A no-arg + // method here only HIDES the virtual -- it never overrides it -- so the + // engine's L4Application::MakeGaugeRenderer ran instead and built a BASE + // L4GaugeRenderer (which never parses gauge\l4gauge.cfg -> empty symbol + // table -> "undefined label 'bhk1Init'" -> no ports). Matching the 3-arg + // signature makes this the real override. BT is fullscreen with no + // secondary/aux gauge indices, so the args are ignored (the + // BTL4GaugeRenderer ctor hardcodes windowed=false / NULL indices). + // + (void)secondaryIndex; (void)aux1Index; (void)aux2Index; + if (getenv("L4GAUGE") == NULL) // FUN_004dee74 { return NULL; diff --git a/game/reconstructed/btl4app.hpp b/game/reconstructed/btl4app.hpp index 6c36f8a..b622c4f 100644 --- a/game/reconstructed/btl4app.hpp +++ b/game/reconstructed/btl4app.hpp @@ -70,7 +70,7 @@ MakeAudioRenderer(); GaugeRenderer* - MakeGaugeRenderer(); + MakeGaugeRenderer(int *secondaryIndex, int *aux1Index, int *aux2Index); ModeManager* MakeModeManager();