NIMBY Rails devblog 2025-01
1.16 is dynamic schedules (actually dynamic train dispatching, but more on that later)
The goal of 1.16 is to implement dynamic schedules, and see how it goes from there. What exactly that means, and what is already implemented in 1.16, will be explained in this post, but it’s by no means the end of it, since some very interesting possibilities have now opened up.
But first, “dynamic schedules” needs to be defined. A dynamic schedule is a combination of schedule options and train behavior which in the end results in trains choosing from multiple possible shifts to run, rather than always running a specific shift. Properly implementing this has multiple effects in the game sim, specially in the train sim. I will explain the incremental changes in the game systems which when combined, result in the dynamic schedules feature.
Multiple trains per shift
Every journey starts with a single step, and the most obvious first step was making it possible to enable multiple trains to a single shift:

In 1.16 you can enable zero to N trains for any individual shift in any schedule. This new UI in the schedule editor allows to select trains and add them to the train-shift matrix, then to enable them for any train-shift combination. The same train can be enabled in any amount of shifts, including shifts from different schedules. There’s no limitations to this feature. If you are patient enough you could enable every train in every shift in every schedule if you wanted (and get a nice slowdown in the sim, of course).
Keep in mind this UI is about enabling (in the sense of allowing) trains to run a given shift. It does not assign trains to shifts! This is because in 1.16 the concept of assigning a train to shift is now out of control of the player. It is now under control of a new part of the train AI. Of course, if you only enable one singular shift for a given train, the train AI will only ever pick that singular shift, reproducing the 1.15 behavior. This is how all 1.15 saves are imported and it remains possible to edit schedules that way.
What also remains true is the fact that, once a shift is assigned to a train, only one train can run it. Before said shift can be assigned to a different train, it first must become unassigned again. But how to accomplish this?
Shifts with holes
It is now possible to configure in the train orders what is the behavior of the train after a given order is completed:

Checked “Continue into next order” is the default, and it corresponds with the behavior of the game since v1.5. It means the train will continue into the next order, waiting to do so if needed.
But when unchecked, it means the train stops running its current shift, becoming unassigned. This has implications for both the train and the shift timetable. For the shift, it means there’s now a hole in its runs timetable:

Indeed, using this new feature, you can now make shifts full of holes:

There’s really not much more going on than this at the orders level for this feature, at least currently. Other options might become possible later on, and I will give some hints at the end of the post.
But for the train, becoming unassigned means it remains stopped where it is at the moment. This seemingly simple behavior has very deep implications. Indeed nothing like this has ever existed in the game.
Refactoring train motion AI: dynamic train dispatching
How so? Ever since v1.1, 4 years ago, the one singular rule which has always held up in the train AI to this day is this: a train is always running some orders, and it always has a destination. This stops being true in 1.16.
In 1.16 trains can now exist spawned on the tracks with no shift assigned to them, and therefore they have no orders and no destination. This is a very major change to the train AI. In previous versions the game logic which runs the trains orders was intimately tied to the game logic which drives the trains on the tracks. At one level it was deciding on target speeds and taking a branch, then on the next level it was looking at the run table for the next destination. Building on work started in v1.8, the train driving logic has now been made completely independent from the destination logic.
In place of the hardcoded “run a shift” logic, there’s now a new concept in the train sim: dynamic train dispatch systems. These systems provide information on where to go and when (and how long) to stop to the train driving logic, without said driving logic knowing anything about schedules or runs. At the same time, these systems cannot control the train driving in any way, they can only provide new destinations or stop information. They are black boxes to each other.
Now, when a train is running a shift, it means a dispatch system is providing new destinations to it as it reaches its next destination. In particular it means the schedule dispatch system is currently tasked with dispatching the train. When said system encounters a hole, it stops giving destinations to the train, removes the train-shift assignation, and erases itself from the train data. The train then remains stopped on the tracks, and this is the crucial part, flags itself as available to be dispatched by any other system.
What other system? Well, currently, only the schedule dispatch system. While the train is this state, the schedule dispatch system will try to find it a new shift to run. To do so, all of following conditions must hold true:
- The shift must be currently unassigned
- The train must be enabled to run the shift
- The current sim time-of-week must match the stopping time (be between arrival and departure times) of a stop in a run in the shift
- The current location of the train must also match the platform set of said stop (be on the main or the secondary platform of the stop)
In other words, if the current train location and the sim time match that of an unassigned shift stop, then said shift can be assigned to the train. If multiple trains match, only one train will be assigned to it. After the train is assigned it goes under the schedule dispatch system control, and it starts giving the train stop and destination orders (the very first one usually to complete the stop it matched against to). Intervened or newly purchased trains must also match all the previous rules, except rule 4, before they are spawned.
So here it is, trains can now change their shift by entering a new unassigned state, and letting a dispatch system find what to do next. The first implemented dispatch system, the schedule dispatcher, just emulates the 1.15 behavior, with the new capability of having holes in the shift which return the train to the unassigned state. Becoming dispatched by the schedule dispatcher requires matching a strict set of rules, but it guarantees no wild unpredictable paths from across the map to take a shift which is still hours away from starting, for example.
But what about the pax!?
A common misunderstanding with this game design is what schedules/timetables are supposed to be for. Players often believe the main motivation of the schedule system is to tell the trains what to do. This is wrong. The schedule system exists for the pax. If pax didn’t exist, or if they were much dumber, the schedule system would not need to exist in the way it is now. It could be both much simpler (“here’s a list of stops, go run in them in order”), or much more flexible (“here’s 10000 lines of player provided code full of conditionals and nondeterministic inter-train coordination to tell the trains what to do”).
But pax must be able to plan multi leg trips, and to do so they need timetables. So the trains need to run those timetables, and need to do so in a predictable way. A train suddenly changing into unassigned state or into a new shift makes the job of the pax pathfinder impossible. Or if the act of changing into a new shift was so deterministic that it was possible to predict its outcome for the pax pathfinder, it means said feature would be equivalent to the existing schedule system, making it redundant. Therefore a hard decision had to be made.
Pax always understand shift holes as a technical maneuver and will never plan paths past a hole. It doesn’t matter if the player then sets up the possible shifts as pax service (from the same platform even! remember the previous rules). I understand this might seem too restrictive to some players. But making shift holes always be a technical maneuver opens up an amazing new possibility: train dispatching without schedules.
1.16 is done but it is also not done
While I was arriving to the conclusion I explained in the previous section, I came to a revelation: if a train is not being dispatched by a shift, it can do anything, since no pax is waiting for it. It can stay where it is. It can go to a depot 10km away. It can go do 100 laps in a test track. It can just flip flop on the platform for 20h, blinking its lights and playing with the horn. As long as pax are concerned, it does not exist. And as long as it respects the driving rules, the other trains don’t care either.
As I spoiled earlier, what if the schedule dispatch system wasn’t the only dispatch system in the game? What if other systems, unconcerned by/from pax, were also able to give destination and stop orders to trains? What if these different systems were able to coexist with each other, even coordinate with each other? What if shift holes were actually the escape hatch in the schedule dispatch system to allow the train to become controlled by some other dispatch system?
1.16 is already feature complete for its goal, namely to allow for dynamic shift train assignment. A lot of complex behaviors are already possible with the mechanics explained earlier, and the difficulty of setting them up is equivalent or easier to anything you already could do in 1.15 with technical maneuvers, except now trains can freely hop into a different shift if they comply with the rules to do so. But before I commit to a release I want to explore further the new concept of multiple dispatch systems, and see where it goes.