Créer un mod
Signaux et réseau
Plusieurs modèles dans un mod, des règles locales et des décisions liées au signal suivant.
Garder la même composition pour chaque modèle
Le point d’entrée assemble les modèles avec signalMod. Chaque modèle a son dossier et les mêmes rôles. Commencez avec peu de fichiers, puis séparez les rôles ci-dessous quand le modèle grandit ; les fonctions Kotlin se branchent directement dans signalModel.
| Rôle | Contenu du modèle |
|---|---|
| Déclaration | Identité, catalogue, replis et raccordement des fonctions au SDK. |
| États et motifs | Deux enums locales et éventuellement un alias Indication pour la décision. |
| Panneau et réglages | Cases visibles, libellés et valeurs utilisées par les règles. |
| Règles | Choix de la décision à partir du contexte SDK et du voisin. |
| Textures | Choix du SVG et phase de clignotement au temps simulé. |
| Conduite et vitesses | Consigne générique et valeurs choisies par votre modèle. |
| Diagnostics | Décisions considérées comme des anomalies. |
Les intégrations facultatives communes à plusieurs modèles appartiennent au niveau du mod. Ne recopiez pas le contexte SDK, le parcours du réseau ou un moteur de freinage dans chaque dossier. Le SDK prépare ces informations et exécute les consignes ; les règles restent chez vous.
Un modèle, ses propres enums
package wiki.models
import nimby.*
// Deux vocabulaires indépendants, même si leurs ordinaux commencent tous à zéro.
enum class MainAspect { Closed, Open }
enum class MainReason { Unknown, Clear }
enum class DistantAspect { Wait, Proceed }
enum class DistantReason { Unknown, MainClosed, MainOpen }
val mainSignal = signalModel(
"mon-reseau.principal", "Principal", "textures_principal",
fallback = Indication(MainAspect.Closed, MainReason.Unknown)
) {
rules {
if (fresh && routeKnown && block == Occupancy.Clear &&
settingsStatus != SettingsStatus.Unavailable &&
!observation.forcedStop && !observation.lampFailed)
Indication(MainAspect.Open, MainReason.Clear)
else Indication(MainAspect.Closed, MainReason.Unknown)
}
images { if (it.aspect == MainAspect.Open) "main-open.svg" else "main-closed.svg" }
}
val distantSignal = signalModel(
"mon-reseau.annonce", "Annonce", "textures_annonce",
fallback = Indication(DistantAspect.Wait, DistantReason.Unknown)
) {
rules {
when {
!fresh || !routeKnown || block != Occupancy.Clear ||
settingsStatus == SettingsStatus.Unavailable ||
observation.forcedStop || observation.lampFailed ->
Indication(DistantAspect.Wait, DistantReason.Unknown)
next == null -> null // Demander au SDK de calculer le voisin.
else -> when (next?.of(mainSignal)?.aspect) {
MainAspect.Closed -> Indication(DistantAspect.Wait, DistantReason.MainClosed)
MainAspect.Open -> Indication(DistantAspect.Proceed, DistantReason.MainOpen)
null -> Indication(DistantAspect.Wait, DistantReason.Unknown)
}
}
}
images { if (it.aspect == DistantAspect.Proceed) "distant-proceed.svg" else "distant-wait.svg" }
}
// Exemple de composition et de lecture : aucune consigne de conduite déclarée.
fun createNetworkMod() = signalMod("mon-reseau", "Mon réseau") {
signal(mainSignal)
signal(distantSignal)
}
Chaque modèle possède ses enums d’indication et de motif, ses replis, ses règles, ses images et ses consignes. Le mod ne fait que les assembler. Ses cases sont locales : deux modèles peuvent avoir une case active avec des défauts différents.
Cet exemple illustre le dialogue entre deux modèles fictifs. Il ne fournit aucune consigne de conduite : ajoutez driving selon vos règles et déclarez les quatre SVG dans votre catalogue de ressources.
| Élément | Rôle | Exemple fictif |
|---|---|---|
| Mod | Paquet installé, identité et liste des modèles. | mon-reseau |
| Type de signal | Règles, cases et catalogue propres à un modèle constructible. | mon-reseau.principal |
| Signal posé | Une instance de ce type sur une voie, avec son ID et ses réglages. | Un signal choisi dans une partie |
Créer un nouveau type ne nécessite pas de créer un autre mod. Choisissez un ID de mod distinct de ceux des types. Un fichier par modèle permet de faire évoluer ses règles sans ajouter des conditions partout dans le point d’entrée.
Comprendre next
Le SDK tente d’abord une décision locale avec next = null. Si votre règle renvoie null, il résout le signal suivant et rappelle la règle avec sa décision. Un lien absent ou un cycle sans décision locale utilise invalidNetwork. Une fermeture locale peut donc interrompre la dépendance au voisin.
val voisin = next?.of(mainSignal)
// Indication<MainAspect, MainReason>?
// null : pas ce modèle (ou pas encore de voisin).
val idDuVoisin = next?.id
val typeDuVoisin = next?.type?.id
val consigneDuVoisin = next?.drivingRuleLe SDK fournit directement next à tous les modèles : ID, type, décision et consigne déclarée par le voisin. Aucun adaptateur ni fichier Neighbours à écrire. Vous pouvez lire sa consigne générique ou utiliser of(mainSignal) pour ses enums. Le modèle amont reste responsable de son interprétation et du repli pour les informations inconnues.
Le contexte fournit aussi settings, observation, fresh et settingsStatus. Un profil absent reçoit les défauts déclarés ; un profil indisponible rend l’observation non fraîche. Le statut est conservé pour vos règles. next concerne le lien aval résolu dans le réseau du mod, pas tous les signaux géographiquement proches ni les décisions privées des autres mods.
Une règle qui dépend du voisin doit aussi décider comment traiter ses propres observations inconnues. Le SDK ne transforme pas automatiquement un canton inconnu en canton libre.
Observer un train en approche
signalModel("monmod.approche", "Signal à l’approche", "textures_approche",
fallback = Indication(Aspect.Closed, Reason.Unknown)) {
observeApproach = true
rules {
if (fresh && routeKnown && block == Occupancy.Clear && approachingTrain != null
&& !observation.forcedStop && !observation.lampFailed)
Indication(Aspect.Open, Reason.Clear)
else Indication(Aspect.Closed, Reason.Unknown)
}
images { if (it.aspect == Aspect.Open) "open.svg" else "closed.svg" }
}approachingTrain contient un identifiant de train lorsque l’observation trouve son premier signal orienté dans le sens de marche. C’est une observation géométrique en amont ; elle ne prouve ni réservation ni autorisation de mouvement.