TDynamicObject::Update() reads ctOwner->Controlling() every frame, but ctOwner is
only ever refreshed by CheckVehicles() and cleared only by Dettach(). Both
TController::MoveTo() and ~TDynamicObject() delete a controller while the rest of
the consist still points at it, so the vehicles keep dereferencing freed memory
until some other driver happens to claim them.
Seen as a segfault in DynObj.cpp on ctOwner->Controlling()->MainCtrlPosNo after
changing cabs through an EN57 with an AI driver in the leading unit: ctOwner was
a live-looking pointer into a freed TController, its mvControlling long gone.
~TController() now clears ctOwner on every vehicle still pointing at it. nullptr
is what the readers already expect - they fall back on Mechanik or skip the block
- and the next CheckVehicles() re-establishes ownership.
A driver without movePrimary no longer just idles. UpdateSecondary() cuts
BrakeValveActive so a secondary AI stops fighting the primary over the brake
pipe, and - as long as no driver in the consist has iEngineActive - keeps
scanning ahead of CabOccupied, so it can take movePrimary and start the engine
once a signal permits.
scan_route() orients by CabOccupied when DirActive is 0, and
ClaimConsistPrimary() clears movePrimary on every other driver in the consist,
then calls TableClear() and CheckVehicles(), so sSpeedTable is rebuilt for the
new front and ctOwner follows it.
- walk the consist with FirstFind()/Neighbour() instead of Next()/Prev(), which
stopped at the starting vehicle and mishandled reversed members;
- ReassignConsistPrimary() picks a single driver after any coupling change,
both from TDynamicObject::couple() and from the AI path;
- IsOccupiedByAnotherConsist() decides membership with BelongsToConsist() and
takes a signed distance, so a consist left behind no longer cuts the scan
short. DistanceToAnotherConsist() hands back the gap it already computes, so
OtherConsistAtSignal() counts a foreign vehicle only while it stands closer
than the signal itself, instead of comparing track starts;
- TSpeedPos::Set() marks an entry with spNotOurShuntSignal when a
spShuntSemaphor was already permitting as the entry was added. A shunting
signal does not address its permission to a particular consist, so a dormant
driver does not wake up on a permission it did not see light up; the flag
drops once the signal shows stop, so its own turn is never blocked. spSemaphor
is excluded: a semaphore sets a route for us and permits us even when found
already lit. The check reads SemPermitIndex - the entry that produced the
permitting command - because SemNextIndex can point at an earlier signal.
The flag is also stamped whenever the permission turns out to belong to a
consist standing in front of us - both where TableUpdateEvent() already knows
the signal is for the vehicle ahead, and in check_route_ahead() - so the
addressee pulling away does not hand the still-lit signal over to whoever
waited behind it;
- the scantable debug panel lists the flags by name (spNotOurShuntSignal shows
as not-ours) and folds the nearest vehicle ahead into the table by its own
distance.
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.
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.
MS DXC on Windows (at least IDxcUtils::BuildArguments) does not support forward slashes in UNC paths. This change feeds it native format with backslashes.
This makes it possible to build Linux and Windows binaries from a single source on a network share.
This is a regression introduced in 7525b54f (merged via df5a8a89), GLM_FORCE_DEFAULT_ALIGNED_GENTYPES causes visible screen corruption, particularly in the smoke from chimneys or vehicles.