Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
d964f6f
Document SP3.2 operating vocabulary DSL design (#696)
Copilot Jul 6, 2026
56b07ab
SP3.1: LLM model evaluation for Goal 10 agent roles (docs only) (#640)
Copilot Jul 6, 2026
03163c1
SP0.1: Extract RuleBasedDispatcher from ShuntingLoop; scaffold :dispa…
Copilot Jul 7, 2026
1f78d33
feat(#541): squash-merge sensor ports for perception interface (#722)
bedaHovorka Jul 7, 2026
ec6d784
SP0.3: Define actuator ports for command interface (#723)
Copilot Jul 7, 2026
c2c5b76
SP0.4: Make simulation state observable at a control step (#725)
Copilot Jul 7, 2026
d9f35eb
SP0.6: Isolate agent runtime behind sensor/actuator interfaces (#726)
Copilot Jul 8, 2026
5082e83
SP0.8: Add thread-safe ActuatorCommandQueue for command handoff (#736)
Copilot Jul 8, 2026
e84a09a
SP0.7: Reshape Dispatcher to a pure decide() seam + DispatchObservati…
Copilot Jul 9, 2026
61188f4
SP0.9: sim-thread applier draining ActuatorCommandQueue via actuator …
Copilot Jul 9, 2026
34301f8
SP0.10: AgentLoopDriver — paced sense-decide-act loop via SimulationC…
Copilot Jul 10, 2026
75f3208
SP0.11: Thin ShuntingLoop shell + Koin/DI wiring for the lifted dispa…
Copilot Jul 11, 2026
b30df7f
SP0.12: A3 determinism re-validation + vyhybna integration gate for t…
Copilot Jul 11, 2026
51784ac
SP1.1: Complete `:dispatcher-agent` Gradle module integration with CI…
Copilot Jul 11, 2026
dba2c6e
SP0.13a (#749): gate findNextReservationTarget on reservation-eligibi…
bedaHovorka Jul 11, 2026
cd651b1
fix(ci): move flaky controlled-loop overhead check from JUnit gate to…
bedaHovorka Jul 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .github/workflows/gradle-java21.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ jobs:
env:
GITHUB_ACTOR: ${{ github.actor }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: ./gradlew :desktop-ui:ktlintCheck :core:ktlintCheck --no-daemon --stacktrace
run: ./gradlew :desktop-ui:ktlintCheck :core:ktlintCheck :dispatcher-agent:ktlintCheck --no-daemon --stacktrace

- name: Run unit tests
timeout-minutes: 10
Expand Down Expand Up @@ -111,6 +111,11 @@ jobs:
core/build/test-results/jvmTest
core/build/test-results/integrationTest
core/build/reports/jacoco/jvmTest/jacocoTestReport.xml
dispatcher-agent/build/classes/kotlin/main
dispatcher-agent/build/classes/kotlin/test
dispatcher-agent/build/test-results/test
dispatcher-agent/build/test-results/integrationTest
dispatcher-agent/build/reports/jacoco/test/jacocoTestReport.xml
retention-days: 7
if-no-files-found: error

Expand Down
1 change: 1 addition & 0 deletions .github/workflows/sonarqube.yml
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ jobs:
./gradlew sonar \
-x :desktop-ui:test -x :desktop-ui:integrationTest -x :desktop-ui:jacocoTestReport \
-x :core:jvmTest -x :core:integrationTest -x :core:jacocoTestReport \
-x :dispatcher-agent:test -x :dispatcher-agent:integrationTest -x :dispatcher-agent:jacocoTestReport \
-Dsonar.host.url=https://sonarcloud.io \
-Dsonar.organization=$SONAR_ORGANIZATION \
-Dsonar.token=$SONAR_TOKEN \
Expand Down
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,7 @@ View build status: [GitHub Actions](https://github.com/bedaHovorka/interlockSim/
- `CZECH_RAILWAY_TERMINOLOGY.md` - Czech terminology verification and translation guide (373 lines)
- `TRAIN_PASSIVATION_FIX.md` - Train physics passivation fix (Issue #291)
- `KOIN_SCOPE_LIFECYCLE_TESTS.md` - Koin scope lifecycle test documentation (Issue #220)
- `GOAL_10_SP3_1_LLM_MODEL_EVALUATION.md` - LLM model comparison for the Goal 10 DISPATCHER agent (Issue #534, SP3.1; TRAIN agents are algorithmic-only, never LLM)

## Known Issues

Expand Down
2 changes: 2 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ COPY --chown=builder:builder detekt-strict.yml /build/interlockSim/
COPY --chown=builder:builder .editorconfig /build/interlockSim/
COPY --chown=builder:builder core/build.gradle.kts /build/interlockSim/core/
COPY --chown=builder:builder core-test/build.gradle.kts /build/interlockSim/core-test/
COPY --chown=builder:builder dispatcher-agent/build.gradle.kts /build/interlockSim/dispatcher-agent/
COPY --chown=builder:builder desktop-ui/build.gradle.kts /build/interlockSim/desktop-ui/

# All subsequent Gradle commands run as the builder user.
Expand Down Expand Up @@ -97,6 +98,7 @@ RUN --mount=type=cache,target=/home/builder/.gradle/caches,id=app-gradle-v2,uid=
COPY --chown=builder:builder desktop-ui/src/ /build/interlockSim/desktop-ui/src/
COPY --chown=builder:builder core/src/ /build/interlockSim/core/src/
COPY --chown=builder:builder core-test/src/ /build/interlockSim/core-test/src/
COPY --chown=builder:builder dispatcher-agent/src/ /build/interlockSim/dispatcher-agent/src/

# Layer 5: Compile and package with cache mount (no tests).
# Tests are decoupled from the app image build and run in the separate
Expand Down
27 changes: 18 additions & 9 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,11 @@ val checkKdisco by tasks.registering {
// ===========================================

tasks.register("test") {
dependsOn(":core:jvmTest", ":desktop-ui:test")
dependsOn(":core:jvmTest", ":desktop-ui:test", ":dispatcher-agent:test")
}

tasks.register("integrationTest") {
dependsOn(":core:integrationTest", ":desktop-ui:integrationTest")
dependsOn(":core:integrationTest", ":desktop-ui:integrationTest", ":dispatcher-agent:integrationTest")
}

listOf(
Expand Down Expand Up @@ -101,19 +101,21 @@ sonar {
property("sonar.projectName", "interlockSim - Railway Interlocking Simulator")
property("sonar.projectVersion", version.toString())

// Source and test paths (desktop-ui + :core KMP subproject).
// Source and test paths (desktop-ui + :core KMP subproject + :dispatcher-agent).
// Kept in sync with sonar-project.properties (used for local sonar-scanner runs).
property(
"sonar.sources",
"desktop-ui/src/main/kotlin,core/src/commonMain/kotlin," +
"core/src/jvmMain/kotlin,core/src/nativeMain/kotlin",
"core/src/jvmMain/kotlin,core/src/nativeMain/kotlin," +
"dispatcher-agent/src/main/kotlin",
)
property(
"sonar.tests",
"desktop-ui/src/test/kotlin,core/src/commonTest/kotlin,core/src/jvmTest/kotlin",
"desktop-ui/src/test/kotlin,core/src/commonTest/kotlin,core/src/jvmTest/kotlin," +
"dispatcher-agent/src/test/kotlin",
)
property("sonar.java.binaries", "desktop-ui/build/classes/kotlin/main,core/build/classes/kotlin/jvm/main")
property("sonar.java.test.binaries", "desktop-ui/build/classes/kotlin/test,core/build/classes/kotlin/jvm/test")
property("sonar.java.binaries", "desktop-ui/build/classes/kotlin/main,core/build/classes/kotlin/jvm/main,dispatcher-agent/build/classes/kotlin/main")
property("sonar.java.test.binaries", "desktop-ui/build/classes/kotlin/test,core/build/classes/kotlin/jvm/test,dispatcher-agent/build/classes/kotlin/test")

property("sonar.java.source", javaVersion)
property("sonar.java.target", javaVersion)
Expand All @@ -125,13 +127,16 @@ sonar {
"desktop-ui/build/test-results/test," +
"desktop-ui/build/test-results/integrationTest," +
"core/build/test-results/jvmTest," +
"core/build/test-results/integrationTest",
"core/build/test-results/integrationTest," +
"dispatcher-agent/build/test-results/test," +
"dispatcher-agent/build/test-results/integrationTest",
)
property(
"sonar.coverage.jacoco.xmlReportPaths",
listOf(
file("desktop-ui/build/reports/jacoco/test/jacocoTestReport.xml"),
file("core/build/reports/jacoco/jvmTest/jacocoTestReport.xml"),
file("dispatcher-agent/build/reports/jacoco/test/jacocoTestReport.xml"),
).joinToString(",") { it.absolutePath },
)

Expand Down Expand Up @@ -161,6 +166,7 @@ tasks.named("sonar") {
dependsOn(
":desktop-ui:test", ":desktop-ui:integrationTest", ":desktop-ui:jacocoTestReport",
":core:jvmTest", ":core:integrationTest", ":core:jacocoTestReport",
":dispatcher-agent:test", ":dispatcher-agent:integrationTest", ":dispatcher-agent:jacocoTestReport",
)
}

Expand All @@ -175,18 +181,21 @@ val jacocoAggregatedReport by tasks.registering(JacocoReport::class) {
dependsOn(
":core:jvmTest", ":core:integrationTest",
":desktop-ui:test", ":desktop-ui:integrationTest",
":dispatcher-agent:test", ":dispatcher-agent:integrationTest",
)

executionData.setFrom(
fileTree("core/build").include("jacoco/*.exec"),
fileTree("desktop-ui/build").include("jacoco/*.exec"),
fileTree("dispatcher-agent/build").include("jacoco/*.exec"),
)
sourceDirectories.setFrom(
files("core/src/commonMain/kotlin", "core/src/jvmMain/kotlin", "desktop-ui/src/main/kotlin")
files("core/src/commonMain/kotlin", "core/src/jvmMain/kotlin", "desktop-ui/src/main/kotlin", "dispatcher-agent/src/main/kotlin")
)
classDirectories.setFrom(
fileTree("core/build/classes/kotlin/jvm/main"),
fileTree("desktop-ui/build/classes/kotlin/main"),
fileTree("dispatcher-agent/build/classes/kotlin/main"),
)

reports {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,23 @@ package cz.vutbr.fit.interlockSim.context
*
* ## Threading Contract
*
* - [awaitIfPaused] is called from the simulation coroutine (kDisco dispatcher thread).
* It suspends (not blocks) so the coroutine yields while paused.
* - [throttle] is called from the simulation coroutine to apply wall-clock pacing.
* - [isPaused] is called from the simulation coroutine to check pause state.
* - [pollStepEvent] is called from the simulation coroutine; returns `true` once if
* a step-event was requested, then resets.
* - [pollStepTime] is called from the simulation coroutine; returns the pending
* time-delta once, then resets.
* The kDisco simulation kernel runs on its own single thread. The methods below
* are invoked from one of several outside callers, which may run concurrently
* with each other and with the kernel:
* - the **simulation coroutine** — the controlled event loop in
* `DefaultSimulationContext` (GUI/animated runs) — calls [awaitIfPaused],
* [throttle], [isPaused], [pollStepEvent], [pollStepTime];
* - the **external driver coroutine** — the SP0.10 drive-loop driver (#732), which
* paces its own sense→decide→act cycle independently of the kernel — calls
* [awaitIfPaused] and [throttle];
* - [requestPause] is called by external agents (e.g. the collision detection
* service) from arbitrary threads.
*
* The GUI/control thread sets pause state and enqueues step requests via its own
* implementation (e.g., `SimulationRunner`). The simulation coroutine polls these
* values through this interface.
* Implementations must be thread-safe across all of these callers: the
* GUI/control thread sets pause state and enqueues step requests, while the
* simulation coroutine and the external driver coroutine poll. The interface
* itself stays KMP-pure (see below); platform-specific synchronisation lives in
* implementations such as `SimulationRunner` / `NoOpSimulationController`.
*
* ## KMP Purity
*
Expand All @@ -44,18 +49,19 @@ interface SimulationController {
/**
* Suspends the simulation coroutine while the controller is in paused state.
*
* Called from the simulation coroutine at each iteration of the controlled loop.
* When paused, this function suspends until the controller resumes or a step
* is requested. Uses `suspend` (not blocking) so it cooperates with kDisco's
* coroutine-based dispatcher.
* Called from the simulation coroutine or the external driver coroutine at each
* iteration of the controlled loop. When paused, this function suspends until
* the controller resumes or a step is requested. Uses `suspend` (not blocking)
* so it cooperates with kDisco's coroutine-based dispatcher.
*/
suspend fun awaitIfPaused()

/**
* Applies wall-clock throttling to pace the simulation relative to real time.
*
* Called from the simulation coroutine after advancing the simulation clock.
* The implementation may delay execution to match the desired speed multiplier.
* Called from the simulation coroutine or the external driver coroutine after
* advancing the simulation clock. The implementation may delay execution to
* match the desired speed multiplier.
*
* @param simDeltaSeconds the simulation time that has just elapsed (in seconds)
*/
Expand All @@ -64,8 +70,8 @@ interface SimulationController {
/**
* Returns the current pause state.
*
* Called from the simulation coroutine to determine whether to enter the
* pause-wait loop.
* Called from the simulation coroutine or the external driver coroutine to
* determine whether to enter the pause-wait loop.
*
* @return `true` if the simulation is currently paused
*/
Expand Down
Loading
Loading