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

AI braking: plan approaches from the consist brake, stop 8-15 m short

The new braking curve below plans the stop at the signal itself unless
the stopping point moves with it (in testing, trains stopped 0-3 m past
the signal), so both go in one change.

1. The braking curve.

The braking curve towards a lower speed or a stop assumed a constant
deceleration from the present speed over the whole distance. A pneumatic
brake does not work like that:

- it acts on the state the train is in once it has applied; while the
  train is still speeding up (downhill), plan from the speed it will have
  reached by then, over the distance left by then;
- cast iron blocks grip harder as the train slows, so a constant
  deceleration plan asks for too much at the start and then releases;
  follow the shape of the consist brake table (fBrake_a0/a1) over speed
  instead (shaped_braking_demand), over the distance left once the brake
  has had its fill time;
- no power while the train is still above the speed it is braking for.

IncBrake() only adds braking when the brake table says the present
position is not enough. When the table overestimates the consist (see
the previous rapid step fix, or a descent), the train keeps slowing too
little with the handle held. After the full fill time with the handle
unchanged, believe the train rather than the table (fBrakeHeldTime).

When the AI asks for no braking and the train is still slowing down more
than that, release. With the gentler plan above the demand eases off
early, and without this a heavy freight train in G came to a stand a few
hundred metres before a W4 with its brake still on (seen in testing).

2. The stopping window.

In train running the final approach to a stop signal began at
fMaxProximityDist (15-40 m, cargo 25-50 m), aimed at half of
fMinProximityDist (10-15 m, cargo 20-25 m), and set VelDesired = 0,
which asks for hard braking whatever the distance left. Where a train
came to a stand depended on how it entered that phase: on the test
sceneries anywhere from 1 to 45 m short of the signal on the flat and
25-42 m uphill for heavy freight.

For train running only (a W4 stop point, shunting and coupling keep the
old margins and the old final approach):

- stop 8-15 m short of the signal whatever the length of the train, aim
  at the middle of that window (stopping_margin) both with the braking
  curve and in the final approach, and ask there only for what the
  distance left takes, following the consist brake table;
- start the final approach 50 m out (cargo 60 m) whether the train is
  moving or standing; it used to be 15-40 m while moving and 50 m once
  stopped, and with the gentler final approach above a train that came
  to a stand between the two released to creep closer and, uphill,
  rolled back and tried again;
- uphill, while the gradient alone would bring the train to a stand short
  of the window, keep it rolling at no more than the speed the gradient
  can take off by then, with power if needed;
- uphill, put the train brake on as the train comes to a stand, keep it
  on while it stands, and apply it for real if the train rolls back. This
  is done at the very end of pick_optimal_speed(): the adjustments made
  before it assume a train moving forward and can raise the demand back
  to "no braking" (with the rule placed earlier, EMUs rolled back up to
  90 m in testing). The EMU branch of
  control_braking_force() gets the "slipping back" condition the wagon
  branch has always had.


3. Known limitation.

Heavy trains of wagons with the ESt3AL2 distributor (413S, 601W and
alike) respond more slowly than the AI expects: with the present brake
model the distributor fills the AL2 relay chamber, whose capacity is set
to 2 in TNESt3::SetSize (Przekladniki[i]->CreateCap(i)), through
orifices sized for about 1 l, so these wagons need 34 s to reach 95 %
cylinder pressure in G against 21 s for a plain ESt3. On a 20 per mille
descent an ET22 with 20 loaded 413S wagons overran the stop signal by up
to 6.5 m in 6 of 24 runs at x20 with this change. With the relay chamber
at 1 l and the release orifices at about 80 % of their diameter, as the
author of the brake model suggested, none of them did (0 of 16).
This commit is contained in:
pwarszawski
2026-09-28 15:45:10 +02:00
parent 92bd7ef827
commit ce87aa0e09
2 changed files with 200 additions and 24 deletions

View File

@@ -3122,6 +3122,17 @@ bool TController::IncBrake()
}
}
}
else if( BrakeCtrlPosition > 0.0
&& AccDesired < 0.0 // only while braking is asked for, not when called for a train slipping back while starting
&& fBrakeHeldTime > (
mvOccupied->BrakeDelayFlag > bdelay_G ?
mvOccupied->BrakeDelay[ 1 ] :
mvOccupied->BrakeDelay[ 3 ] ) ) {
// the table says the present position is enough, but the brake has had its full fill
// time since the handle last moved and the train is still not slowing down as asked:
// believe the train rather than the table
OK = BrakeLevelAdd( BrakingLevelIncrease );
}
else
OK = false;
}
@@ -3781,6 +3792,7 @@ void TController::BrakeLevelSet(double b)
if (BrakeCtrlPosition == b)
return; // nie przeliczać, jak nie ma zmiany
BrakeCtrlPosition = std::clamp(b, (double)gbh_MIN, (double)gbh_MAX);
fBrakeHeldTime = 0.0;
}
bool TController::BrakeLevelAdd(double b)
@@ -5911,6 +5923,7 @@ TController::update_timers( double dt ) {
ElapsedTime += dt;
WaitingTime += dt;
fBrakeTime -= dt; // wpisana wartość jest zmniejszana do 0, gdy ujemna należy zmienić nastawę hamulca
fBrakeHeldTime += dt;
if( mvOccupied->fBrakeCtrlPos != mvOccupied->Handle->GetPos( bh_FS ) ) {
// brake charging timeout starts after charging ends
BrakeChargingCooldown += dt;
@@ -6659,12 +6672,27 @@ TController::determine_proximity_ranges() {
// na jaka odleglosc i z jaka predkoscia ma podjechac do przeszkody
// jeśli pociąg
if( is_train() ) {
fMinProximityDist = std::clamp( 5 + iVehicles, 10, 15 );
fMaxProximityDist = std::clamp( 10 + iVehicles, 15, 40 );
if( true == IsAtPassengerStop ) {
// a stop point keeps the margins its W4 parameters (head or middle of the consist,
// platform length) were set up against
fMinProximityDist = std::clamp( 5 + iVehicles, 10, 15 );
fMaxProximityDist = std::clamp( 10 + iVehicles, 15, 40 );
if( IsCargoTrain ) {
fMinProximityDist += 10.0;
}
}
else {
// a train stops 8-15 m short of the signal whatever its length; closer than the lower
// edge it stops at once, the braking aims at the middle of the window, 3.5 m further out
fMinProximityDist = 8.0;
// the final approach begins far enough out to slow from the coasting speed down to the window,
// and at the same distance whether the train is moving or standing, so it does not flip
// between creeping closer and holding still when it comes to a stand short of the window
fMaxProximityDist = 50.0;
}
if( IsCargoTrain ) {
// increase distances for cargo trains to take into account slower reaction to brakes
fMinProximityDist += 10.0;
// start the final approach earlier for cargo trains, their brakes react more slowly
fMaxProximityDist += 10.0;
/*
if( IsHeavyCargoTrain ) {
@@ -6683,7 +6711,7 @@ TController::determine_proximity_ranges() {
if( mvOccupied->Vel < 0.1 ) {
// jak stanie za daleko, to niech nie dociąga paru metrów
fMaxProximityDist = 50.0;
fMaxProximityDist = std::max( fMaxProximityDist, 50.0 );
}
if( iDrivigFlags & moveLate ) {
@@ -7314,6 +7342,26 @@ TController::pick_optimal_speed( double const Range ) {
is_car() ? -2.0 : -0.9,
is_car() ? 2.0 : 0.9 );
if( ( true == is_train() )
&& ( ( OrderCurrentGet() & Obey_train ) != 0 )
&& ( VelNext == 0.0 )
&& ( ActualProximityDist <= fMaxProximityDist )
&& ( fAccGravity < -0.025 ) ) {
// uphill, the gradient alone brings a slow train to a stand within metres, far sooner than a released
// brake applies; so the train brake goes on while the train is coming to a stand, stays on while it
// stands, and is applied for real if the train has started to roll back. done last, so that none of
// the adjustments above, made for a train moving forward, can undo it
auto const vel { DirectionalVel() };
if( std::abs( vel ) < EU07_AI_NOMOVEMENT ) {
VelDesired = 0.0;
AccDesired = std::min( AccDesired, EU07_AI_NOACCELERATION );
}
else if( ( vel < 0.0 )
|| ( ( vel < 5.0 ) && ( AccDesired < 0.0 ) ) ) {
AccDesired = std::min( AccDesired, fAccGravity - 0.15 );
}
}
// if the route ahead is blocked we might need to head the other way
check_route_behind( 1000 ); // NOTE: legacy scan range value
}
@@ -7502,6 +7550,55 @@ brake_response_horizon( TMoverParameters const *Vehicle, TBrakeSystem const Brak
return horizon;
}
// distance [m] in which the gradient alone, with no brake and no power, brings a train rolling uphill at Velocity [km/h] to a stand
static double
uphill_stopping_distance( double const Velocity, double const Gravity ) {
return ( Velocity / 3.6 ) * ( Velocity / 3.6 ) / ( 2.0 * std::max( 0.01, -Gravity ) );
}
// speed [km/h] from which the gradient alone brings a train rolling uphill to a stand in Distance [m]
static double
uphill_coasting_speed( double const Distance, double const Gravity ) {
return 3.6 * std::sqrt( 2.0 * std::max( 0.01, -Gravity ) * std::max( 0.0, Distance ) );
}
// resultant acceleration [m/s2] to ask for now so that a steady application of the pneumatic brake takes the train
// from Velocity down to Target [km/h] over Distance [m], with the brake force following the speed the way the
// consist brake table says it does (cast iron blocks grip harder as the train slows)
static double
shaped_braking_demand(
double const Velocity, double const Target, double const Distance, double const Gravity,
double const Vmax, double const *Brakea0, double const *Brakea1, int const Tablesize ) {
auto const fullbraking = [&]( double const Kmh ) {
auto const index { std::clamp( static_cast<int>( Tablesize * Kmh / std::max( 1.0, Vmax ) ), 1, Tablesize ) };
return std::max( 0.01, Brakea0[ index ] + 12.0 * Brakea1[ index ] ); };
auto const travel = [&]( double const Ratio ) {
auto const steps { 24 };
auto const step { ( Velocity - Target ) / steps };
auto distance { 0.0 };
for( auto i { 0 }; i < steps; ++i ) {
auto const kmh { Target + ( i + 0.5 ) * step };
auto const deceleration { Ratio * fullbraking( kmh ) - Gravity };
if( deceleration <= 0.001 ) {
return std::numeric_limits<double>::max();
}
distance += ( kmh / 3.6 ) * ( step / 3.6 ) / deceleration;
}
return distance; };
auto low { 0.0 };
auto high { 3.0 };
if( travel( high ) > Distance ) {
return Gravity - high * fullbraking( Velocity );
}
for( auto i { 0 }; i < 30; ++i ) {
auto const middle { 0.5 * ( low + high ) };
( travel( middle ) > Distance ? low : high ) = middle;
}
return Gravity - high * fullbraking( Velocity );
}
void
TController::adjust_desired_speed_for_target_speed( double const Range ) {
// ustalanie zadanego przyspieszenia
@@ -7552,9 +7649,33 @@ TController::adjust_desired_speed_for_target_speed( double const Range ) {
}
else {
// hamowanie tak, aby stanąć
VelDesired = VelNext;
AccDesired = ( VelNext * VelNext - vel * vel ) / ( 25.92 * ( ActualProximityDist + 0.1 - 0.5*fMinProximityDist ) );
AccDesired = std::min( AccDesired, fAccThreshold );
if( ( OrderCurrentGet() & Obey_train ) == 0 ) {
VelDesired = VelNext;
AccDesired = ( VelNext * VelNext - vel * vel ) / ( 25.92 * ( ActualProximityDist + 0.1 - 0.5*fMinProximityDist ) );
AccDesired = std::min( AccDesired, fAccThreshold );
}
else {
// a train keeps its desired speed until the lower edge of the stopping window (a desired
// speed of 0 here asks for hard braking whatever the distance left), aims at the middle
// of the window, and asks only for what that takes
auto const stoppingroom { std::max( 0.5, ActualProximityDist - stopping_margin() ) };
AccDesired = (
BrakeSystem == TBrakeSystem::Pneumatic ?
// cast iron blocks grip harder as the train comes to a stand
shaped_braking_demand(
vel, VelNext, stoppingroom, fAccGravity,
mvOccupied->Vmax, fBrake_a0, fBrake_a1, BrakeAccTableSize ) :
( VelNext * VelNext - vel * vel ) / ( 25.92 * stoppingroom ) );
if( ( fAccGravity < -0.025 )
&& ( AccDesired >= fAccGravity - 0.001 ) // no brake needed at all
&& ( ActualProximityDist - uphill_stopping_distance( vel, fAccGravity ) > stopping_margin() ) ) {
// uphill the gradient alone would bring the train to a stand short of the
// middle of the stopping window; keep it rolling at the present speed until that changes,
// but no faster than the gradient alone can take off by the middle of the window
AccDesired = std::min( 0.0, AccPreferred );
VelDesired = min_speed( VelDesired, uphill_coasting_speed( ActualProximityDist - stopping_margin(), fAccGravity ) );
}
}
}
}
else {
@@ -7578,17 +7699,57 @@ TController::adjust_desired_speed_for_target_speed( double const Range ) {
if( brakingdistance + std::max(slowdowndistance, fMaxProximityDist) >= ActualProximityDist - fMaxProximityDist ) {
// don't slow down prematurely; as long as we have room to come to a full stop at a safe distance, we're good
// ensure some minimal coasting speed, otherwise a vehicle entering this zone at very low speed will be crawling forever
auto const brakingpointoffset = VelNext * braking_distance_multiplier( VelNext );
AccDesired = std::min(
AccDesired,
( VelNext * VelNext - vel * vel )
/ ( 25.92
* std::max(
ActualProximityDist - brakingpointoffset,
std::min(
ActualProximityDist,
brakingpointoffset ) )
+ 0.1 ) ); // najpierw hamuje mocniej, potem zluzuje
auto const brakingpointoffset = (
( VelNext == 0.0 ) && is_train() && ( OrderCurrentGet() & Obey_train ) ?
stopping_margin() : // the same stopping point as the final approach
VelNext * braking_distance_multiplier( VelNext ) );
// a pneumatic brake acts on the state the train will be in once it has responded; while the
// train is still speeding up, plan the deceleration from there, so more is asked for at once
auto velocity { vel };
auto distance { ActualProximityDist };
if( ( BrakeSystem == TBrakeSystem::Pneumatic )
&& ( AbsAccS > 0.0 ) ) {
auto const horizon { brake_response_horizon( mvOccupied, BrakeSystem, AbsAccS, fBrake_a0[ 0 ] ) };
velocity = vel + 3.6 * horizon * AbsAccS;
distance = std::max( 0.5 * ActualProximityDist, ActualProximityDist - ( vel + velocity ) * 0.5 / 3.6 * horizon );
}
auto const brakingroom {
std::max(
distance - brakingpointoffset,
std::min(
distance,
brakingpointoffset ) ) };
auto deceleration { ( VelNext * VelNext - velocity * velocity ) / ( 25.92 * brakingroom + 0.1 ) };
if( ( BrakeSystem == TBrakeSystem::Pneumatic ) && ( velocity > VelNext ) ) {
// follow the shape of the consist brake force over speed rather than assume a constant
// deceleration, over the distance left once the brake has had time to fill (while the
// train is still speeding up, the prediction above has taken that off already)
auto room { brakingroom };
if( ( AbsAccS <= 0.0 )
&& ( ( VelNext > 0.0 ) || ( BrakeCtrlPosition <= 0.0 ) ) ) { // for a stop, only while the brake is still to be applied
room = std::max(
0.5 * brakingroom,
brakingroom - vel / 3.6 * brake_response_horizon( mvOccupied, BrakeSystem, 1.0, fBrake_a0[ 0 ] ) );
}
deceleration = shaped_braking_demand(
velocity, VelNext, room + 0.1 / 25.92, fAccGravity,
mvOccupied->Vmax, fBrake_a0, fBrake_a1, BrakeAccTableSize );
}
AccDesired = std::min( AccDesired, deceleration ); // najpierw hamuje mocniej, potem zluzuje
if( vel > VelNext ) {
if( deceleration < fAccGravity - 0.001 ) {
// no power while still above the speed being braked for
AccDesired = std::min( AccDesired, EU07_AI_NOACCELERATION );
}
else if( ( fAccGravity < -0.025 )
&& ( ( OrderCurrentGet() & Obey_train ) != 0 )
&& ( ActualProximityDist - uphill_stopping_distance( vel, fAccGravity ) > brakingpointoffset ) ) {
// uphill the gradient alone slows the train down more than it is asked to;
// keep it rolling at the present speed until that changes
AccDesired = std::min( 0.0, AccPreferred );
VelDesired = min_speed( VelDesired, uphill_coasting_speed( ActualProximityDist - brakingpointoffset, fAccGravity ) );
}
}
}
}
}
@@ -7959,7 +8120,13 @@ void TController::control_tractive_force() {
if( ActualProximityDist > (is_car() ? fMinProximityDist : // cars are allowed to move within min proximity distance
fMaxProximityDist) ? // other vehicle types keep wider margin
true :
velocity + 1.0 < VelNext ) {
( velocity + 1.0 < VelNext )
// uphill, a train told to hold its speed on the way to a stop needs the power to do it
|| ( ( VelNext == 0.0 )
&& ( ( OrderCurrentGet() & Obey_train ) != 0 )
&& ( AccDesired >= 0.0 )
&& ( fAccGravity < -0.025 )
&& ( ActualProximityDist > fMinProximityDist ) ) ) {
// ...to można przyspieszyć
increase_tractive_force();
}
@@ -8039,9 +8206,10 @@ void TController::control_braking_force() {
auto const AccMax{ std::min(fBrake_a0[0] + 12 * fBrake_a1[0], mvOccupied->MED_amax) };
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 > accdesiredresultant + accmargin
|| BrakeCtrlPosition < 0 ) ) {
if( ( AccDesired < accthreshold // jeśli hamować - u góry ustawia się hamowanie na fAccThreshold
&& ( AbsAccS > accdesiredresultant + accmargin
|| BrakeCtrlPosition < 0 ) )
|| ( fAccGravity < -0.05 && velocity < -0.1 && AccDesired < 0.0 ) ) { // also brake if uphill and slipping back
// hamować bardziej, gdy aktualne opóźnienie hamowania mniejsze niż (AccDesired)
cue_action( driver_hint::brakingforceincrease );
}
@@ -8084,7 +8252,8 @@ void TController::control_braking_force() {
* 0.5 ); // Ra: tymczasowo, bo przeżyna S1
}
}
if ( accdesiredresultant < fAccGravity - 0.05
if ( ( accdesiredresultant < fAccGravity - 0.05
|| AccDesired >= EU07_AI_NOACCELERATION ) // no braking asked for, yet the train is slowing down more than that
&& 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