@@ -420,6 +420,8 @@ public float OdometerM
420420 public double ? DynamicBrakeCommandStartTime ;
421421 protected bool DynamicBrakeBlendingOverride ; // true when DB lever >0% should always override the blending. When false, the bigger command is applied.
422422 protected bool DynamicBrakeBlendingForceMatch = true ; // if true, dynamic brake blending tries to achieve the same braking force as the airbrake would have.
423+ public float DynamicBrakeBlendingRetainedPressurePSI { get ; private set ; } = - 1.0f ; // the amount of pressure that will always be retained in the brake cylinders during blended braking
424+ public float DynamicBrakeBlendingMinSpeedMpS { get ; private set ; } = - 1.0f ; // below this speed, blended braking is disabled
423425 protected bool DynamicBrakeControllerSetupLock ; // if true if dynamic brake lever will lock until dynamic brake is available
424426
425427 public float DynamicBrakeBlendingPercent { get ; protected set ; } = - 1 ;
@@ -1097,6 +1099,7 @@ public override void Parse(string lowercasetoken, STFReader stf)
10971099 case "engine(ortsdynamicbrakeshaspartialbailoff" : DynamicBrakePartialBailOff = stf . ReadBoolBlock ( false ) ; break ;
10981100 case "engine(ortsdynamicbrakereplacementwithenginebrake" : DynamicBrakeEngineBrakeReplacement = stf . ReadBoolBlock ( false ) ; break ;
10991101 case "engine(ortsdynamicbrakereplacementwithenginebrakeatspeed" : DynamicBrakeEngineBrakeReplacementSpeed = stf . ReadFloatBlock ( STFReader . UNITS . SpeedDefaultMPH , null ) ; break ;
1102+ case "engine(ortsdynamicblendingminimumspeed" : DynamicBrakeBlendingMinSpeedMpS = stf . ReadFloatBlock ( STFReader . UNITS . SpeedDefaultMPH , null ) ; break ;
11001103 case "engine(dynamicbrakesdelaytimebeforeengaging" : DynamicBrakeDelayS = stf . ReadFloatBlock ( STFReader . UNITS . Time , null ) ; break ;
11011104 case "engine(dynamicbrakesresistorcurrentlimit" : DynamicBrakeMaxCurrentA = stf . ReadFloatBlock ( STFReader . UNITS . Current , null ) ; break ;
11021105 case "engine(numwheels" : MSTSLocoNumDrvWheels = stf . ReadFloatBlock ( STFReader . UNITS . None , 4.0f ) ; if ( MSTSLocoNumDrvWheels < 1 ) STFException . TraceWarning ( stf , "Engine:NumWheels is less than 1, parts of the simulation may not function correctly" ) ; break ;
@@ -1177,6 +1180,7 @@ public override void Parse(string lowercasetoken, STFReader stf)
11771180 break ;
11781181 case "engine(ortsdynamicblendingoverride" : DynamicBrakeBlendingOverride = stf . ReadBoolBlock ( false ) ; break ;
11791182 case "engine(ortsdynamicblendingforcematch" : DynamicBrakeBlendingForceMatch = stf . ReadBoolBlock ( false ) ; break ;
1183+ case "engine(ortsdynamicblendingretainedpressure" : DynamicBrakeBlendingRetainedPressurePSI = stf . ReadFloatBlock ( STFReader . UNITS . PressureDefaultPSI , null ) ; break ;
11801184 case "engine(vacuumbrakeshasvacuumpump" : VacuumPumpFitted = stf . ReadBoolBlock ( false ) ; break ;
11811185 case "engine(enginecontrollers(ortssteamheat" : SteamHeatController . Parse ( stf ) ; break ;
11821186 case "engine(name" : stf . MustMatch ( "(" ) ; LocomotiveName = stf . ReadString ( ) ; break ;
@@ -1263,6 +1267,7 @@ public override void Copy(MSTSWagon copy)
12631267 DynamicBrakePartialBailOff = locoCopy . DynamicBrakePartialBailOff ;
12641268 DynamicBrakeEngineBrakeReplacement = locoCopy . DynamicBrakeEngineBrakeReplacement ;
12651269 DynamicBrakeEngineBrakeReplacementSpeed = locoCopy . DynamicBrakeEngineBrakeReplacementSpeed ;
1270+ DynamicBrakeBlendingMinSpeedMpS = locoCopy . DynamicBrakeBlendingMinSpeedMpS ;
12661271 DynamicBrakeMaxCurrentA = locoCopy . DynamicBrakeMaxCurrentA ;
12671272 DynamicBrakeSpeed1MpS = locoCopy . DynamicBrakeSpeed1MpS ;
12681273 DynamicBrakeSpeed2MpS = locoCopy . DynamicBrakeSpeed2MpS ;
@@ -1334,6 +1339,7 @@ public override void Copy(MSTSWagon copy)
13341339 DynamicBrakeCommandStartTime = locoCopy . DynamicBrakeCommandStartTime ;
13351340 DynamicBrakeBlendingOverride = locoCopy . DynamicBrakeBlendingOverride ;
13361341 DynamicBrakeBlendingForceMatch = locoCopy . DynamicBrakeBlendingForceMatch ;
1342+ DynamicBrakeBlendingRetainedPressurePSI = locoCopy . DynamicBrakeBlendingRetainedPressurePSI ;
13371343 DynamicBrakeControllerSetupLock = locoCopy . DynamicBrakeControllerSetupLock ;
13381344
13391345 MainPressureUnit = locoCopy . MainPressureUnit ;
@@ -1889,6 +1895,21 @@ public override void Initialize()
18891895 DynamicBrakeEngineBrakeReplacementSpeed = DynamicBrakeSpeed2MpS ;
18901896 }
18911897
1898+ // Define blending minimum speed if it was left undefined (use MSTS minimum dynamic brake speed)
1899+ if ( DynamicBrakeBlendingMinSpeedMpS < 0 )
1900+ {
1901+ DynamicBrakeBlendingMinSpeedMpS = DynamicBrakeSpeed1MpS ;
1902+ }
1903+
1904+ // Define blending retained pressure if it was left undefined
1905+ if ( DynamicBrakeBlendingRetainedPressurePSI < 0 )
1906+ {
1907+ if ( BrakeSystem is AirSinglePipe airSystem )
1908+ DynamicBrakeBlendingRetainedPressurePSI = 2.0f * airSystem . BrakeCylinderSpringPressurePSI ;
1909+ else
1910+ DynamicBrakeBlendingRetainedPressurePSI = 0.0f ;
1911+ }
1912+
18921913 // Initialise track sanding parameters
18931914 if ( MaxTrackSandBoxCapacityM3 == 0 )
18941915 {
@@ -2022,24 +2043,43 @@ protected void CorrectBrakingParams()
20222043 public void DynamicBrakeBlending ( float elapsedClockSeconds )
20232044 {
20242045 // Local blending
2025- if ( Math . Abs ( SpeedMpS ) > DynamicBrakeSpeed1MpS && airPipeSystem != null && airPipeSystem . AutoCylPressurePSI > 0.1f
2046+ if ( airPipeSystem == null )
2047+ {
2048+ DynamicBrakeBlendingPercent = - 1 ;
2049+ return ;
2050+ }
2051+ float autoDemandedPressurePSI = airPipeSystem . AutoCylPressurePSI * airPipeSystem . RelayValveRatio ;
2052+
2053+ if ( Math . Abs ( SpeedMpS ) > DynamicBrakeBlendingMinSpeedMpS && autoDemandedPressurePSI > DynamicBrakeBlendingRetainedPressurePSI
20262054 && ThrottlePercent == 0 && ! ( DynamicBrakeController != null && DynamicBrakeBlendingOverride && DynamicBrakeController . SavedValue > 0 ) )
20272055 {
2028- float maxCylPressurePSI = airPipeSystem . GetMaxCylPressurePSI ( ) ;
2029- float target = airPipeSystem . AutoCylPressurePSI * airPipeSystem . RelayValveRatio / maxCylPressurePSI ;
2056+ float target = ( autoDemandedPressurePSI - DynamicBrakeBlendingRetainedPressurePSI ) /
2057+ ( airPipeSystem . ReferencePressurePSI - DynamicBrakeBlendingRetainedPressurePSI ) ;
20302058
20312059 if ( DynamicBrakeBlendingForceMatch )
20322060 {
2033- float diff = target * FrictionBrakeBlendingMaxForceN - DynamicBrakeForceN ;
2034- float threshold = 100 ;
2035- if ( diff > threshold && DynamicBrakePercent < 100 )
2036- DynamicBrakeBlendingPercent = Math . Min ( DynamicBrakePercent + 100 * elapsedClockSeconds , 100 ) ;
2037- else if ( diff < - threshold && DynamicBrakePercent > 1 )
2038- DynamicBrakeBlendingPercent = Math . Max ( DynamicBrakePercent - 100 * elapsedClockSeconds , 1 ) ;
2061+ if ( DynamicBrake )
2062+ {
2063+ float diff = target * FrictionBrakeBlendingMaxForceN - DynamicBrakeForceN ;
2064+ float normDiff = diff / MaxDynamicBrakeForceN ;
2065+
2066+ if ( Math . Abs ( diff ) > 100.0f )
2067+ {
2068+ // Limit rate of change to reduce overshoots
2069+ if ( diff > 0 && DynamicBrakeForceRampUpNpS > 0 )
2070+ normDiff = Math . Min ( normDiff , DynamicBrakeForceRampUpNpS / MaxDynamicBrakeForceN ) ;
2071+ if ( diff < 0 && DynamicBrakeForceRampDownNpS > 0 )
2072+ normDiff = Math . Max ( normDiff , - DynamicBrakeForceRampDownNpS / MaxDynamicBrakeForceN ) ;
2073+
2074+ DynamicBrakeBlendingPercent = MathHelper . Clamp ( DynamicBrakePercent + 100.0f * normDiff * elapsedClockSeconds , 1f , 100f ) ;
2075+ }
2076+ }
2077+ else // Don't increase dynamic brake setting until set-up has completed
2078+ DynamicBrakeBlendingPercent = 1.0f ;
20392079 }
20402080 else
20412081 {
2042- DynamicBrakeBlendingPercent = target * 100 ;
2082+ DynamicBrakeBlendingPercent = MathHelper . Clamp ( target * 100 , 1f , 100f ) ;
20432083 }
20442084 }
20452085 else
0 commit comments