NIMBY Rails devblog 2021-06

Path signals

The first builds of version v1.2 only had simple block signals. The signal check for simple block signals is to look for a train inside the “block”, which is every reachable track segment from the signal, up to the next signal or balise. This includes track segments the train will never take, like those that start at diamonds or from branched tracks in the opposite direction. This has to be done because this block check is only looking for the trains themselves. So trains will be very safe when passing by a block signal, being sure no train is anywhere inside the block, but they will also wait for trains which are taking a path that will never cross their own.

Path signals have a much simpler check. Rather than checking every possible track segment in every direction and diamond, they only check for the track segments covered by the path of the train waiting at the signal. By itself this cuts down on the amount of checking to be done. But it also means that potentially conflicting trains are not detected unless they are already occupying some track segment of the waiting train path. To fix this a new concept has to be introduced: track reservations.

Train AI is now capable of storing a track reservation of the future path it will take to its current destination. These reservations are slices of track segments, and are internally represented in a way similar to train cars on the tracks. This makes the existing code for checking paths and blocks automatically compatible with them. With this feature path signals were complete; now, when they check the train path, they look for both trains and reservations.

After fixing some bugs, the initial path signals and track reservation system worked well, but it had a huge CPU cost for the train AI, making it several times slower in some of my test maps (it was less noticeable the more ridership a map had). The reason for the slowdown was the super paranoid track reservation system: all reservations were completely remade fresh on every frame. This is very robust but also very slow.

The next phase of development focused on optimizing the reservation system and it also ended optimizing all signals. Now reservations are only proactively refreshed when certain events occur during the train simulation, like starting from a stop, passing by a signal or balise, entering or exiting a branch, etc. These events are an order of magnitude less frequent that the “every frame” frequency of the original reservation. In addition to this, and to avoid useless stops, when a train is at speed and comes by a signal, the AI is able to request a refresh of all the potentially stale reservations it finds during an initial check of the signal in a single frame. And finally all signal processing is now done at much lower frequencies than the train sim AI rate. For example, once a train already decided to wait at a signal, it will only check it again once every 3s; this applies to all kinds of signals. These new lazy (for the reservations) and low frequency (for the signal checks) attributes of the signal system open the door for more complex signals that do more/heavier processing in the future.

Bug of the month

I don’t usually comment on bugs on these updates, but this one took weeks to track down. A bug was making busy stations stop being able to load pax into trains at some point, and completely filling them up. I was initially very puzzled because I fixed exactly such a bug just after the end of v1.1 development, and indeed the current public version of v1.1 still has it. v1.2 was launched with a fix, and I had more pressing issues at the moment than to revisit something I believed to be fixed, so I took a look from time to time, not finding anything. The weeks went by and after more reports I was finally able to get a couple saves which reproduced it. But of course, the bug goes away on reloading the game.

That usually means it has something to do with the transient simulation state, or with some cache. Even with this clue I was stumped. The saves worked perfectly in my normal testing. These were very big stations yet were filling and emptying normally, doing train loads of multi-K pax without an issue. Out of ideas I dusted an old fuzzer I haven’t used since the pre-release days of v1.0, which randomly creates lines, to create a large amount of random lines to add more varied pax routes. But just right out the bat, after barely seconds of fuzzing, the bug reproduced. It was too early for new pax to have picked up new lines, so it was clearly the act of creating or editing lines that triggered it. This made it much more likely a pax pathfinder cache bug, and I focused on that next. It turned out the cache was fine, but there was an extra step performed after caching a path, which tried to anticipate future pathfinds for some of the subpaths, and that was the bug. The code didn’t work, and it was introducing invalid paths in the cache. This was 99.999% invisible because pax figured out the good paths anyway, but if you tried hard enough and long enough (or tried on a very large / complex save not so hard), it managed to replace valid paths with the invalid ones.

New stock trains

Max outdid himself again with a new set of 13 trains. They are specified to try to fill some blind spots in the initial set of 7, which was for example very limited for trams and small trains in general, despite local trains being important for the game mechanics. Or deploying long regional and long distance lines without making every country a high speed rail utopia.

They are mostly based on real world trains, with some free interpretation to take advantage of the perspective. This also extends to the specs, based on the real specs but tweaked to fill the missing areas in the stock set. Curious players and modders can take a look at the mod.txt file of the base game and follow the links in the comments to read about the original trains.

Train direction flip

Trains are finally capable of reversing direction without warping like an accordion. What looked like a nice simulation upgrade detail turned out into a multi week development effort with multiple steps over several builds, with a huge complexity. The reason? Compatibility with v1.1 imported maps, and with existing station design in general. It wasn’t possible to do the obvious thing, which is just to make the train reverse direction in place, because when reversing direction inside a station it’s possible for the train to be longer than the platform, and it would make the train head stick out of the station, potentially even past a one way signal, thus getting the train stuck in place. Doing such a thing would immediately make many v1.1 saves and some v1.2 new saves require heavy editing to fix. Basically it has never been a game error to build a too short platform, with only a small penalty for train boarding speed, but never a train error that stops it requiring an intervention.

The solution was to still perform the old way of reversing direction, but within a single simulation step, thus appearing instant. This looks like a real direction reversion when the platform is long enough, and results in a folded train when it is not. The folded train might still look like an ugly broken thing, but at least it’s not invalidating the players effort. Enabling the train simulation to be capable of doing this was a huge effort with many ramifications, and frankly it would have been more correct and cleaner to just let trains reversing in too little space to flag an error, but now it’s done.

State of v1.2

v1.2 has already accomplished most of its development targets, with the only major remaining milestone being (user) track mods. It will be made the default version soon (probably just after the summer sale ends), and then the final v1.2 beta series will start.