From 6232930f4ca31d4f64cd02f18ab77beb73bf3dc0 Mon Sep 17 00:00:00 2001 From: maj00r Date: Sun, 4 Oct 2026 14:28:40 +0200 Subject: [PATCH] Leave the last vehicle's coupler at 0 so the train gets its end signals The simulator hangs end signals only on a last vehicle with coupler 0; every new car came with 3 and the pass that couples neighbours never touched the last one, nor did taking a car off the end. The coupler editor says so for the last vehicle. --- StarterNG.Tests/ConsistTailTests.cs | 68 +++++++++++++++++++++++++ StarterNG/Domain/Consist.cs | 18 +++++++ StarterNG/Views/Depot/VehicleDetails.cs | 2 + StarterNG/startercfg/lang/cz.xml | 1 + StarterNG/startercfg/lang/en.xml | 1 + StarterNG/startercfg/lang/hu.xml | 1 + StarterNG/startercfg/lang/pl.xml | 1 + StarterNG/startercfg/lang/ru.xml | 1 + 8 files changed, 93 insertions(+) create mode 100644 StarterNG.Tests/ConsistTailTests.cs diff --git a/StarterNG.Tests/ConsistTailTests.cs b/StarterNG.Tests/ConsistTailTests.cs new file mode 100644 index 0000000..87fa521 --- /dev/null +++ b/StarterNG.Tests/ConsistTailTests.cs @@ -0,0 +1,68 @@ +using StarterNG.Classes; +using StarterNG.Domain; +using StarterNG.Domain.Vehicles; +using StarterNG.Tests.Fakes; + +namespace StarterNG.Tests; + +// The simulator puts end signals only on a last vehicle whose coupler is 0. +public class ConsistTailTests +{ + private readonly Consist _consist; + private readonly VehicleCatalog _catalog; + + public ConsistTailTests() + { + var installation = new TestInstallation(new InMemoryFileSystem() + .WithFile(TestInstallation.At("dynamic", "pkp", "b111", "textures.txt"), + "!=b\nb111-01.mat=b111,B111\nb111-02.mat=b111,B111")); + installation.Vehicles.Load(installation.Library.Vehicles); + _catalog = installation.Library.Vehicles; + _consist = new Consist(_catalog, new VehicleInfo(_catalog, installation.Physics)); + } + + [Fact] + public void A_new_last_car_is_left_uncoupled_at_the_back() + { + var first = Append("b111-01"); + var last = Append("b111-02"); + + _consist.AutoConnectAll(); + + Assert.NotEqual(0, first.Cars[0].Coupling.Flags); + Assert.Equal(0, last.Cars[0].Coupling.Flags); + } + + [Fact] + public void Taking_off_the_last_car_frees_the_one_now_at_the_back() + { + var first = Append("b111-01"); + var last = Append("b111-02"); + _consist.AutoConnectAll(); + + _consist.Remove(last); + + Assert.Equal(0, first.Cars[0].Coupling.Flags); + } + + [Fact] + public void Freeing_the_back_keeps_the_brake_setting() + { + var car = Append("b111-01"); + car.Cars[0].Coupling = Coupling.Parse("3.BP"); + + _consist.AutoConnectAll(); + + Assert.Equal("0.BP", car.Cars[0].Coupling.ToString()); + } + + private ConsistItem Append(string skin) + { + var item = new ConsistItem + { + Cars = new List { _consist.MakeDynamic(_catalog.TextureForSkin(skin)!, null) } + }; + _consist.Insert(_consist.Count, item); + return item; + } +} diff --git a/StarterNG/Domain/Consist.cs b/StarterNG/Domain/Consist.cs index 7adb709..d9509f6 100644 --- a/StarterNG/Domain/Consist.cs +++ b/StarterNG/Domain/Consist.cs @@ -324,6 +324,10 @@ public sealed class Consist : IReadOnlyList int gap = _items.IndexOf(item); _items.Remove(item); + // The car now at the end still holds the coupler of the one taken off it. + if (gap == _items.Count) + FreeTail(); + if (ReferenceEquals(Selected, item)) Selected = _items.Count == 0 ? null @@ -551,6 +555,8 @@ public sealed class Consist : IReadOnlyList left.Coupling.Flags = left.Coupling.Locked ? -common : common; } + + FreeTail(); } public bool CanFormUnit(ConsistItem left, ConsistItem right) @@ -598,6 +604,18 @@ public sealed class Consist : IReadOnlyList public static Dynamic HeadCar(ConsistItem item) => item.Flipped ? item.Cars[^1] : item.Cars[0]; + /// + /// Clears the coupler at the back of the consist. The simulator hangs the end + /// signals only on a last vehicle whose coupler is 0 - anything else couples it + /// to whatever stands behind - and every new car comes with 3. Its parameters, + /// the brake setting among them, stay. + /// + private void FreeTail() + { + if (_items.Count > 0 && _items[^1].Cars.Count > 0) + TailCar(_items[^1]).Coupling.Flags = 0; + } + /// /// Whether a car runs turned. Its own sign is the truth - it is what the export /// writes - and decides which coupler is at which end; a card's flag agrees diff --git a/StarterNG/Views/Depot/VehicleDetails.cs b/StarterNG/Views/Depot/VehicleDetails.cs index f621207..19105bf 100644 --- a/StarterNG/Views/Depot/VehicleDetails.cs +++ b/StarterNG/Views/Depot/VehicleDetails.cs @@ -179,6 +179,8 @@ public sealed class VehicleDetails return; bool isLast = unitIdx >= _consist.Count - 1; + if (isLast) + couplerPanel.Children.Add(PanelNote(App.Loc["CouplingLastNote"])); var d = Consist.TailCar(item); diff --git a/StarterNG/startercfg/lang/cz.xml b/StarterNG/startercfg/lang/cz.xml index ad37c04..a590c87 100644 --- a/StarterNG/startercfg/lang/cz.xml +++ b/StarterNG/startercfg/lang/cz.xml @@ -280,6 +280,7 @@ Rozdělit jednotku na jednotlivé vozy Společné pro celou jednotku Spřáhlo na konci jednotky + Poslední vozidlo: spřáhlo 0 dává koncovky. Jiný kód spojí soupravu s tím, co stojí za ní, a simulátor koncovky nenasadí. Spojit Spojit toto vozidlo s následujícím do jedné jednotky Přetáhněte pro změnu pořadí diff --git a/StarterNG/startercfg/lang/en.xml b/StarterNG/startercfg/lang/en.xml index d98656e..cfee6bc 100644 --- a/StarterNG/startercfg/lang/en.xml +++ b/StarterNG/startercfg/lang/en.xml @@ -280,6 +280,7 @@ Split the unit into individual cars Shared by the whole unit Coupler at the rear of the unit + Last vehicle: coupler 0 gets the end signals. Any other code couples the train to whatever stands behind it, and the simulator puts up no end signals. Join Couple this vehicle with the next one into a single unit Drag to reorder diff --git a/StarterNG/startercfg/lang/hu.xml b/StarterNG/startercfg/lang/hu.xml index 44a4b10..adebde5 100644 --- a/StarterNG/startercfg/lang/hu.xml +++ b/StarterNG/startercfg/lang/hu.xml @@ -280,6 +280,7 @@ Az egység szétbontása külön kocsikra Az egész egységre közös Az egység végén lévő kapcsolókészülék + Utolsó jármű: a 0-s kapcsolat adja a zárjelzőket. Más kód a mögötte álló járműhöz kapcsolja a szerelvényt, és a szimulátor nem tesz fel zárjelzőt. Összekapcsolás A jármű összekapcsolása a következővel egy egységgé Húzza az átrendezéshez diff --git a/StarterNG/startercfg/lang/pl.xml b/StarterNG/startercfg/lang/pl.xml index 4a3d61c..5e47050 100644 --- a/StarterNG/startercfg/lang/pl.xml +++ b/StarterNG/startercfg/lang/pl.xml @@ -280,6 +280,7 @@ Rozdziel zespół na pojedyncze człony Ustawienie wspólne dla całego zespołu Sprzęg na końcu zespołu + Ostatni pojazd: sprzęg 0 daje końcówki. Inny kod połączy skład z tym, co stoi za nim, a symulator nie założy końcówek. Złącz Połącz ten pojazd z następnym w jeden zespół Przeciągnij, aby zmienić kolejność diff --git a/StarterNG/startercfg/lang/ru.xml b/StarterNG/startercfg/lang/ru.xml index 7250151..4307c5c 100644 --- a/StarterNG/startercfg/lang/ru.xml +++ b/StarterNG/startercfg/lang/ru.xml @@ -280,6 +280,7 @@ Разделить сцеп на отдельные вагоны Общее для всего состава Сцепка в конце состава + Последняя единица: сцепка 0 даёт хвостовые сигналы. Другой код сцепит состав с тем, что стоит за ним, и симулятор не поставит хвостовые сигналы. Соединить Соединить этот вагон со следующим в один состав Перетащите для изменения порядка