NIMBY Rails devblog 2023-03

Fixed departure times

With 1.7 came another round of reviews and fixes for the line leg estimation. This time it was focused on braking times and properly timing paths involving reversal of direction. The concept of stop departure was also changed to properly account for the braking time. All these changes improve the game mechanics, but create extra work for players when loading a previous version save into 1.7.

To help with this situation in the future, fixed departures times have been introduced. It is now possible to, rather that give a specific stop time for a given line stop, to give a time value relative to the start of line. The game will automatically set a stop time which respects that time, or give a warning it if becomes impossible to do so (with at least 10s of margin). This happens automatically, even if the change of times is due to bug fixes. But it is also useful for making changes to your tracks which modify leg times.

This feature is for now handicapped on the fact “start of line” time means crossing into the middle of platform going into stop 1 of the line. This will be addressed in 1.8, which aims to change the definition of stop arrival to be the intuitive one (a train arrives when brakes down into 0 speed).

Shared games world map

The sharing feature now has a proper in-game map, which has replaced the random zoom backgrounds of past versions. It’s just a simple world map in 1.7, but in 1.8 it will gain extra features.

Mod cleanup

A new button labelled “Disable all unused mods” in the in-game mods panel automatically disables mods which have zero usage in your save (so no track, building or train provided by said mod is in use).

Another button, “Remove all orphaned and unused rules”, does the same thing at the “rule” level (rules are the individual mod objects like train cars or track kinds). Sometimes mods are updated and the newer versions drops a rule, but in order to avoid breaking your save, the game keeps the rule stored as part of the save. This button deletes all of these rules as long as they don’t have usage in the save.

Store initial multiplayer save as a hidden shared game

The connection relay system used by the game (Steam Relay) is optimized for latency, not for throughput, and has a (undocumented but very empirically proven by now) bandwidth limit of 128 KB/s. Joining a multiplayer session always involves an initial download of the current snapshot of the game, which is basically a saved game file minus some header information. Even when compressed this data is at least few megabytes, and for larger builds it can run into several tens. Downloading this data at 128 KB/s is no a good experience.

To solve this the hoster will now automatically upload the save snapshot to the shared games server, and send the URL to the client. These saves are hidden, stored separately from the regular shared games, unlimited, and are automatically deleted after some time. This system has now been live for a couple weeks and it’s working very well, but if my server ever goes offline, or if there are connections problems either by the hoster or the client, it will automatically fall back to sending the snapshot via Steam Relay.

Likes for shared games

Sharing saved games has proven to be quite popular, with almost 300 uploads in March. The world map is now quite busy in some areas so it was time to start introducing some extra features for browsing it. This will be addressed in 1.8 for the most part, but for 1.7 I wanted to introduce “likes”.

Since there is no concept of subscriber count for shared games, like in Steam Workshop mods, I hope likes serve as a kind of replacement.

(v1.8) Shared games browser

Likes are just one of the data points you can use to sort and browse shared games by in v1.8:

The default browsing mode is still zoom/pan the map and hover on top of save icons, but if you open the left panel in the same screen, you can use a sortable listing of all the shared games. I will keep an eye on how popular sharing is becoming and improve this browsing system as needed.

(v1.8) Unified branch and append track tool

This is something I’ve been meaning to do for ages, and it’s finally here: the branch tool is no more. Instead you can create (and merge!) branches directly from the track append tool:

The append tool is basically the same, except that when hovering the mouse on top of a track, and away from any “+” track end control, you get the branch icon. If you then click, you start a branch at that point, and you are still in track append mode (which is what the branch tool did, anyway).

But if you are currently “holding” a track, in the mode which allows you to keep adding segments or to link it to another track, it is now possible to merge the track into another just by moving the mouse on top of a different track. This will automatically create a branch (if the track rules are respected). You are free to move the mouse along the track to choose the branch point, or even away from the track, to discard the branch and keep building normal track.

(v1.8) Optimized user leg timer

I’ve been having fun downloading shared games and using them to test the game. One of the bigger ones is a recreation of UK lines with realistic timetables. And it is very slow to load, seemingly stuck in the “Initializing timetables” step for way too long. It turns out this save is making massive usage of user leg timings. This feature is slow, since it is based on an iterative solver which repeats train simulations until it finds a “good enough” service speed for the leg (and sometimes it never manages to, with a cap at 100 attempts per leg).

1.8 will optimize this feature greatly. First, the criteria for accepting a simulation as “good enough” has gone from 1s to 3s. This means 1.8 accepts a service speed which makes the train run the leg with a maximum difference of 3s compared to the user leg time. Second, the iterative solver now makes pseudo-random (seeded deterministically for MP and consistency), quadratic “perturbations” over the main estimator, to avoid failing into periodic errors. The combination of these two changes greatly improves the performance, from an average of 8 sims per leg required in 1.7 to 2.5 sims per leg in 1.8. And third, the limit for giving up is now just 15 iterations rather than 100. This is perfectly fine, because in addition to being much faster, the new estimation is also much better at finding good leg speeds, with 10x less failures compared to 1.7

(v1.8) Start of track + point pathfind

Finding a path over tracks has always been done with “pairs of tracks” steps. This means that the origin and destination of a path over tracks is always a track segment followed by the next step a train would take. This is done to imprint directionality to every step a train takes, and it shows up in other areas of the game, like the way platforms are always two track segments.

This concept is one of the most core parts of the game logic and AI. A lot has changed over the past two years, but this has remained untouched. But it limiting in some ways. In particular it cannot express correctly the act of starting or ending a path on a specific point of a track. Ever wondered by platforms are now quite limited on what track constructions and signals are allowed? This is one of the main reasons.

1.5 already made it clear this was problematic. A very insidious bug surfaced with the new secondary platform feature and trains waiting at the designated signal taking wild paths to their platform. It was a two faced issue, with changing a train destination outside of its stop not being properly supported, and the pathfinding done for this signal case being quite tricky and having to compensate for the underlying pathfinding algorithm not being able to really start a pathfind at an exact spot of the track. I realized I was building more and more of these special cases rather than tackle the problem at the underlying level.

So for 1.8 I am finally going to make track pathfinding always be based on a origin and destination expressed as a single segment of track, a point in said track, and a direction. What makes this possible at all is that is only needed for the origin and destination. The intermediate steps can remain based on pairs of tracks since that is working just fine. Indeed, if a exact point by point trace of a path is required, there is already existing code to transform said simplified set of steps into a detailed trace. This is how train AI has always worked, for example. But now this capability will also extend to the origin and destination.

I am going to slowly migrate systems to this (half) new pathfinder. The systems can coexist, at least while 1.8 is private. If the new system is successful, it will enable new gameplay and much needed improvements, like making line stops match their intuitive definition. No more “middle of platform” timing and complicate rules on how and where braking is substracted.