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>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
c0d2eb0aae
commit
7fc4acb89f
+166
-13
@@ -882,19 +882,172 @@ void
|
|||||||
// MessageBoard @004cb678 Make / @004cb704 ctor
|
// MessageBoard @004cb678 Make / @004cb704 ctor
|
||||||
//###########################################################################
|
//###########################################################################
|
||||||
//
|
//
|
||||||
// @004cb678 -- Make: allocate (0xa4) and construct "MessageBoard" from
|
// Resolve the tracked mech's current HUD status message (msg strip cell + sender
|
||||||
// DAT_0051b43c..0051b508; verify the strip image exists (BitMapCache).
|
// name bitmap). DEFERRED: StatusMessagePool (btstubs.cpp:62) is a NULL stub +
|
||||||
// @004cb704 -- ctor (vtable PTR_FUN_0051bddc). GraphicGauge base; interns +
|
// BTPlayer+0x1dc is never populated, so this returns False today -> the board stays
|
||||||
// AddRefs the message strip bitmap (this[0x25]); sets the port origin; color =
|
// empty. Lives in btplayer.cpp (a complete-BTPlayer TU) to dodge the databinding
|
||||||
// param_10 (this[0x26]).
|
// trap; MUST have a real (stub) definition or the /FORCE link AVs the call.
|
||||||
// @004cb788 -- dtor: release the strip, free the name, base dtor.
|
extern Logical BTResolveMessageBoard(Entity *tracked_mech, int *messageId, BitMap **nameBitmap);
|
||||||
// @004cb7fc -- BecameActive: previousNameId = -1; previousMessageId = -2.
|
|
||||||
// @004cb82c -- Execute. Track the local player's mech (this[0x24]). Read its
|
// Overflow lock only (like SectorDisplay/Sensor: the 2007 GraphicGauge base is not
|
||||||
// controller message ids (mech+0x190 controller, +0xc message id, +0x10 name
|
// byte-identical to 1995, so exact-offset asserts would fail; every read here is via
|
||||||
// id). When the message id changes, blit the corresponding 0x80x0x20 cell of
|
// named members). Binary offsets: trackedMech@0x90 stripImage@0x94 color@0x98
|
||||||
// the strip ((id&3)<<7, (id>>2)<<5) tinted by FUN_004c2f88 (=7); -1 clears.
|
// previousNameId@0x9C previousMessageId@0xA0, sizeof 0xA4 == Make alloc.
|
||||||
// When the name id changes, blit the player's name bitmap (App+0xc8 palette
|
struct MessageBoardLayoutCheck {
|
||||||
// cache lookup) similarly.
|
static_assert(sizeof(MessageBoard) <= 0xA4, "MessageBoard must fit its 0xA4 Make alloc");
|
||||||
|
};
|
||||||
|
|
||||||
|
// CFG shape (L4GAUGE.CFG:4913, port=sec, offset=(113,607)):
|
||||||
|
// messageBoard( E, ModeAlwaysActive, btsmsgs.pcx, 0 )
|
||||||
|
MethodDescription
|
||||||
|
MessageBoard::methodDescription =
|
||||||
|
{
|
||||||
|
"messageBoard",
|
||||||
|
MessageBoard::Make,
|
||||||
|
{
|
||||||
|
{ ParameterDescription::typeRate, NULL }, // p[0] rate (E)
|
||||||
|
{ ParameterDescription::typeModeMask, NULL }, // p[1] mode (ModeAlwaysActive)
|
||||||
|
{ ParameterDescription::typeString, NULL }, // p[2] strip bitmap (btsmsgs.pcx)
|
||||||
|
{ ParameterDescription::typeColor, NULL }, // p[3] draw/erase color (0)
|
||||||
|
PARAMETER_DESCRIPTION_END
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
|
// @004cb678 -- Make. Alloc 0xa4 + construct; presence-check the strip bitmap
|
||||||
|
// (binary returns whether it exists). The `entity` param is ignored by the binary.
|
||||||
|
//
|
||||||
|
Logical
|
||||||
|
MessageBoard::Make(
|
||||||
|
int display_port_index,
|
||||||
|
Vector2DOf<int> position,
|
||||||
|
Entity * /*entity -- ignored by FUN_004cb678*/,
|
||||||
|
GaugeRenderer *gauge_renderer
|
||||||
|
)
|
||||||
|
{
|
||||||
|
ParameterDescription *p = methodDescription.parameterList;
|
||||||
|
|
||||||
|
MessageBoard *gauge = (MessageBoard *)operator new(0xa4); // FUN_00402298(0xa4)
|
||||||
|
if (gauge != NULL)
|
||||||
|
new (gauge) MessageBoard(
|
||||||
|
p[0].data.rate, p[1].data.modeMask,
|
||||||
|
(L4GaugeRenderer *)gauge_renderer,
|
||||||
|
display_port_index, // graphics_port_number
|
||||||
|
position.x, position.y, // offset=(113,607) -> SetOrigin
|
||||||
|
p[2].data.string, // strip image (btsmsgs.pcx)
|
||||||
|
p[3].data.color, // color (0)
|
||||||
|
"MessageBoard");
|
||||||
|
|
||||||
|
L4Warehouse *wh = (L4Warehouse *)gauge_renderer->warehousePointer; // renderer+0x4c
|
||||||
|
BitMap *strip = wh->bitMapBin.Get(p[2].data.string); // FUN_00442aec
|
||||||
|
if (strip == NULL)
|
||||||
|
return False;
|
||||||
|
wh->bitMapBin.Release(p[2].data.string); // FUN_00442c12
|
||||||
|
return True;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// @004cb704 -- ctor (vtable PTR_FUN_0051bddc). GraphicGauge base (owner 0); copy +
|
||||||
|
// hold the strip bitmap; SetOrigin; color = param. trackedMech starts NULL.
|
||||||
|
//
|
||||||
|
MessageBoard::MessageBoard(
|
||||||
|
GaugeRate rate, ModeMask mode_mask, L4GaugeRenderer *renderer_in,
|
||||||
|
int graphics_port_number, int x, int y,
|
||||||
|
const char *strip_image, int color_in, const char *identification_string)
|
||||||
|
: GraphicGauge(rate, mode_mask, renderer_in, 0, // FUN_00444818 (owner 0)
|
||||||
|
graphics_port_number, identification_string)
|
||||||
|
{
|
||||||
|
stripImage = new char[strlen(strip_image) + 1]; // nameCopy (FUN_004700ac)
|
||||||
|
strcpy(stripImage, strip_image);
|
||||||
|
L4Warehouse *wh = (L4Warehouse *)renderer_in->warehousePointer;
|
||||||
|
wh->bitMapBin.Get(stripImage); // FUN_00442aec (hold for life)
|
||||||
|
|
||||||
|
localView.SetOrigin(x, y); // this+0x48 vtbl+0x10
|
||||||
|
color = color_in; // this[0x26]
|
||||||
|
trackedMech = NULL; // this[0x24] (:3864)
|
||||||
|
// previousNameId / previousMessageId set by BecameActive (byte-faithful).
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// @004cb788 -- dtor. Release + free the strip; base dtor runs implicitly.
|
||||||
|
//
|
||||||
|
MessageBoard::~MessageBoard()
|
||||||
|
{
|
||||||
|
L4Warehouse *wh = (L4Warehouse *)renderer->warehousePointer;
|
||||||
|
wh->bitMapBin.Release(stripImage); // FUN_00442c12
|
||||||
|
delete[] stripImage; stripImage = NULL; // FUN_004022e8
|
||||||
|
}
|
||||||
|
|
||||||
|
// @004cb7e4 -- TestInstance: out-of-line forward (so it isn't /FORCE-stubbed).
|
||||||
|
Logical MessageBoard::TestInstance() const { return GraphicGauge::TestInstance(); }
|
||||||
|
|
||||||
|
// @004cb818 -- SetSource: bind the source mech. (No recovered caller -> deferred.)
|
||||||
|
void MessageBoard::SetSource(Entity *mech) { trackedMech = mech; }
|
||||||
|
|
||||||
|
// @004cb7fc -- BecameActive: reset the change-sentinels. NON-inactivating.
|
||||||
|
void
|
||||||
|
MessageBoard::BecameActive()
|
||||||
|
{
|
||||||
|
previousNameId = -1; // this[0x27] @0x9c (:3916)
|
||||||
|
previousMessageId = -2; // this[0x28] @0xa0 (:3917)
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// @004cb82c -- Execute. Draw the message strip cell + sender name when they change.
|
||||||
|
// FAITHFUL: the board only draws while a source mech is bound; the binder (SetSource)
|
||||||
|
// has no recovered caller AND the per-player status feed (StatusMessagePool) is a NULL
|
||||||
|
// stub, so trackedMech stays NULL and Execute is a safe no-op == the DEFERRED empty board.
|
||||||
|
//
|
||||||
|
void
|
||||||
|
MessageBoard::Execute()
|
||||||
|
{
|
||||||
|
if (trackedMech == NULL) // binary gate :3949
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Message record via the safe bridge (NOT raw *(mech+0x190)/+0x1dc offsets).
|
||||||
|
int messageId = -1;
|
||||||
|
BitMap *nameBitmap = NULL;
|
||||||
|
BTResolveMessageBoard(trackedMech, &messageId, &nameBitmap);
|
||||||
|
|
||||||
|
// --- message strip cell (:3960-3981) ---
|
||||||
|
if (messageId != previousMessageId)
|
||||||
|
{
|
||||||
|
previousMessageId = messageId;
|
||||||
|
localView.MoveToAbsolute(0, 0); // vtbl+0x24
|
||||||
|
if (messageId == -1) {
|
||||||
|
localView.SetColor(color); // vtbl+0x18
|
||||||
|
localView.DrawFilledRectangleToRelative(0x80, 0x20); // vtbl+0x4c
|
||||||
|
} else {
|
||||||
|
int cellX = (messageId & 3) << 7; // :3969
|
||||||
|
int cellY = (messageId >> 2) << 5; // :3970
|
||||||
|
localView.SetColor(7); // FUN_004c2f88()==7
|
||||||
|
L4Warehouse *wh = (L4Warehouse *)renderer->warehousePointer;
|
||||||
|
BitMap *strip = wh->bitMapBin.Get(stripImage); // FUN_00442aec
|
||||||
|
if (strip != NULL)
|
||||||
|
localView.DrawBitMapOpaque(color, 0, strip, // vtbl+0x58
|
||||||
|
cellX, cellY, cellX + 0x7f, cellY + 0x1f);
|
||||||
|
wh->bitMapBin.Release(stripImage); // FUN_00442c12
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- sender name cell (:3983-4009). STRUCTURAL SIMPLIFICATION (marked): the binary
|
||||||
|
// tracks the previous name ENTITY pointer at 0x9c and erases it via +0x1e0->App+0xc8;
|
||||||
|
// here previousNameId is a bool (0/1). Functionally identical while DEFERRED (name
|
||||||
|
// always NULL); RESTORE the entity-pointer tracking + erase branch when the feed goes live.
|
||||||
|
int nameState = (nameBitmap != NULL) ? 1 : 0;
|
||||||
|
if (nameState != previousNameId)
|
||||||
|
{
|
||||||
|
localView.MoveToAbsolute(0x80, 0); // vtbl+0x24 (name cell x=128)
|
||||||
|
if (nameBitmap == NULL) {
|
||||||
|
localView.SetColor(color); // vtbl+0x18
|
||||||
|
localView.DrawFilledRectangleToRelative(0x80, 0x20); // vtbl+0x4c
|
||||||
|
} else {
|
||||||
|
localView.SetColor(7);
|
||||||
|
localView.DrawBitMapOpaque(color, 0, nameBitmap, 0, 0, // vtbl+0x58
|
||||||
|
nameBitmap->Data.Size.x - 1, nameBitmap->Data.Size.y - 1);
|
||||||
|
}
|
||||||
|
previousNameId = nameState; // :4009
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// === btl4grnd.cpp begins at @004cbea0 (BTL4GaugeRenderer ctor) -- not this TU.
|
// === btl4grnd.cpp begins at @004cbea0 (BTL4GaugeRenderer ctor) -- not this TU.
|
||||||
|
|||||||
@@ -260,31 +260,40 @@
|
|||||||
|
|
||||||
|
|
||||||
//#######################################################################
|
//#######################################################################
|
||||||
// MessageBoard -- the rank / message bitmap board. @004cb678 Make /
|
// MessageBoard (config keyword "messageBoard") -- the secondary-MFD comm/status
|
||||||
// @004cb704 ctor / @004cb788 dtor / @004cb7fc BecameActive / @004cb82c
|
// message ticker. @004cb678 Make / @004cb704 ctor / @004cb788 dtor /
|
||||||
// Execute. vtable PTR_FUN_0051bddc. Tracks the local player's mech
|
// @004cb7fc BecameActive / @004cb82c Execute. vtable PTR_FUN_0051bddc.
|
||||||
// (App+0x6c -> +0x190 controller +0xc/+0x10 message ids) and blits the
|
// Blits one cell of the strip bitmap btsmsgs.pcx (message id -> cell (id&3)<<7,
|
||||||
// message strip cell ((id&3)<<7, (id>>2)<<5) plus a player name bitmap.
|
// (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 :
|
class MessageBoard :
|
||||||
public GraphicGauge
|
public GraphicGauge
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
static MethodDescription methodDescription; // "messageBoard"
|
||||||
static Logical Make(int, Vector2DOf<int>, Entity *, GaugeRenderer *); // @004cb678
|
static Logical Make(int, Vector2DOf<int>, Entity *, GaugeRenderer *); // @004cb678
|
||||||
MessageBoard( // @004cb704
|
MessageBoard( // @004cb704
|
||||||
GaugeRate, ModeMask, L4GaugeRenderer *, int,
|
GaugeRate, ModeMask, L4GaugeRenderer *, int graphics_port_number,
|
||||||
int x, int y, const char *strip_image, int color,
|
int x, int y, const char *strip_image, int color,
|
||||||
const char *identification_string);
|
const char *identification_string);
|
||||||
~MessageBoard(); // @004cb788
|
~MessageBoard(); // @004cb788
|
||||||
Logical TestInstance() const;
|
Logical TestInstance() const;
|
||||||
void BecameActive(); // @004cb7fc
|
void BecameActive(); // @004cb7fc
|
||||||
|
void SetSource(Entity *); // @004cb818 (binds trackedMech; no caller -> deferred)
|
||||||
void Execute(); // @004cb82c
|
void Execute(); // @004cb82c
|
||||||
protected:
|
protected:
|
||||||
int enabled; // @0x90 this[0x24]
|
Entity *trackedMech; // @0x90 this[0x24] source mech (SetSource; ctor=0) (FIX: was int enabled)
|
||||||
char *stripImage; // @0x94 this[0x25]
|
char *stripImage; // @0x94 this[0x25] interned btsmsgs.pcx (held ref)
|
||||||
int color; // @0x98 this[0x26]
|
int color; // @0x98 this[0x26] param (CFG 4th arg = 0)
|
||||||
int previousMessageId; // @0x9C this[0x27]
|
int previousNameId; // @0x9C this[0x27] BecameActive=-1 (FIX: was previousMessageId)
|
||||||
int previousNameId; // @0xA0 this[0x28]
|
int previousMessageId; // @0xA0 this[0x28] BecameActive=-2 (FIX: was previousNameId)
|
||||||
|
friend struct MessageBoardLayoutCheck;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -149,6 +149,7 @@ MethodDescription
|
|||||||
&GeneratorCluster::methodDescription, // "GeneratorCluster" -- the 4 generator engineering panels (buttons 9-12)
|
&GeneratorCluster::methodDescription, // "GeneratorCluster" -- the 4 generator engineering panels (buttons 9-12)
|
||||||
&SectorDisplay::methodDescription, // "sectorDisplay" -- radar SECTOR X/Z read-out (Secondary overlay)
|
&SectorDisplay::methodDescription, // "sectorDisplay" -- radar SECTOR X/Z read-out (Secondary overlay)
|
||||||
&PrepEngrScreen::methodDescription, // "prepEngr" -- per-Eng-screen static label overlay (12x)
|
&PrepEngrScreen::methodDescription, // "prepEngr" -- per-Eng-screen static label overlay (12x)
|
||||||
|
&MessageBoard::methodDescription, // "messageBoard" -- sec-MFD comm/status ticker (DEFERRED source -> empty)
|
||||||
&BTL4ChainToPrevious
|
&BTL4ChainToPrevious
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1118,6 +1118,21 @@ int BTPilotIsSelected(void *pilot, void *local_player)
|
|||||||
return ((void *)((BTPlayer *)pilot) == (void *)target->GetPlayerLink()) ? 1 : 0;
|
return ((void *)((BTPlayer *)pilot) == (void *)target->GetPlayerLink()) ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MessageBoard data bridge (consumed by btl4gau3.cpp MessageBoard::Execute). DEFERRED:
|
||||||
|
// the per-player status queue (StatusMessagePool, btstubs.cpp:62) is a NULL stub and
|
||||||
|
// BTPlayer+0x1dc is never populated, so there are no messages -> return False (empty
|
||||||
|
// board). Real impl (when StatusMessagePool is wired): owningPlayer (mech+0x190) ->
|
||||||
|
// status record (+0x1dc) -> {messageId@+0xc, nameEntity@+0x10 -> name bitmap} via the
|
||||||
|
// compiled BTPlayer/Player__StatusMessage members (decode AddStatusMessage @0042e580).
|
||||||
|
// A real (stub) definition is REQUIRED or the /FORCE link AVs MessageBoard::Execute's call.
|
||||||
|
class BitMap;
|
||||||
|
Logical BTResolveMessageBoard(Entity * /*tracked_mech*/, int *messageId, BitMap **nameBitmap)
|
||||||
|
{
|
||||||
|
*messageId = -1;
|
||||||
|
*nameBitmap = 0;
|
||||||
|
return False;
|
||||||
|
}
|
||||||
|
|
||||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
// gauge scoring wave: PRODUCERS -- posted by the combat path (mech4.cpp) to feed
|
// gauge scoring wave: PRODUCERS -- posted by the combat path (mech4.cpp) to feed
|
||||||
// the scoreboard. The inflictor is always the viewpoint mech in bring-up, so the
|
// the scoreboard. The inflictor is always the viewpoint mech in bring-up, so the
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 45 KiB |
Reference in New Issue
Block a user