TrainApp is a Go-based railway simulation. It models how trains move through a railway network, how they follow schedules, and how the system reserves track resources to avoid conflicts.
This project is a simplified railway operations simulator. The goal is not to build a graphical app, but to model the core logic behind railway movement in a programmable and testable way.
The simulator is meant to answer questions such as:
- How is a railway network represented as data?
- How does a train move through a sequence of connected resources?
- How can the system reserve a path before allowing movement?
- How can conflicting movements be avoided safely?
- How can the system advance through time using discrete events rather than a continuous loop?
The program builds a sample railway world and runs a discrete-event simulation. It demonstrates how a railway system can be represented in code and how trains can be scheduled and coordinated safely.
The emphasis is on modeling the underlying mechanics of railway operation rather than on a polished UI or full operational realism.
At a high level, the system contains four major parts:
-
Infrastructure
- stations and platforms
- track segments
- points and switches
- block sections (modeled, not yet enforced by the dispatcher)
- route topology and connectivity
-
Trains
- train identity and schedule
- current location and movement state
- route requests and progress
- arrival and departure behavior at stations
-
Control logic
- dispatcher decisions
- interlocking and safety checks
- route allocation for shared resources
- queueing and retry behavior when a path is blocked
-
Simulation engine
- event scheduling
- time advancement
- state updates as trains move and arrive
- coordination between train activity and infrastructure state
.
├── main.go # Entry point (work in progress, see below)
├── worlds/ # Sample railway worlds used by main.go
│ ├── testWorld.go # Older 3-station world (TPJ-PDKT-KKDI) with block sections and trains
│ └── tpjKkdiWorld.go # Newer TPJ-KKDI world with crossovers and candidate-path generation
├── railway/ # Core simulation code
│ ├── world.go # Main container for stations, tracks, trains, and state
│ ├── train.go # Train model, schedule, and movement logic
│ ├── stations.go # Station and platform definitions
│ ├── tracks.go # Track segments and route pieces
│ ├── points.go # Track points and node behavior
│ ├── switch.go # Switches, diamond crossings, and slips
│ ├── blocks.go # Block sections and occupancy control
│ ├── graph.go # Track graph and pathfinding over the network
│ ├── path.go # Path representation and reservation checks for a train's route
│ ├── authority.go # MovementAuthority type for granting/extending a train's authorized path
│ ├── signals.go # Signal type definition (not yet wired into the simulation)
│ ├── templates.go # Helpers to build common station layouts
│ ├── dispatcher.go # Route granting and conflict handling
│ ├── interlocking.go # Safety rules for shared resources
│ ├── sim.go # Simulation orchestration
│ └── events.go # Event definitions and handlers
├── des/ # Discrete-event engine
├── units/ # Distance and speed helpers
├── example/ # Example simulation output
├── dumps/ # Example output files
├── logs/ # Runtime logs
├── go.mod # Go module definition
└── README.md # Project documentation
The architecture is organized around a simple idea: model the railway as a set of connected resources, then let trains request access to those resources over time.
The World object is the top-level container. It holds the full railway model, including stations, tracks, points, block sections, and trains. It is the shared context in which all other components operate.
The network is built from connected railway elements:
- Track points act as nodes in the topology
- Track segments connect those nodes and represent physical travel sections
- Switches change route choices at junctions and influence path selection
- Block sections are intended to protect track sections so trains do not occupy the same space at the same time, though this is not yet enforced by the dispatcher
- A
Signaltype exists to represent signals at track points, but it is not yet instantiated or used by the simulation - A track graph connects points and segments into a searchable network, used to find a path between two points and to check that every segment on that path is reserved for the train using it
TrackGraph.GenerateCandidatePathsis an in-progress replacement for pathfinding that enumerates every possible path between two tracks instead of just one; the olderFindPath/FindPathToTrackare kept for reference but marked@Deprecated- A
Pathcan be extended track-by-track as a train's route grows
This layer is responsible for representing the physical form of the railway and the possible movement relationships between elements.
Each train has a schedule and a lifecycle that can be understood as a sequence of state transitions. It can:
- enter the network
- request a route
- reserve required resources
- move along track sections
- arrive at or depart from stations
- wait when a route is unavailable
- continue to the next leg of its journey
This layer captures the train as an active participant in the system rather than as a passive record.
This layer makes the system safe. The dispatcher decides whether a train can proceed, and interlocking logic prevents conflicting access to shared resources. In practice, this is the layer that turns abstract topology into coordinated train operation.
The discrete-event engine drives the system forward. Instead of running a continuous loop, it processes events in time order, such as:
- train arrival
- train departure
- route grant
- route rejection
- movement update
- resource release
This separation allows the system to reason about time explicitly and to update state only when meaningful events occur.
Movement authority (MovementAuthorized / MovementAuthorityEnded) is defined as an event type in events.go, and a corresponding MovementAuthority type now exists in authority.go to hold and extend a train's authorized path. Neither is yet triggered or handled by the dispatcher or event engine.
main.go is being reworked and currently contains two versions of the entry point:
- Active (
func main): callsworlds.BuildTpjKkdiWorld(), which builds a two-station world (TPJ and KKDI, each with four platforms and a crossover) and does some basic ad-hoc testing ofTrackGraph.GenerateCandidatePaths. No trains are scheduled and the discrete-event simulation is not started. - Commented out, kept for reference: an earlier
func mainthat callsworlds.BuildTestWorld(), creates trains with schedules viaTrain.AddSchedule, adds them to the world, and runs the full discrete-event simulation withsim.Init()/sim.Run(). This reflects the older, more complete end-to-end flow described below and will be reconciled with the candidate-path work above.
The worlds/ package holds both sample worlds (testWorld.go, tpjKkdiWorld.go) so main.go can switch between them while this is in progress.
This is the flow the project is built around, and the one exercised by the commented-out simulation path in main.go (see above); the currently active main.go only builds a world and lists candidate paths, and does not yet run this flow end to end.
- The railway world is built (see worlds/) with stations, platforms, tracks, points, and block sections.
- Trains are created and assigned schedules that define when they should arrive at or depart from stations.
- A train requests a route when it wants to move.
- The dispatcher checks whether the required resources are free and whether the route is safe.
- If the route is accepted, the train occupies the relevant sections and continues moving.
- As each train progresses, the event engine updates the state of the world and triggers the next relevant event.
- When a route cannot be granted, the system waits for the blocking resource to be released or for the train to be re-evaluated later.
- The process repeats until the trains complete their planned journeys or the simulation reaches its end condition.
The main design choices are:
- Event-driven simulation: time is advanced through scheduled events rather than a real-time loop.
- Explicit resource reservation: trains must reserve shared track resources before using them.
- Graph-based railway representation: the network is modeled as connected nodes and edges.
- Separation of responsibilities: infrastructure, train logic, control logic, and the engine are kept distinct.
- Simplified realism: the project focuses on the core ideas of railway operations instead of full operational detail.
- Deterministic behavior: the flow of events is organized so the model can be reasoned about and extended predictably.
- Go 1.26.3 or newer
From the project root, run:
go run .go build -o trainappSee TODO.md for planned work and improvements.
See LICENSE for the license text.