Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ For complete navigation services architecture, Koin DI integration patterns, and
- `objects/paths/` - Route management

**Simulation engine:**
- Built on kDisco library (`cz.hovorka.kdisco:kdisco-core-jvm:0.6.0`) — replaces jDisco entirely (Phase 1 migration complete 2026-03-20)
- Built on kDisco library (`cz.ksimulantenbande.kdisco:kdisco-core-jvm:0.6.1-SNAPSHOT`) — replaces jDisco entirely (Phase 1 migration complete 2026-03-20)
- kDisco repo: https://github.com/bedaHovorka/kdisco
- `sim/` package contains simulation processes (e.g., `ShuntingLoop`)

Expand Down
2 changes: 1 addition & 1 deletion TEAM.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ This file defines specialized agent roles for the interlockSim project. When Cla

**Focus Areas:**
1. **Simulation Engine**
- kDisco 0.6.0 (Phase 1 migration complete 2026-03-20; Koin injection now allowed in sim/)
- kDisco 0.6.1-SNAPSHOT (Phase 1 migration complete 2026-03-20; Koin injection now allowed in sim/)
- Kalasim migration planning (Goal 10, Phase 2, future)
- `:fast-sim` native CLI subproject (linuxX64, PR #421)
- Discrete-event simulation patterns
Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ val checkKdisco by tasks.registering {
doLast {
val kdiscoJar =
file(
"${System.getProperty("user.home")}/.m2/repository/cz/hovorka/kdisco/kdisco-core-jvm/$kdiscoVersion/kdisco-core-jvm-$kdiscoVersion.jar",
"${System.getProperty("user.home")}/.m2/repository/cz/ksimulantenbande/kdisco/kdisco-core-jvm/$kdiscoVersion/kdisco-core-jvm-$kdiscoVersion.jar",
)

if (kdiscoJar.exists()) {
Expand Down
4 changes: 2 additions & 2 deletions core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
* which benefits from commonMain staying free of JVM-only APIs.
* 3. kDisco 0.4.0 ships multiplatform artifacts (jvm + linuxX64
* klibs) and this module consumes the multiplatform coordinate
* (`cz.hovorka.kdisco:kdisco-core`; see commonMain dependencies
* (`cz.ksimulantenbande.kdisco:kdisco-core`; see commonMain dependencies
* below). Remaining blockers for fully portable common code are
* test infrastructure (MockK/JUnit5 are JVM-only; commonTest is
* restricted to kotlin.test) and a Koin/xmlutil native audit on
Expand Down Expand Up @@ -129,7 +129,7 @@ kotlin {
val commonMain by getting {
dependencies {
// KMP multiplatform artifacts (jvm + linuxX64 klibsavailable in mavenLocal)
implementation("cz.hovorka.kdisco:kdisco-core:$kdiscoVersion")
implementation("cz.ksimulantenbande.kdisco:kdisco-core:$kdiscoVersion")
implementation("io.insert-koin:koin-core:$koinVersion")
implementation("io.github.oshai:kotlin-logging:$kotlinLoggingVersion")
implementation("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
*/
package cz.vutbr.fit.interlockSim.context

import cz.hovorka.kdisco.DiscoException
import cz.hovorka.kdisco.Process
import cz.hovorka.kdisco.Random
import cz.hovorka.kdisco.Simulation
import cz.ksimulantenbande.kdisco.DiscoException
import cz.ksimulantenbande.kdisco.Process
import cz.ksimulantenbande.kdisco.Random
import cz.ksimulantenbande.kdisco.Simulation
import cz.vutbr.fit.interlockSim.context.SimulationContext.ReportType
import cz.vutbr.fit.interlockSim.context.navigation.BlockEvent
import cz.vutbr.fit.interlockSim.context.navigation.PathReservationService
Expand Down Expand Up @@ -61,6 +61,7 @@ import cz.vutbr.fit.interlockSim.util.Util
import cz.vutbr.fit.interlockSim.util.platformIdentityCode
import io.github.oshai.kotlinlogging.KotlinLogging
import kotlinx.coroutines.runBlocking
import cz.ksimulantenbande.kdisco.SimulationEvent as KDiscoSimulationEvent
import cz.vutbr.fit.interlockSim.sim.events.BlockEvent as AnimBlockEvent

/**
Expand Down Expand Up @@ -206,7 +207,7 @@ open class DefaultSimulationContext(
private val pendingBlockEventListeners: MutableList<(BlockEvent) -> Unit> = mutableListOf()

/** Raw kdisco event listeners registered before run(); wired into kdisco at run() time. */
private val pendingSimEventListeners: MutableList<(cz.hovorka.kdisco.SimulationEvent) -> Unit> = mutableListOf()
private val pendingSimEventListeners: MutableList<(KDiscoSimulationEvent) -> Unit> = mutableListOf()

/** Spatial-conflict event listeners registered before run(); wired into kdisco at run() time. */
private val pendingConflictEventListeners: MutableList<(ConflictDetectedEvent) -> Unit> = mutableListOf()
Expand Down Expand Up @@ -1227,7 +1228,7 @@ open class DefaultSimulationContext(
pendingBlockEventListeners += listener
}

override fun onSimulationEvent(listener: (cz.hovorka.kdisco.SimulationEvent) -> Unit) {
override fun onSimulationEvent(listener: (KDiscoSimulationEvent) -> Unit) {
if (simulationHasStarted) return
pendingSimEventListeners += listener
}
Expand Down Expand Up @@ -1410,7 +1411,7 @@ open class DefaultSimulationContext(
if (listeners.isEmpty()) return
val snapshot = listeners.toList()
sim.onEvent { event ->
if (event is cz.hovorka.kdisco.SimulationEvent.Custom) {
if (event is KDiscoSimulationEvent.Custom) {
val payload = event.payload
if (payload is T) {
snapshot.forEach { it(payload) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
*/
package cz.vutbr.fit.interlockSim.context

import cz.hovorka.kdisco.Condition
import cz.ksimulantenbande.kdisco.Condition
import cz.vutbr.fit.interlockSim.context.navigation.BlockEvent
import cz.vutbr.fit.interlockSim.context.navigation.PathResult
import cz.vutbr.fit.interlockSim.context.navigation.RoutingServices
Expand All @@ -29,7 +29,7 @@ import cz.vutbr.fit.interlockSim.sim.conflict.TemporalConflictDetector
import cz.vutbr.fit.interlockSim.sim.conflict.TemporalConflictEvent
import cz.vutbr.fit.interlockSim.sim.events.BlockEventListener
import cz.vutbr.fit.interlockSim.sim.metrics.MetricsServices
import cz.hovorka.kdisco.SimulationEvent as KDiscoSimulationEvent
import cz.ksimulantenbande.kdisco.SimulationEvent as KDiscoSimulationEvent
import cz.vutbr.fit.interlockSim.sim.events.BlockEvent as AnimBlockEvent

/**
Expand Down Expand Up @@ -150,7 +150,7 @@ interface SimulationEnvironment : NetworkState {
* Create a kDisco [Condition] that becomes true when the path starting at
* [separator] is reserved for [trainId].
*
* This lets a train process suspend with [cz.hovorka.kdisco.Process.waitUntil]
* This lets a train process suspend with [cz.ksimulantenbande.kdisco.Process.waitUntil]
* and resume deterministically as soon as the dispatcher reserves the path
* (or as soon as a conflicting train releases the required blocks).
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import cz.vutbr.fit.interlockSim.objects.tracks.DynamicTrackBlock
/**
* Domain events emitted when block reservation or occupancy state changes.
*
* Delivered via kdisco's event bus as [cz.hovorka.kdisco.SimulationEvent.Custom] payloads.
* Delivered via kdisco's event bus as [cz.ksimulantenbande.kdisco.SimulationEvent.Custom] payloads.
* Subscribe via [cz.vutbr.fit.interlockSim.context.SimulationEnvironment.onBlockEvent].
*
* All fields are captured at emission time (simulation-thread values).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
*/
package cz.vutbr.fit.interlockSim.context.navigation

import cz.hovorka.kdisco.Process
import cz.hovorka.kdisco.emitCustom
import cz.ksimulantenbande.kdisco.Process
import cz.ksimulantenbande.kdisco.emitCustom
import cz.vutbr.fit.interlockSim.context.RouteFinder
import cz.vutbr.fit.interlockSim.context.SimulationEnvironment
import cz.vutbr.fit.interlockSim.exceptions.PathSeparatorChangeException
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
*/
package cz.vutbr.fit.interlockSim.context.navigation

import cz.hovorka.kdisco.Condition
import cz.ksimulantenbande.kdisco.Condition
import cz.vutbr.fit.interlockSim.context.SimulationContext
import cz.vutbr.fit.interlockSim.exceptions.requireValidState
import cz.vutbr.fit.interlockSim.objects.cells.DynamicRailSwitch
Expand Down Expand Up @@ -506,7 +506,7 @@ class PathReservationRegistry(
* The condition is deterministic: kDisco re-evaluates all wait notices after
* every discrete event (including the block-release event that happens when
* [unregister] or [unregisterBlock] removes the owner). A process can suspend
* with [cz.hovorka.kdisco.Process.waitUntil] and resume without busy-polling.
* with [cz.ksimulantenbande.kdisco.Process.waitUntil] and resume without busy-polling.
*
* @param block The block to wait for
* @return A condition evaluating to true once the block is free
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
*/
package cz.vutbr.fit.interlockSim.exceptions

import cz.hovorka.kdisco.Process
import cz.ksimulantenbande.kdisco.Process

/**
* Exception thrown during simulation - at start, between start and end of simulation.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
*/
package cz.vutbr.fit.interlockSim.objects.cells

import cz.hovorka.kdisco.Process
import cz.ksimulantenbande.kdisco.Process
import cz.vutbr.fit.interlockSim.exceptions.PathSeparatorChangeException
import cz.vutbr.fit.interlockSim.exceptions.requireSimulation
import cz.vutbr.fit.interlockSim.objects.core.Cell
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
*/
package cz.vutbr.fit.interlockSim.objects.cells

import cz.hovorka.kdisco.Process
import cz.ksimulantenbande.kdisco.Process
import cz.vutbr.fit.interlockSim.exceptions.PathSeparatorChangeException
import cz.vutbr.fit.interlockSim.exceptions.requireSimulationNotNull
import cz.vutbr.fit.interlockSim.objects.cells.RailSwitch.Conf
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
*/
package cz.vutbr.fit.interlockSim.objects.paths

import cz.hovorka.kdisco.Process
import cz.ksimulantenbande.kdisco.Process
import cz.vutbr.fit.interlockSim.context.SimulationContext
import cz.vutbr.fit.interlockSim.context.SimulationContext.ReportType
import cz.vutbr.fit.interlockSim.domain.ABSOLUTE_MAX_SPEED
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
*/
package cz.vutbr.fit.interlockSim.objects.tracks

import cz.hovorka.kdisco.Process
import cz.ksimulantenbande.kdisco.Process
import cz.vutbr.fit.interlockSim.exceptions.TrackOperationException
import cz.vutbr.fit.interlockSim.exceptions.requireSimulation
import cz.vutbr.fit.interlockSim.exceptions.requireSimulationNotNull
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
*/
package cz.vutbr.fit.interlockSim.objects.tracks

import cz.hovorka.kdisco.Process
import cz.hovorka.kdisco.emitCustom
import cz.ksimulantenbande.kdisco.Process
import cz.ksimulantenbande.kdisco.emitCustom
import cz.vutbr.fit.interlockSim.context.navigation.BlockEvent
import cz.vutbr.fit.interlockSim.exceptions.TrackOperationException
import cz.vutbr.fit.interlockSim.exceptions.requireSimulation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
*/
package cz.vutbr.fit.interlockSim.ports

import cz.hovorka.kdisco.DiscoException
import cz.hovorka.kdisco.Process
import cz.ksimulantenbande.kdisco.DiscoException
import cz.ksimulantenbande.kdisco.Process
import cz.vutbr.fit.interlockSim.context.SimulationEnvironment
import cz.vutbr.fit.interlockSim.objects.cells.DynamicRailSemaphore
import cz.vutbr.fit.interlockSim.objects.cells.NodeCell
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
*/
package cz.vutbr.fit.interlockSim.sim

import cz.hovorka.kdisco.Resource
import cz.ksimulantenbande.kdisco.Resource
import cz.vutbr.fit.interlockSim.context.SimulationContext
import cz.vutbr.fit.interlockSim.objects.core.TrackFacility
import cz.vutbr.fit.interlockSim.objects.tracks.DynamicTrackBlock
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
*/
package cz.vutbr.fit.interlockSim.sim

import cz.hovorka.kdisco.Continuous
import cz.hovorka.kdisco.Process
import cz.ksimulantenbande.kdisco.Continuous
import cz.ksimulantenbande.kdisco.Process
import cz.vutbr.fit.interlockSim.exceptions.requireSimulation

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,22 @@ data class QueuedTrainObservation(
* [ShuntingLoop]). `null` when no FREE next separator exists, in which case the
* dispatcher emits [DispatchDecision.NoAction] for this input (the train waits
* and is reconsidered next tick).
*
* **Populated only where a forward reservation is possible** (Issue #749). The shell
* resolves it exclusively for inputs satisfying
* `!pathAlreadyExtendedBeyond && (isApproachingThisInput || pathSetUpTowardThisInput)`;
* for every other input — FREE, not approaching this input, or already extended beyond
* it — this is `null` **without the search having been run**. Resolving it means a BFS
* plus a per-candidate topological-path enumeration
* ([PathReservationService.findNextReservationTarget][cz.vutbr.fit.interlockSim.context.navigation.PathReservationService.findNextReservationTarget]);
* running it for the ~98% of inputs whose value is then discarded cost ~9% of fast-sim
* wall time.
*
* A `null` here therefore means *"no forward-reservation target applies"*, not
* *"the search found nothing"* — the two are indistinguishable to a dispatcher, and
* both call for the same response (no reservation for this input on this tick).
* Dispatcher implementations — including future LLM-backed ones — must not read
* `toSeparatorName == null` as evidence that the track ahead is occupied.
* @property state Occupancy state of the block.
* @property ownerTrainId Name of the train associated with this block: the
* occupant's name when OCCUPIED, [cz.vutbr.fit.interlockSim.objects.tracks.DynamicTrackBlock.trainName]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@
*/
package cz.vutbr.fit.interlockSim.sim

import cz.hovorka.kdisco.Process
import cz.hovorka.kdisco.Random
import cz.hovorka.kdisco.dtMax
import cz.hovorka.kdisco.dtMin
import cz.hovorka.kdisco.maxAbsError
import cz.hovorka.kdisco.maxRelError
import cz.ksimulantenbande.kdisco.Process
import cz.ksimulantenbande.kdisco.Random
import cz.ksimulantenbande.kdisco.dtMax
import cz.ksimulantenbande.kdisco.dtMin
import cz.ksimulantenbande.kdisco.maxAbsError
import cz.ksimulantenbande.kdisco.maxRelError
import cz.vutbr.fit.interlockSim.context.SimulationEnvironment
import io.github.oshai.kotlinlogging.KotlinLogging

Expand All @@ -31,6 +31,13 @@ open class Generator(

override suspend fun startAction() {
dtMin = 1e-6
// Block-boundary and tail-entry events are now located by kDisco root-finding
// (`Process.waitCrossing`, see Train.kt) rather than by step granularity, so `dtMax` no
// longer has to be tiny to keep *those* events' overshoot negligible. Motor's
// AccelerationStopCondition (velocity-target detection, Train.kt) is NOT yet converted —
// it still relies on step granularity via plain `waitUntil` — so `dtMax` is deliberately
// left unchanged here rather than raised further; converting Motor and re-tuning dtMax
// is tracked separately (Issue #760, follow-up to #750).
dtMax = 1e-3
maxRelError = 1e-2
maxAbsError = 1e-2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
*/
package cz.vutbr.fit.interlockSim.sim

import cz.hovorka.kdisco.Condition
import cz.hovorka.kdisco.Head
import cz.hovorka.kdisco.Link
import cz.hovorka.kdisco.Process
import cz.ksimulantenbande.kdisco.Condition
import cz.ksimulantenbande.kdisco.Head
import cz.ksimulantenbande.kdisco.Link
import cz.ksimulantenbande.kdisco.Process
import cz.vutbr.fit.interlockSim.context.SimulationContext.ReportType
import cz.vutbr.fit.interlockSim.context.SimulationEnvironment
import cz.vutbr.fit.interlockSim.context.navigation.PathReservationService
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
*/
package cz.vutbr.fit.interlockSim.sim

import cz.hovorka.kdisco.DiscoException
import cz.hovorka.kdisco.Process
import cz.ksimulantenbande.kdisco.DiscoException
import cz.ksimulantenbande.kdisco.Process
import io.github.oshai.kotlinlogging.KotlinLogging

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
*/
package cz.vutbr.fit.interlockSim.sim

import cz.hovorka.kdisco.Process
import cz.hovorka.kdisco.Resource
import cz.ksimulantenbande.kdisco.Process
import cz.ksimulantenbande.kdisco.Resource
import cz.vutbr.fit.interlockSim.context.SimulationContext
import cz.vutbr.fit.interlockSim.context.SimulationContext.ReportType
import cz.vutbr.fit.interlockSim.context.SimulationEnvironment
Expand Down Expand Up @@ -52,7 +52,7 @@ import io.github.oshai.kotlinlogging.KotlinLogging
* ## Determinism
*
* Train injection times come from caller-supplied specs, so runs are repeatable
* when paired with a fixed kDisco seed. No standalone [cz.hovorka.kdisco.Random]
* when paired with a fixed kDisco seed. No standalone [cz.ksimulantenbande.kdisco.Random]
* generator is required.
*
* ## Scope limitations (documented)
Expand Down
Loading
Loading