From 24ce46ab6d58805f765fdf9898d56235516b0bde Mon Sep 17 00:00:00 2001 From: RT Date: Sun, 26 Jul 2026 11:59:56 -0500 Subject: [PATCH] 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) Co-authored-by: GitHub Copilot --- CLAUDE.md | 55 +++++++++ .../code/CoreTech/Libraries/GameOS/render.cpp | 107 +++++++++++++++++- .../code/CoreTech/Libraries/GameOS/render.hpp | 1 + .../Code/MW4Application/MW4Application.cpp | 19 ++++ 4 files changed, 177 insertions(+), 5 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 7eeb89f2..8eb610a9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1112,6 +1112,61 @@ Smaller than it sounds because the mechanisms already exist: - New switches are documented in `-help` (`-fps` under LOGGING AND DIAGNOSTICS, `-tcoop` under DISPLAY AND VIDEO). +## STEP 11: Cameraship Map/Armor screen shows background but no overlays — SOLVED +## (2026-07-26): it is the CTCL role, NOT the video card + +Reported as *"the secondary armor/score/map screen in cameraship mode shows the background BMP +fine but none of the overlay graphics, with certain video cards but not others"*. **The video +card is a red herring. The machine was not being told it was a cameraship.** + +### Root cause +`-ctcltype ` maps **straight** to the role enum — no remapping +(`MW4Application.cpp:~1534`, `g_nCTCL = token[0]-'0'`; `ctcl_params.h`): +`1 = console`, **`2 = game pod`**, **`3 = cameraship`**, `4 = none`. +The failing launch line used `-ctcltype 2` (game pod). The overlay drawing in +`mw4\Code\MW4\hudchat.cpp:~596` is gated on +`bool draw_mr = CTCL_GetType()==_ECTCL_CameraShip;` (`#ifdef _DEBUG` -> unconditional `true`), +so with role 2 the overlay block simply never executes. Fixed by launching `-ctcltype 3` +(user-confirmed working). + +### ⚠️ Why this is so hard to spot — the two gates are INDEPENDENT +**The Map/Armor screen opens and paints its background regardless of the CTCL role.** +`HSH_EnterFullScreen2` / `CMR_Device` decide purely on *"is a spare secondary monitor +available?"* and never consult `CTCL_GetType()`; only `hudchat.cpp` checks the role. So a wrong +`-ctcltype` produces a screen that lights up, shows the correct artwork, survives mode changes +and renders nothing — **visually identical to a graphics-card/DirectDraw fault**, which is +exactly why it got mis-attributed to specific GPUs. Diagnose it from `gos-displays.txt` +(`CTCL type = ...`), never by eye. +- Corollary: `mr_device` also opens in **non-cameraship** configs. Observed here with + `-tmfds 1` when `g_nDualHead = -1` (no spanned pair found): MFD mode 1 fell through to the + single-secondary `mr_device` path on a *game pod*, giving the same background-only screen. +- **Field triage:** on a failing machine read that pod's `ctcl.ini` launch line and its + `gos-displays.txt`. `CTCL type = 2` means it is misconfigured, not broken. + +### Diagnostics added (kept) +- **`CTCL type = ` line in `gos-displays.txt`** (`render.cpp` `HSH_EnterFullScreen2`, via the + already-present `g_pfnCTCL_GetType` hook — no game-code dependency). Always logged, states in + words whether overlays will be drawn. One look settles the question. +- **`-tmr <0-3>`** Map/Armor diagnostic ladder (`g_nMRDiag`, `CMR_Device::BeginScene`): + 0 = normal, 1 = background blit with `DDBLTFAST_WAIT|DDBLTFAST_NOCOLORKEY`, + 2 = `Clear` instead of the background blit, 3 = additionally alpha-blend off + untextured + magenta `DrawQuad`. Results that cracked this: **3 drew magenta** (device/3D/flip all fine), + **2 was pure black** (the background blit was never erasing the overlays), 1 unchanged. +- **Draw-call counter** — `CHSH_Device::m_nDrawCalls`, bumped in `DrawQuad` / `DrawThickFrame` / + `DrawTexture`, reset in `CMR_Device::BeginScene`, reported for the first 5 frames from + `CMR_Device::EndScene` when `g_nMRDiag` is set. **This was the decisive instrument:** + `0 draw call(s)` proves the overlay code never ran (a gating problem), whereas non-zero with a + blank screen would mean it ran and drew invisibly (texture/alpha). Keep it — it separates + "not running" from "not visible" in one run. +- Also logged: `CMR_Device` surface creation (`pDDSBackground`/`pDDSTexture`/`pDDSMapTexture`) + plus the overlay texture's size/bpp and channel masks. Healthy reference reading on the W4100: + `256x256 16bpp a=0000F000 r=00000F00 g=000000F0 b=0000000F` (normal 4-bit alpha, not zero). + +### Method note +Every hypothesis reasoned forward from the code was wrong again (blit erasing overlays; alpha +channel; per-GPU rendering). The `-tmr` ladder plus the draw-call counter settled it in two runs. +Consistent with the rest of STEP 10: **instrument, don't infer.** + ## Next steps (proposed) - [ ] (Decision pending) Borderless-windowed migration — see the staged assessment in STEP 10. Step 2 (one panel) is the cheap, decisive experiment. Note this is the only route that would diff --git a/Gameleap/code/CoreTech/Libraries/GameOS/render.cpp b/Gameleap/code/CoreTech/Libraries/GameOS/render.cpp index 62c67ff8..cf354e6c 100644 --- a/Gameleap/code/CoreTech/Libraries/GameOS/render.cpp +++ b/Gameleap/code/CoreTech/Libraries/GameOS/render.cpp @@ -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" ); } diff --git a/Gameleap/code/CoreTech/Libraries/GameOS/render.hpp b/Gameleap/code/CoreTech/Libraries/GameOS/render.hpp index ffd1a5f9..6640d772 100644 --- a/Gameleap/code/CoreTech/Libraries/GameOS/render.hpp +++ b/Gameleap/code/CoreTech/Libraries/GameOS/render.hpp @@ -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); diff --git a/Gameleap/code/mw4/Code/MW4Application/MW4Application.cpp b/Gameleap/code/mw4/Code/MW4Application/MW4Application.cpp index 594e5c55..e5aa46c7 100644 --- a/Gameleap/code/mw4/Code/MW4Application/MW4Application.cpp +++ b/Gameleap/code/mw4/Code/MW4Application/MW4Application.cpp @@ -98,6 +98,9 @@ extern int g_nFpsLog; // [panelcoop] SetCooperativeLevel form used by the radar/MFD panels; 0 = legacy. // Defined in CoreTech GameOS render.cpp. extern int g_nHshCoopMode; +// [mrdiag] Cameraship secondary-screen (Map/Armor) diagnostic mode; 0 = normal. +// Defined in CoreTech GameOS render.cpp. +extern int g_nMRDiag; extern LONG g_ThrottleDir; extern bool g_bCanSuicideAllways; extern bool g_f3dtarget; @@ -881,6 +884,14 @@ static const char* const g_apszCommandLineHelp[] = " panels never contend for it.", " 5 = as 4, plus ALLOWREBOOT.", " Results are reported in gos-displays.txt.", +" -tmr <0-3> Cameraship Map/Armor screen diagnostics.", +" 0 = normal.", +" 1 = wait for the background blit to finish before", +" drawing the overlays.", +" 2 = skip the background image entirely.", +" 3 = as 2, plus a solid magenta test rectangle.", +" Use when that screen shows its background but no", +" overlay graphics or text.", " -mechview <1|2> Show the rotating mech view. 1 = on the radar", " -mv <1|2> screen, 2 = on the main screen. Default: off.", " -3dt Draw the MFD target as a live 3D model instead of", @@ -1709,6 +1720,14 @@ int WINAPI WinMain( HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int n if ((0 <= n) && (n <= 5)) g_nHshCoopMode = n; } + // [mrdiag] Cameraship Map/Armor screen diagnostics. 0 = normal behaviour. + token = strstr(all_lower, "-tmr "); + if (token && token[5]) + { + int n = atoi(&token[5]); + if ((0 <= n) && (n <= 3)) + g_nMRDiag = n; + } // [fpslog] Enable the per-second frame pacing report written to gos-fps.txt. g_nFpsLog = (strstr(all_lower, "-fps") != NULL) ? 1 : 0; g_bCanSuicideAllways = (strstr(all_lower, "-suicide") != NULL);