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
+84
View File
@@ -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<L4GraphicsPort*>(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");