mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-17 01:39:20 +02:00
fix(ai): release horn when human driver takes over from AI (#210)
* fix(ai): release what the AI was holding when the player takes over * fix(ai): keep the light selector lookup within its table, Sonar findings of the handover - the light selector size comes from the vehicle definition (LightsList Size) and isn't limited, while the table holds 18 positions; a bigger selector read past the table, and so did a table index other than the front or rear one. Only the positions the table holds are looked at now, and the selector stays where it is for a bad table index (cpp:S2259), - the lambdas of the handover capture what they use explicitly (cpp:S3608), - the chained assignments resetting the headlight state on the AI taking over are split (cpp:S1121).
This commit is contained in:
@@ -52,6 +52,9 @@ TEST_CASE("brakes: service braking stops a heavier wagon too") {
|
||||
and a test on a half-defined vehicle tests the definition rather than the simulation.
|
||||
- The random numbers of the simulation are reseeded before every test case, so a test gives the
|
||||
same result alone or among the others.
|
||||
- Logic which lives in classes too big to set up in a test (the AI driver, the cab) goes into free
|
||||
functions on the vehicle state the class calls, and the test calls them the same way: see
|
||||
`vehicle/handover.h`, which `TController::TakeControl()` uses, and `test_handover.cpp`.
|
||||
- `vehicle` creates the vehicle the way `TDynamicObject::Init()` does and steps it the way the
|
||||
simulation loop does, on a straight track of the given friction and gradient. Coupled vehicles
|
||||
aren't supported yet.
|
||||
|
||||
34
tests/unit/data/cab_vehicle.json
Normal file
34
tests/unit/data/cab_vehicle.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"_comment": "The wagon with the cab devices the driver uses: sandbox, light preset selector and modern headlight dimmer, both tables made after the Vectron's. No traction: enough for the logic of the cab controls.",
|
||||
"Param": { "Category": "train", "M": 42200, "Vmax": 120, "SandCap": 500 },
|
||||
"Dimensions": { "L": 26.8, "H": 4.7, "W": 2.88, "Cx": 0.15 },
|
||||
"Wheels": { "D": 0.96, "Tw": 1.435, "Axle": "2'2'", "Ad": 2.5, "Bd": 19.5, "Rmin": 80, "BearingType": "Roll" },
|
||||
"Brake": {
|
||||
"BrakeValve": "KE", "NBpA": 6, "MBF": 15, "MaxBP": 3.8, "TareMaxBP": 2.3,
|
||||
"BCN": 12, "BCR": 0.127, "BCD": 0.04, "BCM": 1.5, "BCS": 1.2, "BSA": 0, "BM": "Disk1", "BVV": 125
|
||||
},
|
||||
"BuffCoupl": { "CType": "Screw", "kB": 9200, "DmaxB": 0.11, "FmaxB": 320, "kC": 4500, "DmaxC": 0.1, "FmaxC": 850, "beta": 0.58, "AllowedFlag": 115 },
|
||||
"Cntrl": { "BrakeSystem": "Pneumatic", "LocalBrake": "ManualBrake", "BrakeDelays": "P", "MaxBPMass": 63 },
|
||||
"Switches": { "ModernDimmer": "Yes" },
|
||||
"_lights": "light bits: 1 headlight left, 2 red marker left, 4 headlight upper, 16 headlight right, 32 red marker right",
|
||||
"LightsList": {
|
||||
"Size": 4, "Wrap": "No", "Default": 1,
|
||||
"rows": [
|
||||
[0, 0],
|
||||
[21, 34],
|
||||
[34, 0],
|
||||
[17, 0]
|
||||
]
|
||||
},
|
||||
"_dimmer": "rows: high beam, dimmed, off",
|
||||
"DimmerList": {
|
||||
"Size": 5, "Cycle": "no", "DefaultPos": 3,
|
||||
"rows": [
|
||||
["yes", "no", "no"],
|
||||
["yes", "yes", "no"],
|
||||
["no", "no", "yes"],
|
||||
["no", "no", "no"],
|
||||
["no", "yes", "no"]
|
||||
]
|
||||
}
|
||||
}
|
||||
169
tests/unit/test_handover.cpp
Normal file
169
tests/unit/test_handover.cpp
Normal file
@@ -0,0 +1,169 @@
|
||||
#include "stdafx.h"
|
||||
#include "doctest.h"
|
||||
#include "vehicle.h"
|
||||
|
||||
#include "vehicle/handover.h"
|
||||
|
||||
using test::definition;
|
||||
using test::vehicle;
|
||||
|
||||
// what the human driver gets when taking a vehicle over from the AI (Q key). the AI operates some controls
|
||||
// directly and only the AI ever released them, so the player inherited e.g. a horn blowing forever.
|
||||
|
||||
namespace {
|
||||
|
||||
int const pc1{light::headlight_left | light::headlight_upper | light::headlight_right};
|
||||
int const allheadlights{pc1 | light::redmarker_left | light::redmarker_right};
|
||||
|
||||
vehicle cab_vehicle() {
|
||||
vehicle cab{definition::load("cab_vehicle")};
|
||||
cab->DirActive = 1; // the AI drives: reverser set
|
||||
return cab;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("handover: the horn the AI was sounding goes quiet (eu07/maszyna#511)") {
|
||||
auto cab{cab_vehicle()};
|
||||
SUBCASE("low tone") { cab->WarningSignal = 1; }
|
||||
SUBCASE("high tone") { cab->WarningSignal = 2; }
|
||||
SUBCASE("both tones") { cab->WarningSignal = 1 | 2; }
|
||||
REQUIRE(cab->WarningSignal != 0);
|
||||
|
||||
handover::release_ai_controls(cab.mover(), cab.mover());
|
||||
|
||||
CHECK(cab->WarningSignal == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("handover: sanding the AI started stops") {
|
||||
auto cab{cab_vehicle()};
|
||||
cab->Sandbox(true); // the way the AI sands, bypassing the sanding button
|
||||
REQUIRE(cab->SandDose);
|
||||
|
||||
handover::release_ai_controls(cab.mover(), cab.mover());
|
||||
|
||||
CHECK_FALSE(cab->SandDose);
|
||||
}
|
||||
|
||||
TEST_CASE("handover: sanding the player or the automatic system asked for goes on") {
|
||||
auto cab{cab_vehicle()};
|
||||
SUBCASE("sanding button held") { cab->SandboxManual(true); }
|
||||
SUBCASE("automatic sanding") {
|
||||
cab->SandboxAutoAllow(true);
|
||||
cab->SandboxAuto(true);
|
||||
}
|
||||
REQUIRE(cab->SandDose);
|
||||
|
||||
handover::release_ai_controls(cab.mover(), cab.mover());
|
||||
|
||||
CHECK(cab->SandDose);
|
||||
}
|
||||
|
||||
TEST_CASE("handover: the departure signal the AI was giving stops") {
|
||||
auto cab{cab_vehicle()};
|
||||
cab->signal_departure(true);
|
||||
REQUIRE(cab->DepartureSignal);
|
||||
|
||||
handover::release_ai_controls(cab.mover(), cab.mover());
|
||||
|
||||
CHECK_FALSE(cab->DepartureSignal);
|
||||
}
|
||||
|
||||
TEST_CASE("handover: the brake releaser the AI was holding lets go") {
|
||||
auto cab{cab_vehicle()};
|
||||
cab->BrakeReleaser(1);
|
||||
REQUIRE(cab->Hamulec->Releaser());
|
||||
|
||||
handover::release_ai_controls(cab.mover(), cab.mover());
|
||||
|
||||
CHECK_FALSE(cab->Hamulec->Releaser());
|
||||
}
|
||||
|
||||
TEST_CASE("handover: nothing the AI didn't latch changes") {
|
||||
auto cab{cab_vehicle()};
|
||||
|
||||
handover::release_ai_controls(cab.mover(), cab.mover());
|
||||
|
||||
CHECK(cab->WarningSignal == 0);
|
||||
CHECK_FALSE(cab->SandDose);
|
||||
CHECK_FALSE(cab->DepartureSignal);
|
||||
CHECK_FALSE(cab->Hamulec->Releaser());
|
||||
}
|
||||
|
||||
TEST_CASE("handover: the light selector moves to the position giving the head lights the AI has set") {
|
||||
auto cab{cab_vehicle()};
|
||||
REQUIRE(cab->LightsPos == 1); // untouched selector: lights off
|
||||
|
||||
SUBCASE("Pc1") {
|
||||
CHECK(handover::light_selector_position(cab.mover(), end::front, pc1, allheadlights) == 2);
|
||||
}
|
||||
SUBCASE("lights off") {
|
||||
CHECK(handover::light_selector_position(cab.mover(), end::front, 0, allheadlights) == 1);
|
||||
}
|
||||
SUBCASE("vehicle without the upper headlight, which the AI's Pc1 can't light") {
|
||||
auto const inventory{allheadlights & ~light::headlight_upper};
|
||||
CHECK(handover::light_selector_position(cab.mover(), end::front, pc1 & inventory, inventory) == 2);
|
||||
}
|
||||
SUBCASE("no position matches: the selector stays") {
|
||||
CHECK(handover::light_selector_position(cab.mover(), end::front, light::headlight_left, allheadlights) == 1);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("handover: the light selector stays within its table") {
|
||||
auto cab{cab_vehicle()};
|
||||
|
||||
SUBCASE("selector bigger than the table, as a vehicle definition can declare") {
|
||||
// only the positions the table holds are looked at, the rest can't match
|
||||
for (auto &table : cab->Lights) {
|
||||
std::fill(std::begin(table) + cab->LightsPosNo, std::end(table), 0);
|
||||
}
|
||||
cab->LightsPosNo = 100;
|
||||
CHECK(handover::light_selector_position(cab.mover(), end::front, pc1, allheadlights) == 2);
|
||||
CHECK(handover::light_selector_position(cab.mover(), end::front, light::headlight_left, allheadlights) == 1);
|
||||
}
|
||||
SUBCASE("table other than the front or rear one: the selector stays") {
|
||||
CHECK(handover::light_selector_position(cab.mover(), -1, pc1, allheadlights) == 1);
|
||||
CHECK(handover::light_selector_position(cab.mover(), 2, pc1, allheadlights) == 1);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("handover: the light selector already matching the lights stays") {
|
||||
auto cab{cab_vehicle()};
|
||||
cab->LightsPos = 2; // Pc1
|
||||
|
||||
CHECK(handover::light_selector_position(cab.mover(), end::front, pc1, allheadlights) == 2);
|
||||
}
|
||||
|
||||
TEST_CASE("handover: the headlight dimmer leaves the off position when the head lights are lit") {
|
||||
auto cab{cab_vehicle()};
|
||||
REQUIRE(cab->enableModernDimmer);
|
||||
REQUIRE(cab->dimPositions.size() == 5);
|
||||
cab->modernDimmerPosition = 2; // off, left there by the player before the AI took over
|
||||
|
||||
SUBCASE("to the default position") {
|
||||
CHECK(handover::dimmer_position(cab.mover(), true) == 3);
|
||||
}
|
||||
SUBCASE("to the first regular position, when the default one is off as well") {
|
||||
cab->modernDimmerDefaultPosition = 2;
|
||||
CHECK(handover::dimmer_position(cab.mover(), true) == 3);
|
||||
}
|
||||
SUBCASE("unless the head lights are off anyway") {
|
||||
CHECK(handover::dimmer_position(cab.mover(), false) == 2);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("handover: the headlight dimmer in any other position stays") {
|
||||
auto cab{cab_vehicle()};
|
||||
for (auto const position : {0, 1, 3, 4}) {
|
||||
CAPTURE(position);
|
||||
cab->modernDimmerPosition = position;
|
||||
CHECK(handover::dimmer_position(cab.mover(), true) == position);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("handover: a vehicle without the modern dimmer keeps its dimmer state") {
|
||||
vehicle wagon{definition::load("wagon")};
|
||||
REQUIRE_FALSE(wagon->enableModernDimmer);
|
||||
|
||||
CHECK(handover::dimmer_position(wagon.mover(), true) == wagon->modernDimmerPosition);
|
||||
}
|
||||
Reference in New Issue
Block a user