Changes to how the AI works the train brake on the way to a lower speed
or a stop, so that it uses few, early applications instead of a late
deep one:
- Approaching a lower speed, the deceleration may fall short of the
demand by only half of a step (fBrake_a1) before the brake is applied
harder. With a full step the demand crept up meanwhile and a long G
train got its next step many seconds too late.
- The shortfall is checked every 3 s. If it keeps growing at a handle
left alone for longer than the brake takes to respond, the AI adds a
small step at once instead of waiting until the margin calls for a
deep one. A shortfall that shrinks on its own (brake still filling,
blocks gripping harder at lower speed) does not trigger it.
- Releasing, a position short of the first one counts as giving the
minimum stage of the distributor, not less.
- Holding the speed on a descent, the predicted excess speed is taken
off over the time the brake takes to respond, not at once, so a slow
brake does not get a deep application that then takes as long to
release.
The condition added for holding the speed uphill near a stop pushed the
cognitive complexity of control_tractive_force() to 26, over the 25
SonarCloud allows. It now lives in is_too_fast_uphill(); the behaviour
is unchanged.
* 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).
- end the charging position when the control reservoir of any vehicle, the
locomotive included, rises 0.05 bar above the nominal pipe pressure
- standing and unable to depart for 10 s: allow the charging position
regardless of the equivalent pipe pressure and the train type
Resolve the conflicts with the merged nested ifs (#194) in
control_tractive_force() and control_braking_force(): the conditions keep
the new flattened form, with this branch's changes carried into them.
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).
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.
- The warning signal is no longer going to be played for an obnoxiously long amount of time by AI drivers when driving vehicles with a manual warning signal switch, most notably EN57.
- A new `moveDepartureWarned` driving flag has been created to accomodate this behavior.
- Added the action time counter and the missing driving flags to the Vehicle AI section.