Files
StarterNG/StarterNG.Tests/BlackBox/BlackBoxFile.cs
maj00r 0d16524953 feat(blackbox): the cab panel as fits the vehicle, every switch the driver worked in the events
- The rest of the cab record is read when written: what drives the
  vehicle, the doors' permits and departure signal, the brakes of the
  whole train, a diesel's oil and temperatures, the cruise control and
  the rest.
- Cab panel: the traction as fits what drives the vehicle (voltage and
  current of an electric one; revolutions, oil pressure and temperatures
  of a diesel; clutch or torque converter of a mechanical or hydraulic
  one; nothing for a draisine), the equalizing reservoir, the main
  reservoir pipe and the most braked cylinder of the train, the brakes'
  setting and mode, a group of the doors, the pumps of a diesel where an
  electric vehicle has its pantographs, the hand brake, the releaser, the
  horn's tone, the radio's channel, the integrated handle of a unit.
- Events: every switch on and off, doors opened and closed, the departure
  signal, the hand brake, the brakes' setting, the radio channel, the
  cruise control's speed; moving with the doors open and an overheating
  engine as faults.
- A vehicle with no lamps at an end (a draisine) is not judged on its
  lights, and one which never had a voltage shows none.
2026-11-11 02:43:51 +01:00

398 lines
13 KiB
C#

using System.Numerics;
using System.Text;
using Eu07.BlackBox;
namespace StarterNG.Tests.BlackBox;
/// <summary>
/// Writes a black box the way the simulator does (blackbox_doc.md of its repository),
/// field by field, so the reader is checked against the layout and not against itself.
/// </summary>
internal sealed class BlackBoxFile
{
private readonly MemoryStream _bytes = new();
private readonly BinaryWriter _file;
public BlackBoxFile(ushort version = 1, bool header = true)
{
_file = new BinaryWriter(_bytes, Encoding.UTF8, leaveOpen: true);
if (header)
{
_file.Write("EU07BBX\0"u8);
_file.Write(version);
_file.Write((ushort)0);
}
}
public byte[] Bytes => _bytes.ToArray();
public MemoryStream Stream() => new(Bytes);
/// <summary>A record of a type, its length and what the writer puts in it.</summary>
public BlackBoxFile Record(byte type, Action<BinaryWriter> payload)
{
using var body = new MemoryStream();
using (var w = new BinaryWriter(body, Encoding.UTF8, leaveOpen: true))
payload(w);
_file.Write(type);
_file.Write((uint)body.Length);
_file.Write(body.ToArray());
return this;
}
public BlackBoxFile Info(double secondOfDay = 10 * 3600 + 34 * 60, string scenery = "td.scn") =>
Record(0x01, w =>
{
w.Write(scenery);
w.Write("2026-11-08T22:56:30");
w.Write((ushort)2026);
w.Write((ushort)312);
w.Write(secondOfDay);
w.Write("ep07-bb");
w.Write("EU07 test");
w.Write(0.1f);
w.Write(1.0f);
w.Write(60.0f);
});
public BlackBoxFile Track(uint index, string name, params Vector3[] points) =>
Track(index, name, 0, 0, points);
public BlackBoxFile Track(uint index, string name, byte kind, byte branch, params Vector3[] points) =>
Record(0x10, w =>
{
w.Write(index);
w.Write(name);
w.Write(kind);
w.Write(branch);
w.Write((byte)1);
w.Write((uint)points.Length);
foreach (var p in points)
{
w.Write(p.X);
w.Write(p.Y);
w.Write(p.Z);
}
});
public BlackBoxFile Signal(uint index, string name, double x, double y, double z) =>
Record(0x11, w =>
{
w.Write(index);
w.Write(name);
w.Write(x);
w.Write(y);
w.Write(z);
});
public BlackBoxFile Vehicle(uint id, string name, string train = "", float length = 16.3f) =>
Record(0x12, w =>
{
w.Write(id);
w.Write(name);
w.Write("303e-ep-tv");
w.Write(train);
w.Write(length);
w.Write(2.9f);
w.Write((byte)1);
});
public BlackBoxFile Removed(double time, uint id) =>
Record(0x13, w =>
{
w.Write(time);
w.Write(id);
});
public BlackBoxFile Cab(double time, uint vehicle, double z, float speed, byte driving = 1, byte flags = 0x03,
float target = 40, float nextLimit = -1, float nextDistance = -1, float brakeCylinder = 0,
uint devices = 0x0007, bool extended = true, sbyte cabEnd = 1, byte security = 0,
float lateral = 0.2f, ushort lightsHead = 0x15, ushort lightsTail = 0x22, byte? movement = null,
short hintHead = -1, short hintTail = -1, ushort? equippedHead = null, ushort? equippedTail = null,
byte? engine = null, float oilTemperature = 70) =>
Record(0x20, w =>
{
w.Write(time);
w.Write(vehicle);
w.Write(5.0);
w.Write(0.4);
w.Write(z);
w.Write(speed);
w.Write(0.1f); // acceleration
w.Write(1.5f); // odometer
w.Write(driving);
w.Write(flags);
w.Write((sbyte)3); // main controller
w.Write((sbyte)0);
w.Write((sbyte)1);
w.Write(0.0f); // train brake
w.Write(0.0f);
w.Write(brakeCylinder);
w.Write(5.0f); // brake pipe
w.Write(320.0f); // current
w.Write(target);
w.Write(nextLimit);
w.Write(nextDistance);
w.Write(0.3f);
w.Write(150.0f);
w.Write(60.0f);
w.Write(-1.0f);
w.Write(devices);
w.Write(8.5f); // main reservoir
w.Write(3000.0f); // voltage
w.Write(1100.0f); // revolutions
w.Write(lightsHead); // lights at the head of the train
w.Write(lightsTail);
// The cab, the train protection and the lateral acceleration: a recording made before them ends above.
if (extended)
{
w.Write(cabEnd);
w.Write(security);
w.Write(lateral);
// what the driver goes by and the lights asked for: a recording made before them ends above
if (movement is { } kind)
{
w.Write(kind);
w.Write(hintHead);
w.Write(hintTail);
// the lamps the vehicles at the ends have: a recording made before them ends above
if (equippedHead is { } head)
{
w.Write(head);
w.Write(equippedTail ?? head);
// what drives it and the rest: a recording made before them ends above
if (engine is { } kind2)
{
w.Write(kind2);
w.Write((byte)2); // brake setting P
w.Write((byte)3); // radio channel
w.Write(4.8f); // equalizing reservoir
w.Write(8.0f); // main reservoir pipe
w.Write(0.45f); // oil pressure
w.Write(75.0f); // water
w.Write(oilTemperature);
w.Write(80.0f); // engine
w.Write(0.0f); // cruise control
w.Write(900.0f); // power
w.Write((sbyte)0); // hand brake
w.Write((byte)1); // brake mode
w.Write((byte)0); // horn
w.Write(0.5f); // integrated controller
w.Write(1.0f); // clutch
w.Write(0.0f); // torque converter
w.Write(2.5f); // the highest brake cylinder of the train
}
}
}
}
});
public BlackBoxFile Vehicles(double time, bool keyframe, params (uint Id, float X, float Z, float Speed)[] states) =>
Record(0x21, w =>
{
w.Write(time);
w.Write((byte)(keyframe ? 1 : 0));
w.Write((uint)states.Length);
foreach (var s in states)
{
w.Write(s.Id);
w.Write(s.X);
w.Write(0.4f);
w.Write(s.Z);
w.Write(0.0f); // heading
w.Write(s.Speed);
w.Write((byte)2);
}
});
public BlackBoxFile Switch(double time, uint track, byte state) =>
Record(0x22, w =>
{
w.Write(time);
w.Write(track);
w.Write(state);
});
public BlackBoxFile Aspect(double time, uint signal, float speed1, float speed2, string command = "SetVelocity") =>
Record(0x23, w =>
{
w.Write(time);
w.Write(signal);
w.Write(speed1);
w.Write(speed2);
w.Write(command);
});
public BlackBoxFile Command(double time, uint vehicle, bool pressed, string name) =>
Record(0x30, w =>
{
w.Write(time);
w.Write(vehicle);
w.Write((byte)(pressed ? 1 : 0));
w.Write(1.0);
w.Write(2.0);
w.Write(name);
});
public BlackBoxFile Driver(double time, uint vehicle, byte driving) =>
Record(0x31, w =>
{
w.Write(time);
w.Write(vehicle);
w.Write(driving);
});
public BlackBoxFile Passed(double time, uint vehicle, uint signal, string name, float speed1, float speed,
byte flags = 0, double z = 1300) =>
Record(0x32, w =>
{
w.Write(time);
w.Write(vehicle);
w.Write(signal);
w.Write(name);
w.Write(5.0);
w.Write(0.4);
w.Write(z);
w.Write(speed1);
w.Write(-1.0f);
w.Write(speed);
w.Write(2.5f);
w.Write(flags);
});
public BlackBoxFile User(double time, uint vehicle, bool inCab, sbyte? cabEnd = null) =>
Record(0x33, w =>
{
w.Write(time);
w.Write(vehicle);
w.Write((byte)(inCab ? 1 : 0));
if (cabEnd is { } end)
w.Write(end);
});
public BlackBoxFile StopPoint(uint index, string station, double z, float stopAt, float platform, float? heading = null) =>
Record(0x14, w =>
{
w.Write(index);
w.Write(station);
w.Write(5.0);
w.Write(0.4);
w.Write(z);
w.Write(stopAt);
w.Write(platform);
if (heading is { } way)
w.Write(way);
});
public BlackBoxFile HornPoint(uint index, string name, double z, float duration = 2, float tone = 1) =>
Record(0x15, w =>
{
w.Write(index);
w.Write(name);
w.Write(5.0);
w.Write(0.4);
w.Write(z);
w.Write(duration);
w.Write(tone);
});
public BlackBoxFile Timetable(double time, uint vehicle, string train, string from, string to,
params (string Station, float Arrival, float Departure)[] rows) =>
Record(0x24, w =>
{
w.Write(time);
w.Write(vehicle);
w.Write(train);
w.Write("ROJ");
w.Write(from);
w.Write(to);
w.Write(100.0f);
w.Write((uint)rows.Length);
float km = 0;
foreach (var row in rows)
{
w.Write(row.Station);
w.Write(km += 2.5f);
w.Write(100.0f);
w.Write(row.Arrival);
w.Write(row.Departure);
w.Write((byte)0);
}
});
public BlackBoxFile Station(double time, uint vehicle, byte kind, ushort row, string station, double z,
float distance = 1, float stopAt = 0, float trainLength = 87, float platform = 150,
float arrival = -1, float departure = -1) =>
Record(0x35, w =>
{
w.Write(time);
w.Write(vehicle);
w.Write(kind);
w.Write(row);
w.Write(station);
w.Write(5.0);
w.Write(0.4);
w.Write(z);
w.Write(distance);
w.Write(stopAt);
w.Write(trainLength);
w.Write(platform);
w.Write(arrival);
w.Write(departure);
});
public BlackBoxFile HornRequest(double time, uint vehicle, double z, float duration = 2) =>
Record(0x36, w =>
{
w.Write(time);
w.Write(vehicle);
w.Write(duration);
w.Write(1.0f);
w.Write(5.0);
w.Write(0.4);
w.Write(z);
});
public BlackBoxFile Camera(double time, byte mode, uint vehicle, double z, byte view = 0, float heading = 0.5f) =>
Record(0x25, w =>
{
w.Write(time);
w.Write(mode);
w.Write(view);
w.Write(vehicle);
w.Write(5.0);
w.Write(2.4);
w.Write(z);
w.Write(heading);
w.Write(-0.1f);
});
public BlackBoxFile Coupling(double time, uint vehicle, byte side, uint other, byte otherSide, byte flags,
byte previousFlags, uint previousOther) =>
Record(0x37, w =>
{
w.Write(time);
w.Write(vehicle);
w.Write(side);
w.Write(other);
w.Write(otherSide);
w.Write(flags);
w.Write(previousFlags);
w.Write(previousOther);
w.Write(5.0);
w.Write(0.4);
w.Write(1150.0);
});
public BlackBoxFile Clock(double time, double secondOfDay) =>
Record(0x34, w =>
{
w.Write(time);
w.Write((ushort)2026);
w.Write((ushort)312);
w.Write(secondOfDay);
});
public BlackBoxFile End(double time) => Record(0x3f, w => w.Write(time));
}