From 00805cfe77c00fef1e688abfaa886c83f94f6631 Mon Sep 17 00:00:00 2001 From: jerrrrycho Date: Sat, 17 Oct 2026 17:15:28 +0200 Subject: [PATCH] fix: review findings of PR 217 - the pressures of the mover are in bar (MinCP=7.0 in the vehicle definitions), so the 0.2 the pneumatic horns needed was 0.2 bar; it's 2 bar now, and the tests take their pressures in bar too, - the lamps of the ground relay and of the motor overload relays follow the pressure switch as the line contactors one does, TMoverParameters::MainCircuitLampsEnabled(), so the lamps of one cab go out together, - the joint oil and fuel pump switch leaves out a pump started automatically, which ignores the switching: with one, it couldn't turn the other off, and showed off with both running (OilFuelPumpsEnabled()), - the kind of the train heating switch comes from the occupied vehicle on entering the cab too, as in the switch commands; in a multiple unit these are different vehicles, - CouplerForce() put the force taken back by the other vehicle on its far coupler when the vehicles faced each other (Couplers[1 - End] rather than the coupler it's coupled with), - the velocity difference along the coupler axis computed once, CouplerVelocityDifference(), for CouplerForce() and CollisionVelocity(), - the attachcoupler flag added without the SetFlag() call with 0, the index of the mirror moves noted as by side. Tests: the coupler force of vehicles facing each other, the joint switch with an automatic pump, the lamps of the main circuit, the horn and compressor pressures in bar. --- McZapkie/MOVER.h | 25 +++++++++++--- McZapkie/Mover.cpp | 19 +++++++---- tests/unit/test_compressor.cpp | 12 +++---- tests/unit/test_couplers.cpp | 16 +++++++++ tests/unit/test_horn.cpp | 8 ++--- tests/unit/test_line_contactors.cpp | 11 ++++++ tests/unit/test_pumps.cpp | 52 +++++++++++++++++++++++++++++ vehicle/DynObj.h | 2 +- vehicle/Train.cpp | 12 +++---- 9 files changed, 128 insertions(+), 29 deletions(-) create mode 100644 tests/unit/test_pumps.cpp diff --git a/McZapkie/MOVER.h b/McZapkie/MOVER.h index b0c5d8e9..bb36694b 100644 --- a/McZapkie/MOVER.h +++ b/McZapkie/MOVER.h @@ -1719,8 +1719,8 @@ class TMoverParameters int EmergencyBrakeWarningSignal{0}; // combined with basic WarningSignal when manual emergency brake is active horn_supply HornSupply{horn_supply::air}; // of the low and high tone horns (WarningSignal 1 and 2) horn_supply Horn3Supply{horn_supply::air}; // of the third horn (WarningSignal 4) - // main reservoir pressure [MPa] below which a pneumatic horn stays silent - static constexpr double HornMinPressure{0.2}; + // main reservoir pressure [bar, as MinCP and MaxCP] below which a pneumatic horn stays silent + static constexpr double HornMinPressure{2.0}; TUniversalCtrlTable UniCtrlList; /*lista pozycji uniwersalnego nastawnika*/ int UniCtrlListSize = 0; /*wielkosc listy pozycji uniwersalnego nastawnika*/ bool UniCtrlIntegratedBrakePNCtrl = false; /*zintegrowany nastawnik JH obsluguje hamulec PN*/ @@ -2447,16 +2447,31 @@ class TMoverParameters // closing speed [km/h] of the End of this vehicle and the OtherEnd of the other vehicle it touches, along the coupler // axis: the sum of the speeds in a head-on collision, the difference in a rear-end one, negative when they part double CollisionVelocity(int const End, TMoverParameters const &Other, int const OtherEnd) const; + // velocity [m/s] of this vehicle relative to the other vehicle it touches with its End and the OtherEnd of the + // other one, along this vehicle's axis + double CouplerVelocityDifference(int const End, TMoverParameters const &Other, int const OtherEnd) const; // whether the automatic couplers at the End of this vehicle and the OtherEnd of the other one can catch each other on // contact, rather than the vehicles colliding (with crash damage enabled, only at a closing speed which doesn't derail them) bool AutomaticCouplingSafe(int const End, TMoverParameters const &Other, int const OtherEnd) const; // the horns of the Signal (WarningSignal bits) which have the air or the voltage they need to sound int SoundingHorns(int const Signal) const; - // whether the lamp of the open line contactors is lit, telling the start can be made: the pressure switch, when - // fitted, cuts the start off and so puts out the lamp, otherwise the lamp goes out once the brakes are applied + // whether the signal lamps of the main circuit (line contactors, ground relay, motor overload relays) can light: + // the pressure switch, when fitted, cuts the start off and puts them out, otherwise they go out once the brakes are applied + bool MainCircuitLampsEnabled() const + { + return false == ControlPressureSwitch && (HasControlPressureSwitch || BrakePress < 1.0); + } + // whether the lamp of the open line contactors is lit, telling the start can be made bool LineContactorsLampLit() const { - return false == StLinFlag && false == ControlPressureSwitch && (HasControlPressureSwitch || BrakePress < 1.0); + return false == StLinFlag && MainCircuitLampsEnabled(); + } + // whether the pumps a joint oil and fuel pump switch works, those not started automatically, are all switched on + bool OilFuelPumpsEnabled() const + { + auto const fuelmanual{FuelPump.start_type != start_t::automatic}; + auto const oilmanual{OilPump.start_type != start_t::automatic}; + return (fuelmanual || oilmanual) && (false == fuelmanual || FuelPump.is_enabled) && (false == oilmanual || OilPump.is_enabled); } // whether the radio is switched on and has the low voltage to work bool RadioPowered() const diff --git a/McZapkie/Mover.cpp b/McZapkie/Mover.cpp index 02762301..8840ef33 100644 --- a/McZapkie/Mover.cpp +++ b/McZapkie/Mover.cpp @@ -1183,13 +1183,18 @@ double TMoverParameters::EngineMaxRPM() const // ************************************************************************************************* double TMoverParameters::CollisionVelocity(int const End, TMoverParameters const &Other, int const OtherEnd) const { - // V runs along the vehicle's own axis, towards its front; the other vehicle's velocity is turned around when the - // vehicles face each other (touch with the same ends), as in CouplerForce() - auto const velocitydifference{V - Other.V * DirPatch(End, OtherEnd)}; + auto const velocitydifference{CouplerVelocityDifference(End, Other, OtherEnd)}; // the front end closes in on the other vehicle when this one moves forward faster, the rear end when it's slower return (End == end::front ? velocitydifference : -velocitydifference) * 3.6; // m/s -> km/h } +double TMoverParameters::CouplerVelocityDifference(int const End, TMoverParameters const &Other, int const OtherEnd) const +{ + // V runs along the vehicle's own axis, towards its front; the other vehicle's velocity is turned around when the + // vehicles face each other (touch with the same ends) + return V - Other.V * DirPatch(End, OtherEnd); +} + bool TMoverParameters::AutomaticCouplingSafe(int const End, TMoverParameters const &Other, int const OtherEnd) const { return false == Global.crash_damage || CollisionVelocity(End, Other, OtherEnd) <= SafeCollisionVelocity; @@ -5331,7 +5336,7 @@ double TMoverParameters::CouplerForce(int const End, double dt) auto const newdistance{initialdistance + 10.0 * distancedelta}; - auto const dV{V - othervehicle->V * DirPatch(End, otherend)}; + auto const dV{CouplerVelocityDifference(End, *othervehicle, otherend)}; auto const absdV{std::abs(dV)}; // potentially generate sounds on clash or stretch @@ -5448,7 +5453,7 @@ double TMoverParameters::CouplerForce(int const End, double dt) auto const safe{AutomaticCouplingSafe(End, *othervehicle, otherend)}; auto const caught{safe && Attach(End, otherend, othervehicle, couplingtype)}; // HACK: we're reusing sound enum to mark whether vehicle was connected to another - SetFlag(AIFlag, caught ? sound::attachcoupler : 0); + AIFlag |= (caught ? sound::attachcoupler : 0); // coupled, or locked until the vehicles part, so they don't couple as soon as the collision slows them down thiscoupler.AutomaticCouplingAllowed = safe && false == caught; othercoupler.AutomaticCouplingAllowed = safe && false == caught; @@ -5458,8 +5463,8 @@ double TMoverParameters::CouplerForce(int const End, double dt) if (thiscoupler.CouplingFlag != coupling::faux) { - // uzgadnianie prawa Newtona - othervehicle->Couplers[1 - End].CForce = -CF; + // uzgadnianie prawa Newtona, on the coupler of the other vehicle this one is coupled with + othervehicle->Couplers[otherend].CForce = -CF; } return CF; diff --git a/tests/unit/test_compressor.cpp b/tests/unit/test_compressor.cpp index 4a69c24a..c5aa2fff 100644 --- a/tests/unit/test_compressor.cpp +++ b/tests/unit/test_compressor.cpp @@ -14,9 +14,9 @@ double const step{0.01}; // a compressor which starts on its own, fed from the main circuit unless given another Power, its pressure // switch between MinCP and MaxCP, and an empty main reservoir for it to fill -vehicle compressor_vehicle(double const Delay, std::string const &Power = "Main", double const MinCP = 0.65) { +vehicle compressor_vehicle(double const Delay, std::string const &Power = "Main", double const MinCP = 6.5) { auto definition{definition::load("wagon")}; - definition.set("Brake", "Vv", 1.0).set("Brake", "MinCP", MinCP).set("Brake", "MaxCP", 0.8); + definition.set("Brake", "Vv", 1.0).set("Brake", "MinCP", MinCP).set("Brake", "MaxCP", 8.0); definition.set("Brake", "CompressorSpeed", 0.01); if (false == Power.empty()) { definition.set("Brake", "CompressorPower", Power); @@ -99,17 +99,17 @@ TEST_CASE("compressor: a compressor fed from the low voltage waits from the low TEST_CASE("compressor: the pressure switch stops the compressor above the cut-out pressure") { auto vehicle{compressor_vehicle(0.0)}; vehicle->Mains = true; - vehicle->CompressedVolume = 0.85; // over MaxCP, in the reservoir of 1 m3 + vehicle->CompressedVolume = 8.5; // over MaxCP, in the reservoir of 1 m3 run_compressor(vehicle, step); CHECK(vehicle->CompressorGovernorLock); CHECK_FALSE(vehicle->CompressorFlag); } TEST_CASE("compressor: without a pressure switch the air over the cut-out pressure is vented") { - auto vehicle{compressor_vehicle(0.0, "Main", 0.8)}; // MinCP the same as MaxCP: no pressure switch + auto vehicle{compressor_vehicle(0.0, "Main", 8.0)}; // MinCP the same as MaxCP: no pressure switch vehicle->Mains = true; - vehicle->CompressedVolume = 0.85; + vehicle->CompressedVolume = 8.5; run_compressor(vehicle, step); REQUIRE(vehicle->CompressorFlag); // nothing stops it - CHECK(vehicle->CompressedVolume < 0.85 * 0.81); // a fifth of the air let out + CHECK(vehicle->CompressedVolume < 8.5 * 0.81); // a fifth of the air let out } diff --git a/tests/unit/test_couplers.cpp b/tests/unit/test_couplers.cpp index 46d2ff07..108b4f59 100644 --- a/tests/unit/test_couplers.cpp +++ b/tests/unit/test_couplers.cpp @@ -424,3 +424,19 @@ TEST_CASE("couplers: automatic couplers of different control systems couple with CHECK(pair.coupling() == (coupling::coupler | coupling::brakehose)); } } + +TEST_CASE("couplers: the coupler force of vehicles facing each other lands on the couplers holding them") { + // the force a vehicle takes from its coupler, the other one takes back on its own coupler of the pair, whichever + // way the vehicles face; it used to land on the far end of the other vehicle when they faced each other + auto first{automatic_wagon(5.0, "first")}; + auto second{automatic_wagon(0.0, "second")}; + test::pair pair{first, end::front, second, end::front, 1.0}; + pair.run(5.0); + REQUIRE(pair.coupled()); + + pair.step(); + REQUIRE(second->Couplers[end::front].CForce != 0.0); + CHECK(first->Couplers[end::front].CForce == doctest::Approx(-second->Couplers[end::front].CForce)); + CHECK(first->Couplers[end::rear].CForce == 0.0); // nothing there + CHECK(second->Couplers[end::rear].CForce == 0.0); +} diff --git a/tests/unit/test_horn.cpp b/tests/unit/test_horn.cpp index b90853ef..18e5c532 100644 --- a/tests/unit/test_horn.cpp +++ b/tests/unit/test_horn.cpp @@ -46,7 +46,7 @@ namespace { test::vehicle horn_vehicle(test::definition Definition) { // a main reservoir of its own - return test::vehicle{Definition.set("Brake", "Vv", 1.0).set("Brake", "MinCP", 0.65).set("Brake", "MaxCP", 0.8)}; + return test::vehicle{Definition.set("Brake", "Vv", 1.0).set("Brake", "MinCP", 6.5).set("Brake", "MaxCP", 8.0)}; } int const allhorns{horn::low | horn::high | 4}; @@ -57,7 +57,7 @@ TEST_CASE("horn: a pneumatic horn sounds on the air of the main reservoir only") auto vehicle{horn_vehicle(test::definition::load("wagon"))}; REQUIRE(vehicle->HornSupply == horn_supply::air); // the default - vehicle->Compressor = 0.65; + vehicle->Compressor = 6.5; CHECK(vehicle->SoundingHorns(allhorns) == allhorns); vehicle->Compressor = 0.0; // the reservoir is empty CHECK(vehicle->SoundingHorns(allhorns) == 0); @@ -67,7 +67,7 @@ TEST_CASE("horn: a vehicle without a main reservoir takes the horn air from the test::vehicle vehicle{test::definition::load("wagon")}; REQUIRE(vehicle->VeselVolume == 0.0); - vehicle->ScndPipePress = 0.5; + vehicle->ScndPipePress = 5.0; CHECK(vehicle->SoundingHorns(horn::low) == horn::low); vehicle->ScndPipePress = 0.0; CHECK(vehicle->SoundingHorns(horn::low) == 0); @@ -93,7 +93,7 @@ TEST_CASE("horn: the third horn can have a supply of its own, an electric horn o vehicle->Compressor = 0.0; vehicle->Power24vIsAvailable = true; CHECK(vehicle->SoundingHorns(allhorns) == 4); - vehicle->Compressor = 0.65; + vehicle->Compressor = 6.5; vehicle->Power24vIsAvailable = false; CHECK(vehicle->SoundingHorns(allhorns) == (horn::low | horn::high)); } diff --git a/tests/unit/test_line_contactors.cpp b/tests/unit/test_line_contactors.cpp index 783750ec..79c419ed 100644 --- a/tests/unit/test_line_contactors.cpp +++ b/tests/unit/test_line_contactors.cpp @@ -46,3 +46,14 @@ TEST_CASE("line contactors: closed contactors put the lamp out") { vehicle->StLinFlag = true; CHECK_FALSE(vehicle->LineContactorsLampLit()); } + +TEST_CASE("line contactors: the ground relay and overload lamps follow the same rule") { + // all the lamps of the main circuit go out together, with the pressure switch where it's fitted + auto vehicle{locomotive(true)}; + vehicle->BrakePress = 1.5; + vehicle->MotorConnectorsCheck(); + CHECK(vehicle->MainCircuitLampsEnabled()); + vehicle->BrakePress = 2.5; + vehicle->MotorConnectorsCheck(); + CHECK_FALSE(vehicle->MainCircuitLampsEnabled()); +} diff --git a/tests/unit/test_pumps.cpp b/tests/unit/test_pumps.cpp new file mode 100644 index 00000000..2245e3eb --- /dev/null +++ b/tests/unit/test_pumps.cpp @@ -0,0 +1,52 @@ +#include "stdafx.h" +#include "doctest.h" +#include "vehicle.h" + +// the joint oil and fuel pump switch (MaSzyna-EU07/maszyna#81) works the pumps which aren't started automatically + +namespace { + +test::vehicle pumps_vehicle(std::string const &Fuelstart, std::string const &Oilstart) { + return test::vehicle{test::definition::load("wagon").set("Cntrl", "FuelStart", Fuelstart).set("Cntrl", "OilStart", Oilstart)}; +} + +// what the two-state joint switch does when pressed, as TTrain::OnCommand_oilfuelpumptoggle() +void press_joint_switch(test::vehicle &Vehicle) { + auto const enable{false == Vehicle->OilFuelPumpsEnabled()}; + Vehicle->FuelPumpSwitch(enable, range_t::local); + Vehicle->FuelPumpSwitchOff(false == enable, range_t::local); + Vehicle->OilPumpSwitch(enable, range_t::local); + Vehicle->OilPumpSwitchOff(false == enable, range_t::local); +} + +} // namespace + +TEST_CASE("pumps: the joint switch turns both manual pumps on, then off") { + auto vehicle{pumps_vehicle("Manual", "Manual")}; + REQUIRE_FALSE(vehicle->OilFuelPumpsEnabled()); + press_joint_switch(vehicle); + CHECK(vehicle->FuelPump.is_enabled); + CHECK(vehicle->OilPump.is_enabled); + CHECK(vehicle->OilFuelPumpsEnabled()); + press_joint_switch(vehicle); + CHECK_FALSE(vehicle->FuelPump.is_enabled); + CHECK_FALSE(vehicle->OilPump.is_enabled); +} + +TEST_CASE("pumps: with one pump started automatically the joint switch still turns the other one off") { + auto vehicle{pumps_vehicle("Manual", "Automatic")}; + press_joint_switch(vehicle); + REQUIRE(vehicle->FuelPump.is_enabled); + CHECK(vehicle->OilFuelPumpsEnabled()); // the switch shows on + press_joint_switch(vehicle); + CHECK_FALSE(vehicle->FuelPump.is_enabled); +} + +TEST_CASE("pumps: with one manual pump on and the other off the joint switch turns both on") { + auto vehicle{pumps_vehicle("Manual", "Manual")}; + vehicle->FuelPumpSwitch(true, range_t::local); + REQUIRE_FALSE(vehicle->OilFuelPumpsEnabled()); + press_joint_switch(vehicle); + CHECK(vehicle->FuelPump.is_enabled); + CHECK(vehicle->OilPump.is_enabled); +} diff --git a/vehicle/DynObj.h b/vehicle/DynObj.h index 5d52ba16..2d9636b2 100644 --- a/vehicle/DynObj.h +++ b/vehicle/DynObj.h @@ -576,7 +576,7 @@ private: std::vector m_doorsounds; // can expect symmetrical arrangement, but don't count on it bool m_doorlocks { false }; // sound helper, current state of door locks std::vector m_mirrorsounds; - std::array m_mirrormotion; // left and right + std::array m_mirrormotion; // indexed by side: right, left sound_source sHorn1 { sound_placement::external, 5 * EU07_SOUND_RUNNINGNOISECUTOFFRANGE }; sound_source sHorn2 { sound_placement::external, 5 * EU07_SOUND_RUNNINGNOISECUTOFFRANGE }; sound_source sHorn3 { sound_placement::external, 5 * EU07_SOUND_RUNNINGNOISECUTOFFRANGE }; diff --git a/vehicle/Train.cpp b/vehicle/Train.cpp index 1272a137..201b8972 100644 --- a/vehicle/Train.cpp +++ b/vehicle/Train.cpp @@ -4019,8 +4019,8 @@ void TTrain::OnCommand_oilfuelpumptoggle(TTrain *Train, command_data const &Comm { return; } - // both pumps go off if both were on, otherwise both go on - auto const enable{false == (vehicle->FuelPump.is_enabled && vehicle->OilPump.is_enabled)}; + // both pumps go off if both were on, otherwise both go on; a pump started automatically doesn't take part + auto const enable{false == vehicle->OilFuelPumpsEnabled()}; Train->ggOilFuelPumpButton.UpdateValue(enable ? 1.0 : 0.0, Train->dsbSwitch); vehicle->FuelPumpSwitch(enable); vehicle->FuelPumpSwitchOff(false == enable); @@ -8980,8 +8980,8 @@ bool TTrain::Update(double const Deltatime) btLampkaOpory.Turn(mvControlled->StLinFlag && mvControlled->ResistorsFlagCheck()); btLampkaBezoporowa.Turn(mvControlled->ResistorsFlagCheck() || mvControlled->MainCtrlActualPos == 0); // do EU04 btLampkaStyczn.Turn(mvControlled->LineContactorsLampLit()); // mozna prowadzic rozruch - btLampkaPrzekRozn.Turn(!mvControlled->GroundRelay && !mvControlled->ControlPressureSwitch && mvControlled->BrakePress < 1.0); // relay is off and needs a reset - btLampkaNadmSil.Turn(mvControlled->FuseFlagCheck() && !mvControlled->ControlPressureSwitch && mvControlled->BrakePress < 1.0); // relay is off and needs a reset + btLampkaPrzekRozn.Turn(!mvControlled->GroundRelay && mvControlled->MainCircuitLampsEnabled()); // relay is off and needs a reset + btLampkaNadmSil.Turn(mvControlled->FuseFlagCheck() && mvControlled->MainCircuitLampsEnabled()); // relay is off and needs a reset btLampkaUkrotnienie.Turn(TestFlag(mvControlled->Couplers[mvControlled->CabOccupied == 1 ? rear : front].CouplingFlag, control)); btLampkaHamPosp.Turn(TestFlag(mvOccupied->Hamulec->GetBrakeStatus(), 1)); // lampka drugiego stopnia hamowania // TODO: youBy wyciągnąć flagę wysokiego stopnia @@ -11405,7 +11405,7 @@ void TTrain::set_cab_controls(int const Cab) ggDoorStepButton.PutValue(mvOccupied->Doors.step_enabled ? 1.f : 0.f); } // heating - if (false == ggTrainHeatingButton.is_push() && false == mvControlled->HeatingSwitchImpulse) + if (false == ggTrainHeatingButton.is_push() && false == mvOccupied->HeatingSwitchImpulse) { ggTrainHeatingButton.PutValue(mvControlled->Heating ? 1.f : 0.f); } @@ -11461,7 +11461,7 @@ void TTrain::set_cab_controls(int const Cab) // joint oil and fuel pump if (ggOilFuelPumpButton.type() != TGaugeType::push) { - ggOilFuelPumpButton.PutValue(mvControlled->FuelPump.is_enabled && mvControlled->OilPump.is_enabled ? 1.f : 0.f); + ggOilFuelPumpButton.PutValue(mvControlled->OilFuelPumpsEnabled() ? 1.f : 0.f); } // traction motor fans if (ggMotorBlowersFrontButton.type() != TGaugeType::push)