mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-11-08 18:50:34 +01:00
* test: real vehicles and golden traces in the unit tests The game's own vehicles (dynamic/ of the release used for the tests) as JSON of what the game's vehicle loader reads of their .fiz files, includes expanded: test_fiz.cpp converts them on demand and checks the loader reads the same sections, values and rows as of the game's file (fiz.h). Chosen to cover every engine type, brake valve, brake handle, local brake, coupler type, door type and brake delay set the game data uses. Golden traces (golden.h) characterise the simulation, compared within a relative tolerance or exactly, re-recorded with EU07_UNIT_TESTS_UPDATE_GOLDEN=1: - the parameters of every real vehicle as loaded, - the brakes of every real vehicle through charge, service steps, release, overcharge and emergency, on every brake delay setting, - every position of every driver's and local brake handle, - every powered vehicle started up the way the AI does it, driven to full power and braked to a stop (drive.h), - the real electric units and a locomotive with wagons driven whole, as trains of coupled vehicles stepped the way the game steps them (consist.h), - scenarios of the controls (scenario.h): doors, reversing and cab change, slip and sand, emergency stops, releasers, the ep and the electrodynamic brake, heating, loading, a collision, and frames of several physics iterations. * test: commands passed between vehicles Every command RunCommand knows run on every real vehicle and recorded, commands travelling through a train with a vehicle facing backwards, a coupling without the control cable, and loading and unloading by commands. * test: every switch of the cab, sent to the next vehicle Each switch sending its state to the train, each way and to each range, with the controllers and direction among them, on every real vehicle driving another of its kind, recorded for both. * test: the switches of the cab worked on a pair under the catenary The same switches with two seconds for the vehicles to act on each, so the pantographs rise, the main switch closes and the auxiliaries start. * test: gauges of trains in multiple and the damage descriptions The ammeters and the brake force the AI expects, recorded on the electric units and locomotives in multiple driven, the damage described on the driver's panel and ammeters showing the current of controlled vehicles. * test: give load commands their place, as the game does The loading test set its commands with SetInternalCommand, which leaves TCommand::Location as it is: uninitialised in a fresh vehicle, so the distance check of Load= and UnLoad= read garbage and the test failed now and then. The game gives them through PutCommand with the place of the platform, and so do the tests now. * test: brake valves and handles no vehicle of the game data has Fitted to a wagon and to an EU07: the valve variants and handles the loader knows that no real vehicle uses, the CV1 ones and the test handle among them, and a name the loader doesn't know. * test: braking from speed with the automatic ED brake, and a draisine Trains with the EP09, the E6ACT and the EW58 braked by ep, light and full service applications from speed, and the draisine pumped from rest and rolling. Only the E6ACT brings its electrodynamic brake in here. * test: the EP09's electrodynamic brake in service braking held to a stop start_up() took a vehicle for ready once its line breaker was closed and its reservoir full, which a train made already moving is at once: the EP09's manually started converter was never switched on, so there was no 110 V and no electrodynamic brake. It now waits for the converter. The EP09 with wagons is braked from 120 km/h by light, medium, high and full service applications held to a stop: the motors brake, the air of its cylinders is let out and given back as the train slows down. The flag of the electrodynamic brake stays on at a standstill below 2.1 bar. C strings given to CAPTURE are shown as text. * test: the converter switched on before a train is ready, the EW58's ep-staged electrodynamic brake start_up() called a train ready as soon as its line breaker closed and the air was there, so the converter of a unit having it in another car than the cab (the EW58's middle car) was never switched on, and its electrodynamic brake, gated by the 110 V, never came in. The converter and the compressor now always get switched on first. The EW58 brakes electrically by the stages of the ep brake, not by the pipe alone. * test: the ET42 and the 214M railbus among the real vehicles The ET42's halves (its own series motor branch: the motors in series at the high current setting) and the 214MA, a diesel railbus with the hydraulic torque converter, the retarder and the controller of an induction motor drive, converted from the game data and recorded in every sweep of the real vehicles. * test: the wagons' hand brakes released with the train's brakes; the 754, SM31 and SU46 release_brakes() released the hand brake of the driver's vehicle only, so every wagon set up braked kept its hand brake on and got dragged along. It now releases the hand and spring brakes of the whole train, as TController::AutoRewident() does when the AI takes it over, and the traces of the trains with wagons are recorded anew. The 754, the SM31 and the SU46 join the real vehicles for the kinds of the diesel-electrics' field shunting relays the others lack (RelayType 0, 1 and 46). * test: traction on a lost and too high catenary voltage, the ET42, the diesel-electrics' shunting relays The line breaker opened by the voltage relays of a series motor locomotive and by the limits of an induction motor one. The ET42 at the high current setting, which keeps its halves' motors in series, and its switched electrodynamic brake, whose current trips the overload relay from the top speed of the low setting. The field shunting of the diesel-electrics through each kind of their relays, at full power and notched back. * test: the EW58's electrodynamic brake stages, the EU47's speed control and braking at the voltage's limit The stages of the EW58's motor brake set by the positions of its ep handle, the first one alone over 100 km/h. The EU47's second controller holding, raising and lowering the speed set, its local brake setting the flag of the electrodynamic brake without the motors braking, and its brake feeding back less current into a catenary over the drive's MaxV. * test: diesel engines too cold and too hot to start, the 214M railbus's controller and retarder The heat of the diesel engines: at -10 degrees the ones with the lowest water temperature set don't start until the heater warms them, at 88 degrees the ones with a second water circuit don't start while it cools, and the SM42 run hot at full power is kept under its highest temperature by its fans and shutters. The 214M railbus's controller from the air brake through the retarder and neutral to power, its torque converter's lock-up and its gearbox. The controller helpers of the traction tests (full power, waiting for the line contactors, and notching back) move to test::scenario. * test: couplers coupled, uncoupled, broken and colliding; loads assigned, loaded and unloaded Coupling by hand touching or apart and of the same kind or not, with the sounds of what changes; uncoupling a screw coupler pressed together, stretched, with the hoses alone or broken; the automatic couplers of electric units coupling on contact, without the control cable between two kinds; a coupler broken with its brake hose; collisions over 15 km/h derailing. The pantographs set up by the pantstate load, a load of one kind at a time up to the capacity, and the steps of loading and unloading. * test: the battery drained and charged, the pantographs' tank and pressure switch, the 214M's speed control The EU07's battery drained without the converter by the lights and the small compressor until it opens the line breaker and nothing starts, charged by the converter, and switched off. The pantographs on their own leaking tank until the pressure switch opens the line breaker, and the small compressor pumping it up and priming the switch. The 214M railbus's speed control set by its buttons, steps and power limit, braking down with the retarder, and standing by. * test: the MHZ handles' buttons, the valves at speed, every brake material, the NESt3's relays The overload and the high pressure buttons of the MHZ handles. The KE and the LSt at speed in the R and P settings, under the anti-slip request and the releaser button. Every material of the brake shoes and disks the loader takes, braking a wagon from speed, the ones no game vehicle has among them. The CV1_R and the Oerlikon valve's relays set by its name (-ED, AL2 without ESt3) fitted to a wagon. * test: the loader's lists and lines: the 218M, SA134 and 22WEc, a compressor list, Pmax by speed, skipped lines The 218M (V2NList), the SA134 (HTCList) and the Elf 22WEc (ffBrakeList) join the real vehicles for the lists the others lack. Definitions made by the tests for what no game file has: a compressor list, whose table of whole numbers reads a factor of 0.9 as 0; the compressor's limits apart for the rear cab; the highest power of an induction motor drive by the speed, extrapolated past the table; and the lines the loader skips or trims (comments, a hash anywhere, leading spaces, carriage returns). * test: what the cab and the AI call on a vehicle: gauges of the train, the split ED brake, spring brake, cut-off The engine rotation of up to three vehicles in multiple and the lamps of the overload relay and the starting resistors read through the control cable; the engine's idle speed and the ET42's second controller shown in its dynamic brake; the controller of the electrodynamic brake apart from the local brake, which no game vehicle has yet; the spring brake shut off and released, the ep brake's fuse and the handle stepped by the AI; damaged motors cut off; the cab activated by itself; a road vehicle moved across a road, for which the tests' track gets a width; and the lamps and the buzzer of the security system left unattended. * test: the EP05 and its camshaft among the real vehicles, the headlights' colour and brightness The EP05, whose camshaft closes the line breaker and the line contactors by the camshaft's own position, converted from the game data and recorded in every sweep of the real vehicles. The Headlights section, which only the 214M's own file has, in a definition made by the test. * test: what the unit tests reach of the simulation core, what's dead or left, and what they found COVERAGE.md: the blocks of Mover.cpp, hamulce.cpp, Oerlikon_ESt.cpp and friction.cpp the tests run, the code nothing in the game runs, the code only data no game vehicle has would run, what's reachable and left, and the behaviour the tests found that may deserve a look. * test: check invariants of the physics on every step of every golden trace Rules any vehicle keeps whatever a test does (no NaN, cylinders and pipes within their limits, no current with the line breaker or contactors open, no force without power, distance as the speed runs, no acceleration over 5 g), checked on every step, with no comparison to a recorded file. The traces where a test or a game vehicle breaks one are excused with the reason; the vehicles' cases are in the findings of COVERAGE.md. * test: key figures of every golden trace, per part of the run Duration, speeds, distance, time to stop, the brake pipe's lowest pressure and time to recover, the peaks of the cylinders, the motor current and the tractive force, for each part between two events, in data/golden/<name>.figures. Compared within 0.5% or the last decimal, so they hold on any compiler, and an intended change of behaviour shows in a few readable lines. Traces sampled by hand keep none. * test: keep the golden samples out of the repository, recorded on the code before a change The samples of the golden traces are 29 MB and only mean something against the code before the change they check, so they're recorded locally before it (EU07_UNIT_TESTS_UPDATE_GOLDEN=1) and compared after it, and ignored by git. The key figures and the invariants stay, and are what the CI checks. Without samples recorded a trace compares only its key figures, but with EU07_UNIT_TESTS_GOLDEN_EXACT=1, the check of a change which shouldn't alter anything, it fails, so the check can't pass by having nothing to compare. * fix: draw the noise of the control pipe pressure in one order on every compiler The order of the two Random(10) calls in 'Random(10) - Random(10)' is unspecified: GCC draws them the other way round than MSVC Release, which mirrored the control pipe pressure around 5 bar and failed 32 unit tests on the Linux CI. The draws are sequenced in the order of MSVC Release, which the golden traces were recorded with. * test: count the start-up rounds with an integer, not a double (SonarCloud S2193) * test: behaviour in CHECKs, the key figures out of the repository The key figures of the golden traces are recorded with their samples on the code before a change, and compared after it, instead of being kept: a trace without them checks the invariants and its test's CHECKs, which is what the CI runs. The scenarios of the controls state what they show in CHECKs, and the runs of every real vehicle what holds for each of them. Writing them showed scenarios which didn't do what they were named for: the cab change stopped in the engine room, the slip runs put the controller to full power at once and never got the line contactors closed, and the train stopped by the alarm chain never got its pipe charged again. They do it now; the rest went to the findings. * test: the SonarCloud findings in the unit tests std::format for the texts put together, emplace_back, const references, string_view parameters, std::array and std::vector for the C arrays, the ranges algorithms, names no longer shadowing the coupler ends, comments in the empty loops; start_up(), trace::check() and the converter of the definitions split up. BrakeForceR() is const, which it always was in effect, so the gauges test needs no const_cast. The golden traces compare bit for bit.
633 lines
23 KiB
C++
633 lines
23 KiB
C++
#include "stdafx.h"
|
|
#include "doctest.h"
|
|
#include "scenario.h"
|
|
#include "drive.h"
|
|
|
|
#include "McZapkie/hamulce.h"
|
|
#include "utilities/utilities.h"
|
|
|
|
// characterisation of the controls a driver and a crew use besides driving and braking: doors, direction and
|
|
// cab change, sanding against slip, the emergency brake, releasers, the ep and the dynamic brake, heating,
|
|
// loading, collisions and the frame of several physics iterations, each in a scenario on real vehicles,
|
|
// recorded in golden traces (data/golden/controls/)
|
|
|
|
using test::scenario;
|
|
using namespace test::channels;
|
|
|
|
namespace {
|
|
|
|
void notch_up(scenario &Scenario, int Positions, double Seconds) {
|
|
for (int i = 0; i < Positions; ++i) {
|
|
Scenario.driver().controlling.IncMainCtrl(1);
|
|
Scenario.wait(Seconds);
|
|
}
|
|
}
|
|
|
|
void notch_down(scenario &Scenario) {
|
|
while (Scenario.driver().controlling.DecMainCtrl(1)) {
|
|
Scenario.wait(0.5);
|
|
}
|
|
Scenario.driver().controlling.DecScndCtrl(2);
|
|
}
|
|
|
|
void brake_to_stop(scenario &Scenario, double Seconds = 60.0) {
|
|
Scenario.act("brake to stop");
|
|
Scenario.cab().BrakeLevelSet(Scenario.cab().Handle->GetPos(bh_FB));
|
|
Scenario.wait(Seconds);
|
|
Scenario.cab().BrakeLevelSet(Scenario.cab().Handle->GetPos(bh_RP));
|
|
Scenario.wait(20.0);
|
|
}
|
|
|
|
// the pipe charged again after an emergency stop: the releaser held with the handle at running, then let go
|
|
void recharge(scenario &Scenario) {
|
|
auto &cab{Scenario.cab()};
|
|
Scenario.act("releaser held, the pipe charged");
|
|
cab.BrakeLevelSet(cab.Handle->GetPos(bh_RP));
|
|
cab.BrakeReleaser(1);
|
|
Scenario.wait(30.0);
|
|
cab.BrakeReleaser(0);
|
|
Scenario.wait(30.0);
|
|
}
|
|
|
|
bool stopped(scenario &Scenario) {
|
|
for (std::size_t i = 0; i < Scenario.train().size(); ++i) {
|
|
if (Scenario.vehicle(i).Vel > 0.1) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// the brake cylinders of every vehicle of the train under the pressure given
|
|
bool released(scenario &Scenario, double Pressure = 0.1) {
|
|
for (std::size_t i = 0; i < Scenario.train().size(); ++i) {
|
|
if (Scenario.vehicle(i).BrakePress > Pressure) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool open(TMoverParameters const &Vehicle, int Side) {
|
|
return Vehicle.Doors.instances[Side].is_open;
|
|
}
|
|
|
|
// the leaf shut, whatever the step does (closed, is_closed, wants the step folded too)
|
|
bool shut(TMoverParameters const &Vehicle, int Side) {
|
|
return Vehicle.Doors.instances[Side].position <= 0.f;
|
|
}
|
|
|
|
bool closed(TMoverParameters const &Vehicle, int Side) {
|
|
return Vehicle.Doors.instances[Side].is_closed;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("controls: the doors of an electric unit, opened, locked, permitted and closed by driving off") {
|
|
for (auto const &[name, cars] : std::vector<std::pair<std::string, std::vector<char const *>>>{
|
|
{"elf", {"elf_v1/21we-a", "elf_v1/21we-b", "elf_v1/21we-c"}},
|
|
{"en57", {"en57_v1/5bgaiii_led_spot", "en57_v1/6ba_840", "en57_v1/5bgb"}},
|
|
{"impuls", {"impuls_v1/31we-a_pr", "impuls_v1/31we-b_pr", "impuls_v1/31we-c_pr", "impuls_v1/31we-d_pr"}}}) {
|
|
CAPTURE(name);
|
|
scenario scenario{"controls/doors/" + name, motion() + doors()};
|
|
for (auto const *car : cars) {
|
|
scenario.add(car, scenario.train().size() == 0 ? 1 : 0);
|
|
}
|
|
// the EN57 doesn't get ready (COVERAGE.md), its doors work all the same
|
|
auto const ready{scenario.start_up()};
|
|
CHECK(ready == (name != "en57"));
|
|
auto &cab{scenario.cab()};
|
|
auto const &last{scenario.vehicle(scenario.train().size() - 1)};
|
|
scenario.act("permit left");
|
|
cab.PermitDoors(side::left, true);
|
|
scenario.wait(2.0);
|
|
scenario.act("open left");
|
|
cab.OperateDoors(side::left, true);
|
|
scenario.wait(10.0);
|
|
CHECK(open(cab, side::left));
|
|
CHECK(open(last, side::left));
|
|
CHECK(closed(cab, side::right));
|
|
scenario.act("close left");
|
|
cab.OperateDoors(side::left, false);
|
|
scenario.wait(10.0);
|
|
CHECK(shut(cab, side::left));
|
|
CHECK(shut(last, side::left));
|
|
scenario.act("open right without permit");
|
|
cab.OperateDoors(side::right, true);
|
|
scenario.wait(5.0);
|
|
// the EN57 needs no permit
|
|
CHECK(shut(cab, side::right) == cab.Doors.permit_needed);
|
|
scenario.act("permit right, open");
|
|
cab.PermitDoors(side::right, true);
|
|
cab.OperateDoors(side::right, true);
|
|
scenario.wait(10.0);
|
|
CHECK(open(cab, side::right));
|
|
CHECK(open(last, side::right));
|
|
scenario.act("lock");
|
|
cab.LockDoors(true);
|
|
scenario.wait(5.0);
|
|
scenario.act("permit preset, steps off, remote control");
|
|
cab.ChangeDoorPermitPreset(1);
|
|
cab.PermitDoorStep(false);
|
|
cab.ChangeDoorControlMode(true);
|
|
scenario.wait(5.0);
|
|
cab.ChangeDoorPermitPreset(-1);
|
|
cab.PermitDoorStep(true);
|
|
cab.ChangeDoorControlMode(false);
|
|
scenario.act("close all, unlock");
|
|
cab.OperateDoors(side::right, false);
|
|
cab.OperateDoors(side::left, false);
|
|
cab.LockDoors(false);
|
|
scenario.wait(10.0);
|
|
CHECK(shut(cab, side::left));
|
|
CHECK(shut(cab, side::right));
|
|
scenario.act("open both and drive off");
|
|
cab.PermitDoors(side::left, true);
|
|
cab.PermitDoors(side::right, true);
|
|
cab.OperateDoors(side::left, true);
|
|
cab.OperateDoors(side::right, true);
|
|
scenario.wait(5.0);
|
|
CHECK_FALSE(shut(cab, side::left));
|
|
CHECK_FALSE(shut(cab, side::right));
|
|
scenario.get_going();
|
|
notch_up(scenario, 4, 2.0);
|
|
scenario.wait(20.0);
|
|
// the doors of a train ready to run close as the driver starts off. no CHECK of the speed: the Elf and
|
|
// the Impuls keep their steps out under the permit, which keeps them from moving (COVERAGE.md)
|
|
CHECK(shut(cab, side::left) == ready);
|
|
CHECK(shut(cab, side::right) == ready);
|
|
CHECK(shut(last, side::left) == ready);
|
|
CHECK(shut(last, side::right) == ready);
|
|
notch_down(scenario);
|
|
brake_to_stop(scenario);
|
|
scenario.check();
|
|
}
|
|
}
|
|
|
|
TEST_CASE("controls: the doors of wagons, worked from the locomotive") {
|
|
scenario scenario{"controls/doors/eu07 with wagons", motion() + doors()};
|
|
scenario.add("4e_v1/4e", 1).add("bdhpumn_v3/bdhpumn").add("11xa_v2/93a");
|
|
scenario.start_up();
|
|
auto &cab{scenario.cab()};
|
|
auto &coach{scenario.vehicle(1)};
|
|
auto const &freight{scenario.vehicle(2)};
|
|
// the locomotive works the coach's doors for the consist and for the unit, not with its local controls; the
|
|
// freight wagon's doors don't move from the locomotive
|
|
for (auto const range : {range_t::consist, range_t::unit, range_t::local}) {
|
|
CAPTURE(static_cast<int>(range));
|
|
scenario.act(std::format("open right {}", static_cast<int>(range)));
|
|
cab.PermitDoors(side::right, true, range);
|
|
cab.OperateDoors(side::right, true, range);
|
|
scenario.wait(10.0);
|
|
CHECK(open(coach, side::right) == (range != range_t::local));
|
|
CHECK(shut(coach, side::left));
|
|
CHECK(shut(freight, side::right));
|
|
scenario.act("close right");
|
|
cab.OperateDoors(side::right, false, range);
|
|
scenario.wait(10.0);
|
|
CHECK(shut(coach, side::right));
|
|
}
|
|
scenario.act("wagon opens its own");
|
|
coach.OperateDoors(side::left, true, range_t::local);
|
|
// a second at a time, the same run as one wait. the coach's doors close by themselves 5 s after they
|
|
// opened (DoorStayOpen)
|
|
scenario.wait(1.0);
|
|
CHECK_FALSE(shut(coach, side::left));
|
|
scenario.wait(9.0);
|
|
CHECK(shut(coach, side::left));
|
|
coach.OperateDoors(side::left, false, range_t::local);
|
|
scenario.wait(10.0);
|
|
CHECK(shut(coach, side::left));
|
|
scenario.check();
|
|
}
|
|
|
|
TEST_CASE("controls: reversing and changing the cab of a locomotive") {
|
|
scenario scenario{"controls/reverse/eu07", motion() + traction()};
|
|
scenario.add("4e_v1/4e", 1);
|
|
scenario.start_up();
|
|
scenario.get_going();
|
|
auto &cab{scenario.cab()};
|
|
// the speed along the vehicle, its front ahead
|
|
notch_up(scenario, 10, 1.5);
|
|
scenario.wait(10.0);
|
|
CHECK(cab.V > 3.0);
|
|
notch_down(scenario);
|
|
brake_to_stop(scenario);
|
|
CHECK(stopped(scenario));
|
|
scenario.act("backward");
|
|
cab.DirectionBackward();
|
|
cab.DirectionBackward();
|
|
scenario.wait(2.0);
|
|
CHECK(cab.DirActive < 0);
|
|
notch_up(scenario, 10, 1.5);
|
|
scenario.wait(10.0);
|
|
CHECK(cab.V < -3.0);
|
|
notch_down(scenario);
|
|
brake_to_stop(scenario);
|
|
CHECK(stopped(scenario));
|
|
scenario.act("neutral, change cab");
|
|
cab.DirectionForward();
|
|
scenario.wait(1.0);
|
|
cab.CabDeactivisation();
|
|
// through the engine room (cab 0) to the rear cab, as the player walks there
|
|
cab.ChangeCab(-1);
|
|
cab.ChangeCab(-1);
|
|
cab.CabActivisation();
|
|
scenario.wait(5.0);
|
|
CHECK(cab.CabActive < 0);
|
|
scenario.act("drive from the rear cab");
|
|
scenario.get_going();
|
|
notch_up(scenario, 10, 1.5);
|
|
scenario.wait(10.0);
|
|
// forward from the rear cab is the vehicle's backward
|
|
CHECK(cab.V < -3.0);
|
|
notch_down(scenario);
|
|
brake_to_stop(scenario);
|
|
CHECK(stopped(scenario));
|
|
scenario.check();
|
|
}
|
|
|
|
TEST_CASE("controls: wheel slip on a slippery track, sand and the anti-slip brake") {
|
|
for (auto const *path : {"4e_v1/4e", "sm42_v1/6d1", "e186_v2/eu47"}) {
|
|
CAPTURE(path);
|
|
scenario scenario{std::string{"controls/slip/"} + path, motion() + traction() + brakes(), test::track{0.04, 0.0, test::catenary_3kv}};
|
|
scenario.add(path, 1);
|
|
scenario.start_up();
|
|
scenario.get_going();
|
|
auto &cab{scenario.cab()};
|
|
// a notch a second: the controller put to full power at once never gets the line contactors closed
|
|
scenario.act("full power");
|
|
scenario.full_power(0.0);
|
|
auto slipped{cab.SlippingWheels};
|
|
for (int i = 0; i < 10; ++i) {
|
|
scenario.wait(1.0);
|
|
slipped = slipped || cab.SlippingWheels;
|
|
}
|
|
// the electric locomotives slip at full power, the SM42, of less power for its weight, doesn't
|
|
CHECK(slipped == (std::string{path} != "sm42_v1/6d1"));
|
|
scenario.act("sand");
|
|
cab.Sandbox(true);
|
|
scenario.wait(10.0);
|
|
cab.Sandbox(false);
|
|
scenario.act("manual sand");
|
|
cab.SandboxManual(true);
|
|
scenario.wait(5.0);
|
|
cab.SandboxManual(false);
|
|
scenario.act("anti-slip brake");
|
|
cab.AntiSlippingButton();
|
|
cab.AntiSlippingBrake();
|
|
scenario.wait(10.0);
|
|
CHECK(cab.Vel > 5.0);
|
|
notch_down(scenario);
|
|
brake_to_stop(scenario);
|
|
CHECK(stopped(scenario));
|
|
scenario.check();
|
|
}
|
|
}
|
|
|
|
TEST_CASE("controls: emergency stops by the alarm chain and the radio-stop") {
|
|
scenario scenario{"controls/emergency/eu07 with wagons", motion() + brakes()};
|
|
scenario.add("4e_v1/4e", 1).add("bdhpumn_v3/bdhpumn").add("3wl_v2/3wl");
|
|
scenario.start_up();
|
|
scenario.get_going();
|
|
notch_up(scenario, 20, 1.0);
|
|
scenario.wait(20.0);
|
|
notch_down(scenario);
|
|
auto &cab{scenario.cab()};
|
|
CHECK(cab.Vel > 30.0);
|
|
scenario.act("alarm chain in the wagon");
|
|
scenario.vehicle(1).AlarmChainSwitch(true);
|
|
scenario.wait(40.0);
|
|
// the pipe emptied, the whole train braked to a stop
|
|
CHECK(stopped(scenario));
|
|
CHECK(cab.PipePress < 0.5);
|
|
CHECK(scenario.vehicle(2).BrakePress > 1.0);
|
|
scenario.act("alarm chain reset");
|
|
scenario.vehicle(1).AlarmChainSwitch(false);
|
|
scenario.wait(10.0);
|
|
// the FV4a handle stays cut off the emptied pipe of an LSt locomotive (lock_old in UpdatePipePressure)
|
|
// until the releaser is held
|
|
CHECK(cab.PipePress < 0.5);
|
|
recharge(scenario);
|
|
CHECK(cab.PipePress > 4.5);
|
|
scenario.get_going();
|
|
notch_up(scenario, 20, 1.0);
|
|
scenario.wait(20.0);
|
|
notch_down(scenario);
|
|
CHECK(cab.Vel > 30.0);
|
|
scenario.act("radio-stop");
|
|
cab.RadiostopSwitch(true);
|
|
scenario.wait(40.0);
|
|
CHECK(stopped(scenario));
|
|
CHECK(cab.PipePress < 0.5);
|
|
scenario.act("radio-stop reset");
|
|
cab.RadiostopSwitch(false);
|
|
scenario.wait(10.0);
|
|
recharge(scenario);
|
|
CHECK(cab.PipePress > 4.5);
|
|
scenario.check();
|
|
}
|
|
|
|
TEST_CASE("controls: releasers and the universal brake buttons") {
|
|
scenario scenario{"controls/releaser/eu07 with wagons", brakes() + air()};
|
|
scenario.add("4e_v1/4e", 1).add("bdhpumn_v3/bdhpumn").add("3wl_v2/3wl");
|
|
scenario.start_up();
|
|
scenario.get_going();
|
|
auto &cab{scenario.cab()};
|
|
auto &wagon{scenario.vehicle(2)};
|
|
scenario.act("service brake");
|
|
cab.BrakeLevelSet(cab.Handle->GetPos(bh_FB));
|
|
scenario.wait(20.0);
|
|
CHECK(cab.BrakePress > 1.0);
|
|
CHECK(wagon.BrakePress > 1.0);
|
|
cab.BrakeLevelSet(cab.Handle->GetPos(bh_NP));
|
|
auto &coach{scenario.vehicle(1)};
|
|
// the releaser of the cab is passed along the whole train, a wagon's works on the wagon alone
|
|
auto const coachbraked{coach.BrakePress};
|
|
auto wagonbraked{wagon.BrakePress};
|
|
scenario.act("releaser of the locomotive");
|
|
cab.BrakeReleaser(1);
|
|
scenario.wait(10.0);
|
|
cab.BrakeReleaser(0);
|
|
CHECK(cab.BrakePress < 0.5);
|
|
CHECK(coach.BrakePress < coachbraked - 1.0);
|
|
CHECK(wagon.BrakePress < wagonbraked - 0.5);
|
|
CHECK(cab.PipePress < 4.0); // the pipe stays where the service application left it
|
|
wagonbraked = wagon.BrakePress;
|
|
scenario.act("releaser of the wagon");
|
|
wagon.BrakeReleaser(1);
|
|
scenario.wait(10.0);
|
|
wagon.BrakeReleaser(0);
|
|
CHECK(wagon.BrakePress < wagonbraked - 0.5);
|
|
for (int button = 0; button < 3; ++button) {
|
|
scenario.act(std::format("universal button {}", button));
|
|
cab.UniversalBrakeButton(button, 1);
|
|
scenario.wait(5.0);
|
|
cab.UniversalBrakeButton(button, 0);
|
|
scenario.wait(5.0);
|
|
}
|
|
scenario.act("running");
|
|
cab.BrakeLevelSet(cab.Handle->GetPos(bh_RP));
|
|
scenario.wait(60.0);
|
|
CHECK(cab.PipePress > 4.8);
|
|
CHECK(released(scenario));
|
|
scenario.check();
|
|
}
|
|
|
|
TEST_CASE("controls: the ep brake of electric units") {
|
|
for (auto const &[name, cars] : std::vector<std::pair<std::string, std::vector<char const *>>>{
|
|
{"ed72", {"ed72_v1/5bs-ra", "ed72_v1/6bs-sa", "ed72_v1/6bs-sb", "ed72_v1/5bs-rb"}},
|
|
{"ew58", {"ew58_v1/ew58-sa", "ew58_v1/ew58-d", "ew58_v1/ew58-sb"}}}) {
|
|
CAPTURE(name);
|
|
scenario scenario{"controls/ep brake/" + name, motion() + brakes() + handles()};
|
|
for (auto const *car : cars) {
|
|
scenario.add(car, scenario.train().size() == 0 ? 1 : 0);
|
|
}
|
|
scenario.start_up();
|
|
scenario.get_going();
|
|
auto &cab{scenario.cab()};
|
|
scenario.act("ep on");
|
|
cab.SwitchEPBrake(1);
|
|
scenario.wait(5.0);
|
|
auto const &last{scenario.vehicle(scenario.train().size() - 1)};
|
|
auto const positions{std::vector{bh_EPB, bh_EPN, bh_EPR, bh_EPB, bh_RP}};
|
|
for (std::size_t i = 0; i < positions.size(); ++i) {
|
|
CAPTURE(positions[i]);
|
|
scenario.act(std::format("handle {}", positions[i]));
|
|
cab.BrakeLevelSet(cab.Handle->GetPos(positions[i]));
|
|
scenario.wait(15.0);
|
|
if (i == 2) {
|
|
CHECK(released(scenario));
|
|
}
|
|
if (i == 3) {
|
|
// the ep brake works the cylinders of every car by wire, the pipe stays charged (by the first
|
|
// application the EW58's pipe is still charging after the start-up)
|
|
CHECK(cab.PipePress > 4.8);
|
|
CHECK(cab.BrakePress > 1.0);
|
|
CHECK(last.BrakePress > 1.0);
|
|
}
|
|
}
|
|
// the EW58 releases at the running position, the ED72 holds the ep application there, and with the ep
|
|
// brake switched off as well: only the ep release position lets it off
|
|
auto const ed72{name == "ed72"};
|
|
CHECK(released(scenario) != ed72);
|
|
scenario.act("ep off");
|
|
cab.SwitchEPBrake(0);
|
|
scenario.wait(30.0);
|
|
CHECK(released(scenario) != ed72);
|
|
scenario.check();
|
|
}
|
|
}
|
|
|
|
TEST_CASE("controls: the electrodynamic brake of locomotives having one") {
|
|
for (auto const *path : {"ep09_v2/104e", "e186_v2/eu47", "e6act_v2/e6acta", "et22_v3/201e-rn"}) {
|
|
CAPTURE(path);
|
|
scenario scenario{std::string{"controls/dynamic brake/"} + path, motion() + traction() + brakes()};
|
|
scenario.add(path, 1).add("bdhpumn_v3/bdhpumn").add("bdhpumn_v3/bdhpumn");
|
|
scenario.start_up();
|
|
scenario.get_going();
|
|
notch_up(scenario, 60, 1.0);
|
|
scenario.wait(30.0);
|
|
notch_down(scenario);
|
|
auto &cab{scenario.cab()};
|
|
CHECK(cab.Vel > 40.0);
|
|
auto const speed{cab.Vel};
|
|
scenario.act("dynamic brake on");
|
|
cab.DynamicBrakeSwitch(true);
|
|
scenario.wait(5.0);
|
|
scenario.act("brake");
|
|
cab.BrakeLevelSet(cab.Handle->GetPos(bh_FB));
|
|
scenario.wait(40.0);
|
|
CHECK(cab.Vel < speed);
|
|
scenario.act("dynamic brake off");
|
|
cab.DynamicBrakeSwitch(false);
|
|
brake_to_stop(scenario, 30.0);
|
|
CHECK(stopped(scenario));
|
|
scenario.check();
|
|
}
|
|
}
|
|
|
|
TEST_CASE("controls: train heating from the locomotive") {
|
|
scenario scenario{"controls/heating/eu07 with wagons", traction() + air()};
|
|
scenario.add("4e_v1/4e", 1).add("bdhpumn_v3/bdhpumn").add("bdhpumn_v3/bdhpumn");
|
|
scenario.start_up();
|
|
auto &cab{scenario.cab()};
|
|
scenario.act("heating on");
|
|
cab.HeatingSwitch(true);
|
|
scenario.wait(30.0);
|
|
CHECK(cab.Heating);
|
|
CHECK(scenario.vehicle(2).Heating);
|
|
scenario.act("heating off");
|
|
cab.HeatingSwitch(false);
|
|
scenario.wait(10.0);
|
|
CHECK_FALSE(cab.Heating);
|
|
CHECK_FALSE(scenario.vehicle(2).Heating);
|
|
scenario.check();
|
|
}
|
|
|
|
TEST_CASE("controls: a freight wagon loaded and braked") {
|
|
auto emptymass{0.0};
|
|
for (auto const *load : {"", "coal", "ruda", "unknown"}) {
|
|
CAPTURE(load);
|
|
scenario scenario{std::string{"controls/load/3wl "} + (*load ? load : "empty"), motion() + brakes()};
|
|
scenario.add("4e_v1/4e", 1).add("3wl_v2/3wl");
|
|
auto &wagon{scenario.vehicle(1)};
|
|
auto const known{std::string{load} == "coal" || std::string{load} == "ruda"};
|
|
CHECK(wagon.AssignLoad(load, 30.f) == (known || std::string{load}.empty()));
|
|
wagon.ComputeMass();
|
|
if (*load == '\0') {
|
|
emptymass = wagon.TotalMass;
|
|
}
|
|
else {
|
|
// 30 t of a load the wagon takes, none of one it doesn't
|
|
CHECK(wagon.TotalMass == doctest::Approx(emptymass + (known ? 30000.0 : 0.0)).epsilon(0.01));
|
|
}
|
|
scenario.start_up();
|
|
scenario.get_going();
|
|
notch_up(scenario, 20, 1.0);
|
|
scenario.wait(20.0);
|
|
notch_down(scenario);
|
|
CHECK(scenario.cab().Vel > 20.0);
|
|
brake_to_stop(scenario);
|
|
CHECK(stopped(scenario));
|
|
scenario.check();
|
|
}
|
|
}
|
|
|
|
TEST_CASE("controls: a locomotive running into a standing wagon") {
|
|
for (auto const speed : {5.0, 15.0, 40.0}) {
|
|
CAPTURE(speed);
|
|
scenario scenario{std::format("controls/collision/{} kmh", static_cast<int>(speed)), motion()};
|
|
// the wagon stands in front, the locomotive comes from behind it
|
|
scenario.add("bdhpumn_v3/bdhpumn").add("4e_v1/4e", 0, coupling::faux, speed, 30.0);
|
|
auto const &wagon{scenario.vehicle(0)};
|
|
scenario.wait(30.0);
|
|
// slow, the locomotive pushes the wagon off, the buffers only touching, and both roll to a stop; fast,
|
|
// the wagon derails where it stands
|
|
auto const derailed{speed > 30.0};
|
|
CHECK(TestFlag(wagon.DamageFlag, dtrain_out) == derailed);
|
|
CHECK((std::abs(wagon.DistCounter) > 0.0001) != derailed);
|
|
CHECK(stopped(scenario));
|
|
scenario.check();
|
|
}
|
|
}
|
|
|
|
TEST_CASE("controls: a train run with five physics steps in a frame") {
|
|
scenario scenario{"controls/full physics/eu07 with wagons", motion() + traction() + brakes(),
|
|
test::track{0.15, 0.0, test::catenary_3kv, 0, 5}};
|
|
scenario.add("4e_v1/4e", 1).add("bdhpumn_v3/bdhpumn").add("3wl_v2/3wl");
|
|
scenario.start_up();
|
|
scenario.get_going();
|
|
notch_up(scenario, 25, 1.0);
|
|
scenario.wait(30.0);
|
|
notch_down(scenario);
|
|
// no CHECKs of the run: with five steps a frame the motors take no current and the train rolls at a few
|
|
// km/h, which is a finding (COVERAGE.md), not the behaviour to keep
|
|
brake_to_stop(scenario);
|
|
scenario.check();
|
|
}
|
|
|
|
TEST_CASE("controls: the automatic electrodynamic brake of the EP09, in service braking held to a stop") {
|
|
// held for longer, a service application of the train brake makes the EP09 brake electrodynamically: the
|
|
// pressure its distributor asks for (GetEDBCP) brings the motors in as generators, the air of its own
|
|
// cylinders is let out, and given back as the train slows down and the motors' brake force fades
|
|
struct application {
|
|
char const *name;
|
|
double steps; // past the first braking position of the handle
|
|
};
|
|
for (auto const &[name, steps] : {application{"light", 0.0}, application{"medium", 1.0}, application{"high", 2.0}, application{"full service", 3.0}}) {
|
|
CAPTURE(name);
|
|
scenario scenario{std::string{"controls/dynamic brake held/ep09 "} + name, motion() + traction() + brakes()};
|
|
scenario.add("ep09_v2/104e", 1, test::consist::full_coupling, 120.0);
|
|
scenario.add("bdhpumn_v3/bdhpumn", 0, test::consist::full_coupling, 120.0);
|
|
scenario.add("bdhpumn_v3/bdhpumn", 0, test::consist::full_coupling, 120.0);
|
|
REQUIRE(scenario.start_up());
|
|
auto &ep09{scenario.cab()};
|
|
REQUIRE(ep09.Power110vIsAvailable);
|
|
scenario.act(std::string{"brake "} + name);
|
|
ep09.BrakeLevelSet(ep09.Handle->GetPos(bh_MB) + steps);
|
|
scenario.wait(10.0);
|
|
CHECK(ep09.DynamicBrakeFlag);
|
|
CHECK(ep09.Im < -100.0);
|
|
CHECK(ep09.BrakePress < 0.25 * ep09.Hamulec->GetEDBCP());
|
|
scenario.wait(110.0);
|
|
// stopped, held by the air alone. the flag of the electrodynamic brake stays on while the cylinders hold
|
|
// less than 2.1 bar, though the motors give nothing; a stronger application drops it before the stop
|
|
CHECK(ep09.Vel == doctest::Approx(0.0));
|
|
CHECK(ep09.Im == doctest::Approx(0.0));
|
|
CHECK(ep09.DynamicBrakeFlag == (ep09.BrakePress <= 2.1));
|
|
CHECK(ep09.BrakePress == doctest::Approx(ep09.Hamulec->GetEDBCP()).epsilon(0.05));
|
|
scenario.act("release");
|
|
ep09.BrakeLevelSet(ep09.Handle->GetPos(bh_RP));
|
|
scenario.wait(30.0);
|
|
scenario.check();
|
|
}
|
|
}
|
|
|
|
TEST_CASE("controls: the automatic electrodynamic brake under ep and service braking, from speed") {
|
|
// ep, light and full service braking of trains running at speed, whose locomotive or unit has the automatic
|
|
// electrodynamic brake
|
|
struct train {
|
|
char const *name;
|
|
std::vector<char const *> cars; // the first one driven
|
|
double speed; // km/h at the start
|
|
bool epelectric; // whether the ep brake alone brings the motors in
|
|
};
|
|
// the EW58's motors brake by the stages of the ep brake (TUHEX), with the 110 V of the converter in its
|
|
// middle car; the E6ACT's by its distributor, from a deeper service application
|
|
for (auto const &[name, cars, speed, epelectric] : {
|
|
train{"e6act with wagons", {"e6act_v2/e6acta", "bdhpumn_v3/bdhpumn", "bdhpumn_v3/bdhpumn"}, 100.0, false},
|
|
train{"ew58", {"ew58_v1/ew58-sa", "ew58_v1/ew58-d", "ew58_v1/ew58-sb"}, 90.0, true},
|
|
}) {
|
|
CAPTURE(name);
|
|
scenario scenario{std::string{"controls/dynamic brake from speed/"} + name, motion() + traction() + brakes()};
|
|
for (auto const *car : cars) {
|
|
scenario.add(car, car == cars.front() ? 1 : 0, test::consist::full_coupling, speed);
|
|
}
|
|
scenario.start_up();
|
|
auto &cab{scenario.cab()};
|
|
auto const brake{[&scenario, &cab](char const *Event, double Position, double Seconds) {
|
|
scenario.act(Event);
|
|
cab.BrakeLevelSet(Position);
|
|
scenario.wait(Seconds);
|
|
}};
|
|
brake("ep brake", cab.Handle->GetPos(bh_EPB), 15.0);
|
|
CHECK(cab.DynamicBrakeFlag == epelectric);
|
|
CHECK((cab.Im < -100.0) == epelectric);
|
|
brake("ep hold", cab.Handle->GetPos(bh_EPN), 5.0);
|
|
brake("first step", cab.Handle->GetPos(bh_MB), 20.0);
|
|
brake("deeper", cab.Handle->GetPos(bh_MB) + 1.0, 15.0);
|
|
// the pipe alone doesn't bring the EW58's motors in
|
|
CHECK((cab.Im < -50.0) != epelectric);
|
|
brake("release", cab.Handle->GetPos(bh_RP), 20.0);
|
|
brake("full service", cab.Handle->GetPos(bh_FB), 40.0);
|
|
brake("release", cab.Handle->GetPos(bh_RP), 20.0);
|
|
scenario.check();
|
|
}
|
|
}
|
|
|
|
TEST_CASE("controls: a draisine pumped along by hand") {
|
|
// the pumping force, half a stroke forwards and half backwards, is below the resistance of a draisine
|
|
// standing, which stays where it is; a rolling one is kept going
|
|
for (auto const speed : {0.0, 10.0}) {
|
|
CAPTURE(speed);
|
|
scenario scenario{std::format("controls/draisine/{}", static_cast<int>(speed)), motion() + traction()};
|
|
scenario.add("dr2_v1/dr2", 1, test::consist::full_coupling, speed);
|
|
auto &cab{scenario.cab()};
|
|
cab.CabActivisation();
|
|
scenario.act("hand brake off");
|
|
test::release_brakes(cab);
|
|
for (int stroke = 0; stroke < 40; ++stroke) {
|
|
scenario.act("stroke");
|
|
cab.IncMainCtrl(1);
|
|
scenario.wait(0.5);
|
|
cab.DecMainCtrl(1);
|
|
scenario.wait(0.5);
|
|
}
|
|
CHECK((speed > 0.0) == (cab.Vel > 1.0));
|
|
scenario.act("coast");
|
|
scenario.wait(20.0);
|
|
scenario.check();
|
|
}
|
|
}
|