@@ -13,6 +13,7 @@ import cz.vutbr.fit.interlockSim.context.SimulationContext.ReportType
1313import cz.vutbr.fit.interlockSim.context.SimulationEnvironment
1414import cz.vutbr.fit.interlockSim.context.navigation.PathResult
1515import cz.vutbr.fit.interlockSim.context.navigation.TrainNavigationService
16+ import cz.vutbr.fit.interlockSim.exceptions.SimulationException
1617import cz.vutbr.fit.interlockSim.exceptions.requireSimulation
1718import cz.vutbr.fit.interlockSim.exceptions.requireSimulationNotNull
1819import cz.vutbr.fit.interlockSim.objects.cells.DynamicInOut
@@ -804,6 +805,7 @@ class Train :
804805 * Create train
805806 * @param env The simulation environment
806807 * @param timetable Train timetable
808+ * @throws SimulationException if train length exceeds track distance between InOuts
807809 */
808810 constructor (env: SimulationEnvironment ? , timetable: Timetable ? ) {
809811 this .env = requireSimulationNotNull(env) { " env must not be null" }
@@ -815,8 +817,93 @@ class Train :
815817 val inName = validatedTimetable.getIn().name
816818 val outName = validatedTimetable.getOut().name
817819 trainNavService = env.getTrainNavigationService()
820+
821+ // Issue #60: Validate train length against track distance between InOuts
822+ validateTrainLength(env, validatedTimetable, this .length)
823+
818824 logger.debug { " Train $number created: from $inName to $outName , length $length " }
819825 }
826+
827+ /* *
828+ * Validates that train length does not exceed the shortest track distance between InOuts.
829+ *
830+ * **Issue #60: Track Length Validation**
831+ * - Calculates shortest path distance between origin and destination InOuts
832+ * - Ensures train can physically fit on the track
833+ * - Prevents runtime simulation errors from track being too short
834+ *
835+ * **Implementation:**
836+ * - Uses TopologyNavigator to find all possible paths
837+ * - Calculates total track distance for each path
838+ * - Validates train length against shortest available path
839+ * - Gracefully handles test mocks by catching exceptions
840+ *
841+ * @param env Simulation environment providing topology navigator
842+ * @param timetable Train timetable with origin and destination InOuts
843+ * @param trainLength Length of the train in meters
844+ * @throws SimulationException if train length exceeds shortest track distance
845+ * @since 2026-02-06 (Issue #60)
846+ */
847+ private fun validateTrainLength (
848+ env : SimulationEnvironment ,
849+ timetable : Timetable ,
850+ trainLength : Double
851+ ) {
852+ val inOut = timetable.getIn()
853+ val outOut = timetable.getOut()
854+
855+ // Skip validation if either InOut is not registered in this context (e.g. mock objects in tests)
856+ val contextInOuts = env.getInOuts()
857+ if (! contextInOuts.contains(inOut) || ! contextInOuts.contains(outOut)) {
858+ logger.trace { " Train length validation skipped: InOuts not registered in context (likely test mock)" }
859+ return
860+ }
861+
862+ try {
863+ val topologyNavigator = env.getTopologyNavigator()
864+
865+ // Find all topologically possible paths between InOuts
866+ val paths = topologyNavigator.findAllTopologicalPaths(
867+ start = inOut,
868+ target = outOut,
869+ maxDepth = 100
870+ )
871+
872+ requireSimulation(paths.isNotEmpty()) {
873+ " Train length validation failed: No route exists between " +
874+ " InOut '${inOut.name} ' and InOut '${outOut.name} '. " +
875+ " Railway network must provide at least one path between entry and exit points."
876+ }
877+
878+ // Calculate distance for each path and find the shortest using idiomatic Kotlin
879+ val shortestPathDistance = paths.minOf { path ->
880+ path.sumOf { section -> section.length() }
881+ }
882+
883+ // Validate train length against shortest path
884+ requireSimulation(trainLength <= shortestPathDistance) {
885+ " Train length ($trainLength m) exceeds track distance ($shortestPathDistance m) " +
886+ " between InOut '${inOut.name} ' and InOut '${outOut.name} '. " +
887+ " Minimum track length required: $trainLength m, available: $shortestPathDistance m. " +
888+ " Reduce train length or increase track distance to resolve this issue."
889+ }
890+
891+ logger.debug {
892+ " Train length validation passed: train=$trainLength m, " +
893+ " shortest path=$shortestPathDistance m (${inOut.name} → ${outOut.name} )"
894+ }
895+ } catch (e: SimulationException ) {
896+ // Rethrow validation failures (train too long, no route, etc.)
897+ throw e
898+ } catch (e: Exception ) {
899+ // Any other exception indicates an unexpected failure in topology/navigation logic.
900+ // Log at WARN and rethrow so that validation is not silently bypassed.
901+ logger.warn(e) {
902+ " Train length validation failed due to unexpected error; simulation will be aborted: ${e.message} "
903+ }
904+ throw e
905+ }
906+ }
820907
821908 override fun distanceToSemaphore (): Double =
822909 if (pathToSemaphore == null ) 0.0 else pathToSemaphore!! .length() - front.getPosition()
0 commit comments