From 2dea177909315d99cda548cf09057f01d01b788e Mon Sep 17 00:00:00 2001 From: RT Date: Sun, 9 Aug 2026 15:44:41 -0500 Subject: [PATCH] Fix the 2D target-MFD mech image: atlas capacity and two name mismatches Three separate defects, all the same class: a per-mech image is looked up by string, the lookup misses, and nothing says so. Two were pre-existing and live; all three would have been inherited by the six V4H chassis. 1. The atlas was full, so mech id 64 showed another mech. HSH_CreateMFDTextures built a 1024x1024 texture holding one 128x128 tile per chassis, 8 per row, indexed by mech id -- 64 tiles for 65 names. Zeus (id 64) addressed y=1024. On write, DrawBitmapToSurface blits through a GDI DC, so the blit fell outside the clip rect and was discarded. On read, DrawTexture2 divides by tw/th, giving v = 1.000..1.117; the MFD device never sets D3DTSS_ADDRESS so it is D3D7's default WRAP, and that range wrapped to 0.000..0.117 -- exactly tile 0. Targeting a Zeus therefore displayed the Annihilator: not garbage, not black, a clean picture of the wrong mech, which is why it went unnoticed. The atlas is now 1024x2048 (128 tiles). Tile origins are unchanged and the draw derives UVs from pixel coordinates over tw/th, so the taller surface re-normalises automatically: all 64 other chassis sample byte-identical regions, and only Zeus moves -- from the Annihilator's pixels to its own. CreatePixelFormatTexture returns NULL on failure and SetTexture(0,NULL) renders untextured rather than crashing, so on a card that caps texture dimensions at 1024 an unguarded change would have silently blanked all 65 images. It now falls back to the original size, which restores exactly the previous behaviour, and says which size it got in gos-displays.txt. The granted height travels on the device as m_nMechAtlasH because mode 4 gives each of the two panels its own IDirectDraw7 and swaps the texture between them. A capacity guard now logs and skips a tile past the end instead of writing it off-surface and reading back someone else's picture, so this cannot recur silently when the roster grows. 2. Assassin II had no target image at all. 5813aeb6 renamed hsh/MFD/assassinii.bmp to assassin2.bmp to match texturename[], but mechnames[] is a separate list and still said "assassinii", so the tile was never written and the mech showed whatever the freshly created texture happened to contain. mechnames[5] is now "assassin2"; the two arrays agree on all 65 ids. 3. Behemoth II's cockpit doll texture was missing, and that one is fatal. texturename[12] is "behemothii" but the texture was named behemoth2. It was absent from the loose tree, from textures.hint and from the packed textures.mw4. HUDDamage::Reset -> AddTexture -> MLRTexturePool::Add -> ResourceImagePool::LoadImageGOS resolves content\textures\hud\behemothii.tga, and on a miss LAB_ONLY substitutes a placeholder while Release does STOP("Texture ... could not be found!"). m_MechID there is the player's own mech, so flying a Behemoth II in a Release build should have been a hard stop -- masked in Profile by the placeholder. Three of the four layers already said "behemothii", so the texture was the outlier: behemoth2.tga is renamed to behemothii.tga and its hint page follows. The art is unchanged; it is byte-identical to behemoth.tga, since Behemoth II deliberately inherits Behemoth's doll. Also removed the orphans left by 5813aeb6 -- hsh/hud/assassinii.bmp, hsh/radar/hud/assassinii.bmp, Content/textures/HUD/Assassinii.tga and the [hud\assassinii] pool page. Nothing referenced them. The two stray dolls were not identical to the assassin2 pair (0.5% and 9.5% of pixels), so coord.cpp row 5 was scored against both first: identical results (MFD 0.925, Radar 0.934), i.e. same component geometry with faint re-encode noise. hsh/Mechs/assassin ii.bmp is deliberately kept -- portraits are keyed by the localised display name, not by texturename[]. Verified: every string-keyed per-mech art lookup now resolves for all 65 chassis -- mechnames[] into hsh/MFD, and texturename[] into hsh/hud, hsh/radar/hud and Content/textures/HUD -- with zero disagreements between the two arrays. The other behemoth2 entries in textures.hint are the mech skin and footstep pages and are untouched; a global rename there would have broken the skin. Needs a Release and Profile rebuild for the code, and a textures.mw4 repack for the hint and art changes. Worth confirming on hardware by targeting a Zeus and an Assassin II, and by flying a Behemoth II in the Release build. Co-authored-by: Claude Opus 5 (Anthropic) Co-authored-by: GitHub Copilot --- .../code/CoreTech/Libraries/GameOS/render.cpp | 52 ++++++++++++++++--- .../code/CoreTech/Libraries/GameOS/render.hpp | 4 +- Gameleap/code/mw4/Code/MW4/huddamage.cpp | 3 +- .../mw4/Content/textures/HUD/behemoth2.tga | 3 -- .../HUD/{Assassinii.tga => behemothii.tga} | 0 Gameleap/mw4/Content/textures/textures.hint | 17 +----- Gameleap/mw4/hsh/hud/assassinii.bmp | 3 -- Gameleap/mw4/hsh/radar/hud/assassinii.bmp | 3 -- 8 files changed, 50 insertions(+), 35 deletions(-) delete mode 100644 Gameleap/mw4/Content/textures/HUD/behemoth2.tga rename Gameleap/mw4/Content/textures/HUD/{Assassinii.tga => behemothii.tga} (100%) delete mode 100644 Gameleap/mw4/hsh/hud/assassinii.bmp delete mode 100644 Gameleap/mw4/hsh/radar/hud/assassinii.bmp diff --git a/Gameleap/code/CoreTech/Libraries/GameOS/render.cpp b/Gameleap/code/CoreTech/Libraries/GameOS/render.cpp index 8e1e5a1a..1edf3493 100644 --- a/Gameleap/code/CoreTech/Libraries/GameOS/render.cpp +++ b/Gameleap/code/CoreTech/Libraries/GameOS/render.cpp @@ -725,7 +725,7 @@ char *mechnames[]={ "arcticwolf", "ares", "argus", - "assassinii", + "assassin2", "atlas", "avatar", "awesome", @@ -800,21 +800,56 @@ char *mechnames[]={ // hang off the same card. Attempting it makes the runtime emulate the access with a // VRAM->system-memory readback every time, which stalls the whole GPU. Hence: each // device gets its own complete copy of these textures. +// [mfdatlas] The 2D target atlas holds one 128x128 tile per chassis, 8 per row, indexed +// by mech id. At 1024x1024 that is 64 tiles for 65 names, so id 64 (zeus) addressed +// y=1024: GDI clipped the write away, and the draw's v range 1.0..1.117 wrapped back to +// row 0, so targeting a Zeus silently showed the Annihilator. 2048 high = 128 tiles. +// Older cards cap texture dimensions at 1024 and would return NULL for the taller +// surface, which would blank EVERY mech image, so fall back to the original size. +#define HSH_MFD_ATLAS_W 1024 +#define HSH_MFD_ATLAS_H 2048 +#define HSH_MFD_TILE 128 + static void HSH_CreateMFDTextures(LPDIRECTDRAW7 pdd, LPDIRECTDRAWSURFACE7* ppMechTexture, - LPDIRECTDRAWSURFACE7* ppSpriteTexture) + LPDIRECTDRAWSURFACE7* ppSpriteTexture, + int* pnAtlasHeight) { char temppath[MAX_PATH]; if(g_f3dtarget){ *ppMechTexture=CreatePixelFormatTexture(pdd,128,128,&DDPF_R5G6B5);//Texture for storing 3D mech image... + if(pnAtlasHeight) + *pnAtlasHeight=128; }else{ // MSL 5.02 Target MFD Image - *ppMechTexture=CreatePixelFormatTexture(pdd,1024,1024,&DDPF_R5G6B5);//Texture for storing 2D mech image... + int nAtlasH=HSH_MFD_ATLAS_H; + *ppMechTexture=CreatePixelFormatTexture(pdd,HSH_MFD_ATLAS_W,nAtlasH,&DDPF_R5G6B5);//Texture for storing 2D mech image... + if(*ppMechTexture==NULL){ + nAtlasH=HSH_MFD_ATLAS_W; + *ppMechTexture=CreatePixelFormatTexture(pdd,HSH_MFD_ATLAS_W,nAtlasH,&DDPF_R5G6B5); + HSH_LogInit(" [mfd] %dx%d target atlas refused by the driver; using %dx%d\r\n", + HSH_MFD_ATLAS_W,HSH_MFD_ATLAS_H,HSH_MFD_ATLAS_W,nAtlasH); + } + if(pnAtlasHeight) + *pnAtlasHeight=nAtlasH; + + const int nPerRow=HSH_MFD_ATLAS_W/HSH_MFD_TILE; + const int nCapacity=nPerRow*(nAtlasH/HSH_MFD_TILE); + HSH_LogInit(" [mfd] target atlas %dx%d, %d tiles, %d chassis\r\n", + HSH_MFD_ATLAS_W,nAtlasH,nCapacity,(int)ARRAYSIZE(mechnames)); + //Store images for all mechs. for(int mech=0;mech=nCapacity){ + HSH_LogInit(" [mfd] *** atlas full at %d tiles: '%s' (id %d) has no target image ***\r\n", + nCapacity,mechnames[mech],mech); + continue; + } + int x=(mech%nPerRow)*HSH_MFD_TILE; + int y=(mech/nPerRow)*HSH_MFD_TILE; sprintf(temppath,"%s\\hsh\\mfd\\%s.bmp",AssetsDirectory1,mechnames[mech]); @@ -1397,7 +1432,7 @@ bool CMFDRight_Device::InitSecond() return false; // Independent copy of the MFD texture set owned by THIS DirectDraw object. - HSH_CreateMFDTextures(pDD, &pDDSMechTexture, &pDDSTexture); + HSH_CreateMFDTextures(pDD, &pDDSMechTexture, &pDDSTexture, &m_nMechAtlasH); pDDSDamageTexture = 0; // created on demand by LoadDamageTexture pDDSTargetTexture = 0; // created on demand by LoadTargetTexture tw = 512; @@ -1708,7 +1743,7 @@ static DWORD channel_color[6]={ CMFD_Device::CMFD_Device() - : ch((DWORD)-1), m_pRightDevice(NULL), m_bSwappedToRight(false) + : m_nMechAtlasH(1024), ch((DWORD)-1), m_pRightDevice(NULL), m_bSwappedToRight(false) { } @@ -1739,7 +1774,7 @@ bool CMFD_Device::InitSecond() if( !CHSH_Device::InitSecond(1024,512) ) // 640 x 480 return false; - HSH_CreateMFDTextures(pDD, &pDDSMechTexture, &pDDSTexture); + HSH_CreateMFDTextures(pDD, &pDDSMechTexture, &pDDSTexture, &m_nMechAtlasH); pDDSDamageTexture=0;//On Reset: recreated... no need to create now. // MSL 5.03 Target Damage Display @@ -1790,6 +1825,7 @@ void CMFD_Device::SwapRightState() HSH_SWAP_MEMBER(LPDIRECTDRAWSURFACE7, pDDSMechTexture, r->pDDSMechTexture); HSH_SWAP_MEMBER(LPDIRECTDRAWSURFACE7, pDDSDamageTexture, r->pDDSDamageTexture); HSH_SWAP_MEMBER(LPDIRECTDRAWSURFACE7, pDDSTargetTexture, r->pDDSTargetTexture); + HSH_SWAP_MEMBER(int, m_nMechAtlasH, r->m_nMechAtlasH); // CHSHFont has no virtual functions, so a raw byte swap is safe here and avoids // running constructor/destructor side effects on a temporary copy. diff --git a/Gameleap/code/CoreTech/Libraries/GameOS/render.hpp b/Gameleap/code/CoreTech/Libraries/GameOS/render.hpp index 6640d772..ea8b2ed8 100644 --- a/Gameleap/code/CoreTech/Libraries/GameOS/render.hpp +++ b/Gameleap/code/CoreTech/Libraries/GameOS/render.hpp @@ -172,6 +172,7 @@ public: LPDIRECTDRAWSURFACE7 pDDSDamageTexture;//Mech damage texture.. // MSL 5.03 Target Damage Display LPDIRECTDRAWSURFACE7 pDDSTargetTexture;//Target damage texture.. + int m_nMechAtlasH; // height the driver actually granted for the 2D target atlas DWORD ch; //current channel.... CMFDRight_Device* m_pRightDevice; // mode 4: right 640x480 device (NULL in modes 1-3) @@ -209,9 +210,10 @@ public: LPDIRECTDRAWSURFACE7 pDDSMechTexture; LPDIRECTDRAWSURFACE7 pDDSDamageTexture; LPDIRECTDRAWSURFACE7 pDDSTargetTexture; + int m_nMechAtlasH; public: CMFDRight_Device() - : pDDSMechTexture(0), pDDSDamageTexture(0), pDDSTargetTexture(0) {} + : pDDSMechTexture(0), pDDSDamageTexture(0), pDDSTargetTexture(0), m_nMechAtlasH(1024) {} bool InitFirst(); bool InitSecond(); virtual bool BeginScene() { return true; } diff --git a/Gameleap/code/mw4/Code/MW4/huddamage.cpp b/Gameleap/code/mw4/Code/MW4/huddamage.cpp index 03cf9f7d..2331e5fe 100644 --- a/Gameleap/code/mw4/Code/MW4/huddamage.cpp +++ b/Gameleap/code/mw4/Code/MW4/huddamage.cpp @@ -2020,7 +2020,8 @@ else mfd_device.pD3DDevice->SetTextureStageState(0,D3DTSS_MINFILTER,D3DTFG_LINEAR); //Need to write routine to calculate coordinates per mech type... // MSL 5.02 Target MFD Image - mfd_device.tw=1024,mfd_device.th=1024; + // [mfdatlas] height is whatever the driver granted, not a constant + mfd_device.tw=1024,mfd_device.th=(float)mfd_device.m_nMechAtlasH; // MSL 5.02 Target MFD Image Resize // mfd_device.DrawTexture2(42,104,240,204,0xFFFFFFFF,x,y,x+120,y+120); mfd_device.DrawTexture2(42,104,240,225,0xFFFFFFFF,x,y,x+120,y+120); diff --git a/Gameleap/mw4/Content/textures/HUD/behemoth2.tga b/Gameleap/mw4/Content/textures/HUD/behemoth2.tga deleted file mode 100644 index cb03aeaa..00000000 --- a/Gameleap/mw4/Content/textures/HUD/behemoth2.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:80b9fbda940a0280a3bcf0f7729ccce51313ee4c562199e210df49b400fadf42 -size 65580 diff --git a/Gameleap/mw4/Content/textures/HUD/Assassinii.tga b/Gameleap/mw4/Content/textures/HUD/behemothii.tga similarity index 100% rename from Gameleap/mw4/Content/textures/HUD/Assassinii.tga rename to Gameleap/mw4/Content/textures/HUD/behemothii.tga diff --git a/Gameleap/mw4/Content/textures/textures.hint b/Gameleap/mw4/Content/textures/textures.hint index 555be22a..96c7ddcd 100644 --- a/Gameleap/mw4/Content/textures/textures.hint +++ b/Gameleap/mw4/Content/textures/textures.hint @@ -50987,21 +50987,6 @@ pageout=default resourcify=true bias=0 -[hud\assassinii] -format=alpha -mipmap=none -readonly=true -memory=video -dontshrink=true -nogamma=false -blueisalpha=false -pinkisalpha=false -mipfilter=box -reloadfromdisk=false -pageout=default -resourcify=true -bias=0 - [hud\atlas] format=alpha mipmap=none @@ -51062,7 +51047,7 @@ pageout=default resourcify=true bias=0 -[hud\behemoth2] +[hud\behemothii] format=alpha mipmap=none readonly=true diff --git a/Gameleap/mw4/hsh/hud/assassinii.bmp b/Gameleap/mw4/hsh/hud/assassinii.bmp deleted file mode 100644 index 98448ce2..00000000 --- a/Gameleap/mw4/hsh/hud/assassinii.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d888a627cf939b7e161ab88076ab7263c1b47115e8f8320c380d778539a21cb0 -size 263224 diff --git a/Gameleap/mw4/hsh/radar/hud/assassinii.bmp b/Gameleap/mw4/hsh/radar/hud/assassinii.bmp deleted file mode 100644 index 6d9e7451..00000000 --- a/Gameleap/mw4/hsh/radar/hud/assassinii.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0e8790683183db6cfee48e07aa7330d59f5ee841696a9265ba70a1217e3901cd -size 263224