Gauges: the CLOSEOUT wave -- radar pips, Myomers dedup, attribute parity, ledger sweep (tasks #14-#17)

TASK #17 -- AUTHENTIC RADAR SYMBOLOGY (the cross-blip stand-in retired).
The recon proved the 'missing pip raster set' never existed as BT game code:
pips are the ENGINE's L4GaugeImage vector-stroke system (T0 source in tree,
L4GAUIMA.cpp == FUN_0046f0c0 line-for-line), the 'pip table' is L4Warehouse::
gaugeImageBin keyed by Entity::resourceID, and BTL4.RES ships 110 type-0x12
shapes (every mech/vehicle/building/tree).  The six btl4rdr stubs are wired
to the real facilities: contacts draw their authentic model silhouettes with
LOD selection, the target gets the binary's 4px-inflated box highlight, and
player-name labels resolve via Mission::GetSmallNameBitmap (the prebuilt
64x16 egg rasters keyed by the player's bitmapindex).  DECODED: the invented
'VideoObject' was Entity::owningPlayer all along (+0x190; nameID =
playerBitmapIndex@0x1E0, target = BTPlayer::objectiveMech@0x284 -- new
bridge BTPlayerObjectiveMechOf); the 'LabelledEntity' class is Landmark
(cultural.h; label path dormant -- no landmark content ships, no runtime
landmarkID writer).  The L4GREND BT_DEV_GAUGES warehouse guard is removed
(its AV had a different culprit, below); resource type 18 corrected to
GaugeImageStream in decomp-reference (was mislabeled 'ModelList').

TASK #14 -- the Myomers ODR duplicate ELIMINATED: the powersub.cpp/hpp
'Myomers' (classID 0xBC3 -- actually Sensor) is retired whole; it duplicated
?DefaultData@Myomers@@ against the real class (dumpbin-verified) and /FORCE
picked the winner by link order.  The real Myomers (0xBC6) now chains
PoweredSubsystem's handler set (ids 4-8) and publishes its SEVEN binary
attributes (@00511588: SpeedEffect/Current/Recommended/Min/MaxSeekVoltage-
Index/SeekVoltage/OutputVoltage) -- the old empty unchained index starved
the Myomer engineering panel of every resolve.

TASK #16 -- attribute parity: MechWeapon publishes the FULL binary table
@0x511890 (11 entries; ids renumbered to binary truth -- the port aliases
had squatted the binary's DistanceToTarget/TargetWithinRange ids; the
streamed TriggerState 0x13 binding unchanged).  Binary names resolved two
TODO members: pipState -> estimatedReadyTime (attr 0x1A), and the EXT-model
flag is the binary's RearFiring (0x1B).  ThermalSight LightState published.
HUD (offset conflicts) + missile-side tables (id encoding suspect)
documented for a re-dump instead of publishing blind.
  THE CRASH THIS EXPOSED [T2, cdb-verified]: gotcha #11's dense-table gap
is a LATENT AV, not a guaranteed one -- the old table's 0x0D..0x12 gap
(task #5) survived on heap luck; the renumber reshuffled allocations and
Find() AV'd on a garbage entryName in WeaponCluster's PercentDone resolve.
Fixed with five named PAD entries (the mech.cpp attrPad idiom) + a
static_assert locking the pad base to PoweredSubsystem::NextAttributeID.
Gotcha #11 amended with the proof.

TASK #15 -- stale-ledger sweep: GAUGE_COMPOSITE ('composite not yet built',
Reservoir shadow, PlayerStatus/vehicleSubSystems 'remaining', valve-dormant-
until-0xBD3, sensor guard, the superseded 'Heat MFD near-static' reframe --
all banner-corrected), gauges-hud frontmatter, L4VB16 + powersub comments.

Verified live: 50/50 config attribute bindings resolve, 0 NULLs, 0 parse
skips, mech spawns and simulates 31/31 subsystems, no cross-blip fallbacks,
the pip cache fills through entity registration without the old guard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-12 00:57:45 -05:00
co-authored by Claude Fable 5
parent 80cadc98c7
commit dd27238ceb
16 changed files with 309 additions and 384 deletions
+81 -77
View File
@@ -63,6 +63,13 @@
//
#include <bt.hpp>
// task #17 (radar symbology): the "pip" system is the ENGINE's L4GaugeImage
// (vector-stroke shapes, resource type 0x12) cached in L4Warehouse::
// gaugeImageBin; name labels are the Mission's prebuilt egg bitmaps.
#include <L4GAUIMA.hpp> // L4GaugeImage -- the pip vector shapes (fwd shim)
#include <L4WRHOUS.hpp> // L4Warehouse::gaugeImageBin -- the pip table
#include <MISSION.hpp> // Mission::GetSmallNameBitmap -- the name labels
#include <PLAYER.hpp> // Player::playerBitmapIndex
#pragma hdrstop
#if !defined(BTL4RDR_HPP)
@@ -104,21 +111,20 @@ static int
//###########################################################################
//
// A tracked entity's "video object": carries the name-bitmap id used for the
// floating label and the entity's current weapons target. (BT engine concept;
// absent from the WinTesla Entity.)
// task #17 DECODED: the "video object" at *(entity+0x190) IS Entity::
// owningPlayer (ENTITY.h:589; resourceID is the adjacent +0x1BC) -- the radar
// reads its playerBitmapIndex (+0x1E0, the egg 'bitmapindex' key) for the
// name label and BTPlayer::objectiveMech (+0x284) for the target box. The
// old invented VideoObject class is retired.
//
class VideoObject
{
public:
int nameID;
Entity *target;
};
// The "pip" is the ENGINE's L4GaugeImage (L4GAUIMA.h -- FUN_0046f0c0 IS
// L4GaugeImage::Draw line-for-line), and the "pip table" is L4Warehouse::
// gaugeImageBin (WarehouseBinOf<L4GaugeImage>, warehouse+0x58) reached via
// GaugeRenderer::warehousePointer (+0x4C) -- keyed by Entity::resourceID
// (BTL4.RES ships 110 type-0x12 vector shapes: every mech/vehicle/building/
// tree). Thin facades keep the recovered draw-loop shape:
//
// One radar symbol ("pip") + the per-model lookup table that owns them.
//
class Pip
class Pip // == L4GaugeImage
{
public:
void
@@ -129,14 +135,25 @@ public:
int color,
AffineMatrix &transform,
int box_color
);
)
{
((L4GaugeImage *)this)->Draw(lod_index, meters_per_pixel,
view, color, transform, box_color);
}
};
class PipTable
class PipTable // == L4Warehouse
{
public:
Pip *
LookUpPip(int model_class_ID, int variant);
LookUpPip(ResourceDescription::ResourceID entity_resource_ID)
{
// FUN_004688ce(warehouse+0x58, entity+0x1BC, 0) -- the refcounting
// Get, exactly as the binary's per-frame loop calls it (the
// mission-end Purge reclaims; one vector shape per model).
return (Pip *)((L4Warehouse *)this)->gaugeImageBin.Get(
entity_resource_ID, False);
}
};
//
@@ -165,11 +182,10 @@ static PipTable *
GetPipTable(GaugeRenderer *renderer);
//
// Per-entity video object, name-bitmap cache, and the "labelled static entity"
// test (nav beacons etc. carry a name bitmap).
// Name-label + target resolution (task #17): the label bitmap comes from the
// owning Player's bitmapindex through the Mission's SMALL prebuilt-raster
// chain; the target from the owning BTPlayer's objectiveMech (bridge).
//
static VideoObject *
GetVideoObject(Entity *entity);
static BitMap *
LookUpNameBitmap(int name_ID);
static int
@@ -222,18 +238,17 @@ void
return;
}
VideoObject *video_object = GetVideoObject(entity); // *(entity+400)
if (video_object == NULL)
Player *owning_player = entity->GetOwningPlayer(); // *(entity+0x190)
if (owning_player == NULL)
{
nameBitmap = NULL;
nameBitmap = NULL; // unowned (dummy/effect): no label
}
else
{
//
// Resolve the entity's name id through the global name-bitmap cache.
//
int nameID = video_object->nameID; // *(vobj+0x1E0)
nameBitmap = LookUpNameBitmap(nameID); // cache vtbl+0x0C
// player+0x1E0 playerBitmapIndex -> the Mission's SMALL name-raster
// chain (prebuilt 64x16 egg bitmaps, loaded 1-based by
// Mission::LoadNameBitmaps).
nameBitmap = LookUpNameBitmap(owning_player->playerBitmapIndex);
}
//
@@ -790,7 +805,7 @@ void
}
Pip *pip = (pips != NULL)
? pips->LookUpPip(entity->GetClassID(), 0) // FUN_004688ce(pips+0x58,entity+0x1bc,0)
? pips->LookUpPip(entity->GetResourceID()) // FUN_004688ce(pips+0x58,entity+0x1bc,0)
: NULL;
if (pip != NULL)
{
@@ -875,7 +890,7 @@ void
}
Pip *pip = (pips != NULL)
? pips->LookUpPip(entity->GetClassID(), 0)
? pips->LookUpPip(entity->GetResourceID())
: NULL;
if (pip != NULL)
{
@@ -891,12 +906,9 @@ void
}
else if (entity->GetClassID() == 0xBB9) // MechClassID -- radar contacts are mechs
{
// BRING-UP: the authentic pip raster set (pips table) is not present
// in this engine build, so draw a simple cross blip at the contact's
// projected radar position (the target flashes in boxColor). Filtered
// to mechs so the ~300 dynamic world entities (effects/props) the grid
// carries don't clutter the display -- the real pip table would key the
// symbol off the model class, which is the proper follow-up.
// FALLBACK (kept, task #17): a mech whose model has no type-0x12
// pip resource (all shipped mechs HAVE one -- this covers dev
// content) still draws the cross blip at the projected position.
//
// Project delta = (entity - viewpoint) directly to radar pixels: the
// worldToView camera matrix bakes a scale that is wrong for a point
@@ -1000,28 +1012,9 @@ void
//###########################################################################
//###########################################################################
//
// FUN_0046f0c0 / FUN_004688ce -- radar pip symbol + per-model table.
//
void
Pip::Draw(
Scalar /*lod_index*/,
Scalar /*meters_per_pixel*/,
GraphicsView * /*view*/,
int /*color*/,
AffineMatrix & /*transform*/,
int /*box_color*/
)
{
// BEST-EFFORT: the pip raster set lives in the BT renderer's pip table,
// which is not present in this engine build.
}
Pip *
PipTable::LookUpPip(int /*model_class_ID*/, int /*variant*/)
{
return NULL;
}
// (task #17: the Pip::Draw / PipTable::LookUpPip stub bodies that lived here
// are retired -- both are inline facades over the engine's L4GaugeImage /
// L4Warehouse::gaugeImageBin now, see the class declarations above.)
//
// FUN_00417ab4 -- the gauge renderer's "operator entity" (the mech whose
@@ -1040,34 +1033,39 @@ static Entity *
}
//
// renderer+0x4C -- the BT pip table. Not present on the WinTesla renderer.
// renderer+0x4C -- GaugeRenderer::warehousePointer: the L4Warehouse whose
// gaugeImageBin (+0x58) is the pip table (task #17; the "not present on
// WinTesla" note was wrong -- the facility is engine T0, L4WRHOUS.h).
//
static PipTable *
GetPipTable(GaugeRenderer * /*renderer*/)
GetPipTable(GaugeRenderer *renderer)
{
return NULL;
return (renderer != NULL) ? (PipTable *)renderer->warehousePointer : NULL;
}
//
// *(entity+400) -- the entity's video object (name id + current target).
//
static VideoObject *
GetVideoObject(Entity * /*entity*/)
{
return NULL;
}
//
// The global name-bitmap cache (DAT_004efc94 App singleton, +0xC8 graphics).
// The "global name-bitmap cache" DECODED (task #17): DAT_004efc94+0xC8 is
// Application::currentMission, and the cache is the Mission's SMALL
// name-bitmap VChain (prebuilt 64x16 egg rasters keyed 1-based by the egg
// 'bitmapindex'). All T0 (MISSION.h / APP.h).
//
static BitMap *
LookUpNameBitmap(int /*name_ID*/)
LookUpNameBitmap(int name_ID)
{
return NULL;
Mission *mission = (application != NULL)
? application->GetCurrentMission() : NULL;
if (mission == NULL || name_ID <= 0)
{
return NULL;
}
return mission->GetSmallNameBitmap(name_ID);
}
//
// entity+0x21C -- a labelled entity's name id.
// entity+0x21C -- Landmark::landmarkID (cultural.h; the static loop's label
// key into the LARGE chain). DORMANT in shipped content: no runtime writer
// exists (the id was baked by the 1995 map tool) and no [landmarks] section
// exists in any egg -- kept faithful-but-inert [T1].
//
static int
GetNameID(Entity * /*entity*/)
@@ -1076,7 +1074,9 @@ static int
}
//
// FUN_0041a1a4 -- IsDerivedFrom(LabelledEntityClass).
// FUN_0041a1a4 vs 0x4e70c4 -- IsDerivedFrom("Landmark") (task #17 decode:
// the "LabelledEntityClass" IS the Landmark class, parent CulturalIcon).
// Dormant with GetNameID above (no landmark content ships).
//
static Logical
IsLabelledEntity(Entity * /*entity*/)
@@ -1090,8 +1090,12 @@ static Logical
static Entity *
GetTarget(Entity *owner)
{
VideoObject *video_object = (owner != NULL) ? GetVideoObject(owner) : NULL;
return (video_object != NULL) ? video_object->target : NULL;
// *(*(owner+0x190)+0x284) -- the owning BTPlayer's objectiveMech (the
// designated target; complete-type bridge, btplayer.cpp).
extern Entity *BTPlayerObjectiveMechOf(void *player);
Player *owning_player = (owner != NULL) ? owner->GetOwningPlayer() : NULL;
return (owning_player != NULL)
? BTPlayerObjectiveMechOf(owning_player) : NULL;
}
//