diff --git a/vehicle/Driver.cpp b/vehicle/Driver.cpp index 5b61b6dd1..09b607b69 100644 --- a/vehicle/Driver.cpp +++ b/vehicle/Driver.cpp @@ -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( 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::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 diff --git a/vehicle/Driver.h b/vehicle/Driver.h index c493afc8a..0e65a4b94 100644 --- a/vehicle/Driver.h +++ b/vehicle/Driver.h @@ -353,6 +353,12 @@ private: void control_tractive_force(); void increase_tractive_force(); void control_braking_force(); + // how far short of a stop the AI aims: in train running the middle of the 8-15 m window before a signal, + // otherwise (a W4 stop point, shunting, coupling) half the minimum proximity distance, as before + double stopping_margin() const { + return ( ( OrderCurrentGet() & Obey_train ) != 0 ) && ( false == IsAtPassengerStop ) ? + fMinProximityDist + 3.5 : + 0.5 * fMinProximityDist; } void apply_independent_brake_only(); void check_route_ahead( double const Range ); void check_route_behind( double const Range ); @@ -387,6 +393,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 fBrakeHeldTime = 0.0; // czas od ostatniego przestawienia kranu hamulca zasadniczego 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