17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-09 13:42:26 +01:00
Commit Graph

46 Commits

Author SHA1 Message Date
pwarszawski
439e892abd AI: use the locomotive releaser only when starting off
The rule "wyluzuj lokomotywe, to szybciej ruszymy" released the
locomotive's own brake whenever power was asked for and the locomotive
cylinder held more than 0.4 bar. It had no speed condition, so it also
fired in motion, at 50-75 km/h, right after an application. On a
locomotive with the EStED distributor (E186) this vented the control
reservoir; with the pipe not yet recharged the locomotive brake then
faded to nothing at the next application.

The rule is meant for starting off, so it now applies only below
1 km/h.
2026-11-07 15:35:35 +01:00
pwarszawski
18bc7e06bf AI braking: move the uphill power-off check into its own function
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.
2026-11-02 16:43:16 +01:00
pwarszawski
1f3cf4fe56 AI braking: don't let the running brake test override a stop ahead
adjust_desired_speed_for_braking_test runs after the target speed was
worked out and overwrites AccDesired. In its third phase it holds the
deceleration near fAccThreshold until the speed has dropped by 8 km/h.
On a 20 per mille descent that application barely holds the speed, so
the phase can last minutes, and a stop signal coming up meanwhile is
ignored. In one real-time run an E186 with 19 container wagons started
the test 1290 m before a stop signal, lost 7 km/h by the signal and
overran it by 274 m after the emergency application.

While the test runs the AI now uses whichever of the two asks for the
harder braking: the test may brake harder than the route needs, never
softer.

Measured in real time, four runs of 32 consists each on a 20 per mille
descent, on the flat and on the descent with half the rail adhesion:
the test no longer runs within 600 m of a stop signal (before: down to
414 m on this branch, and 14 m with the overrun above in a build with
other brake changes), no overruns, stops 8-15 m short of the signal 86,
117 and 83 of 128 (before 90, 117 and 78).
2026-11-02 16:02:14 +01:00
pwarszawski
6b82f1471a AI braking: take power off when holding the speed uphill near a stop
Within fMaxProximityDist of a stop, going uphill, the AI holds its speed
with AccDesired = min(0, AccPreferred). control_tractive_force only cut
the power uphill when AccDesired < 0, so an AccDesired of exactly 0 kept
the controller where it was. A consist that came into that range under
power kept full power and crept up to the signal.

Holding the speed uphill on the way to a stop now asks for no speeding
up: power comes off once the train is faster than AccDesired allows.

With the current brake valves this rarely shows: on a 10 per mille climb,
four real-time runs of 32 consists each, the E186 with 25 empty 408W drew
power in 0-14 samples of the last 50 m before and 0-8 after, no overruns
either way. It surfaced once the AI predicted the ESt3 brake closer to
what the valve delivers, where the same train crept to 12 km/h from 40 m
out and overran the signal; with this change it stopped short in all runs.
2026-11-02 16:02:06 +01:00
pwarszawski
8112733e0d Merge remote-tracking branch 'origin/master' into fix187
# Conflicts:
#	McZapkie/Oerlikon_ESt.h
#	McZapkie/hamulce.h
2026-10-22 07:08:29 +02:00
40a4267bd3 Merge pull request #189 from pwarszawski/fix/ai-ready-released-brakes
AI: don't cut the power for a brake already released below 0.4 bar
2026-10-21 09:00:34 +02:00
jerrrrycho
c7f9796387 fix(ai): release horn when human driver takes over from AI (#210)
* 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).
2026-10-16 14:44:30 +02:00
pwarszawski
71a19f32fb Compare the brake force with 10 kN directly 2026-10-12 06:54:17 +02:00
pwarszawski
504d69b26a AI braking: keep braking for a lower speed inside the proximity range
Inside the proximity range only a stop had a final approach of its own;
for a lower speed ahead that branch set nothing, so AccDesired stayed at
the preferred acceleration. With the range at 50 m, which this PR uses so
that trains stop 8-15 m short of a signal, the AI stopped braking within
the last 50 m before a speed restriction and even took power: EMUs reached
a 20 km/h restriction at 25 km/h. On master the range is 15-40 m, which
kept this mostly out of sight.

A lower speed ahead now keeps the braking curve right up to where it
begins. Stops are unchanged.

Measured in real time on current master (with #199), with a 20 km/h
restriction one coach long, two runs of 32 consists each: over 22 km/h in
the restriction on the flat 9 on master, 15 before this change, 1 after
(a 36WEa at 22.1 km/h); on the 20 per mille descent 15, 0 and 0. Stops
8-15 m short of the signal stay as they were (flat 57 and 55 of 64,
descent 44 and 42), no overruns.
2026-10-10 23:39:19 +02:00
c26041f2e7 Merge pull request #199 from MaSzyna-EU07/brake-diag
AI - prevent overcharge when pipe pressure of car is too high for too long
2026-10-10 20:37:12 +02:00
9935257783 Add nosonar tags 2026-10-09 18:40:31 +02:00
917c8f7285 Merge branch 'master' into feat/modern-editor 2026-10-08 17:44:58 +02:00
maj00r
4e406fdd86 fix(ai): end brake charging on control reservoir overcharge, charge on blocked departures
- 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
2026-10-07 18:17:59 +02:00
jerrrrycho
79bd0c8e91 fix(sonar): addressing reliability/security issues (#202) 2026-10-04 20:00:13 +02:00
27a94509c4 feat(roads): right of way and lane turns at junctions, road weights, kerbs, medians and forking roads, banks to the ground, roundabouts, pedestrian crossings, spawn sets by count; fix(editor): model skin export, placement on the ground under the orthophoto 2026-10-04 01:55:35 +02:00
pwarszawski
b71ad90c93 Declare isbraking in the init-statement of the if 2026-10-03 14:32:09 +02:00
pwarszawski
c38ec7faa8 Merge master into fix/ai-ready-released-brakes 2026-10-03 14:31:56 +02:00
maj00r
affa7cdaa9 AI - prevent overcharge when pipe pressure of car is too high for too long 2026-10-02 19:55:23 +02:00
pwarszawski
ac47e3a261 Merge master into fix/ai-braking-split
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.
2026-10-02 15:22:39 +02:00
maj00r
314e7df294 braking diagnostics 2026-10-01 21:57:18 +02:00
jerrrrycho
1474d2b773 fix: merge nested ifs in Driver.cpp cpp:s1066 (#194) 2026-10-01 13:30:36 +02:00
jakubg1
99c5a0090b Remove brake position check from TController::PrepareEngine()
The function would flag the consist as not ready (`iEngineActive` set to `false`) when the brake handle was at a cutoff position. Since this is what the brake lever is exactly starting at in the EW58 train, the train when asked to move the brake lever to operating position would not listen since the `control_tractive_and_braking_force()` function which is supposed to do that is gated behind the readiness flag in the `TController::Update()` function.

This makes sure EW58 trains can be driven by AI once again instead of being stuck.

I don't really know why the brake position check was there, since it would fail only if:
- There is a brake handle
- The brake handle is in the cutoff position
- The brake handle is NOT in the operating position

The third check seems redundant but curiously there is one case where this matters and it's... MHZ_EN57. Its operating and cutoff positions are both defined as `0`, which might be a hack, since the actual cutoff position in the MHZ_EN57 handle is meant to be `6`.
2026-10-01 02:10:21 +02:00
pwarszawski
4f2dfd9318 Drop redundant comparisons with true/false
In the conditions this PR adds; no change in behaviour.
2026-09-29 09:47:51 +02:00
pwarszawski
f2f152e5d3 AI: don't cut the power for a brake already released below 0.4 bar
When the AI takes power after a brake release, determine_consist_state()
marks the consist not Ready if any vehicle's distributor reports the lap
or application state (b_hld | b_on), whatever the speed, and the sanity
check in control_handles() then puts the master controller back to zero.
A distributor stays in the lap position until the brake pipe has fully
recharged, so a few seconds after a release, with 0.2-0.3 bar left in a
cylinder, one wagon of a train is enough to throw an EU07 or EP09 that
has just reached the 2nd-5th position back to zero; it starts again
moments later.

The check in motion was meant to be lenient (brake force over 10 kN),
but the distributor state was applied at any speed. In motion it now
counts only when the cylinder holds more than 0.4 bar, the UIC figure
already used a few lines below for a released brake. At a stand the
check is unchanged.

Measured in real time against master, two runs of each scenery (a 20 per
mille descent, flat and a 10 per mille climb with 32 consists each, and a
descent with 15 freight trains of other wagon types), with a 20 km/h
restriction one coach long: throttle-downs to zero in motion caused by
readiness drop from 13 to 1, 7 to 2, 0 to 0 and 13 to 6. Each of those
left has a wagon over 0.4 bar that is applying or lapped, or one still
releasing with more than 10 kN at walking pace. No coupler broke; signal
overruns, speeds in the restriction and rolling back on the climb
are the same as on master within run-to-run spread.
2026-09-28 23:21:19 +02:00
jerrrrycho
176427f96b refactor: sonar c++20 changes (#185) 2026-09-28 21:08:15 +02:00
jerrrrycho
4137f5504a fix: sonar sast fixes - sonar.yml, nosonar, linux chmod modification 2026-09-28 21:07:53 +02:00
pwarszawski
ce87aa0e09 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).
2026-09-28 15:45:10 +02:00
pwarszawski
92bd7ef827 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.
2026-09-28 15:45:10 +02:00
pawelw
95f41f069f Speed table: keep it sorted and keep the signal indices valid (#178)
* Fix the speed table index remap in TablePurger()

TablePurger() rebuilds the speed table and has to move SemNextIndex and
SemNextStopIndex along with it. Two cases came out wrong.

A trimmed point was never invalidated: the index was first overwritten with
the position the next kept point would take, and the guard that should reset
it to -1 compared the loop counter against that new value, so it only matched
when nothing had been trimmed before it. The index then pointed at an
unrelated record, and if that record happens to sit at zero velocity the
placement can no longer be updated either, because the update requires the
recorded one to be non-zero.

An index pointing at the last entry was left untouched, because the loop stops
one short of it and that entry is appended afterwards. The stale value ended
up past the end of the trimmed table, which is what the provisory check in
TableTraceRoute() catches.

The placements are now recorded separately and assigned once the new table is
complete, with the always-kept last entry handled too. The trim condition is
shared by the counting and the copying pass instead of being spelled out
twice.

TableCheck() drops entries beyond a switch that changed under the scan without
adjusting the placements either, so they are invalidated there as well. The
check in TableTraceRoute() stays as a safety net, without the TODO.

* Sort the whole speed table in TableSort()

TableSort() made a single bubble pass over the table. That only moves an entry
one place towards the front, but TableTraceRoute() adds the scanned events of a
track before the track itself, so a track carrying k scanned events lands k
places too far and needs k passes. With two or more events in one slot - a cab
signal resonator next to a signal, a signal and a W4 - the track entry stays
behind entries that are farther away, and TableUpdate() walks the table out of
distance order until enough later scans have shifted it back.

The range before the last slot is now sorted with std::stable_sort, and the
semaphor placements are moved along by looking their events up again.

---------

Co-authored-by: pwarszawski <pwarszawski@gmail.com>
2026-09-23 01:02:11 +02:00
jakubg1
f8cb5e4431 Fix EP fuse auto-(dis)engaging
When the Autorewident function is enabled, the EP fuse switch will no longer automatically engage or disengage if the player is controlling the train. This would happen for example when approaching a station.
2026-08-24 00:24:15 +02:00
maj00r
5fce417a51 fix: guard Random/LocalRandom against inverted min>max bounds
std::uniform_real/int_distribution has UB when min>max. The old "fancy Random"
used interpolate(a,b,t) which tolerated a>b; the migration to
std::uniform_real_distribution did not, and only one inverted call site was
fixed by hand. Swap the bounds inside Random()/Random(int)/LocalRandom() so
every call site is safe, and reorder the two inverted literal calls in
Driver.cpp (fActionTime buzzer / departure delay) for readability.
2026-07-02 00:37:52 +02:00
maj00r
03bfbb1345 fix: restore min>max tolerance lost in clamp->std::clamp migration
The dumb3d->glm / utilities-simplification refactor replaced the old custom
clamp() (which tolerated Lo>Hi, returning the upper bound) with std::clamp,
where inverted bounds are undefined behaviour. Only a few sites were patched
afterwards, by hand, with std::minmax plumbing (incl. a stray `static` inside
a per-axle loop). Others silently produced wrong results in the AI braking /
acceleration path.

Add a safe_clamp() helper (normalizes inverted bounds) and use it ONLY where
Lo<=Hi cannot be proven at compile time (config min/max pairs, sign-dependent
expressions, container underflow). Sites with constant bounds or [0, x>=0]
keep std::clamp. Remove the manual std::minmax workarounds and rewrite the
damaged-track jolt code value-first.
2026-07-02 00:37:52 +02:00
jerrrrycho
d85096f64d reformat: remove redundant parentheses 2026-06-30 21:19:46 +02:00
jerrrrycho
bf147dd155 refactor: replace null with nullptr 2026-06-29 02:48:25 +02:00
docentYT
a787bdd77c fix min max in Random 2026-05-12 16:56:48 +02:00
docentYT
d464628a8e Fix problem with min value bigger than max value in std::clamps 2026-05-03 22:18:40 +02:00
docentYT
13c7317f6e Do not allow implicit cast for inline std::string to_string(bool Value) and change to_string to std::string for non boolean values 2026-05-03 15:49:29 +02:00
docentYT
3628eb0fc3 Replace interpolate with std::lerp and glm::mix 2026-05-02 01:31:38 +02:00
docentYT
d1f16411a1 Replace clamp with std::clamp 2026-05-01 22:14:29 +02:00
docentYT
7d561f8443 use glm::length2 instead of glm::dot for squared length 2026-04-27 23:13:12 +02:00
docentYT
fae68642bc use glm instead of Math3D in vehicle
WARNING: Model rotation is broken
2026-04-27 13:07:38 +02:00
docentYT
ed28eff066 use glm instead of Math3D in world 2026-04-27 00:36:03 +02:00
jakubg1
407b187688 Tweak the door closing and warning signal delays
- 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.
2026-04-04 00:13:26 +02:00
jerrrrycho
9f43dca48b refactor: create paths namespace instead of global variables 2026-03-15 11:55:30 +01:00
copilot-swe-agent[bot]
7e49aa8740 Rewrite #includes to use explicit subdirectory paths
Co-authored-by: Hirek193 <23196899+Hirek193@users.noreply.github.com>
2026-03-14 19:16:48 +00:00
copilot-swe-agent[bot]
0531086bb9 Reorganize source files into logical subdirectories
Co-authored-by: Hirek193 <23196899+Hirek193@users.noreply.github.com>
2026-03-14 19:01:57 +00:00