Camera-seat ranking window LIVE: the 1995 broadcast overlay reimplemented

The stubbed dpl-instance ranking display is reborn as screen-space
quads in CameraShipHUDRenderable::Render:
  - followed-player callsign banner (bottom center; the old direct
    index was correct -- Execute already converts to the 0-based
    texture slot)
  - the RANKING WINDOW: one row per scoring player, [ordinal][callsign]
    in rank order right of center; visibility = the Director's
    authentic flash logic (10s on / 15s off, solid final 30s); rows
    follow LIVE playerRank pointers so they re-sort as scores change
  - discovery: each 128x32 ordinal bitmap packs TWO ordinals side by
    side ('1st|2nd', '3rd|4th' -- why 4 bitmaps serve 8 players);
    draw = texture rank/2 with a u-half selected by rank parity
  - alpha-blended A4R4G4B4 white-on-transparent textures x green
    diffuse = the authentic green look

BT_SHOT moved AFTER the 2D pass -- it captured the backbuffer pre-HUD,
so overlays were on screen but invisible to screenshots (cost one
false debugging round).  GetOrdinalTexture accessor added.

Screenshot-verified: '1st MAVERICK' standings + MAVERICK banner over
live auto-directed coverage; 2-node mech smoke PASS (un-regressed;
the new render path only executes when a CameraDirector HUD exists).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-18 16:32:23 -05:00
co-authored by Claude Fable 5
parent 4459262b9c
commit 0a77d8e54b
5 changed files with 214 additions and 95 deletions
+60 -60
View File
@@ -162,7 +162,7 @@ pilot=10.99.0.2:1502
hostType=0
advancedDamage=1
loadzones=1
name=PILOT
name=MAVERICK
bitmapindex=1
experience=expert
badge=VGL
@@ -187,26 +187,9 @@ vehicle=camera
vehicleValue=1000
color=White
[largebitmap]
bitmap=BitMap::Large::PILOT
bitmap=BitMap::Large::MAVERICK
bitmap=BitMap::Large::CAMERA
[BitMap::Large::PILOT]
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
[BitMap::Large::MAVERICK]
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
@@ -214,6 +197,23 @@ bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=03E0F807C38039FFE3FF8380FC0701E0
bitmap=03E0F807C1C071FFE3FFC383FF0703C0
bitmap=03F1F807C1C071FFE3FFE387FF870780
bitmap=03F1F80EE1C071C00381E38F07C70F00
bitmap=03B1B80EE0E0E1C00380E38E01871E00
bitmap=03B1B81EF0E0E1C00380E39E00073C00
bitmap=03B1B81C70E0E1C00381E39C00077800
bitmap=03BBB81C7071C1FFC3FFC39C0007FC00
bitmap=03BBB8383871C1FFC3FF839C0007FC00
bitmap=039B38383871C1FFC3FE039C0007CE00
bitmap=039B383FFC3B81C0038F039C00078E00
bitmap=039B387FFC3B81C00387839E00070700
bitmap=039F387FFC3B81C00383838E01870780
bitmap=039F38F01E1F01C00381C38F07C70380
bitmap=038E38E00E1F01FFE381E387FF8701C0
bitmap=038E38E00E1F01FFE380E383FF0701E0
bitmap=038E39C0070E01FFE380F380FC0700E0
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
@@ -232,26 +232,26 @@ bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=003F8007E00FE03F87FFF8FFF800FC00
bitmap=00FFE007F00FE03F87FFF8FFFC00FE00
bitmap=01FFF007F00FE03F87FFF8FFFE00FE00
bitmap=03FFF80FF00FF07F87FFF8FFFF01FE00
bitmap=07E0F80F780F7077878000F01F01EF00
bitmap=07807C0F780F7077878000F00F01EF00
bitmap=0F80301F780F7077878000F00F03EF00
bitmap=0F00001E3C0F7077878000F01F03C780
bitmap=0F00001E3C0F38E787FFF0FFFE03C780
bitmap=0F00003E3C0F38E787FFF0FFFE07C780
bitmap=0F00003C1E0F38E787FFF0FFFC0783C0
bitmap=0F00003C1E0F18C787FFF0FFF00783C0
bitmap=0F00307FFE0F1DC7878000F0F80FFFC0
bitmap=0F807C7FFF0F1DC7878000F07C0FFFE0
bitmap=07807C7FFF0F1DC7878000F03C0FFFE0
bitmap=07E1F8F80F0F0D87878000F03E1F01E0
bitmap=03FFF8F0078F0F8787FFF8F01F1E00F0
bitmap=01FFF0F0078F0F8787FFF8F00F1E00F0
bitmap=00FFE1F0078F070787FFF8F00FBE00F0
bitmap=003F81E003CF070787FFF8F007FC0078
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
bitmap=00000000000000000000000000000000
@@ -262,21 +262,21 @@ x=128
y=32
width=8
[smallbitmap]
bitmap=BitMap::Small::PILOT
bitmap=BitMap::Small::MAVERICK
bitmap=BitMap::Small::CAMERA
[BitMap::Small::PILOT]
bitmap=0000000000000000
bitmap=0000000000000000
bitmap=0000000000000000
bitmap=0000000000000000
bitmap=0000000000000000
bitmap=0000000000000000
bitmap=0000000000000000
bitmap=0000000000000000
bitmap=0000000000000000
[BitMap::Small::MAVERICK]
bitmap=0000000000000000
bitmap=0000000000000000
bitmap=0000000000000000
bitmap=38E3B07FBF31E630
bitmap=38E398D831B33660
bitmap=3DE6D8D831B606C0
bitmap=3DE6D8D831B607C0
bitmap=3566CD9FBF3607E0
bitmap=3566CD9833360660
bitmap=376FED9831B60660
bitmap=376C671831B33630
bitmap=326C671FB0F1E630
bitmap=0000000000000000
bitmap=0000000000000000
bitmap=0000000000000000
@@ -288,15 +288,15 @@ width=4
bitmap=0000000000000000
bitmap=0000000000000000
bitmap=0000000000000000
bitmap=0000000000000000
bitmap=0000000000000000
bitmap=0000000000000000
bitmap=0000000000000000
bitmap=0000000000000000
bitmap=0000000000000000
bitmap=0000000000000000
bitmap=0000000000000000
bitmap=0000000000000000
bitmap=007871C77EFC3800
bitmap=00CC71C760C63800
bitmap=0180D9EF60C66C00
bitmap=0180D9EF60C66C00
bitmap=0180D9AB7EFC6C00
bitmap=0180D9AB60CC6C00
bitmap=0181FDBB60C6FE00
bitmap=00CD8DBB60C6C600
bitmap=00798D937EC3C600
bitmap=0000000000000000
bitmap=0000000000000000
bitmap=0000000000000000
+11 -3
View File
@@ -47,9 +47,17 @@ authentic path scoped.
— the heat family's "frozen 300" note stands CORRECTED). Verified live: mech+camera 2-seat
relay session — replicated player links (`bmp=1+veh`), director locks the goal (30s timer),
camera ship tracks the moving mech (`sees=1`) and CUTS between authored cameras
(screenshots). Egg: pilot page `hostType=1` + `vehicle=camera`. Remaining: the
ranking-window overlay draw (L4VIDRND.cpp:3016 stubs), a camera-seat row helper in the
operator app (hostType not in the roster UI), shot-composition polish. Status: core LIVE.
(screenshots). Egg: pilot page `hostType=1` + `vehicle=camera`. **RANKING WINDOW LIVE (same day)**: the stubbed
1995 overlay reimplemented as screen-space quads in `CameraShipHUDRenderable::Render` the
followed player's callsign banner (bottom center, the Execute index is ALREADY 0-based) + a
rank-ordered row per scoring player ([ordinal][callsign], right of center), visibility = the
Director's flash logic (10s on/15s off, solid final 30s), live `playerRank` pointers re-sort
rows as scores change. **Each 128×32 ordinal bitmap packs TWO ordinals side by side**
("1st|2nd", "3rd|4th" — why 4 bitmaps serve 8 players): texture rank/2, u-half by parity.
BT_SHOT now captures AFTER the 2D pass (it used to dump pre-HUD — overlays were on screen
but invisible to screenshots). Screenshot-verified: "1st MAVERICK" + banner over live
coverage. Remaining: operator-app camera-seat row helper (hostType not in the roster UI),
shot-composition polish. Status: broadcast display COMPLETE.
## Pod-hardware readiness gaps (Phase 8 assessment, 2026-07-17) [[pod-hardware]]
Everything below EXISTS in the tree but has NEVER run against real pod hardware. Mechanism
+31 -28
View File
@@ -7431,6 +7431,9 @@ void
Check_Pointer(display_rank_window);
mCamShipHUD = new CameraShipHUDRenderable(entity, CameraShipHUDRenderable::Dynamic, player_index, display_rank_window);
if (getenv("BT_CAM_LOG"))
DEBUG_STREAM << "[cam] CameraShipHUDRenderable created\n"
<< std::flush;
}
break;
}
@@ -8518,34 +8521,6 @@ void DPLRenderer::ExecuteImplementation(RendererComplexity, RendererOrigin::Inte
BTDrawTranslocationSpheres(mDevice, &viewTransform, (float)dT, mTargetRenderTime);
}
// DIAG (off by default): BT_SHOT=<path> dumps this instance's backbuffer (the rendered
// WORLD, before the 2D HUD overlay) to a PNG once ~180 frames in, for non-disruptive
// per-instance frame capture without foregrounding the window. Each instance sets its
// own path. Built for the -net render glitch (task #53) but general.
{
const char *shotPath = getenv("BT_SHOT");
if (shotPath)
{
static int s_btShotN = 0;
++s_btShotN;
// dump (overwriting) every 90 frames once past 90, so the file always holds a
// recent frame regardless of load timing / debug fps.
if (s_btShotN >= 90 && (s_btShotN % 90) == 0)
{
IDirect3DSurface9 *bb = 0;
if (SUCCEEDED(mDevice->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &bb)) && bb)
{
HRESULT hr = D3DXSaveSurfaceToFileA(shotPath, D3DXIFF_PNG, bb, 0, 0);
bb->Release();
DEBUG_STREAM << "[btshot] frame " << s_btShotN << " -> " << shotPath
<< " hr=0x" << std::hex << (unsigned)hr << std::dec << "\n" << std::flush;
}
else
DEBUG_STREAM << "[btshot] GetBackBuffer FAILED frame " << s_btShotN << "\n" << std::flush;
}
}
}
// The BT targeting reticle / weapon pips (2D screen space, cockpit view
// only -- the dpl2d layer; see game/reconstructed/dpl2d.cpp).
{
@@ -8588,6 +8563,34 @@ void DPLRenderer::ExecuteImplementation(RendererComplexity, RendererOrigin::Inte
if (mCamShipHUD)
mCamShipHUD->Render(0, &viewTransform);
// DIAG (off by default): BT_SHOT=<path> dumps this instance's backbuffer to a
// PNG every 90 frames -- non-disruptive per-instance frame capture without
// foregrounding the window. Built for the -net render glitch (task #53);
// moved AFTER the 2D pass (2026-07-18, camera-seat bring-up) so captures
// include the HUD overlays (reticle, camera-ship ranking window) the screen
// actually shows.
{
const char *shotPath = getenv("BT_SHOT");
if (shotPath)
{
static int s_btShotN = 0;
++s_btShotN;
if (s_btShotN >= 90 && (s_btShotN % 90) == 0)
{
IDirect3DSurface9 *bb = 0;
if (SUCCEEDED(mDevice->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &bb)) && bb)
{
HRESULT hr = D3DXSaveSurfaceToFileA(shotPath, D3DXIFF_PNG, bb, 0, 0);
bb->Release();
DEBUG_STREAM << "[btshot] frame " << s_btShotN << " -> " << shotPath
<< " hr=0x" << std::hex << (unsigned)hr << std::dec << "\n" << std::flush;
}
else
DEBUG_STREAM << "[btshot] GetBackBuffer FAILED frame " << s_btShotN << "\n" << std::flush;
}
}
}
// DEV-COMPOSITE: in DOCKED mode (BT_DEV_GAUGES_DOCK) blit the 6-surface gauge panel
// into this window as the LAST draw before EndScene (no-op otherwise / off pod).
extern void BTDrawGaugeInset(LPDIRECT3DDEVICE9 device);
+1
View File
@@ -379,6 +379,7 @@ public:
void SetCurrentFogLimits(float fogNear, float fogFar);
LPDIRECT3DTEXTURE9 GetNameTexture(int playerIndex) { return mNameTextures[playerIndex]; }
LPDIRECT3DTEXTURE9 GetOrdinalTexture(int rank) { return mOrdinalTextures[rank]; }
inline LPDIRECT3DDEVICE9 GetDevice() { return mDevice; }
inline LPD3DXMATRIXSTACK GetMatrixStack() { return m_MatrixStack; }
+111 -4
View File
@@ -3045,17 +3045,124 @@ void CameraShipHUDRenderable::Execute()
VideoRenderable::Execute();
}
//
// BT port (camera-seat bring-up, 2026-07-18): draw one screen-space textured
// quad (u/v 0..1) -- shared by the followed-callsign banner and the ranking
// window rows. The name/ordinal bitmaps load black-on-transparent
// (LoadBitSliceTexture, A4R4G4B4), so alpha blending keys them over the scene.
//
static void CameraHUDDrawQuad(
LPDIRECT3DDEVICE9 device,
LPDIRECT3DTEXTURE9 texture,
float x, float y, float w, float h,
float u0 = 0.0f, float u1 = 1.0f)
{
if (texture == NULL)
{
return;
}
L4VERTEX_2D_TEX quad[4];
memset(quad, 0, sizeof(quad));
const DWORD color = D3DCOLOR_XRGB(0, 128, 0);
for (int i = 0; i < 4; ++i)
{
quad[i].z = 0.0f;
quad[i].rhw = 1.0f;
quad[i].color = color;
}
quad[0].x = x + w; quad[0].y = y; quad[0].u = u1; quad[0].v = 0.0f;
quad[1].x = x; quad[1].y = y; quad[1].u = u0; quad[1].v = 0.0f;
quad[2].x = x + w; quad[2].y = y + h; quad[2].u = u1; quad[2].v = 1.0f;
quad[3].x = x; quad[3].y = y + h; quad[3].u = u0; quad[3].v = 1.0f;
device->SetTexture(0, texture);
device->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, quad,
sizeof(L4VERTEX_2D_TEX));
}
void CameraShipHUDRenderable::Render(int pass, const D3DXMATRIX *viewTransform)
{
if (application->GetApplicationState() == Application::RunningMission && oldFollowedPlayerIndex >= 0 && oldFollowedPlayerIndex < MAX_PLAYER_NAMES)
if (application->GetApplicationState() != Application::RunningMission)
{
LPDIRECT3DDEVICE9 device = myRenderer->GetDevice();
return;
}
LPDIRECT3DDEVICE9 device = myRenderer->GetDevice();
device->SetFVF(L4VERTEX_2D_TEX_FVF);
device->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
device->SetFVF(L4VERTEX_2D_TEX_FVF);
//
// The followed player's callsign banner (bottom center). Execute already
// converts the 1-based goalPlayerIndex to the 0-based texture slot.
//
if (oldFollowedPlayerIndex >= 0 && oldFollowedPlayerIndex < MAX_PLAYER_NAMES)
{
device->SetStreamSource(0, mVB, 0, sizeof(L4VERTEX_2D_TEX));
device->SetTexture(0, myRenderer->GetNameTexture(oldFollowedPlayerIndex));
device->SetTexture(0,
myRenderer->GetNameTexture(oldFollowedPlayerIndex));
device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
}
//
// THE RANKING WINDOW (the un-stubbed 1995 display): one row per scoring
// player -- [1st/2nd/... ordinal][callsign] -- in RANK order, right of
// center (the old DCS placement: window at x .22 y .16, ordinal left of
// the name). Visibility is the Director's flash logic (10s on / 15s off,
// solid for the final 30s). playerRank[bmp-1] -> &playerRanking (LIVE
// pointers captured at ctor), so the rows re-sort as the score changes.
//
if (getenv("BT_CAM_LOG"))
{
static int s_rlog = 0;
if ((s_rlog++ % 300) == 0)
DEBUG_STREAM << "[cam] HUD render: followed="
<< oldFollowedPlayerIndex
<< " rankwin=" << (displayRankingWindow
? (int)*displayRankingWindow : -1)
<< " count=" << playerCount << "\n" << std::flush;
}
if (displayRankingWindow != NULL && *displayRankingWindow
&& playerRank != NULL && playerCount > 0)
{
const float width = myRenderer->GetWidth();
const float height = myRenderer->GetHeight();
const float nameW = width * 0.20f;
const float ordW = width * 0.10f;
const float rowH = nameW * (32.0f / 128.0f); // bitmap aspect
const float pad = width * 0.01f;
const float x0 = width * 0.55f;
const float y0 = height * 0.16f;
for (int bmp = 0; bmp < playerCount && bmp < MAX_PLAYER_NAMES; ++bmp)
{
if (playerRank[bmp] == NULL)
{
continue;
}
int rank = *playerRank[bmp];
if (getenv("BT_CAM_LOG"))
{
static int s_rowlog = 0;
if ((s_rowlog++ % 300) == 0)
DEBUG_STREAM << "[cam] rank row: bmp=" << bmp
<< " rank=" << rank << "\n" << std::flush;
}
if (rank < 0 || rank >= MAX_PLAYER_NAMES)
{
continue; // non-scoring (director/camera)
}
const float y = y0 + rank * (rowH * 1.15f);
// each 128x32 ordinal bitmap packs TWO ordinals side by side
// ("1st|2nd", "3rd|4th", ...) -- texture rank/2, u-half by parity
CameraHUDDrawQuad(device, myRenderer->GetOrdinalTexture(rank / 2),
x0, y, ordW, rowH,
(rank & 1) ? 0.5f : 0.0f, (rank & 1) ? 1.0f : 0.5f);
CameraHUDDrawQuad(device, myRenderer->GetNameTexture(bmp),
x0 + ordW + pad, y, nameW, rowH);
}
}
device->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~