Claim set · data/claims/openttd.yaml

OpenTTD 15.3 (source release)

Game: OpenTTD.

OpenTTD 15.3 has six kinds of rail signal: the block signal; three pre-signals (entry, exit and combo); the path signal; and the one-way path signal. Each comes as a light signal or a semaphore, which look different and behave alike. openttd/signal-types

About: OpenTTD Rail signalling

Value
[
 "block",
 "entry",
 "exit",
 "combo",
 "path",
 "one-way-path"
]
CodeOpenTTD source code, release 15.3 · src/signal_type.h#L15-L19/** Variant of the signal, i.e. how does the signal look? */ enum SignalVariant : uint8_t { SIG_ELECTRIC = 0, ///< Light signal SIG_SEMAPHORE = 1, ///< Old-fashioned semaphore signal };OpenTTD source code, release 15.3 · src/signal_type.h#L22-L29/** Type of signal, i.e. how does the signal behave? */ enum SignalType : uint8_t { SIGTYPE_BLOCK = 0, ///< block signal SIGTYPE_ENTRY = 1, ///< presignal block entry SIGTYPE_EXIT = 2, ///< presignal block exit SIGTYPE_COMBO = 3, ///< presignal inter-block SIGTYPE_PBS = 4, ///< normal pbs signal SIGTYPE_PBS_ONEWAY = 5, ///< no-entry signalChecked by claude, 2026-10-26 — read the SignalVariant and SignalType enums at the 15.3 tag

Cited by: OpenTTD, Rail signalling

Any signal that isn't a path signal holding a reservation turns red while a train is anywhere in the block it leads into; a train inside a depot doesn't count. A block runs from signal to signal through junctions, stations, level crossings, tunnels and bridges, over the owning company's track only. openttd/block-signal

About: OpenTTD Rail signalling

CodeOpenTTD source code, release 15.3 · src/signal.cpp#L422-L428/* Signal state of reserved path signals is handled by the reserve/unreserve process. */ if (IsPbsSignal(sig) && (GetRailReservationTrackBits(tile) & TrackToTrackBits(track)) != TRACK_BIT_NONE) continue; /* determine whether the new state is red */ if (flags.Test(SigFlag::Train)) { /* train in the segment */ newstate = SIGNAL_STATE_RED;OpenTTD source code, release 15.3 · src/signal.cpp#L194-L197static bool IsTrainAndNotInDepot(const Vehicle *v) { return v->type == VEH_TRAIN && Train::From(v)->track != TRACK_BIT_DEPOT; }OpenTTD source code, release 15.3 · src/signal.cpp#L279if (GetTileOwner(tile) != owner) continue; // do not propagate signals on others' tiles (remove for tracksharing)OpenTTD source code, release 15.3 · src/signal.cpp#L302-L306/* If no train detected yet, and there is not no train -> there is a train -> set the flag */ if (!flags.Test(SigFlag::Train) && EnsureNoTrainOnTrackBits(tile, tracks).Failed()) flags. Set(SigFlag::Train); } else { if (tracks_masked == TRACK_BIT_NONE) continue; // no incidating track if (!flags.Test(SigFlag::Train) && HasVehicleOnTile(tile, IsTrainAndNotInDepot)) flags.Set(SigFlag::Train);Checked by claude, 2026-10-26 — read ExploreSegment (L266-L401), which walks the block and sets the train flag on railway, station, level-crossing and tunnel or bridge tiles, and UpdateSignalsAroundSegment (L410-L460)

A reserved path signal's state is set by the reservation instead (path-signal); that is the one branch the train test skips.

Cited by: OpenTTD, Rail signalling

With no train in the block, an entry signal turns red when the block ahead has at least one exit or combo signal leading out of it and none of them is green, whichever way the waiting train would go. A combo signal acts as an entry and an exit at once; one placed both ways doesn't count its own back face as a way out. When an exit or combo signal changes color, the block behind it is worked out again, so a chain of combo signals passes the state back block by block. openttd/pre-signals

About: OpenTTD Rail signalling

CodeOpenTTD source code, release 15.3 · src/signal.cpp#L432-L446} else { /* is it a bidir combo? - then do not count its other signal direction as exit */ if (sig == SIGTYPE_COMBO && HasSignalOnTrackdir(tile, ReverseTrackdir(trackdir))) { /* at least one more exit */ if (flags.Test(SigFlag::MultiExit) && /* no green exit */ (!flags.Test(SigFlag::Green) || /* only one green exit, and it is this one - so all other exits are red */ (!flags.Test(SigFlag::MultiGreen) && GetSignalStateByTrackdir(tile, ReverseTrackdir(trackdir)) == SIGNAL_STATE_GREEN))) { newstate = SIGNAL_STATE_RED; } } else { // entry, at least one exit, no green exit …OpenTTD source code, release 15.3 · src/signal.cpp#L329-L338/* if it is a presignal EXIT in OUR direction and we haven't found 2 green exits yes, do special check */ if (!flags.Test(SigFlag::MultiGreen) && IsPresignalExit(tile, track) && HasSignalOnTrackdir(tile, trackdir)) { // found presignal exit if (flags.Test(SigFlag::Exit)) flags.Set(SigFlag::MultiExit); // found two (or more) exits flags.Set(SigFlag::Exit); // found at least one exit - allow for compiler optimizations if (GetSignalStateByTrackdir(tile, trackdir) == SIGNAL_STATE_GREEN) { // found green presignal exit if (flags.Test(SigFlag::Green)) flags.Set(SigFlag::MultiGreen); flags.Set(SigFlag::Green); } }OpenTTD source code, release 15.3 · src/signal.cpp#L448-L454/* only when the state changes */ if (newstate != GetSignalStateByTrackdir(tile, trackdir)) { if (IsPresignalExit(tile, TrackdirToTrack(trackdir))) { /* for pre-signal exits, add block to the global set */ DiagDirection exitdir = TrackdirToExitdir(ReverseTrackdir(trackdir)); _globset.Add(tile, exitdir); // do not check for full global set, first update all signals }OpenTTD source code, release 15.3 · src/rail_map.h#L307-L315inline bool IsPresignalEntry(Tile t, Track track) { return GetSignalType(t, track) == SIGTYPE_ENTRY || GetSignalType(t, track) == SIGTYPE_COMBO; } inline bool IsPresignalExit(Tile t, Track track) { return GetSignalType(t, track) == SIGTYPE_EXIT || GetSignalType(t, track) == SIGTYPE_COMBO; }Checked by claude, 2026-10-26 — read the exit and green flags set in ExploreSegment, the entry and combo branches of UpdateSignalsAroundSegment, and IsPresignalEntry/IsPresignalExit, which count a combo as both

Cited by: OpenTTD, Rail signalling

A path signal with no reservation behind it is red when a train is in its block, and also when the block holds a junction or has more than one signal leading into it; otherwise it is green. A train at a path signal reserves track to the next safe place to stop, and the signal turns green for it when the reservation succeeds. A safe place is a depot, a signal that isn't a path signal, the tile before a path signal on a single line ahead, or, where the search allows it, a dead end; a one-way path signal facing the other way counts only as such a dead end. openttd/path-signal

About: OpenTTD Rail signalling

CodeOpenTTD source code, release 15.3 · src/signal.cpp#L422-L431/* Signal state of reserved path signals is handled by the reserve/unreserve process. */ if (IsPbsSignal(sig) && (GetRailReservationTrackBits(tile) & TrackToTrackBits(track)) != TRACK_BIT_NONE) continue; /* determine whether the new state is red */ if (flags.Test(SigFlag::Train)) { /* train in the segment */ newstate = SIGNAL_STATE_RED; } else if (IsPbsSignal(sig) && flags.Any({SigFlag::Split, SigFlag::MultiEnter})) { /* Turn path signals red if the segment has a junction or more than one way in. */ newstate = SIGNAL_STATE_RED;OpenTTD source code, release 15.3 · src/pbs.cpp#L381-L417bool IsSafeWaitingPosition(const Train *v, TileIndex tile, Trackdir trackdir, bool include_line_end, bool forbid_90deg) { if (IsRailDepotTile(tile)) return true; if (IsTileType(tile, MP_RAILWAY)) { /* For non-pbs signals, stop on the signal tile. */ if (HasSignalOnTrackdir(tile, trackdir) && !IsPbsSignal(GetSignalType(tile, TrackdirToTrack(trackdir)))) return true; } /* Check next tile. For performance reasons, we check for 90 degree turns ourself. */ CFollowTrackRail ft(v, GetAllCompatibleRailTypes(v->railtypes)); /* End of track? */ if (!ft.Follow(tile, trackdir)) { /* Last tile of a terminus station is a safe position. */ …OpenTTD source code, release 15.3 · src/train_cmd.cpp#L2747-L2750if (track != INVALID_TRACK && HasPbsSignalOnTrackdir(tile, TrackEnterdirToTrackdir(track, enterdir))) { do_track_reservation = true; changed_signal = true; SetSignalStateByTrackdir(tile, TrackEnterdirToTrackdir(track, enterdir), SIGNAL_STATE_GREEN);OpenTTD source code, release 15.3 · src/train_cmd.cpp#L2759-L2764if (do_track_reservation) { res_dest = ExtendTrainReservation(v, &tracks, &dest_enterdir); if (res_dest.tile == INVALID_TILE) { /* Reservation failed? */ if (mark_stuck) MarkTrainAsStuck(v); if (changed_signal) SetSignalStateByTrackdir(tile, TrackEnterdirToTrackdir(best_track, enterdir), SIGNAL_STATE_RED);Checked by claude, 2026-10-26 — read UpdateSignalsAroundSegment, IsSafeWaitingPosition, ChooseTrainTrack's reservation (green first, back to red if ExtendTrainReservation fails), and the reservation walk in train_cmd.cpp (L2430-L2475) that turns a passed path signal back to red

Cited by: OpenTTD, Rail signalling

The pathfinder treats a signal that faces only against the train as a dead end, unless it is a two-way path signal: that one a train may pass from behind, at a cost of 15 tiles of track while it is within the first ten signals of the path. openttd/signal-from-behind

About: OpenTTD Rail signalling

Value: 1500 path-cost units (100 a tile)

CodeOpenTTD source code, release 15.3 · src/pathfinder/yapf/yapf_costrail.hpp#L176-L183int cost = 0; /* if there is one-way signal in the opposite direction, then it is not our way */ if (IsTileType(tile, MP_RAILWAY)) { bool has_signal_against = HasSignalOnTrackdir(tile, ReverseTrackdir(trackdir)); bool has_signal_along = HasSignalOnTrackdir(tile, trackdir); if (has_signal_against && !has_signal_along && IsOnewaySignal(tile, TrackdirToTrack(trackdir))) { /* one-way signal in opposite direction */ n.segment->end_segment_reason.Set(EndSegmentReason::DeadEnd);OpenTTD source code, release 15.3 · src/pathfinder/yapf/yapf_costrail.hpp#L237-L239if (has_signal_against && IsPbsSignal(GetSignalType(tile, TrackdirToTrack(trackdir)))) { cost += n.num_signals_passed < Yapf().PfGetSettings().rail_look_ahead_max_signals ? Yapf().PfGetSettings().rail_pbs_signal_back_penalty : 0; }OpenTTD source code, release 15.3 · src/rail_map.h#L318-L321inline bool IsOnewaySignal(Tile t, Track track) { return GetSignalType(t, track) != SIGTYPE_PBS; }OpenTTD source code, release 15.3 · src/table/settings/pathfinding_settings.ini#L261-L264var = pf.yapf.rail_pbs_signal_back_penalty … def = 15 * YAPF_TILE_LENGTHChecked by claude, 2026-10-26 — read SignalCost and IsOnewaySignal (every type but SIGTYPE_PBS); the back-penalty cap is rail_look_ahead_max_signals, 10 by default (ini L207-L210)

Cited by: OpenTTD, Rail signalling

OpenTTD's block signal does what Factorio's rail signal does: it keeps a train out of a block another train is in. openttd/block-signal-like-factorio

About: OpenTTD Rail signalling

Dev blogOpenTTD source code, release 15.3 · src/signal.cpp#L426-L428if (flags.Test(SigFlag::Train)) { /* train in the segment */ newstate = SIGNAL_STATE_RED;Friday Facts #81 - Chain signals · The definitionWhile normal signal prevents train from entering the occupied blockChecked by claude, 2026-10-26 — read UpdateSignalsAroundSegment at the 15.3 tag and the definition in FFF-81's pinned capture (factorio-logistics/signals-and-blocks)

Cited by: OpenTTD, Rail signalling

OpenTTD's entry pre-signal and Factorio's chain signal both look past the next block, but differently: the entry signal clears when any exit signal of the block ahead is green, whichever exit the train will take, while the chain signal looks only at the exit on the train's path. Friday Facts #81 (2015) says the chain signal resembles the "TTD pre signals" but works differently. openttd/entry-signal-vs-chain-signal

About: OpenTTD Rail signalling

Dev blogOpenTTD source code, release 15.3 · src/signal.cpp#L443-L444} else { // entry, at least one exit, no green exit if (IsPresignalEntry(tile, TrackdirToTrack(trackdir)) && flags.Test(SigFlag::Exit) && !flags.Test(SigFlag::Green)) newstate = SIGNAL_STATE_RED;Friday Facts #81 - Chain signals · The definitionchain signal prevents train from entering the block also when the exit isn't free. When more exits exist, the one relevant to the train path is taken into account.Friday Facts #81 - Chain signals · The chain signal in detailThe chain signals have some similarities with the TTD pre signals, but as they actually work differently, we also decided to call them differently to avoid confusion.Checked by claude, 2026-10-26 — read the entry branch at the 15.3 tag; its exit and green flags are set for any exit signal in the block (L329-L338), not per route. FFF-81's sentences are in its pinned capture (factorio-logistics/chain-signal)

Cited by: OpenTTD, Rail signalling

Trains route with YAPF ("Yet Another PathFinder"); OpenTTD 15.0-beta1 (December 2024) removed the older NPF pathfinder and the setting that chose between them. openttd/yapf-only

About: OpenTTD Pathfinding

CodeOpenTTD source code, release 15.3 · src/train_cmd.cpp#L2505return YapfTrainChooseTrack(v, tile, enterdir, tracks, path_found, do_track_reservation, dest, final_dest);OpenTTD source code, release 15.3 · changelog.md#L609- Remove: NPF and pathfinder change settings (#12209)Checked by claude, 2026-10-26 — read ChooseTrainTrack's call and the changelog line, under its 15.0-beta1 (2024-12-24) heading

Cited by: OpenTTD

YAPF prices a route in hundredths of a tile: 100 for a tile of straight track and 71 for a corner piece, the short piece inside a curve, plus 100 for a 45° bend, 600 for a 90° turn, 200 for an upward slope, 300 for a level crossing, added only on a straight tile, 5000 for reversing in a depot, and 1000 for each tile of a station platform the train passes through. The destination's platform instead costs 800 if it's longer than the train and 4000 if it's shorter. openttd/yapf-track-cost

About: OpenTTD Pathfinding

Value
{
 "tile": 100,
 "cornerTile": 71,
 "curve45": 100,
 "curve90": 600,
 "slope": 200,
 "levelCrossing": 300,
 "depotReverse": 5000,
 "stationPerTile": 1000,
 "longerPlatform": 800,
 "shorterPlatform": 4000
}
CodeOpenTTD source code, release 15.3 · src/pathfinder/pathfinder_type.h#L16-L19static const int YAPF_TILE_LENGTH = 100; /** Length (penalty) of a corner with YAPF */ static const int YAPF_TILE_CORNER_LENGTH = 71;OpenTTD source code, release 15.3 · src/pathfinder/yapf/yapf_costrail.hpp#L124-L146inline int OneTileCost(TileIndex &tile, Trackdir trackdir) { int cost = 0; /* set base cost */ if (IsDiagonalTrackdir(trackdir)) { cost += YAPF_TILE_LENGTH; switch (GetTileType(tile)) { case MP_ROAD: /* Increase the cost for level crossings */ if (IsLevelCrossing(tile)) { cost += Yapf().PfGetSettings().rail_crossing_penalty; } …OpenTTD source code, release 15.3 · src/pathfinder/yapf/yapf_costrail.hpp#L102-L108if (TrackFollower::Allow90degTurns() && HasTrackdir(TrackdirCrossesTrackdirs(td1), td2)) { /* 90-deg curve penalty */ cost += Yapf().PfGetSettings().rail_curve90_penalty; } else if (td2 != NextTrackdir(td1)) { /* 45-deg curve penalty */ cost += Yapf().PfGetSettings().rail_curve45_penalty;OpenTTD source code, release 15.3 · src/pathfinder/yapf/yapf_costrail.hpp#L450-L453uint platform_length = follower->tiles_skipped + 1; /* We don't know yet if the station is our target or not. Act like * if it is pass-through station (not our destination). */ segment_cost += Yapf().PfGetSettings().rail_station_penalty * platform_length;OpenTTD source code, release 15.3 · src/pathfinder/yapf/yapf_costrail.hpp#L604-L607/* Reduce the extra cost caused by passing-station penalty (each station receives it in the segment cost). */ extra_cost -= Yapf().PfGetSettings().rail_station_penalty * platform_length; /* Add penalty for the inappropriate platform length. */ extra_cost += PlatformLengthPenalty(platform_length);OpenTTD source code, release 15.3 · src/table/settings/pathfinding_settings.ini#L153-L201var = pf.yapf.rail_station_penalty … def = 10 * YAPF_TILE_LENGTH … var = pf.yapf.rail_slope_penalty … def = 2 * YAPF_TILE_LENGTH … var = pf.yapf.rail_curve45_penalty … def = 1 * YAPF_TILE_LENGTH … …OpenTTD source code, release 15.3 · src/table/settings/pathfinding_settings.ini#L279-L300var = pf.yapf.rail_longer_platform_penalty … def = 8 * YAPF_TILE_LENGTH … var = pf.yapf.rail_longer_platform_per_tile_penalty … def = 0 * YAPF_TILE_LENGTH … var = pf.yapf.rail_shorter_platform_penalty … def = 40 * YAPF_TILE_LENGTHChecked by claude, 2026-10-26 — read OneTileCost, CurveCost and PfCalcCost's station and platform-length branches, and the defaults in pathfinding_settings.ini; the per-tile platform penalties default to 0

Every penalty is a game setting (SC_EXPERT in the settings table); the numbers are the defaults. A speed-limit penalty for slow bridges and a path-signal reservation cost (300 for crossing reserved track, 800 a tile for a reserved platform) also exist and aren't listed here.

Cited by: OpenTTD

For each of the first ten signals along a route, a red block or pre-signal adds a cost that falls with distance: 500 for the first, then 405, 320, 245, 180, 125, 80, 45, 20 and 5; green signals and red path signals add none. If the first signal is red, a block or entry signal adds 1000 more and an exit or combo signal 10000. With the rail_firstred_twoway_eol setting on, as it is by default, a red first block or pre-signal that is two-way, met after the route has had a choice of track, ends that branch of the search as a dead end. openttd/yapf-signal-cost

About: OpenTTD Pathfinding

Value
{
 "lookAhead": [
  500,
  405,
  320,
  245,
  180,
  125,
  80,
  45,
  20,
  5
 ],
 "firstRedBlock": 1000,
 "firstRedExit": 10000
}
CodeOpenTTD source code, release 15.3 · src/pathfinder/yapf/yapf_costrail.hpp#L62-L69int p0 = Yapf().PfGetSettings().rail_look_ahead_signal_p0; int p1 = Yapf().PfGetSettings().rail_look_ahead_signal_p1; int p2 = Yapf().PfGetSettings().rail_look_ahead_signal_p2; this->sig_look_ahead_costs.clear(); this->sig_look_ahead_costs.reserve(Yapf().PfGetSettings().rail_look_ahead_max_signals); for (uint i = 0; i < Yapf().PfGetSettings().rail_look_ahead_max_signals; i++) { this->sig_look_ahead_costs.push_back(p0 + i * (p1 + i * p2)); }OpenTTD source code, release 15.3 · src/pathfinder/yapf/yapf_costrail.hpp#L192-L229int look_ahead_cost = (n.num_signals_passed < this->sig_look_ahead_costs.size()) ? this->sig_look_ahead_costs[n.num_signals_passed] : 0; if (sig_state != SIGNAL_STATE_RED) { /* green signal */ n.flags_u.flags_s.last_signal_was_red = false; /* negative look-ahead red-signal penalties would cause problems later, so use them as positive penalties for green signal */ if (look_ahead_cost < 0) { /* add its negation to the cost */ cost -= look_ahead_cost; } } else { /* we have a red signal in our direction * was it first signal which is two-way? */ …OpenTTD source code, release 15.3 · src/table/settings/pathfinding_settings.ini#L111-L129var = pf.yapf.rail_firstred_twoway_eol … def = true … var = pf.yapf.rail_firstred_penalty … def = 10 * YAPF_TILE_LENGTH … var = pf.yapf.rail_firstred_exit_penalty … def = 100 * YAPF_TILE_LENGTHOpenTTD source code, release 15.3 · src/table/settings/pathfinding_settings.ini#L207-L237var = pf.yapf.rail_look_ahead_max_signals … def = 10 … var = pf.yapf.rail_look_ahead_signal_p0 … def = 500 … var = pf.yapf.rail_look_ahead_signal_p1 … def = -100 … …Checked by claude, 2026-10-26 — read SignalCost and the look-ahead table built in the constructor (p0 + i × (p1 + i × p2) with 500, −100 and 5, for i from 0 to 9); every term is positive, so the green-signal branch for a negative term never runs at the defaults

Cited by: OpenTTD, Rail signalling

Both games' train pathfinders count a red signal or a busy block for less the farther ahead it lies: Factorio divides the penalty for an occupied block by how many blocks it is from the start, and OpenTTD's default penalty for a red signal that isn't a path signal falls from 500 at the first signal to 5 at the tenth and stops. openttd/path-cost-vs-factorio

About: OpenTTD Pathfinding

WikiTrain path finder · Path finding penaltiesWhen the rail block is occupied by a train -> Add a penalty of 2 * length of the block divided by the number of blocks from the start, so the far away occupied paths don't matter much.OpenTTD source code, release 15.3 · src/pathfinder/yapf/yapf_costrail.hpp#L214-L218/* look-ahead signal penalty */ if (!IsPbsSignal(sig_type) && look_ahead_cost > 0) { /* add the look ahead penalty only if it is positive */ cost += look_ahead_cost; }OpenTTD source code, release 15.3 · src/pathfinder/yapf/yapf_costrail.hpp#L67-L68for (uint i = 0; i < Yapf().PfGetSettings().rail_look_ahead_max_signals; i++) { this->sig_look_ahead_costs.push_back(p0 + i * (p1 + i * p2));OpenTTD source code, release 15.3 · src/table/settings/pathfinding_settings.ini#L207-L237var = pf.yapf.rail_look_ahead_max_signals … def = 10 … var = pf.yapf.rail_look_ahead_signal_p0 … def = 500 … var = pf.yapf.rail_look_ahead_signal_p1 … def = -100 … …Checked by claude, 2026-10-26 — Factorio's rule from the wiki's penalties section (factorio-logistics/path-cost-length); OpenTTD's from the look-ahead table at the 15.3 tag

Cited by: OpenTTD, Rail signalling

Factorio's pathfinder, which costs a route by the length of its rail segments in tiles, adds 2000 for each train stop on the route other than the one at the train's start, while OpenTTD's charges 10 tiles' worth for each tile of station platform a train runs through. openttd/station-cost-vs-factorio

About: OpenTTD Pathfinding

Value
{
 "factorioPerStop": 2000,
 "openttdPerPlatformTile": 10
}
CodeTrain path finder · Path finding penaltiesBase cost for a block/segment is the length of the segment (linear grid length along the center of the rail).Train path finder · Path finding penaltiesWhen the path includes a train stop -> Add a penalty of 2000.Train path finder · History, 0.18.1Train stop at train's starting segment exit is no longer counted into penalty.factorio-data: Factorio's Lua prototype definitions, version 2.1.23 · core/prototypes/utility-constants.lua#L320train_stop_penalty = 2000,OpenTTD source code, release 15.3 · src/pathfinder/yapf/yapf_costrail.hpp#L450-L453uint platform_length = follower->tiles_skipped + 1; /* We don't know yet if the station is our target or not. Act like * if it is pass-through station (not our destination). */ segment_cost += Yapf().PfGetSettings().rail_station_penalty * platform_length;OpenTTD source code, release 15.3 · src/table/settings/pathfinding_settings.ini#L153-L156var = pf.yapf.rail_station_penalty … def = 10 * YAPF_TILE_LENGTHChecked by claude, 2026-10-26 — read train_stop_penalty at Factorio 2.1.23 (factorio-logistics/path-penalties, whose unit is tiles of track) and rail_station_penalty at OpenTTD 15.3, where 100 units make a tile

Cited by: OpenTTD

A vehicle's orders send it to stations, depots and waypoints, or skip ahead on a condition: its load, reliability, maximum reliability, top speed, age, remaining lifetime or need for servicing, or always. At a station an order sets how to load (if available, full load, full load any cargo, or none) and how to unload (normally, unload all, transfer, or none). openttd/order-kinds

About: OpenTTD Rail transport

CodeOpenTTD source code, release 15.3 · src/order_type.h#L50-L61enum OrderType : uint8_t { OT_BEGIN = 0, OT_NOTHING = 0, OT_GOTO_STATION = 1, OT_GOTO_DEPOT = 2, OT_LOADING = 3, OT_LEAVESTATION = 4, OT_DUMMY = 5, OT_GOTO_WAYPOINT = 6, OT_CONDITIONAL = 7, OT_IMPLICIT = 8, OT_ENDOpenTTD source code, release 15.3 · src/order_type.h#L67-L81enum class OrderUnloadType : uint8_t { UnloadIfPossible = 0, ///< Unload all cargo that the station accepts. Unload = 1, ///< Force unloading all cargo onto the platform, possibly not getting paid. Transfer = 2, ///< Transfer all cargo onto the platform. NoUnload = 4, ///< Totally no unloading will be done. }; /** * Loading order types. */ enum class OrderLoadType : uint8_t { LoadIfPossible = 0, ///< Load as long as there is cargo that fits in the train. FullLoad = 2, ///< Full load all cargoes of the consist. …OpenTTD source code, release 15.3 · src/order_type.h#L128-L136enum class OrderConditionVariable : uint8_t { LoadPercentage = 0, ///< Skip based on the amount of load Reliability = 1, ///< Skip based on the reliability MaxSpeed = 2, ///< Skip based on the maximum speed Age = 3, ///< Skip based on the age RequiresService = 4, ///< Skip when the vehicle requires service Unconditionally = 5, ///< Always skip RemainingLifetime = 6, ///< Skip based on the remaining lifetime MaxReliability = 7, ///< Skip based on the maximum reliabilityChecked by claude, 2026-10-26 — read the OrderType, OrderUnloadType, OrderLoadType and OrderConditionVariable enums at the 15.3 tag; OT_LOADING, OT_LEAVESTATION, OT_DUMMY and OT_IMPLICIT are states and markers, not orders a player gives

Cited by: OpenTTD

A train with a full-load order stays at the station until every vehicle in it is full; with "full load any cargo" it may leave once all the vehicles carrying some one cargo are full. The test runs on a loading round in which nothing was loaded or unloaded. Aircraft with passengers are the exception to the any-cargo rule: they wait until the passenger hold is full. openttd/full-load

About: OpenTTD Rail transport

CodeOpenTTD source code, release 15.3 · src/economy.cpp#L1824-L1828if (v->cargo.StoredCount() >= v->cargo_cap) { SetBit(cargo_full, v->cargo_type); } else { SetBit(cargo_not_full, v->cargo_type); }OpenTTD source code, release 15.3 · src/economy.cpp#L1866-L1876if (front->current_order.IsFullLoadOrder()) { if (front->current_order.GetLoadType() == OrderLoadType::FullLoadAny) { /* if the aircraft carries passengers and is NOT full, then * continue loading, no matter how much mail is in */ if ((front->type == VEH_AIRCRAFT && IsCargoInClass(front->cargo_type, CargoClass::Passengers) && front->cargo_cap > front->cargo.StoredCount()) || (cargo_not_full != 0 && (cargo_full & ~cargo_not_full) == 0)) { // There are still non-full cargoes finished_loading = false; } } else if (cargo_not_full != 0) { finished_loading = false; }OpenTTD source code, release 15.3 · src/order_type.h#L77-L81enum class OrderLoadType : uint8_t { LoadIfPossible = 0, ///< Load as long as there is cargo that fits in the train. FullLoad = 2, ///< Full load all cargoes of the consist. FullLoadAny = 3, ///< Full load a single cargo of the consist. NoLoad = 4, ///< Do not load anything.Checked by claude, 2026-10-26 — read the cargo_full/cargo_not_full masks in LoadUnloadVehicle and the finished_loading test in its else branch, entered when a round loaded and unloaded nothing (L1847)

Cited by: OpenTTD

Without a full-load order, a vehicle stops loading once a round has unloaded nothing and filled no vehicle to the round's limit, no cargo reserved for it is left, and it has waited its timetabled time at the stop, less however late it is running. openttd/leave-when-loaded

About: OpenTTD Rail transport

CodeOpenTTD source code, release 15.3 · src/economy.cpp#L1855-L1859/* We loaded less cargo than possible for all cargo types and it's not full * load and we're not supposed to wait any longer: stop loading. */ if (!anything_unloaded && full_load_amount == 0 && reservation_left == 0 && !front->current_order.IsFullLoadOrder() && front->current_order_time >= std::max(front->current_order.GetTimetabledWait() - front->lateness_counter, 0)) { front->vehicle_flags.Set(VehicleFlag::StopLoading);OpenTTD source code, release 15.3 · src/economy.cpp#L1789-L1793if (loaded == cap_left) { SetBit(full_load_amount, v->cargo_type); } else { ClrBit(full_load_amount, v->cargo_type); }Checked by claude, 2026-10-26 — read the StopLoading test in LoadUnloadVehicle and where full_load_amount is set per cargo (a round that loaded all the space it could)

A vehicle running late shortens its stop by its lateness, down to none, which is how a timetable lets it catch up.

Cited by: OpenTTD

The OpenTTD wiki describes timetables as setting how long a vehicle waits at each station and spends on each leg, with an autofill button that measures a run, and a late counter; it says they keep vehicles on shared orders from bunching up. openttd/timetable

About: OpenTTD Rail transport

WikiOpenTTD Wiki — Timetable · Using timetables, first paragraphYou can choose how much time a vehicle should spend in a stationOpenTTD Wiki — Timetable · Using timetables'Autofill' will measure how long your vehicle takes to travel each leg of the journey, and how long it spends in stations.OpenTTD Wiki — Timetable · Using timetablesTimetables are useful for preventing vehicles from "bunching up".OpenTTD Wiki — Timetable · Timetable window, "Reset late counter"Sets late counter to zero.Checked by claude, 2026-10-26 — read the timetable page in its pinned January 2026 capture

Cited by: OpenTTD

A train's window holds a small view of the map that follows the train and zooms with the mouse wheel unless the wheel is switched off in the settings; its location button centers the main view on the train, follows it on a double click, and opens a new viewport on it with Ctrl. openttd/train-window-view

About: OpenTTD Rail transport

CodeOpenTTD source code, release 15.3 · src/vehicle_gui.cpp#L2837NWidget(NWID_VIEWPORT, INVALID_COLOUR, WID_VV_VIEWPORT), SetMinimalSize(226, 84), SetResize(1, 1),OpenTTD source code, release 15.3 · src/vehicle_gui.cpp#L3039this->GetWidget<NWidgetViewport>(WID_VV_VIEWPORT)->InitializeViewport(this, static_cast<VehicleID>(this->window_number), ScaleZoomGUI(_vehicle_view_zoom_levels[v->type]));OpenTTD source code, release 15.3 · src/vehicle_gui.cpp#L3338-L3343void OnMouseWheel(int wheel, WidgetID widget) override { if (widget != WID_VV_VIEWPORT) return; if (_settings_client.gui.scrollwheel_scrolling != SWS_OFF) { DoZoomInOutWindow(wheel < 0 ? ZOOM_IN : ZOOM_OUT, this); }OpenTTD source code, release 15.3 · src/vehicle_gui.cpp#L3249-L3262case WID_VV_LOCATION: // center main view if (_ctrl_pressed) { ShowExtraViewportWindow(TileVirtXY(v->x_pos, v->y_pos)); } else { const Window *mainwindow = GetMainWindow(); if (click_count > 1) { /* main window 'follows' vehicle */ mainwindow->viewport->follow_vehicle = v->index; } else { if (mainwindow->viewport->follow_vehicle == v->index) mainwindow->viewport->follow_vehicle = VehicleID::Invalid(); ScrollMainWindowTo(v->x_pos, v->y_pos, v->z_pos); } …OpenTTD source code, release 15.3 · src/lang/english.txt#L4441STR_VEHICLE_VIEW_TRAIN_CENTER_TOOLTIP :{BLACK}Centre main view on train's location. Double click to follow train in main view. Ctrl+Click to open a new viewport on train's locationChecked by claude, 2026-10-26 — read the vehicle view window's widget tree, its constructor (the viewport is given the vehicle id to follow) and OnMouseWheel, and the tooltip string

Cited by: OpenTTD