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) <noreply@anthropic.com>
Co-authored-by: GitHub Copilot <copilot@github.com>
This commit is contained in:
2026-08-09 15:44:41 -05:00
co-authored by Claude Opus 5 GitHub Copilot
parent 42a66157ea
commit 2dea177909
8 changed files with 50 additions and 35 deletions
@@ -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<ARRAYSIZE(mechnames);mech++){
int x=(mech%8)*128;
int y=(mech/8)*128;
// A tile past the end is clipped away on write and wraps to row 0 on read,
// i.e. it shows a different mech's picture. Say so rather than do that.
if(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.
@@ -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; }
+2 -1
View File
@@ -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);
Binary file not shown.
+1 -16
View File
@@ -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
Binary file not shown.
Binary file not shown.