mirror of
https://github.com/MaSzyna-EU07/StarterNG.git
synced 2026-11-11 16:57:11 +01:00
Black box: the queries of the user's train worked out once a read, lights as the driver checks them
- The stations, the signals passed, the horn points, the couplings, the consists and the timetable progress of the user's train are kept until a read adds something; the views asked for them every frame, which on a long session with many trains took the window down. A read that adds nothing no longer counts as a change. - The lights are checked against the lamps the vehicles at the ends have (written by the simulator from now on), as its driver checks them: Pc 1 without the upper headlight, with the auxiliary lamps, Pc 5 with the rear-end signals. Only while the user drives: the autopilot's lights are its own. - A train off the platform counts at both ends: its head past the stop point as well as its tail before the platform's end. - A recording without the couplings keeps the timetable for any vehicle of the user. - A signal passed at danger is ringed on the map only once it was. - In the tapes window a black box opens on a click or Enter, not when the arrow keys go over it. - The viewer's screenshot of a window not laid out saves nothing and still closes.
This commit is contained in:
@@ -81,7 +81,8 @@ public sealed class BlackBoxReader
|
||||
}
|
||||
|
||||
_offset += at;
|
||||
Recording.Changed();
|
||||
if (read > 0)
|
||||
Recording.Changed();
|
||||
return read;
|
||||
}
|
||||
|
||||
@@ -161,6 +162,9 @@ public sealed class BlackBoxReader
|
||||
// What the driver went by and the lights asked for came later still.
|
||||
if (r.Remaining >= 5)
|
||||
sample = sample with { Movement = (Movement)r.U8(), LightHintHead = (short)r.U16(), LightHintTail = (short)r.U16() };
|
||||
// The lamps the vehicles at the ends have, which a pattern is checked against, later again.
|
||||
if (r.Remaining >= 4)
|
||||
sample = sample with { EquippedHead = (Lights)r.U16(), EquippedTail = (Lights)r.U16() };
|
||||
rec.AddCab(sample);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -124,15 +124,16 @@ public sealed record DrivingSummary(
|
||||
else if (sample.Driving == Driver.Human && sample.AbsSpeedKmh > Rounding)
|
||||
driven += step;
|
||||
|
||||
// the light signals at both ends, while the train moves
|
||||
if (sample.AbsSpeedKmh > 1 && HeadCodes.Right(sample) is { } right)
|
||||
// the light signals at both ends, while the user drives the train and it moves: the autopilot's are its own
|
||||
if (sample.Driving == Driver.Human && sample.AbsSpeedKmh > 1 &&
|
||||
HeadCodes.HeadRight(sample) is { } headRight && HeadCodes.TailRight(sample) is { } tailRight)
|
||||
{
|
||||
lightsJudged += step;
|
||||
if (!right)
|
||||
if (!headRight || !tailRight)
|
||||
{
|
||||
lightsWrong += step;
|
||||
var fault = new LightsFault(before.Time, sample.Time, HeadCodes.Of(sample.LightsHead), HeadCodes.RequiredHead(sample)!.Value,
|
||||
HeadCodes.Of(sample.LightsTail), HeadCodes.RequiredTail(sample)!.Value);
|
||||
!headRight, HeadCodes.Of(sample.LightsTail), HeadCodes.RequiredTail(sample)!.Value, !tailRight);
|
||||
if (faults.Count > 0 && faults[^1].SameAs(fault) && fault.Start - faults[^1].End < 2)
|
||||
faults[^1] = faults[^1] with { End = sample.Time };
|
||||
else
|
||||
@@ -186,11 +187,10 @@ public sealed record DrivingSummary(
|
||||
}
|
||||
|
||||
/// <summary>A stretch the user's train moved with other light signals than it was to show, at its head and its tail.</summary>
|
||||
public sealed record LightsFault(double Start, double End, HeadCode Head, HeadCode HeadRequired, HeadCode Tail, HeadCode TailRequired)
|
||||
public sealed record LightsFault(double Start, double End, HeadCode Head, HeadCode HeadRequired, bool HeadWrong,
|
||||
HeadCode Tail, HeadCode TailRequired, bool TailWrong)
|
||||
{
|
||||
public bool HeadWrong => Head != HeadRequired;
|
||||
public bool TailWrong => Tail != TailRequired;
|
||||
|
||||
public bool SameAs(LightsFault other) =>
|
||||
Head == other.Head && HeadRequired == other.HeadRequired && Tail == other.Tail && TailRequired == other.TailRequired;
|
||||
Head == other.Head && HeadRequired == other.HeadRequired && HeadWrong == other.HeadWrong &&
|
||||
Tail == other.Tail && TailRequired == other.TailRequired && TailWrong == other.TailWrong;
|
||||
}
|
||||
|
||||
@@ -70,12 +70,81 @@ public static class HeadCodes
|
||||
_ => null
|
||||
};
|
||||
|
||||
/// <summary>Whether both ends showed what they were to while the train moved; null when nothing tells it.</summary>
|
||||
public static bool? Right(CabSample sample)
|
||||
/// <summary>Whether the head of the train showed what it was to; null when nothing tells it.</summary>
|
||||
public static bool? HeadRight(CabSample sample) => sample.Movement switch
|
||||
{
|
||||
if (RequiredHead(sample) is not { } head || RequiredTail(sample) is not { } tail)
|
||||
return null;
|
||||
return Of(sample.LightsHead) == head && Of(sample.LightsTail) == tail;
|
||||
Movement.Train => Meets(sample.LightsHead, sample.EquippedHead, sample.LightHintHead, HeadCode.Pc1),
|
||||
Movement.Shunting => Shows(HeadCode.Tb1, sample.LightsHead, sample.EquippedHead),
|
||||
_ => null
|
||||
};
|
||||
|
||||
/// <summary>Whether the tail of the train showed what it was to; null when nothing tells it.</summary>
|
||||
public static bool? TailRight(CabSample sample) => sample.Movement switch
|
||||
{
|
||||
Movement.Train => Meets(sample.LightsTail, sample.EquippedTail, sample.LightHintTail, HeadCode.Pc5),
|
||||
Movement.Shunting => Shows(HeadCode.Tb1, sample.LightsTail, sample.EquippedTail),
|
||||
_ => null
|
||||
};
|
||||
|
||||
/// <summary>Whether both ends showed what they were to; null when nothing tells it.</summary>
|
||||
public static bool? Right(CabSample sample) =>
|
||||
HeadRight(sample) is { } head && TailRight(sample) is { } tail ? head && tail : null;
|
||||
|
||||
private const Lights HighBeams = Lights.HighBeamLeft | Lights.HighBeamRight;
|
||||
private const Lights Auxiliary = Lights.AuxiliaryLeft | Lights.AuxiliaryRight;
|
||||
|
||||
// the pattern asked for at an end, the usual one when none is: the driver sets one asked for as it is
|
||||
private static bool Meets(Lights lit, Lights? equipped, short hint, HeadCode usual)
|
||||
{
|
||||
if (hint < 0)
|
||||
return Shows(usual, lit, equipped);
|
||||
var pattern = (Lights)hint;
|
||||
if (usual == HeadCode.Pc1 && pattern == (Lights.RedMarkerLeft | Lights.HeadlightRight | Lights.HeadlightUpper))
|
||||
return Shows(HeadCode.Pc2, lit, equipped);
|
||||
return (lit & ~HighBeams) == (equipped is { } lamps ? pattern & lamps : pattern);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the lamps lit at an end show the signal, as the simulator's driver checks it against the lamps
|
||||
/// the vehicle has there: a vehicle without the upper headlight shows Pc 1 with the two others, one with
|
||||
/// auxiliary lamps may show them instead, and one with no lamps for it the rear-end signals. A recording
|
||||
/// made before the lamps were written has the pattern alone. The high beams tell nothing.
|
||||
/// </summary>
|
||||
public static bool Shows(HeadCode code, Lights lit, Lights? equipped)
|
||||
{
|
||||
if (equipped is not { } lamps)
|
||||
return Of(lit) == code;
|
||||
lit &= ~HighBeams;
|
||||
bool auxiliary = (lamps & Auxiliary) != 0;
|
||||
switch (code)
|
||||
{
|
||||
case HeadCode.Pc1:
|
||||
{
|
||||
var pattern = lamps & (Lights.HeadlightLeft | Lights.HeadlightRight | Lights.HeadlightUpper);
|
||||
var fallback = lamps & (Auxiliary | Lights.HeadlightUpper);
|
||||
return (pattern != 0 && lit == pattern) || (auxiliary && lit == fallback) ||
|
||||
(pattern == 0 && fallback == 0 && lit == Lights.RearEndSignals);
|
||||
}
|
||||
case HeadCode.Pc2:
|
||||
{
|
||||
var pattern = lamps & (Lights.RedMarkerLeft | Lights.HeadlightRight | Lights.HeadlightUpper);
|
||||
var fallback = lamps & (Lights.RedMarkerLeft | Lights.AuxiliaryRight | Lights.HeadlightUpper);
|
||||
return lit == pattern || (auxiliary && lit == fallback);
|
||||
}
|
||||
case HeadCode.Pc5:
|
||||
{
|
||||
var pattern = lamps & (Lights.RedMarkerLeft | Lights.RedMarkerRight);
|
||||
var fallback = lamps & Lights.RearEndSignals;
|
||||
return (pattern != 0 && lit == pattern) || (fallback != 0 && lit == fallback);
|
||||
}
|
||||
case HeadCode.Tb1:
|
||||
// one headlight, of those there are
|
||||
return lit == (lamps & Lights.HeadlightLeft) || lit == (lamps & Lights.HeadlightRight);
|
||||
case HeadCode.Off:
|
||||
return lit == Lights.None;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>The name of a signal as the rules give it.</summary>
|
||||
|
||||
@@ -91,7 +91,27 @@ public sealed class Recording
|
||||
|
||||
// ----- filled by the reader -----
|
||||
|
||||
internal void Changed() => ++Revision;
|
||||
internal void Changed()
|
||||
{
|
||||
++Revision;
|
||||
_memo.Clear();
|
||||
_consists.Clear();
|
||||
}
|
||||
|
||||
// What the questions about the user's train worked out, kept until a read adds something: the views ask them every frame.
|
||||
private readonly Dictionary<object, object> _memo = new();
|
||||
private readonly Dictionary<(int Vehicle, int Coupling), IReadOnlySet<int>> _consists = new();
|
||||
private static readonly object UserStationsKey = new(), UserPassingsKey = new(), UserHornChecksKey = new(), UserCouplingsKey = new();
|
||||
|
||||
/// <summary>What make gives, worked out once until the recording changes.</summary>
|
||||
internal T Memo<T>(object key, Func<T> make) where T : class
|
||||
{
|
||||
if (_memo.TryGetValue(key, out var kept))
|
||||
return (T)kept;
|
||||
var made = make();
|
||||
_memo[key] = made;
|
||||
return made;
|
||||
}
|
||||
|
||||
private void Saw(double time) => _lastTime = Math.Max(_lastTime, time);
|
||||
|
||||
@@ -371,8 +391,8 @@ public sealed class Recording
|
||||
public IReadOnlyList<TimeSpanRange> AutopilotSpans() => CabSpans(s => s.Driving == Driver.Ai);
|
||||
|
||||
/// <summary>The signals the user's train passed: those passed by the vehicle the samples are of at the time.</summary>
|
||||
public IEnumerable<SignalPassing> UserPassings() =>
|
||||
_passings.Where(p => IsUserTrain(p.Time + 0.5, p.Vehicle, p.Position, Math.Max(0, p.Overrun) + 250));
|
||||
public IReadOnlyList<SignalPassing> UserPassings() =>
|
||||
Memo(UserPassingsKey, () => _passings.Where(p => IsUserTrain(p.Time + 0.5, p.Vehicle, p.Position, Math.Max(0, p.Overrun) + 250)).ToList());
|
||||
|
||||
/// <summary>
|
||||
/// Whether a vehicle was of the user's train at t. The records of a train give the
|
||||
@@ -393,12 +413,22 @@ public sealed class Recording
|
||||
/// <summary>The vehicles coupled into one train with the one given at t, itself included.</summary>
|
||||
public IReadOnlySet<int> ConsistAt(int vehicle, double t)
|
||||
{
|
||||
// What each coupler held at t: the couplings in the order written, each telling both ends.
|
||||
// The trains change only at a coupling: the consist is the same until the next one.
|
||||
int last = LastAtOrBefore(_couplings, t, c => c.Time);
|
||||
if (_consists.TryGetValue((vehicle, last), out var kept))
|
||||
return kept;
|
||||
var consist = Consist(vehicle, last);
|
||||
_consists[(vehicle, last)] = consist;
|
||||
return consist;
|
||||
}
|
||||
|
||||
private HashSet<int> Consist(int vehicle, int last)
|
||||
{
|
||||
// What each coupler held after the coupling given: the couplings in the order written, each telling both ends.
|
||||
var held = new Dictionary<(int Vehicle, int Side), (int Other, int OtherSide)>();
|
||||
foreach (var change in _couplings)
|
||||
for (int i = 0; i <= last; i++)
|
||||
{
|
||||
if (change.Time > t)
|
||||
break;
|
||||
var change = _couplings[i];
|
||||
if (held.Remove((change.Vehicle, change.Side), out var before))
|
||||
held.Remove((before.Other, before.OtherSide));
|
||||
if (change.Other != 0 && (change.Flags & CouplingFlags.Coupler) != 0)
|
||||
@@ -432,10 +462,11 @@ public sealed class Recording
|
||||
_couplings.Where(c => c.Time > 0.01 && (c.Coupled || c.Uncoupled || c.Flags != c.PreviousFlags));
|
||||
|
||||
/// <summary>The couplings the user's train took part in, before or after them.</summary>
|
||||
public IEnumerable<CouplingChange> UserCouplings() =>
|
||||
CouplingEvents().Where(c => CabAt(c.Time) is { } s && c.Time - s.Time <= 1.5 &&
|
||||
(ConsistAt(s.Vehicle, c.Time - 0.01).Contains(c.Vehicle) ||
|
||||
ConsistAt(s.Vehicle, c.Time + 0.01).Contains(c.Vehicle)));
|
||||
public IReadOnlyList<CouplingChange> UserCouplings() =>
|
||||
Memo(UserCouplingsKey, () => CouplingEvents().Where(c => CabAt(c.Time) is { } s && c.Time - s.Time <= 1.5 &&
|
||||
(ConsistAt(s.Vehicle, c.Time - 0.01).Contains(c.Vehicle) ||
|
||||
ConsistAt(s.Vehicle, c.Time + 0.01).Contains(c.Vehicle)))
|
||||
.ToList());
|
||||
|
||||
/// <summary>
|
||||
/// The timetable of the train the user was in at t: the last one written by then, else the first; none when the
|
||||
@@ -444,18 +475,21 @@ public sealed class Recording
|
||||
public Timetable? TimetableAt(double t)
|
||||
{
|
||||
var timetable = LastAtOrBefore(_timetables, t, x => x.Time) is var i and >= 0 ? _timetables[i] : _timetables.FirstOrDefault();
|
||||
if (timetable is null || CabAt(t) is not { } sample || sample.Vehicle == timetable.Vehicle)
|
||||
// A recording made before the couplings were written can't tell the trains apart: the timetable stays.
|
||||
if (timetable is null || CabAt(t) is not { } sample || sample.Vehicle == timetable.Vehicle || _couplings.Count == 0)
|
||||
return timetable;
|
||||
// another vehicle of the same train (the other cab, a car of it) keeps the timetable
|
||||
return ConsistAt(sample.Vehicle, t).Contains(timetable.Vehicle) ? timetable : null;
|
||||
}
|
||||
|
||||
/// <summary>The stations of the timetable of the user's train, as it came to them.</summary>
|
||||
public IEnumerable<StationEvent> UserStations() =>
|
||||
_stations.Where(s => IsUserTrain(s.Time, s.Vehicle, s.Position, Math.Max(s.TrainLength, 50) + 150));
|
||||
public IReadOnlyList<StationEvent> UserStations() =>
|
||||
Memo(UserStationsKey, () => _stations.Where(s => IsUserTrain(s.Time, s.Vehicle, s.Position, Math.Max(s.TrainLength, 50) + 150)).ToList());
|
||||
|
||||
/// <summary>The horn points the user's train passed, and whether the horn sounded there.</summary>
|
||||
public IEnumerable<HornCheck> UserHornChecks()
|
||||
public IReadOnlyList<HornCheck> UserHornChecks() => Memo(UserHornChecksKey, () => HornChecks().ToList());
|
||||
|
||||
private IEnumerable<HornCheck> HornChecks()
|
||||
{
|
||||
foreach (var request in _hornRequests)
|
||||
{
|
||||
|
||||
@@ -240,13 +240,16 @@ public sealed record StationEvent(double Time, int Vehicle, StationKind Kind, in
|
||||
/// Whether the whole train stood along the platform, when its length is known: as the driver takes it, the length of
|
||||
/// the trains a W32 is for is the room it has there too.
|
||||
/// </summary>
|
||||
public bool? OnPlatform => Platform != 0 ? Distance >= -2 && Distance + TrainLength <= PlatformLength + 2 : null;
|
||||
public bool? OnPlatform => Platform != 0 ? OffPlatform <= PlatformTolerance : null;
|
||||
|
||||
/// <summary>How far off the platform a train may stand and still count as along it [m].</summary>
|
||||
public const double PlatformTolerance = 2;
|
||||
|
||||
/// <summary>The length of the platform [m], without the digit of its side.</summary>
|
||||
public double PlatformLength => Platforms.Length(Platform);
|
||||
|
||||
/// <summary>How far the tail stood off the platform [m], 0 along it.</summary>
|
||||
public double OffPlatform => Platform != 0 ? Math.Max(0, Distance + TrainLength - PlatformLength) : 0;
|
||||
/// <summary>How much of the train stood off the platform [m]: the head past the stop point, the tail before the platform's end; 0 along it.</summary>
|
||||
public double OffPlatform => Platform != 0 ? Math.Max(0, -Distance) + Math.Max(0, Distance + TrainLength - PlatformLength) : 0;
|
||||
}
|
||||
|
||||
public enum CameraMode : byte
|
||||
@@ -315,7 +318,8 @@ public readonly record struct CabSample(double Time, int Vehicle, Position Posit
|
||||
float MainReservoir, float Voltage, float EngineRpm,
|
||||
Lights LightsHead, Lights LightsTail, sbyte CabEnd = 0,
|
||||
SecurityFlags Security = SecurityFlags.None, float LateralAcceleration = 0,
|
||||
Movement Movement = Movement.Unknown, short LightHintHead = -1, short LightHintTail = -1)
|
||||
Movement Movement = Movement.Unknown, short LightHintHead = -1, short LightHintTail = -1,
|
||||
Lights? EquippedHead = null, Lights? EquippedTail = null)
|
||||
{
|
||||
public bool Has(CabDevices device) => (Devices & device) == device;
|
||||
public bool Has(SecurityFlags flag) => (Security & flag) == flag;
|
||||
|
||||
@@ -66,7 +66,10 @@ public sealed record StationProgress(
|
||||
recording.TimetableAt(t) is { } timetable ? Of(recording, timetable) : Array.Empty<StationProgress>();
|
||||
|
||||
/// <summary>The rows of a timetable of the recording, each with what the train did there.</summary>
|
||||
public static IReadOnlyList<StationProgress> Of(Recording recording, Timetable timetable)
|
||||
public static IReadOnlyList<StationProgress> Of(Recording recording, Timetable timetable) =>
|
||||
recording.Memo((typeof(StationProgress), timetable), () => Work(recording, timetable));
|
||||
|
||||
private static IReadOnlyList<StationProgress> Work(Recording recording, Timetable timetable)
|
||||
{
|
||||
// The records of this train: from its timetable on, until another one is written. A train given its next
|
||||
// timetable may come to the first station of it at once, and a recording made before the simulator wrote the
|
||||
|
||||
Reference in New Issue
Block a user