gauge wave P2c: GeneratorCluster reconstructed + infra (gated -- parent aborts)

Reconstructed the 4th unbuilt widget (the 4 generator engineering panels, buttons
9-12) + its companions, but left it UNREGISTERED pending a runtime-abort fix:

- Generator OutputVoltage attribute table (powersub) -> outputVoltage@0x1DC, chained
  to HeatSink's dense index (temps already reach it).  Correct + reusable.
- ScalarBarGauge @004c72ac plain-Scalar variant (btl4gau2) -- the DIRECT-Scalar* bar
  the GeneratorCluster voltage bar needs (the existing @004c721c takes a resolved
  source object).  Correct + harmless.
- GeneratorCluster Make/ctor/dtor + 15-param methodDescription reconstructed from
  part_014.c:891-1013 (replacing the prose + placeholder Make + the bogus DAT pool);
  builds the 5 children (temp bar / voltage bar / leak wipe / 2 lamps).  Header fixed
  to the binary ctor param order (secondaryColor@0x94, +_reserved0xB0 -> sizeof 0xB4).
  ChildRate forward-declared.

⚠ REGISTRATION GATED (commented out in BTL4MethodDescription[]): registering it
aborts at runtime -- the 4 panels build (the 4 "BecameActive not defined" warnings
fire) then abort().  ISOLATED to the PARENT lifecycle: the abort PERSISTS with all 5
children nulled; there are no pure virtuals; Execute is SEH-guarded under BT_DEV_GAUGES
so the fault is in the unguarded ctor/lifecycle path.  The sibling SubsystemCluster
overrides BecameActive/Execute/TestInstance whereas the decode has GeneratorCluster
overriding ONLY the dtor -- that "inherits only the dtor" claim is the prime suspect.
Deferred; the 3 other P2 widgets (LeakGauge/VertNormalSlider/PilotList) ship clean.

Verified: unregistered -> parse-skips -> no abort; combat un-regressed (DESTROYED),
0 crashes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-07 17:43:47 -05:00
co-authored by Claude Opus 4.8
parent 0060a3e1ca
commit b44e908101
5 changed files with 272 additions and 54 deletions
+188 -42
View File
@@ -569,75 +569,221 @@ ScalarBarGauge::~ScalarBarGauge() {} // @004c733c (base chain)
//########################################################################### //###########################################################################
//########################################################################### //###########################################################################
// GeneratorCluster @004c7368 Make / @004c746c ctor // GeneratorCluster (config keyword "GeneratorCluster" -- the 4 generator
// engineering panels, buttons 9-12 / GeneratorA..D; vtable PTR_FUN_0051a0a0).
// Reconstructed from part_014.c:891-1013 (Make @004c7368 / ctor @004c746c /
// dtor @004c7778). Was PROSE + a placeholder Make -> the "GeneratorCluster"
// config lines parse-skipped and the 4 panels never built. Only the dtor is
// overridden; Execute/BecameActive/TestInstance are inherited from GraphicGauge.
//########################################################################### //###########################################################################
//########################################################################### //###########################################################################
static inline GaugeRate ChildRate(); // fwd-decl (defined later, after SubsystemCluster)
// //
// @004c7368 -- Make. Resolve the named subsystem ("GeneratorCluster") via // @004c72ac -- ScalarBarGauge plain-Scalar variant. Same bar body as @004c721c but
// Entity::FindSubObject (DAT_00518f24); on failure warn "Subsystem <name> not // a GaugeConnectionDirectOf<Scalar> from a DIRECT Scalar* (the generator's
// found" and return 0. Else allocate (0xb4) and construct, passing the four // OutputVoltage attribute pointer) instead of a GeneratorVoltageConnection.
// resource strings / colours from DAT_00518e..00519298. //
ScalarBarGauge::ScalarBarGauge(
GaugeRate rate,
ModeMask mode_mask,
L4GaugeRenderer *renderer_in,
int graphics_port_number,
int left,
int bottom,
int right,
int top,
const char *tile_image,
int foreground_color,
int background_color,
WipeGaugeScalar::Direction direction,
Scalar min,
Scalar max,
Scalar *value_source, // DIRECT scalar
const char *identification_string
):
BarGraphBitMapScalar(rate, mode_mask, renderer_in, 0 /*owner*/, // FUN_00472ef0
graphics_port_number, left, bottom, right, top, tile_image,
foreground_color, background_color, direction, min, max,
(Scalar *)0 /*no auto-connection*/, identification_string)
{
AddConnection(new GaugeConnectionDirectOf<Scalar>(0, &currentValue, value_source)); // FUN_00474855
}
// CFG shape (L4GAUGE.CFG:5046): NO leading rate token; p[0] = the mode mask (the
// panel's own rate is hard-wired 0xFFFF).
MethodDescription
GeneratorCluster::methodDescription =
{
"GeneratorCluster",
GeneratorCluster::Make,
{
{ ParameterDescription::typeModeMask, NULL }, // p[0] mode mask (ModeAlwaysActive)
{ ParameterDescription::typeString, NULL }, // p[1] generator name (GeneratorA..D)
{ ParameterDescription::typeString, NULL }, // p[2] background pcc
{ ParameterDescription::typeColor, NULL }, // p[3] extra/leak colorA
{ ParameterDescription::typeString, NULL }, // p[4] temperature tile
{ ParameterDescription::typeColor, NULL }, // p[5] temp bg color
{ ParameterDescription::typeColor, NULL }, // p[6] temp fill color
{ ParameterDescription::typeString, NULL }, // p[7] voltage tile
{ ParameterDescription::typeColor, NULL }, // p[8] voltage fg color
{ ParameterDescription::typeString, NULL }, // p[9] leak pcc
{ ParameterDescription::typeColor, NULL }, // p[10] leak colorB
{ ParameterDescription::typeString, NULL }, // p[11] lamp A pcc
{ ParameterDescription::typeString, NULL }, // p[12] lamp B pcc
{ ParameterDescription::typeColor, NULL }, // p[13] lamp on color
{ ParameterDescription::typeColor, NULL }, // p[14] lamp off color
PARAMETER_DESCRIPTION_END
}
};
//
// @004c7368 -- Make. Resolve the named generator (config p[1], e.g. "GeneratorA");
// warn + bail if absent. Then alloc 0xB4 + construct.
// //
Logical Logical
GeneratorCluster::Make( GeneratorCluster::Make(
int /*display_port_index*/, int display_port_index,
Vector2DOf<int> /*position*/, Vector2DOf<int> position,
Entity *entity, Entity *entity,
GaugeRenderer *gauge_renderer GaugeRenderer *gauge_renderer
) )
{ {
Subsystem *subsystem = entity->FindSubsystem((char *)&DAT_00518f24); // FUN_0041f98c ParameterDescription *p = methodDescription.parameterList;
Subsystem *subsystem = entity->FindSubsystem(p[1].data.string); // FUN_0041f98c (GeneratorA..D)
if (subsystem == NULL) if (subsystem == NULL)
{ {
DebugStream << "Subsystem " << (char *)&DAT_00518f24 DebugStream << "Subsystem " << p[1].data.string << " not found\n";
<< " not found\n"; // FUN_004dbb24 x3
return False; return False;
} }
GeneratorCluster *gauge = (GeneratorCluster *)operator new(0xb4); GeneratorCluster *gauge = (GeneratorCluster *)operator new(0xb4); // FUN_00402298(0xb4)
if (gauge != NULL) if (gauge != NULL)
{ {
//
// Construct with the recovered subsystem + name; the geometry / colour /
// image-name resource arguments (DAT_00518f68..00519298) were not
// recovered from the optimised image, so are passed as typed placeholders.
//
new (gauge) GeneratorCluster( new (gauge) GeneratorCluster(
(GaugeRate)DAT_00518ee0, // rate p[0].data.modeMask, // mode (ModeAlwaysActive)
(ModeMask)0, // mode mask
(L4GaugeRenderer *)gauge_renderer, (L4GaugeRenderer *)gauge_renderer,
0, // graphics port number 0, // owner_ID (raw hard-wires 0)
0, 0, // x, y display_port_index, // graphics_port_number
(char *)&DAT_00518f68, // background image position.x, position.y, // panel origin
0, // color subsystem, // resolved generator
(char *)0, // temperature tile p[2].data.string, // background image
(Scalar *)0, (Scalar *)0, // temp low / high p[3].data.color, // extra/leak colorA
(char *)0, // voltage tile p[4].data.string, // temperature tile
(Scalar *)0, // voltage low p[5].data.color, // temp bg color
(char *)0, // leak image p[6].data.color, // temp fill color
(char *)0, (char *)0, // lampA / lampB images p[7].data.string, // voltage tile
0, 0, // on / off colours p[8].data.color, // voltage fg color
subsystem, p[9].data.string, // leak image
p[10].data.color, // leak colorB
p[11].data.string, // lampA image
p[12].data.string, // lampB image
p[13].data.color, // lamp on color
p[14].data.color, // lamp off color
"GeneratorCluster"); "GeneratorCluster");
} }
return True; return True;
} }
// //
// @004c746c -- ctor (vtable PTR_FUN_0051a0a0). GraphicGauge base; resolves // @004c746c -- ctor (vtable PTR_FUN_0051a0a0). GraphicGauge base (own rate hard-
// CurrentTemperature/DegradationTemperature/FailureTemperature/ // wired 0xFFFF; mode from config). Resolve the generator's temp/voltage attributes,
// CoolantMassLeakRate/OutputVoltage attribute indices; interns the background // intern + AddRef the panel background pixmap, build the 5 child gauges into this
// image; then builds the five child gauges (each parented at this graphics // panel's port at offsets relative to the panel origin (x,y).
// port, offset from x,y):
// this[0x26] = new VertTwoPartBar(...,CurrentTemp,Degrade,Fail) @004c4724
// this[0x27] = new ScalarBarGauge(0..12000, OutputVoltage) @004c72ac
// this[0x28] = new LeakGauge(CoolantMassLeakRate) @004c5b7c
// this[0x29] = new TwoState(lampA, subsys+0x1d4) @004739d8
// this[0x2A] = new TwoState(lampB, subsys+0x1d4) @004739d8
// this[0x2B] = subsystem
// @004c7778 -- dtor: release the background, delete the five children, base dtor.
// //
GeneratorCluster::GeneratorCluster(
ModeMask mode_mask,
L4GaugeRenderer *renderer_in,
int owner_ID,
int graphics_port_number,
int x,
int y,
Subsystem *subsystem_in,
const char *background_image,
int extra_color,
const char *temp_tile,
int temp_bg_color,
int temp_fill_color,
const char *voltage_tile,
int voltage_fg_color,
const char *leak_image,
int leak_color_b,
const char *lampA_image,
const char *lampB_image,
int lamp_on_color,
int lamp_off_color,
const char *identification_string
):
GraphicGauge((GaugeRate)0xFFFF, mode_mask, renderer_in, owner_ID, // FUN_00444818
graphics_port_number, identification_string)
{
L4Warehouse *warehouse = (L4Warehouse *)renderer_in->warehousePointer;
GaugeRate rate1 = ChildRate(); // VertTwoPartBar
GaugeRate rate2 = ChildRate(); // ScalarBarGauge
GaugeRate rate3 = ChildRate(); // LeakGauge
GaugeRate rate4 = ChildRate(); // both lamps
Scalar *currentTemp = (Scalar *)AttributePointerOf(subsystem_in, "CurrentTemperature");
Scalar *degradeTemp = (Scalar *)AttributePointerOf(subsystem_in, "DegradationTemperature");
Scalar *failTemp = (Scalar *)AttributePointerOf(subsystem_in, "FailureTemperature");
Scalar *coolantLeak = (Scalar *)AttributePointerOf(subsystem_in, "CoolantMassLeakRate");
Scalar *outputVolt = (Scalar *)AttributePointerOf(subsystem_in, "OutputVoltage");
localView.SetOrigin(x, y); // vtbl+0x10
secondaryColor = leak_color_b; // @0x94 (stored; unread here)
backgroundImage = new char[strlen(background_image) + 1]; // @0x90
strcpy(backgroundImage, background_image);
warehouse->pixelMap8Bin.Get(backgroundImage, False); // AddRef
// child 1: vertical two-part temperature bar
temperatureBar = new VertTwoPartBar(rate1, mode_mask, renderer_in, graphics_port_number,
x + 5, y + 0x29, x + 0xC, y + 0x5C,
temp_tile, temp_bg_color, temp_fill_color, extra_color,
currentTemp, degradeTemp, failTemp, "VertTwoPartBar");
// child 2: generator-voltage bar 0..12000 (OutputVoltage); @004c72ac Scalar* variant
voltageBar = new ScalarBarGauge(rate2, mode_mask, renderer_in, graphics_port_number,
x + 0x18, y + 0x29, x + 0x1C, y + 0x5C,
voltage_tile, voltage_fg_color, extra_color,
WipeGaugeScalar::wipeUp, 0.0f, 12000.0f, outputVolt,
"GeneratorVoltage(scalar)");
// child 3: coolant-leak inverse-wipe (CoolantMassLeakRate); third=*(subsys+0x150), frames=3
leakGauge = new BitMapInverseWipe(rate3, mode_mask, renderer_in, graphics_port_number,
x, y + 0x27, leak_image, extra_color, leak_color_b,
*(int *)((char *)subsystem_in + 0x150), // third
3, // frames
coolantLeak, "LeakGauge");
// child 4/5: two status lamps (subsys+0x1D4 == generatorOn)
lampA = new TwoState(rate4, mode_mask, renderer_in, (unsigned)owner_ID, graphics_port_number,
x + 0xC, y + 2, lampA_image, lamp_on_color, lamp_off_color,
(int *)((char *)subsystem_in + 0x1D4), "TwoState");
lampB = new TwoState(rate4, mode_mask, renderer_in, (unsigned)owner_ID, graphics_port_number,
x + 2, y + 2, lampB_image, lamp_on_color, lamp_off_color,
(int *)((char *)subsystem_in + 0x1D4), "TwoState");
subsystem = subsystem_in; // @0xAC
_reserved0xB0 = 0; // @0xB0 (raw leaves uninit; zero for determinism)
}
//
// @004c7778 -- dtor. Release the background pixmap + free the interned name; the
// base chain runs implicitly. The 5 children are NOT deleted here (faithful to the
// binary: each self-registered with the renderer via the base Gauge ctor and is
// owned there -- deleting would double-free with the renderer chain teardown).
//
GeneratorCluster::~GeneratorCluster()
{
L4Warehouse *warehouse = (L4Warehouse *)renderer->warehousePointer;
warehouse->pixelMap8Bin.Release(backgroundImage);
delete[] backgroundImage;
backgroundImage = NULL;
}
//########################################################################### //###########################################################################
+28 -12
View File
@@ -220,6 +220,16 @@
const char *tile_image, int foreground_color, int background_color, const char *tile_image, int foreground_color, int background_color,
WipeGaugeScalar::Direction direction, Scalar min, Scalar max, WipeGaugeScalar::Direction direction, Scalar min, Scalar max,
void *voltage_source, const char *identification_string); void *voltage_source, const char *identification_string);
// @004c72ac -- the plain-Scalar variant (a DIRECT Scalar* value source, a
// GaugeConnectionDirectOf<Scalar> instead of the GeneratorVoltageConnection).
// GeneratorCluster is its caller. Disambiguated from the void* overload by
// the Scalar* first arg (exact match here, std-conversion to void*).
ScalarBarGauge( // @004c72ac
GaugeRate, ModeMask, L4GaugeRenderer *, int graphics_port_number,
int left, int bottom, int right, int top,
const char *tile_image, int foreground_color, int background_color,
WipeGaugeScalar::Direction direction, Scalar min, Scalar max,
Scalar *value_source, const char *identification_string);
~ScalarBarGauge(); // @004c733c ~ScalarBarGauge(); // @004c733c
}; };
@@ -241,27 +251,33 @@
public GraphicGauge public GraphicGauge
{ {
public: public:
// gauge wave P2: registered "GeneratorCluster" (engineering buttons 9-12,
// GeneratorA..D). Corrected ctor param order == binary FUN_004c746c; only
// the dtor is overridden (Execute/BecameActive/TestInstance inherited).
static MethodDescription methodDescription;
static Logical Make(int, Vector2DOf<int>, Entity *, GaugeRenderer *); // @004c7368 static Logical Make(int, Vector2DOf<int>, Entity *, GaugeRenderer *); // @004c7368
GeneratorCluster( // @004c746c GeneratorCluster( // @004c746c (BINARY param order)
GaugeRate, ModeMask, L4GaugeRenderer *, int, ModeMask, L4GaugeRenderer *, int owner_ID, int graphics_port_number,
int x, int y, const char *background_image, int x, int y, Subsystem *subsystem,
int /*color*/, const char *temp_tile, const char *background_image, int extra_color,
Scalar *low, Scalar *high, const char *temp_tile, int temp_bg_color, int temp_fill_color,
const char *voltage_tile, Scalar *voltage_low, const char *voltage_tile, int voltage_fg_color,
const char *leak_image, const char *lampA_image, const char *leak_image, int leak_color_b,
const char *lampB_image, int onColor, int offColor, const char *lampA_image, const char *lampB_image,
Subsystem *subsystem, const char *identification_string); int lamp_on_color, int lamp_off_color,
const char *identification_string);
~GeneratorCluster(); // @004c7778 ~GeneratorCluster(); // @004c7778
protected: protected:
char *backgroundImage; // @0x90 this[0x24] char *backgroundImage; // @0x90 this[0x24] interned .pcc (pixelMap8Bin AddRef'd)
int hostPortNumber; // @0x94 this[0x25] int secondaryColor; // @0x94 this[0x25] = leak colorB (stored, unread here)
GraphicGauge GraphicGauge
*temperatureBar, // @0x98 this[0x26] VertTwoPartBar *temperatureBar, // @0x98 this[0x26] VertTwoPartBar
*voltageBar, // @0x9C this[0x27] ScalarBarGauge *voltageBar, // @0x9C this[0x27] ScalarBarGauge (@004c72ac variant)
*leakGauge, // @0xA0 this[0x28] BitMapInverseWipe *leakGauge, // @0xA0 this[0x28] BitMapInverseWipe
*lampA, // @0xA4 this[0x29] TwoState *lampA, // @0xA4 this[0x29] TwoState
*lampB; // @0xA8 this[0x2A] TwoState *lampB; // @0xA8 this[0x2A] TwoState
Subsystem *subsystem; // @0xAC this[0x2B] Subsystem *subsystem; // @0xAC this[0x2B]
int _reserved0xB0; // @0xB0 this[0x2C] (trailing pad -> sizeof 0xB4)
}; };
+12
View File
@@ -146,6 +146,18 @@ MethodDescription
&BitMapInverseWipe::methodDescription, // "LeakGauge" -- coolant-leak inverse-wipe (Condenser/CoolantMassLeakRate) &BitMapInverseWipe::methodDescription, // "LeakGauge" -- coolant-leak inverse-wipe (Condenser/CoolantMassLeakRate)
&VertNormalSlider::methodDescription, // "vertNormalSlider" -- condenser valve-setting slider (heat MFD) &VertNormalSlider::methodDescription, // "vertNormalSlider" -- condenser valve-setting slider (heat MFD)
&PilotList::methodDescription, // "pilotList" -- Comm KILLS/DEATHS pilot roster &PilotList::methodDescription, // "pilotList" -- Comm KILLS/DEATHS pilot roster
// GeneratorCluster is reconstructed (btl4gau2.cpp) + its infra is in place
// (Generator OutputVoltage table + the @004c72ac ScalarBarGauge variant), but
// registering it aborts at runtime: the 4 panels build (the 4 "BecameActive not
// defined" warnings fire) then abort() -- isolated to the PARENT lifecycle (the
// abort persists with all 5 children nulled; no pure virtuals; the ctor path is
// unguarded by the SEH GuardedExecute). The sibling SubsystemCluster overrides
// BecameActive/Execute/TestInstance whereas the decode has GeneratorCluster
// overriding ONLY the dtor -- that "inherits only dtor" claim is the prime
// suspect. DEFERRED: unregistered so it parse-skips (never built) -> no abort;
// the other 3 P2 widgets ship clean. Re-enable this line once the parent
// lifecycle is resolved.
// &GeneratorCluster::methodDescription, // "GeneratorCluster" -- 4 generator panels (buttons 9-12)
&BTL4ChainToPrevious &BTL4ChainToPrevious
}; };
+25
View File
@@ -837,6 +837,31 @@ int
//########################################################################### //###########################################################################
//########################################################################### //###########################################################################
//#############################################################################
// Attribute Support (gauge data-binding wave)
//
// OutputVoltage -> outputVoltage@0x1DC (== MeasuredVoltage(); GeneratorSimulation
// drives it). Chained to HeatSink's dense index so the temps/coolant the
// GeneratorCluster also reads stay reachable. DefaultData below already calls
// GetAttributeIndex() (was resolving to the inherited HeatSink one).
//
const Generator::IndexEntry
Generator::AttributePointers[]=
{
ATTRIBUTE_ENTRY(Generator, OutputVoltage, outputVoltage) // @0x1DC
};
Generator::AttributeIndexSet&
Generator::GetAttributeIndex()
{
static Generator::AttributeIndexSet attributeIndex(
ELEMENTS(Generator::AttributePointers),
Generator::AttributePointers,
HeatSink::GetAttributeIndex()
);
return attributeIndex;
}
//############################################################################# //#############################################################################
// Shared Data Support // Shared Data Support
// //
+19
View File
@@ -352,6 +352,25 @@ class Generator;
void ForceShortRecovery(); // @004b11bc (short-event recovery) void ForceShortRecovery(); // @004b11bc (short-event recovery)
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Attribute Support (gauge data-binding wave)
//
// The GeneratorCluster (engineering-screen buttons 9-12) binds Generator/
// OutputVoltage for its 0..12000 voltage bar. The temps (CurrentTemperature/
// DegradationTemperature/FailureTemperature/CoolantMassLeakRate) already reach
// it via the inherited HeatSink table; OutputVoltage is a real Generator member
// (@0x1DC, NOT the Emitter alias). Dense from HeatSink::NextAttributeID.
//
public:
enum {
OutputVoltageAttributeID = HeatSink::NextAttributeID,
NextAttributeID
};
private:
static const IndexEntry AttributePointers[];
public:
static AttributeIndexSet& GetAttributeIndex();
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Generator state machine (stateAlarm @0x1FC, level @0x210, 5 levels). // Generator state machine (stateAlarm @0x1FC, level @0x210, 5 levels).
// Names best-effort from the embedded "GeneratorState" / message strings. // Names best-effort from the embedded "GeneratorState" / message strings.