From b5953ef61e451dc0bb9ff15f886342678800a3f5 Mon Sep 17 00:00:00 2001 From: jerrrrycho Date: Sun, 8 Nov 2026 13:00:24 +0100 Subject: [PATCH] feat: lock the field shunting in both directions on resistive positions With ScndCtrlResistiveLock=Yes the shunt controller can't be moved down either while the master controller is at zero or on a resistive position (eu07/maszyna#402: "inc/dec"). So that a shunt step can't be left stuck there, the shunt controller drops to 0 whenever the master controller is at zero or on a resistive position: right after IncMainCtrl()/DecMainCtrl() move it there (before the positions go down the consist) and in every update, for the master controller set directly (AI clamp, consist). Electrically nothing changes, the relays already drop the shunting on resistive positions. Reducing power is never blocked: the EP09/ET21 master controller turns back with the shunt on, the ET22 keeps its interlock (shunt off first), and on the ET22 a shunt left on at a resistive position would otherwise lock both controllers. Tests: the shunt goes down on resistance-free positions only; the master controller taken back or up onto a resistive position from a shunt step, by hand and directly, on the ET22 and EP09. --- McZapkie/MOVER.h | 7 +++-- McZapkie/Mover.cpp | 16 +++++++++- tests/unit/test_cntrl.cpp | 63 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 81 insertions(+), 5 deletions(-) diff --git a/McZapkie/MOVER.h b/McZapkie/MOVER.h index 825bec6d..6a70147a 100644 --- a/McZapkie/MOVER.h +++ b/McZapkie/MOVER.h @@ -1943,7 +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 ScndCtrlResistiveLock{false}; // the shunt controller can't be moved, up or down, while the master controller is at zero or on a resistive position, and drops to 0 when the master controller gets there (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*/ @@ -2373,8 +2373,11 @@ 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 + // whether ScndCtrlResistiveLock keeps the shunt controller from being moved at the current master controller position bool IsScndCtrlLockedByMainCtrl() const; + // ScndCtrlResistiveLock: the shunt controller drops to 0 once the master controller is at zero or on a resistive position, so it + // can't stay engaged there, locked in place (eu07/maszyna#402) + void ScndCtrlResistiveLockCheck(); bool IsScndCtrlNoPowerPos() const; bool IsScndCtrlMaxPowerPos() const; diff --git a/McZapkie/Mover.cpp b/McZapkie/Mover.cpp index b9d18d0b..8154eb92 100644 --- a/McZapkie/Mover.cpp +++ b/McZapkie/Mover.cpp @@ -1631,6 +1631,8 @@ void TMoverParameters::compute_movement_(double const Deltatime) BRVentilators = false; } + // the master controller set directly, not through IncMainCtrl()/DecMainCtrl() (AI, consist): the locked shunt follows it as well + ScndCtrlResistiveLockCheck(); // automatyczny rozruch if (EngineType == TEngineType::ElectricSeriesMotor && AutoRelayCheck()) { @@ -2537,6 +2539,7 @@ bool TMoverParameters::IncMainCtrl(int CtrlSpeed) if (true == OK) { + ScndCtrlResistiveLockCheck(); // e.g. from the last series position on to the resistive parallel ones SendCtrlToNext("MainCtrl", MainCtrlPos, CabActive); //??? SendCtrlToNext("ScndCtrl", ScndCtrlPos, CabActive); } @@ -2662,6 +2665,7 @@ bool TMoverParameters::DecMainCtrl(int CtrlSpeed) if (OK) { + ScndCtrlResistiveLockCheck(); // the shunt drops when the master controller leaves a resistance-free position /*OK:=*/SendCtrlToNext("MainCtrl", MainCtrlPos, CabActive); // hmmmm...???!!! /*OK:=*/SendCtrlToNext("ScndCtrl", ScndCtrlPos, CabActive); } @@ -2822,7 +2826,7 @@ bool TMoverParameters::DecScndCtrl(int CtrlSpeed) } else if (ScndCtrlPosNo > 0 && CabActive != 0) { - if (ScndCtrlPos > 0 && !CoupledCtrl && (EngineType != TEngineType::DieselElectric || !AutoRelayFlag)) + if (ScndCtrlPos > 0 && !CoupledCtrl && (EngineType != TEngineType::DieselElectric || !AutoRelayFlag) && !IsScndCtrlLockedByMainCtrl()) { if (CtrlSpeed == 1) { @@ -2878,6 +2882,16 @@ bool TMoverParameters::IsScndCtrlLockedByMainCtrl() const return ScndCtrlResistiveLock && (IsMainCtrlNoPowerPos() || RList[MainCtrlPos].R >= 0.01); } +void TMoverParameters::ScndCtrlResistiveLockCheck() +{ + // the shunt controller, locked in both directions on zero and the resistive positions, would otherwise stay engaged there once the + // master controller is taken back (or set from elsewhere, e.g. by the AI or another vehicle of the consist) with the shunt on + if (ScndCtrlPos > 0 && !CoupledCtrl && IsScndCtrlLockedByMainCtrl()) + { + ScndCtrlPos = 0; + } +} + int TMoverParameters::GetVirtualScndPos() const { if (TrainType == dt_ET42 && DynamicBrakeFlag && !ScndCtrlPos) diff --git a/tests/unit/test_cntrl.cpp b/tests/unit/test_cntrl.cpp index e6c53940..c73c45d8 100644 --- a/tests/unit/test_cntrl.cpp +++ b/tests/unit/test_cntrl.cpp @@ -96,12 +96,71 @@ TEST_CASE("cntrl: ScndCtrlResistiveLock keeps the field shunting off at zero and CHECK(locked->ScndCtrlPos == 1); } } - SUBCASE("the shunting still goes off on a resistive position") { + SUBCASE("the shunt goes down on a resistance-free position") { + controllers_at(locked, resistancefree); + REQUIRE(locked->IncScndCtrl(1)); + REQUIRE(locked->IncScndCtrl(1)); + CHECK(locked->DecScndCtrl(1)); + CHECK(locked->ScndCtrlPos == 1); + CHECK(locked->DecScndCtrl(2)); + CHECK(locked->ScndCtrlPos == 0); + } + SUBCASE("the shunt doesn't go down on a resistive position either") { + // a state the lock doesn't let arise (see below), set by hand to check the controller alone + controllers_at(locked, resistancefree - 1); + locked->ScndCtrlPos = 1; + CHECK(locked->IsScndCtrlLockedByMainCtrl()); + CHECK_FALSE(locked->DecScndCtrl(1)); + CHECK_FALSE(locked->DecScndCtrl(2)); + CHECK(locked->ScndCtrlPos == 1); + } + SUBCASE("the master controller taken back from a shunt step drops the shunt") { + controllers_at(locked, resistancefree); + REQUIRE(locked->IncScndCtrl(1)); + if (locked->TrainType == dt_ET22) { + // the ET22's master controller doesn't turn with the shunt on: the shunt goes off first, as before + CHECK_FALSE(locked->DecMainCtrl(1)); + CHECK(locked->MainCtrlPos == resistancefree); + CHECK(locked->DecScndCtrl(1)); + } + CHECK(locked->DecMainCtrl(1)); + CHECK(locked->MainCtrlPos == resistancefree - 1); + CHECK(locked->ScndCtrlPos == 0); + // and the master controller goes on down to zero, nothing left locked + while (locked->MainCtrlPos > 0 && locked->DecMainCtrl(1)) { + } + CHECK(locked->MainCtrlPos == 0); + } + SUBCASE("the master controller set directly (AI, consist) drops the shunt in the next update") { controllers_at(locked, resistancefree); REQUIRE(locked->IncScndCtrl(1)); locked->MainCtrlPos = resistancefree - 1; - CHECK(locked->DecScndCtrl(1)); + locked.step(); CHECK(locked->ScndCtrlPos == 0); + CHECK(locked->MainCtrlPos == resistancefree - 1); + } + SUBCASE("the master controller moved up from a shunt step onto a resistive position drops the shunt") { + // the last series position, followed by the resistive parallel ones + auto lastseries{0}; + for (auto position{resistancefree}; position > 0 && position < locked->MainCtrlPosNo; position = resistance_free_position(locked.mover(), position)) { + if (locked->RList[position + 1].R >= 0.01) { + lastseries = position; + break; + } + } + REQUIRE(lastseries > 0); + controllers_at(locked, lastseries); + locked->DirActive = 1; + REQUIRE(locked->IncScndCtrl(1)); + if (locked->TrainType == dt_ET22) { + CHECK_FALSE(locked->IncMainCtrl(1)); // the ET22's interlock, as before + CHECK(locked->ScndCtrlPos == 1); + } + else { + CHECK(locked->IncMainCtrl(1)); + CHECK(locked->MainCtrlPos == lastseries + 1); + CHECK(locked->ScndCtrlPos == 0); + } } } }