diff --git a/game/reconstructed/btl4gau2.cpp b/game/reconstructed/btl4gau2.cpp index 9571266..d06d1ab 100644 --- a/game/reconstructed/btl4gau2.cpp +++ b/game/reconstructed/btl4gau2.cpp @@ -980,6 +980,7 @@ GeneratorCluster::GeneratorCluster( Scalar *currentTemp = (Scalar *)AttributePointerOf(subsystem_in, "CurrentTemperature"); Scalar *degradeTemp = (Scalar *)AttributePointerOf(subsystem_in, "DegradationTemperature"); Scalar *failTemp = (Scalar *)AttributePointerOf(subsystem_in, "FailureTemperature"); + extern Scalar BTHeatSinkLeakFullScale(::Subsystem *sub); // heat.cpp (#97) Scalar *coolantLeak = (Scalar *)AttributePointerOf(subsystem_in, "CoolantMassLeakRate"); Scalar *outputVolt = (Scalar *)AttributePointerOf(subsystem_in, "OutputVoltage"); @@ -1006,7 +1007,7 @@ GeneratorCluster::GeneratorCluster( // 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 + BTHeatSinkLeakFullScale(subsystem_in), // third: full-scale divisor (#97 bridge) 3, // frames coolantLeak, "LeakGauge"); @@ -1460,6 +1461,7 @@ SubsystemCluster::SubsystemCluster( void *linkedHeat = ResolveLink(heatSink); // FUN_00417ab4 void *linkTemp = AttributePointerOf(linkedHeat, "CurrentTemperature"); void *linkDegrade = AttributePointerOf(linkedHeat, "DegradationTemperature"); + extern Scalar BTHeatSinkLeakFullScale(::Subsystem *sub); // heat.cpp (#97) void *coolantLeak = AttributePointerOf(subsystem_in, "CoolantMassLeakRate"); coolingLoopB = new AnimatedSubsystemLamp(ChildRate(), eng_mode, renderer_in, // @004c70a4 @@ -1513,7 +1515,7 @@ SubsystemCluster::SubsystemCluster( leakGauge = new BitMapInverseWipe(ChildRate(), eng_mode, renderer_in, // @004c5b7c engPort, 0x255, 0xe0, "eleak.pcc", 0, 0xff, - *(int *)((char *)subsystem_in + 0x150) /*third*/, 3 /*frames*/, + BTHeatSinkLeakFullScale(subsystem_in) /*third*/, 3 /*frames*/, (Scalar *)coolantLeak, "LeakGauge"); // BEST-EFFORT raw (subsys+0x150) failedState = False; // @0xC4 this[0x31] (1 = destroyed) diff --git a/game/reconstructed/btl4gaug.cpp b/game/reconstructed/btl4gaug.cpp index 50f11f3..fb56b07 100644 --- a/game/reconstructed/btl4gaug.cpp +++ b/game/reconstructed/btl4gaug.cpp @@ -2054,7 +2054,7 @@ Logical BitMapInverseWipe::BitMapInverseWipe( GaugeRate rate, ModeMask mode_mask, L4GaugeRenderer *renderer_in, int graphics_port_number, int x, int y, const char *image, - int color_a, int color_b, int third, int frames, + int color_a, int color_b, Scalar third, int frames, Scalar *value_pointer, const char *identification_string ): GraphicGauge(rate, mode_mask, renderer_in, 0, graphics_port_number, @@ -2107,7 +2107,21 @@ void BitMapInverseWipe::BecameActive() // @004c5cf4 // void BitMapInverseWipe::Execute() { - int level = (int)(value + (value < 0.0f ? -0.5f : 0.5f)); // FUN_004dcd94 round + // #97 -- the level is NORMALISED, which this reconstruction had lost. + // Ghidra renders the round as a bare `FUN_004dcd94()` because it drops the + // x87 expression feeding __ftol (KB gotcha #19). The real prologue is: + // fild [fullWidth] ; ST0 = (float)fullWidth (frames*2) + // fmul [this+0xb4] ; ST0 *= value (leak rate) + // fdiv [this+0xb0] ; ST0 /= third (full scale) + // call __ftol ; level = round(ST0) + // Without it the gauge rounded the RAW leak rate, which never exceeds ~1.0, + // so `level` could only ever be 0 or (via the floor below) 1 -- one triangle, + // always, no matter how bad the leak. Players: "you can get up to three + // triangles"; "only seeing one level (lowest) right now". + Scalar scaled = (third > 0.0f) + ? ((Scalar)fullWidth * value / third) + : value; + int level = (int)(scaled + (scaled < 0.0f ? -0.5f : 0.5f)); // FUN_004dcd94 round if (level < 0) level = 0; if (level > fullWidth) level = fullWidth; if (value > 0.0025f && level < 1) level = 1; // _DAT_0050e3d8 diff --git a/game/reconstructed/btl4gaug.hpp b/game/reconstructed/btl4gaug.hpp index a615721..46fc213 100644 --- a/game/reconstructed/btl4gaug.hpp +++ b/game/reconstructed/btl4gaug.hpp @@ -522,7 +522,7 @@ BitMapInverseWipe( // @004c5b7c GaugeRate, ModeMask, L4GaugeRenderer *, int, int x, int y, const char *image, - int color_a, int color_b, int third, int frames, + int color_a, int color_b, Scalar third, int frames, Scalar *value_pointer, const char *); ~BitMapInverseWipe(); // @004c5c80 Logical TestInstance() const; @@ -537,7 +537,7 @@ int frameHeight; // @0xA4 this[0x29] int frames; // @0xA0 this[0x28] int previousLevel; // @0xAC this[0x2B] - int third; // @0xB0 this[0x2C] + Scalar third; // @0xB0 this[0x2C] full-scale divisor (fdiv, #97) Scalar value; // @0xB4 this[0x2D] (connection) }; diff --git a/game/reconstructed/heat.cpp b/game/reconstructed/heat.cpp index 6ac3032..2ae7b55 100644 --- a/game/reconstructed/heat.cpp +++ b/game/reconstructed/heat.cpp @@ -538,7 +538,10 @@ HeatSink::HeatSink( thermalConductance = subsystem_resource->thermalConductance; // +0xF0 heatFilter.Initialize(15, 0.0f); // FUN_0043ad4f(this+0x144, 0xF, 0) - filterDecay = 0.4f; + // @004b8fec stores 0.15f here (param_1[0x54] = 0x3e19999a). The value is + // otherwise unread in the port, but it IS the leak gauge's full-scale + // divisor: BitMapInverseWipe divides by subsystem+0x150 (#97). + filterDecay = 0.15f; // was 0.4f -- did not match the image thermalMass = subsystem_resource->thermalMass; // +0xF4 heatEnergy = thermalMass * startingTemperature; coolantFlowScale = 1.0f; @@ -1311,6 +1314,23 @@ int BTSubsystemIsCondenser(::Subsystem *sub) { return (sub != 0 && sub->IsDerivedFrom(*Condenser::GetClassDerivations())) ? 1 : 0; } +// +// #97 bridge: the leak gauge's FULL-SCALE divisor. BitMapInverseWipe computes +// level = round(fullWidth * leakRate / thirdParam) +// and the binary sources thirdParam from subsystem+0x150 (@004b8fec writes +// 0.15f there). The gauge TU cannot see the HeatSink layout, and raw-reading +// +0x150 from there would land on whatever OUR layout puts at that offset -- +// the databinding trap. Resolve it through the named member instead. +// +Scalar BTHeatSinkLeakFullScale(::Subsystem *sub) +{ + if (sub == 0 || !sub->IsDerivedFrom(*HeatSink::GetClassDerivations())) + return 0.15f; // the authored default + Scalar fs = ((HeatSink *)sub)->LeakGaugeFullScale(); + return (fs > 0.0f) ? fs : 0.15f; // never divide by zero +} + + int BTCondenserNumber(::Subsystem *sub) { return (sub != 0) ? ((Condenser *)sub)->condenserNumber : -1; // +0x1D4 diff --git a/game/reconstructed/heat.hpp b/game/reconstructed/heat.hpp index 9df46eb..bab477a 100644 --- a/game/reconstructed/heat.hpp +++ b/game/reconstructed/heat.hpp @@ -564,7 +564,9 @@ inline int Scalar startingTemperature; // @0x13C resource +0xE4 (saved initial temp) Scalar thermalConductance; // @0x140 resource +0xF0 HeatFilter heatFilter; // @0x144 15-sample running average (12 bytes -> 0x150) - Scalar filterDecay; // @0x150 init 0.4f + Scalar filterDecay; // @0x150 init 0.15f (@004b8fec) -- ALSO the + // leak gauge's full-scale divisor (#97) + Scalar LeakGaugeFullScale() const { return filterDecay; } Scalar thermalMass; // @0x154 resource +0xF4 Scalar heatEnergy; // @0x158 init = thermalMass * startingTemperature Scalar coolantFlowScale; // @0x15C init 1.0f (== "word57")