From b44e908101ef658a6d2fa36f4c8ab0b3f58206ba Mon Sep 17 00:00:00 2001 From: arcattack Date: Tue, 7 Jul 2026 17:43:47 -0500 Subject: [PATCH] gauge wave P2c: GeneratorCluster reconstructed + infra (gated -- parent aborts) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- game/reconstructed/btl4gau2.cpp | 230 ++++++++++++++++++++++++++------ game/reconstructed/btl4gau2.hpp | 40 ++++-- game/reconstructed/btl4grnd.cpp | 12 ++ game/reconstructed/powersub.cpp | 25 ++++ game/reconstructed/powersub.hpp | 19 +++ 5 files changed, 272 insertions(+), 54 deletions(-) diff --git a/game/reconstructed/btl4gau2.cpp b/game/reconstructed/btl4gau2.cpp index 64db794..d3f0c24 100644 --- a/game/reconstructed/btl4gau2.cpp +++ b/game/reconstructed/btl4gau2.cpp @@ -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 -// Entity::FindSubObject (DAT_00518f24); on failure warn "Subsystem not -// found" and return 0. Else allocate (0xb4) and construct, passing the four -// resource strings / colours from DAT_00518e..00519298. +// @004c72ac -- ScalarBarGauge plain-Scalar variant. Same bar body as @004c721c but +// a GaugeConnectionDirectOf from a DIRECT Scalar* (the generator's +// OutputVoltage attribute pointer) instead of a GeneratorVoltageConnection. +// +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(0, ¤tValue, 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 GeneratorCluster::Make( - int /*display_port_index*/, - Vector2DOf /*position*/, + int display_port_index, + Vector2DOf position, Entity *entity, 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) { - DebugStream << "Subsystem " << (char *)&DAT_00518f24 - << " not found\n"; // FUN_004dbb24 x3 + DebugStream << "Subsystem " << p[1].data.string << " not found\n"; return False; } - GeneratorCluster *gauge = (GeneratorCluster *)operator new(0xb4); + GeneratorCluster *gauge = (GeneratorCluster *)operator new(0xb4); // FUN_00402298(0xb4) 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( - (GaugeRate)DAT_00518ee0, // rate - (ModeMask)0, // mode mask + p[0].data.modeMask, // mode (ModeAlwaysActive) (L4GaugeRenderer *)gauge_renderer, - 0, // graphics port number - 0, 0, // x, y - (char *)&DAT_00518f68, // background image - 0, // color - (char *)0, // temperature tile - (Scalar *)0, (Scalar *)0, // temp low / high - (char *)0, // voltage tile - (Scalar *)0, // voltage low - (char *)0, // leak image - (char *)0, (char *)0, // lampA / lampB images - 0, 0, // on / off colours - subsystem, + 0, // owner_ID (raw hard-wires 0) + display_port_index, // graphics_port_number + position.x, position.y, // panel origin + subsystem, // resolved generator + p[2].data.string, // background image + p[3].data.color, // extra/leak colorA + p[4].data.string, // temperature tile + p[5].data.color, // temp bg color + p[6].data.color, // temp fill color + p[7].data.string, // voltage tile + p[8].data.color, // voltage fg color + 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"); } return True; } // -// @004c746c -- ctor (vtable PTR_FUN_0051a0a0). GraphicGauge base; resolves -// CurrentTemperature/DegradationTemperature/FailureTemperature/ -// CoolantMassLeakRate/OutputVoltage attribute indices; interns the background -// image; then builds the five child gauges (each parented at this graphics -// 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. +// @004c746c -- ctor (vtable PTR_FUN_0051a0a0). GraphicGauge base (own rate hard- +// wired 0xFFFF; mode from config). Resolve the generator's temp/voltage attributes, +// intern + AddRef the panel background pixmap, build the 5 child gauges into this +// panel's port at offsets relative to the panel origin (x,y). // +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; +} //########################################################################### diff --git a/game/reconstructed/btl4gau2.hpp b/game/reconstructed/btl4gau2.hpp index 819a1a5..b813e4a 100644 --- a/game/reconstructed/btl4gau2.hpp +++ b/game/reconstructed/btl4gau2.hpp @@ -220,6 +220,16 @@ const char *tile_image, int foreground_color, int background_color, WipeGaugeScalar::Direction direction, Scalar min, Scalar max, void *voltage_source, const char *identification_string); + // @004c72ac -- the plain-Scalar variant (a DIRECT Scalar* value source, a + // GaugeConnectionDirectOf 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 }; @@ -241,27 +251,33 @@ public GraphicGauge { 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, Entity *, GaugeRenderer *); // @004c7368 - GeneratorCluster( // @004c746c - GaugeRate, ModeMask, L4GaugeRenderer *, int, - int x, int y, const char *background_image, - int /*color*/, const char *temp_tile, - Scalar *low, Scalar *high, - const char *voltage_tile, Scalar *voltage_low, - const char *leak_image, const char *lampA_image, - const char *lampB_image, int onColor, int offColor, - Subsystem *subsystem, const char *identification_string); + GeneratorCluster( // @004c746c (BINARY param order) + ModeMask, L4GaugeRenderer *, int owner_ID, int graphics_port_number, + int x, int y, Subsystem *subsystem, + 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); ~GeneratorCluster(); // @004c7778 protected: - char *backgroundImage; // @0x90 this[0x24] - int hostPortNumber; // @0x94 this[0x25] + char *backgroundImage; // @0x90 this[0x24] interned .pcc (pixelMap8Bin AddRef'd) + int secondaryColor; // @0x94 this[0x25] = leak colorB (stored, unread here) GraphicGauge *temperatureBar, // @0x98 this[0x26] VertTwoPartBar - *voltageBar, // @0x9C this[0x27] ScalarBarGauge + *voltageBar, // @0x9C this[0x27] ScalarBarGauge (@004c72ac variant) *leakGauge, // @0xA0 this[0x28] BitMapInverseWipe *lampA, // @0xA4 this[0x29] TwoState *lampB; // @0xA8 this[0x2A] TwoState Subsystem *subsystem; // @0xAC this[0x2B] + int _reserved0xB0; // @0xB0 this[0x2C] (trailing pad -> sizeof 0xB4) }; diff --git a/game/reconstructed/btl4grnd.cpp b/game/reconstructed/btl4grnd.cpp index b680f1f..1a28183 100644 --- a/game/reconstructed/btl4grnd.cpp +++ b/game/reconstructed/btl4grnd.cpp @@ -146,6 +146,18 @@ MethodDescription &BitMapInverseWipe::methodDescription, // "LeakGauge" -- coolant-leak inverse-wipe (Condenser/CoolantMassLeakRate) &VertNormalSlider::methodDescription, // "vertNormalSlider" -- condenser valve-setting slider (heat MFD) &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 }; diff --git a/game/reconstructed/powersub.cpp b/game/reconstructed/powersub.cpp index 29d30e2..fd9248f 100644 --- a/game/reconstructed/powersub.cpp +++ b/game/reconstructed/powersub.cpp @@ -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 // diff --git a/game/reconstructed/powersub.hpp b/game/reconstructed/powersub.hpp index cc57ab1..33ad713 100644 --- a/game/reconstructed/powersub.hpp +++ b/game/reconstructed/powersub.hpp @@ -352,6 +352,25 @@ class Generator; 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). // Names best-effort from the embedded "GeneratorState" / message strings.