A vehicle left alone in a consist was starting its engine on its own: the
Wait_for_orders hack in handle_engine() takes a live 24V battery as a human
switching the vehicle on, and every vehicle a scenery starts with has one. The
guard for that was in master, commented out. With the engine running the driver
counted as active again, so a permitting signal went on to hand it Obey_train -
measured on td.scn: Prepare_engine 12 ms after the coupler was released, with
the signal still at stop.
A command cell keeps its text once the command has been delivered, which left
the point at zero velocity and hid the rest of the table behind it. The point is
now marked as sent and stops braking for it.
Also drops the parameter CommandClaimsPrimary() no longer reads, and puts the
brake release blocks in determine_consist_state() back at their original
indentation - the scope never changed, but the diff read as if it had.
DirectionInitial() and PrepareDirection() go back to EU07_AI_NOMOVEMENT,
so a standing consist is left alone at load as it was. The thresholds the
brake charging cut-off works with get names instead of bare numbers, as
does the overcharge margin in the pipe pressure update.
A dormant driver scans, but bailed out before anything could act on the
table. Signals stay out of it - only cm_Command is dispatched, so a memory
cell put in front of a parked consist can hand it Prepare_engine.
A permitting signal no longer wakes a dormant AI: the synthetic departure
command, the shunt-signal ownership tag and the secondary scan that fed
them are gone, and cm_SetVelocity / cm_ShuntVelocity go back to plain
PutCommand with the pre-existing order mask. A secondary now only settles
its own vehicle. Claiming the lead stays for explicit orders.
The rest is SonarCloud cleanup on the branch: if-init statements, merged
and flattened conditionals, split chained assignments, ranges::any_of,
pointer-to-const parameters, std::array for the flag name table and no
more shadowed names.
The check added to TableTraceRoute() decided consist membership through
BelongsToConsist(), which falls back on TDynamicObject::is_connected() and its
Next()/Prev() walk - the same pair that mishandles reversed members. When it
failed to recognise one of our own cars the scan ended on our own consist, no
signal ever reached the speed table, and the driver stood at a clear semaphore
for good. Seen on a shunting locomotive that had just uncoupled, with ctOwner
still cleared.
The signal-permission code does not need it: OtherConsistAtSignal() looks the
route over on its own and DistanceToAnotherConsist() measures to the vehicle
rather than to the end of a track. Both stay.
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.
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.
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.
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.
- 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.