18
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-15 14:04:13 +01:00
Commit Graph

245 Commits

Author SHA1 Message Date
e433b43fef Script screens: led_post and resize of the Lua screens on the gpu 2026-11-10 16:22:35 +01:00
c4924f756d Lua script screens beside the WebAssembly ones 2026-11-10 15:29:25 +01:00
18c333b089 wasm display support 2026-11-10 00:57:37 +01:00
d5e524ee60 Merge pull request #231 from dos1/aiui
driveruipanels: Allow to toggle AI driver from the driving aid
2026-11-09 13:00:26 +00:00
7edf23b8ff Merge pull request #270 from MaSzyna-EU07/fix/fpslimit-frame-pacing
fpslimit: pace frames in integer clock ticks, show the real frame rate (eu07/maszyna#416)
2026-11-09 12:56:40 +00:00
0c95dbfa5a Merge pull request #280 from MaSzyna-EU07/feat/radio-fuse
Radio fuse: a cut-off in the radio's power supply (eu07/maszyna#438)
2026-11-09 12:53:14 +00:00
71733a343a Merge pull request #299 from MaSzyna-EU07/fix/ui-tooltips-stay-in-window
fix(ui): tooltips and popups stay in the main window, and a long tooltip wraps
2026-11-09 12:46:56 +00:00
65468a2eae Merge pull request #302 from miloszunio/fix/gizmo-yaw-rotation-master
fix(editor): model yaw rotation with the gizmo jumps past 90 degrees
2026-11-09 12:46:24 +00:00
Claude
58e4bbff49 fix(editor): a model turned with the gizmo about the vertical axis no longer jumps back and forth
The angles of the whole gizmo matrix were decomposed every frame and the model was turned by their difference from its own.
The decomposition gives the angle about the vertical axis only within -90..90 degrees: past 90 it comes out as 180 - angle
with the other two axes turned by 180, so a drag one way turned the model back and flipped it. The turn is now taken from
the delta matrix of this frame (small, so its angles decompose with no ambiguity) and wrapped to -180..180.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DDcMn1Q7wdYkbxDUWscNHZ
2026-11-09 07:56:51 +01:00
jerrrrycho
8fb596f218 fix(simulation): any exception ends a test run with error, other runs as on master
main() tells a test run (--test) apart before anything runs. Only a test run
catches every exception and installs a std::terminate handler, both ending the
run with the error code and a test: line; any other run keeps master's
exception handling, so on Linux an uncaught runtime_error aborts at the throw
site again.

A test run doesn't start the Discord RPC thread: it published the test
scenery as the player's presence and held the closing up to 5 s.

run.py drops a case scenery's .sbt cache after a run cut short (wall_timeout,
error, a crash, killed by run.py), which could have been writing it.

The -edit branch of the test command line parser had no effect (a token that
isn't an option is ignored either way) and is gone; its test checks what can
be seen instead.
2026-11-08 19:46:16 +01:00
maj00r
be40008808 fix(ui): tooltips and popups stay in the main window, and a long tooltip wraps
With the panels in separate windows on (the default), a tooltip or a popup that didn't fit in the main window got a window of
the system of its own, with an OpenGL context of its own. The editor froze so on 2026-11-08: its main thread stuck for good in
the NVIDIA driver while making the context of an invisible window 1925 x 29 px, a tooltip in one line wider than the screen.
The Polish texts of the editor have tooltips of up to 360 characters in a line; the one of the curve mode of laying track is
2088 px wide.

- io.ConfigViewportsNoAutoDetach (a MaSzyna addition, set with the separate windows) keeps the tooltips and popups shown over
  the main window inside it too, placed and shrunk as they are without the separate windows: they get no viewport of their
  own. A panel dragged out of the main window by hand still gets its window, and the tooltips and popups over it may go past
  its edge as before.
- io.ConfigTooltipsWrapWidth (a MaSzyna addition): SetTooltip() wraps its text at this width. The ui sets 40 em, 520 px at the
  font of 13 px.

Checked in the editor on a hidden desktop: the tooltip of the curve mode, full screen 1920 x 1080, and the one of the redo
button at the edge of a window 1280 x 720 each made a window of their own before, and none now. Both are drawn in the main
window, the long one in five lines.
2026-11-08 19:26:19 +01:00
jerrrrycho
43fc80f452 fix: radio fuse review: working push buttons, own hint, HUD
- radioon_sw:/radiooff_sw:/radiofuseon_sw:/radiofuseoff_sw: are now loaded
  as push-button gauges in initialize_gauge, so they can be clicked; the
  enable/disable commands press them while held. radiotoggle and
  radiofusetoggle flip the state directly, so they don't move them.
- New driver hint "Close radio fuse" (radiofuseon); "Switch on radio" is
  given only when the radio's own switch is off.
- The HUD shows the radio channel only with the fuse closed.

(eu07/maszyna#438)
2026-11-08 17:52:39 +01:00
Sebastian Krzyszkowiak
b8ea91bd44 driveruipanels: Allow to toggle AI driver from the driving aid
Possibly the single greatest UX improvement for newcomers that could
there ever be.
2026-11-08 12:14:44 +01:00
jerrrrycho
03a417c7b0 feat(simulation): a real time watchdog ends a test run that hangs
A test run that hung, in the loading or anywhere else, waited until whoever
started it killed it. A watchdog thread now ends a run still going test.wall_timeout
seconds of real time after the start of the simulator, loading included: it says
`test: timeout ... still running after N s of real time` and quits with the
timeout code, whatever the main thread is doing, with no dialog. A test that has
ended but whose simulator doesn't close ends with its own result.

The limit is test.wall_timeout (settings, the scenery's config block) or
--test-wall-timeout, which wins. Not given, it is test.timeout + 600 s: ten
minutes for the loading, and a simulation that runs at least as fast as real
time is never cut short. test.timeout 0 means no watchdog unless the limit is
given; test.wall_timeout 0 turns it off.

run.py passes the case's timeout as --test-wall-timeout and kills a simulator
only 30 s after it. A timeout from the watchdog counts as wall_timeout there, so
a run that hung doesn't pass a case expecting the simulation time to run out;
test_mode_wall_timeout ends while loading on a limit of 2 s.
2026-11-08 11:33:36 +01:00
jerrrrycho
14d25520f3 fix(simulation): a test's standard output carries its result only
The log was printed in colour to stdout too (debuglog bit 2), so a script reading
the verdict of a test got the whole log with it. In test mode the log goes to the
log file only and stdout has the `test: ...` line alone; the usage text of a bad
command line goes to stderr. Whether the run is a test is now known before the
log service starts.
2026-11-08 11:23:02 +01:00
jerrrrycho
1626df2bc4 fix(simulation): a scenery's config block can't turn back on what a test keeps off
The config block of a scenery is read like the settings files, after the test
mode settings were applied, so a scenery with soundenabled, multiplayer,
execonexit, feedbackmode or the like in it got them back in a test run. The test
mode settings now are applied again after each config block of the scenery;
apply_test_mode_settings() moved to simulationtest.cpp for that.
2026-11-08 11:22:37 +01:00
jerrrrycho
7f8892cda6 fix(simulation): a test doesn't read the player's own eu07.ini
The simulator reads eu07.ini from %APPDATA%/MaSzyna (~/.config/MaSzyna on Linux)
when there is one, in place of the game folder's. A test run then depended on
whose machine it ran on. With --test only the game folder's eu07.ini is read,
then the files given with -ini. The test mode options are taken from the command
line before the settings for that; --test-timeout still wins over test.timeout
of any settings file.
2026-11-08 11:21:57 +01:00
jerrrrycho
486a714f52 refactor(simulation): the rules of the test mode in a file of their own, with unit tests
The command line options of the test mode and the end conditions checked after
each frame are now plain functions in simulation/simulationtestrules.cpp, which
needs nothing of the running simulation: parse_test_command_line() and
evaluate_frame(). The file is part of the core library, so the unit tests check
them (tests/unit/test_test_mode.cpp): the --test-timeout value, values of other
options not taken for options, a pass that waits for the stop, the standstill,
the timeout. test_manager only gathers what a frame brought and acts on the
verdict. No change in behaviour.
2026-11-08 11:20:52 +01:00
jerrrrycho
93e2b85d08 fix(simulation): the test mode gives nothing to the outside and leaves the player's files alone
The test mode turned off what comes in from outside, but a few settings
of the user's eu07.ini still reached out of a test run:

- feedbackmode: 1 and 2 toggle Caps Lock / Scroll Lock by injecting key
  events into the whole system, 3 and 4 drive an LPT port or a PoKeys
  desk (and 4 reads the desk's switches);
- multiplayer: events are reported to a multiplayer server, and some
  events are held back for it;
- execonexit: a program is run with system() at the end of each run,
  which also waits for it;
- showsystemconsole: a console of the run's own pops up and stdout is
  reopened on it, so whoever started the test with a redirected output
  doesn't get the `test:` line (and a console the run was started from
  would be hidden without it);
- imgui.ini in the game folder was written by every run, and by several
  at once with run.py -j.

All of these are off in test mode. Nothing changes outside it.
2026-11-08 09:36:51 +01:00
jerrrrycho
4356e7c3fd fix(simulation): a test run that can't start ends with the error code and says why
- `--test` is looked for before the rest of the command line is read. An
  unknown option before it (e.g. `-s x.scn --test-timout 90 --test`) used
  to end the run with exit code 0, which a caller reads as a pass.
- `--test-timeout` without a value, or with one that isn't a number of
  seconds, fails the run instead of leaving the default or, through atof,
  turning the timeout off.
- These, a missing -s, a failed start and an exception caught in main()
  end the test through test_manager::conclude(), which writes the usual
  `test: error (exit code 5) ... : <reason>` line to stdout and the log.
  Before, `eu07 --test` alone exited with 5 and nothing anywhere: the
  error went to the log queue and the process ended before the log
  thread wrote it. In test mode main() now stops the log thread and
  waits for it before _Exit().

Outside the test mode nothing changes: conclude() does nothing there.
2026-11-08 09:36:28 +01:00
jerrrrycho
30bdc3da61 feat: radio fuse, a cut-off in the radio's power supply (eu07/maszyna#438)
TMoverParameters::RadioFuse, closed by default, stands for a fuse in the
cabinet, a lever on the desk or the switch on the radio's power supply.
RadioPowered() needs it closed, so with it open the radio's buttons, calls,
messages and the radio-stop stop working; opening it at a standstill resets
the radio-stop as switching the radio off does, and the radio and radio-stop
lamps go out. New commands radiofusetoggle/radiofuseenable/radiofusedisable
(no default keys), cab controls radiofuse_sw: (follows the fuse state),
radiofuseon_sw: and radiofuseoff_sw:, and radio_fuse for the Python screens.
The AI turning the radio back on closes the fuse too.

Existing vehicles don't change: the fuse starts closed and opens only from a
new cab control or a bound key. SVN data, optional: cabs that have such a
fuse or lever get one of the new controls in their .mmd, in the form of
radio_sw: (e.g. "radiofuse_sw: { <submodel> rot 0 0 0 }"); lang/*.po can
translate "radio fuse".

(eu07/maszyna#438)
2026-11-07 21:33:28 +01:00
jerrrrycho
e8c4f3592e fix: fpslimit paced in integer clock ticks, FPS shown counts the wait (eu07/maszyna#416)
The limiter slept off the rest of each frame with sleep_for() given a float
duration. MSVC adds that to steady_clock::now() in float nanoseconds, so after
a few hours of system uptime the wake-up time is rounded to tens of ms: at
95 h uptime fpslimit 60 had no effect at all and fpslimit 30 gave ~22 fps, in
the full and the legacy renderer alike.

frame_pacer now schedules frames on a fixed cadence in steady_clock ticks and
the loop waits with sleep_until; an overshooting wait is made up in the next
frame. The FPS shown and the motion blur use the new mainloop_period stopwatch
(frame start to frame start, wait included); the targetfps/fpsaverage
draw-range controllers keep reading the work time as before.

(eu07/maszyna#416)
2026-11-07 18:47:41 +01:00
jerrrrycho
dd019d88ba feat(simulation): the test mode takes no input from outside (eu07/maszyna#541)
With --test the gamepad, mouse picking, UART, ZeroMQ and motion telemetry are
off, the hidden window gets no key, mouse, scroll or char callbacks, and the
window procedure that takes WM_COPYDATA commands from other programs isn't
installed, so nothing but the scenery drives a test run.
2026-11-07 17:38:37 +01:00
jerrrrycho
441caeed64 refactor(simulation): tidy the test mode code
The test manager is reached through simulation::test() instead of a global,
the result cell is owned by a unique_ptr until the memory table takes it, the
cell value is compared with a margin, strings are built with std::format and
the --test settings moved to apply_test_mode_settings(). The driver's signal
check is folded into UpdateSpeedPosition(), which updates a speed table entry
and reports a signal at stop the head of the train just passed. The test.*
keys are listed with the other settings at the start of the log.
2026-11-07 17:12:11 +01:00
jerrrrycho
02e29f6971 feat(simulation): test mode, --test runs a scenery unattended and exits with the result (eu07/maszyna#541)
eu07 --test -s <scenery> [--test-timeout <s>] runs the scenery in ghostview with
the null renderer (models loaded, no textures), a hidden window, no sound,
network or python, and in fixed steps of 0.03 s of simulation time per frame
(three 10 ms physics steps) as fast as it can. Without -seed it uses a fixed
seed, so two runs give byte-identical physicslogs.

The scenery reports its result to the __test.result memory cell, which the
simulator always creates: value 1 passes once every vehicle has stood still for
1 s, value 0 fails at once. The run ends with exit code
  0 pass, 1 fail,
  2 a train passed a signal at stop (SetVelocity 0 0, as seen in its driver's
    speed table; SetVelocity 0 x on automatic block doesn't count),
  3 timeout (test.timeout, default 3600 s of simulation time),
  4 standstill (every vehicle still for test.standstill s, default 300, with no
    result; a pass with test.pass_on_standstill yes),
  5 error (no -s, the scenery didn't load or has no vehicles, an exception).
The test.* keys go in the scenery's config block; test.fail_if_stop_overran no
only logs a signal passed at danger. Outside the test mode a signal passed at
danger is logged.

The null renderer gets a ui backend which draws nothing, hands out the null
texture and material instead of throwing, and ends the ui frame. A GL context
is still made for the hidden window, without asking for a version, as parts
of the ui create textures with gl calls of their own. No crash dialog or
message box in test mode; the window code no longer needs a monitor.
2026-11-07 16:20:42 +01:00
jerrrrycho
798fd89dd0 test: scenario tests that run the simulator and check a vehicle's physicslog
tests/scenarios/run.py builds a test scenery from a game install's scenery
(own trainsets removed, case fragment added), runs eu07 in ghostview with
physicslog on, and checks what the vehicle did: starts, max speed, brakes,
stops. First case: EU07 on td.scn driven by AI on the td/rozklad timetable,
with the Stacja1 exit route set by an event launcher instead of shift+4.

eu07 gets a -ini <file> option: an extra settings file read after eu07.ini,
so a test run can switch to a window and turn on physicslog without touching
the user's config.

Local only: scenery and vehicle data are not in the repo.
2026-11-07 16:20:41 +01:00
Sebastian Krzyszkowiak
605a94cf2b uilayer: Force ui viewports to be disabled under gamescope and Steam Big Picture (#232) 2026-11-07 12:33:46 +01:00
maj00r
088eeaa743 fix(editor): a scenery saved under another name keeps one file of turntable controls, not the old one beside its own (#251)
The editor writes the turntable controls on each save to <scenery>_obrotnice.scm and includes it. A copy of a
scenery, saved under another name, still included the file of the scenery it came from. Its save wrote a file of
its own and included it too, so each turntable had its events twice.

Now the save leaves out of the scenery the other files of turntable controls it includes, told by the line the
editor writes at their start. Their files stay on the disk, for the scenery they were made for. A file of
turntables made by hand, without that line, stays included.

Co-authored-by: maj00r <maj00r@host>
2026-11-07 12:32:02 +01:00
bcda4aaa42 Merge pull request #244 from MaSzyna-EU07/split/1-small-fixes
Split/1 small fixes
2026-11-07 11:19:45 +00:00
3f6a10f1fe Merge pull request #237 from MaSzyna-EU07/feat/track-uneven
Feat/track uneven
2026-11-07 11:18:49 +00:00
maj00r
a6778c9fe2 refactor: the Sonar remarks on the small fixes taken care of
- the log stops through a flag kept in a function, not a global one
- the messages are put together with std::format
- a vehicle dropped on a trainset in the launcher goes in once the
  trainset is drawn, so the vehicles are gone through with a plain loop
  again; the drop split into a new vehicle and a moved one
- the scenario that can't be opened is caught as the runtime error it
  is, a file name outside of the code page as the system error it is
- a track of an unknown type is left out through the pointer owning it
- the keyword of a track type written out by a function of its own
- the picked node forgotten with ranges::replace_if, the empty
  Forget_Node of the base renderer told why it is empty
- FileCased takes the path it works on
2026-11-06 19:54:54 +01:00
maj00r
b7ff8bab73 Merge pull request #236 from MaSzyna-EU07/fix/imgui-panels-no-auto-detach
fix(ui): keep panels in the main window when resizing them at the scr…
2026-11-04 01:08:30 +01:00
maj00r
ed0ff36a69 fix(app): a scenario failing as it loads goes back to the launcher too, the files of other cases looked for only off Windows
A scenario failing past its opening popped the loader mode and went on to pop it again and push the driver mode with the scenery half loaded; it goes back to the launcher as one that can't be opened. FileCased() goes through the folder only where the file system tells the case apart: on Windows a missing file was looked for in its whole folder for nothing, and a name outside the code page threw from the parser. The Discord thread sleeps a quarter of a second outside the driver mode, not to hold the closing up.
2026-11-03 21:53:52 +01:00
maj00r
e230d7b902 fix: a deleted model is no longer the picked node, an orthophoto tile isn't swept as a grey stub, the ED brake arrays freed
Deleting a model in the editor left the renderer holding it as the picked node, the next pick used freed memory: the renderer forgets it now (Forget_Node). A texture filled straight from memory (internalsrc:) can't be read again, released by the garbage sweep it came back grey; it stays now, and the units bound with the old name are rebound. The arrays of the ED brake force sharing are vectors, they leaked once per update. The scene hierarchy and the editor menu items go through the translation. Python caches ignored.
2026-11-03 21:45:58 +01:00
maj00r
e75d849679 fix(app): a scenery file named in another case than given is found, a scenario that can't be opened goes back to the launcher
The command line gives the name of the scenery lowercased, and the sceneries made on Windows name their files and includes in any case: on a case-sensitive filesystem they weren't found. FileCased() finds the file whose name differs only in case; the parser and the scenario loader use it. A scenario that can't be opened logs the reason and goes back to the launcher instead of ending with an exception.
2026-11-03 21:45:57 +01:00
maj00r
b34238f33f fix(app): the log written to the end of the closing, each step of it with its time; the Discord thread idle outside the driver mode
The log service stopped with applicationQuitOrder, so whatever was logged while the simulator closed was lost; it now runs until StopLogService() and writes out what came before. The closing logs each of its steps with the time since it began, to tell where a closing that hangs stops. Outside the driver mode the Discord thread spun a core in its loop, it now sleeps a second between the checks.
2026-11-03 21:45:57 +01:00
maj00r
a68c535be9 fix(track): the cab keeps the shake a track doesn't replace, and the editor redraws all that an edit changes
The side sway from the lateral acceleration now goes off only on a track with a sideways value, and the random jolts on one with a vertical or a sideways value. Dips at the rail joints and cross level alone replace neither. On a switch it goes by the path set and what its joined tracks allow.

An edit in the editor also rebuilds the other switches of a joined group with the tracks they meet, and the tracks of a rail run whose joints moved. Removing a track does the same. While dragging, the tracks further off wait until the drag ends.

The clatter of a bogie's axles was right: the followers stand at the pivots and the offsets are the places of the wheel sets. The misleading name goes.

Also what Sonar had left: a parameter as a span, a const method, a struct and its member apart, commented out code, and a function of the grading tool split, with the same output.
2026-11-01 20:12:33 +01:00
Sebastian Krzyszkowiak
823807201c Fix driver UI "mode windows" menu (#226)
Make the keyboard shortcuts match the actually used ones and check
the correct panels for is_open.
2026-11-01 13:40:57 +01:00
jerrrrycho
d995674bee fix(physics): automatic couplers don't catch above the safe collision… (#217)
* fix: automatic coupler crashes, horns, compressor start delay, radio and cab lever sounds

fix(cab): the line contactors lamp goes out with the pressure switch (eu07/maszyna#470)

The lamp of the open line contactors, which tells the start can be made, went out at 1 bar in the brake
cylinders, while the pressure switch cuts the start off only at 2 bar (or below 3.6 bar in the main pipe), so
with the brakes partly applied the start could be made with the lamp out. With a pressure switch fitted the lamp
now follows it; without one it goes out at 1 bar as before. The same holds for the lamp of the second unit, which
used to keep its last state between 1 bar and the cut-off. The condition is
TMoverParameters::LineContactorsLampLit().

Tests: tests/unit/test_line_contactors.cpp.

refactor: Sonar findings of PR 217

- horn_supply::pneumatic shadowed the pneumatic enumerator of the sound flags (cpp:S1117), it's horn_supply::air;
  the supplies read with using enum (S6177),
- CouplerForce(): the automatic coupling refused or caught without another level of nested ifs (S134), the
  same outcome: too fast, the coupling is locked; caught, coupled and locked; otherwise left to try again,
- CompressorCheck(): the power the compressor doesn't draw yet noted without a TODO (S1135),
- tests: the previous crash damage setting in an in-class initializer (S3230), the loops which stop early with
  break rather than in their conditions (S886), the stand-ins of test::pair in a std::vector rather than an array
  (S5945), units in the comments of the example without /* */ (S1103).

test: the cases the changes of the branch left without a test

- Cntrl.: BDelay1 to BDelay4 reach the brake delays (now through std::format), the default start types of the
  ground and converter overload relays of a multiple unit and of other vehicles (out of the nested conditionals),
- the compressor: fed from the low voltage it waits from the low voltage coming on; the pressure switch stops it
  above the cut-out pressure, without one the excess air is vented (the vented fraction rewritten),
- the horns: a vehicle without any air sounds as before, the threshold pressure, an electric horn on 110 V,
- the positions of a cab control below zero, and a control without position sounds.

refactor(cab): the kind of the train heating switch as a flag

Read once with the vehicle definition, rather than compared as text in each of the switch commands.

feat(cab): train heating switch springing back (MaSzyna-EU07/maszyna#85)

TrainHeating=[toggle]|impulse in the Switches: section of the vehicle definition makes the train heating switch
an unstable one: it goes up to switch the heating on, down to switch it off, and back to the middle once let go,
instead of staying where it was put. The position of such a switch isn't set from the heating state when the cab
is entered.

Tests: tests/unit/test_switches.cpp.

feat(sound): mirror sounds (MaSzyna-EU07/maszyna#84, eu07/maszyna#322)

The sound definitions of a vehicle (.mmd) take mirroropen: and mirrorclose:, played as a mirror sets off
unfolding or folding, and the locations section mirrors: <offset>... end, the offsets along the vehicle of the
mirror pairs, as for the doors (negative ahead of the centre), each pair at the sides of the vehicle. As the
animation, only the mirrors at the occupied cab sound. The start of a move is mirror_moves::motion in
vehicle/mirrors.h.

Tests: tests/unit/test_mirrors.cpp.

feat(cab): joint oil and fuel pump switch (MaSzyna-EU07/maszyna#81)

A cab control oilfuelpump_sw: switches both the oil and the fuel pump at once, with the new command
oilfuelpumptoggle (no key by default). As a two-state switch it turns both pumps off when both are on and both on
otherwise; as an impulse switch it's held to start them, like the separate pump switches. Its position follows
the pumps when the cab is entered.

refactor(editor): text fields filled without strncpy (MaSzyna-EU07/maszyna#112)

The last three strncpy calls Sonar flags (cpp:S5816), in the road and track editors, copy the text into the
ImGui field buffers with std::string::copy and terminate it right after; the text gets cut to the buffer as
before.

fix: automatic coupler crashes, horns, compressor start delay, radio and cab lever sounds

- automatic couplers don't catch above the safe collision speed (#86): with crash damage on they couple at a
  closing speed of 15 km/h at most, a faster hit locks the coupling until the vehicles part and the collision
  runs its course. On the way: CollisionVelocity() read a head-on 10 + 10 km/h as 14.1 km/h (quaternions turn
  by half the angle), it's now taken from the touching ends; CollisionDetect() took the other vehicle's speed
  the wrong way round for vehicles facing each other. Head-on hits now derail sooner, at the intended limit,
- a single horn lever can't sound both tones at once (eu07/maszyna#392),
- a skid ends once the vehicle stops on locked wheels (eu07/maszyna#479),
- electronic destination signs are blanked at the end of the timetable (eu07/maszyna#446),
- horns need the air or the voltage to sound (eu07/maszyna#290, #76): HornType=[Pneumatic]|Electric and
  Horn3Type in Cntrl.; a pneumatic horn needs 0.2 MPa in the main reservoir (or the main pipe without one),
  an electric one the low voltage; the lever stays where it was put,
- compressor start delay (#67): CompressorStartDelay [s] in Brake., counted from the compressor getting its
  power, after the engine starts say; none by default,
- the channel and volume buttons of a radio without power do nothing (eu07/maszyna#429); the power check of
  the radio is TMoverParameters::RadioPowered(),
- a lever moved smoothly sounds the positions it passes (#83).

Sonar findings in the functions changed: nesting, nested conditionals, commented-out code, std::format,
references to const. Unit tests: test::pair for two vehicles running into each other, test_couplers,
test_horn, test_wheelslip, test_compressor, test_radio, test_gauge_positions.

* fix: review findings of PR 217

- the pressures of the mover are in bar (MinCP=7.0 in the vehicle definitions), so the 0.2 the pneumatic horns
  needed was 0.2 bar; it's 2 bar now, and the tests take their pressures in bar too,
- the lamps of the ground relay and of the motor overload relays follow the pressure switch as the line
  contactors one does, TMoverParameters::MainCircuitLampsEnabled(), so the lamps of one cab go out together,
- the joint oil and fuel pump switch leaves out a pump started automatically, which ignores the switching:
  with one, it couldn't turn the other off, and showed off with both running (OilFuelPumpsEnabled()),
- the kind of the train heating switch comes from the occupied vehicle on entering the cab too, as in the
  switch commands; in a multiple unit these are different vehicles,
- CouplerForce() put the force taken back by the other vehicle on its far coupler when the vehicles faced each
  other (Couplers[1 - End] rather than the coupler it's coupled with),
- the velocity difference along the coupler axis computed once, CouplerVelocityDifference(), for CouplerForce()
  and CollisionVelocity(),
- the attachcoupler flag added without the SetFlag() call with 0, the index of the mirror moves noted as by side.

Tests: the coupler force of vehicles facing each other, the joint switch with an automatic pump, the lamps of
the main circuit, the horn and compressor pressures in bar.

* fix: coupler force of vehicles facing each other taken back along the other vehicle's axis

CForce runs along the axis of its own vehicle, so the force the other vehicle takes back on its coupler of the pair
is turned around once more when the vehicles face each other (-CF * DirPatch()), as that vehicle computes it itself.

Tests: the coupler forces of the pair facing each other and facing the same way.
2026-11-01 09:12:36 +01:00
maj00r
9364b03e1a fix(track): what Sonar reports on the unevenness
The blockers are in the EP brake of a vehicle, older than this work: its
arrays made with new[] are vectors now, which also removes the deletes.

- The caches of the unevenness of a track are no longer mutable: what fills
  them isn't const any more.
- TSegment gets back to RaPositionGet in one piece, with what the path has
  to do with the others in one struct.
- Less nesting in the group of switches and in the fields of the editor,
  nested conditionals written out, and the attribute is read from a
  string_view. A test no longer counts in floating point.

No change in behaviour.
2026-10-31 20:17:47 +01:00
maj00r
2b8d7d7cf3 refactor(track): what goes with unevenness in one place, readable
The rules stay in TrackUneven.h with no dependence on the engine, as they can
be tested. Their use on the tracks moves out of Track.cpp to TrackUneven.cpp,
split into steps: the group of switches, the values of its paths, the meeting
of two ends. One set of helpers for the paths of a track takes the place of
the same conditions written out in several places, and the value at the ends
of a switch is worked out in one place. The clatter of an axle leaves the long
Update of a vehicle. The fields in the editor say themselves when they show.

No change in behaviour.
2026-10-31 19:05:56 +01:00
maj00r
ec2d226688 fix(editor): the uneven attribute is written in the layout of the entry it goes into
Its numbers go by position, and the positions differ between a plain track
and a switch. A switch made in the style of a plain track wrote them in the
layout of the plain one, so they were read back as other values: the up and
down part as the value of the path p3-p4, and so on.

The values are now one struct with pure functions to read, write and resolve
them, which replaces eight fields and which the unit tests can reach.
2026-10-31 18:58:20 +01:00
maj00r
02abc7df0a feat(track): all of the unevenness in one attribute, with values for each path of a switch
Usage, before endtrack, in mm, 0-100, separated by semicolons with no
spaces:

  a plain track:  uneven value;up;side;cross;dip
  a switch:       uneven p1-p2;p3-p4;up p1-p2;side p1-p2;up p3-p4;side p3-p4

  uneven 18           the value alone
  uneven 18;;;25;6    with its own cross level and dip at the joints
  uneven 0;;30        sideways alone
  uneven 14;35        a switch, 14 on the straight path, 35 on the diverging

What's left out takes the first number, the dip goes by the up and down
one, and a path p3-p4 with no numbers of its own takes those of p1-p2.
This replaces unevenup, unevenside, unevencross and jointdip. The fields
in the editor stay, track panel, section Ride and sound.

At the end they share, the paths of a switch keep to the lower value, and
each comes to its own value along the way. Joined switches take the lowest
of the same path, a path joined to another path the lower of the two, and
a plain track comes to the value of the path it meets. See
track_uneven_doc.md.
2026-10-31 18:33:35 +01:00
maj00r
e5266e1e6b feat(track): track unevenness declared in the track entry
Usage, optional attributes of a track entry, before endtrack:

  uneven <mm>       peak deviation of a rail on a 10 m chord, 0-100; normal and switch
  unevenup <mm>     up and down alone, instead of uneven; normal and switch
  unevenside <mm>   sideways alone, instead of uneven; normal and switch
  unevencross <mm>  cross level alone, instead of uneven; normal only
  jointdip <mm>     depth of the dip at rail joints, every sound distance; normal only,
                    default 0.4 of the up and down component

Values of uneven follow PLK Id-14 appendix 1: 6 mm is the limit at 160 km/h, 10 at 120,
18 at 80, 35 at 40; half of it for a track in a good state. Example:

  uneven 18
  unevencross 25
  jointdip 6
  endtrack

The same fields are in the editor, track panel, section Ride and sound. See
track_uneven_doc.md.

The unevenness is a displacement field over the plan of the scenery, with no random
numbers: one place strays the same every time, and joined tracks, the paths of a switch,
sleepermodel and axles at one place move together. Tracks meet at a joint at the lower of
their values. Joined switches take the lowest of theirs, the tracks joined to them come to
it, and a track with none of a component keeps the switch to its design in it. The clatter
comes from the dipping joints. Curve radius and grade for the physics stay as designed.

Tracks with it get geometry every 2 m on straights: 4x the vertices on Baltyk with all
tracks flagged, 0.2 ms more per frame on td.scn.
2026-10-31 15:47:10 +01:00
maj00r
5fa03304ef fix(ui): keep panels in the main window when resizing them at the screen edge
Pulling a panel's grip into the corner of the screen pushed it a couple of
pixels past the main window, and ImGui gave it an OS window of its own. Now
a panel only comes out when dragged with the cursor outside the main window.

A panel that stays in the main window needs its grips in reach, so a resize
stops at the edge, and a panel left past the edge or too big for the window
is moved back and shrunk to fit.

This is a new io.ConfigViewportsNoAutoDetach option in our copy of ImGui, so
it has to be carried over when ImGui gets updated. ImGui itself has no option
for this, as of 1.93.1.
2026-10-31 01:47:40 +01:00
maj00r
04af7d5758 fix(python): stop python workers before destroying windows on exit 2026-10-21 21:23:29 +02:00
maj00r
746399c771 fix(ui): panels dragged out of the simulator window get no taskbar or alt+tab entries of their own and are owned by the main window on windows (always over it, minimized with it); ctrl+tab of imgui (window switcher, on even without keyboard navigation since 1.91) switched off, it took the key of the boost of the train 2026-10-11 23:00:21 +02:00
combolek
3ed864812f Fix line endings (#211)
There where some extra CRs introduced in a recent commit.
2026-10-11 20:42:09 +02:00
jerrrrycho
8125166690 Fix/sonar reliability security (#205)
* fix(sonar): addressing reliability/security issues

* fix(sonar): addressing reliability/security issues

* fix(sonar): addressing reliability/security issues

* fix(sonar): addressing last reliability/security issues

* fix(sonar): addressing last reliability/security issues

* fix(sonar): addressing last reliability/security issues

* refactor(sonar): imgui update

* refactor(sonar): hasChar revert
2026-10-11 15:31:45 +02:00
jerrrrycho
6263fe539b Fix/sonar maintainability rebased (#209)
* refactor(sonar): use override instead of redundant virtual (cpp:S3471)

* refactor(sonar): remove empty statements, one declaration per statement (cpp:S1116, cpp:S1659)

* refactor(sonar): drop redundant lambda return types (cpp:S3574)

* refactor(sonar): prefer using over typedef (cpp:S5416)

* refactor(sonar): use =default for trivial special members (cpp:S3490)

* refactor(sonar): use nullptr literal (cpp:S4962)

* refactor(sonar): remove redundant access specifiers (cpp:S3539)

* refactor(sonar): remove redundant casts (cpp:S1905)

* refactor(sonar): use std::scoped_lock instead of std::lock_guard (cpp:S5997)

* refactor(sonar): construct elements in place with emplace_back (cpp:S6003)

* refactor(sonar): make narrowing conversions explicit (cpp:S5276)

* refactor(sonar): mark unused parameters (cpp:S1172)

Unused names are commented out; parameters used only on some platforms
are marked [[maybe_unused]].

* refactor(sonar): declare non-mutating member functions const (cpp:S5817)

Virtual functions are left untouched so overrides keep matching.

* refactor(sonar): merge collapsible nested if statements (cpp:S1066)

Comments that preceded the inner if are kept above the merged statement.

* refactor(sonar): scope single-use variables in if init-statements (cpp:S6004)

* fix(sonar): give polymorphic base classes virtual destructors (cpp:S1235)

* refactor(sonar): use try_emplace for map insertion (cpp:S6030)

* refactor(sonar): use auto where the type is spelled out on the right (cpp:S5827)

* refactor(sonar): remove redundant parentheses (cpp:S1110)

* refactor(sonar): document intentionally empty functions (cpp:S1186)

* refactor(sonar): remove dead stores (cpp:S1854)

Side effects (e.g. LISTLINE++) are kept.

* refactor(sonar): remove unused variables, captures and private fields (cpp:S1481, cpp:S1068)

* refactor(sonar): use structured bindings for pair access (cpp:S6005)

* refactor(sonar): use range-based loops and ranges algorithms (cpp:S5566)

* refactor(sonar): split chained assignments (cpp:S1121)

* refactor(sonar): add default cases to switch statements (cpp:S3562)

* refactor(sonar): use std::size for array lengths (cpp:S7127)

* refactor(sonar): use std::ranges algorithms and std::erase(_if) (cpp:S6197)

* refactor(sonar): access static members through the class (cpp:S2209)

* refactor(sonar): add braces around nested if-else (cpp:S5261)

* refactor(sonar): avoid side effects inside typeid operands (cpp:S5279)

* refactor(sonar): drop top-level const from by-value return types (cpp:S5951)

* refactor(sonar): declare variables separately from their type definitions (cpp:S3646)

* refactor(sonar): declare loop counters in the for statement (cpp:S5955)

* refactor(sonar): rely on class template argument deduction (cpp:S6012)

* refactor(sonar): inherit base constructors instead of forwarding them (cpp:S5952)

* refactor(sonar): rename locals that shadow outer declarations (cpp:S1117)

Only local variables and definition parameters are renamed; shadowing
struct fields and enumerators in headers are left for a separate change.

* refactor(sonar): reduce enum qualification with using enum (cpp:S6177)

Applied only where no unqualified name in the block could be captured by
the introduced enumerators.

* refactor(sonar): mark converting constructors explicit (cpp:S1709)

sound_source and the gl::object conversion operators stay implicit; they
are relied upon by copy-list-initialisation and GL calls.

* refactor(sonar): initialise members in initialiser lists or declarations (cpp:S3230)

* refactor(sonar): make read-only local references and pointers const (cpp:S5350)

* refactor(sonar): take read-only pointer and reference parameters as const (cpp:S995)

TTrain command handlers keep their signature, it is fixed by the handler table.

* refactor(sonar): pass expensive-to-copy parameters by const reference (cpp:S1238)

Signatures declared in files touched by PR 206 (Driver.h, EU07.cpp) and
virtual overrides are left unchanged.

* refactor(sonar): use transparent comparators for string-keyed maps (cpp:S6045)

std::unordered_map instances and map types exposed in headers used by
other modules are left as they are.

* fix(sonar): do not use UI text as a printf format string (cpp:S5281)

Text lines containing '%' would otherwise be interpreted as format
specifiers by ImGui::TextColored.

* refactor(sonar): use std::bind_front for callbacks (cpp:S5995)

* refactor(sonar): drop const_cast on string literals passed to the Python C API (cpp:S859)

* refactor(sonar): take read-only string parameters as std::string_view (cpp:S6009)

The Windows messaging helpers keep std::string, they need c_str().

* refactor(sonar): make read-only global tables const (cpp:S5421)

Mutable global state (flags, timers, singletons) is left as is.

* refactor(sonar): replace single-case switch statements with if (cpp:S1301)

* refactor(sonar): format UUIDs and log timestamps with std::format (cpp:S6494, cpp:S5945)

* refactor(sonar): use std::array for local fixed-size arrays (cpp:S5945)

Arrays that are class members or part of headers are left unchanged.

* fix(sonar): test the bearing damage bit, not bit 0 (cpp:S872)

'DamageFlag & dtrain_bearing > 0' parsed as 'DamageFlag & (dtrain_bearing > 0)',
i.e. DamageFlag & 1, so bearing friction reacted to the wrong damage flag.

* refactor(sonar): assorted small fixes

- empty() instead of size() comparisons (cpp:S1155)
- member initialisers in declaration order (cpp:S3229)
- no redundant c_str() when a std::string is accepted (cpp:S7121)
- contains() on associative containers (cpp:S6171)
- const reference instead of a copying range-for (cpp:S6032)
- starts_with() for prefix checks (cpp:S6178)
- no std::move on temporaries (cpp:S5274)
- has_value() on std::optional (cpp:S7172)
- bool literals for bool members (cpp:S5820)
- inline only on the definition (cpp:S3548)
- explicit base access specifier (cpp:S5965)
- explicit element access instead of dereferencing an array (cpp:S7116)
- rely on the C++20 rewritten operator!= (cpp:S6186)

* refactor(sonar): more small fixes

- std::format instead of iostream manipulators in to_string/to_hex_str (cpp:S6495);
  output verified identical, negative hex values keep the unsigned form
- remove an empty compound statement (cpp:S108)
- drop an always-false unsigned < 0 branch (cpp:S1768)
- explicit lambda capture (cpp:S3608)
- std::make_unique (cpp:S5950)

* chore: strip stray carriage returns left in TTrain constructor comments

* fix: keep quantize() in its own value type

The S5276 change cast the result of the generic quantize<Type_>() to
unsigned int, which truncated float/double callers (shadow angle slider,
editor rotation snapping, AI speed control steps) and broke negative values.

* fix(sonar): use integer loop counters in segment sampling (cpp:S2193)

* refactor(sonar): clean up issues reported on PR 209

- remove locals left unused after the dead-store cleanup (cpp:S1481, cpp:S1854)
- unnamed unused parameter (cpp:S1172)
- single-use declarations moved into if init-statements (cpp:S6004)
- auto for casted initialisers (cpp:S5827)
- const references and pointers where nothing is modified (cpp:S5350)
- rename structured bindings shadowed by inner declarations (cpp:S1117)
2026-10-11 13:22:33 +02:00