Gauges: convert generator-voltage bar to named-member bridge

The eng-page generator-voltage bar (evolt.pcc, ScalarBarGauge @004c721c) and
the MyomerCluster seek-voltage graph were fed by GeneratorVoltageConnection,
which walked the same dead path as the generator-letter lamp:
ResolveLink(AttributePointerOf(subsystem,"InputVoltage")) -> read resolved
+0x1DC == Generator::outputVoltage. The BT_DEV_GAUGES audio attribute rows
shifted the chained attribute ids so AttributePointerOf no longer lands on
voltageSource@0x1D0 -> the bar always read 0.

New BTGeneratorVoltage(subsystem) bridge (powersub.cpp) reads the NAMED member
via PoweredSubsystem::ResolveVoltageSource()->Generator::MeasuredVoltage()
(outputVoltage@0x1DC), bypassing the attribute table -- same pattern as
BTPowerSourceFrame/BTCoolingLoopFrame. Both GeneratorVoltageConnection callers
(the ScalarBarGauge param + the MyomerCluster seekValue) now pass subsystem_in.

[voltfeed]-verified: Myomers -> 10000V from GeneratorD (was 0 on the dead
path). Both pod + glass builds clean. Eng-page render awaiting live playtest.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-21 00:21:41 -05:00
co-authored by Claude Opus 4.8
parent 8c7ff639de
commit f2eaf3d0ac
3 changed files with 63 additions and 12 deletions
+8 -3
View File
@@ -162,10 +162,15 @@ independent bugs stacked (all fixed, commit e634709):
now passes `subsystem_in` (not the `InputVoltage` attribute slot). **Lesson: gauge value feeds
must read named members through a complete-type bridge, NOT `AttributePointerOf`+`ResolveLink`
— the attribute table's chained ids are unstable under the audio rows.** The eng-page
`GeneratorVoltageConnection` (evolt voltage bar, btl4gau2.cpp:1737) still uses the same
`AttributePointerOf("InputVoltage")` path and is the remaining sibling to convert.
generator-voltage bar + MyomerCluster seek-voltage graph (`GeneratorVoltageConnection`,
evolt.pcc, btl4gau2.cpp:806/1750) were on the same dead path and are now converted the same
way -> `BTGeneratorVoltage(subsystem)` reads `Generator::MeasuredVoltage()`
(outputVoltage@0x1DC); `[voltfeed]`-verified (Myomers -> 10000V from GeneratorD). Still on the
OLD path but NOT yet converted (different attribute, direct read, not confirmed broken): the
GeneratorCluster + EnergyWeaponCluster bars that read `AttributePointerOf(subsystem,
"OutputVoltage")` directly (btl4gau2.cpp:968, ~1920).
Diagnostics retained (env `BT_LOOP_LOG`): `[loopfeed]` (BTCoolingLoopFrame) + `[busfeed]`
(BTPowerSourceFrame) print each lamp's resolved frame + source.
(BTPowerSourceFrame) + `[voltfeed]` (BTGeneratorVoltage) print each widget's resolved value + source.
**HUMAN-VERIFIED live 2026-07-21** (pod build, solo ARENA1 cockpit): the loop number + generator
letter boxes render correctly on the weapon panels.
+17 -9
View File
@@ -233,9 +233,18 @@ protected:
//
// GeneratorVoltageConnection -- @004c3288 ctor / @004c32f4 sampler (vt 0x518e90).
// Resolves the voltage source and copies its output voltage (+0x1dc); 0 if
// unresolved. Drives ScalarBarGauge / SeekVoltageGraph value slots.
// The binary resolves the subsystem's InputVoltage link and copies its output
// voltage (resolved+0x1DC == Generator::outputVoltage); 0 if unresolved. Drives
// the eng-page generator-voltage bar (ScalarBarGauge) + the MyomerCluster
// seek-voltage graph.
// DATABINDING FIX 2026-07-21 (gotcha 8, sibling of the PowerSource/CoolingLoop
// fixes): the ResolveLink(AttributePointerOf(subsystem,"InputVoltage")) path is
// dead after the BT_DEV_GAUGES audio attribute rows shifted the chained ids, so
// the bar always read 0. 'source' now carries the SUBSYSTEM and
// BTGeneratorVoltage (powersub.cpp) reads the NAMED member via
// ResolveVoltageSource()->MeasuredVoltage(), bypassing the attribute table.
//
extern Scalar BTGeneratorVoltage(void *subsystem);
class GeneratorVoltageConnection : public GaugeConnection
{
public:
@@ -243,9 +252,7 @@ public:
: GaugeConnection(0), source(source), destination(destination) {}
void Update() // @004c32f4
{
void *resolved = ResolveLink(source);
*destination = (resolved == 0) ? 0.0f
: *(Scalar *)((char *)resolved + 0x1dc);
*destination = BTGeneratorVoltage(source);
}
protected:
void *source; // @0x10
@@ -1466,8 +1473,8 @@ SubsystemCluster::SubsystemCluster(
generatorVoltageBar = new ScalarBarGauge(ChildRate(), eng_mode, renderer_in, // @004c721c
engPort, 0x89, 0x82, 0x93, 0x13b, "evolt.pcc", 0xff, 0,
WipeGaugeScalar::wipeUp, 0.0f, GeneratorMaxVoltage, inputVoltage,
"GeneratorVoltage(slotOf)");
WipeGaugeScalar::wipeUp, 0.0f, GeneratorMaxVoltage, subsystem_in, // pass the SUBSYSTEM
"GeneratorVoltage(slotOf)"); // -> BTGeneratorVoltage reads voltage by name (attr-shift fix)
// stateLamp only when the subsystem is a Generator (IsDerivedFrom 0x50f4bc).
if (IsGeneratorDerived(subsystem_in))
@@ -1738,8 +1745,9 @@ MyomerCluster::MyomerCluster(
// INFERRED (the MyomerCluster ctor @004c8df4 isn't in the assert-anchored export).
(int *)((char *)subsystem_in + 0x320), "OneOfSeveralInt");
void *inputVoltage = AttributePointerOf(subsystem_in, "InputVoltage");
AddConnection(new GeneratorVoltageConnection(&seekValue, inputVoltage)); // @004c3288
// Pass the SUBSYSTEM -- BTGeneratorVoltage resolves voltageSource by name
// (the attribute-table shift makes AttributePointerOf("InputVoltage") dead).
AddConnection(new GeneratorVoltageConnection(&seekValue, subsystem_in)); // @004c3288
}
MyomerCluster::~MyomerCluster()
+38
View File
@@ -716,6 +716,44 @@ int BTPowerSourceFrame(void *subsystem_v)
return frame;
}
//
// BTGeneratorVoltage -- complete-type bridge for the GeneratorVoltageConnection
// (@004c3288), which drives the eng-page generator-voltage bar (evolt.pcc,
// ScalarBarGauge @004c721c) and the MyomerCluster seek-voltage graph. The
// binary resolved the subsystem's InputVoltage link and read resolved+0x1DC ==
// Generator::outputVoltage (0..12000, == MeasuredVoltage()). Same databinding
// trap as BTPowerSourceFrame: the port's ResolveLink(AttributePointerOf(...,
// "InputVoltage")) path is dead after the BT_DEV_GAUGES audio attribute rows
// shifted the chained ids, so the bar read 0. This reads the NAMED member via
// PoweredSubsystem::ResolveVoltageSource(), bypassing the attribute table.
//
Scalar BTGeneratorVoltage(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 << "[voltfeed] " << (sub ? sub->GetName() : "(null)")
<< " -> 0 (not a PoweredSubsystem)\n" << std::flush;
return 0.0f;
}
Generator *gen = (Generator *)((PoweredSubsystem *)sub)->ResolveVoltageSource();
if (gen == 0 || gen->GetClassID() != RegisteredClass::GeneratorClassID)
{
if (trace) DEBUG_STREAM << "[voltfeed] " << sub->GetName()
<< " -> 0 (no Generator)\n" << std::flush;
return 0.0f;
}
Scalar volts = gen->MeasuredVoltage(); // outputVoltage @0x1DC
if (trace) DEBUG_STREAM << "[voltfeed] " << sub->GetName() << " -> " << volts
<< "V (generator '" << gen->GetName() << "')\n" << std::flush;
return volts;
}
//#############################################################################
// Voltage-source linkage helpers