A discrete-event/continuous combined simulation system for railway interlocking with a graphical track editor and XML-based configuration.
BSc Thesis Project (2006/2007) Brno University of Technology Faculty of Information Technology Author: Bedrich Hovorka
InterlockSim is a Kotlin-based railway interlocking simulator that combines:
- Graphical Editor - Design railway track layouts with switches, semaphores, and entry/exit points
- Discrete Event Simulation - Simulate train movements and interlocking logic using the kDisco framework
- Continuous Simulation - Model continuous train positions and speeds
- XML Configuration - Define and save railway networks in a structured XML format
- Process-Oriented Modeling - Built-in examples demonstrate shunting yard operations
The simulator uses a combined discrete-continuous simulation approach powered by the kDisco library (Keld Helsgaun, Roskilde University).
- Interactive track editor with grid-based layout
- Animated GUI simulation - Real-time train movement visualization with event logging (30 FPS)
- XML schema-validated railway network definitions
- Discrete event simulation engine (kDisco-based)
- Built-in examples including shunting loop scenarios
- Swing GUI for visualization and editing
- Assertion-based validation for simulation integrity
- Dynamic state visualization - Track coloring (FREE/RESERVED/OCCUPIED), signal states, switch positions
- Docker and Docker Compose
- X11 Server (for GUI display)
- Linux: Usually already running
- macOS: Install XQuartz
- Windows: Install VcXsrv or Xming
- Java: JDK 21 or later (Java 21 LTS minimum)
- Build Tool: Gradle (wrapper included)
- Dependencies: Automatically managed via Gradle (versions in
gradle.properties)cz.ksimulantenbande.kdisco:kdisco-core0.6.1-SNAPSHOT (from GitHub Packages, ormavenLocal())- Koin 3.5.6 - dependency injection
- Koog 1.1.1 - LLM agent framework, used by
:dispatcher-agent - JUnit 5.11.4 (from Maven Central)
- assertk 0.28.1 - assertions
- MockK 1.13.14 - Kotlin-native mocking for sealed classes
- Burst 2.12.2 and Mokkery 3.3.0 - multiplatform test parameterization and mocking
- LaTeX, gnuplot, make, wmf2eps, sed
Dockerization: 2025 - Complete containerized build and runtime environment with no host dependencies.
The build downloads the kDisco dependency from GitHub Packages, which requires
authentication. Put your credentials in .env (copy .env.example) and export
them before building — docker compose auto-loads .env only for ${VAR}
interpolation, while the BuildKit secret that carries the token reads the
process environment. A non-exported .env produces an empty token and a 401:
set -a; source .env; set +a # export GITHUB_ACTOR / GITHUB_TOKENUse a Personal Access Token with the read:packages scope (see .env.example).
# Build Docker images (after exporting credentials, see above)
docker compose build
# Run graphical editor (X11 forwarding)
docker compose up app
# Run simulation example
docker compose run app java -jar interlockSim.jar example shuntingLoop 60
# Build thesis PDF
docker compose up text
# PDF available in artifacts/text/bakalarka.pdfIf you encounter Can't connect to X11 window server:
# Allow Docker X11 access
xhost +local:docker
docker compose up app
# When done, revoke access
xhost -local:dockerFor more details, see the Docker section below or CLAUDE.md.
The default docker compose run app command opens the Swing editor GUI and requires an X11
server on the Windows host. Docker containers cannot use the Windows display directly, so a
separate X server is needed.
-
Install VcXsrv Download and install from https://sourceforge.net/projects/vcxsrv/
-
Launch XLaunch (included with VcXsrv) with these settings:
- Display settings: Multiple windows, Display number
0 - Client startup: Start no client
- Extra settings: check Disable access control
- Click Finish
- Display settings: Multiple windows, Display number
-
Allow VcXsrv through Windows Firewall Windows will prompt on first launch — click Allow access for both private and public networks. Without this, the Docker container connection is silently dropped.
-
Run the app
docker compose run app
The editor GUI will appear in the VcXsrv window.
The container sets DISPLAY=host.docker.internal:0 when $DISPLAY is not set in the
environment. This routes the X11 connection from the Linux container over TCP to port 6000 on
the Windows host, where VcXsrv is listening. The Unix socket path (/tmp/.X11-unix) used on
Linux does not exist on Windows.
# Clean and build (compiles main + tests, runs tests, creates JAR)
./gradlew clean build
# Run simulation (shunting loop example)
./gradlew runSim
# Run graphical editor
./gradlew runEditor
# Generate JavaDoc
./gradlew javadoc| Task | Description |
|---|---|
./gradlew build |
Compile all sources, run tests, create JAR (build fails if tests fail) |
./gradlew test |
Run unit tests only |
./gradlew integrationTest |
Run integration tests |
./gradlew heavyTest |
Run high-repetition stress tests. Manual only, never in CI |
./gradlew jmh |
Run JMH performance benchmarks (see desktop-ui/src/jmh/kotlin/README.md) |
./gradlew detekt |
Static analysis |
./gradlew ktlintCheck / ktlintFormat |
Check or apply Kotlin formatting |
./gradlew jacocoTestReport |
Coverage report |
./gradlew clean |
Remove build artifacts |
./gradlew shadowJar |
Create uber JAR file with all dependencies |
./gradlew runSim |
Run pre-configured shunting loop simulation |
./gradlew runEditor |
Launch graphical editor |
./gradlew runExample |
Run custom example with parameters |
./gradlew runExampleGui |
Run an example in the animated GUI |
./gradlew runExampleAIGui |
Run the LLM-dispatcher example in the animated GUI (needs Ollama) |
./gradlew runSimFromXml |
Run a simulation from an XML file |
./gradlew javadoc |
Generate JavaDoc documentation |
Note: Dependencies are automatically downloaded during build via Gradle. On Windows, use gradlew.bat instead of ./gradlew.
Open the track editor to design railway layouts:
./gradlew runEditorOr manually (after building):
java -jar desktop-ui/build/libs/interlockSim.jar edit [xmlFile]The graphical track editor showing a simple shunting loop layout with entry/exit points and rail switches.
Run a simulation from an XML configuration file:
java -jar desktop-ui/build/libs/interlockSim.jar sim [xmlFile]Run pre-configured simulation scenarios:
# List all available examples
java -jar desktop-ui/build/libs/interlockSim.jar example
# Run shunting loop example for 1024 time units
java -jar desktop-ui/build/libs/interlockSim.jar example shuntingLoop 1024Quick example:
# Build and run shunting yard simulation (1024 seconds model time)
./gradlew clean build
java -jar desktop-ui/build/libs/interlockSim.jar example shuntingLoop 1024AnimatedSim Milestone - Real-time visual simulation with animated trains and event logging.
Run simulation examples with animated GUI:
# Recommended: Use Gradle task
./gradlew runExampleGui
# Or manually after building
java -jar desktop-ui/build/libs/interlockSim.jar exampleGui shuntingLoop 1024The animated GUI showing real-time train movement with dynamic track coloring (Gray/Yellow/Red), signal states (RED/GREEN), and event timeline with simulation time display.
Features:
- Real-time train animation - Watch trains move smoothly across the track network (30 FPS)
- Dynamic track coloring - Tracks change color based on state:
- 🟤 Gray - FREE (available for reservation)
- 🟡 Yellow - RESERVED (path set for train)
- 🔴 Red - OCCUPIED (train currently on track)
- Signal visualization - Semaphores show RED (stop) or GREEN (proceed)
- Switch position display - Rail switches indicate MAIN or BRANCH position
- Event timeline - Scrollable log of simulation events with filtering:
- Path commands (reservation, release)
- Node events (switch/signal state changes)
- Train events (stop, exit, arrival)
- Continuous updates (position/velocity at 1 Hz)
- Time display - Current simulation time in HH:MM:SS.mmm format
Example Usage:
# Run with default settings (shunting loop, 1024 time units)
./gradlew runExampleGui
# Run specific example with custom duration
java -jar desktop-ui/build/libs/interlockSim.jar exampleGui shuntingLoop 600
# Docker users
docker compose run app java -jar interlockSim.jar exampleGui shuntingLoop 1024Performance:
- Smooth 30 FPS animation with multiple trains
- Event-driven updates (no polling overhead)
- Optimized state capture with caching (20-80× faster than polling)
Technical Details:
- Built with Swing GUI components
- Thread-safe animation controller (EDT marshaling)
- Immutable state snapshots for rendering
- PropertyChangeListener-based event propagation
See docs/IMPLEMENTATION_SUMMARY_ISSUE_205.md for detailed architecture documentation.
java -jar desktop-ui/build/libs/interlockSim.jar (sim|simgui|edit|example|exampleGui|aiSweep) [arguments]
Modes:
sim [file.xml]- Run simulation from XML file (console output)simgui [file.xml]- Run simulation from XML file with the animated GUIedit [file.xml]- Open editor (optionally load file)example [name] [endTime]- Run built-in example (console output)exampleGui [name] [endTime]- Run built-in example with animated GUIaiSweep --grid <grid.json> [--out <dir>] [--dry-run]- Run the dispatcher reliability sweep. Manual only, never in CI; the LLM arm needs a live Ollama. No grid files are committed — write the grid for the campaign you run. See CLAUDE.md for an example grid.
Note: For memory-constrained environments, add -Xmx300m.
The project is a multi-module Gradle build. All production code is Kotlin.
interlockSim/
├── build.gradle.kts # Root build script (thin aggregator)
├── settings.gradle.kts # Subprojects: :core, :core-test, :dispatcher-agent,
│ # :desktop-ui, and :fast-sim on Linux hosts
├── gradle.properties # Dependency versions
├── gradle/ # Gradle wrapper files
│
├── core/ # KMP :core - domain model, simulation engine, XML
│ └── src/
│ ├── commonMain/kotlin/cz/vutbr/fit/interlockSim/
│ │ ├── context/ # Simulation and editing contexts
│ │ ├── objects/ # Domain model (tracks, cells, paths)
│ │ ├── sim/ # Simulation scenarios and engine
│ │ ├── pathfinding/, ports/, domain/, di/, util/, xml/
│ ├── commonMain/resources/cz/vutbr/fit/interlockSim/resource/
│ │ ├── data.xsd # XML schema
│ │ └── vyhybna.xml # Example configuration
│ ├── commonTest/, jvmTest/ # Tests
│ ├── jvmMain/ # JVM actuals (XML parsing, factories)
│ │ └── resources/logback.xml # Logging configuration
│ └── nativeMain/, nativeInterop/ # linuxX64 actuals, libxml2 interop
│
├── core-test/ # KMP :core-test - shared test fixtures (TestFixtures)
├── dispatcher-agent/ # JVM :dispatcher-agent - Goal 10 dispatcher
│ └── src/main/kotlin/cz/vutbr/fit/interlockSim/dispatcher/
│ ├── agents/, executor/, planner/, observation/, sweep/, di/
│
├── desktop-ui/ # JVM :desktop-ui - GUI and application entry point
│ └── src/
│ ├── main/kotlin/cz/vutbr/fit/interlockSim/
│ │ ├── Main.kt # Application entry point
│ │ ├── AppMetadata.kt # Name, version, window titles
│ │ ├── ExampleRegistry.kt # Built-in examples
│ │ └── gui/, di/
│ ├── test/kotlin/ # Desktop tests
│ ├── jmh/kotlin/ # JMH benchmarks
│ └── build/libs/interlockSim.jar # Packaged application
│
├── fast-sim/ # Native :fast-sim - linuxX64 CLI binary
├── docs/ # Architecture docs
├── text/ # LaTeX thesis source
└── .github/workflows/ # CI/CD workflows
Railway networks are defined using XML with the following elements:
<RailSwitch>- Track switches (points)<RailSemaphore>- Signals<InOut>- Entry and exit points- Track connections with spatial coordinates
Example configuration: core/src/commonMain/resources/cz/vutbr/fit/interlockSim/resource/vyhybna.xml
XML Schema: core/src/commonMain/resources/cz/vutbr/fit/interlockSim/resource/data.xsd
Built on kDisco (Java framework for combined discrete and continuous simulation):
- Process-oriented simulation paradigm
- Discrete event scheduling
- Continuous variable support
- Time-determined and state-determined events
Context System - Composition-based architecture (Issue #153, 2026-01-20):
- BaseContext - Abstract base class with shared infrastructure (grid, graph, property change notification)
- DefaultEditingContext : BaseContext - Editing operations (putCell, removeCell, moveCell, joinCells)
- DefaultSimulationContext : BaseContext - Simulation operations (run, stop, pathToNextSemaphore)
- ContextTransformer - Factory for transforming EditingContext to SimulationContext
- Immutability enforcement via freeze() mechanism (simulation contexts are immutable after initialization)
- Factory pattern for context creation (EditingContextFactory, SimulationContextFactory, XMLContextFactory)
- Grid parameterization for type-safe cell access (RailwayNetGrid)
Context Separation:
- EditingContext and SimulationContext are separate interfaces (no inheritance between them)
- Both implementations extend BaseContext independently (composition over inheritance)
- Follows Interface Segregation Principle - simulation contexts do NOT support editing operations
- Network structure is frozen at simulation initialization time for correctness
Object Model - Track facilities, blocks, cells, and paths:
- Static objects (NodeCell: RailSwitch, RailSemaphore, InOut)
- Dynamic wrappers (DynamicPathSeparator, DynamicTrack) for simulation state
- Grid-based spatial representation with pathfinding
GUI - Swing-based editor with grid canvas
XML Factory - Schema-validated configuration loading
The project includes Docker support for both the Java application and LaTeX thesis compilation, eliminating the need to install Java 21, Gradle, or LaTeX tools on the host machine.
- app - Java application with GUI support (X11 forwarding)
- text - LaTeX thesis compilation
Build both services:
docker compose buildRun editor GUI:
# Method 1 (Recommended): Use .Xauthority file (more secure)
docker compose up app
# Method 2: If you get authorization errors, allow X11 connections from Docker
xhost +local:docker
docker compose up app
# When done with Method 2, revoke access for security:
xhost -local:dockerRun simulation example:
docker compose run app java -jar interlockSim.jar example shuntingLoop 60Run simulation with custom XML:
docker compose run -v $(pwd)/myfile.xml:/app/myfile.xml app java -jar interlockSim.jar sim myfile.xmlBuild thesis PDF:
docker compose up text
# PDF will be available in artifacts/text/bakalarka.pdfExtract compiled JAR:
docker compose build app
# JAR will be available in artifacts/app/interlockSim.jarRoot Dockerfile (multi-stage build):
- Builder stage - Uses Eclipse Temurin 21 JDK
- Builds kDisco dependency (Maven install, Java 6 compatibility)
- Resolves dependencies via Gradle (automatic download)
- Compiles Java sources (Java 21 target)
- Runs all tests (build fails if tests fail)
- Creates uber JAR with all dependencies
- Runner stage - Eclipse Temurin 21 JRE with X11 libraries
- Minimal runtime environment
- X11 forwarding for GUI support
- No build tools in final image
text/Dockerfile:
- Based on Debian Bookworm
- Full TeX Live installation with Czech language support
- Image conversion tools (wmf2eps, autotrace, gnuplot)
- Compiles thesis PDF from LaTeX sources
Both services copy build outputs to /artifacts inside the container, which is mounted to ./artifacts/ on the host:
artifacts/app/interlockSim.jar- Compiled applicationartifacts/text/bakalarka.pdf- Compiled thesis
The application uses SLF4J with Logback for comprehensive logging of simulation events and operations.
Main application: core/src/jvmMain/resources/logback.xml
Sweep runs: desktop-ui/src/main/resources/logback-sweep.xml
Available log levels (from most to least verbose):
TRACE- Very detailed diagnostic informationDEBUG- Detailed information for debuggingINFO- General informational messages (default)WARN- Warning messages for potential issuesERROR- Error messages for failures
Method 1: Edit logback.xml (recommended for development)
Edit core/src/jvmMain/resources/logback.xml:
<!-- Change root logger level (affects all loggers) -->
<root level="DEBUG">
<appender-ref ref="CONSOLE"/>
</root>
<!-- Or change specific package/class level -->
<logger name="cz.vutbr.fit.interlockSim.sim.Train" level="TRACE"/>
<logger name="cz.vutbr.fit.interlockSim.sim.ShuntingLoop" level="DEBUG"/>Method 2: System property (runtime override)
java -Dlogback.level=DEBUG -cp "build/main:lib/compile/*" cz.vutbr.fit.interlockSim.Main example shuntingLoop 1024Method 3: Environment variable (Docker)
docker compose run -e ROOT_LOG_LEVEL=DEBUG app java -jar interlockSim.jar example shuntingLoop 60The following loggers are pre-configured in logback.xml:
cz.vutbr.fit.interlockSim.simulation- Simulation events (INFO)kDisco.statistics- kDisco statistical reports (INFO)cz.vutbr.fit.interlockSim.sim.Train- Train behavior (DEBUG)cz.vutbr.fit.interlockSim.sim.ShuntingLoop- Shunting operations (DEBUG)cz.vutbr.fit.interlockSim.objects.paths.AbstractPath- Path management (DEBUG)cz.vutbr.fit.interlockSim.objects.tracks.SimpleTrack- Track operations (DEBUG)
Console: Real-time output with format: HH:mm:ss.SSS [thread] LEVEL Logger.method(File:Line) - message
File: logs/interlockSim.log with timestamp format: yyyy-MM-dd HH:mm:ss.SSS [thread] LEVEL Logger.method(File:Line) - message
Comprehensive JUnit 5.11.4 test suite with assertk 0.28.1 assertions. Tests live in each
subproject: core/src/{commonTest,jvmTest}/kotlin/, desktop-ui/src/test/kotlin/,
dispatcher-agent/src/test/kotlin/ and fast-sim/src/linuxX64Test/kotlin/.
Test coverage statistics, last counted February 2026:
- 1840 tests total (1836 passing, 4 skipped, 0 failing)
- 51% code coverage (8,824/17,070 instructions covered)
- 145 test classes across 6 expansion phases
That count predates the :core-test, :dispatcher-agent and :fast-sim subprojects, so it is
a floor, not the current figure. For the current numbers run ./gradlew test jacocoTestReport,
or read the CI coverage artifact (see "Test Coverage" below).
Coverage by package:
- objects.tracks/ - 85% (excellent), xml/ - 85% (excellent)
- util/ - 75% (good), objects.cells/ - 72% (good), context/ - 70% (good)
- objects.paths/ - 52% (medium), sim/ - 33% (limited by kDisco framework)
Test expansion phases completed (2026-01-10):
- Safety-critical components (Train physics, Track state, RailSwitch, RailSemaphore)
- Simulation engine core (Train state transitions, path interaction, InOutWorker)
- Path and track integration (AbstractPath, path/track coordination)
- Main entry points and cell edge cases (CLI parsing, example loading, NodeCell)
- Generator and advanced simulations (Generator, shunting operations, timetables)
- Exception handling and edge cases (SimulationException, validation, deadlock detection)
Run tests:
# Run unit tests only
./gradlew test
# Run integration tests
./gradlew integrationTest
# Run all tests
./gradlew test integrationTest
# Run JMH performance benchmarks
./gradlew jmh
# Or as part of build
./gradlew clean buildTests are automatically executed during the build process. The build will fail if any test fails.
Performance Benchmarks: JMH benchmarks are available in desktop-ui/src/jmh/ for accurate performance measurements. See desktop-ui/src/jmh/kotlin/README.md for details.
CLAUDE.md- Comprehensive development guide (build, test, architecture)docs/STATIC_DYNAMIC_SEPARATION_ARCHITECTURE.md- Complete static/dynamic separation architecture with diagramsdocs/CONTEXT_REFACTORING_DESIGN.md- Context refactoring and factory pattern designdocs/FACTORY_PATTERN_IMPLEMENTATION.md- Factory pattern implementation summarydocs/KOTLIN_STYLE_GUIDE.md- Kotlin coding standards and detekt configurationTEAM.md- Multi-agent development workflows and roles
Build the thesis PDF:
cd text
makeRequirements: LaTeX, gnuplot, make, wmf2eps, sed
Generate JavaDoc:
./gradlew javadocOutput: desktop-ui/build/docs/javadoc/ directory
The simulation engine is kDisco, a Kotlin discrete-event library maintained in its own repository at https://github.com/bedaHovorka/kdisco. It replaced the 2004 jDisco library on 2026-03-20.
There is no planned migration to another simulation framework. An earlier road map had a
second phase that would swap kDisco for Kalasim; that plan is dropped (owner decision,
2026-08-24). docs/jdisco-research.md and docs/SIMULATION_LIBRARY_DECISION_ROUND2.md record
the framework research and the decision as it stood then. Read them as history.
InterlockSim © 2006-2007 Bedrich Hovorka BSc Thesis, Brno University of Technology
kDisco Library © 2001-2004 Keld Helsgaun, Roskilde University, Denmark Research use only
This repository includes:
- Complete Kotlin source code (multi-module; kDisco is an external dependency)
- Gradle build system with Kotlin DSL
- Comprehensive JUnit 5 test suite
- XML schemas and example configurations
- Docker support for containerized builds
- GitHub Actions CI/CD workflows
- LaTeX thesis source and images
- Documentation (JavaDoc, README, CLAUDE.md)
The project uses GitHub Actions for continuous integration and quality assurance:
- Gradle Build (Java 21) - Compiles, tests, and creates JAR artifacts
- SonarQube Analysis - Code quality analysis and coverage reporting
View workflow status: GitHub Actions
The authoritative figure is the CI coverage artifact, not this file. See "Testing" above for the last recorded count and how to measure the current one.
Coverage Reports:
- JaCoCo reports generated with every build
- Available in CI artifacts:
jacoco-coverage-report-{sha} - View locally:
./gradlew jacocoTestReport→build/reports/jacoco/test/html/index.html
For information on:
- Running CI checks locally
- Verifying test coverage improvements
- Creating PRs with proper documentation
- Understanding CI/CD workflows
See:
- Contributing Guide - Complete contribution guidelines
- PR Template - Pull request checklist
- CI/CD Quick Reference - CI status badges and commands
Project: Railway Interlocking Simulator (InterlockSim 0.2)
Version note: 0.2 is the application version, defined by PROGRAM_VERSION in
desktop-ui/src/main/kotlin/cz/vutbr/fit/interlockSim/AppMetadata.kt, and it is what the
window title shows. The version=1.0 in gradle.properties is the artifact version of the
Gradle modules; the two are separate on purpose.
Institution: Brno University of Technology, Faculty of Information Technology Year: 2006/2007
For development guidance, see CLAUDE.md.

