From d68284ede2dee23690658c3dc82855bba9e03578 Mon Sep 17 00:00:00 2001 From: arcattack Date: Tue, 7 Jul 2026 17:19:04 -0500 Subject: [PATCH] gauge wave P2a: build the condenser LeakGauge + valve slider (heat panel) The gauge-widget-decode workflow reconstructed the unbuilt cockpit widgets from the binary. First two (the heat MFD's missing SET/LEAK columns): - LeakGauge (BitMapInverseWipe, keyword "LeakGauge"): the class body already existed + is byte-faithful; it was just UNREGISTERED so every LeakGauge(...) config line was parse-skipped. Added the Make factory + methodDescription + the BTL4MethodDescription[] registration. Value @6 = Condenser/CoolantMassLeakRate (already published P1); 0 undamaged -> the leak wipe is authentically empty. - VertNormalSlider (keyword "vertNormalSlider"): the condenser VALVE slider @2 was PROSE-ONLY. Reconstructed all 7 functions (Make/ctor/dtor/TestInstance/ BecameActive/Execute/Draw) from part_013.c:14051-14175 (Make @004c4b08 by disassembly) -- an XOR indicator row=Round(span*value) over the track; fixed the jumbled header member layout to the byte-exact order (sizeof 0xB0). Published ValveSetting -> coolantFlowScale@0x15C on the HeatSink table so Condenser/ValveSetting resolves (verified: all 6 condensers OK). The valve indicator renders (condenser 1 shows it near the top = coolantFlowScale 1.0 = valve open). Both /FORCE-safe (every vtable slot has a real body; link log grep clean, no unresolved VertNormalSlider/BitMapInverseWipe). Verified DBASE+dev gauges: the heat panel now shows all 3 columns per condenser (TEMP scales, SET valve indicator, LEAK track), coolant bar stays full, combat un-regressed (TARGET DESTROYED), 0 crashes. The player-valve-toggle -> coolantFlowScale drive (SetValveSetting vtable+0x48) is the remaining Phase-3 valve-mechanism work; the slider shows the static valve value today. Co-Authored-By: Claude Opus 4.8 (1M context) --- game/reconstructed/btl4gaug.cpp | 229 +++++++++++++++++++++++++++++++- game/reconstructed/btl4gaug.hpp | 37 ++++-- game/reconstructed/btl4grnd.cpp | 2 + game/reconstructed/heat.cpp | 3 +- game/reconstructed/heat.hpp | 1 + 5 files changed, 256 insertions(+), 16 deletions(-) diff --git a/game/reconstructed/btl4gaug.cpp b/game/reconstructed/btl4gaug.cpp index 923dfb2..ace49e2 100644 --- a/game/reconstructed/btl4gaug.cpp +++ b/game/reconstructed/btl4gaug.cpp @@ -1230,13 +1230,168 @@ void } } +//########################################################################### +// VertNormalSlider (config keyword "vertNormalSlider" -- the condenser VALVE +// slider @2 in GenericHeatGauges1/2; vtable PTR_FUN_00518c34). Reconstructed +// from part_013.c:14051-14175 (Make @004c4b08 by disassembly). One +// GaugeConnectionDirectOf drives a normalised [0,1] value; Execute maps +// it to row = Round(span*value) and XOR-toggles a width x baseline indicator. +//########################################################################### + +// CFG shape (L4GAUGE.CFG:4839): vertNormalSlider(rate,mode,(w,h),fgColor,bgColor,thickness,valveAttr) +MethodDescription + VertNormalSlider::methodDescription = + { + "vertNormalSlider", + VertNormalSlider::Make, + { + { ParameterDescription::typeRate, NULL }, // rate ID + { ParameterDescription::typeModeMask, NULL }, // mode mask + { ParameterDescription::typeVector, NULL }, // (width,height) + { ParameterDescription::typeColor, NULL }, // fill / foreground colour + { ParameterDescription::typeColor, NULL }, // background colour + { ParameterDescription::typeInteger, NULL }, // baseline (indicator thickness) + { ParameterDescription::typeAttribute, NULL }, // value source (Condenser/ValveSetting, Scalar [0,1]) + PARAMETER_DESCRIPTION_END + } + }; + // -// @004c4b84 ctor / @004c4cac BecameActive / @004c4cc0 Execute -// -- VertNormalSlider (vtable PTR_FUN_00518c34): one GaugeConnectionDirectOf -// drives a normalised [0,1] value (clamped via _DAT_004c4d3c=0.0 / -// _DAT_004c4d40=1.0, saturating to 0x3f800000); Execute @004c4cc0 maps it to a -// row and calls Draw @004c4d48 which MoveTo()s and FillTo()s the lit rectangle. +// @004c4b08 -- Make. Allocate 0xb0 + placement-construct. No owned bitmap, so no +// image-exists check; always returns True (the binary returns eax=1 even on alloc +// failure). `entity` unused (binds by attribute pointer already resolved into +// parameterList[6]). // +Logical + VertNormalSlider::Make( + int display_port_index, + Vector2DOf position, + Entity * /*entity -- unused*/, + GaugeRenderer *gauge_renderer + ) +{ + ParameterDescription *p = methodDescription.parameterList; + + VertNormalSlider *gauge = (VertNormalSlider *)operator new(0xb0); // FUN_00402298(0xb0) + if (gauge != NULL) + { + new (gauge) VertNormalSlider( // FUN_004c4b84 + p[0].data.rate, p[1].data.modeMask, + (L4GaugeRenderer *)gauge_renderer, + display_port_index, // graphics_port_number + position.x, position.y, // left, bottom + position.x + p[2].data.vector.x, // right = x + width + position.y + p[2].data.vector.y, // top = y + height + p[3].data.color, // fill / foreground colour + p[4].data.color, // background colour + p[5].data.integer, // baseline (indicator thickness) + (Scalar *)p[6].data.attributePointer, // value source (Condenser/ValveSetting) + "VertNormalSlider"); + } + return True; +} + +// +// @004c4b84 -- ctor (vtable PTR_FUN_00518c34). GraphicGauge base; set the port +// extent (bottom->top), latch the raster op to XOR + the pen to the fill colour +// ONCE (so every Draw toggles the indicator in place), cache geometry, wire one +// Scalar connection. previousFill is NOT init here -- BecameActive sets it -1. +// +VertNormalSlider::VertNormalSlider( + GaugeRate rate, + ModeMask mode_mask, + L4GaugeRenderer *renderer_in, + int graphics_port_number, + int left, + int bottom, + int right, + int top, + int fill_color, + int background_color, + int baseline_in, + Scalar *value_pointer, + const char *identification_string +): + GraphicGauge(rate, mode_mask, renderer_in, 0, // FUN_00444818 (owner_ID 0) + graphics_port_number, identification_string) +{ + localView.SetPositionWithinPort(left, bottom, right, top); // vtbl+0x08 + localView.SetOperation(GraphicsDisplay::Xor); // vtbl+0x0c (op == 3) + localView.SetColor(fill_color); // vtbl+0x18 + + fillColor = fill_color; // @0xA4 + backgroundColor = background_color; // @0xA8 + baseline = baseline_in; // @0x9C + width = (right - left) - 1; // @0x94 + height = top - bottom; // @0x98 + span = (top - bottom) - baseline_in; // @0xA0 + + AddConnection(new GaugeConnectionDirectOf(0, &value, value_pointer)); // -> @0xAC +} + +// +// @004c4c68 -- dtor. Owns no resource; the connection + localView are released by +// the base teardown, so the body is empty (the base-dtor chain runs implicitly). +// +VertNormalSlider::~VertNormalSlider() +{ +} + +// +// @004c4c94 -- TestInstance. Non-virtual out-of-line forward to the base (its body +// MUST exist because the header declares it -- else /FORCE stubs it to an AV). +// +Logical + VertNormalSlider::TestInstance() const +{ + return GraphicGauge::TestInstance(); +} + +// +// @004c4cac -- BecameActive. Invalidate the cached row so the first Execute repaints. +// +void + VertNormalSlider::BecameActive() +{ + previousFill = -1; // @0x90 +} + +// +// @004c4cc0 -- Execute. Clamp value to [0,1], map to row = Round(span*value), and +// on a change XOR-erase the old indicator + XOR-draw the new one. +// +void + VertNormalSlider::Execute() +{ + if (value < 0.0f) + value = 0.0f; + else if (value > 1.0f) + value = 1.0f; + + int pixel = HeatRound((Scalar)span * value); // row + + if (pixel != previousFill) + { + Draw(); // erase old (XOR at old previousFill) + previousFill = pixel; + Draw(); // draw new (XOR at new previousFill) + } +} + +// +// @004c4d48 -- Draw (file-private, non-virtual). Move the pen to the track's left +// edge at the current row, draw a width x baseline filled rectangle (XOR op + fill +// colour latched in the ctor, so a re-Draw at the same row erases). +// +void + VertNormalSlider::Draw() +{ + if (previousFill >= 0) + { + localView.MoveToAbsolute(0, previousFill); // vtbl+0x24 + localView.DrawFilledRectangleToRelative(width, baseline); // vtbl+0x4c + } +} //########################################################################### @@ -1649,6 +1804,70 @@ void OneOfSeveralStates::BecameActive() // @004c552c OneOfSeveral::BecameActive(); } +// +// gauge wave P2 -- the "LeakGauge" config factory + methodDescription. The class +// body (ctor/dtor/BecameActive/Execute) already existed + is byte-faithful; it was +// just UNREGISTERED, so every `LeakGauge(...)` config line (GenericHeatGauges1/2) +// was parse-skipped and the coolant-leak inverse-wipe never built. CFG shape +// (L4GAUGE.CFG:4858): LeakGauge(rate,mode,image.pcc,colorA,colorB,frames,third,levelAttr). +// value source @6 = Condenser/CoolantMassLeakRate -- already published (heat.cpp:345). +// +MethodDescription + BitMapInverseWipe::methodDescription = + { + "LeakGauge", + BitMapInverseWipe::Make, + { + { ParameterDescription::typeRate, NULL }, // rate ID + { ParameterDescription::typeModeMask, NULL }, // mode mask + { ParameterDescription::typeString, NULL }, // leak strip bitmap -> @0x90 + { ParameterDescription::typeColor, NULL }, // colorA empty-cell -> @0x94 + { ParameterDescription::typeColor, NULL }, // colorB leak-cell -> @0x98 + { ParameterDescription::typeInteger, NULL }, // frames (=3) -> frames/fullWidth + { ParameterDescription::typeScalar, NULL }, // third (.15; raw @0xB0, inert in base Execute) + { ParameterDescription::typeAttribute, NULL }, // level source (Scalar CoolantMassLeakRate) + PARAMETER_DESCRIPTION_END + } + }; + +Logical + BitMapInverseWipe::Make( + int display_port_index, + Vector2DOf position, + Entity * /*entity -- unused*/, + GaugeRenderer *gauge_renderer + ) +{ + ParameterDescription *p = methodDescription.parameterList; + + BitMapInverseWipe *gauge = (BitMapInverseWipe *)operator new(0xb8); // FUN_00402298(0xb8) + if (gauge != NULL) + { + // NOTE the ctor lists `third` BEFORE `frames`, so map p[6]->third, p[5]->frames BY NAME. + new (gauge) BitMapInverseWipe( // FUN_004c5b7c + p[0].data.rate, p[1].data.modeMask, + (L4GaugeRenderer *)gauge_renderer, + display_port_index, // graphics_port_number + position.x, position.y, // -> localView.SetOrigin(x,y) + p[2].data.string, // image (eleak.pcc) + p[3].data.color, // color_a + p[4].data.color, // color_b + p[6].data.integer, // third (.15 raw bits; inert) -> @0xB0 + p[5].data.integer, // frames (3) + (Scalar *)p[7].data.attributePointer, // level source (Condenser/CoolantMassLeakRate) + "LeakGauge"); + } + + L4Warehouse *warehouse = (L4Warehouse *)gauge_renderer->warehousePointer; + if (warehouse->bitMapBin.Get(p[2].data.string) == NULL) // FUN_00442aec + { + DebugStream << "LeakGauge: Missing image '" << p[2].data.string << "'\n"; + return False; + } + warehouse->bitMapBin.Release(p[2].data.string); // FUN_00442c12 + return True; +} + // // @004c5b7c -- BitMapInverseWipe ctor (vtable 0x518a9c): GraphicGauge base; // SetOrigin(x,y), intern the image, store colours + frame geometry (frameWidth = diff --git a/game/reconstructed/btl4gaug.hpp b/game/reconstructed/btl4gaug.hpp index f93a460..c8bd482 100644 --- a/game/reconstructed/btl4gaug.hpp +++ b/game/reconstructed/btl4gaug.hpp @@ -308,26 +308,37 @@ public GraphicGauge { public: + // gauge wave P2: the "vertNormalSlider" config factory (the condenser VALVE + // slider @2 in GenericHeatGauges1/2). Registered in BTL4MethodDescription[] + // (btl4grnd.cpp); the ctor/Execute existed only as prose, so the keyword was + // parse-skipped and the SET column never built. + static MethodDescription methodDescription; + static Logical Make(int, Vector2DOf, Entity *, GaugeRenderer *); // @004c4b08 + VertNormalSlider( // @004c4b84 GaugeRate, ModeMask, L4GaugeRenderer *, int, int left, int bottom, int right, int top, int fill_color, int background_color, int baseline, Scalar *value_pointer, const char *identification_string); - ~VertNormalSlider(); + ~VertNormalSlider(); // @004c4c68 - Logical TestInstance() const; + Logical TestInstance() const; // @004c4c94 -> GraphicGauge::TestInstance void BecameActive(); // @004c4cac void Execute(); // @004c4cc0 -> Draw @004c4d48 + private: + void Draw(); // @004c4d48 (XOR indicator blit; non-virtual) protected: - Scalar value; // @0xAC this[0x2B] (connection) - int previousFill; // @0x90 this[0x24] - int fillColor; // @0x94 this[0x25] - int backgroundColor; // @0x98 this[0x26] - int baseline; // @0x9C this[0x27] - int width; // @0xA0 this[0x28] - int height; // @0xA4 this[0x29] - int span; // @0xA8 this[0x2A] + // Corrected byte-exact layout (from the ctor's this[N] stores @004c4b84; + // the earlier order was jumbled). 8 words, 0x90..0xAF, sizeof == 0xB0. + int previousFill; // @0x90 this[0x24] XOR indicator row; -1 = none + int width; // @0x94 this[0x25] (right-left)-1 + int height; // @0x98 this[0x26] top-bottom (track height) + int baseline; // @0x9C this[0x27] indicator thickness + int span; // @0xA0 this[0x28] height-baseline (travel) + int fillColor; // @0xA4 this[0x29] indicator colour + int backgroundColor; // @0xA8 this[0x2A] stored, unused by Draw + Scalar value; // @0xAC this[0x2B] connection dst [0,1] }; @@ -488,6 +499,12 @@ public GraphicGauge { public: + // gauge wave P2: registered in BTL4MethodDescription[] (btl4grnd.cpp) as + // "LeakGauge" -- the config factory the class was missing (the ctor/Execute + // already existed but the keyword was parse-skipped so it never built). + static MethodDescription methodDescription; + static Logical Make(int, Vector2DOf, Entity *, GaugeRenderer *); // @004c... (config factory) + BitMapInverseWipe( // @004c5b7c GaugeRate, ModeMask, L4GaugeRenderer *, int, int x, int y, const char *image, diff --git a/game/reconstructed/btl4grnd.cpp b/game/reconstructed/btl4grnd.cpp index 897c234..fa683e6 100644 --- a/game/reconstructed/btl4grnd.cpp +++ b/game/reconstructed/btl4grnd.cpp @@ -143,6 +143,8 @@ MethodDescription &MapDisplay::methodDescription, // "map" -- the radar / tactical display &PlayerStatus::methodDescription, // "PlayerStatus" -- comm/score name-tag gauge &VehicleSubSystems::methodDescription, // "vehicleSubSystems" -- engineering-screen subsystem cluster panels + &BitMapInverseWipe::methodDescription, // "LeakGauge" -- coolant-leak inverse-wipe (Condenser/CoolantMassLeakRate) + &VertNormalSlider::methodDescription, // "vertNormalSlider" -- condenser valve-setting slider (heat MFD) &BTL4ChainToPrevious }; diff --git a/game/reconstructed/heat.cpp b/game/reconstructed/heat.cpp index 5212dbb..8e489d8 100644 --- a/game/reconstructed/heat.cpp +++ b/game/reconstructed/heat.cpp @@ -343,7 +343,8 @@ const HeatSink::IndexEntry ATTRIBUTE_ENTRY(HeatSink, NormalizedPressure, heatLoad), // @0x120 (smoothed radiated heat) ATTRIBUTE_ENTRY(HeatSink, DegradationPressure, coolantEfficiency), // @0x124 ATTRIBUTE_ENTRY(HeatSink, CoolantMassLeakRate, coolantDraw), // @0x130 (LeakGauge; damage-driven) - ATTRIBUTE_ENTRY(HeatSink, HeatSink, linkedSinks) // @0x164 (Eng linked-sink temp readout) + ATTRIBUTE_ENTRY(HeatSink, HeatSink, linkedSinks), // @0x164 (Eng linked-sink temp readout) + ATTRIBUTE_ENTRY(HeatSink, ValveSetting, coolantFlowScale) // @0x15C (condenser valve slider @2; init 1.0f) }; HeatSink::AttributeIndexSet& diff --git a/game/reconstructed/heat.hpp b/game/reconstructed/heat.hpp index 3b0c0fa..eb61e45 100644 --- a/game/reconstructed/heat.hpp +++ b/game/reconstructed/heat.hpp @@ -343,6 +343,7 @@ inline int DegradationPressureAttributeID, // @0x124 coolantEfficiency CoolantMassLeakRateAttributeID, // @0x130 coolantDraw (damage-driven leak) HeatSinkAttributeID, // @0x164 linkedSinks (link to master sink) + ValveSettingAttributeID, // @0x15C coolantFlowScale (condenser valve slider @2) NextAttributeID }; private: