NIMBY Rails devblog 2021-10

v1.3 released

v1.3 was released on the beta branch on October 15th. The focus of the initial build was on the new map data, with the other scheduled v1.3 features to be added as betas in the coming weeks. Map processing was going on until the very last day, since some serious performance problems were found in the map indexing step and had to be solved before the final map file was generated. But it made for a very good optimization effort, with the map processor now being able to index and pre-process the entire world data in six hours in a nine year old server.

User editable tags in trains and lines

One of the top requested features has been some kind of folder organization for lines. At the same time I had my own ideas for other category-like concept, namely tags, to organize and filter game objects. I decided to unify both ideas and now the game offers user editable tags that can be organized in hierarchies.

Players first create and nest tags in the new tag editor panel. Tags are generic and not object specific, any tag can be assigned to any game object (among those which allow it). The train mod tags have also been converted to use this system, and are automatically added to new trains.

The first implemented usage for tags is nested grouping modes for the line editor and the train editor.

Experimental tagged balises

My original motivation for tags was to enable signal filters. Basically allow signals to behave differently depending on how the incoming train (and its line) are tagged. As a first test, and after some interest by some players, I implemented a simple filter for balises. This filter makes the balise visible or invisible to the subset of matching trains, thus extending the reservation and signal checks of the train beyond the balise when the filter does not match.

This is just a minimal test. The matching is a very simple “any tag matches” check. The signal picked for the experiment is also quite simple in its functionality. More filtered signals will be introduced in the future, for example a filtered “no way” signal checked by the train pathfinder, like one way signals.

New rules for path signals and reservations

For v1.3 both path signals and reservations have been made “long”. This means they always trace the train path past station stops, and keep going down the train path in the line, until a signal is found, a full loop of the line is traced, or the 200km tracing limit is reached. Additionally, stopped trains do not lose their reservation. This makes signals and reservations more robust and rigorous, but also changed the behavior of some v1.2 builds. Combined with some early bugs in the new rules, it made for some fixing in the first two weeks of v1.3, both for players and for me.

On top of this work a couple more refinements were trialed and ultimately discarded. For example, bounding reservations by other trains on the tracks was tested, under the assumption the train shouldn’t be tracing past trains blocking their path. This assumption is wrong, since it’s entirely possible the tracing train is meant to enter a branch past the blocking train. Losing the last part of said reservation would remove the reservation on the branch, making this a possible collision scenario. Although this code was discarded, it was important because for the first time trains were detecting other trains beyond collision range. It could have been the basis for a “moving block” system, or for a distant braking system. In any case it also had a noticeable performance hit, so it was doomed effort.

Single layer vertex store for track tiles

Track drawing has never been particularly optimized, and given the complexity of builds since v1.2, it was time to review this system. v1.3 introduces a new instanced line rendering method which is faster and takes less VRAM, but even before this is implemented for tracks, an easier optimization was required. Tracks are drawn in 4 steps, with 4 separate layers. The original code was just duplicating the geometry for the tracks four times, but now this is done properly, storing a single layer and then configuring a separate draw operation for each layer while using the same geometry data. This adds draw calls, but the total number is still very low, while also reducing the required VRAM by 75%. And this is even before the line instancing code is adapted for tracks, due for later in v1.3.