Lire et agir
Lire les horaires et le retard des trains
Séparer affectation, échéances simulées, retard prévu et offsets du plan de ligne.
Identifier ce que le train doit exécuter
Demandez TrainQuery(includeTimetables = true), qui demande aussi le service et les voies/gares nécessaires. L’affectation décrit horaire, service horaire et index d’ordre lorsqu’ils sont disponibles. La ligne est jointe depuis le service observé, qui décrit l’activité en cours ; ces informations répondent à des questions différentes.
| Donnée | Contrat |
|---|---|
| Timetable | Identité de l’horaire affecté. Son nom n’est pas résolu actuellement : name reste null. |
| TimetableShiftId | Identité du service à l’intérieur d’un horaire. Conserver ensemble timetableId et value. |
| orderIndex | Index d’ordre observé, éventuellement absent ; ne pas le confondre avec un index de gare. |
| Gare de service | Lieu lié au service courant ; différent de la gare de position physique. |
Convertir les échéances sans mélanger les horloges
Les échéances du service sont des instants simulés. En Native, train.service?.times regroupe les valeurs brutes et leurs GameInstant. En JVM, record.service fournit directement les valeurs et leurs Instant. Les accesseurs de date traitent l’origine simulée ; ne passez pas directement arrivalTimeUs à une conversion depuis 1970.
| Accesseur | Ce qu’il représente |
|---|---|
| observedAt | Instant simulé associé à l’observation du service. |
| arrival | Échéance d’arrivée du service, si disponible. |
| departure | Échéance de départ du service, si disponible. |
| dispatchRetry | Échéance de nouvelle tentative de départ, distincte du départ prévu. |
| arrivalRemainingSeconds | Durée signée jusqu’à l’arrivée ; peut être négative. |
| departureRemainingSeconds | Durée avant départ bornée à zéro une fois l’échéance passée. |
| dispatchRetryRemainingSeconds (Native) / dispatchRemainingSeconds (JVM) | Durée avant nouvelle tentative de départ, bornée à zéro. |
GameInstant conserve les secondes UTC et la microseconde. Sa conversion dateTime produit un calendrier à la seconde ; gardez GameInstant pour la précision fine. Les durées ne sont pas des dates et l’horloge réelle capturedAtMillis ne doit pas servir à soustraire une échéance simulée.
Utiliser l’estimation de retard pour ce qu’elle mesure
predictedArrivalDelaySeconds est l’estimation signée fournie par le jeu : positive pour du retard, négative pour de l’avance. Elle provient de predictedArrivalDelayUs, une durée en microsecondes. Ce n’est pas une différence à recalculer entre l’heure réelle et arrival, ni une garantie sur l’arrivée future.
Lire le plan de ligne sans inventer un passage absolu
Native expose context.linePlan(trainId), un LinePlan nullable dans la capture du callback. Demandez d’abord le lot nécessaire et évitez de renouveler le réseau entre les lectures liées. En JVM, selectedTrain ajoute lineStops au lot spécialisé. Un plan absent peut être indisponible ou instable ; un point de passage hors gare peut avoir stationId = null.
arrivalOffsetSeconds et departureOffsetSeconds sont relatifs au plan de ligne. plannedDwellSeconds calcule leur différence lorsque les deux existent. Une course partielle, une boucle ou l’affectation du train empêche de transformer ces offsets seuls en date de prochain passage : affichez-les comme des offsets de plan.
Préparer une fiche horaire sans valeurs inventées
package wiki.traintimetables
import nimby.*
data class TrainTiming(
val id: TrainId,
val timetable: Timetable?,
val shift: TimetableShiftId?,
val orderIndex: Int?,
val observedAt: GameInstant?,
val arrival: GameInstant?,
val departure: GameInstant?,
val dispatchRetry: GameInstant?,
val predictedArrivalDelaySeconds: Double?,
val plan: LinePlan?,
)
fun readTiming(context: ToolContext, id: TrainId): TrainTiming? {
// Une seule requête riche avant de lire le plan dans cette capture.
val snapshot = context.trains(TrainQuery(includeTimetables = true))
val train = snapshot[id] ?: return null
val times = train.service?.times
val plan = context.linePlan(id)
return TrainTiming(
id, train.assignment?.timetable, train.assignment?.shift,
train.assignment?.orderIndex, times?.observedAt, times?.arrival,
times?.departure, times?.dispatchRetry,
train.predictedArrivalDelaySeconds, plan,
)
}
// Conserver les offsets ; ne pas fabriquer une date de prochain passage.
fun plannedDwells(plan: LinePlan): List<Pair<Int, Long?>> =
plan.stops.map { it.index to it.plannedDwellSeconds }
package wiki.jvmtraintimetables
import fr.nimby.sdk.*
import java.time.Instant
data class PlannedStop(
val index: Int,
val station: Station?,
val arrivalOffsetSeconds: Int?,
val departureOffsetSeconds: Int?,
val dwellSeconds: Long?,
)
data class TrainTiming(
val id: TrainId,
val timetable: Timetable?,
val shift: TimetableShiftId?,
val orderIndex: Int?,
val observedAt: Instant?,
val arrival: Instant?,
val departure: Instant?,
val dispatchRetry: Instant?,
val predictedArrivalDelaySeconds: Double?,
val stops: List<PlannedStop>?,
)
fun readTiming(game: Game, id: TrainId): TrainTiming? {
// selectedTrain ajoute son plan ; la liste trains reste une capture par lot.
val snapshot = game.trains.snapshot(
selectedTrain = id,
query = TrainQuery(includeTimetables = true),
)
val record = snapshot.train(id) ?: return null
val service = record.service
val stops = snapshot.lineStops?.map { stop ->
PlannedStop(
stop.index, stop.station?.let(snapshot::station),
stop.arrivalOffsetSeconds, stop.departureOffsetSeconds,
stop.plannedDwellSeconds,
)
}
return TrainTiming(
id, record.details?.timetable, record.details?.shift,
record.details?.orderIndex, service?.observedAt, service?.arrival,
service?.departure, service?.dispatchRetry,
record.metadata?.predictedArrivalDelaySeconds, stops,
)
}
readTiming conserve chaque absence, les identités composées et la nature des instants. Affichez séparément le retard prévu, les échéances du service et le plan. Les modèles PlannedStop et TrainTiming peuvent être testés avec des valeurs connues, absentes, anticipées ou déjà passées sans lancer le jeu.