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) <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-07 17:19:04 -05:00
co-authored by Claude Opus 4.8
parent 8379693a76
commit d68284ede2
5 changed files with 256 additions and 16 deletions
+224 -5
View File
@@ -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<Scalar> 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
// <Scalar> 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<int> 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<Scalar>(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<int> 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 =
+27 -10
View File
@@ -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<int>, 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<int>, Entity *, GaugeRenderer *); // @004c... (config factory)
BitMapInverseWipe( // @004c5b7c
GaugeRate, ModeMask, L4GaugeRenderer *, int,
int x, int y, const char *image,
+2
View File
@@ -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
};
+2 -1
View File
@@ -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&
+1
View File
@@ -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: