From 10e1a700555139983e7ba5679e8ed21fd0bff833 Mon Sep 17 00:00:00 2001 From: Cyd Date: Mon, 27 Jul 2026 09:47:19 -0500 Subject: [PATCH] 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 --- restoration/source410/BT_L4/BTL4GAU3.CPP | 27 +- restoration/source410/BT_L4/BTL4MSSN.CPP | 736 +++++++++--------- .../source410/BT_L4/GAUGE-BLOCK.NOTES.md | 25 + 3 files changed, 418 insertions(+), 370 deletions(-) diff --git a/restoration/source410/BT_L4/BTL4GAU3.CPP b/restoration/source410/BT_L4/BTL4GAU3.CPP index 67e8946f..cdad1163 100644 --- a/restoration/source410/BT_L4/BTL4GAU3.CPP +++ b/restoration/source410/BT_L4/BTL4GAU3.CPP @@ -37,6 +37,10 @@ #include #pragma hdrstop +#if !defined(MISSION_HPP) +# include +#endif + #if !defined(BTL4GAU3_HPP) # include #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); } diff --git a/restoration/source410/BT_L4/BTL4MSSN.CPP b/restoration/source410/BT_L4/BTL4MSSN.CPP index 7d240c0c..65f8f3af 100644 --- a/restoration/source410/BT_L4/BTL4MSSN.CPP +++ b/restoration/source410/BT_L4/BTL4MSSN.CPP @@ -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 -#pragma hdrstop - -#if !defined(BTL4MSSN_HPP) -# include -#endif - -#if !defined(BTL4APP_HPP) -# include -#endif - -#if !defined(L4GREND_HPP) -# include -#endif - -#if !defined(L4HOST_HPP) -# include -#endif - -#if !defined(HOSTMGR_HPP) -# include -#endif - -#if !defined(NOTATION_HPP) -# include -#endif - -#if !defined(SCNROLE_HPP) -# include -#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 +#pragma hdrstop + +#if !defined(BTL4MSSN_HPP) +# include +#endif + +#if !defined(BTL4APP_HPP) +# include +#endif + +#if !defined(L4GREND_HPP) +# include +#endif + +#if !defined(L4HOST_HPP) +# include +#endif + +#if !defined(HOSTMGR_HPP) +# include +#endif + +#if !defined(NOTATION_HPP) +# include +#endif + +#if !defined(SCNROLE_HPP) +# include +#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(); +} diff --git a/restoration/source410/BT_L4/GAUGE-BLOCK.NOTES.md b/restoration/source410/BT_L4/GAUGE-BLOCK.NOTES.md index b7d436fc..d040a097 100644 --- a/restoration/source410/BT_L4/GAUGE-BLOCK.NOTES.md +++ b/restoration/source410/BT_L4/GAUGE-BLOCK.NOTES.md @@ -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.