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.
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.
- A path of no length gave a joint with a vector across of NaN to the path
joined to it, on any scenery, with unevenness or not. It now has no
vector across, and so makes no joint.
- Axles no longer stray along the track. It moved vehicles against each
other, changing the distance their couplers go by.
- The bend under an axle goes straight on past the ends of a path: the
polynomial ran off on a short curved one, with a kick in the cab shake.
- A track left out of its rail run, where tracks are joined one way only,
no longer walks the whole run again on every call.
- The clatter of an axle goes by the outer axle on its side of the vehicle,
and not always by the same one, as the two can be on different tracks.
- A vertex works out the amounts once, and the foot of the ballast not at all.
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.
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.
Ends less than 20 cm apart meet in the middle with a common vector
across, which closes the cracks from coordinates that don't match in the
data, on tracks with no unevenness as well. At such a joint a switch takes
the cant of the plain track, and alike ones meet halfway: the slips on
Baltyk had steps of 0.2 degrees. The physics goes by the same cant.
Fewer short waves: at uneven 18 the humps over 12.6 mm on the 10 m chord
go from 28 to 38 per km, with the peak still at 18 mm. Waves of 47-83 m
join them. They hardly count on that chord, the body rises and falls on
them.
The documentation tells how far unevenness goes before a pantograph
can't keep up. A wire rising fast enough trips the main breaker on a
straight track too: it does at tor0290 on Baltyk, over about 46 km/h.
Tracks laid towards each other settled to opposite sides: a step of cant
of twice the value at their joint, in the geometry and under the axles.
It's now a tilt of the plan, which reads the same from either track.
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.
* 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)
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.
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>
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.
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.
In TSegment there were two different formulas for a derivative. Most places called GetFirstDerivative() that used the direct formula from the curve points. However RaPositionGet() had its own calculation based on pre-computed cubic polynomial coefficients. This latter method is faster and simpler, so change GetFirstDerivative() to use it and change RaPositionGet() to just call GetFirstDerivative().
TSegment::GetTFromS() is a significant hotspot in the non-rendering part of the state_manager update (about 13%). This change introduces a FastGetTFromS() version that interpolates from precomputed values instead of doing the whole calculation on every call. Computing position from track distance is now done in constant time. This reduces the contribution to about 0.3%.