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;
}
//
+16
View File
@@ -1492,3 +1492,19 @@ int BTPlayerRoleLocksAdvanced(void *owner_mech)
return 0; // dev-permissive [T3]
return (player->roleClassIndex == 0) ? 1 : 0; // player+0x274 == 0
}
//#############################################################################
// BTPlayerObjectiveMechOf -- complete-type bridge (task #17)
//
// The radar map's target resolve: *(*(owner+0x190)+0x284) = the owning
// BTPlayer's objectiveMech (the designated target; the same slot the
// power-routing valve gate reads past). btl4rdr.cpp can't include
// btplayer.hpp (TU type collisions), so it calls through this bridge.
//
Entity *BTPlayerObjectiveMechOf(void *player)
{
if (player == 0)
return 0;
return (Entity *)((BTPlayer *)player)->GetObjectiveMech(); // @0x284
}
+38 -7
View File
@@ -108,15 +108,46 @@ Simulation::AttributeIndexSet MechWeapon::AttributeIndex; // legacy empty member
// gauge data-binding wave: OutputVoltage/PercentDone -> rechargeLevel (1.0 ==
// ready; ComputeOutputVoltage/the charge state machine drive it, so both animate
// as the weapon recharges). Chained to PoweredSubsystem's dense index.
// task #16: the ID GAP between our parent chain (ends 0x0D) and the pinned
// binary ids MUST be padded -- see the enum note. Locked so a future parent
// publish shows up here instead of silently double-assigning ids:
static_assert((int)PoweredSubsystem::NextAttributeID
== (int)MechWeapon::MechWeaponPadFirstAttributeID,
"MechWeapon pad base != PoweredSubsystem::NextAttributeID -- re-count the pads");
const MechWeapon::IndexEntry
MechWeapon::AttributePointers[]=
{
// The authentic entry (binary table @0x511890): the streamed controls maps
// bind the pod fire buttons directly onto this Scalar -- fireImpulse IS the
// binary's "TriggerState" (the edge-detected fire channel destination).
ATTRIBUTE_ENTRY(MechWeapon, TriggerState, fireImpulse), // @0x31C id 0x13
ATTRIBUTE_ENTRY(MechWeapon, OutputVoltage, rechargeLevel), // @0x320 (charge/ready level)
ATTRIBUTE_ENTRY(MechWeapon, PercentDone, rechargeLevel) // @0x320 (recharge dial fraction)
// task #16: the FULL binary table @0x511890 (eleven entries) + the
// port-extra OutputVoltage alias. Every member offset matches the
// binary's (layout static_assert-locked); TriggerState stays the streamed
// fire-button binding (id 0x13 PINNED).
//
// The five PADS fill our chain-vs-binary id gap (0x0D..0x11) with valid,
// named, never-bound entries (the binary's own ids here are the powered/
// aux-screen family we don't publish); target = rechargeLevel, harmless.
{ (int)MechWeapon::MechWeaponPadFirstAttributeID + 0, "MechWeaponPad0D",
(Simulation::AttributePointer)&MechWeapon::rechargeLevel },
{ (int)MechWeapon::MechWeaponPadFirstAttributeID + 1, "MechWeaponPad0E",
(Simulation::AttributePointer)&MechWeapon::rechargeLevel },
{ (int)MechWeapon::MechWeaponPadFirstAttributeID + 2, "MechWeaponPad0F",
(Simulation::AttributePointer)&MechWeapon::rechargeLevel },
{ (int)MechWeapon::MechWeaponPadFirstAttributeID + 3, "MechWeaponPad10",
(Simulation::AttributePointer)&MechWeapon::rechargeLevel },
{ (int)MechWeapon::MechWeaponPadFirstAttributeID + 4, "MechWeaponPad11",
(Simulation::AttributePointer)&MechWeapon::rechargeLevel },
ATTRIBUTE_ENTRY(MechWeapon, PercentDone, rechargeLevel), // 0x12 @0x320
ATTRIBUTE_ENTRY(MechWeapon, TriggerState, fireImpulse), // 0x13 @0x31C
ATTRIBUTE_ENTRY(MechWeapon, DistanceToTarget, rangeToTarget), // 0x14 @0x324
ATTRIBUTE_ENTRY(MechWeapon, TargetWithinRange, targetWithinRange), // 0x15 @0x34C
ATTRIBUTE_ENTRY(MechWeapon, WeaponRange, effectiveRange), // 0x16 @0x328 (heat-degraded)
ATTRIBUTE_ENTRY(MechWeapon, PipPosition, pipPosition), // 0x17 @0x338
ATTRIBUTE_ENTRY(MechWeapon, PipColor, pipColor), // 0x18 @0x33C
ATTRIBUTE_ENTRY(MechWeapon, PipExtendedRange, pipExtendedRange), // 0x19 @0x348
ATTRIBUTE_ENTRY(MechWeapon, EstimatedReadyTime, estimatedReadyTime),// 0x1A @0x330
ATTRIBUTE_ENTRY(MechWeapon, RearFiring, usesExternalModel), // 0x1B @0x334
ATTRIBUTE_ENTRY(MechWeapon, WeaponState, weaponAlarm), // 0x1C @0x350
ATTRIBUTE_ENTRY(MechWeapon, OutputVoltage, rechargeLevel) // 0x1D (port alias)
};
MechWeapon::AttributeIndexSet&
@@ -303,7 +334,7 @@ MechWeapon::MechWeapon(
previousFireImpulse = 0.0f; // 0x3A4
recoil = 0.0f; // 0x3E8
useConfiguredPip = (usesExternalModel == 0); // 0x3E0
pipState = 0; // 0x330
estimatedReadyTime = 0; // 0x330 (binary attr "EstimatedReadyTime")
Check_Fpu();
}
+37 -5
View File
@@ -168,9 +168,38 @@ class CockpitHud;
// a few dense-index slots (Build sizes by max id) -- harmless.
// OutputVoltage/PercentDone are port gauge additions
// (name-resolved) renumbered after it.
TriggerStateAttributeID = 0x13,
OutputVoltageAttributeID, // 0x14
PercentDoneAttributeID, // 0x15
// task #16 (attribute parity): the FULL binary table @0x511890,
// ids renumbered to binary truth. PercentDone was 0x15 and the
// port-extra OutputVoltage alias squatted 0x14 -- both collided
// with the binary's DistanceToTarget/TargetWithinRange ids.
// (Numeric ids matter to the streamed type-6 maps -- which bind
// ONLY TriggerState 0x13, unchanged -- and to binary parity;
// gauge configs bind by NAME.)
//
// THE ID GAP MUST BE PADDED (task #16 crash): our reconstruction's
// parent chain ends at PoweredSubsystem::NextAttributeID == 0x0D
// (the binary's reaches 0x12 -- we publish fewer base attrs), and
// AttributeIndexSet::Build leaves the slots between UNINITIALIZED
// -- Find() strcmps EVERY slot, so a gap slot's garbage entryName
// is a latent AV (gotcha #11; the old pinned table survived on
// heap luck -- the renumber reshuffled allocations and it fired
// live in WeaponCluster's "PercentDone" resolve). Pads below fill
// ids 0x0D..0x11 (the mech.cpp attrPad idiom).
MechWeaponPadFirstAttributeID = 0x0D, // == PoweredSubsystem::NextAttributeID (locked below)
PercentDoneAttributeID = 0x12, // @0x320 rechargeLevel
TriggerStateAttributeID, // 0x13 @0x31C (PINNED -- the fire-button binding)
DistanceToTargetAttributeID, // 0x14 @0x324 rangeToTarget
TargetWithinRangeAttributeID, // 0x15 @0x34C
WeaponRangeAttributeID, // 0x16 @0x328 -- the HEAT-DEGRADED effectiveRange,
// not the configured max (binary truth)
PipPositionAttributeID, // 0x17 @0x338
PipColorAttributeID, // 0x18 @0x33C
PipExtendedRangeAttributeID, // 0x19 @0x348
EstimatedReadyTimeAttributeID, // 0x1A @0x330
RearFiringAttributeID, // 0x1B @0x334
WeaponStateAttributeID, // 0x1C @0x350 weaponAlarm
OutputVoltageAttributeID, // 0x1D PORT-EXTRA alias -> rechargeLevel (the
// engineering clusters bind it by name)
NextAttributeID
};
private:
@@ -361,8 +390,11 @@ class CockpitHud;
Scalar rangeToTarget; // @0x324 distance to current target (-1 if none)
Scalar effectiveRange; // @0x328 (1 - heatLoad) * weaponRange (heat-degraded)
Scalar weaponRange; // @0x32C resource WeaponRange (configured max)
int pipState; // @0x330 reset 0 (TODO: name -- pip/active state)
Logical usesExternalModel; // @0x334 resource model-name compare flag (best-effort)
int estimatedReadyTime; // @0x330 reset 0 (binary attr 0x1A "EstimatedReadyTime"
// names this slot -- resolves the old pipState TODO)
Logical usesExternalModel; // @0x334 the "EXT" model-name marker; the binary attr
// 0x1B names this slot "RearFiring" (an EXT/rear-mount
// weapon fires backward)
int pipPosition; // @0x338 resource PipPosition
RGBColor pipColor; // @0x33C resource PipColor (3 floats, init -1,-1,-1)
int pipExtendedRange; // @0x348 resource PipExtendedRange
+36 -6
View File
@@ -75,17 +75,47 @@ Derivation
"Myomers"
);
Receiver::MessageHandlerSet
Myomers::MessageHandlers;
// task #14: the REAL shared data. The old empty static
// MessageHandlers/AttributeIndex meant (a) no inherited message handlers
// (the powered ids 4-8 among them), (b) an EMPTY UNCHAINED attribute index --
// even CurrentTemperature/InputVoltage resolved NULL on Myomers instances, so
// the Myomer engineering panel's cluster children were silently blank. Both
// now chain PoweredSubsystem's sets, and the SEVEN binary attributes
// (IndexEntry table @00511588) are published. Function-local statics per the
// static-init-order rule (reconstruction-gotchas #9).
Receiver::MessageHandlerSet&
Myomers::GetMessageHandlers()
{
static Receiver::MessageHandlerSet messageHandlers(
PoweredSubsystem::GetMessageHandlers()); // copy-inherit (ids 4-8)
return messageHandlers;
}
Myomers::AttributeIndexSet
Myomers::AttributeIndex;
Myomers::AttributeIndexSet&
Myomers::GetAttributeIndex()
{
static const Myomers::IndexEntry entries[]=
{
ATTRIBUTE_ENTRY(Myomers, SpeedEffect, speedEffect), // 0x12 @0x31C
ATTRIBUTE_ENTRY(Myomers, CurrentSeekVoltageIndex, currentSeekVoltageIndex), // 0x13 @0x320
ATTRIBUTE_ENTRY(Myomers, RecommendedSeekVoltageIndex, recommendedSeekVoltageIndex), // 0x14 @0x324
ATTRIBUTE_ENTRY(Myomers, MinSeekVoltageIndex, minSeekVoltageIndex), // 0x15 @0x328
ATTRIBUTE_ENTRY(Myomers, MaxSeekVoltageIndex, maxSeekVoltageIndex), // 0x16 @0x32C
ATTRIBUTE_ENTRY(Myomers, SeekVoltage, seekVoltage), // 0x17 @0x330 (array base)
ATTRIBUTE_ENTRY(Myomers, OutputVoltage, outputVoltage) // 0x18 @0x344
};
static Myomers::AttributeIndexSet attributeIndex(
ELEMENTS(entries), entries,
PoweredSubsystem::GetAttributeIndex()
);
return attributeIndex;
}
Myomers::SharedData
Myomers::DefaultData(
&Myomers::ClassDerivations,
Myomers::MessageHandlers,
Myomers::AttributeIndex,
Myomers::GetMessageHandlers(),
Myomers::GetAttributeIndex(),
Myomers::StateCount
);
+6 -3
View File
@@ -111,7 +111,11 @@ class Mech;
public:
static Derivation ClassDerivations;
static SharedData DefaultData; // @0051154c
static Receiver::MessageHandlerSet MessageHandlers;
// task #14: accessor idiom (function-local statics) -- the old empty
// static MessageHandlers/AttributeIndex members starved the class of
// BOTH its own 7 binary attrs AND every inherited resolve (temps,
// InputVoltage) -> the Myomer engineering panel's children fed NULL.
static Receiver::MessageHandlerSet& GetMessageHandlers();
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Streamed model resource.
@@ -162,9 +166,8 @@ class Mech;
};
private:
static const IndexEntry AttributePointers[]; // @00511588
protected:
static AttributeIndexSet AttributeIndex;
static AttributeIndexSet& GetAttributeIndex(); // @00511588 table, task #14
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Test Class Support
+14 -163
View File
@@ -849,171 +849,19 @@ int
//###########################################################################
//###########################################################################
// Myomers
// (Myomers mislabel -- RETIRED, task #14)
//###########################################################################
//###########################################################################
//#############################################################################
// Shared Data Support
//
Myomers::SharedData
Myomers::DefaultData(
Myomers::GetClassDerivations(),
Myomers::GetMessageHandlers(),
Myomers::GetAttributeIndex(),
Myomers::StateCount
);
Derivation*
Myomers::GetClassDerivations()
{
static Derivation classDerivations(PoweredSubsystem::GetClassDerivations(), "Myomers");
return &classDerivations;
}
// The "Myomers" block that lived here (ctor @4b1d18, classID 0xBC3,
// outputVoltage/powered/voltageAvailable) was a MISLABEL of Sensor (real
// reconstruction: sensor.cpp). Its Myomers::DefaultData / ctor / dtor /
// TestInstance / MyomersSimulation / ResetToInitialState / HandleMessage /
// CreateStreamedSubsystem definitions ODR-DUPLICATED the REAL Myomers
// (myomers.cpp, classID 0xBC6) -- /FORCE picked the winner by link order,
// so which SharedData live Myomers instances got was link-order-dependent
// (the same archetype as the fixed Condenser stub dedup). Retired whole.
//
// @004b1d18 -- master myomer bundle registers a separate Performance and a
// status-flag bit (0x8); copies skip the live simulation.
//
Myomers::Myomers(
Mech *owner,
int subsystem_ID,
SubsystemResource *subsystem_resource,
SharedData &shared_data
):
PoweredSubsystem(owner, subsystem_ID, subsystem_resource, /*shared_data=*/DefaultData) // FUN_004b0f74(..,&DAT_0050fa1c,0,0)
{
outputVoltage = 1.0f; // this[0xc7]
powered = 0; // this[0xc8]
voltageAvailable = 0; // this[0xc9]
simulationFlags |= 0x8; // this[0xa] |= 8
if ((subsystem_resource->subsystemFlags & SegmentCopyMask) != 4) // (flags & 0xC) != 4
{
SetPerformance((Performance)&Myomers::MyomersSimulation); // PTR @0050fa94
}
}
//
// @004b1d90
//
Myomers::~Myomers()
{
Check(this);
BT_ClearMyomers((Mech *)owner); // *(this[0x34]+0x374) = 0
Check_Fpu();
}
Logical Myomers::TestClass(Mech &) { return True; }
Logical Myomers::TestInstance() const // @004b1e18
{
return IsDerivedFrom(*GetClassDerivations()); // FUN_0041a1a4(..,0x50fa2c)
}
//
// @004b1c4c -- compute the actuator drive voltage available to the locomotion
// model from the powering generator, throttled by heat damage and gated by
// the electrical state.
//
void
Myomers::MyomersSimulation(Scalar time_slice)
{
PoweredSubsystem::PoweredSubsystemSimulation(time_slice); // FUN_004b0bd0
Generator *source = (Generator *)voltageSource.Resolve(); // this[0x38] linked source
outputVoltage = RatedVoltageRef - source->MeasuredVoltage(); // 1.0 - *(source+0x158)
if (simulationState == 1) // this[0x10] @0x40
{
outputVoltage = 0.0f;
powered = 0;
}
else
{
powered = 1;
}
if (electricalStateAlarm.GetLevel() == Ready) // this[0x9e] == 4
{
voltageAvailable = 0;
}
else
{
voltageAvailable = 1;
outputVoltage = 0.0f;
}
//
// Heat damage (HeatSink heat-state @0x184) throttles the drive.
//
switch (heatAlarm.GetLevel()) // this+0x184 (heat-state inside heatAlarm@0x170+0x14)
{
case HeatSink::NormalHeat: // 0
powered = 1;
break;
case HeatSink::DegradationHeat: // 1
outputVoltage *= DegradedDriveScale; // *= 0.5
powered = 1;
break;
case HeatSink::FailureHeat: // 2
outputVoltage = 0.0f;
powered = 0;
break;
}
}
//
// @004b1c18 -- reset clears the myomer output state, then chains to the base.
//
void
Myomers::ResetToInitialState(Logical powered_flag)
{
if (powered_flag)
{
outputVoltage = 1.0f; // this[0xc7]
powered = 0; // this[0xc8]
voltageAvailable = 0; // this[0xc9]
}
PoweredSubsystem::ResetToInitialState(powered_flag); // FUN_004b0e6c
}
//
// @004b1bfc -- straight delegation to the base.
//
Logical
Myomers::HandleMessage(int message)
{
return PoweredSubsystem::HandleMessage(message); // FUN_004b0efc
}
//
// @004b1dcc -- no extra resource fields; just tags the record as Myomers.
//
int
Myomers::CreateStreamedSubsystem(
NotationFile *model_file,
const char *model_name,
const char *subsystem_name,
SubsystemResource *subsystem_resource,
NotationFile *subsystem_file,
const ResourceDirectories *directories,
int passes
)
{
if (
!PoweredSubsystem::CreateStreamedSubsystem( // FUN_004b13ac
model_file, model_name, subsystem_name,
subsystem_resource, subsystem_file, directories, passes
)
)
{
return False;
}
subsystem_resource->subsystemModelSize = 0x190;
subsystem_resource->classID = RegisteredClass::MyomersClassID; // 0x0bc3, resource+0x20
return True;
}
//###########################################################################
//###########################################################################
@@ -1509,8 +1357,11 @@ Subsystem *CreatePoweredSubsystem(Mech *owner, int id, void *seg)
// Bridge for the vehicleSubSystems gauge factory (btl4gau2.cpp). The engineering-
// screen assignment lives on PoweredSubsystem (auxScreenNumber / auxScreenPlacement
// / auxScreenLabel, resource +0x104/+0x108/+0x10C). Reading it from btl4gau2.cpp
// via a raw offset (sub+0x1dc) is wrong because the reconstructed heat-leaf branch
// is not byte-exact -- so the gauge factory calls this bridge, which casts through
// via a raw offset (sub+0x1dc) is wrong because raw offsets on our compiled
// objects are the databinding trap (2026-07-12: the "heat-leaf not byte-exact"
// wording is stale -- the leaf IS byte-exact since the sensor re-base,
// sensor.cpp:407-413 -- but the bridge stays: named members are the rule)
// -- so the gauge factory calls this bridge, which casts through
// the real PoweredSubsystem type and returns the NAMED fields. Returns False for
// non-PoweredSubsystem-derived subsystems (== the FUN_0041a1a4 / 0x50f4bc type
// filter the vehicleSubSystems Make applies before dispatch).
+7 -75
View File
@@ -296,81 +296,13 @@ class Generator;
static_assert(offsetof(PoweredSubsystem, thermalResistivityCoefficient)== 0x30C, "PoweredSubsystem::thermalResistivityCoefficient @0x30C");
static_assert(sizeof(PoweredSubsystem) == 0x31C, "sizeof(PoweredSubsystem) 0x31C");
};
//###########################################################################
//############################# Myomers #################################
//###########################################################################
//
// The mech's artificial-muscle bundle: a PoweredSubsystem whose output
// voltage (and hence the actuator drive available to the locomotion model)
// tracks the powering generator and is throttled back by heat damage.
//
// vtable @0050fb0c, ctor @004b1d18, dtor @004b1d90, shared data @0050fa1c.
// RegisteredClass::MyomersClassID == 0x0bc3.
//
class Myomers:
public PoweredSubsystem
{
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Shared Data Support
//
public:
static Derivation *GetClassDerivations(); // @0050fa2c
static SharedData DefaultData; // @0050fa1c
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Test Class Support
//
public:
static Logical
TestClass(Mech&);
Logical
TestInstance() const; // @004b1e18
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Construction and Destruction
//
public:
typedef PoweredSubsystem::SubsystemResource SubsystemResource;
Myomers(
Mech *owner,
int subsystem_ID,
SubsystemResource *subsystem_resource,
SharedData &shared_data = DefaultData
); // @004b1d18
~Myomers(); // @004b1d90
static int
CreateStreamedSubsystem( // @004b1dcc
NotationFile *model_file,
const char *model_name,
const char *subsystem_name,
SubsystemResource *subsystem_resource,
NotationFile *subsystem_file,
const ResourceDirectories *directories,
int passes = 1
);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Simulation / overrides
//
public:
void
MyomersSimulation(Scalar time_slice); // @004b1c4c (Performance, PTR @0050fa94)
void
ResetToInitialState(Logical powered = True); // slot 10, @004b1c18
Logical
HandleMessage(int message); // slot 9, @004b1bfc (-> PoweredSubsystem)
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Local data.
//
protected:
Scalar outputVoltage; // @0x31C init 1.0f -- drive available to the locomotion model
int powered; // @0x320 init 0 -- True while the generator is on-line
int voltageAvailable; // @0x324 init 0 -- True while electricalState == Ready
};
// (task #14: the "Myomers" class that lived here -- vtable 0050fb0c / ctor
// @4b1d18 / classID 0xBC3, members outputVoltage/powered/voltageAvailable --
// was a MISLABEL of **Sensor** (the real reconstruction is sensor.cpp/hpp;
// the real Myomers, classID 0xBC6, is myomers.cpp/hpp). Its definitions
// ODR-DUPLICATED ?DefaultData@Myomers@@ etc. against the real class, and
// /FORCE silently picked the winner by link order -- the real Myomers could
// receive this block's SharedData or vice versa. RETIRED whole.)
//###########################################################################
//############################# Generator ##############################
+17 -3
View File
@@ -52,14 +52,28 @@ Derivation
Receiver::MessageHandlerSet
ThermalSight::MessageHandlers;
ThermalSight::AttributeIndexSet
ThermalSight::AttributeIndex;
// task #16: publish the binary attr table @00511270 -- one entry,
// {LightState (id 4) -> thermalActive @0x1D8} (offset encoded 0x1D9 = 0x1D8|1).
// Function-local statics per the static-init-order rule.
ThermalSight::AttributeIndexSet&
ThermalSight::GetAttributeIndex()
{
static const ThermalSight::IndexEntry entries[]=
{
ATTRIBUTE_ENTRY(ThermalSight, LightState, thermalActive) // id 4 @0x1D8
};
static ThermalSight::AttributeIndexSet attributeIndex(
ELEMENTS(entries), entries,
PowerWatcher::GetAttributeIndex()
);
return attributeIndex;
}
ThermalSight::SharedData
ThermalSight::DefaultData( // @00511228
&ThermalSight::ClassDerivations,
ThermalSight::MessageHandlers,
ThermalSight::AttributeIndex,
ThermalSight::GetAttributeIndex(),
ThermalSight::StateCount
);
+1 -1
View File
@@ -109,7 +109,7 @@
private:
static const IndexEntry AttributePointers[]; // @00511270
protected:
static AttributeIndexSet AttributeIndex;
static AttributeIndexSet& GetAttributeIndex(); // @00511270 table (task #16)
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Local data.