Want to praise Simutrans?
Your feedback is important for us ;D.

My thoughts about working method designs and related stuff in extended

Started by Sirius, August 24, 2019, 11:39:22 PM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.


Hey there,
I love the wide diversity of working methods and the wide diversity of signals within these methods.
Especially for historical working methods, I didn't even know these existed.

However, I would call some of them not pretty well designed assuming they should work close to reality.
In this thread I will collect all thes things and I will share my ideas about how we could improve these.
I want to explicitly say that I don't want to call the whole signaling system bad, it's really awesome, but apart from bugs, there are some details that are not perfect by design.

I really love the idea of this feature!
However, I don't think it is pretty well designed for multiple reasons:
1. Inconsistency between diagonals and straights:
When a train travels along a long straight, it can see to the last tile in that direction, which is graphically already a diagonal.
When a train travels along a diagonal, it can see to the last tile that is graphically a diagonal, but the last tile in that direction would be first tile of the straight.
Given there are only 8 directions in Simutrans and there are no real curves, i would expect trains to see at least everything at a straight in moving direction until some given max sighting distance.
In the case of diagonals, this means the train should be able to see that first graphically straight tile behind the diagonal since it is still on that line.
Also, for slopes, a train should be able to see a raising slope, when it is on a flat track and the flat track, when it is on a falling slope. Don't forget about raising slopes immediately after a falling one.

1. Inconsistent behavior of sighting at diagonals:
Not quite sure if this is a bug or I just don't understand how it was designed, however, such an essential feature should be easy to understand. If this is a bug, please let me know, so I will create a bug report.
Sometimes a train will look ahead to the last tile that is graphically a diagonal, sometimes it will look ahead to first tile behind that diagonal and sometimes it will not look ahead at all.
What I would expect is not that important because I would suggest suggest some different behavior in later points but for completeness, here is what I would expect of the current sighting system:
A train should always look ahead to the last tile that can be seen in the current travel direction (for sure limited by max_sighting_distance), that means always the first tile behind the current straight or diagonal.
Furthermore, when entering that first tile of the next diagonal or straight, it should be able to look ahead to its end again. Currently it seems that a train will start to look in a new direction when it leaves its reservation.

2. Corners are handled as corners but this is not consistent to curve speeds in simutrans-extended, which does some kind of curvature radius approximation from corners.
In reality it is not a problem to see ahead a few kilometers when the curve radius is high enough. The lower the curvature radius, the shorter you can see.
My idea about sighting distance is to couple it to the curvature radius, for sure never being larger than some max_sighting_distance.

3. Slopes:
Currently, when the slope changes it is not possible to look ahead that point, however in reality it is possible to do so as long as the slope raises (e.g. going from downwards to flat or from flat to upwards)
I would expect it to act just like this.
Furthermore, when the gradient changes are low, it is still possible to look further than in case of a rapid gradient change.
Since I don't think we have a measure of gradient changes yet, I think implementing this would not be worth the work, or at least not as long as drive_by_sight does not really care about sighting distance anyway.

Emergency halt and emergencies:
Currently, as far as I know, unexpected trains on the route, making an emergency halt necessary, can only happen in time_interval signalling mode. No matter how fast the train is, it will always come to a halt at only one tile and will always wait after the emergency halt.
My other ideas will create more situations in which emergency halts and emergencies can happen.
An emergency halt in reality would mean using sand or magnetic brakes (I don't know how exactly these are called in english).
In simutrans-extended it should greatly increase operating costs while it is braking but also increase brake force. By how much these are increased could be set up in the dat files by adding some emergency_brake_force value and some emergency_brake_cost value, additionally there should be some hardcoded default value in case the dat does not specify these.
When a train starts an emergency brake and comes to a halt without crashing, there will be no emergency, so the train can immediately continue its journey in drive_by_sight.
When it crashes into another train, it will have some waiting time before it can continue. How long depends on how fast the train was when it crashed.
This should simulate the fact that at low speeds there may be low damages to the train so the train can still move on its own or get pulled by an other train, while at very high speeds a crane is needed to move the train away from the track and the track needs to be repaired before operation can continue.

Blind people should never be employed as train drivers in Simutrans-extended:
Tran drivers in simutrans-extended should always use their sighting distance to watch out for other trains on their route to brake down their trains if they can or start an emergency brake if they can't. Currently, this can only happen in drive_by_sight, where this is already properly handled and in time_interval mode, where train drivers won't even try to prevent an emergency when a train appears in sighting distance.
Train drivers not being blind will become more important with my further ideas about drive_by_sight and one_train_staff

The drive_by_sight working method:
The most simple working method:
I really like it for its simplicity but sometimes it is too simple simulating blind, sometimes magic, idiots moving a train instead of train drivers.

1. Currently, a train will simply drive along its way, as long as at least one tile ahead is free.
This is fine, as long as everything is moving. When for example at the end of a completely double tracked line there is a junction and immediately after this junction is a head station with a train reversing in it, and the train moving towards that station can see the train in the station, it will simply continue until getting stucked in front of that train reversing in the station. Deadlocks can also occur when a train has to stop at a junction of a branching line.
My idea is that if a train can see another train on its route, and there is a junction somewhere in between these two trains, it will try to stop in front of that junction, waiting there until it can continue. If it can't brake quick enough, it will start an emergency halt. If it still can't brake quick enough it will simply come to a halt later.
For sure, considering its brake distance. If it can't stop in front of the junction, it will still block that junction, which simply means that the player did not install enough brake carriages to that train for that specific route.
If it can't see ahead of that junction, the train will still come to a halt at the junction, which is in this case a bad design of track.
I also want to mention that my suggested sighting_distance rework would also greatly improve this.

2. Apart from being blind, train drivers in Simutrans-extended also seem to be wizzards!
When a train in drive_by_sight crosses a track that is controlled by some other signalling system, for example absolute block, and there is a reservation of a train that is far-far away, the train driver operating in drive_by_sight will magically know about this reservation and wait until that train has passed.
What I suggest is that some types of operation modes will not create these red "registered" reservations but only create an other type of "unregistered" reservation which simply represents the trains boundings and is only considered when that train is in sighting distance.
Trains operating in these modes (drive_by_sight, one_train_staff, time_interval) should just ignore registered reservations, that means not stopping at them and not erasing them while passing over. In this case, for sure operation is not safe anymore which can lead to emergency stops or emergencies.

The one_train_staff working method:
Currently my favorite signaling system because of its very simple but also extremely strong layout just before the telegraph was invented.
I have already written a whole post about it so here is the short version of it:

1. Wizzards everywhere:
When a train being in any other mode than one_train_staff enters a one_train_staff cabinet, it will wait until the next section is free. Also for this working method, there are some wizzards knowing if there is any reservation between this cabinet and the next one.
What I would suggest is that this working method, does not use nor check for reservations at all. The reason for this is that nobody, except the person who has the staff, knows if this section is free or not nor does a driver waiting at a one_train_staff cabinet know if there is anything else on the tracks that did not pickup the staff.
The decision if a train may enter a one_train_staff signaled section or not should only be determined by the one_train_staff cabinet itself. To do so, it needs a bool has_staff that will switch to false when a train not being in one_train_staff mode passes and will switch to true, when a train being in one_train_staff mode passes.
However, one has to implement some way to manually reset one train staff cabinets and there needs to be some thoughts of what should happen when a train in one_train_staff working method is sold or its working method is reset to drive_by_sight using the block tool.

2. Slow down at the cabinet:
Trains seem to slow down to round about 14 km/h when entering a one_train_staff controlled section, but they won't stop! When leaving, they won't even slow down.
I would expect trains to stop at any one_train_staff cabinet on its way.

3. Staff and Ticket:
It would be nice to also simulate Staff and Ticket working method which works like one_train staff but also allows multiple trains in the same direction to enter the section.

4. Automatic exchange
It would also be nice to simulate automatic exchange by introducing some one train staff cabinet with automatic exchange which allows to hand over the staff without stopping at up to 64 km/h. The working method is still one_train_staff.

The time_interval working method:
1. Yet another wizzard? Oh no it's a witch! :
It seems to be solid but also with some wizzards in case of a tile in sight distance is already reserved by some far-far away train.
Well I would expect it to ignore that reservation and not to create any registered reservation. However, it should check if there is actually a train in sight distance.

Not much to say about absolute block and token block. Apart from some already reported bugs of the token block, these seem to work just as expected from reality assuming token_block is what english wiki calls an "Electric Token"

Well so far about my thoughts up to the absolute block era. I will complete this as ingame time passes by.


Thank you for your thoughts and having taken the time to write these up in detail.

Two preliminary points: first of all, the signalling code is highly complex and not very well organised, the latter coming from having adapted the existing code from Standard rather than started afresh entirely. The result of this is that many apparently minor changes to behaviour can require a truly gargantuan amount of work. The second point is that my development time is very limited: at present, I am able to do very little development at all because I am waiting on the availability of parts to replace my main home computer (and I am in any event away from home for a week). However, once that temporary situation has ended, I will then have to spend my time prioritising bug fixes and then developing the new high priority balance critical features discussed elsewhere, which is likely to take a considerable time. The net result of all that is that it is unlikely that I will be able to dedicate time to significant work on signalling beyond bug fixes in the foreseeable future. It is always possible that somebody else might take an interest in coding improvements to the signalling, however, so I will respond substantively to some of the points raised.

Reservation behaviour

Many of the issues that you raise about trains apparently being able to detect other trains by magic comes from the basic reservation behaviour. This is that a train will reserve a track by marking the individual tiles with a reservation marker. When checking whether a track is free, the standard methods for doing so all check for whether it is reserved, rather than whether it has a train in it. This is correct in at least almost all cases.

In the cases where there are two signalling systems interacting, such as absolute block and time interval, or absolute block and drive by sight, this is not entirely incorrect: although not directly (visibly) simulated, one would expect some system for interfacing the two signalling systems at the boundary between the two working methods (e.g. time interval and absolute block; one would expect in such a case that some absolute block equipment would have been installed at time interval signalling boxes at the edges of the absolute block system). Occasionally, this might lead to a situation that one would not expect in reality given the signalling systems of the time, as in a grade crossing between a line controlled by absolute block and a line controlled by drive by sight, but situations such as this are rare and would not exist in reality: it is better for the game to behave generously to the player in these instances than to allow a conflict that could not have occurred in reality because the design would never have been permitted.


Actual collisions are purposely not simulated, as it is a long-standing design philosophy of Simutrans not to simulate dipterous disasterous occurrences. In any event, actually simulating collisions in an economically realistic way would be fantastically complicated and make it very difficult for players to leave a long-running online game for an extended period of time without supervision. It is thus important that conflicts be as self-remedying as possible.

The emergency stop mechanic for time interval signalling is intended to achieve these goals: it is not intended to be a realistic recreation of what happens when trains meet in the time interval method: rather, it is intended as an approximate simulation of the overall operational consequences of the possibility of trains meeting, which is that there is a penalty to players for overloading the time interval lines in that the trains are delayed, but both trains are able to continue on their way after a delay and without player intervention of any kind. For these purposes, it does not matter that the trains cannot easily see other trains further ahead and start braking earlier.

Simutrans-Extended is not intended to be a computerised model railway: it is intended to be an economic simulation, and thus operations are simulated only in so far as they are economically significant in a way that works well with the design aims, which include long-term unsupervised running on multi-player server games.

One train staff and staff and ticket

The reason that the one train staff working method reserves the whole line is to make sure that only one train can ever be on the line at the same time. A method that did not do this would not be a one train staff, but something closer to the staff and ticket method, albeit without the tickets (which would approximate to the very old way of working single lines in the 1830s before the tickets were invented; this lasted a very short time).

This method of working is extremely fragile (as it was in real life: it was not uncommon that people had to be sent on horseback to retrieve the staff from the other end of the section, even after tickets were invented), and would be likely to lead to frequent deadlocks if used. This would be very difficult and unpleasant for players to have to manage, and would be unworkable in an online game. Staff and ticket is therefore deliberately not implemented. I should note that this has been discussed at length before quite a number of times.


There are one or two things that you discuss that might be able to be achieved with less effort than an overhaul of the whole system, being trains not stopping at the one train staff and token cabinets (and, yes, this is intended to represent the electric token system) and upward gradients not affecting the sighting distance.

As to behaviour on diagonals, this is challenging, because, fundamentally, ways only have four directions: the ordinal directions are inferred for the purposes of graphics from ways laid in alternating cardinal directions on adjacent tiles, and then some adjustments are made for speed and distance in the code. It might in principle be possible to make diagonal behaviour consistent with straight behaviour for sighting distances, but I anticipate that this might be a large amount of work, and, unless I am missing something, the issue is relatively marginal in terms of gameplay.

In any event, thank you again for having taken the time to communicate your thoughts on this matter: it is appreciated.
Download Simutrans-Extended.

Want to help with development? See here for things to do for coding, and here for information on how to make graphics/objects.

Follow Simutrans-Extended on Facebook.


Hey James,
Ye I have already read about the complex and bad organized signalling code somewhere so I was expecting this to cause strange behaviors in many ways.
I was not about to say that you should change things now as I am aware that maintaining bad organized code is not pretty amusing and changing behavior of bad designed code is some kind of hell.
I just wanted to share my thoughts about how I think things should optimally be designed so that players knowing this working method in the real world can use this in simutrans-extended without knowing its exact implementation in simutrans-ex, which is, when I remember correctly, one of the main high level design goals of extended.

Maybe one day someone will make greater changes to the signalling system or will completely rewrite it because it became unmaintainable anyway, in which case these thoughts should be good guideline.

I will always file a bug report for stuff that I would consider important to being changed soon.
Really don't get me wrong, I know about your computer and don't want to create any kind of time pressure. Your work at extended is great and you do it just for free, which is even greater!

Reservation behaviour
I know the current reservation behavior is kind of simple and I already expected that checking for trains in a block is only done by checking for reservations
but there seems to be at least a difference in between directional reservations and non directional reservations, so I would suggest a third type of reservation which eats up block reservations but will remember these for example by an internal reference to that reservation and will restore that original reservation on that tile when it leaves that tile.

When there are two signaling systems interacting, from a real-world standpoint, especially in early years of railway transport, a train waiting at a time_interval signal can not know about an other train in one_train_staff working method being far-far away.
Also a few years later, when absolute block comes in place, a train being in drive_by_sight can not know about the reservation on its own. It will only know about it when there is a signal or anything else tells the driver to wait.
You are totally right that these track designs would not be allowed at all in the real world, however in Simutrans-extended there is nothing that forbids this.
In the real world, most train accidents happened because of some train operating on a track that it is not permitted to operate on, which is human error.
Happily, here is no such kind of simulated random human error in simutrans-extended, but there is also no "Federal Railway Authority" or something like this controlling if a track layout follows all the required rules for this track to operate securely, so a bad track layout is, apart from time_interval signaling, the only kind of human error that can happen in game and should eventually cause accidents.

QuoteActual collisions are purposely not simulated, as it is a long-standing design philosophy of Simutrans not to simulate dipterous occurrences.
I understand and like that decision, as the two most annoying features of openTTD are breakdowns immediately followed by crashes that make the train explode.
However, in time_interval signaling there is already some extremely simple kind of collision simulation. When colliding, trains will come to a halt, waiting for some time before they will continue in drive_by_sight.
What I propose is very close to this current behavior. The only thing that should be changed compared to collisions currently happening happening in time_interval_signaling is that the duration of the stop should depend on the speed at which the crash occured, maybe not linearly scaling.
Also, I would expect train drivers to use their eyes and doing everything they can to prevent a crash when they see a train in front of them.
I don't think that long-term unsupervised running on multi-player server games would be negatively affected by this.

Especially for high load time_interval_lines, the current implementation, where train drivers don't look ahead and will always immediately come to a halt waiting for a fixed time, will lead to accumulating crashes when a train being slightly faster than an other one due to being empty, which is most often caused by already being at a close distance to the other train.
This can have a huge economical effect and will require a player to take action at highly loaded tracks whereas a train driver looking ahead can, when trains are operating at roughly the same speeds, could prevent most of these crashes at highly frequented time_interval signaled tracks.
I just want to mention that I don't want a computerized model railway otherwise I would be playing transportfever, which is imho much too much of a computerized railway and much too few of an economical simulation.

One train staff and staff and ticket
As long as the one_train_staff cabinet knows if it has a staff or not and will only let trains pass when it has, it still won't be possible for more than one train to be in the same section at the same time.
Furthermore,as long as the number of trains operating on it is greater or equal to the number of single tracked sections controlled by one_train_staff and the traffic is symmetric, which means that a train passing in one direction will always later on pass in the opposite direction, the system can be proved to be deadlock free.
For example this layout ----=----=----=----=----, placing a pair of one_train_staff cabinets at both ends of the double tracked sections, where "-" is a single tracked section and "=" is a double tracked section it can not deadlock as long as there are at least 5 trains operating on it.

For staff and ticket I must admit that things are not that simple so it could only be used reliably in combination with a well scheduled timetable. It still would be useful in some admittedly rare cases but will cause deadlocks in many cases where it seems to be a good option to the player, so I understand why you don't want to add this to the game.

Well yeah I expected behavior on diagonals to be challenging due to the fact that there are no real diagonal directions for tracks ingame and that there wouldn't be such a strange behavior if i was easy to handle.
I confirm that behavior on diagonals seems to depend on the direction in which the track was placed, at least in a quick test.
Knowing this behavior, I will simply never place signals on diagonals directly in front of an edge, however this is far from intuitive and very important when using absolute block signaling!
I first noticed this problem when I placed a signal on a diagonal just in front of an edge and my trains started braking down to nearly zero speed for some reason.
Now that I have observed sighting distance, I know that the signal simply was one tile ahead of sighting distance, so trains had to come closer to it at a speed that would allow them to break down at only one tile.
So we really need a consistent behavior for sighting on diagonals.
Optimally allowing trains to always look one tile ahead of the graphically displayed corner since that tile is still in the same direction. This should be simple as soon as we have a reliable way to say in which direction a tile will be used by a train on a given route.
If we know this, the only thing we have to do is detecting the next direction change on that route, adding one tile ahead and remembering that segm1ent, so we don't have to recalculate for each tile that we enter.
It would be even more useful to calculate all sighting segments up to the next station, so we could relatively easy approximate some kind of curvature radius from this data to calculate curve speeds and sighting distance.
However, I will only describe the way to get the current sighting segment here, creating the whole list of sighting segments is just looping on, whereas the rest would be a whole post on its own.

The direction in which a train will pass a tile can be represented by a, vector (x,y). This direction depends only on the tiles before and after our current tile. It is calculated as the difference of nextTile->location - prevTile->location. So (0,2) would be NORTH, (1,1) would be NORTHEAST, (2,0) would be EAST and so on.
Apart of a direction, a vector always has a length. We can see that straight vectors in our case do have all the same length and diagonal vectors do all have the same length. This can be used to determine if a direction is diagonal (sqLen(direction)==2) or a straight (sqLen(direction)==4) but that's just a side note, we won't need that.
We also need to take some special consideration about dead end tracks. In Simutrans these are always straight, so the only thing we have to test for is if there is a change in y or a change in x in between the location of the previous tile and the current tile or in between the current tile and the next tile depending on if the route is towards or against the dead end.

Programmatically in pseudo code getting the direction in which a tile will be used along a given path is as simple as that:

    if(!nextTile) return  (2*(currentTile->x - prevTile->x), 2*(currentTile->y - prevTile->y)) //moving towards a dead end
    if(!prevTile) return  (2*(nextTile->x - currentTile->x, nextTile->y - currentTile->y)//moving away from a dead end
    return (nextTile->x - prevTile->x, nextTile->y - prevTile->y)//there is no dead end

Now the only thing we need to do is to iterate over our current path, which has to include our current tile to find the change in direction.
Note that I will assume nonexistent array indices to be null. I don't think this is not true for C++ so these cases (first and last iteration step) have to be handled differently in C++

getSighting(remainingRoute): //remaining route is a list of all tiles to the next waypoint including the current tile of our train
    // our first point, which is always one tile before an edge or a station tile is always in our sighting segment.
    //We will create a sighting segment where all, except for the first and the last tile are used in the same direction as the second tile
    currentDirection:=getDirection(remainingRoute[0], remainingRoute[1],remainingRoute[2])

    //We already handled the first tiles, so we can skip it
    for i from 1 to remainingRoute->size:
        //always add that segment
        //if the tile we just added has a different direction, it was one tile ahead of the edge so we can return now.
        if(currentDirection!=getDirection(remainingRoute[i-1],remainingRoute[i],remainingRoute[i+1])) return sightingSegment //edge detected
    //in case that there is no edge to the end of the route we will return the whole route
    return sightingSegment