Cameraship Map/Armor diagnostics: -tmr ladder, draw counter, CTCL type log

Investigating a field report that the cameraship secondary armor/score/map
screen showed its background BMP but no overlay graphics "on certain video
cards". The video card was a red herring: the machine was launched with
-ctcltype 2 (game pod) instead of 3 (cameraship), and the overlay block in
hudchat.cpp is gated on CTCL_GetType()==_ECTCL_CameraShip, so it never ran.

This is hard to spot because the two gates are independent. HSH_EnterFullScreen2
and CMR_Device open the screen and paint its background based purely on whether
a spare secondary monitor exists, and never consult the CTCL role. A wrong
-ctcltype therefore yields a screen that lights up, shows the correct artwork
and renders nothing, which is visually identical to a DirectDraw failure. The
same background-only screen also appears on a game pod when an MFD mode finds no
dual-head span and falls through to the single-secondary mr_device path.

Diagnostics added:

- render.cpp: log the CTCL type in gos-displays.txt from HSH_EnterFullScreen2,
  via the existing g_pfnCTCL_GetType hook so GameOS gains no game-code
  dependency. Always logged, and states in words whether overlays will be drawn.
- render.cpp/.hpp: CHSH_Device::m_nDrawCalls, incremented in DrawQuad,
  DrawThickFrame and DrawTexture, reset in CMR_Device::BeginScene and reported
  for the first 5 frames from CMR_Device::EndScene when -tmr is active. Zero
  draws proves the overlay code never ran; non-zero with a blank screen would
  mean it ran and drew invisibly. This distinction is what settled the case.
- render.cpp: log CMR_Device surface creation plus the overlay texture's
  dimensions, depth and channel masks.
- New -tmr <0-3> switch (MW4Application.cpp, documented in -help): 0 normal,
  1 background blit with DDBLTFAST_WAIT|DDBLTFAST_NOCOLORKEY, 2 Clear instead of
  the background blit, 3 as 2 plus alpha blending off and an untextured magenta
  DrawQuad.

Mode 3 drew magenta and mode 2 was pure black, which cleared the 3D device, the
flip and the background blit in two runs; the draw counter then reported 0 and
pointed straight at the gate. Documented as STEP 11 in CLAUDE.md, including the
field triage rule: read "CTCL type =" in gos-displays.txt before suspecting
drivers.

Co-authored-by: Claude Opus 5 (Anthropic) <noreply@anthropic.com>
Co-authored-by: GitHub Copilot <copilot@github.com>
This commit is contained in:
2026-07-26 11:59:56 -05:00
co-authored by Claude Opus 5 GitHub Copilot
parent b40892910b
commit 24ce46ab6d
4 changed files with 177 additions and 5 deletions
@@ -486,6 +486,7 @@ CHSH_Device::CHSH_Device()
pDDSTarget=0;
pDDSTexture=0;
pD3DDevice=0;
m_nDrawCalls=0; // [mrdiag]
tw=512;
th=512;
}
@@ -527,6 +528,14 @@ const DWORD dwFlags = DDSCL_SETFOCUSWINDOW | DDSCL_CREATEDEVICEWINDOW |
// Selected with -tcoop <0-3>. See CHSH_Device::InitFirst for what each one does.
int g_nHshCoopMode = 0;
// [mrdiag] Cameraship secondary-screen (Map/Armor) diagnostic mode, -tmr <0-3>.
// 0 = unchanged
// 1 = wait for the background blit to finish before starting the 3D scene
// 2 = skip the background blit entirely (Clear instead)
// 3 = as 2, plus draw a solid magenta quad to prove whether ANY 3D output lands
// See CMR_Device::BeginScene.
int g_nMRDiag = 0;
extern HRESULT wDirectDrawCreateEx( GUID* lpGUID, void** lplpDD, REFIID iid, IUnknown* pUnkOuter );
//
@@ -932,6 +941,7 @@ bool CHSH_Device::Release()
void CHSH_Device::DrawQuad(int x1,int y1,int x2,int y2,DWORD dwColor)
{
m_nDrawCalls++; // [mrdiag]
D3DTLVERTEX Vertices[4]; // Vertices for the cube
Vertices[0] = D3DTLVERTEX(D3DVECTOR( (float)x1-0.5f, (float)y2-0.5f,0.9f),1.0f,dwColor,0, 0.0f, 1.0f );
@@ -1041,6 +1051,7 @@ void CHSH_Device::DrawFrameList(int framecount,RECT * prc,DWORD dwColor)
void CHSH_Device::DrawThickFrame(int x1,int y1,int x2,int y2,int t,DWORD dwColor)
{
m_nDrawCalls++; // [mrdiag]
if(t>1){
RECT rc[4]={
{x1 ,y1 ,x2 ,y1+t},
@@ -1149,6 +1160,7 @@ void CHSH_Device::DrawTexture2(float x,float y,float w,float h,DWORD dwColor, fl
//Not scaled....
void CHSH_Device::DrawTexture(float x,float y,DWORD dwColor, float u1,float v1,float u2,float v2)
{
m_nDrawCalls++; // [mrdiag]
D3DTLVERTEX v[4];
float w=u2-u1;
float h=v2-v1;
@@ -1233,6 +1245,26 @@ bool CMR_Device::InitSecond()
//3. Texture for map..
pDDSMapTexture=CreatePixelFormatTexture(pDD,256,256,&DDPF_R5G6B5);
// [mrdiag] Report what actually got created. A missing surface here produces a
// screen that shows some things and not others, which is very hard to interpret
// from the outside - the original code only beeped when the background failed.
HSH_LogInit( " CMR_Device surfaces:\r\n" );
HSH_LogInit( " pDDSBackground = %s\r\n", pDDSBackground ? "ok" : "*** NULL ***" );
HSH_LogInit( " pDDSTexture = %s\r\n", pDDSTexture ? "ok" : "*** NULL ***" );
HSH_LogInit( " pDDSMapTexture = %s\r\n", pDDSMapTexture ? "ok" : "*** NULL ***" );
if( pDDSTexture ) {
DDSURFACEDESC2 dsd; ZeroMemory(&dsd,sizeof(dsd)); dsd.dwSize=sizeof(dsd);
if( SUCCEEDED(pDDSTexture->GetSurfaceDesc(&dsd)) )
HSH_LogInit( " overlay texture : %lux%lu %lubpp a=%08lX r=%08lX g=%08lX b=%08lX\r\n",
(unsigned long)dsd.dwWidth, (unsigned long)dsd.dwHeight,
(unsigned long)dsd.ddpfPixelFormat.dwRGBBitCount,
(unsigned long)dsd.ddpfPixelFormat.dwRGBAlphaBitMask,
(unsigned long)dsd.ddpfPixelFormat.dwRBitMask,
(unsigned long)dsd.ddpfPixelFormat.dwGBitMask,
(unsigned long)dsd.ddpfPixelFormat.dwBBitMask );
}
HSH_LogInit( " -tmr diagnostic mode = %d\r\n", g_nMRDiag );
tw=256;
th=256;
@@ -1247,11 +1279,32 @@ bool CMR_Device::BeginScene()
//SetRenderTargetTexture();
//Draw background image to Texture. Used as a substitute for Clear..
// [mrdiag] The cameraship secondary screen is the ONLY panel with no offscreen
// render target (InitSecond(0,0)), so it blits its background straight onto the
// back buffer and then renders 3D into that same surface. The blit below is
// issued with flags 0 - no DDBLTFAST_WAIT - and nothing ever checks GetBltStatus,
// while every other blit in the engine uses DDBLT_WAIT. If a driver does not
// serialise its 2D blit engine against the 3D pipeline, that blit can land after
// the frame's 3D draws and erase them, leaving only the background.
//
// -tmr selects between the possibilities so they can be told apart on real hardware.
if(sh_game_started){
//Draw background only when game has started.
RECT rc={0,0,640,480};
pDDSBack->BltFast(0,0,pDDSBackground,&rc,0);
switch( g_nMRDiag )
{
case 1: // wait for the blit to complete before any 3D is issued
pDDSBack->BltFast(0,0,pDDSBackground,&rc,DDBLTFAST_WAIT|DDBLTFAST_NOCOLORKEY);
break;
case 2: // no background at all - if the overlays appear, the blit was erasing them
case 3:
pD3DDevice->Clear(0,NULL,D3DCLEAR_TARGET,0,1.0f,0);
break;
default: // 0 = original behaviour
pDDSBack->BltFast(0,0,pDDSBackground,&rc,0);
break;
}
}else{
//If game has not started.. show blank.
pD3DDevice->Clear(0,NULL,D3DCLEAR_TARGET ,0,1.0f,0);
@@ -1259,12 +1312,33 @@ bool CMR_Device::BeginScene()
pD3DDevice->BeginScene();
// [mrdiag] mode 3: a solid untextured quad. If even this does not appear, nothing
// this device draws in 3D is reaching the screen, and the background blit is
// innocent - the fault is in the 3D pass itself.
if( g_nMRDiag==3 ) {
pD3DDevice->SetRenderState( D3DRENDERSTATE_ALPHABLENDENABLE, FALSE );
pD3DDevice->SetTexture(0,0);
DrawQuad( 64, 64, 576, 416, 0xFFFF00FF ); // magenta
}
pD3DDevice->SetTexture(0,pDDSTexture);
m_nDrawCalls=0; // [mrdiag] count what the game draws between here and EndScene
return true;
}
bool CMR_Device::EndScene()
{
// [mrdiag] Report how many draws the game actually issued on this device. Zero means
// the overlay code never ran (a gating problem, not a rendering one); a non-zero count
// with a blank screen means it ran and drew nothing visible.
{
static int s_nFrames=0;
if( g_nMRDiag && s_nFrames<5 ) {
s_nFrames++;
HSH_LogInit( " [mrdiag] frame %d: game issued %d draw call(s) on the Map/Armor screen\r\n",
s_nFrames, m_nDrawCalls );
}
}
pD3DDevice->EndScene();
if( gCaptureScreen && (gCaptureDevice == 2))
{
@@ -2931,6 +3005,19 @@ void HSH_EnterFullScreen2()
HSH_LogInit( "\r\nHSH_EnterFullScreen2\r\n" );
HSH_LogInit( "--------------------\r\n" );
// [mrdiag] The cameraship Map/Armor overlays are drawn by hudchat.cpp only when
// CTCL_GetType() reports _ECTCL_CameraShip (3). If it reports anything else the
// screen still opens and still shows its background, but nothing is ever drawn on
// it - which looks identical to a rendering fault.
if( g_pfnCTCL_GetType ) {
int nType=(*g_pfnCTCL_GetType)();
HSH_LogInit( " CTCL type = %d%s\r\n", nType,
(nType==_ECTCL_CameraShip) ? " (cameraship - overlays WILL be drawn)"
: " (NOT cameraship - Map/Armor overlays will NOT be drawn)" );
} else {
HSH_LogInit( " CTCL type = unavailable (g_pfnCTCL_GetType is NULL)\r\n" );
}
if(IsMultimonitorAvaliable()){
HSH_LogInit( " path: multi-monitor (MFD + radar)\r\n" );
@@ -2970,9 +3057,19 @@ void HSH_EnterFullScreen2()
}
}else if(IsSecondaryMonitorAvaliable()){
HSH_LogInit( " path: single secondary monitor (mr_device)\r\n" );
mr_device.InitFirst();
mr_device.InitSecond();
hsh_mrdev_initialized=true;
bool bMR = mr_device.InitFirst();
if( bMR )
bMR = mr_device.InitSecond();
// [panelcoop] same reasoning as the MFD path above: do not claim the device is
// live if it never came up, or the per-frame code runs on null surfaces.
if( bMR ) {
hsh_mrdev_initialized=true;
HSH_LogInit( " result: cameraship secondary screen initialised OK\r\n" );
} else {
HSH_LogInit( " result: *** cameraship secondary screen FAILED - continuing without it ***\r\n" );
mr_device.Release();
hsh_mrdev_initialized=false;
}
}else{
HSH_LogInit( " path: none - no secondary displays opened\r\n" );
}
@@ -89,6 +89,7 @@ public:
SIZE size_back;
SIZE size_target;
float tw,th;//texture width,height
int m_nDrawCalls; // [mrdiag] draws issued since the last BeginScene
CHSH_Device();
bool InitFirst(int devicenum,DWORD resx,DWORD resy, CHSH_Device* pOtherHSHD = NULL);