RailCommand Routes: Graph-Derived Paths and TrainController Verification

Last updated: August 15, 2026

Concept

A RailCommand route is a settable path across your layout. The key mental model (ADR-0230) is that the operable route set is DERIVED from the track graph — it is never hand-authored and never imported as source of truth. RouteDerivationService walks the graph between route terminals — every track Block, plus every leg-modeled (ported) Turntable or TransferTable, which is itself a terminal and never an interior node (the device node is one occupancy identity, ADR-0202) — honoring each turnout's port model, and records the Closed/Thrown turnout positions (and, for a ported movable-bridge endpoint, the required bridge position) that path requires. Because the routes come from the graph, they stay correct: any layout edit re-derives them, exactly like signal masts.

When you import a TrainController .yrrg file, RailCommand decodes its route records but does not treat them as the route set. Instead the imported TrainController routes become a per-layout verification corpus — an independent ground truth used to prove the imported connectivity matches what TrainController believed. This is why the product reports things like "113/150 TC routes verified by graph derivation": the derived routes are checked against the imported corpus. The decode reads both route populations a file can carry: the routes drawn on switchboard pages, and the embedded dispatcher routes some file-classes persist inside the dispatcher graph — each of those carries TrainController's own from/to block record, so on such a file the corpus arrives fully provenanced (e.g. 255 corpus routes on a file whose pages draw only 18).

Separately, Interlocking Routes (the /routes page) are the signal-to-signal reservations a dispatcher sets and locks in a live session — with turnout settings, conflict detection, and a lock state.

How To

  1. Import and read the summary. After a TrainController import at /app/railcommand/layouts/import/traincontroller, the result summary reports "N derived routes (M TC verification routes)" and, below it, the conformance line: e.g. 113/150 TC routes verified by graph derivation (37 underivable, 27 derived-but-not-in-TC). The line is green when nothing is underivable, amber when some routes are.
  2. Read the verdicts. Each corpus route carries a verdict: Matched (a derived route confirms that connectivity), Underivable (no derived equivalent — the detail names the exact endpoint blocks and turnout cells), and the informational derived-but-not-in-TC count (routes your graph supports that TrainController's set simply did not list — not a defect).
  3. Manage interlocking routes. Open Interlocking Routes at /app/railcommand/layouts/{LayoutId:guid}/routes. The cards show Total Routes, Active, and Conflicts Detected; filter by All / Active / Inactive. Each row lists Name, Entrance / Exit Signal, Turnouts, Speed, Status, and Lock. Use + New Route to open the builder at .../routes/builder.
  4. Set and lock a route in operate. In a live session from the CTC panel (.../ctc-panel), press Set Route to request-and-lock an interlocking route (a conflict-checked turnout and block lock) and Release to unlock it. The Routes page Lock column reflects the current lock state.

Troubleshooting

  • Conflicts Detected is red / a route has a red dot. Two routes demand incompatible turnout or block states; they cannot be locked together. Edit one route or release the conflicting one.
  • A TrainController route shows Underivable. The graph cannot confirm that path. Check the localized endpoints and turnout cells in the detail — common causes are a severed track ladder at a panel edge, or a turntable/transfer-table with no authored track-pairing. A leg-modeled (ported) turntable or transfer table is now a route terminal, so its bridge-to-track routes derive with the required bridge position carried onto the route; an unconfigured import whose stalls are drawn (the device decodes a stall count, but its position ring is not decoded yet) derives its bridge-to-track routes from the drawn legs as the position-agnostic union — the routes exist without a required bridge position, a warning names the device, and actuation stays configuration-gated: setting a route across the device is still refused in an operating session until its track plan is authored. This is a temporary stopgap, not a limit of the file format: TrainController does store the position table, and once RailCommand decodes it these devices will get exact position-tagged routes. If a ported device's bridge route is missing, derivation rejected it fail-closed — an endpoint port that did not resolve, an exit with no requiredBridgePositions tag, or duplicate tags on one port — and a warning names the device.
  • The drawn-stall union is scoped to unconfigured IMPORTS, and only those. Three other movable-device situations look similar and behave differently, by design. A device you placed by hand and never configured derives no ports and no routes — nothing was imported, so there is no drawing to trust as an author's intent. A device whose track plan is authored but does not validate likewise derives no ports and no routes, and a warning names the validation errors: you configured that device, so the error is the answer rather than a substituted union. And a configured device whose bridge-position tags are missing or duplicated for a port stays fail-closed as before. Only the imported-and-not-yet-configured device falls back to its drawing.
  • A turntable's stall routes are still missing after re-deriving an older layout. Re-derivation cannot decode anything — the stall count comes from the file at import time. A layout imported before this shipped picks up only the stall tracks that happen to touch the device's own cell; TrainController draws the glyph as one grid cell but renders it as a circle several cells wide, so the tracks that stop at the rim are exactly the ones missing. Derivation now says so in a warning naming the remedy: re-import the TrainController file, or author the device's track plan. On a layout that does carry a decoded stall count, a shortfall against that count is reported as its own warning rather than quietly filled.
  • Fewer derived routes than expected. Path enumeration is bounded; if a cap was hit, a loud warning names the specific block pair. Re-derive after fixing connectivity, or raise the derivation limits.
  • Set Route fails in the CTC panel. The requested route conflicts with an already-locked route, or the session is not ready (CTC readiness blockers must be cleared first).

Safety Notes

The web app is where you author and verify — derive routes, read conformance, and define interlocking routes. It does not actuate hardware. The Avalonia desktop presents live state and forwards operator intent; it does not drive trains. The local/UE5 runtime owns all safety-critical execution — interlocking conflict enforcement, movement authority, and physical turnout and signal actuation happen there, never in the browser or desktop. Setting or locking a route grants an authority window; it never itself moves a train. Treat a "verified" conformance result as an import-connectivity check, not an operational safety certification: a green verdict does not replace testing routes against your physical hardware before an operating session.