From cc30fc8400b1b78906e92b2e80955b320c29f9db Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 7 May 2026 06:18:46 +0000 Subject: [PATCH 1/7] Initial plan From 4d14ced78de70c60d295acdb1403333835ddf74f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 7 May 2026 06:22:52 +0000 Subject: [PATCH 2/7] docs: correct Goal 7 speed control notes Agent-Logs-Url: https://github.com/bedaHovorka/interlockSim/sessions/377a2ef7-bc5a-45a9-a471-74d5fad657c6 Co-authored-by: bedaHovorka <5263405+bedaHovorka@users.noreply.github.com> --- LONG_TERM_GOALS.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/LONG_TERM_GOALS.md b/LONG_TERM_GOALS.md index 13f89754e..ae1ae08f3 100644 --- a/LONG_TERM_GOALS.md +++ b/LONG_TERM_GOALS.md @@ -222,7 +222,8 @@ Users can adjust simulation speed from slow motion (for detailed observation) to **Dependencies:** None (quick win) **Implementation Notes:** -- kDisco already supports speed control; needs UI exposure +- The simulation library interface used by the model (historically jDisco, now kDisco/KMP) runs in pure simulation time and has no native wall-clock speed control or synchronization. +- Speed control is implemented externally via wall-clock throttling in `SimulationRunner` around `SimulationContext.run()`, preserving simulation semantics unchanged and requiring zero modifications to the `sim/` package. - Quick win - implement early for immediate value --- From 2290a6d69d8d865ee683b14a79962aed420d572b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 7 May 2026 07:24:46 +0000 Subject: [PATCH 3/7] docs: mark Goal 7 complete Agent-Logs-Url: https://github.com/bedaHovorka/interlockSim/sessions/a66e1723-36d6-4c44-8d1f-8ca5cd450ddc Co-authored-by: bedaHovorka <5263405+bedaHovorka@users.noreply.github.com> --- LONG_TERM_GOALS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/LONG_TERM_GOALS.md b/LONG_TERM_GOALS.md index ae1ae08f3..7b45b7d31 100644 --- a/LONG_TERM_GOALS.md +++ b/LONG_TERM_GOALS.md @@ -209,6 +209,7 @@ Users can automatically collect key performance indicators during simulation inc **Category:** I: System Operations **Priority:** Critical **Development Estimate:** 1 month +**Status:** ✅ COMPLETE **User Value:** Users can adjust simulation speed from slow motion (for detailed observation) to fast forward (for quick scenario completion). This provides flexibility for different use cases: slow for education, fast for research batch runs. From 62d68babc91f0569da77df5b3ff2bf0b0f7e6457 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 7 May 2026 06:18:46 +0000 Subject: [PATCH 4/7] Initial plan From 5910affd5c268a8346b6d18c785ad5f04aadf757 Mon Sep 17 00:00:00 2001 From: Bedrich Hovorka Date: Thu, 7 May 2026 11:30:39 +0200 Subject: [PATCH 5/7] docs: correct Goal 7 speed-control implementation notes RealTimeSynch lives in sim/ShuntingLoop.kt (not outside sim/); SimulationRunner.throttle() is the external extension point but ShuntingLoop does not call it in production. Co-Authored-By: Claude Sonnet 4.6 --- LONG_TERM_GOALS.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/LONG_TERM_GOALS.md b/LONG_TERM_GOALS.md index 7b45b7d31..001cea094 100644 --- a/LONG_TERM_GOALS.md +++ b/LONG_TERM_GOALS.md @@ -224,7 +224,8 @@ Users can adjust simulation speed from slow motion (for detailed observation) to **Implementation Notes:** - The simulation library interface used by the model (historically jDisco, now kDisco/KMP) runs in pure simulation time and has no native wall-clock speed control or synchronization. -- Speed control is implemented externally via wall-clock throttling in `SimulationRunner` around `SimulationContext.run()`, preserving simulation semantics unchanged and requiring zero modifications to the `sim/` package. +- Speed control for `ShuntingLoop` is implemented via the `RealTimeSynch` inner process inside `sim/ShuntingLoop.kt` (enabled by `enableRealTimeSync`, paced by `speedMultiplier`). This resides in the `sim/` package. +- `SimulationRunner` provides a complementary external throttling API (`throttle()`, `awaitIfPaused()`) callable from the simulation thread; this is the designed extension point for future simulation processes that delegate pacing outside the `sim/` package. - Quick win - implement early for immediate value --- From 7142fa08349b025eabab037156d0aaeed822b586 Mon Sep 17 00:00:00 2001 From: Bedrich Hovorka Date: Thu, 7 May 2026 11:40:54 +0200 Subject: [PATCH 6/7] fix: complete always-true condition fix in TrainPathInteractionTest Commit 6946b37 fixed tests 4 and 5 but missed test 2, which still had the same `val x = train != null; assertThat(x).isEqualTo(true)` pattern. Also removes unused mock variables in tests 2, 4, 5 that were never wired into assertions (`pathAhead`, `blockedTrack`, `pathBlocked`, `semaphore`, `track`, `pathUnblocked`). Co-Authored-By: Claude Sonnet 4.6 --- .../sim/TrainPathInteractionTest.kt | 21 +------------------ 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/core/src/jvmTest/kotlin/cz/vutbr/fit/interlockSim/sim/TrainPathInteractionTest.kt b/core/src/jvmTest/kotlin/cz/vutbr/fit/interlockSim/sim/TrainPathInteractionTest.kt index fb74a5e03..bdf6b550c 100644 --- a/core/src/jvmTest/kotlin/cz/vutbr/fit/interlockSim/sim/TrainPathInteractionTest.kt +++ b/core/src/jvmTest/kotlin/cz/vutbr/fit/interlockSim/sim/TrainPathInteractionTest.kt @@ -16,9 +16,7 @@ import assertk.assertions.isNotNull import cz.vutbr.fit.interlockSim.objects.cells.DynamicInOut import cz.vutbr.fit.interlockSim.testutil.KoinTestBase import cz.vutbr.fit.interlockSim.testutil.MockSimulationContext -import cz.vutbr.fit.interlockSim.testutil.createMockBlockedTrack import cz.vutbr.fit.interlockSim.testutil.createMockPath -import cz.vutbr.fit.interlockSim.testutil.createMockSemaphoreMock import cz.vutbr.fit.interlockSim.testutil.createMockSimulationContext import cz.vutbr.fit.interlockSim.testutil.createMockTrack import cz.vutbr.fit.interlockSim.testutil.withMessage @@ -92,22 +90,14 @@ class TrainPathInteractionTest : KoinTestBase() { @Test fun `train requests path reservation ahead`() { - // Arrange: Create a path representing the route ahead - val track1 = createMockTrack("TRACK1", 100.0) - val track2 = createMockTrack("TRACK2", 100.0) - val pathAhead = createMockPath(track1, track2) - val mockOutOut = mockk(relaxed = true) every { mockOutOut.name } returns "EXIT" val timetable = Timetable(mockInOut, mockOutOut, Time(0.0), Time(0.0), 50.0) val train = Train(mockContext, timetable) - // Act: Train exists and can theoretically request path reservation - val trainExists = train != null - // Assert: Train instance is valid for path operations - assertThat(trainExists).isEqualTo(true) + assertThat(train).isNotNull() } @Test @@ -136,10 +126,6 @@ class TrainPathInteractionTest : KoinTestBase() { @Test fun `train handles path not available - waits`() { - // Arrange: Create a blocked path scenario - val blockedTrack = createMockBlockedTrack("BLOCKED_TRACK", 100.0) - val pathBlocked = createMockPath(blockedTrack) - val mockOutOut = mockk(relaxed = true) every { mockOutOut.name } returns "EXIT" @@ -152,11 +138,6 @@ class TrainPathInteractionTest : KoinTestBase() { @Test fun `train handles path becoming available - proceeds`() { - // Arrange: Create a path that initially blocks but becomes free - val semaphore = createMockSemaphoreMock(true) // Initially allowing - val track = createMockTrack("TRACK", 100.0) - val pathUnblocked = createMockPath(track) - val mockOutOut = mockk(relaxed = true) every { mockOutOut.name } returns "EXIT" From 63082e4f0b613e3398e4c90322c309f92cf552a0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 7 May 2026 09:53:35 +0000 Subject: [PATCH 7/7] test: revert unrelated TrainPathInteractionTest changes Agent-Logs-Url: https://github.com/bedaHovorka/interlockSim/sessions/7e2018b6-a99c-4481-88ed-b1a527375dc3 Co-authored-by: bedaHovorka <5263405+bedaHovorka@users.noreply.github.com> --- .../sim/TrainPathInteractionTest.kt | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/core/src/jvmTest/kotlin/cz/vutbr/fit/interlockSim/sim/TrainPathInteractionTest.kt b/core/src/jvmTest/kotlin/cz/vutbr/fit/interlockSim/sim/TrainPathInteractionTest.kt index bdf6b550c..fb74a5e03 100644 --- a/core/src/jvmTest/kotlin/cz/vutbr/fit/interlockSim/sim/TrainPathInteractionTest.kt +++ b/core/src/jvmTest/kotlin/cz/vutbr/fit/interlockSim/sim/TrainPathInteractionTest.kt @@ -16,7 +16,9 @@ import assertk.assertions.isNotNull import cz.vutbr.fit.interlockSim.objects.cells.DynamicInOut import cz.vutbr.fit.interlockSim.testutil.KoinTestBase import cz.vutbr.fit.interlockSim.testutil.MockSimulationContext +import cz.vutbr.fit.interlockSim.testutil.createMockBlockedTrack import cz.vutbr.fit.interlockSim.testutil.createMockPath +import cz.vutbr.fit.interlockSim.testutil.createMockSemaphoreMock import cz.vutbr.fit.interlockSim.testutil.createMockSimulationContext import cz.vutbr.fit.interlockSim.testutil.createMockTrack import cz.vutbr.fit.interlockSim.testutil.withMessage @@ -90,14 +92,22 @@ class TrainPathInteractionTest : KoinTestBase() { @Test fun `train requests path reservation ahead`() { + // Arrange: Create a path representing the route ahead + val track1 = createMockTrack("TRACK1", 100.0) + val track2 = createMockTrack("TRACK2", 100.0) + val pathAhead = createMockPath(track1, track2) + val mockOutOut = mockk(relaxed = true) every { mockOutOut.name } returns "EXIT" val timetable = Timetable(mockInOut, mockOutOut, Time(0.0), Time(0.0), 50.0) val train = Train(mockContext, timetable) + // Act: Train exists and can theoretically request path reservation + val trainExists = train != null + // Assert: Train instance is valid for path operations - assertThat(train).isNotNull() + assertThat(trainExists).isEqualTo(true) } @Test @@ -126,6 +136,10 @@ class TrainPathInteractionTest : KoinTestBase() { @Test fun `train handles path not available - waits`() { + // Arrange: Create a blocked path scenario + val blockedTrack = createMockBlockedTrack("BLOCKED_TRACK", 100.0) + val pathBlocked = createMockPath(blockedTrack) + val mockOutOut = mockk(relaxed = true) every { mockOutOut.name } returns "EXIT" @@ -138,6 +152,11 @@ class TrainPathInteractionTest : KoinTestBase() { @Test fun `train handles path becoming available - proceeds`() { + // Arrange: Create a path that initially blocks but becomes free + val semaphore = createMockSemaphoreMock(true) // Initially allowing + val track = createMockTrack("TRACK", 100.0) + val pathUnblocked = createMockPath(track) + val mockOutOut = mockk(relaxed = true) every { mockOutOut.name } returns "EXIT"