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

3113 Commits

Author SHA1 Message Date
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
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
pwarszawski
36d4f16c01 Brake force table: take the rapid step threshold from the distributor
TMoverParameters::BrakeForceR() halves the force of a vehicle set to R
below 40 km/h. That is one approximation for every distributor, but a
NESt3/ESt4 distributor with a rapid relay engages the step above 70 km/h
and keeps it down to 50 km/h (TNESt3::GetPF, RapidStatus), and below
that its cylinder pressure is divided by the relay's own multiplier
(TRapid, RapidMult). For a vehicle braking from speed the full force
therefore lasts down to 50 km/h, not 40. The AI builds its consist brake
table (fBrake_a0/a1) from BrakeForceR(), so between 40 and 50 km/h it
expected twice the force the wagons actually deliver.

Add TBrake::GetRapidRatio(), which keeps the old approximation as the
default, and override it in TNESt3 with the relay's threshold and
multiplier. The brake itself is unchanged; only the prediction is.
2026-09-27 17:50:23 +02:00
maj00r
b1bdd42178 Forget a buffer's bindings when it is deleted (#182)
The binding tracker kept the name of a deleted buffer. When gl handed that
name to a new buffer, the tracker took it for already bound and skipped the
bind, so a new vao was left without its index buffer and the nvidia driver
read indices from a null pointer.
2026-09-23 01:03:01 +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
1d9c608396 light_common: add normal offset to shadow lookups 2026-09-19 21:56:02 +02:00
cb6b97b904 light_common: weight textureGather shadow PCF by sub-texel position 2026-09-19 21:15:49 +02:00
2b51417741 Merge pull request #179 from MaSzyna-EU07/fix/reflections-and-burned-colors
Fix black objects with map: none
2026-09-19 18:44:04 +02:00
306c7eaf26 Adjust param values 2026-09-19 17:09:58 +02:00
468ea1d747 Add shader diffuse multiply 2026-09-19 16:56:39 +02:00
maj00r
2f14006b0b Merge pull request #176 from MaSzyna-EU07/random-seed-entropy
Random seed entropy
2026-09-14 23:29:25 +02:00
7ab753cd4c Merge pull request #177 from MaSzyna-EU07/fix/reflections-and-burned-colors
Improve skybox
2026-09-14 23:15:54 +02:00
maj00r
3fa3853018 fix for sonar 2026-09-14 22:51:33 +02:00
8df81eb46d Improve skybox 2026-09-14 21:11:08 +02:00
e7b64c7397 Merge branch 'random-seed-entropy' of https://github.com/MaSzyna-EU07/maszyna into random-seed-entropy 2026-09-14 19:28:45 +02:00
788135cc2d Improve random engine entropy and clean up random engine initialization 2026-09-14 19:26:24 +02:00
maj00r
2945f3f9b2 use random_device for local generator 2026-09-14 19:11:07 +02:00
maj00r
1c2f397fc5 seed from ini can be word 2026-09-14 18:36:07 +02:00
maj00r
ff3daea78c seed the random engines from the system pool, not std::random_device 2026-09-14 18:26:16 +02:00
jerrrrycho
c3007e3237 ci: skip Sonar coverage gate 2026-09-06 15:02:10 +02:00
7a469190ca Merge pull request #168 from pwarszawski/whois-owner-fallback
whois: fall back to the consist owner in the +0/+7 mode too
2026-09-05 21:08:29 +02:00
3b061a1bf1 Merge pull request #171 from pwarszawski/event-name-registration-casefold
event names: fix registration/lookup casing mismatch for non-ASCII identifiers
2026-09-05 21:07:41 +02:00
76eede79aa Merge pull request #154 from MaSzyna-EU07/uart-independent-dynamic-brake
Add independent dynamic brake support (eg. Vectron) to UART controllers
2026-09-05 20:04:34 +02:00
fadcc2d666 Merge pull request #170 from MaSzyna-EU07/fix/reflections-and-burned-colors
Fix relfections on default shader and tonemapping
2026-09-05 20:03:31 +02:00
Piotr Warszawski
2dcae05c77 event names: drop redundant locale-dependent ToLower() at registration
make_event() lowercased the event name twice: cParser::getToken() already
folds ASCII via toLowerChar() (which intentionally leaves bytes >= 0x80
alone), then ToLower() ran std::tolower() over every byte on top of that.

With a non-"C" global locale, that second pass remaps cp1250 uppercase
accented letters (e.g. 0xAF -> 0xBF). Every lookup path - TTrack::AssignEvents(),
child-event resolution in multi_event, event-launcher name resolution -
normalizes only through the parser, so a registered name containing such a
letter can no longer be found: the track logs "can't find assigned event"
and the binding is silently dropped.

Removing the extra ToLower() makes the registration key match every lookup
site (ASCII-only folding, non-ASCII bytes compared verbatim), and matches
the direction toLowerChar()'s own comment already points at for UTF-8.
2026-09-05 19:10:58 +02:00
Piotr Warszawski
1ce576c0c6 whois: log the no-activator case instead of failing silently
A whois with no triggering vehicle previously just wrote nothing to
the target cell with no trace in the log, which is a real pain to
diagnose from the scenery side (this is exactly the kind of thing that
cost real debugging time on a station script recently).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-04 22:22:25 +02:00
Piotr Warszawski
7ead4f11b4 whois: guard against a null activator
whois_event::run_() dereferenced m_activator unconditionally in every
branch (this predates this PR). getvalues_event::run_() and
putvalues_event::run_() already guard the same way elsewhere in this
file; whois_event was missing it. Single early return covers every
branch, old and new.
2026-09-04 22:09:23 +02:00
Piotr Warszawski
4f47233a89 whois: fall back to the consist owner in the +0/+7 mode too
The +8/+16/+24/+32/+40 (and the new +48) modes all pick the reporting
vehicle the same way: the activator's own Mechanik if it's the primary
driver, otherwise the consist's controlling vehicle (ctOwner) - so the
event still resolves correctly if a wagon happens to trigger it ahead
of its locomotive.

+0 (train name / stations left / stop flag) predates that pattern and
never got it: it required m_activator->Mechanik directly and silently
wrote nothing otherwise. A whois hook placed where the loco doesn't
necessarily lead (e.g. a train that reverses direction, or one being
propelled) can have a wagon trigger the event first and the memcell
stays empty, with no error - hard to diagnose from scenery side.

Same owner-selection, same guard semantics (still nothing written if
neither the activator nor its consist has a controller), only the
source of the vehicle it reads from is widened.
2026-09-04 21:01:33 +02:00
6befd69968 Merge pull request #149 from combolek/getpf
state_manager: Optimize computation of pressure flow for Oerlikon ESt valves
2026-09-04 16:39:49 +02:00
maj00r
a9b561cd65 Merge pull request #162 from MaSzyna-EU07/bugfix/260825-00jor
make targetfps great again
2026-09-03 22:18:16 +02:00
jakubg1
db6296124b Merge pull request #166 from jakubg1/dev/revert_remove_instructor_panel
Revert "Remove training card window"
2026-09-02 12:37:40 +02:00
jakubg1
ba19db8715 Revert "Remove training card window"
This reverts commit 363b50c9c2.
2026-09-02 12:32:35 +02:00
jakubg1
4e35a5f45f Merge pull request #160 from jakubg1/dev/remove_instructor_panel
Remove training card window
2026-09-02 11:59:49 +02:00
jakubg1
a2f62c56ff Merge pull request #161 from jakubg1/dev/epfuse_engage_fix
Fix EP fuse auto-(dis)engaging
2026-09-02 11:59:30 +02:00
766a957527 Fix relfections on default shader and tonemapping 2026-09-01 20:41:06 +02:00
maj00r
0aceb120e2 make targetfps great again 2026-08-25 18:03:55 +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
jakubg1
363b50c9c2 Remove training card window
This was a leftover from attempts at modifying MaSzyna for professional simulators.
Interaction with this window could cause a crash.
2026-08-23 22:37:33 +02:00
jakubg1
9a0b63a97c Merge pull request #133 from dos1/launcher-panel
launcher: Open on scenery list panel
2026-08-23 21:53:12 +02:00
df9ba73a88 Merge pull request #158 from MaSzyna-EU07/migrate-to-actions
Migrate to actions
2026-08-20 23:11:27 +02:00
66302bd88e Remove appveyor and update readme 2026-08-19 14:50:28 +02:00
8024c2be7b Revert "Delete appveyor.yml"
This reverts commit 8a22553e7c.
2026-08-19 14:48:03 +02:00
3c61168783 Revert "Delete azure-pipelines.yml"
This reverts commit 67bb6e1d3c.
2026-08-19 14:47:59 +02:00
67bb6e1d3c Delete azure-pipelines.yml eu07-2026.423-prerelease 2026-08-19 14:45:49 +02:00
8a22553e7c Delete appveyor.yml eu07-2026.422-prerelease 2026-08-19 14:45:33 +02:00
6b50a6aa68 Update CI configuration for Windows and Linux builds 2026-08-19 14:23:40 +02:00
1cfe44a9d3 Add Linux Python environment setup in workflow
Added a step to prepare a Linux Python environment with a virtual environment and install Pillow.
2026-08-19 14:07:30 +02:00
fee2dcea28 Fix path for vswhere.exe in build_bin.yml 2026-08-19 13:20:53 +02:00
af08beaaa8 Change push branch filter to include all branches 2026-08-19 13:17:00 +02:00