From 5bc4e7cd2e15e19d6f85ec0ace1f666941983f98 Mon Sep 17 00:00:00 2001 From: jerrrrycho Date: Sat, 7 Nov 2026 21:01:54 +0100 Subject: [PATCH] feat: lock field shunting at zero and on resistive positions New optional Cntrl. key ScndCtrlResistiveLock=Yes: IncScndCtrl() refuses to step the shunt controller up while the master controller is at its no-power position or on a resistive RList position (R >= 0.01), as the interlock of the ET21, ET22 and EP09 does. Decreasing the shunt stays allowed so it can't get stuck. The AI doesn't choose field shunting while the lock applies. Without the key nothing changes. Game data: the key is to be added to the Cntrl. line of the ET21 (et21_v2), ET22 (et22_v3, et22-2000_v2) and EP09 (ep09_v1, ep09_v2) .fiz files that define it; until then they behave as before. (eu07/maszyna#402) --- McZapkie/MOVER.h | 3 ++ McZapkie/Mover.cpp | 9 ++++- tests/unit/test_cntrl.cpp | 80 +++++++++++++++++++++++++++++++++++++++ vehicle/Driver.cpp | 2 +- 4 files changed, 92 insertions(+), 2 deletions(-) diff --git a/McZapkie/MOVER.h b/McZapkie/MOVER.h index 199c65a0..825bec6d 100644 --- a/McZapkie/MOVER.h +++ b/McZapkie/MOVER.h @@ -1943,6 +1943,7 @@ class TMoverParameters double MirrorVelClose{5.0}; bool MirrorForbidden{false}; /*czy jest pozwolenie na otworzenie lusterek (przycisk)*/ bool ScndS = false; /*Czy jest bocznikowanie na szeregowej*/ + bool ScndCtrlResistiveLock{false}; // the shunt controller can't be moved up while the master controller is at zero or on a resistive position (ScndCtrlResistiveLock=Yes in Cntrl.) bool SpeedCtrl = false; /*czy jest tempomat*/ speed_control SpeedCtrlUnit; /*parametry tempomatu*/ double SpeedCtrlButtons[10]{30, 40, 50, 60, 70, 80, 90, 100, 110, 120}; /*przyciski prędkości*/ @@ -2372,6 +2373,8 @@ class TMoverParameters bool IncScndCtrl(int CtrlSpeed); bool DecScndCtrl(int CtrlSpeed); int GetVirtualScndPos() const; + // whether ScndCtrlResistiveLock keeps the shunt controller from being moved up at the current master controller position + bool IsScndCtrlLockedByMainCtrl() const; bool IsScndCtrlNoPowerPos() const; bool IsScndCtrlMaxPowerPos() const; diff --git a/McZapkie/Mover.cpp b/McZapkie/Mover.cpp index 330e8eb8..b9d18d0b 100644 --- a/McZapkie/Mover.cpp +++ b/McZapkie/Mover.cpp @@ -2751,7 +2751,7 @@ bool TMoverParameters::IncScndCtrl(int CtrlSpeed) { // if (RList[MainCtrlPos].R=0) and (MainCtrlPos>0) and (ScndCtrlPos= 0.01); +} + int TMoverParameters::GetVirtualScndPos() const { if (TrainType == dt_ET42 && DynamicBrakeFlag && !ScndCtrlPos) @@ -10897,6 +10903,7 @@ void TMoverParameters::LoadFIZ_Cntrl(std::string const &line) extract_value(EIMCtrlEmergency, "EIMCtrlEmergency", line, ""); extract_value(ScndS, "ScndS", line, ""); // brak pozycji rownoleglej przy niskiej nastawie PSR + extract_value(ScndCtrlResistiveLock, "ScndCtrlResistiveLock", line, ""); // bocznikowanie tylko na pozycjach bezoporowych extract_value(InitialCtrlDelay, "IniCDelay", line, ""); extract_value(CtrlDelay, "SCDelay", line, ""); diff --git a/tests/unit/test_cntrl.cpp b/tests/unit/test_cntrl.cpp index 0dd3ddc2..43c983ed 100644 --- a/tests/unit/test_cntrl.cpp +++ b/tests/unit/test_cntrl.cpp @@ -37,3 +37,83 @@ TEST_CASE("cntrl: the relays of a multiple unit start on their own, of other veh CHECK(unit->ConverterOverloadRelayStart == start_t::automatic); } } + +namespace { + +// the first position of the master controller after Position without the starting resistors in the circuit +int resistance_free_position(TMoverParameters const &Vehicle, int Position = 0) { + for (auto position{Position + 1}; position <= Vehicle.MainCtrlPosNo; ++position) { + if (Vehicle.RList[position].R < 0.01) { + return position; + } + } + return 0; +} + +// the vehicle with its cab occupied, the master controller set to Position and the shunt controller off +void controllers_at(vehicle &Vehicle, int Position) { + Vehicle->CabActive = 1; + Vehicle->MainCtrlPos = Position; + Vehicle->ScndCtrlPos = 0; +} + +} // namespace + +TEST_CASE("cntrl: ScndCtrlResistiveLock keeps the field shunting off at zero and on the resistive positions (eu07/maszyna#402)") { + for (auto const *path : {"et22_v3/201e-rn", "ep09_v2/104e"}) { + CAPTURE(path); + vehicle locked{definition::fiz(path).set("Cntrl", "ScndCtrlResistiveLock", "Yes"), 0.0, "test", 1}; + REQUIRE(locked->ScndCtrlResistiveLock); + REQUIRE(locked->ScndCtrlPosNo > 0); + auto const resistancefree{resistance_free_position(locked.mover())}; + REQUIRE(resistancefree > 1); + REQUIRE(locked->RList[resistancefree - 1].R >= 0.01); + + SUBCASE("zero") { + controllers_at(locked, 0); + CHECK(locked->IsScndCtrlLockedByMainCtrl()); + CHECK_FALSE(locked->IncScndCtrl(1)); + CHECK_FALSE(locked->IncScndCtrl(2)); + CHECK(locked->ScndCtrlPos == 0); + } + SUBCASE("resistive positions") { + for (auto position{1}; position < resistancefree; ++position) { + CAPTURE(position); + controllers_at(locked, position); + CHECK_FALSE(locked->IncScndCtrl(1)); + CHECK(locked->ScndCtrlPos == 0); + } + } + SUBCASE("every resistance-free position") { + for (auto position{resistancefree}; position > 0; position = resistance_free_position(locked.mover(), position)) { + CAPTURE(position); + controllers_at(locked, position); + CHECK_FALSE(locked->IsScndCtrlLockedByMainCtrl()); + CHECK(locked->IncScndCtrl(1)); + CHECK(locked->ScndCtrlPos == 1); + } + } + SUBCASE("the shunting still goes off on a resistive position") { + controllers_at(locked, resistancefree); + REQUIRE(locked->IncScndCtrl(1)); + locked->MainCtrlPos = resistancefree - 1; + CHECK(locked->DecScndCtrl(1)); + CHECK(locked->ScndCtrlPos == 0); + } + } +} + +TEST_CASE("cntrl: without ScndCtrlResistiveLock the field shunting engages on any position, as before") { + for (auto const *path : {"et22_v3/201e-rn", "ep09_v2/104e"}) { + CAPTURE(path); + vehicle unlocked{definition::fiz(path), 0.0, "test", 1}; + CHECK_FALSE(unlocked->ScndCtrlResistiveLock); + for (auto const position : {0, 1}) { + CAPTURE(position); + controllers_at(unlocked, position); + CHECK_FALSE(unlocked->IsScndCtrlLockedByMainCtrl()); + CHECK(unlocked->IncScndCtrl(1)); + CHECK(unlocked->ScndCtrlPos == 1); + } + } +} diff --git a/vehicle/Driver.cpp b/vehicle/Driver.cpp index 08f0c0d2..bb6ff823 100644 --- a/vehicle/Driver.cpp +++ b/vehicle/Driver.cpp @@ -3364,7 +3364,7 @@ bool TController::IncSpeed() mvOccupied->Vel <= (IsCargoTrain ? 40 : 30) + (seriesmodefieldshunting ? 5 : 0) - (fAccGravity < -0.025 ? 10 : 0)); // when not in series mode use the first available parallel mode configuration until 50/60 km/h for passenger/cargo train // (if there's only one parallel mode configuration it'll be used regardless of current speed) - auto const usefieldshunting = mvControlling->StLinFlag && mvControlling->RList[mvControlling->MainCtrlPos].R < 0.01 && + auto const usefieldshunting = mvControlling->StLinFlag && mvControlling->RList[mvControlling->MainCtrlPos].R < 0.01 && false == mvControlling->IsScndCtrlLockedByMainCtrl() && (useseriesmode ? mvControlling->RList[mvControlling->MainCtrlPos].Bn == 1 : true == sufficientacceleration && true == sufficienttractionforce && mvOccupied->Vel <= (IsCargoTrain ? 60 : 50) + (parallelmodefieldshunting ? 5 : 0) ? mvControlling->RList[mvControlling->MainCtrlPos].Bn > 1 :