Traffic Signals (signals.json)

This document describes the signals.json file used by BeamNG levels for traffic signals.

signals.json stores traffic signal data such as stop signs, traffic lights, signal controllers, and timed signal sequences. It is used by traffic AI, navigation, signal visualization, and gameplay systems.


File location

The default traffic signals file is located in the level root:

levels/<levelName>/signals.json

Example:

levels/west_coast_usa/signals.json

If the file exists, it is automatically loaded when the level starts.


Related files

File Purpose
signals.json Level signal instances, controllers, and sequences.
signalControllerDefinitions.json Optional level-specific custom controller state/type definitions.
settings/trafficSignals.json Default global controller state/type definitions.
map.json / navgraph Used to associate signals with road graph nodes.

Overview

Traffic signals are split into three main data types:

Type Description
Instance A placed signal point in the world. Has position, direction, controller, and optional sequence.
Controller Defines what type of signal this is and which states it can use.
Sequence Defines timed phases for one or more controllers, usually for traffic light intersections.

These are stored in three top-level arrays:

{
  "instances": [],
  "controllers": [],
  "sequences": {}
}

or, for modern saved files:

{
  "instances": [],
  "controllers": [],
  "sequences": []
}
Some older files may use an empty object for sequences ({}). New files should use an array ([]).

Minimal stop sign example

A simple stop sign setup can use one controller and many instances.

{
  "controllers": [
    {
      "id": 2,
      "name": "stop",
      "type": "signStop",
      "isSimple": true,
      "states": [
        {
          "state": "basicStop"
        }
      ]
    }
  ],
  "instances": [
    {
      "id": 1,
      "name": "stop 1",
      "controllerId": 2,
      "sequenceId": 0,
      "pos": [734.165, 755.369, 177.949],
      "dir": [-0.925, 0.378, -0.005],
      "group": "group_1_3",
      "startDisabled": false
    }
  ],
  "sequences": []
}

This creates one stop sign instance using the shared stop controller.


IDs and references

Signals use numeric IDs to link elements together.

Field Description
instance.id Unique ID for a signal instance.
controller.id Unique ID for a controller.
sequence.id Unique ID for a sequence.
instance.controllerId References a controller ID.
instance.sequenceId References a sequence ID. 0 means no sequence/basic behavior.

IDs must be unique across all instances, controllers, and sequences.

Do not reuse the same numeric ID for multiple elements. Duplicate IDs are removed or ignored when signal data is loaded.

Signal instances

Signal instances represent placed signals in the world.

Example:

{
  "id": 1,
  "name": "stop 1",
  "controllerId": 2,
  "sequenceId": 0,
  "pos": [734.165, 755.369, 177.949],
  "dir": [-0.925, 0.378, -0.005],
  "group": "group_1_3",
  "startDisabled": false
}

Instance fields

Field Type Description
id number Unique signal instance ID.
name string Signal instance name. Also used to link world signal objects.
pos array[3] World position of the signal point.
dir array[3] Direction the signal faces / applies to.
group string Optional intersection/group name.
controllerId number Controller used by this instance. Required for functional signals.
sequenceId number Sequence used by this instance. 0 means no sequence/basic signal.
useCurrentLane bool Optional/experimental lane-specific behavior. Currently unused.
startDisabled bool If true, signal starts inactive/off.

Position and direction

pos defines where the signal logic point is placed.

"pos": [734.165, 755.369, 177.949]

dir defines the direction the signal applies toward.

"dir": [-0.925, 0.378, -0.005]

For a stop sign or traffic light, dir should generally point in the direction vehicles travel when approaching or passing the signal.

The signal system uses pos and dir to:

  • Find the nearest/best road segment
  • Determine whether a vehicle has passed the signal
  • Associate signal state with navigation graph edges
  • Send signal actions to vehicle AI

Signal groups

The optional group field groups related signals, usually at the same intersection.

Example:

"group": "group_8_9_10_11"

Groups are mostly editor/organization metadata. The editor can automatically group instances that are close together, share the same sequence, and face toward the same intersection area.


Signal controllers

Controllers define what kind of signal behavior an instance uses.

Example stop sign controller:

{
  "id": 2,
  "name": "stop",
  "type": "signStop",
  "isSimple": true,
  "states": [
    {
      "state": "basicStop"
    }
  ]
}

Controller fields

Field Type Description
id number Unique controller ID.
name string Controller name.
type string Controller type key from controller definitions.
isSimple bool If true, controller does not need timed sequence logic.
defaultIndex number Optional default state index.
states array Ordered list of controller states.

Controller states

Each controller contains an ordered list of states:

"states": [
  {
    "state": "greenTrafficLight",
    "duration": 12
  },
  {
    "state": "yellowTrafficLight",
    "duration": 4
  },
  {
    "state": "redTrafficLight",
    "duration": 1
  }
]

Controller state fields

Field Type Description
state string State key from controller definitions.
duration number Duration of this state in seconds. Optional for simple controllers.

A negative duration is treated as an infinite duration in editor/runtime logic.


Simple controllers

Simple controllers do not require a sequence.

Example:

{
  "id": 2,
  "name": "stop",
  "type": "signStop",
  "isSimple": true,
  "states": [
    {
      "state": "basicStop"
    }
  ]
}

Signal instances using simple controllers can use:

"sequenceId": 0

This is typical for:

  • Stop signs
  • Yield signs
  • Static traffic signs
  • Simple always-on signal actions

Sequences

Sequences define timed signal phases.

They are mainly used for traffic lights at intersections.

Example:

{
  "id": 10,
  "name": "intersection_sequence",
  "startTime": 0,
  "startDisabled": false,
  "ignoreTimer": false,
  "phases": [
    {
      "controllerIds": [3]
    },
    {
      "controllerIds": [4]
    }
  ]
}

Sequence fields

Field Type Description
id number Unique sequence ID.
name string Sequence name.
phases array Ordered list of phases.
startTime number Start offset in seconds. Can be negative to skip ahead.
startDisabled bool If true, sequence starts disabled/off.
ignoreTimer bool If true, sequence does not advance with the main timer.

Phases

A phase defines which controllers run together.

{
  "controllerIds": [3, 4]
}

Each controller can only be used once per sequence.

A sequence can contain multiple phases:

"phases": [
  { "controllerIds": [3] },
  { "controllerIds": [4] }
]

The duration of a phase is calculated from the longest controller duration in that phase.


Advanced phase start times

A phase may optionally define startTime:

{
  "startTime": 5,
  "controllerIds": [3]
}

This allows phases to overlap or start at explicit offsets inside the sequence timeline.

If no startTime is set, phases run one after another.


Controller definitions

The controller type and state names are resolved from controller definition data.

Default definitions are loaded from:

settings/trafficSignals.json

A level can override or extend them with:

levels/<levelName>/signalControllerDefinitions.json

When loading a level, the signal system:

  1. Loads default definitions.
  2. Looks for signalControllerDefinitions.json in the level folder.
  3. Merges custom states and types into the defaults.
  4. Loads signals.json.

signalControllerDefinitions.json

This optional file defines custom signal states and controller types for a level.

Simplified example:

{
  "states": {
    "customFlashingYellow": {
      "name": "Custom Flashing Yellow",
      "action": "slow",
      "duration": 3,
      "lights": ["yellow"],
      "flashingLights": [
        ["yellow"],
        ["black"]
      ],
      "flashingInterval": 0.5
    }
  },
  "types": {
    "customWarningLight": {
      "name": "Custom Warning Light",
      "states": ["customFlashingYellow"],
      "defaultIndex": 1,
      "isSimple": true
    }
  }
}

State definition fields

Field Type Description
name string Display name for the state.
action string AI/navigation action associated with the state.
duration number Default duration in seconds.
lights array[string] Visible light colors for this state.
flashingLights array[array[string]] Optional sequence of light color arrays for flashing behavior.
flashingInterval number Time between flashing light changes.

Type definition fields

Field Type Description
name string Display name for the controller type.
states array[string] Ordered list of state keys used by this type.
defaultIndex number Default state index.
isSimple bool Whether this type can be used without a sequence.

Signal actions

Signal states can define an action. This action is sent to vehicle AI through the navigation signal data.

Known action names include:

Action Meaning
alert Alert/caution behavior.
stop Stop behavior.
briefStop Short stop behavior.
slow Slow/caution behavior.
none No special action.

Internally these are converted to numeric action values and sent to vehicle Lua.


Light colors

Signal states define visible light colors using strings.

Common colors:

red
amber
yellow
green
cyan
blue
white
black

black means off.

Example:

"lights": ["red", "black", "black"]

Traffic signal objects use instance color fields:

instanceColor
instanceColor1
instanceColor2
instanceColor3

The signal system switches these values on/off depending on the current state.


Linking signal objects

A signal instance is logical data. The visible 3D traffic light/sign objects are separate scene objects.

Visible signal objects are usually TSStatic objects with a dynamic field:

signalInstance

The value must match the signal instance name.

Example:

signalInstance = "traffic light 1"

When the signal changes state, all linked objects with that signalInstance value are updated.

If a signal instance has no linked objects, it can still affect AI/navigation, but no visible traffic light mesh will change.

Runtime processing

When the level starts, the signal system:

  1. Loads controller definitions.
  2. Loads signals.json.
  3. Creates instances, controllers, and sequences.
  4. Waits for the navigation graph to load.
  5. Finds the best road segment for each signal instance.
  6. Builds a map-node-to-signal lookup table.
  7. Activates sequences and timers.
  8. Sends signal updates to vehicle AI.

The signal system depends on the navigation graph. If the navgraph is reloaded, traffic signals are finalized again.


Map node signals

At runtime, signals are associated with road graph edges.

For each signal instance, the system determines the best road segment and stores data such as:

  • Instance name
  • Position
  • State
  • Action
  • Optional lane usage

This data is sent to vehicle Lua so AI vehicles can react to stop signs, traffic lights, and other signals.


Editor workflow

Use:

World Editor → Gameplay → Traffic Signals Editor

The editor provides tabs for:

  • Signals
  • Controllers
  • Sequences
  • Simulation

It can:

  • Create signal instances
  • Move and rotate signal instances
  • Link visible signal objects
  • Create/edit controllers
  • Create/edit timed sequences
  • Simulate signal timing
  • Validate signal errors
  • Save signals.json
  • Edit custom controller definitions

Basic workflow

  1. Open the Traffic Signals Editor.
  2. Create or load signals.json.
  3. Create controllers or use existing controller types.
  4. Place signal instances in the world.
  5. Assign each signal a controller.
  6. Assign a sequence if it is a timed traffic light.
  7. Link visible TSStatic signal objects.
  8. Run simulation to test states.
  9. Save the file.

Minimal examples

Stop signs

{
  "controllers": [
    {
      "id": 1,
      "name": "stop",
      "type": "signStop",
      "isSimple": true,
      "states": [
        {
          "state": "basicStop"
        }
      ]
    }
  ],
  "instances": [
    {
      "id": 2,
      "name": "stop 1",
      "controllerId": 1,
      "sequenceId": 0,
      "pos": [0, 0, 0],
      "dir": [1, 0, 0],
      "startDisabled": false
    }
  ],
  "sequences": []
}

Two-phase traffic light

{
  "controllers": [
    {
      "id": 1,
      "name": "north_south",
      "type": "lightsBasic",
      "states": [
        {"state": "greenTrafficLight", "duration": 12},
        {"state": "yellowTrafficLight", "duration": 4},
        {"state": "redTrafficLight", "duration": 1}
      ]
    },
    {
      "id": 2,
      "name": "east_west",
      "type": "lightsBasic",
      "states": [
        {"state": "greenTrafficLight", "duration": 12},
        {"state": "yellowTrafficLight", "duration": 4},
        {"state": "redTrafficLight", "duration": 1}
      ]
    }
  ],
  "sequences": [
    {
      "id": 3,
      "name": "intersection_sequence",
      "startTime": 0,
      "phases": [
        {"controllerIds": [1]},
        {"controllerIds": [2]}
      ]
    }
  ],
  "instances": [
    {
      "id": 4,
      "name": "traffic light north",
      "controllerId": 1,
      "sequenceId": 3,
      "pos": [0, 10, 0],
      "dir": [0, -1, 0],
      "group": "intersection_01",
      "startDisabled": false
    },
    {
      "id": 5,
      "name": "traffic light east",
      "controllerId": 2,
      "sequenceId": 3,
      "pos": [10, 0, 0],
      "dir": [-1, 0, 0],
      "group": "intersection_01",
      "startDisabled": false
    }
  ]
}

Best practices

  • Use the Traffic Signals Editor instead of manual editing when possible.
  • Keep IDs unique.
  • Keep names unique.
  • Use simple controllers for stop signs and static signs.
  • Use sequences for timed traffic lights.
  • Make sure signal instances are near valid roads.
  • Make dir point along the vehicle approach/pass direction.
  • Link visible signal objects using signalInstance.
  • Use groups for intersections.
  • Test with Simulation mode before saving.
  • Validate using Tools → Check Signal Errors.

Common issues

Signal does not affect AI

Possible causes:

  • Signal is too far from a valid road segment.
  • controllerId is missing or invalid.
  • sequenceId is invalid.
  • Navigation graph has not loaded/reloaded.
  • Signal direction is wrong.

Traffic light mesh does not change lights

Possible causes:

  • No linked TSStatic object.
  • Object does not have correct signalInstance dynamic field.
  • signalInstance value does not match the signal name.
  • Mesh/material does not use expected instance color fields.

Signal appears as error in debug

Possible causes:

  • Missing controller.
  • Missing sequence.
  • Could not find closest road node.
  • Duplicate element name.
  • Duplicate ID.

Sequence does not advance

Possible causes:

  • ignoreTimer is true.
  • Sequence is disabled.
  • Controller durations are missing or infinite.
  • Sequence has zero duration.

Stop sign needs a sequence

Stop sign/simple signal controllers usually do not need a sequence. Use:

"sequenceId": 0

Custom signal type not found

Check:

signalControllerDefinitions.json

and verify that the controller type exists.


Summary

signals.json defines level traffic signal logic.

It contains:

  • instances - placed signal points
  • controllers - signal behavior/state lists
  • sequences - timed phase logic

The system links signal instances to nearby navigation graph edges, updates visible signal objects through signalInstance, and sends signal actions to vehicle AI.

Last modified: May 28, 2026

Any further questions?

Join our discord
Our documentation is currently incomplete and undergoing active development. If you have any questions or feedback, please visit this forum thread.