17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-09 12:32:24 +01:00

AI braking: hold the speed on a descent

Two changes that only work together: the first on its own lets a heavy
freight train brake less on a descent than it needs (in testing an
ET42 with 30 loaded wagons overran the stop signal by 16-48 m at 20 per
mille), the second makes the hold on a descent what it should be.

The comparison that decides whether the AI may skip its reaction time
between two brake steps stays on the brake-only demand, as before: it
is about how much brake is asked for, and a heavy train in G approaching
a stop on a descent needs its brake built up at once. Compared in the
resultant frame, that train built up its brake one step per reaction
time and overran the signal (16 copies of the ET42 train, x20:
22 of 32 overruns with the comparison moved, 0 of 48 with it kept).

1. Compare the measured deceleration in the same frame.

Going downhill, adjust_desired_speed_for_current_speed() subtracts the
gravity component from AccDesired, so that IncBrake() sizes the handle
position for what the brake alone has to deliver. control_braking_force()
then compares that brake-only value with AbsAccS, which is the resultant
acceleration and already contains the gravity. On a 20 per mille descent
the two differ by about 0.2 m/s2, so the AI asked for more braking than
the target needed and then released, and repeated that.

Keep the subtracted part in fAccDesiredGravity and add it back wherever
AccDesired is compared with AbsAccS. It is zero everywhere else (flat,
uphill, braking test), so the behaviour there does not change.

2. Hold the speed with one continuous law.

On a descent the AI looks ahead to decide whether it will exceed the
target speed. The look-ahead was (1 - a0) * 30 / 3.6 s, a few seconds,
while a pneumatic brake in G takes tens of seconds to apply or to
release; the AI therefore acted on an estimate the train had already
left behind. The demand then jumped between the braking threshold above
the target and a lerp towards full acceleration below it, and with the
brake that slow this turned into long alternating applications and
releases.

- look ahead for half the brake application time while the train is
  speeding up and half the release time while it is slowing down
  (brake_response_horizon), never less than before;
- use one law on both sides of the target, proportional to the
  predicted difference, and hold a band just below the limit that widens
  with the gradient (pneumatic up to 3 km/h, EP/electric up to 1.5 km/h);
- once below a lower limit ahead, hold that limit instead of speeding up
  towards the line speed and braking again.
This commit is contained in:
pwarszawski
2026-09-28 15:45:10 +02:00
parent 36d4f16c01
commit 92bd7ef827
2 changed files with 77 additions and 25 deletions

View File

@@ -7483,6 +7483,25 @@ TController::adjust_desired_speed_for_limits() {
}
}
// how long the present acceleration of a consist lasts whatever is done with the handle now: half the time
// its pneumatic brake takes to apply (while speeding up) or to release (while slowing down), which in G
// is tens of seconds; never shorter than the look-ahead the AI has always used
static double
brake_response_horizon( TMoverParameters const *Vehicle, TBrakeSystem const Brakesystem, double const Acceleration, double const Brakea0 ) {
auto horizon { ( 1.0 - Brakea0 ) * 30.0 / 3.6 };
if( Brakesystem == TBrakeSystem::Pneumatic ) {
auto const isrelease { Acceleration < 0.0 };
horizon = std::max(
horizon,
0.5 * (
Vehicle->BrakeDelayFlag > bdelay_G ?
Vehicle->BrakeDelay[ isrelease ? 0 : 1 ] :
Vehicle->BrakeDelay[ isrelease ? 2 : 3 ] ) );
}
return horizon;
}
void
TController::adjust_desired_speed_for_target_speed( double const Range ) {
// ustalanie zadanego przyspieszenia
@@ -7496,6 +7515,7 @@ TController::adjust_desired_speed_for_target_speed( double const Range ) {
// gdy jedzie wolniej niż potrzeba, albo nie ma przeszkód na drodze
// normalna jazda
AccDesired = VelDesired != 0.0 ? AccPreferred : -0.01;
fAccDesiredGravity = 0.0;
auto const vel { DirectionalVel() };
@@ -7654,35 +7674,57 @@ TController::adjust_desired_speed_for_current_speed() {
if( fAccGravity > 0.025 ) {
// going sharply downhill we may need to start braking sooner than usual
// try to estimate increase of current velocity before engaged brakes start working
auto const speedestimate = vel + ( 1.0 - fBrake_a0[ 0 ] ) * 30.0 * AbsAccS;
if( speedestimate > VelDesired ) {
// jesli jedzie za szybko do AKTUALNEGO
if( VelDesired == 0.0 ) {
// jesli stoj, to hamuj, ale i tak juz za pozno :)
AccDesired = std::min( AccDesired, -0.85 ); // hamuj solidnie
}
else {
// if it looks like we'll exceed maximum speed start thinking about slight slowing down
AccDesired = std::min( AccDesired, -0.05 + fAccThreshold );
// HACK: for cargo trains with high braking threshold ensure we cross that threshold
if( true == IsCargoTrain
&& fBrake_a0[0] > 0.2 ) {
AccDesired -= std::clamp( fBrake_a0[ 0 ] - 0.2, 0.0, 0.15 );
}
}
// look ahead for as long as the brake takes to respond, since that is how long the
// present acceleration will last whatever is done with the handle now; a pneumatic
// brake applies and releases on its own time scale, which in G is tens of seconds
auto const horizon { brake_response_horizon( mvOccupied, BrakeSystem, AbsAccS, fBrake_a0[ 0 ] ) };
auto const speedestimate = vel + 3.6 * horizon * AbsAccS;
// already below a lower limit ahead, the train has no business speeding up towards it
auto holdlimit { VelDesired };
if( ( VelNext > 0.0 )
&& ( VelNext < holdlimit )
&& ( vel <= VelNext + fVelPlus )
&& ( ActualProximityDist < std::max( 2.0 * fBrakeDist, 500.0 ) ) ) {
holdlimit = VelNext;
}
// hold a band just below the limit; the steeper the descent the harder the train pulls
// away once the brake eases, so the band widens with the gradient (about 3 km/h at 20 per mille)
// an electro-pneumatic or electric brake responds at once and needs far less room
auto const holdspeed { holdlimit - (
BrakeSystem == TBrakeSystem::Pneumatic ?
std::clamp( fAccGravity * 15.0, 1.0, 3.0 ) :
std::clamp( fAccGravity * 7.5, 1.0, 1.5 ) ) };
if( ( speedestimate > VelDesired )
&& ( VelDesired == 0.0 ) ) {
// jesli stoj, to hamuj, ale i tak juz za pozno :)
AccDesired = std::min( AccDesired, -0.85 ); // hamuj solidnie
}
else {
// stop accelerating when close enough to target speed
// one continuous law on both sides of the target, proportional to the predicted
// difference: a demand that jumps to the braking threshold above the target and to
// full acceleration below it alternates, with a brake that responds in seconds,
// between a hard application and a full release
AccDesired = std::min(
AccDesired, // but don't override decceleration for VelNext
std::lerp( // ease off as you close to the target velocity
EU07_AI_NOACCELERATION, AccPreferred,
std::clamp( VelDesired - speedestimate, 0.0, fVelMinus ) / fVelMinus ) );
std::clamp(
EU07_AI_NOACCELERATION - 0.04 * ( speedestimate - holdspeed ),
-0.05 + fAccThreshold,
std::max( EU07_AI_NOACCELERATION, AccPreferred ) ) );
// HACK: for cargo trains with high braking threshold ensure we cross that threshold
if( true == IsCargoTrain
&& fBrake_a0[0] > 0.2
&& speedestimate > holdlimit ) {
AccDesired -= std::clamp( fBrake_a0[ 0 ] - 0.2, 0.0, 0.15 );
}
}
// final tweaks
if( vel > EU07_AI_NOMOVEMENT ) {
// going downhill also take into account impact of gravity
AccDesired -= fAccGravity;
// from here on AccDesired is what the brake alone has to deliver, which is what
// IncBrake() sizes the handle position for; AbsAccS already contains the gravity,
// so the comparisons with it have to add this part back
fAccDesiredGravity = fAccGravity;
// HACK: if the max allowed speed was exceeded something went wrong; brake harder
AccDesired -= 0.15 * std::clamp( vel - VelDesired, 0.0, 5.0 );
}
@@ -7773,6 +7815,7 @@ TController::adjust_desired_speed_for_braking_test() {
}
case 1: {
AccDesired = EU07_AI_BRAKINGTESTACCELERATION;
fAccDesiredGravity = 0.0;
VelDesired = DBT_VelocityBrake;
if( ElapsedTime - DBT_BrakingTime > 1 ) {
ForcePNBrake = true;
@@ -7783,6 +7826,7 @@ TController::adjust_desired_speed_for_braking_test() {
}
case 2: {
AccDesired = EU07_AI_BRAKINGTESTACCELERATION;
fAccDesiredGravity = 0.0;
VelDesired = DBT_VelocityBrake;
if( ElapsedTime - DBT_BrakingTime > 2 ) {
DBT_BrakingTime = ElapsedTime;
@@ -7794,6 +7838,7 @@ TController::adjust_desired_speed_for_braking_test() {
}
case 3: {
AccDesired = safe_clamp( -AbsAccS, fAccThreshold * 1.01, fAccThreshold * 1.21 );
fAccDesiredGravity = 0.0;
VelDesired = DBT_VelocityBrake;
if( vel <= DBT_VelocityRelease ) {
DynamicBrakeTest = 4;
@@ -7980,6 +8025,10 @@ void TController::control_braking_force() {
}
}
// AbsAccS is the resultant acceleration, gravity included, so it is compared with the
// resultant the AI asks for, not with the brake-only demand AccDesired holds on a descent
auto const accdesiredresultant { AccDesired + fAccDesiredGravity };
if( is_emu() && !ForcePNBrake ) {
// właściwie, to warunek powinien być na działający EP
// Ra: to dobrze hamuje EP w EZT
@@ -7991,13 +8040,13 @@ void TController::control_braking_force() {
auto const accmargin = AccMax > 1.1 * AccDesired && fAccGravity < 0.025 ?
0.05 : 0.0;
if( AccDesired < accthreshold // jeśli hamować - u góry ustawia się hamowanie na fAccThreshold
&& ( AbsAccS > AccDesired + accmargin
&& ( AbsAccS > accdesiredresultant + accmargin
|| BrakeCtrlPosition < 0 ) ) {
// hamować bardziej, gdy aktualne opóźnienie hamowania mniejsze niż (AccDesired)
cue_action( driver_hint::brakingforceincrease );
}
else if( OrderCurrentGet() != Disconnect ) { // przy odłączaniu nie zwalniamy tu hamulca
if( AbsAccS < AccDesired - 0.05 ) {
if( AbsAccS < accdesiredresultant - 0.05 ) {
// jeśli opóźnienie większe od wymaganego (z histerezą) luzowanie, gdy za dużo
// TBD: check if the condition isn't redundant with the DecBrake() code
if( BrakeCtrlPosition >= 0 ) {
@@ -8013,10 +8062,12 @@ void TController::control_braking_force() {
} // type & dt_ezt
else {
// a stara wersja w miarę dobrze działa na składy wagonowe
if( ( AccDesired < fAccGravity - 0.1 && AbsAccS > AccDesired + fBrake_a1[0] ) // regular braking
if( ( accdesiredresultant < fAccGravity - 0.1 && AbsAccS > accdesiredresultant + fBrake_a1[0] ) // regular braking
|| ( fAccGravity < -0.05 && velocity < -0.1 ) ) { // also brake if uphill and slipping back
// u góry ustawia się hamowanie na fAccThreshold
if( fBrakeTime < 0.0
// the size of the brake demand decides whether to skip the reaction time, so the brake-only part
// (on a descent a heavy train needs its brake built up at once, the gravity does not help it slow)
|| AccDesired < fAccGravity - 0.5
|| BrakeCtrlPosition <= 0 ) {
// jeśli upłynął czas reakcji hamulca, chyba że nagłe albo luzował
@@ -8033,8 +8084,8 @@ void TController::control_braking_force() {
* 0.5 ); // Ra: tymczasowo, bo przeżyna S1
}
}
if ( AccDesired < fAccGravity - 0.05
&& AccDesired - fBrake_a1[0] * 0.51 - AbsAccS > 0.05 ) {
if ( accdesiredresultant < fAccGravity - 0.05
&& accdesiredresultant - fBrake_a1[0] * 0.51 - AbsAccS > 0.05 ) {
// jak hamuje, to nie tykaj kranu za często
// yB: luzuje hamulec dopiero przy różnicy opóźnień rzędu 0.2
if( OrderCurrentGet() != Disconnect ) { // przy odłączaniu nie zwalniamy tu hamulca

View File

@@ -387,6 +387,7 @@ private:
double AccDesired = 0.0; // przyspieszenie, jakie ma utrzymywać (<0:nie przyspieszaj,<-0.1:hamuj)
double VelDesired = 0.0; // predkość, z jaką ma jechać, wynikająca z analizy tableki; <=VelSignal
double fAccDesiredAv = 0.0; // uśrednione przyspieszenie z kolejnych przebłysków świadomości, żeby ograniczyć migotanie
double fAccDesiredGravity = 0.0; // część AccDesired odjęta na spadku jako kompensacja pochylenia; AbsAccS zawiera ją już sam
double VelforDriver = -1.0; // prędkość, używana przy zmianie kierunku (ograniczenie przy nieznajmości szlaku?)
double VelSignal = 0.0; // ograniczenie prędkości z kompilacji znaków i sygnałów // normalnie na początku ma stać, no chyba że jedzie
double VelLimit = -1.0; // predkość zadawana przez event jednokierunkowego ograniczenia prędkości // -1: brak ograniczenia prędkości