Reading and acting
Observe trains: queries, identities and states
Build train cards from only the required groups and preserve the meaning of unknown data.
Choose the entry point
You will obtain a batch of trains, join their information and prepare display values. A Native tool must already receive an active ToolContext; a JVM application must already own an open Game. Results are retainable copies, but a ToolContext belongs only to the callback that supplied it.
| Need | Native | JVM |
|---|---|---|
| Batch of train data | context.trains(query) | game.trains.snapshot(query = query) |
| Join an identified train | snapshot[trainId] | snapshot.train(trainId) |
| A train’s line plan | context.linePlan(trainId) | game.trains.snapshot(selectedTrain = trainId, query = query) |
| Targeted driving measurement | Driving callback observations | game.trains.read(trainId) |
Compose a query for the screen
TrainQuery selects additional data. Declare all the calculation’s needs together, then reuse the batch. Defaults include service and locations; disable them for a screen that does not use them. In Native, a query already covered within the callback reuses its copy; a richer request may trigger a new read.
| Option | Default | What it requests |
|---|---|---|
| includeService | true | Service and operational state associated with the train. |
| includeLocations | true | Tracks and stations for location joins; does not request the platform table. |
| includeTimetables | false | Assignment and deadlines; implies service and tracks/stations even when includeLocations is false. |
| includeLines | false | Line catalogue, identities and available parent relationships. |
| includeTags | false | Tags and line declarations; implies the line catalogue. |
| includeCharacteristics | false | Characteristics of configured and current profiles. |
| includeComposition | false | Ordered composition and vehicle-model catalogue. |
| includePassengers | false | Observed occupant count; independent of capacity. |
The specialised capture does not request the whole map, signals, platforms, occupation, reservations or reserved path. Empty network tables in this profile therefore do not prove that the world is empty. For JVM platform data, request game.snapshot() and use Observation.platform. Request the network only when your feature needs it.
Join data without losing unknowns
TrainId, LineId, StationId, TrackId, TimetableId, TagId and VehicleModelId express the kind of identity. Their value is opaque: do not decode it or replace it with a name. Use indexed batch lookups: get and line in Native, train, line and station in JVM. Vehicles directly carry their model; there is no need to look it up for each card.
| Situation | Expected handling |
|---|---|
| Group not requested | Do not display “none”. Request that group if the screen needs it. |
| Missing nullable object or property | Keep “unknown” or “unavailable”; requesting data does not guarantee it exists. |
| Supplied nullable list is empty | No items in that observed list; this does not extend to unrequested tables. |
| Identity exists, join is missing | Keep the identity and display a fallback label without fabricating the joined object. |
In Native, Train groups available parts: a location request can supply a partial TrainService with presence and places, without service state. Callback requests accumulate; a false option does not remove a group already read. In JVM, Observation.train(id) returns a TrainRecord grouping train, service, details and metadata; service remains absent when its group was not requested. A train being present therefore does not guarantee all its enriched parts.
Distinguish movement, state and alert
Measured speed describes movement. TrainState describes an operational state and TrainAlert a reported condition; none of those fields grants permission to pass a signal. Interpret each enum within its domain, without assigning severity from its position in entries.
| Value | Meaning |
|---|---|
| null | Data unavailable or not requested at this location. |
| Unknown | The game explicitly reports its unknown state. |
| Other | An observed value does not match the SDK’s named categories. |
| TrainAlert.None | No alert reported; this is not movement permission. |
Build cards ready for display
package wiki.trainqueries
import nimby.*
data class TrainCard(
val id: TrainId,
val name: String,
val speedKmh: Double?,
val state: TrainState?,
val alert: TrainAlert?,
val positionStation: Station?,
val serviceStation: Station?,
)
// Call from a tool callback; do not retain its ToolContext.
fun readTrainCards(context: ToolContext): List<TrainCard> {
val snapshot = context.trains(TrainQuery())
return snapshot.trains.map { train ->
TrainCard(
train.trainId, train.name, train.speedKmh,
train.service?.state, train.service?.alert,
train.position?.station, train.service?.locationStation,
)
}
}
// null stays unknown; a known zero speed really is 0.0.
fun measuredSpeedKmh(card: TrainCard): Double? = card.speedKmh
package wiki.jvmtrainqueries
import fr.nimby.sdk.*
import java.nio.file.Path
data class TrainCard(
val id: TrainId,
val name: String,
val speedKmh: Double?,
val state: TrainState?,
val alert: TrainAlert?,
val positionStation: Station?,
val serviceStation: Station?,
)
fun readTrainCards(game: Game): List<TrainCard> {
val snapshot = game.trains.snapshot(query = TrainQuery())
return snapshot.trains.mapNotNull { train ->
val record = snapshot.train(train.trainId) ?: return@mapNotNull null
TrainCard(
train.trainId, train.name, train.speedKmh,
record.service?.state, record.service?.alertState,
record.positionStation, record.locationStation,
)
}
}
// One-off example; an application keeps its connection between reads.
fun readOnce(sdkLibrary: Path, gamePid: Int): List<TrainCard> =
Nimby.connect(sdk = sdkLibrary, processId = gamePid).use(::readTrainCards)
The functions return TrainCard values made only from copied data. Position station and service station are separate facts: a train may run between stations while having a service destination. The interface may display a dash for an unknown without modifying the SDK result.
Retain a copy without treating it as current
A copy remains readable after the callback or read, but does not update itself. In Native, worldId and generation describe its context; capturedAtMillis dates the capture and ageMillis is its age when copied, not a ticking counter. Clear associated calculations on a world or generation change.
In JVM, keep joins within a single Observation and invalidate your cache when changing games or connections. processId and gameHash provide process and version context; they are not a durable save identity. A later operation must check its own current preconditions.