From 73ff00f22e5751b01ffb4f7015eedf9dc6a84a0a Mon Sep 17 00:00:00 2001 From: maj00r Date: Sun, 4 Oct 2026 13:59:01 +0200 Subject: [PATCH] Join and split vehicles facing different ways without moving a car Joining took each card's cars in stored order and turned the lot by the first card's flag, so a turned card came out reversed; splitting gave every car the unit's flag. Both now go by the running order, and the coupler sides by each car's own facing, which is what the export writes. --- StarterNG.Tests/ConsistOrientationTests.cs | 32 +++++++++++++ StarterNG/Domain/Consist.cs | 56 +++++++++++----------- 2 files changed, 60 insertions(+), 28 deletions(-) diff --git a/StarterNG.Tests/ConsistOrientationTests.cs b/StarterNG.Tests/ConsistOrientationTests.cs index 1aeaa05..42abd82 100644 --- a/StarterNG.Tests/ConsistOrientationTests.cs +++ b/StarterNG.Tests/ConsistOrientationTests.cs @@ -55,6 +55,38 @@ public class ConsistOrientationTests Assert.Equal(new[] { "et40-16-b -1", "et40-16-a -1" }, Describe(consist)); } + [Fact] + public void Joining_units_that_face_different_ways_leaves_every_car_as_it_was() + { + var consist = NewConsist(out var first); + var second = Unit(consist, "et40-16-a"); + consist.Insert(1, second); + consist.Flip(second); + var before = Describe(consist); + + consist.Join(first); + + Assert.Single(consist); + Assert.Equal(before, Describe(consist)); + Assert.Equal(new[] { "et40-10-a 1", "et40-10-b 1", "et40-16-b -1", "et40-16-a -1" }, before); + } + + [Fact] + public void Splitting_such_a_unit_gives_each_car_its_own_way_back() + { + var consist = NewConsist(out var first); + var second = Unit(consist, "et40-16-a"); + consist.Insert(1, second); + consist.Flip(second); + var before = Describe(consist); + consist.Join(first); + + consist.Split(consist[0]); + + Assert.Equal(before, Describe(consist)); + Assert.All(consist, card => Assert.Equal(Consist.IsTurned(card.Cars[0]), card.Flipped)); + } + private Consist NewConsist(out ConsistItem unit) { var consist = new Consist(_catalog, new VehicleInfo(_catalog, _installation.Physics)); diff --git a/StarterNG/Domain/Consist.cs b/StarterNG/Domain/Consist.cs index 77c00ee..7adb709 100644 --- a/StarterNG/Domain/Consist.cs +++ b/StarterNG/Domain/Consist.cs @@ -152,17 +152,11 @@ public sealed class Consist : IReadOnlyList Raise(); } - public List Flatten() - { - var flat = new List(); - foreach (var item in _items) - { - var cars = item.Flipped ? item.Cars.AsEnumerable().Reverse().ToList() : item.Cars; - foreach (var car in cars) - flat.Add(car); - } - return flat; - } + public List Flatten() => _items.SelectMany(RunningOrder).ToList(); + + /// A card's cars in the order they run, which is the order the export writes. + public static IEnumerable RunningOrder(ConsistItem item) => + item.Flipped ? Enumerable.Reverse(item.Cars) : item.Cars; public void SyncStartingVehicle() { @@ -406,9 +400,7 @@ public sealed class Consist : IReadOnlyList int i = _items.IndexOf(item); if (i < 0) return; - var order = item.Flipped - ? Enumerable.Reverse(item.Cars).ToList() - : item.Cars; + var order = RunningOrder(item).ToList(); _items.RemoveAt(i); for (int c = 0; c < order.Count; c++) @@ -417,7 +409,8 @@ public sealed class Consist : IReadOnlyList { Cars = new List { order[c] }, Grouped = false, - Flipped = item.Flipped, + // Each car's own way - a unit joined from both ways splits back into them. + Flipped = IsTurned(order[c]), Driver = order[c].DriverType }); } @@ -450,13 +443,17 @@ public sealed class Consist : IReadOnlyList if (last == first) return; + // The cars as they run, each card read the way it faces, so joining vehicles + // that face different ways leaves every car where and how it was. The unit + // keeps the first card's flag; its cars are stored against that flag. var cars = new List(); var driver = eDriverType.Nobody; for (int k = first; k <= last; k++) { - cars.AddRange(_items[k].Cars); + var card = _items[k]; + cars.AddRange(RunningOrder(card)); if (driver == eDriverType.Nobody) - driver = _items[k].Driver; + driver = card.Driver; } bool flipped = _items[first].Flipped; @@ -530,18 +527,14 @@ public sealed class Consist : IReadOnlyList public void AutoConnectAll() { - var flat = new List<(Dynamic car, bool flipped)>(); - foreach (var item in _items) - { - var cars = item.Flipped ? item.Cars.AsEnumerable().Reverse() : item.Cars; - foreach (var c in cars) - flat.Add((c, item.Flipped)); - } + var flat = Flatten(); for (int i = 0; i < flat.Count - 1; i++) { - var (left, lf) = flat[i]; - var (right, rf) = flat[i + 1]; + var left = flat[i]; + var right = flat[i + 1]; + bool lf = IsTurned(left); + bool rf = IsTurned(right); var lp = _info.PhysicsFor(left); var rp = _info.PhysicsFor(right); @@ -570,8 +563,8 @@ public sealed class Consist : IReadOnlyList var lp = _info.PhysicsFor(tail); var rp = _info.PhysicsFor(head); - int leftMax = lp == null ? 3 : (left.Flipped ? lp.AllowedFlagA : lp.AllowedFlagB); - int rightMax = rp == null ? 3 : (right.Flipped ? rp.AllowedFlagB : rp.AllowedFlagA); + int leftMax = lp == null ? 3 : (IsTurned(tail) ? lp.AllowedFlagA : lp.AllowedFlagB); + int rightMax = rp == null ? 3 : (IsTurned(head) ? rp.AllowedFlagB : rp.AllowedFlagA); return (leftMax & rightMax & Coupling.WorkshopLock) != 0 || SameSet(tail, head); } @@ -605,6 +598,13 @@ public sealed class Consist : IReadOnlyList public static Dynamic HeadCar(ConsistItem item) => item.Flipped ? item.Cars[^1] : item.Cars[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 + /// with it except in a unit joined from vehicles facing different ways. + /// + public static bool IsTurned(Dynamic car) => car.Offset < 0; + public static bool HoldsTail(ConsistItem item) => item.Cars.Count > 0 && HoldsNext(TailCar(item));