Custom Signals And State Tables



Writing your own custom signals may seem a little daunting at first, but it’s not too difficult once you get the hang of it. The hardest part to understand is the state table, and since the Rail 3D User Guide doesn’t provide heaps of information on how you might actually write the state table, we’ll attempt to cover it here.

This document is fairly long, so you should use the contents table above to find what you’re after.

Signal file sections


Custom signals go in the Signals subfolder and have the .sig file extension.

Note: The Rail 3D Editor tries to save all new files as a Stock file (extension .stk), so when you first save the file you might have to enter the filename as “Mysignal.sig” (including the quotation marks) to ensure the file is saved with the .sig extension.


The first lines of the signal file should contain the following information:

  • Name - the name of the signal
  • Description - a short description of the signal that will appear in R3D’s signal browser
  • Credit (optional) - your name

This is how the first three lines of your signal might look so far:

 Name          Mysignal
 Description   Simple colour light signal
 Credit        Joe Bloggs

Signal naming conventions

You’ll want to name the signal something more descriptive than Mysignal, as there are probably going to be many different simple colour light signals in your signal library from different countries or railways. It’s recommended that the name of your signal contains a prefix indicating the country or railway it is from. Many people then use other letters/numbers to further describe the function of the signal and its position relative to the track.

So for a 3-position colour light signal positioned at the right of the track from the icy railway of Antarctica, you might call it ant_ICY_3R. Of course, if the icy railway also has 3-position semaphores, you might want to call the 3-position colour light signal ant_ICY_CL3R and the semaphore equivalent ant_ICY_SEM3R - it’s up to you.


When you edit the properties of a signal in Rail 3D, you can set or clear a whole heap of options like Controlled, Shunt and Four Aspect. To speed up the process of adding signals to a layout, you can preset some of these options within the signal file itself. For example, you will always want the Shunt option selected for a shunting signal, so it makes sense to preset this option if you’re making a shunting signal.

You can set the following signal options, or flags:

  • stop
  • distant
  • semaphore
  • repeater
  • controlled
  • four (Four Aspect)
  • shunt
  • aws
  • junction
  • junction2

To set or clear these flags, use set and clear statements. You can set as many or as few flags as you like.

 set     stop
 set     distant
 clear   repeater
 clear   four
 clear   shunt
 clear   aws


  • The junction and junction2 flags are no longer available in the signal properties dialog in Rail 3D. I’m not sure if presetting them does anything or not.
  • Setting semaphore also makes the signal a controlled signal.
  • All other flags must be set manually in the program, i.e. they cannot be preset.

The signal itself

After the header and flag information you should define all the Fills and Strips peripheral to the operation of the signal. This includes things like signal posts, lamp shades, ladders, service platforms etc.


  • If you’re making a signal that is to be mounted on an overhead gantry, you can specify the gantry here, or you can write the gantry in a separate scenery file and add it manually to the layout on the same node as the signal. The latter method works best when you have a gantry spanning multiple tracks and you have multiple signals to add to the gantry.
  • You can use Include in this section too. Note that you won’t be able to include scenery items from the Scenery folder. If you want to do this, you’ll need to copy the scenery item to the Signals folder (or one of its subfolders), rename it with a .sig file extension, rename the scenery component so it has a unique name, and add the Component keyword in the header information so that it doesn’t show up in the Signal browser.
  • Textures can also be used on signals, however they need to be in the Textures folder (This may be wrong - someone please correct this if so).

Signal lamps/arms

Now we come to what are arguably the most important parts of the signal, the signal lamps and/or arms.

To give us something to talk about, you might define a lamp like this:


 Fill −103/−15/100 −100/−15/103 −97/−15/100 −100/−15/97  255:0:0 $
 Fill …
 … etc.

 state      S.X    255.0.0
 state      C.X    …
 … etc.

… and a semaphore arm like this:

 arm −100/0/200 Z

 Fill −130/0/205 −100/0/205 −100/0/195 −130/0/195 255:255:255 $
 Fill …
 … etc.

 state      S.X    0
 state      C.X    …
 … etc.

There are three parts to each lamp/arm section:

  1. A lamp or arm statement.
  2. The panels that make up the lamp or the arm.
  3. A state table, which tells the signal how to behave.

To define a signal lamp:

  1. Type lamp on a line by itself at the start of the lamp section.
  2. Next, define all the Fill panels that will make up the lamp lens. You still need to give the Fill panels a colour, but these are arbitrary only and will be overridden by the state table.
  3. Lastly, define the state table (discussed later).

To define a signal arm:

  1. Type arm W/L/H R on the first line of the arm section.
    • W/L/H represents the point about which the arm will rotate.
    • R is a letter X, Y or Z, representing the axis of rotation. The X-axis corresponds to Rail 3D’s W direction, the Y-axis to the H direction and the Z-axis to the L direction. The Z-axis works for normal semaphore arms, and the Y-axis will do disc signals.
  2. Next, define all the Fill panels that will make up the arm. You can use textures too.
  3. Lastly, define the state table (discussed later).


  • You can define the state table before the Fill panels, but the arm or lamp statement must come first.
  • The lamp/arm definitions must come at the end of the file. This is because the arm and lamp keywords apply to all Fill panels following them until another arm or lamp statement is reached.
  • You can put as many or as few lamps/arms in each signal as you like.

State tables


So, how do you write a state table? Well, that all depends on how complex your signal is. But first things first…

A state table provides information to Rail 3D so that it can work out either what colour a lamp should be or how much an arm should be rotated. Each line in a state table tells the lamp/arm what to do when the signal is at a particular aspect, or state (hence the term state table).


For a lamp, each state table line follows this syntax:

 state    a.b    r:g:b

For an arm, each state table line follows this syntax:

 state    a.b    angle


  • a and b are one of the valid signal states (discussed in the next section), representing the state of this signal and the state of the next signal respectively.
  • r:g:b is the colour to be shown, in standard Rail 3D colour syntax.
  • angle is the angle through which to rotate the arm about the axis specified in the initial arm statement.

List of states

This table describes all of the available states a signal may have. These states are listed in the order of precedence from highest to lowest (discussed later).

State Name Notes
S Stop Signal is at stop.
J Junction The non-default route is set at the first set of points after the signal.
J2 Double junction The non-default route is set at the second set of points after the signal.
C Caution Signal is at caution.
C2 Double caution The next signal is at caution. Available only when the four/Four Aspect flag/option is set.)
H Shunt Signal is cleared for a shunting manoeuvre. Available only when the shunt flag/option is set.
N No route No route has been set from a controlled signal.
L Full clear Signal is at clear/proceed.
P Permissive Signal is cleared for a non-shunting route into an occupied section. Available only when the Permissive option is set.
X “Anything” Used to designate any of the states.


  • See Junction Signals in the Rail 3D User Guide for a helpful diagram illustrating how J and J2 states are assigned at complex junctions.
  • Some of the states occur only when the appropriate flags/options are set. Make sure you get this right otherwise you’ll be wondering why your signal isn’t working properly.
  • The H state doesn’t appear to ever occur on its own. To write a state table that distinguishes a shunting or calling-on aspect, use the ch.X state and not just H.X.

Order of precedence of states

The list of states above is listed in order from the highest state to the lowest state. It’s a bit difficult to explain what this means unless we know what Rail 3D does when it draws signals.

This is how signals get drawn in Rail 3D:

  1. When your layout is running, R3D works out what state the signal has.
  2. R3D looks up the state table that you’ve written in the signal and goes through it until it finds an entry with a state equal to or lower than the signal’s state.
  3. The signal gets drawn with the lamp/arm showing the aspect as identified in Step 2.

We’ll do some examples in the next section.

Writing state tables

A basic start

Let’s say we have a searchlight that shows either red for stop, yellow for caution and green for clear. Trying to write the state table so that it is in the correct ‘order’, we could guess that it will be:

 state    S.X    255.0.0
 state    C.X    255.110.0
 state    L.X    0.255.100

This is a very simple state table. An English translation could be:

  • When this signal is at Stop and the next signal is showing “anything”, show red.
  • When this signal is at Caution and the next signal is showing “anything”, show yellow.
  • When this signal is at Clear and the next signal is showing “anything”, show green.

Assuming that the signal might be at stop, we can follow the steps for how Rail 3D draws signals:

  1. R3D determines that this signal’s state is S, i.e. at Stop.
  2. Going through the state table, R3D finds that the entry for S.X matches the entry for this signal.
  3. The signal gets drawn with a red lamp.

What if the signal was at Caution instead (i.e. the next signal is at Stop)?

  1. R3D determines this signal’s state to be C.
  2. Going through the state table, R3D skips S.X because it is not equal to or lower than C. It then comes to C.X, and finds that this is equal to C.
  3. The signal gets drawn with a yellow lamp.

The X state

You may have noticed in the last example that the signal was at Caution and the next signal was at Stop. What would happen if our state table had a C.S line?

 state    S.X    255.0.0
 state    C.S    255.110.0
 state    C.X    0.0.255     # I made this one blue for the sake of it
 state    L.X    0.255.100

Rail 3D would’ve worked out the signal’s state table like this:

  1. R3D determines this signal’s state to be C. What wasn’t mentioned before is that R3D also knows the next signal’s state is S. Therefore R3D is looking for C.S.
  2. Going through the state table, R3D skips S.X and then finds C.S, which is an exact match.
  3. The signal gets drawn with a yellow lamp.

So what good is the C.X state now? Well, not much. R3D is going to either skip over it or never get to it 99% of the time.1

And what happened in the first example before we added the C.S state? Well, R3D was actually looking for C.S, but since it wasn’t there, it went to where it would’ve been if it were there and then went to the next item down.

But most of the time we wouldn’t bother putting in the C.S state, so C.X is very helpful. It means that any C.? state will be recognised at the C.X line, so all Caution aspects will be handled by just the one line in the state table. This is instead of writing out every combination of C.? there is.

1. Technical note: What about the other 1%? It turns out that some movements like routes set to reverses are C states but not C.S states, so they will be picked up by the C.X line when you already have a C.S state. You might be able to make use of this when modelling subsidiary signals.

Obscured states

We just saw the first example of equal to or lower than, where R3D was looking for C.S, but since we didn’t initially have a state table entry for C.S, it had to go down to the next best entry.

So just how much of this “going to the next best state” is happening? Well, look at the L.X line, and you’ll see that if the signal was looking for L.C, it would find L.X in the state table instead because L.C isn’t there. In these cases, it doesn’t matter that we’ve left out some of the states because the signal ends up showing the correct aspects anyway.

Let’s return to the first example:

 state    S.X    255.0.0
 state    C.X    255.110.0
 state    L.X    0.255.100

Assuming this is a signal without junction indicators, what would happen if a clear junction route (i.e. the non-default route) were set from the signal? Going through the steps again:

  1. R3D determines this signal’s state to be J. Let’s say the next signal is at clear, L. R3D is looking for J.L.
  2. Going through the state table:
    1. R3D skips over S.X because it’s not equal to and definitely higher up the order of precedence than J.L.
    2. The next entry that it comes across is C.X, and this is actually lower than J.L, because J comes before C.
  3. The signal is drawn with a yellow lamp.

This is wrong! The signal is at clear, so we want it to show green, but it’s showing yellow instead. But this is not R3D’s fault - it’s a mistake in our logic.

To get around this, we need to add in lines for the junction states:

 state    S.X    255.0.0
 state    jc.X   255.110.0
 state    J.X    0.255.100
 state    C.X    255.110.0
 state    L.X    0.255.100

This time, both the S.X and jc.X states will be skipped because they are not equal to or lower in precedence than J.L. However, this time J.L will be picked up at the J.X line, and the signal will correctly show green.

So what appeared to be R3D making a mistake was actually an errant omission of ours! Because of the order of precedence, we have to make sure that we don’t accidentally leave out a state, because it might result in the wrong aspect being displayed.


  • At a more complicated junction where the signal doesn’t take into account which route is set, you may have to insert state table entries for the J2 state as well to ensure the correct operation of the signal over all routes.

Multiple lamp - multiple aspect signals

So far our examples have been a searchlight signal with one lamp that changes colour. What about a multiple lamp-multiple aspect signal?

You will need to have a separate lamp statement for each lamp. Therefore, a standard 3-lamp 3-aspect signal will require 3 lamp statements: one each for the red, yellow and green lamps. You are going to have a separate state table for each lamp, but you can’t use the previous state table because it changes one lamp to all three colours.

If we think about it, we realise that the old state table can be adapted to each of the three lamps. Considering the red lamp, it is either showing red, or it is off. Looking at the previous state table:

 state    S.X    255.0.0
 state    jc.X   255.110.0
 state    J.X    0.255.100
 state    C.X    255.110.0
 state    L.X    0.255.100

…we can see that the lamp is only red for the state S.X. At all other times, one of the other lamps should be on, so the red lamp should be off. We can modify the table by changing all the non-red aspects to off (black).

 state    S.X    255.0.0
 state    jc.X   0.0.0
 state    J.X    0.0.0
 state    C.X    0.0.0
 state    L.X    0.0.0

We can do this for the yellow lamp by changing all the non-yellow aspects to black too:

 state    S.X    0.0.0
 state    jc.X   255.110.0
 state    J.X    0.0.0
 state    C.X    255.110.0
 state    L.X    0.0.0

And the same for green:

 state    S.X    0.0.0
 state    jc.X   0.0.0
 state    J.X    0.255.100
 state    C.X    0.0.0
 state    L.X    0.255.100

Now we have three separate state tables, one for each lamp, and because they all contain identical state table entries except for the colours, it’s easy to ensure that all the other lamps will be off when any given lamp is on by ensuring that, for the same state entry, only one lamp has a colour other than 0.0.0 specified.

Simplifying the state table

When you have another look at the state table for the red lamp in the previous example, you might notice that only the first state (S.X) really does anything, and it seems a bit excessive to specify 0.0.0 for all of the remaining states.

 state    S.X    255.0.0
 state    jc.X   0.0.0
 state    J.X    0.0.0
 state    C.X    0.0.0
 state    L.X    0.0.0

Well, we can simplify the state table by removing some of the entries. You actually need only put as much information in the state table as necessary to get it to work properly.

If I were to remove the jc.X line, what would happen if the signal were looking for jc.X? R3D would skip over S.X because it’s not equal to or lower than jc.X. It would then get to J.X, which is lower than jc.X. But J.X has the same colour as jc.X, so this is ok.

Now try removing the J.X line as well when R3D is looking for either jc.X or J.X. Again, S.X will be skipped, and the next lowest is C.X. But this is still the same colour, so it’s ok.

We can keep going down the state table removing all adjacent entries with the same colour and find that it still works fine. As long as all the entries are adjacent and have the same colour, you can do this without messing up the signal. In general, if:

 state    a.b    colour-1
 state    c.d    colour-1
 state    e.f    colour-2
 state    g.h    colour-2
 state    i.j    colour-2
 state    k.l    colour-3

… then you can delete a line if the colour of the next line is identical. This means you can delete the a.b line because c.d has the same colour. You can also delete e.f and g.h, because these aspects will be picked up by i.j. You can’t delete k.l, because if R3D is looking for k.l, it will never find anything equal to or lower than k.l and hence nothing will get displayed. Similarly, you can’t delete c.d or i.j because the line underneath doesn’t have the same colour and the signal will display the wrong aspect.

The same applies for arms and angles: that is, you can delete a line if the line underneath has the same angle.

Sometimes it’s easier to leave in the lines so you can see what you’ve done, especially on more complicated models.

Debugging state tables

Sometimes you can put in a lot of work writing the state table, only to find the signal doesn’t work properly when you use it in Rail 3D. You can test it in the Editor’s preview window before testing it in Rail 3D, but ultimately Rail 3D sets the flags and you want to know how it’s going to operate when you’re using it, not testing it. Here are some things to check before you delete all your work in an irrational bout of rage:

  • You need to save the signal file and restart Rail 3D each time you make changes to the signal file (in that order). If you forget either, you won’t see the changes you made.
  • Ensure that the correct flags/options are set/cleared as appropriate. You can’t get the C2, H or P states unless the relevant options are set. You’ll need to make sure these options are set when you add the signal to a layout too, not just in the header of the signal file.
  • If you’re testing a controlled signal, do you have a train setting a route from the signal? More importantly, is the route being set at all, and is it the correct route? Turn on route locking to see what’s happening.
  • If you’ve used the H state, did you remember to use ch.X instead of H.X?
  • If the signal is protecting a junction, check the following:
    • Make sure the state table deals with junctions appropriately.
    • Check the default routes over the points that are protected by the signal. Remember that a route set over a non-default route is classified as a Junction route (and the signal will thus receive the J state.)
    • Remember that the J and J2 states deal only with the first and second sets of points following the signal.
    • Have you manually set the J and/or J2 points for the signal in the layout in question? If so, you might want to delete the signal and try again.

If you still can’t track down the problem, it’s most likely due to a fault with the state table. Try the following:

  1. Copy the offending state table somewhere else so you don’t lose your work.
  2. Change each state table entry to a unique, distinct colour. (i.e. red, yellow, green, blue, brown, black, purple, white etc.)
  3. Save it, restart Rail 3D and try the signal again.
  4. Note which colour appears. Also see what routes have been set and what the states of the following signal(s) are.
  5. Look back at the state table for the entry with the colour you observed.
    • If this state is not the state you expected the signal to be in, then it’s likely that you have omitted an obscured state from the state table. You will have to figure out exactly what this state is for yourself, as each case is unique.
    • If this is the state you expected, then you must have had the incorrect colour listed in the first place!

Other signal techniques

There are some other things you can do with signals to enhance their functionality, either operational or visual.

Alpha-numeric indicators

Sometimes routes or speeds are shown on the signal in addition to the main aspect via an illuminated letter or number, and you can model these in Rail 3D too. Here are two techniques you can use to do this. In both cases, it’s advisable to ensure that the back of the lamp is solid when viewed from the front. This will stop daylight from showing up the inner workings of how we get the route indicators to display…

Normal fill panels

Just use normal fill panels for simple route indicators. Sometimes routes are indicated by no more than an arrow, which will require around three or four panels. You can also model simple letter shapes using Fill panels.

Using Fills also allows you to show numerous different aspects on the same lamp. If your route/speed indicator needs to show more than one symbol, just define separate ‘lamps’ for the panels that make up each symbol. You then set the lamp to illuminate on the required aspect, and set it to 0.0.0 when it is supposed to be off/unlit.


  • Black (0.0.0) is interpreted as transparent when applied to lamp panels. The lamp actually doesn’t get drawn at all when you specify 0.0.0. This is helpful for multiple-symbol indicators because it means the unlit symbols don’t interfere with the lit symbol. If you need to use black for some reason, use 0.0.1 or something very close to black.

Backlit transparent textures

For more complicated or nicer-looking indicators, you can mimic the real operation of most older indicators by having a lamp behind a stencil.

To do this, create a texture with transparency where the letter/number/symbol should be. You then make a Fill panel with this texture on it as part of the normal structure of the signal. When you come to define the lamp, you need only a simple rectangle behind the texture panel that illuminates at the appropriate time. The pattern on the stencil panel will create the effect of the indicator.

The drawback with this method is that you can have only one symbol. I have thought of a way of getting around this, but have not tried it yet. If your route/speed indicator lamp housing is big enough, you could put the stencil textures in an arm statement instead and have them swing down into view at the appropriate aspect. At all other times you should specify the rotation angle so that they are concealed somewhere else in the lamp housing. If the lamp housing is not big enough, you could also make the stencil rotate about a point several kilometres away from the signal (i.e. a long way away!), so that when it is not being used, it is several kilometres above or below the track and well and truly out of sight!


  • Especially for the latter workarounds, make sure you get the order of transparency correct. For something to be visible through a transparent texture, it must have been drawn already when the texture is drawn. Because Rail 3D draws an object starting from the end of the file, you must have the transparent textures in the file appearing before the things behind them. To get an indicator stencil to swing in/out of view in front of an illuminated panel, you’ll need to have the arm for the indicator following the illumination lamp.


You can use scripts to extend the functionality of your signals. Scripts can be embedded in the signal file itself or added to individual signals in a Rail 3D layout. Scripts have the ability to get information relating to passing trains and the states of other signals, and they can change the aspect of lamps/arms or the speed of a passing train.

For complete information about scripts, you should refer to:


  • Scripts can refer to individual lamps/arms in your signals. For this reason, you can mark each lamp/arm with a unique identifier. Insert the line “id n” immediately after a lamp or arm line, where n is a unique integer.

Spectacle lights

Semaphore signals get hard to see at night, so you probably want to include a spectacle lamp as well. This can be achieved by making a separate lamp that sits just in front of the semaphore arm’s spectacle lenses and changes colour accordingly. But if you want to go the whole hog and do it properly, you can do it with textures.

If you use a .tga texture instead of a .bmp texture, you can make the spectacle lenses partially transparent whilst still retaining their colour. Up closer, this gives the nice effect of being able to see the scenery through the lenses. If you put a lamp behind the lens and make the state table nothing but “state X.X 255.255.255″, it should light up through the lens. Viewed almost side on, you’ll be able to see the white light when you’re not looking through the lens.

Just how authentic is that?!


  • A slight problem - .tga texture transparency doesn’t seem to work in signals yet. I’ve made a signal that’ll test this but can’t test it yet. It works in theory though.

Subsidiary signals

Created: September 20, 2005

Last modified: June 11, 2010, at 11:33 am