Gauges: fix blank per-weapon loop/generator lamps (4A/1B/5D boxes)
The SubsystemCluster per-weapon MFD panels render two image-strip lamps in the TEMP/STATUS area: the cooling-loop number (btploop, 1..6) and the generator letter (btpbus, A..D). Both drew as completely empty boxes. Three stacked databinding bugs, all now fixed: 1. Color drop (btl4gau2.cpp): AnimatedSubsystemLamp/AnimatedSourceLamp ctors dropped the bg/fg color params the binary passes (bg=0xff,fg=0, same as the neighboring temp bar) and hardcoded 0,0 -> OneOfSeveral:: Execute did SetColor(0)+DrawBitMapOpaque(0), i.e. black-on-black. Restored 0xff,0 (verified vs @004c70a4 / caller SubsystemCluster @004c8140). 2. Shadow-field trap (btl4gau2.hpp, gotcha #2): both lamp classes re-declared `int selected;` while already inheriting it from OneOfSeveral (@0xAC). The CoolingLoop/PowerSource connection wrote the derived shadow copy while OneOfSeveral::Execute read the base @0xAC (always 0) -> every lamp stuck on frame 0 ("OFF"). Removed the redeclarations so &selected binds the inherited member. Loop numbers now render (verified: PPC->4, Myomers->5, ERMLaser->1/6, etc.). 3. Attribute-table shift (powersub.cpp + btl4gau2.cpp, gotcha #8): the generator-letter lamp resolved its source via ResolveLink(AttributePointerOf(subsystem,"InputVoltage")), but the BT_DEV_GAUGES audio attribute rows shifted the chained attribute ids so AttributePointerOf no longer landed on voltageSource@0x1D0 -> the link always resolved to 0 (OFF) even though the master PoweredSubsystem ctor DID bind voltageSource to its Generator. New BTPowerSourceFrame bridge reads the NAMED member via PoweredSubsystem::ResolveVoltageSource() and returns Generator::generatorNumber (@0x1E0), bypassing the attribute table -- the same pattern as the BTCoolingLoopFrame fix. Generator letters now render (verified: PPC_1->GeneratorA="A", Myomers-> GeneratorD="D", SRM6->GeneratorB="B"). Result: the "4 A / 1 B / 5 D" boxes render with both the loop number and generator letter, matching the reference. Kept BT_LOOP_LOG-gated [loopfeed]/[busfeed] feed diagnostics; both pod + glass builds clean. Awaiting live playtest verification. 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
c79b9953f9
commit
e634709e5d
@@ -204,9 +204,19 @@ protected:
|
|||||||
|
|
||||||
//
|
//
|
||||||
// PowerSourceConnection -- @004c31ec ctor / @004c3258 sampler (vt 0x518e9c).
|
// PowerSourceConnection -- @004c31ec ctor / @004c3258 sampler (vt 0x518e9c).
|
||||||
// Resolves the power source (Resolve(source)) and copies its bus-lamp state
|
// The binary resolves the subsystem's InputVoltage link and reads
|
||||||
// (+0x1e0); 0 if unresolved.
|
// resolved+0x1E0 == Generator::generatorNumber (1..4 -> the btpbus generator-
|
||||||
|
// letter frame A..D; 0 == OFF).
|
||||||
|
// DATABINDING FIX 2026-07-20 (gotcha 8, sibling of the CoolingLoop fix above):
|
||||||
|
// the binary's path was ResolveLink(AttributePointerOf(subsystem,"InputVoltage"))
|
||||||
|
// but the BT_DEV_GAUGES audio attribute rows shifted the chained attribute ids
|
||||||
|
// so AttributePointerOf no longer lands on voltageSource@0x1D0 -- the link always
|
||||||
|
// resolved to 0 and every generator lamp drew frame 0 ("OFF"). 'source' now
|
||||||
|
// carries the SUBSYSTEM directly and BTPowerSourceFrame (powersub.cpp) reads the
|
||||||
|
// NAMED voltageSource member via ResolveVoltageSource(), bypassing the attribute
|
||||||
|
// table.
|
||||||
//
|
//
|
||||||
|
extern int BTPowerSourceFrame(void *subsystem);
|
||||||
class PowerSourceConnection : public GaugeConnection
|
class PowerSourceConnection : public GaugeConnection
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -214,8 +224,7 @@ public:
|
|||||||
: GaugeConnection(0), source(source), destination(destination) {}
|
: GaugeConnection(0), source(source), destination(destination) {}
|
||||||
void Update() // @004c3258
|
void Update() // @004c3258
|
||||||
{
|
{
|
||||||
void *resolved = ResolveLink(source);
|
*destination = BTPowerSourceFrame(source);
|
||||||
*destination = (resolved == 0) ? 0 : *(int *)((char *)resolved + 0x1e0);
|
|
||||||
}
|
}
|
||||||
protected:
|
protected:
|
||||||
void *source; // @0x10
|
void *source; // @0x10
|
||||||
@@ -707,8 +716,13 @@ AnimatedSubsystemLamp::AnimatedSubsystemLamp(
|
|||||||
void *subsystem,
|
void *subsystem,
|
||||||
const char *identification_string
|
const char *identification_string
|
||||||
):
|
):
|
||||||
|
// COLOR FIX 2026-07-20 (verified vs binary @004c70a4 / caller SubsystemCluster
|
||||||
|
// @004c8140): the ctor DROPPED the background/foreground color params the binary
|
||||||
|
// passes and hardcoded 0,0 -- so Execute's SetColor(0)+DrawBitMapOpaque(0) drew
|
||||||
|
// the loop/gen boxes black-on-black (invisible). The binary passes bg=0xff,fg=0
|
||||||
|
// (the MFD on-color, same as the neighboring temp bar) -- restored.
|
||||||
OneOfSeveral(rate, mode_mask, renderer_in, graphics_port_number, // FUN_004c4d88
|
OneOfSeveral(rate, mode_mask, renderer_in, graphics_port_number, // FUN_004c4d88
|
||||||
x, y, True /*fromImageStrip*/, image, 0, 0, columns, rows,
|
x, y, True /*fromImageStrip*/, image, 0xff, 0, columns, rows,
|
||||||
identification_string)
|
identification_string)
|
||||||
{
|
{
|
||||||
selected = 0; // @0xAC this[0x2B]
|
selected = 0; // @0xAC this[0x2B]
|
||||||
@@ -735,8 +749,10 @@ AnimatedSourceLamp::AnimatedSourceLamp(
|
|||||||
void *source,
|
void *source,
|
||||||
const char *identification_string
|
const char *identification_string
|
||||||
):
|
):
|
||||||
|
// COLOR FIX 2026-07-20 (same drop as AnimatedSubsystemLamp above): restore the
|
||||||
|
// binary's bg=0xff,fg=0 so the generator-letter bus lamp draws (was 0,0 = blank).
|
||||||
OneOfSeveral(rate, mode_mask, renderer_in, graphics_port_number,
|
OneOfSeveral(rate, mode_mask, renderer_in, graphics_port_number,
|
||||||
x, y, True, image, 0, 0, columns, rows, identification_string)
|
x, y, True, image, 0xff, 0, columns, rows, identification_string)
|
||||||
{
|
{
|
||||||
selected = 0;
|
selected = 0;
|
||||||
AddConnection(new PowerSourceConnection(&selected, source)); // FUN_004c31ec
|
AddConnection(new PowerSourceConnection(&selected, source)); // FUN_004c31ec
|
||||||
@@ -1402,7 +1418,9 @@ SubsystemCluster::SubsystemCluster(
|
|||||||
else
|
else
|
||||||
powerSourceA = new AnimatedSourceLamp(ChildRate(), mfd_mode, renderer_in, // @004c7160
|
powerSourceA = new AnimatedSourceLamp(ChildRate(), mfd_mode, renderer_in, // @004c7160
|
||||||
mfd_port, x + 0x10e, y + 0xe, "btpbus.pcc", 5, 1,
|
mfd_port, x + 0x10e, y + 0xe, "btpbus.pcc", 5, 1,
|
||||||
inputVoltage, "PowerSource");
|
subsystem_in, "PowerSource"); // pass the SUBSYSTEM -- BTPowerSourceFrame
|
||||||
|
// reads voltageSource by name (attribute-shift
|
||||||
|
// databinding fix); NOT the inputVoltage slot
|
||||||
|
|
||||||
// Background: image_names[ auxScreenPlacement ] if present. (Placement@0x1e0 is
|
// Background: image_names[ auxScreenPlacement ] if present. (Placement@0x1e0 is
|
||||||
// read via the PoweredSubsystem bridge, not the non-byte-exact raw offset.)
|
// read via the PoweredSubsystem bridge, not the non-byte-exact raw offset.)
|
||||||
@@ -1444,7 +1462,7 @@ SubsystemCluster::SubsystemCluster(
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
powerSourceB = new AnimatedSourceLamp(ChildRate(), eng_mode, renderer_in, // @004c7160
|
powerSourceB = new AnimatedSourceLamp(ChildRate(), eng_mode, renderer_in, // @004c7160
|
||||||
engPort, 0x1a, 0xd0, "btebus.pcc", 5, 1, inputVoltage, "PowerSource");
|
engPort, 0x1a, 0xd0, "btebus.pcc", 5, 1, subsystem_in, "PowerSource"); // see powerSourceA
|
||||||
|
|
||||||
generatorVoltageBar = new ScalarBarGauge(ChildRate(), eng_mode, renderer_in, // @004c721c
|
generatorVoltageBar = new ScalarBarGauge(ChildRate(), eng_mode, renderer_in, // @004c721c
|
||||||
engPort, 0x89, 0x82, 0x93, 0x13b, "evolt.pcc", 0xff, 0,
|
engPort, 0x89, 0x82, 0x93, 0x13b, "evolt.pcc", 0xff, 0,
|
||||||
|
|||||||
@@ -195,8 +195,11 @@
|
|||||||
int x, int y, const char *image, int columns, int rows,
|
int x, int y, const char *image, int columns, int rows,
|
||||||
void *subsystem, const char *identification_string);
|
void *subsystem, const char *identification_string);
|
||||||
~AnimatedSubsystemLamp(); // @004c7134
|
~AnimatedSubsystemLamp(); // @004c7134
|
||||||
protected:
|
// SHADOW-FIELD FIX 2026-07-20 (gotcha #2): 'selected' is inherited from
|
||||||
int selected; // @0xAC this[0x2B] (connection dst)
|
// OneOfSeveral (@0xAC). A redeclared 'int selected;' here shadowed the base
|
||||||
|
// at a higher offset -- the CoolingLoopConnection wrote the shadow while
|
||||||
|
// OneOfSeveral::Execute read the base (always 0) so the loop lamp drew frame
|
||||||
|
// 0 ("OFF"). Removed; the ctor's &selected now binds the inherited member.
|
||||||
};
|
};
|
||||||
|
|
||||||
class AnimatedSourceLamp : // (best-effort)
|
class AnimatedSourceLamp : // (best-effort)
|
||||||
@@ -208,8 +211,10 @@
|
|||||||
int x, int y, const char *image, int columns, int rows,
|
int x, int y, const char *image, int columns, int rows,
|
||||||
void *source, const char *identification_string);
|
void *source, const char *identification_string);
|
||||||
~AnimatedSourceLamp(); // @004c71f0
|
~AnimatedSourceLamp(); // @004c71f0
|
||||||
protected:
|
// SHADOW-FIELD FIX 2026-07-20 (gotcha #2): same as AnimatedSubsystemLamp --
|
||||||
int selected; // @0xAC this[0x2B]
|
// 'selected' is inherited from OneOfSeveral (@0xAC); the redeclaration here
|
||||||
|
// shadowed it so the PowerSourceConnection wrote a dead copy and the bus lamp
|
||||||
|
// drew frame 0. Removed; &selected now binds the inherited base member.
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1140,14 +1140,43 @@ Subsystem *CreateCondenserSubsystem(Mech *owner, int id, void *seg)
|
|||||||
//===========================================================================//
|
//===========================================================================//
|
||||||
int BTCoolingLoopFrame(void *subsystem_v)
|
int BTCoolingLoopFrame(void *subsystem_v)
|
||||||
{
|
{
|
||||||
|
// [loopfeed] diagnostic (env BT_LOOP_LOG): why does the per-weapon cooling-loop
|
||||||
|
// lamp read 0 (the blank frame)? Logs the outcome + reason for the first ~80
|
||||||
|
// samples so every weapon prints once.
|
||||||
|
static int s_loopLog = -1;
|
||||||
|
if (s_loopLog < 0) s_loopLog = getenv("BT_LOOP_LOG") ? 1 : 0;
|
||||||
|
static int s_loopN = 0;
|
||||||
|
int trace = s_loopLog && (s_loopN++ < 80);
|
||||||
|
|
||||||
Subsystem *sub = (Subsystem *)subsystem_v;
|
Subsystem *sub = (Subsystem *)subsystem_v;
|
||||||
if (sub == 0 || !sub->IsDerivedFrom(*HeatSink::GetClassDerivations()))
|
if (sub == 0 || !sub->IsDerivedFrom(*HeatSink::GetClassDerivations()))
|
||||||
|
{
|
||||||
|
if (trace) DEBUG_STREAM << "[loopfeed] " << (sub ? sub->GetName() : "(null)")
|
||||||
|
<< " -> 0 (not a HeatSink)\n" << std::flush;
|
||||||
return 0;
|
return 0;
|
||||||
|
}
|
||||||
HeatSink *sink = (HeatSink *)sub;
|
HeatSink *sink = (HeatSink *)sub;
|
||||||
if (sink->coolantAvailable != 1) // the binary's src+0x134 gate
|
if (sink->coolantAvailable != 1) // the binary's src+0x134 gate
|
||||||
|
{
|
||||||
|
if (trace) DEBUG_STREAM << "[loopfeed] " << sub->GetName()
|
||||||
|
<< " -> 0 (coolantAvailable=" << sink->coolantAvailable << ")\n" << std::flush;
|
||||||
return 0;
|
return 0;
|
||||||
|
}
|
||||||
Subsystem *master = sink->linkedSinks.Resolve(); // attr id 3 "HeatSink" + FUN_00417ab4
|
Subsystem *master = sink->linkedSinks.Resolve(); // attr id 3 "HeatSink" + FUN_00417ab4
|
||||||
if (master == 0 || !master->IsDerivedFrom(*Condenser::GetClassDerivations()))
|
if (master == 0)
|
||||||
|
{
|
||||||
|
if (trace) DEBUG_STREAM << "[loopfeed] " << sub->GetName()
|
||||||
|
<< " -> 0 (linkedSinks EMPTY -- not linked to a condenser)\n" << std::flush;
|
||||||
return 0;
|
return 0;
|
||||||
return ((Condenser *)master)->condenserNumber; // master+0x1d4 (loop/frame number)
|
}
|
||||||
|
if (!master->IsDerivedFrom(*Condenser::GetClassDerivations()))
|
||||||
|
{
|
||||||
|
if (trace) DEBUG_STREAM << "[loopfeed] " << sub->GetName()
|
||||||
|
<< " -> 0 (linkedSinks='" << master->GetName() << "' is NOT a Condenser)\n" << std::flush;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int frame = ((Condenser *)master)->condenserNumber; // master+0x1d4 (loop/frame number)
|
||||||
|
if (trace) DEBUG_STREAM << "[loopfeed] " << sub->GetName() << " -> frame " << frame
|
||||||
|
<< " (condenser '" << master->GetName() << "')\n" << std::flush;
|
||||||
|
return frame;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -663,6 +663,60 @@ Subsystem *
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// BTPowerSourceFrame -- complete-type bridge for the AnimatedSourceLamp
|
||||||
|
// (btpbus/btebus generator-letter lamp). The binary's PowerSourceConnection
|
||||||
|
// sampler (@004c3258) resolves the subsystem's InputVoltage link and reads
|
||||||
|
// resolved+0x1E0 == Generator::generatorNumber (1..4 -> btpbus frames A..D; 0
|
||||||
|
// == OFF).
|
||||||
|
//
|
||||||
|
// The port cannot reproduce the binary's path -- it walked
|
||||||
|
// ResolveLink( AttributePointerOf(subsystem,"InputVoltage") )
|
||||||
|
// but AttributePointerOf now returns the WRONG slot: the BT_DEV_GAUGES audio
|
||||||
|
// attribute rows shifted the chained attribute ids, so the name->pointer lookup
|
||||||
|
// no longer lands on voltageSource@0x1D0 (the same regression that broke the
|
||||||
|
// CoolingLoop lamp -- see btl4gau2.cpp). The link therefore always resolved to
|
||||||
|
// 0 and every generator lamp drew frame 0 ("OFF") even though the master
|
||||||
|
// PoweredSubsystem ctor DID bind voltageSource to its Generator.
|
||||||
|
//
|
||||||
|
// This bridge takes the subsystem directly and reads the NAMED member via
|
||||||
|
// PoweredSubsystem::ResolveVoltageSource() (voltageSource.Resolve()), bypassing
|
||||||
|
// the attribute table entirely -- exactly the BTCoolingLoopFrame pattern.
|
||||||
|
//
|
||||||
|
int BTPowerSourceFrame(void *subsystem_v)
|
||||||
|
{
|
||||||
|
static int s_log = -1;
|
||||||
|
if (s_log < 0) s_log = getenv("BT_LOOP_LOG") ? 1 : 0;
|
||||||
|
static int s_n = 0;
|
||||||
|
int trace = s_log && (s_n++ < 40);
|
||||||
|
|
||||||
|
Subsystem *sub = (Subsystem *)subsystem_v;
|
||||||
|
if (sub == 0 || !sub->IsDerivedFrom(*PoweredSubsystem::GetClassDerivations()))
|
||||||
|
{
|
||||||
|
if (trace) DEBUG_STREAM << "[busfeed] " << (sub ? sub->GetName() : "(null)")
|
||||||
|
<< " -> 0 (not a PoweredSubsystem)\n" << std::flush;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
Generator *gen = (Generator *)((PoweredSubsystem *)sub)->ResolveVoltageSource();
|
||||||
|
if (gen == 0)
|
||||||
|
{
|
||||||
|
if (trace) DEBUG_STREAM << "[busfeed] " << sub->GetName()
|
||||||
|
<< " -> 0 (voltageSource unbound)\n" << std::flush;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (gen->GetClassID() != RegisteredClass::GeneratorClassID)
|
||||||
|
{
|
||||||
|
if (trace) DEBUG_STREAM << "[busfeed] " << sub->GetName()
|
||||||
|
<< " -> 0 (source not a Generator)\n" << std::flush;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int frame = gen->generatorNumber; // @0x1E0 (A..D)
|
||||||
|
if (trace) DEBUG_STREAM << "[busfeed] " << sub->GetName() << " -> frame " << frame
|
||||||
|
<< " (generator '" << gen->GetName() << "')\n" << std::flush;
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//#############################################################################
|
//#############################################################################
|
||||||
// Voltage-source linkage helpers
|
// Voltage-source linkage helpers
|
||||||
//
|
//
|
||||||
|
|||||||
Reference in New Issue
Block a user