Files
arcattackandClaude Opus 4.8 7fc4acb89f gauge-complete P4g: MessageBoard reconstructed + registered -> LAST parse-skip cleared (0 unregistered gauges)
The "messageBoard" cockpit primitive (the secondary-MFD comm/status message ticker,
L4GAUGE.CFG:4913) was PROSE-ONLY (no bodies/methodDescription) -> parse-skipped. This
was the last unregistered gauge primitive.

Reconstructed byte-verified (Make @4cb678, ctor @4cb704, dtor @4cb788, BecameActive
@4cb7fc, Execute @4cb82c; vtable 0051bddc; sizeof 0xA4). THREE header mislabels fixed
(decode + adversarial verify): int enabled -> Entity* trackedMech (a pointer deref'd at
+0x190, set by SetSource not SetEnable); previousMessageId/previousNameId were SWAPPED
(BecameActive writes 0x9c=-1/0xa0=-2; Execute compares messageId vs 0xa0, name vs 0x9c).
Execute blits the strip cell (id -> (id&3)<<7,(id>>2)<<5) + sender name; methodDescription
= 4 params (rate, mode, btsmsgs.pcx, color). Registered in BTL4MethodDescription[].

DEFERRED / EMPTY by design (authentic for bring-up): the source is never bound (SetSource
has no recovered caller) AND the per-player status queue (StatusMessagePool, btstubs.cpp:62)
is a NULL stub -> no status messages exist -> Execute early-returns on the NULL source (safe
no-op == empty board). Data read via a BTResolveMessageBoard bridge in btplayer.cpp (a real
/FORCE-safe stub returning False; the raw mech+0x190/+0x1dc reads stay in the complete-BTPlayer
TU, dodging the databinding trap). The name-cell path is a marked structural simplification to
restore when the feed lands.

VERIFIED: the gauge parse-skip list is now EMPTY ([gskip]=0 -- every config gauge primitive
registered + built); BTResolveMessageBoard resolves (no /FORCE __ImageBase AV); combat TARGET
DESTROYED, no crash; gauge composite renders identically (300/77, clusters, radar un-regressed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 21:39:47 -05:00

305 lines
15 KiB
C++

//===========================================================================//
// File: btl4gau3.hpp //
// Project: BattleTech Brick: Gauge Renderer Manager //
// Contents: Cockpit instrument library, part 3 -- the MULTIPLAYER / TACTICAL //
// HUD displays: the scoreboard, pilot roster, per-player status //
// name-tags, the sector / armour-mosaic display and the message //
// board. These read the shared "Players" node and the live mech //
// roster rather than a single subsystem. //
// * SectorDisplay -- bitmap status grid + dual numeric //
// * PlayerStatusMappingGroup -- 28-zone armour colour-tint group //
// * TeamStatusDisplay -- 8-row scoreboard //
// * PilotList -- 8-pilot roster (3 numerics each) //
// * PlayerStatus -- single-player status name-tag //
// * MessageBoard -- rank / message bitmap board //
//---------------------------------------------------------------------------//
// Date Who Modification //
// -------- --- ---------------------------------------------------------- //
// 02/22/96 CPB Initial coding. //
//---------------------------------------------------------------------------//
// Copyright (C) 1996, Virtual World Entertainment, Inc. All rights reserved //
// PROPRIETARY AND CONFIDENTIAL //
//===========================================================================//
//
// RECONSTRUCTED. No BTL4GAU3.HPP survived; recovered from BTL4OPT.EXE.
// Ghidra pseudo-C in recovered/all/part_014.c (@004c9bd0..@004cbea0). Class
// names recovered from the per-instance identification strings near 0x51bb34:
// "SectorDisplay" 0x51bb34 "PilotList" 0x51bb4a "PlayerStatus" 0x51bb54
// "MessageBoard" 0x51bb61 "PlayerStatusMappingGroup" 0x51bdb4
// "Players" 0x51bcba/0x51bd97 (shared roster node)
//
// Translation-unit extent (BTL4.MAK: btl4gau2 -> btl4gau3 -> btl4grnd):
// first gau3 code @004c9bd0 (PlayerStatusMappingGroup builder)
// last gau3 code @004cbea0-1 (MessageBoard::Execute @004cb82c)
// btl4grnd begins @004cbea0 (BTL4GaugeRenderer ctor -- not this TU)
//
// Vtables (recovered from the ctors): SectorDisplay 0051beec,
// TeamStatusDisplay 0051bea8, PilotList 0051be64, PlayerStatus 0051be20,
// MessageBoard 0051bddc. All derive from GraphicGauge (ctor FUN_00444818).
//
#if !defined(BTL4GAU3_HPP)
# define BTL4GAU3_HPP
# if !defined(SLOT_HPP)
# include <slot.hpp>
# endif
# if !defined(L4GREND_HPP)
# include <l4grend.hpp>
# endif
# if !defined(L4GAUGE_HPP)
# include <l4gauge.hpp>
# endif
# if !defined(BTL4GAUG_HPP)
# include <btl4gaug.hpp>
# endif
//
// Reconstruction alias: the binary's per-player recoloured "Pixmap" is the
// engine's 8-bit pixel map. (Only Pixmap* appears in this header / TU.)
//
typedef PixelMap8 Pixmap;
//#######################################################################
// PlayerStatusMappingGroup -- a colour-tint group of 28 ColorMapperArmor
// gauges, one per named damage zone ("dz_door"... PTR_s_dz_door_0051a240,
// 0x1C=28 entries). Built by PlayerStatus to tint a player's mech outline
// bitmap by per-zone armour damage. @004c9bd0 build / @004c9ca8 destroy /
// @004c9cf4 Execute-all / @004c9d18 SetColor-all. Each element is a
// ColorMapperArmor (FUN_004c3b98) wired to a resolved damage-zone index.
// (palette files adpal.pcc / adpal2.pcc; name "PlayerStatusMappingGroup".)
//#######################################################################
enum { playerStatusZoneCount = 28 };
class PlayerStatusMappingGroup
{
public:
PlayerStatusMappingGroup( // @004c9bd0
GaugeRate, ModeMask, L4GaugeRenderer *, int graphics_port_number,
Entity *mech, int base_color_index,
const char *palette_a, const char *palette_b,
const char *identification_string);
~PlayerStatusMappingGroup(); // @004c9ca8
void Execute(); // @004c9cf4 (run all 28)
void SetColor(int color); // @004c9d18 (recolour all 28)
protected:
ColorMapperArmor *zone[playerStatusZoneCount]; // this[0..0x1B]
};
//#######################################################################
// SectorDisplay (config keyword "sectorDisplay") -- the radar SECTOR X/Z
// read-out on the Secondary overlay: a one-shot grid-cell background blit +
// two 3-digit NumericDisplays showing the linked mech's world sector coords
// (Round(-Z*0.01)+500, Round(X*0.01)+500 -> 100-unit sectors).
// @004c9d44 Make / @004c9e10 ctor / @004c9f94 dtor / @004ca038 BecameActive /
// @004ca068 LinkToEntity (slot 9) / @004ca07c Execute (slot 16). vtable 0x51beec.
// Was PROSE-ONLY (placeholder Make, no ctor/methodDescription) -> the config
// "sectorDisplay" line was parse-skipped. Reconstructed byte-verified from the
// disassembly (ctor @4c9e10, Execute @4ca07c) + methodDescription PE-parse.
//#######################################################################
class SectorDisplay :
public GraphicGauge
{
public:
static MethodDescription methodDescription; // "sectorDisplay"
static Logical Make(int, Vector2DOf<int>, Entity *, GaugeRenderer *); // @004c9d44
SectorDisplay( // @004c9e10 (owner folded to 0, like the siblings)
GaugeRate, ModeMask, L4GaugeRenderer *, int graphics_port_number,
int x, int y, const char *image, int color, int ok_color,
const char *identification_string);
~SectorDisplay(); // @004c9f94
Logical TestInstance() const;
void LinkToEntity(Entity *); // @004ca068 slot 9 -- caches the subject mech
void BecameActive(); // @004ca038 slot 3 (non-inactivating)
void Execute(); // @004ca07c slot 16
protected:
Entity *subject; // @0x90 LinkToEntity target (ctor=0)
char *gridImage; // @0x94 interned image name (copied; blit source)
int cellWidth; // @0x98 imageWidth / 14
int gridLeft; // @0x9C cellWidth*12
int gridRight; // @0xA0 cellWidth*13 - 1
int gridHeight; // @0xA4 imageHeight
int numericColor; // @0xA8 param_9 (both numerics' color)
int gridColor; // @0xAC param_10 (blit color + numeric okColor)
int sectorBaseA; // @0xB0 = 500 (added to the -Z numeric)
int sectorBaseB; // @0xB4 = 500 (added to the +X numeric)
int dirty; // @0xB8 redraw flag (BecameActive=1, Execute clears)
NumericDisplay
*numericA, // @0xBC -Z sector readout
*numericB; // @0xC0 X sector readout
// sizeof == 0xC4 (== Make alloc); overflow-lock only (2007 GraphicGauge base
// is not byte-identical to 1995, and every read here is via named members).
};
static_assert(sizeof(SectorDisplay) <= 0xC4, "SectorDisplay must fit its 0xC4 Make alloc");
//#######################################################################
// TeamStatusDisplay -- the 8-slot multiplayer scoreboard. @004ca208 ctor /
// @004ca39c dtor / @004ca424 SetEnable / @004ca438 BecameActive /
// @004ca478 Execute. vtable PTR_FUN_0051bea8. Each of the 8 rows owns a
// NumericDisplay pair (player number + kill count). Execute reads the
// shared "Players" node (FindByName @00403ad0, "Players" 0x51bcba), slots
// players by their player index (entity+0x1cc), draws each player's name
// bitmap / kill numeric and a selection highlight on the local player.
//#######################################################################
class TeamStatusDisplay :
public GraphicGauge
{
public:
TeamStatusDisplay( // @004ca208
GaugeRate, ModeMask, L4GaugeRenderer *, int,
int x, int y, const char *font_image, int row_spacing,
int flags, const char *identification_string);
~TeamStatusDisplay(); // @004ca39c
Logical TestInstance() const;
void SetEnable(Logical); // @004ca424 this[0x24]
void BecameActive(); // @004ca438
void Execute(); // @004ca478
protected:
Logical enabled; // @0x90 this[0x24]
int originX; // @0x98 this[0x26]
int originY; // @0x9C this[0x27] (param_8-0x20)
int rowSpacing; // @0xA4 this[0x29]
int flags; // @0xA0 this[0x28]
int highlightWidth; // @0xA8 this[0x2A]
int selectedRow; // @0x94 this[0x25] (=999)
struct Row { // 8 rows starting at this[0x3B]
NumericDisplay *numberDisplay; // +0x00 (slot0..7 @+0xEC each row)
NumericDisplay *killDisplay; // +0x44
} row[8];
};
//#######################################################################
// PilotList -- the 8-pilot roster (lance display). @004ca90c Make /
// @004ca958 ctor / @004cab10 dtor / @004caba4 BecameActive / @004cabd0
// Execute. vtable PTR_FUN_0051be64. Each entry has three NumericDisplays
// laid out by a per-entry layout mode (0/1/2 -> DAT_0051af88 table) giving
// the pilot's name, mech and ammo/score positions. Execute walks the lance
// roster via FUN_004b0898 (per-slot mech lookup) one slot per frame; helpers
// DrawMechIcon @004cad70 and DrawAmmoCount @004cae20.
//#######################################################################
class PilotList :
public GraphicGauge
{
public:
// gauge wave P2: registered as "pilotList" -- the Comm KILLS/DEATHS roster.
static MethodDescription methodDescription;
static Logical Make(int, Vector2DOf<int>, Entity *, GaugeRenderer *); // @004ca90c
PilotList( // @004ca958 (NO x,y: positions come from DAT_0051af88)
GaugeRate, ModeMask, L4GaugeRenderer *, int,
const char *font_image,
const char *identification_string);
~PilotList(); // @004cab10
Logical TestInstance() const;
void BecameActive(); // @004caba4
void Execute(); // @004cabd0
protected:
void DrawMechIcon(void *pilot, int selected); // @004cad70
int currentSlot; // @0x90 this[0x24] (round-robin, =9 init)
Logical built; // @0x94 this[0x25]
struct Entry { // 8 entries (stride 0x1C) from this[0x26]
int x; // +0x00
int y; // +0x04
int resolvedMech; // +0x08 (=-1)
int selected; // +0x0C
NumericDisplay *nameDisplay; // +0x10
NumericDisplay *mechDisplay; // +0x14
NumericDisplay *scoreDisplay; // +0x18
} entry[8];
};
//#######################################################################
// PlayerStatus -- a single-player status name-tag. @004cae90 Make /
// @004cb1a8 ctor / @004cb28c dtor / @004cb310 BecameActive / @004cb358
// Execute. vtable PTR_FUN_0051be20. Make validates the player number
// (DAT_0051b07c, must be 1..8; warns "PlayerStatus: Make player number <n>").
// Execute resolves player N from the "Players" node (@00403ad0,
// "Players" 0x51bd97), recolours its mech-outline bitmap per player palette
// via CreateMutantPixelmap8 (@004cafac) + PlayerStatusMappingGroup
// (@004c9bd0, adpal.pcc/adpal2.pcc), and shows the player name + a damaged/
// destroyed colour state (subsys+0x1c4).
//#######################################################################
class PlayerStatus :
public GraphicGauge
{
public:
// Registered in BTL4MethodDescription[] (btl4grnd.cpp) as "PlayerStatus".
static MethodDescription methodDescription;
static Logical Make(int, Vector2DOf<int>, Entity *, GaugeRenderer *); // @004cae90
PlayerStatus( // @004cb1a8 (13 args; owner folded to 0 in base call)
GaugeRate, ModeMask, L4GaugeRenderer *, int graphics_port_number,
int x, int y, int player_number, const char *font_image,
int color, int okColor, int aliveColor, int deadColor,
const char *identification_string);
~PlayerStatus(); // @004cb28c
Logical TestInstance() const;
void BecameActive(); // @004cb310
void Execute(); // @004cb358
protected:
int port; // @0x90 this[0x24] graphics_port_number
int playerIndex; // @0x94 this[0x25] (player_number-1)
Entity *player; // @0x98 this[0x26] cached (non-owned)
BitMap *nameImage; // @0x9C this[0x27] App name-cache BitMap (was mislabeled Pixmap)
Pixmap *recoloredMech; // @0xA0 this[0x28] CreateMutantPixelmap8 result (owned)
int color; // @0xA4 this[0x29]
int okColor; // @0xA8 this[0x2A]
int aliveColor; // @0xAC this[0x2B]
int deadColor; // @0xB0 this[0x2C]
PlayerStatusMappingGroup
*mappingGroup; // @0xB4 this[0x2D]
NumericDisplay
*scoreDisplay; // @0xB8 this[0x2E]
int dirty; // @0xBC this[0x2F] redraw flag
int previousStatus; // @0xC0 this[0x30] last player+0x1C4 alive/dead
int previousMappingState; // @0xC4 this[0x31]
};
//#######################################################################
// MessageBoard (config keyword "messageBoard") -- the secondary-MFD comm/status
// message ticker. @004cb678 Make / @004cb704 ctor / @004cb788 dtor /
// @004cb7fc BecameActive / @004cb82c Execute. vtable PTR_FUN_0051bddc.
// Blits one cell of the strip bitmap btsmsgs.pcx (message id -> cell (id&3)<<7,
// (id>>2)<<5) + the sender's name bitmap. Was PROSE-ONLY (no bodies/
// methodDescription) -> "messageBoard" was parse-skipped.
//
// DEFERRED / EMPTY (authentic for bring-up): the source mech is never bound
// (SetSource @004cb818 has no caller) AND the per-player status queue
// (StatusMessagePool, btstubs.cpp:62) is a NULL stub, so there are no messages
// -> Execute early-returns on the NULL source (a safe no-op == the empty board).
//#######################################################################
class MessageBoard :
public GraphicGauge
{
public:
static MethodDescription methodDescription; // "messageBoard"
static Logical Make(int, Vector2DOf<int>, Entity *, GaugeRenderer *); // @004cb678
MessageBoard( // @004cb704
GaugeRate, ModeMask, L4GaugeRenderer *, int graphics_port_number,
int x, int y, const char *strip_image, int color,
const char *identification_string);
~MessageBoard(); // @004cb788
Logical TestInstance() const;
void BecameActive(); // @004cb7fc
void SetSource(Entity *); // @004cb818 (binds trackedMech; no caller -> deferred)
void Execute(); // @004cb82c
protected:
Entity *trackedMech; // @0x90 this[0x24] source mech (SetSource; ctor=0) (FIX: was int enabled)
char *stripImage; // @0x94 this[0x25] interned btsmsgs.pcx (held ref)
int color; // @0x98 this[0x26] param (CFG 4th arg = 0)
int previousNameId; // @0x9C this[0x27] BecameActive=-1 (FIX: was previousMessageId)
int previousMessageId; // @0xA0 this[0x28] BecameActive=-2 (FIX: was previousNameId)
friend struct MessageBoardLayoutCheck;
};
// NOTE: CreateMutantPixelmap8 (@004cafac) + ReadStreamString (@004caf50) are
// file-PRIVATE in the binary -- declared static at the top of btl4gau3.cpp,
// not here (the old free-function decl had the wrong signature).
#endif