Railway sidings and train pathfinding
Posted: Thu Dec 19, 2024 7:33 am
The train pathfinding system in Factorio has confused me for quite some time, and I think I've figured out why: sidings don't work the way I'd expect them to (and it doesn't seem to be possible to make them work in quite the way I want, though I've found a method that comes close).
Take this image for example: This is a pair of sidings off of a long main line. The sidings have regular rail signals at their entry points, while the main line has only chain signals. What I'd expect is that trains would use the siding to wait if their path takes them past it, but they'd be blocked somewhere further ahead. Instead, sidings like this don't seem to be used only sporadically, with trains instead waiting to take a "lock" on their entire route at once, rather than accepting a path that makes forward progress and then stops at the siding. My understanding is that this is a quirk of the pathfinding algorithm: the main line is shorter (since it doesn't have the curves), so trains prefer to use it! This means that unless there happens to be a train in the block at the time, the main line will win the path cost check, and the train will attempt to take it, without considering the siding.
This leads to a workaround: have the "sidings" actually be bypasses, with the parking spots being on what looks like the main line!
Trains do actually use these correctly, but they're pretty unintuitive (I haven't seen any documentation anywhere on this technique) and kinda unsightly. There's also one substantial issue with this approach: trains stopped at this kind of siding will only attempt to proceed to the next siding every 30 seconds (or 5 if there are multiple destination stations with the same name).
In some cases, the main line and siding are the same length (e.g. if building it into some kinds of turns), and then I have no idea what happens.
The common alternative we tend to see is this: In this pair, both the sidings and the main line use regular (non-chain) signals. This forces trains to make use of the sidings, but has a clear problem: it's possible for two trains to end up waiting side-by-side, with one on the main line and one in the siding. This violates a basic rule of railroading: don't park trains on the main line! We'd all prefer not to be CSX, so this is generally seen as a bad thing.
Some players suggest using large "stacker" waiting areas at each station, but these take up a lot of space, particularly since each individual station generally needs its own. They also don't fully solve the "parking on the shared main line vs waiting to lock too large a mutex" problem like proper sidings do.
Ultimately, what I'd like is a way to tell the game that a given block is a siding (and what the associated main-line block is), so it can do something like this:
- First, attempt to pathfind with a small penalty applied to siding blocks (enough to overcome the length difference)
- If the route is clear and the train can proceed, go ahead
- Otherwise, search the route for mainline blocks with associated sidings, starting from the end and moving towards the train's current position
- For each such block, retry pathfinding with the siding-block penalty removed, and a corresponding penalty instead applied to the mainline block (enough to overcome a stopped train)
- Repeat backwards until either a route is found that allows the train to make forward progress, or all sidings on the route have been exhausted
Also, I'd want a repath to be triggered when any of the siding blocks along the current path become unoccupied, so trains don't end up waiting 30 seconds to make forward progress.
I do have one other issue with the current rail signaling system: there's no way to get efficient use of long mainline sections without using periodic non-chain signals, and non-chain signals mean you risk having trains park on your main line! This can be mitigated using the "bypass sidings" I described above, but it's inefficient to build those *everywhere* (in the extreme case, you end up basically quad-tracking with frequent crossovers). Take this example:
Here we have a main line leading to two stations. There are periodic chain signals, but they don't really do anything; functionally they're just cosmetic. But they shouldn't have to be! Let's see it in action:
As we can see, there are two trains attempting to use the line to reach their respective stations. The first goes ahead, it has to get all the way to its destination before the second can depart. Non-chain signals would solve this, but also create risk of trains waiting on the main line! We can do better:
- First, run a check on the immediate following block, and turn red if it's occupied (as with non-chain signals)
- Then, check for any trains in the rest of the chain (as is done currently)
- Instead of setting the signal red if any exist, check if they have reserved a path that will allow them to exit the chain, and only turn red if they haven't
This would allow the second train to always follow the first one (behind by one signal) without needing to introduce potential points where trains could end up waiting on the main line in automated operation.
It's plausible that there might be some complex chain-signal application around intersections or loops that I'm missing where this solution would break something; if so, I could imagine this could be applied only to chain signals with a configuration setting applied, or to a new third type of rail signal.
This post ended up being pretty long and rambly, and I hope it makes sense to everyone! If nothing else, the "bypass siding" technique seems powerful enough as-is to be worth documenting on the wiki somewhere.
Take this image for example: This is a pair of sidings off of a long main line. The sidings have regular rail signals at their entry points, while the main line has only chain signals. What I'd expect is that trains would use the siding to wait if their path takes them past it, but they'd be blocked somewhere further ahead. Instead, sidings like this don't seem to be used only sporadically, with trains instead waiting to take a "lock" on their entire route at once, rather than accepting a path that makes forward progress and then stops at the siding. My understanding is that this is a quirk of the pathfinding algorithm: the main line is shorter (since it doesn't have the curves), so trains prefer to use it! This means that unless there happens to be a train in the block at the time, the main line will win the path cost check, and the train will attempt to take it, without considering the siding.
This leads to a workaround: have the "sidings" actually be bypasses, with the parking spots being on what looks like the main line!
Trains do actually use these correctly, but they're pretty unintuitive (I haven't seen any documentation anywhere on this technique) and kinda unsightly. There's also one substantial issue with this approach: trains stopped at this kind of siding will only attempt to proceed to the next siding every 30 seconds (or 5 if there are multiple destination stations with the same name).
In some cases, the main line and siding are the same length (e.g. if building it into some kinds of turns), and then I have no idea what happens.
The common alternative we tend to see is this: In this pair, both the sidings and the main line use regular (non-chain) signals. This forces trains to make use of the sidings, but has a clear problem: it's possible for two trains to end up waiting side-by-side, with one on the main line and one in the siding. This violates a basic rule of railroading: don't park trains on the main line! We'd all prefer not to be CSX, so this is generally seen as a bad thing.
Some players suggest using large "stacker" waiting areas at each station, but these take up a lot of space, particularly since each individual station generally needs its own. They also don't fully solve the "parking on the shared main line vs waiting to lock too large a mutex" problem like proper sidings do.
Ultimately, what I'd like is a way to tell the game that a given block is a siding (and what the associated main-line block is), so it can do something like this:
- First, attempt to pathfind with a small penalty applied to siding blocks (enough to overcome the length difference)
- If the route is clear and the train can proceed, go ahead
- Otherwise, search the route for mainline blocks with associated sidings, starting from the end and moving towards the train's current position
- For each such block, retry pathfinding with the siding-block penalty removed, and a corresponding penalty instead applied to the mainline block (enough to overcome a stopped train)
- Repeat backwards until either a route is found that allows the train to make forward progress, or all sidings on the route have been exhausted
Also, I'd want a repath to be triggered when any of the siding blocks along the current path become unoccupied, so trains don't end up waiting 30 seconds to make forward progress.
I do have one other issue with the current rail signaling system: there's no way to get efficient use of long mainline sections without using periodic non-chain signals, and non-chain signals mean you risk having trains park on your main line! This can be mitigated using the "bypass sidings" I described above, but it's inefficient to build those *everywhere* (in the extreme case, you end up basically quad-tracking with frequent crossovers). Take this example:
Here we have a main line leading to two stations. There are periodic chain signals, but they don't really do anything; functionally they're just cosmetic. But they shouldn't have to be! Let's see it in action:
As we can see, there are two trains attempting to use the line to reach their respective stations. The first goes ahead, it has to get all the way to its destination before the second can depart. Non-chain signals would solve this, but also create risk of trains waiting on the main line! We can do better:
- First, run a check on the immediate following block, and turn red if it's occupied (as with non-chain signals)
- Then, check for any trains in the rest of the chain (as is done currently)
- Instead of setting the signal red if any exist, check if they have reserved a path that will allow them to exit the chain, and only turn red if they haven't
This would allow the second train to always follow the first one (behind by one signal) without needing to introduce potential points where trains could end up waiting on the main line in automated operation.
It's plausible that there might be some complex chain-signal application around intersections or loops that I'm missing where this solution would break something; if so, I could imagine this could be applied only to chain signals with a configuration setting applied, or to a new third type of rail signal.
This post ended up being pretty long and rambly, and I hope it makes sense to everyone! If nothing else, the "bypass siding" technique seems powerful enough as-is to be worth documenting on the wiki somewhere.