NIMBY Rails devblog 2020-08

Internal development notes, very slightly cleaned up and commented a month later.

TL;DR: near-final lines model with timings and waypoints, depots, mid-level train sim rewrite, train scheduler, more procedural building generation experiments

Near-final lines model

Lines are the backbone of train circulation in the game. Just like in real life, at the simplest level, they are the sequence of stops that trains must make when they are assigned to them, and so far in-game they were little more that just that, a list of station stops.

It was time to review this prototype code and bring it up with the planned, final lines design. It was a very ambitious design, allowing to set the kind of stop, including stops that were actually guiding the pathfinder like “bypass stops”, or allowing to list a set of platforms in the same station for each stop. But they had a fatal flaw: they were meant to be the highest level of train orders, and as such they had to support features not really related to running a passenger line, like overnight storage.

I fought for a couple of weeks with the original design as written more than a year ago, but in the end I realized I needed to compromise and tone it down a notch. Instead of trying to control the 24/7 schedule of a train using the lines interface, I would divide the task in 2 different interfaces. This is both at a UI level and at a simulation modeling level. This new design makes it clear a line is just for the commercial passenger service of a train, and the train schedule is for making sure a train as a real time, 24/7 map of what is supposed to be doing at any given time. This way the schedule can contain multiple lines for the same given train or orders to store in multiple depots, plus handling all the implicit travel to accomplish those tasks.

The lines model is in any case still plenty interesting:

The most fun feature is the ability to (try to) control train timing. For each leg of the line the player can input a desired travel time, and then the train will try to limit its speed to match it. This is similar to what happens in real life: line timings never assume trains running at a 100% of the max speed available for a given train and set of tracks. Since this is difficult to estimate by the player, the game offers an automatic calculator, which takes into account things like the speed limit of each track in the line path. In essence it simulates a train on the track every time you click the estimate button. The player can then manually tweak the suggested values. It is also possible to change the default station wait time.

Waypoints have also been added, for example to force trains to take bypass tracks to avoid busy stations. It is even possible to give bypass legs a waiting time, with the train stopping on the tracks for the given time, in case players want to experiment with sidings and timing based flow control.

Depots

Following the trend for August, another long standing item from the original design was reviewed and discarded. A design goal of the game is to model a 24/7 real time simulation of a train, with actual night/day cycles in the demand. So like in real life, it would be required to store away trains in terminal stations or in big train yards. But after some testing, I realized it was definitely not fun to precisely schedule each train separately into track waypoints or station platforms. So I decided to introduce depots.

A mainstay of this genre is the magic train depot, capable of storing a number of trains in a limited space. It will be possible to schedule trains to get stored into depots or roll out of depots using the higher level layer of scheduling above lines: the train scheduler.

Train scheduler

Now that the two main ingredients were ready, it was possible to add the last piece of plumbing:

The train scheduler, seen here in its single-train editor version, is the piece of UI that allows to associate a train to one or more lines or depots (and maybe other objects in the future), for a given time and day, for any amount of them. It is mean to determine the purpose of 100% of the seconds in the week for a given train. It must be able to answer the question, “given the current time and day of the week, what is this train supposed to be doing?". For this reason the time slots are continuous, they don’t have a end time, only a start time. They end when the next slot starts, always. And a schedule must always contain one slot. The order kinds are limited (for now) to depot storage and to lines, but I may add waypoints at this high level too.

With this system it will be possible to schedule fleets of trains on the same line(s) easily. For example, give a set of trains the same weekly schedule, with the same slots, but for each train make the starting time of the line slot a few minutes later than the previous one. You got yourself subway-like timing in a couple clicks.

Unlike depots and lines the scheduler only exists as its UI for now, not as part of the simulation model, because of the…

Mid-level train sim rewrite

In case it’s not clear yet, August was a month of reviewing and rewriting, both the design and the code. Wanting to achieve the previous items meant I had to review and throw away code. In the case of the line schedule code, it was long overdue. The existing code was never meant to be more than a prototype, and it was very coupled to the low level train circulation code. Basically it is a bad design if the piece of code that calculates that the train has advanced 0.43 meters and now needs to transition into a new track is also the piece of code that decides the train has arrived in the station and now needs to pathfind to the next stop. These two aspects are related, but they should not be expressed at the same level and in the same function, so to speak.

This flaw in the code design meant that it was impossible to realize either the grandiose original design for lines, or the reviewed lines/scheduler split design. So I undertook the rewrite to make it possible, ripping out the code that makes such mid-level decisions like “stop reached” from the code that deals in meters per second over a time step.

Now that part of the code is generic and isolated from the pure circulation code, and in fact trains could happily run forever without any kind of lines or schedules. And more importantly, the schedule code is now also independent from any given concept of a train line, and it is prepared to schedule any kind of slot. I will tackle this task very soon, adding depot slots to the scheduler UI and finally hooking up this higher level scheduling concept into the new generic scheduling interface.

More procedural building generation experiments

And finally some lighter reading. I continued the experiments with building generation. Last month I achieved the most difficult part, identifying the blocks inside the polyline soup in the OSM map. Now within those blocks I can start trying out ideas to generate buildings. To begin with I needed to offset the polygons inward, to give some space to streets and roads.

The PhD way of doing this is to find the polygon straight skeleton. CGAL has working code for this, but I found it to be very slow, with the library completely focused on scientific computing (it uses arbitrary precision numbers, for example). My numerical robustness needs are “please don’t crash” and “I will take an epsilon of 0.1”, so I went looking for something else. I found the very well performing and simple to use Clipper library, which has both polygon offseting (with self intersection and holes, even) and boolean operations, which will come handy in the experiments. So now I had the polygons with an inset:

Next I wanted to finally add some buildings inside the polygon. I had a very simple idea: walk the perimeter of the polygon and create rectangles with one of their sides on the perimeter, picking a size and ratio that looks like the footprint a building would have in a dense urban area (less dense areas are easier):

The idea works:

I was skipping generating buildings on the corners for obvious reasons, and if they are not obvious, this image will make it obvious:

It was clear it was also needed to clip the buildings to the inset block perimeter, so the ones located in or near the corners don’t stick out:

The image on top also shows the Clipper union operation, which is automatically fusing together the buildings in the corners, after clipping them. This not always desirable. For example look at all the fused together blue buildings in the top block of this image:

Apart from the obvious lack of tuning in building sizes, I also need to find a better way of extruding them, to avoid the fusing outside of the areas (like corners) that require them. Once this is working for the super dense urban case, other cases are much easier to do. For example Brooklyn style dense but separate units are just a case of finding these same building footprints, but then using them as the lot inside of which a smaller polygon is generated, that never touches its lot border.