@@ -238,9 +238,20 @@ class Train :
238238 requireSimulation(position.isStarted() && pv.isStarted()) {
239239 " Position and velocity integration must be active"
240240 }
241- waitUntil {
242- // dtmin - horni odhad zmeny pri poslednim kroku numericke metody behem dobrzdovani k uzlu
243- position.state + dtMin >= nextLength
241+ // State event (zero-crossing): resume exactly when the front reaches the section
242+ // boundary. Root-finding locates the crossing time *within* one integration step,
243+ // so `dtMax` no longer has to be tiny to keep the block-boundary overshoot
244+ // negligible (Issue #750). The threshold mirrors the original
245+ // `waitUntil { position.state + dtMin >= nextLength }`: the boundary counts as
246+ // reached once the front is within `dtMin` of it. Keeping that `dtMin` slack is
247+ // essential — a train decelerating to a STOP semaphore located *at* the boundary
248+ // halts a few nanometres short (v→0 as distance→0), so an exact `nextLength -
249+ // position` guard would asymptote to a tiny positive value and never cross zero.
250+ // The `if` preserves the old "return immediately when already satisfied" semantics
251+ // (`waitCrossing` otherwise waits for a *future* sign change).
252+ val boundaryThreshold = nextLength - dtMin
253+ if (position.state < boundaryThreshold) {
254+ waitCrossing { boundaryThreshold - position.state }
244255 }
245256
246257 position.state - = nextLength
@@ -1051,7 +1062,16 @@ class Train :
10511062
10521063 Process .activate(front)
10531064
1054- waitUntil { front.getTotalDistance() >= getLength() }
1065+ // State event (zero-crossing): start the tail exactly when the front has advanced one
1066+ // train-length, located by root-finding within the integration step (Issue #750). The
1067+ // threshold carries the same `dtMin` slack as the block-boundary crossing above so that a
1068+ // front decelerating to a stop within one train-length of entry still triggers tail entry
1069+ // instead of asymptoting a few nanometres short and hanging. The `if` preserves
1070+ // `waitUntil`'s immediate-return-when-satisfied semantics for degenerate short-length cases.
1071+ val tailEntryThreshold = getLength() - dtMin
1072+ if (front.getTotalDistance() < tailEntryThreshold) {
1073+ waitCrossing { tailEntryThreshold - front.getTotalDistance() }
1074+ }
10551075 Process .activate(tail)
10561076
10571077 out ()
0 commit comments