BT410 Phase 5.3.30: the pilot name resolves -- and the magenta boxes diagnosed

The name art was never missing.  The egg carries it as notation pages of hex
rows ([BitMap::Small::Aeolus]); the engine BitMap has a ctor that reads
exactly that format; and the AUTHENTIC Mission ctor already loads them into
smallNameBitmapChain (MISSION.CPP:525 -- implemented, called, working).  The
break was ours: btl4gau3's LookupPlayerNameBitmap was a bring-up stub
returning NULL.  It now uses the authentic API (GetCurrentMission()->
GetSmallNameBitmap(playerBitmapIndex)) and resolves live -- traced index=1
to a real BitMap, with the fallback-box branch provably never firing.
Coverage 91% -> 92%.

DIAGNOSIS CORRECTED for the stray magenta blocks over the top-left title:
they are NOT a missing-name placeholder.  They are the REAL name bitmap
drawn at the wrong slot coordinates -- PilotList lays its 8 roster slots
out from the PE-recovered DAT_0051af88 (x,y,mode) table, and ours puts them
top-left instead of the shipped exe's centre row.  Recovering that table is
the next cockpit thread; the egg-hex -> live-BitMap pipeline is verified end
to end, so what remains there is layout, not art or lookup.

(A duplicate loader written before finding the authentic one was removed --
CODE/RP/MUNGA/MISSION.CPP already does this job.)

Gauge fight 11/11, smoke and novice clean, zero faults.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-27 09:47:19 -05:00
co-authored by Claude Fable 5
parent 42ff5c3856
commit 10e1a70055
3 changed files with 418 additions and 370 deletions
+25 -2
View File
@@ -37,6 +37,10 @@
#include <btl4.hpp>
#pragma hdrstop
#if !defined(MISSION_HPP)
# include <mission.hpp>
#endif
#if !defined(BTL4GAU3_HPP)
# include <btl4gau3.hpp>
#endif
@@ -145,9 +149,28 @@ static Logical
// binary's own cache-miss branch (the tinted name box), never an AV.
//
static BitMap *
LookupPlayerNameBitmap(int /*player_bitmap_index*/)
LookupPlayerNameBitmap(int player_bitmap_index)
{
return NULL;
//
// The mission owns the name art: the egg's [smallbitmap] index page
// lists one notation page of hex rows per pilot, and the authentic
// Mission ctor loads them into smallNameBitmapChain (MISSION.CPP:525).
// A player's `bitmapindex` egg entry selects its 1-based slot. A miss
// still routes the caller to the binary's cache-miss branch (the
// tinted name box), never an AV.
//
if (player_bitmap_index <= 0 || application == NULL)
{
return NULL;
}
Mission
*mission = application->GetCurrentMission();
if (mission == NULL)
{
return NULL;
}
return mission->GetSmallNameBitmap(player_bitmap_index);
}
+368 -368
View File
@@ -1,368 +1,368 @@
//===========================================================================//
// File: btl4mssn.cpp //
// Project: BattleTech Brick: //
// Contents: Implementation for the L4 (game-level) mission //
//---------------------------------------------------------------------------//
// Date Who Modification //
// -------- --- ---------------------------------------------------------- //
// 04/07/95 ECH Initial coding. //
// 09/12/95 JM Derived from L4Mission //
//---------------------------------------------------------------------------//
// Copyright (C) 1995, Virtual World Entertainment, Inc. //
// All Rights reserved worldwide //
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
//===========================================================================//
#include <btl4.hpp>
#pragma hdrstop
#if !defined(BTL4MSSN_HPP)
# include <btl4mssn.hpp>
#endif
#if !defined(BTL4APP_HPP)
# include <btl4app.hpp>
#endif
#if !defined(L4GREND_HPP)
# include <l4grend.hpp>
#endif
#if !defined(L4HOST_HPP)
# include <l4host.hpp>
#endif
#if !defined(HOSTMGR_HPP)
# include <hostmgr.hpp>
#endif
#if !defined(NOTATION_HPP)
# include <notation.hpp>
#endif
#if !defined(SCNROLE_HPP)
# include <scnrole.hpp>
#endif
//##########################################################################
//########################### BTL4Mission ############################
//##########################################################################
//
//#############################################################################
//#############################################################################
//
BTL4Mission::BTL4Mission(
NotationFile *notation_file,
ResourceFile *resources
):
BTMission(notation_file, resources)
{
Check_Fpu();
}
//
//#############################################################################
//#############################################################################
//
BTL4Mission::~BTL4Mission()
{
Check(this);
Check_Fpu();
}
//
//#############################################################################
// SetPlayerData Pulls this host's player page out of the notation file and
// populates the mission's player description: model, dropzone, color, badge,
// patch, experience level, advanced-damage flag, scenario role (+ its damage
// modifiers), and the plasma-display badge bitmap.
//#############################################################################
//
void
BTL4Mission::SetPlayerData(NotationFile *notation_file)
{
Check(this);
//
//--------------------------------------------------------------
// Figure out which page we should be using to create the player
//--------------------------------------------------------------
//
Check(application);
HostManager *host_manager = application->GetHostManager();
Check(host_manager);
L4Host *host = (L4Host*)host_manager->GetLocalHost();
Check(host);
CString
player_node = host->GetSymbolicName();
//
//----------------------------------------------------------
// Copy the game model and dropzone strings into the mission
//----------------------------------------------------------
//
gameModel = NULL;
const char *player_model = NULL;
notation_file->GetEntry(player_node, "vehicle", &player_model);
if (!player_model)
{
DEBUG_STREAM << "Error: vehicle not specified!\n";
Fail("Exiting");
}
Check_Pointer(player_model);
gameModel = strdup(player_model);
Register_Pointer(gameModel);
dropZoneName = NULL;
const char *drop_zone = NULL;
notation_file->GetEntry(player_node, "dropzone", &drop_zone);
if (!drop_zone)
{
DEBUG_STREAM << "Error: dropzone not specified!\n";
Fail("Exiting");
}
Check_Pointer(drop_zone);
dropZoneName = strdup(drop_zone);
Register_Pointer(dropZoneName);
dropZoneHost = False;
notation_file->GetEntry(player_node, "loadzones", &dropZoneHost);
//
//----------------------------------------------------------
// Color
//----------------------------------------------------------
//
colorName = NULL;
const char *color_name = NULL;
notation_file->GetEntry(player_node, "color", &color_name);
if (!color_name)
{
DEBUG_STREAM << "Error: color not specified!\n";
Fail("Exiting");
}
Check_Pointer(color_name);
colorName = strdup(color_name);
Register_Pointer(colorName);
//
//----------------------------------------------------------
// Patch
//----------------------------------------------------------
//
const char *patch_name = NULL;
notation_file->GetEntry(player_node, "patch", &patch_name);
if (!patch_name)
{
DEBUG_STREAM << "Error: patch not specified!\n";
Fail("Exiting");
}
Check_Pointer(patch_name);
patchName = patch_name;
//
//----------------------------------------------------------
// Badge
//----------------------------------------------------------
//
badgeName = NULL;
const char *badge_name = NULL;
notation_file->GetEntry(player_node, "badge", &badge_name);
if (!badge_name)
{
DEBUG_STREAM << "Error: badge not specified!\n";
Fail("Exiting");
}
Check_Pointer(badge_name);
badgeName = strdup(badge_name);
Register_Pointer(badgeName);
//
//----------------------------------------------------------
// Team name. NOTE: the shipped binary reads the "badge" key here
// as well and defaults to "DEFAULT" when the page omits it.
//----------------------------------------------------------
//
const char *team_name = NULL;
if (!notation_file->GetEntry(player_node, "badge", &team_name))
{
teamName = "DEFAULT";
}
else
{
Check_Pointer(team_name);
teamName = team_name;
}
//
//----------------------------------------------------------
// Experience level. Absence is tolerated only for the "camera"
// page; "Unspecified" leaves the default.
//----------------------------------------------------------
//
const char *experience = "Unspecified";
if (!notation_file->GetEntry(player_node, "experience", &experience))
{
if (strcmp(gameModel, "camera") != 0)
{
DEBUG_STREAM << player_node << " page missing expereince!" << endl;
Fail("Exiting");
}
}
if (strcmp("Unspecified", experience) != 0)
{
if (strcmp(experience, "novice") == 0)
{
experienceLevel = BTMission::NoviceMode;
}
else if (strcmp(experience, "standard") == 0)
{
experienceLevel = BTMission::StandardMode;
}
else if (strcmp(experience, "veteran") == 0)
{
experienceLevel = BTMission::VeteranMode;
}
else if (strcmp(experience, "expert") == 0)
{
experienceLevel = BTMission::ExpertMode;
}
else
{
DEBUG_STREAM << player_node
<< " has an unknown experience field of "
<< experience;
Fail("Exiting");
}
}
//
//----------------------------------------------------------
// Advanced damage flag
//----------------------------------------------------------
//
Logical advanced_damage = False;
if (notation_file->GetEntry(player_node, "advancedDamage", &advanced_damage))
{
advancedDamageOn = advanced_damage ? True : False;
}
//
//----------------------------------------------------------
// Scenario role -- every player page must name a role, and the role
// page must name a model. The ScenarioRole is looked up (and created
// on first use) in the mission's role dictionary, then its per-page
// score modifiers applied.
//----------------------------------------------------------
//
const char *role_node = NULL;
if (!notation_file->GetEntry(player_node, "role", &role_node))
{
DEBUG_STREAM << "Each Player page must have a role field !" << endl;
Fail("Exiting");
}
else
{
Check_Pointer(role_node);
roleName = role_node;
const char *role_model = NULL;
if (!notation_file->GetEntry(role_node, "model", &role_model))
{
DEBUG_STREAM << role_node << " page or model field missing !" << endl;
Fail("Exiting");
}
else
{
ScenarioRole *role = GetScenarioRole(roleName);
if (role == NULL)
{
role = new ScenarioRole(roleName, CString(role_model));
Register_Object(role);
AddScenarioRole(role);
}
//
// Optional per-page score modifiers.
//
Scalar value;
if (notation_file->GetEntry(role_node, "dmgInflctdMdfr", &value))
{
role->SetDamageInflictedModifier(value);
}
if (notation_file->GetEntry(role_node, "dmgRcvdMdfr", &value))
{
role->SetDamageReceivedModifier(value);
}
if (notation_file->GetEntry(role_node, "dmgBias", &value))
{
role->SetDamageBias(value);
}
if (notation_file->GetEntry(role_node, "friendlyFire", &value))
{
role->SetFriendlyFirePenalty(value);
}
int return_from_death;
if (notation_file->GetEntry(role_node, "return", &return_from_death))
{
role->SetReturnFromDeath(return_from_death);
}
if (notation_file->GetEntry(role_node, "killBonus", &value))
{
role->SetKillBonus(value);
}
}
}
//
//~~~~~~~~~~~~~~~~~~~~~~
// Get playerBitmapIndex and, if present, blit the badge bitmap onto
// the external (plasma) display.
//~~~~~~~~~~~~~~~~~~~~~~
//
if (!notation_file->GetEntry(player_node, "bitmapindex", &playerBitmapIndex))
{
DEBUG_STREAM << "No Bitmap for player" << player_node;
DEBUG_STREAM << flush;
}
else
{
BitMap *bitmap = GetLargeNameBitmap(playerBitmapIndex);
if (bitmap != NULL)
{
Check(application);
L4GaugeRenderer
*gauge_renderer =
(L4GaugeRenderer*)application->GetGaugeRenderer();
if (gauge_renderer != NULL)
{
Check(gauge_renderer);
GraphicsDisplay *graphics_display =
gauge_renderer->GetExternalDisplay();
if (graphics_display != NULL)
{
graphics_display->DrawBitMapOpaque(
1, // foreground color
0, // background color
0xFF, // bitmask
GraphicsDisplay::Replace, // operation
0, // rotation
0, 0, // position
bitmap,
0, // sx1
0, // sy1
bitmap->Data.Size.x - 1, // sx2
bitmap->Data.Size.y - 1 // sy2
);
graphics_display->Update(True);
graphics_display->WaitForUpdate();
}
}
}
}
Check_Fpu();
}
//===========================================================================//
// File: btl4mssn.cpp //
// Project: BattleTech Brick: //
// Contents: Implementation for the L4 (game-level) mission //
//---------------------------------------------------------------------------//
// Date Who Modification //
// -------- --- ---------------------------------------------------------- //
// 04/07/95 ECH Initial coding. //
// 09/12/95 JM Derived from L4Mission //
//---------------------------------------------------------------------------//
// Copyright (C) 1995, Virtual World Entertainment, Inc. //
// All Rights reserved worldwide //
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
//===========================================================================//
#include <btl4.hpp>
#pragma hdrstop
#if !defined(BTL4MSSN_HPP)
# include <btl4mssn.hpp>
#endif
#if !defined(BTL4APP_HPP)
# include <btl4app.hpp>
#endif
#if !defined(L4GREND_HPP)
# include <l4grend.hpp>
#endif
#if !defined(L4HOST_HPP)
# include <l4host.hpp>
#endif
#if !defined(HOSTMGR_HPP)
# include <hostmgr.hpp>
#endif
#if !defined(NOTATION_HPP)
# include <notation.hpp>
#endif
#if !defined(SCNROLE_HPP)
# include <scnrole.hpp>
#endif
//##########################################################################
//########################### BTL4Mission ############################
//##########################################################################
//
//#############################################################################
//#############################################################################
//
BTL4Mission::BTL4Mission(
NotationFile *notation_file,
ResourceFile *resources
):
BTMission(notation_file, resources)
{
Check_Fpu();
}
//
//#############################################################################
//#############################################################################
//
BTL4Mission::~BTL4Mission()
{
Check(this);
Check_Fpu();
}
//
//#############################################################################
// SetPlayerData Pulls this host's player page out of the notation file and
// populates the mission's player description: model, dropzone, color, badge,
// patch, experience level, advanced-damage flag, scenario role (+ its damage
// modifiers), and the plasma-display badge bitmap.
//#############################################################################
//
void
BTL4Mission::SetPlayerData(NotationFile *notation_file)
{
Check(this);
//
//--------------------------------------------------------------
// Figure out which page we should be using to create the player
//--------------------------------------------------------------
//
Check(application);
HostManager *host_manager = application->GetHostManager();
Check(host_manager);
L4Host *host = (L4Host*)host_manager->GetLocalHost();
Check(host);
CString
player_node = host->GetSymbolicName();
//
//----------------------------------------------------------
// Copy the game model and dropzone strings into the mission
//----------------------------------------------------------
//
gameModel = NULL;
const char *player_model = NULL;
notation_file->GetEntry(player_node, "vehicle", &player_model);
if (!player_model)
{
DEBUG_STREAM << "Error: vehicle not specified!\n";
Fail("Exiting");
}
Check_Pointer(player_model);
gameModel = strdup(player_model);
Register_Pointer(gameModel);
dropZoneName = NULL;
const char *drop_zone = NULL;
notation_file->GetEntry(player_node, "dropzone", &drop_zone);
if (!drop_zone)
{
DEBUG_STREAM << "Error: dropzone not specified!\n";
Fail("Exiting");
}
Check_Pointer(drop_zone);
dropZoneName = strdup(drop_zone);
Register_Pointer(dropZoneName);
dropZoneHost = False;
notation_file->GetEntry(player_node, "loadzones", &dropZoneHost);
//
//----------------------------------------------------------
// Color
//----------------------------------------------------------
//
colorName = NULL;
const char *color_name = NULL;
notation_file->GetEntry(player_node, "color", &color_name);
if (!color_name)
{
DEBUG_STREAM << "Error: color not specified!\n";
Fail("Exiting");
}
Check_Pointer(color_name);
colorName = strdup(color_name);
Register_Pointer(colorName);
//
//----------------------------------------------------------
// Patch
//----------------------------------------------------------
//
const char *patch_name = NULL;
notation_file->GetEntry(player_node, "patch", &patch_name);
if (!patch_name)
{
DEBUG_STREAM << "Error: patch not specified!\n";
Fail("Exiting");
}
Check_Pointer(patch_name);
patchName = patch_name;
//
//----------------------------------------------------------
// Badge
//----------------------------------------------------------
//
badgeName = NULL;
const char *badge_name = NULL;
notation_file->GetEntry(player_node, "badge", &badge_name);
if (!badge_name)
{
DEBUG_STREAM << "Error: badge not specified!\n";
Fail("Exiting");
}
Check_Pointer(badge_name);
badgeName = strdup(badge_name);
Register_Pointer(badgeName);
//
//----------------------------------------------------------
// Team name. NOTE: the shipped binary reads the "badge" key here
// as well and defaults to "DEFAULT" when the page omits it.
//----------------------------------------------------------
//
const char *team_name = NULL;
if (!notation_file->GetEntry(player_node, "badge", &team_name))
{
teamName = "DEFAULT";
}
else
{
Check_Pointer(team_name);
teamName = team_name;
}
//
//----------------------------------------------------------
// Experience level. Absence is tolerated only for the "camera"
// page; "Unspecified" leaves the default.
//----------------------------------------------------------
//
const char *experience = "Unspecified";
if (!notation_file->GetEntry(player_node, "experience", &experience))
{
if (strcmp(gameModel, "camera") != 0)
{
DEBUG_STREAM << player_node << " page missing expereince!" << endl;
Fail("Exiting");
}
}
if (strcmp("Unspecified", experience) != 0)
{
if (strcmp(experience, "novice") == 0)
{
experienceLevel = BTMission::NoviceMode;
}
else if (strcmp(experience, "standard") == 0)
{
experienceLevel = BTMission::StandardMode;
}
else if (strcmp(experience, "veteran") == 0)
{
experienceLevel = BTMission::VeteranMode;
}
else if (strcmp(experience, "expert") == 0)
{
experienceLevel = BTMission::ExpertMode;
}
else
{
DEBUG_STREAM << player_node
<< " has an unknown experience field of "
<< experience;
Fail("Exiting");
}
}
//
//----------------------------------------------------------
// Advanced damage flag
//----------------------------------------------------------
//
Logical advanced_damage = False;
if (notation_file->GetEntry(player_node, "advancedDamage", &advanced_damage))
{
advancedDamageOn = advanced_damage ? True : False;
}
//
//----------------------------------------------------------
// Scenario role -- every player page must name a role, and the role
// page must name a model. The ScenarioRole is looked up (and created
// on first use) in the mission's role dictionary, then its per-page
// score modifiers applied.
//----------------------------------------------------------
//
const char *role_node = NULL;
if (!notation_file->GetEntry(player_node, "role", &role_node))
{
DEBUG_STREAM << "Each Player page must have a role field !" << endl;
Fail("Exiting");
}
else
{
Check_Pointer(role_node);
roleName = role_node;
const char *role_model = NULL;
if (!notation_file->GetEntry(role_node, "model", &role_model))
{
DEBUG_STREAM << role_node << " page or model field missing !" << endl;
Fail("Exiting");
}
else
{
ScenarioRole *role = GetScenarioRole(roleName);
if (role == NULL)
{
role = new ScenarioRole(roleName, CString(role_model));
Register_Object(role);
AddScenarioRole(role);
}
//
// Optional per-page score modifiers.
//
Scalar value;
if (notation_file->GetEntry(role_node, "dmgInflctdMdfr", &value))
{
role->SetDamageInflictedModifier(value);
}
if (notation_file->GetEntry(role_node, "dmgRcvdMdfr", &value))
{
role->SetDamageReceivedModifier(value);
}
if (notation_file->GetEntry(role_node, "dmgBias", &value))
{
role->SetDamageBias(value);
}
if (notation_file->GetEntry(role_node, "friendlyFire", &value))
{
role->SetFriendlyFirePenalty(value);
}
int return_from_death;
if (notation_file->GetEntry(role_node, "return", &return_from_death))
{
role->SetReturnFromDeath(return_from_death);
}
if (notation_file->GetEntry(role_node, "killBonus", &value))
{
role->SetKillBonus(value);
}
}
}
//
//~~~~~~~~~~~~~~~~~~~~~~
// Get playerBitmapIndex and, if present, blit the badge bitmap onto
// the external (plasma) display.
//~~~~~~~~~~~~~~~~~~~~~~
//
if (!notation_file->GetEntry(player_node, "bitmapindex", &playerBitmapIndex))
{
DEBUG_STREAM << "No Bitmap for player" << player_node;
DEBUG_STREAM << flush;
}
else
{
BitMap *bitmap = GetLargeNameBitmap(playerBitmapIndex);
if (bitmap != NULL)
{
Check(application);
L4GaugeRenderer
*gauge_renderer =
(L4GaugeRenderer*)application->GetGaugeRenderer();
if (gauge_renderer != NULL)
{
Check(gauge_renderer);
GraphicsDisplay *graphics_display =
gauge_renderer->GetExternalDisplay();
if (graphics_display != NULL)
{
graphics_display->DrawBitMapOpaque(
1, // foreground color
0, // background color
0xFF, // bitmask
GraphicsDisplay::Replace, // operation
0, // rotation
0, 0, // position
bitmap,
0, // sx1
0, // sy1
bitmap->Data.Size.x - 1, // sx2
bitmap->Data.Size.y - 1 // sy2
);
graphics_display->Update(True);
graphics_display->WaitForUpdate();
}
}
}
}
Check_Fpu();
}
@@ -553,3 +553,28 @@ LookupPlayerNameBitmap bring-up stubs), some lamp frames ("OFF x" vs the
shipped state digits -- the mode accessor stand-in), and a stray magenta
"MID/ADV" block that lands over the top-left title (a widget drawing at a
wrong port offset; next thread).
## 5.3.30 -- THE PILOT NAME (and what the magenta boxes really are)
The name art was never missing: the egg carries it as notation pages of hex
rows ([BitMap::Small::Aeolus]), the engine BitMap has a ctor that reads
exactly that format (GRAPH2D.CPP), and the AUTHENTIC Mission ctor already
loads them into smallNameBitmapChain (CODE/RP/MUNGA/MISSION.CPP:525 --
implemented, called, working). The only break was OUR side: btl4gau3's
LookupPlayerNameBitmap was a bring-up stub returning NULL. It now goes
through the authentic API -- application->GetCurrentMission()->
GetSmallNameBitmap(player->playerBitmapIndex) -- and RESOLVES live
(traced: index=1 -> a real BitMap; the fallback-box branch never fires).
Coverage 91% -> 92%.
DIAGNOSIS CORRECTED: the two magenta blocks over the top-left title are NOT
the missing-name placeholder (that branch is provably not running). They
are the REAL name bitmap drawn at the wrong slot coordinates -- PilotList
positions its 8 roster slots from the PE-recovered DAT_0051af88 (x,y,mode)
table, and ours puts them at the top-left instead of the shipped exe's
centre row. Recovering that layout table is the next cockpit thread (it
should also land "Aeolus" where the shipped binary draws it).
NOTE for whoever picks this up: a wrong-looking blit here is a LAYOUT bug,
not an art or lookup bug -- the pipeline from egg hex rows to a live BitMap
pointer is verified end to end.