OpenTTD
The open-source remake of Transport Tycoon Deluxe, first released in 2004.
OpenTTD is an open-source remake of Chris Sawyer’s Transport Tycoon Deluxe. The player runs a transport company from around 1950, building rail, road, air and sea routes, and tries to become the highest-ranking company by 20501. Its About page says it mimics the original game as closely as possible while adding new features2.
Owen Rudge, who would release the game, writes that Ludvig Strigeus began rebuilding Transport Tycoon Deluxe in C in June 20023. Players then ran the 1995 game with Josef Drexler’s TTDPatch, and Rudge recalls the patch’s limits: the map size was fixed, no cargo types could be added, and multiplayer was hard to improve4. Rudge released it as OpenTTD 0.1 on 6 March 2004. His release post called it 95% complete, and players still had to copy in the original game’s graphics and sound files to run it5. A community of developers formed around it6. Free replacements for those graphics, sounds and music made it a stand-alone game with version 1.0.0 in 20107, and it moved to GitHub in 2018 and reached Steam in 20218.
It heads the table of Obligement’s Amiga Games Award 20049. Lewis Denby put it on PC Gamer’s 2011 list of 20 free PC games you must play10, and Rock Paper Shotgun’s Graham Smith called it “one of the seven wonders of PC gaming” in 2021, writing that successors such as Transport Fever never recapture its joy of tinkering with trains and lorries11. Ted John has said OpenTTD heavily inspired his OpenRCT2, an open-source remake of RollerCoaster Tycoon 212.
#How it plays
Trains need signals to share track. A plain block signal stops a train while another train is anywhere in the stretch of track ahead, up to the next signals and through any junctions13. Three pre-signals look further. An entry signal also stays red when every exit signal leading out of the block ahead is red, even if that block is empty, so at the mouth of a station whose platform exits all show red it holds trains back. It doesn’t know which exit a train wants: any green exit clears it. A combo signal is an entry and an exit at once, so pre-signals can be chained14. Path signals, added in 200915, let a train reserve its own route through a junction to the next safe place to stop, and turn green only for the train that holds the reservation16. An ordinary path signal can be passed from behind; a one-way path signal can’t17. Each signal comes as a light or a semaphore, which only look different18.
Trains find their way with a pathfinder called YAPF19. It prices each route in track length, adding one tile’s worth for a gentle bend, six for a sharp turn and two for an upward slope, ten tiles’ worth for each tile of a station the train would run through without stopping, and more for a destination platform that’s too short for the train20. A red signal on the way costs most when it is the next one and less with each signal further on, and past the tenth signal a red one costs nothing21.
A vehicle follows a list of orders: stations, depots, waypoints, and conditional orders that skip ahead on its load, age, or need for servicing. Each station order sets how to load and unload22. A full-load order holds a train at the platform until every wagon is full, or, for “full load any cargo”, until the wagons of one cargo are23. Otherwise it stops loading when the cargo runs short and its timetabled stop is over, and a train running late cuts its stop short to catch up24. Timetables, added in 200725, set the stop and travel times for each order, and the wiki says they keep vehicles on shared orders from bunching up26.
#In Factorio’s lineage
In 2016 Friday Facts #126 added an “open ttd” inspired preview of the train to Factorio’s train screen, with a camera view and a map view27. OpenTTD’s train window has such a view: a small window on the map that follows the train and zooms with the mouse wheel28.
The signals invite comparison. OpenTTD’s block signal does the job of Factorio’s rail signal29. Factorio’s chain signal looks past the next block as an entry pre-signal does, but only at the exit on the train’s own route, and Friday Facts #81 says the chain signal resembles the “TTD pre signals” but works differently30. The TTDPatch manual describes pre-signals as a signal type its switches add to Transport Tycoon Deluxe31. OpenTTD’s changelog lists them in its 0.1.1 and 0.2 releases of March and April 200432, and an old wiki comparison says they, like path signals, work exactly as in TTDPatch33.
Both games’ train pathfinders weigh congestion less the farther ahead it lies34. They part on stations: Factorio makes passing a train stop as costly as 2,000 tiles of track, while OpenTTD charges 10 tiles of track for each tile of platform a train runs through35.
Mechanics
| Mechanic | Kind | How |
|---|---|---|
| Rail signalling | Any exit | entry, exit and combo pre-signals: an entry signal turns red when the block ahead has exit signals and none of them is green |
| Rail signalling | Path reservation | path signals, since 2009: a train reserves track to the next safe place to stop, and the signal turns green for it when the reservation succeeds |
| Pathfinding | trains price each route in track length plus bends, slopes, stations passed, and red signals ahead, which count less the farther off they are | |
| Rail transport | orders to stations, depots and waypoints; a train can wait for a full load, and a timetable sets its stops |
Every claim about this game, by concept (35)
References
- ^The OpenTTD wiki's manual calls it an open-source reimplementation of Transport Tycoon Deluxe in which the player runs a transport company from (typically) 1950, building rail, road, air and sea routes, and tries to be the highest-ranking company by 2050.WikiOpenTTD Wiki, "Manual"
How we know
OpenTTD is an open source reimplementation of the Microprose game Transport Tycoon Deluxe (TTD). … As president of a start-up transportation company in (typically) 1950, you have a choice of building rail, road, air and maritime transportation routes to build up your transportation empire. … Outsmart your competition by beating them to attractive passenger and commodities routes to become the game's highest-ranking transport company by the year 2050.
introduction · archived copy ↗ Checked against the evidence by its author, an AI model, 2026-10-26.
Checked by a second AI model, 2026-10-26.
- ^OpenTTD's About page says the game is based upon Chris Sawyer's Transport Tycoon Deluxe and attempts to mimic it as closely as possible while extending it with new features.PublisherOpenTTD, "About"
How we know
OpenTTD is an open source simulation game based upon the popular Microprose game "Transport Tycoon Deluxe", written by Chris Sawyer. It attempts to mimic the original game as closely as possible while extending it with new features.
About · archived copy ↗ Checked against the evidence by its author, an AI model, 2026-10-26.
Checked by a second AI model, 2026-10-26.
- ^Owen Rudge, who released OpenTTD, writes that Ludvig Strigeus ("Ludde") had been working on a clone of Transport Tycoon Deluxe since 30 June 2002, and first told him about it by ICQ message in March 2003: a working version of the game, written in C, though incomplete.Dev blogOpenTTD 20th-anniversary post
How we know
Exactly 21 years ago, I received an ICQ message (look it up, kids) out of the blue from a guy named Ludvig Strigeus (nicknamed Ludde).
paragraph 2 · archived copy ↗ (He’d been working on it since 30th June 2002, so I guess that is technically the birthday of what would become OpenTTD…)
paragraph 5 · archived copy ↗ It was a fully functional version of Transport Tycoon Deluxe, written in C.
paragraph 7 · archived copy ↗ Admittedly incomplete at that time, it was still quite remarkable.
paragraph 8 · archived copy ↗ Checked against the evidence by its author, an AI model, 2026-10-26.
Checked by a second AI model, 2026-10-26.
- ^Rudge recalls that in 2004 the Transport Tycoon community played Transport Tycoon Deluxe with Josef Drexler's TTDPatch, which had fundamental limits: the map size couldn't change, cargo types couldn't be added, and multiplayer was hard to improve. A clone in a high-level language, he writes, "got people very excited indeed". The OpenTTD wiki gives the same reason: the patch's way of changing the game made many features near impossible.Dev blogOpenTTD 20th-anniversary post · OpenTTD Wiki, "Manual"
How we know
To put things in context, in 2004, the Transport Tycoon community generally enjoyed playing the classic Transport Tycoon Deluxe with the excellent TTDPatch started by Josef Drexler.
paragraph 9 · archived copy ↗ TTDPatch was an amazing piece of software, but there were always some fundamental limitations - the map size couldn’t be changed, extra cargo types couldn’t be added, multiplayer was difficult to improve, and so on.
paragraph 11 · archived copy ↗ So having an almost fully-functional clone of TTD, written in a high-level programming language, suddenly appear out of nowhere got people very excited indeed.
paragraph 12 · archived copy ↗ The Patch's method of modifying the game made a lot of potential features near-impossible.
History of Transport Tycoon, "Enter OpenTTD" · archived copy ↗ Checked against the evidence by its author, an AI model, 2026-10-26.
Checked by a second AI model, 2026-10-26.
- ^Rudge released OpenTTD 0.1 as open source on 6 March 2004, naming it himself, with a SourceForge project and a forum on TT-Forums. His release-day post calls it Ludde's "C reconstruction of TTD", 95% complete, and tells players to copy Transport Tycoon Deluxe's Windows graphics and sound file to run it.Dev blogOpenTTD 20th-anniversary post · TT-Forums, OpenTTD 0.1 announcement (2004)
How we know
Coincidentally, this happened exactly a year after I’d first spoken to him, on the 6th March 2004.
paragraph 15 · archived copy ↗ I don’t think I thought very much about it, but decided that “OpenTTD” had a nice ring to it, so registered a SourceForge project, created an OpenTTD forum on TT-Forums, and set it loose.
paragraph 17 · archived copy ↗ OpenTTD 0.1 was released.
paragraph 18 · archived copy ↗ Basically, a guy called Ludde (original author of ScummVM) has spent quite some time working on a C reconstruction of TTD. He's now let me release the code. So here it is. … It is 95% complete. … To play the game, copy the Windows TTD graphics and sample.cat file to the compact directory, and run ttd.exe.
first post, orudge, 06 Mar 2004 19:26 · archived copy ↗ Checked against the evidence by its author, an AI model, 2026-10-26.
Checked by a second AI model, 2026-10-26.
- ^After the release, Rudge writes, a community of developers formed to fix bugs and add features, and support for TTDPatch's NewGRF format began to work four months later. The OpenTTD wiki says other developers joined Strigeus on the project.Dev blogOpenTTD 20th-anniversary post · OpenTTD Wiki, "Manual"revised
How we know
Things really got going after this, and a community started to form with enthusiastic developers fixing bugs, adding in new features, and smoothing off the rough edges.
paragraph 20 · archived copy ↗ Quick wins like higher vehicle limits came in very quickly, and support for TTDPatch’s NewGRF format started to be functional just four months later.
paragraph 23 · archived copy ↗ Response was positive, and other developers joined Ludde in the project.
History of Transport Tycoon, "Enter OpenTTD" · archived copy ↗ Checked against the evidence by its author, an AI model, 2026-10-26.
Revised after a check by a second AI model, 2026-10-26.
- ^Until the end of 2009 OpenTTD needed Transport Tycoon Deluxe's graphics and sounds; free replacements (OpenGFX, OpenSFX and OpenMSX) made it a stand-alone game, and the release that marked it was numbered 1.0.0, which the changelog dates 1 April 2010.WikiOpenTTD Wiki, "Manual" · OpenTTD 15.3 source
How we know
Up till this time graphics and sound files from Transport Tycoon Deluxe were required to play OpenTTD but now free and open alternatives, OpenGFX, OpenSFX and OpenMSX, are also available and supported. … These large community contributions made OpenTTD a fully stand-alone game for the first time and to mark this occasion this release was given the version number 1.0.0.
History of Transport Tycoon, "OpenTTD 1.0.0" · archived copy ↗ 4596### 1.0.0 (2010-04-01)changelog.md line 4596 at 14ec60f · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-26.
Checked by a second AI model, 2026-10-26.
- ^Rudge writes that the move to GitHub in 2018 and the release on Steam in 2021 re-energised development. Rock Paper Shotgun reported in February 2021 that OpenTTD would reach Steam on 1 April, with the 1.11 update, after it had reached the Microsoft Store; the changelog dates 1.11.0 to 1 April 2021.Dev blogOpenTTD 20th-anniversary post · RPS, OpenTTD on Steam (2021) · OpenTTD 15.3 sourcerevised
How we know
The move to GitHub in 2018 and the release of OpenTTD on Steam in 2021 have also re-energised development efforts, with thousands of people now enjoying playing the game regularly.
paragraph 28 · archived copy ↗ Now it should hopefully find a new audience, as it's coming to Steam for the first time on April 1st.
paragraph 1 · archived copy ↗ When it does come to Steam, it'll happen alongside the 1.11 update, which is currently in beta and improves MacOS support, among other tweaks. … It's already available via the Microsoft store now
paragraph 3 · archived copy ↗ 1583### 1.11.0 (2021-04-01)changelog.md line 1583 at 14ec60f · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-26.
Revised after a check by a second AI model, 2026-10-26.
- ^OpenTTD heads the table of Obligement's Amiga Games Award 2004, credited to Ludvig Strigeus and the OpenTTD team under the GPL 2.WebAmiga Games Award 2004
How we know
Ludvig Strigeus / OpenTTD Team
results table, row 1 · archived copy ↗ Checked against the evidence by its author, an AI model, 2026-10-26.
Checked by a second AI model, 2026-10-26.
- ^Lewis Denby put OpenTTD on PC Gamer's May 2011 list of "20 free PC games you must play", writing that dedicated fans had remade Transport Tycoon Deluxe from scratch, open-source and with "a heap of features", and that it "continues to captivate its followers".WebPC Gamer, "20 free PC games you must play" (2011)
How we know
That might be why some dedicated fans took it upon themselves to remake the latter from scratch, making it open-source and adding a heap of features along the way. To this day OpenTTD continues to captivate its followers.
20. OpenTTD · archived copy ↗ Checked against the evidence by its author, an AI model, 2026-10-26.
Checked by a second AI model, 2026-10-26.
- ^Graham Smith of Rock Paper Shotgun called OpenTTD "one of the seven wonders of PC gaming" in 2021, and wrote that for his money later successors such as Transport Fever and Rise of Industry never recapture its "sedate joy of tinkering with trains and lorries".WebRPS, OpenTTD on Steam (2021)
How we know
Open Transport Tycoon Deluxe is one of the seven wonders of PC gaming.
paragraph 1 · archived copy ↗ It's as great a game as it ever was, and for my money, modern successors such as Transport Fever and Rise Of Industry never recapture the same sedate joy of tinkering with trains and lorries in OpenTTD.
paragraph 4 · archived copy ↗ Checked against the evidence by its author, an AI model, 2026-10-26.
Checked by a second AI model, 2026-10-26.
- ^Ted John (IntelOrca) writes that he started OpenRCT2, an open-source C++ reimplementation of RollerCoaster Tycoon 2, in early 2014, "heavily inspired by OpenTTD".Dev blogIntelOrca's project page
How we know
An open-source clone of RollerCoaster Tycoon 2 (2002) written in C++. Heavily inspired by OpenTTD, I started the project early 2014
OpenRCT2 · archived copy ↗ Checked against the evidence by its author, an AI model, 2026-10-26.
Checked by a second AI model, 2026-10-26.
- ^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.CodeOpenTTD 15.3 sourcerevised
How we know
422 /* Signal state of reserved path signals is handled by the reserve/unreserve process. */ 423 if (IsPbsSignal(sig) && (GetRailReservationTrackBits(tile) & TrackToTrackBits(track)) != TRACK_BIT_NONE) continue; ⋮ 425 /* determine whether the new state is red */ 426 if (flags.Test(SigFlag::Train)) { 427 /* train in the segment */ 428 newstate = SIGNAL_STATE_RED;
src/signal.cpp lines 422–428 at 14ec60f · upstream ↗ 194static bool IsTrainAndNotInDepot(const Vehicle *v) ⋮ 196 return v->type == VEH_TRAIN && Train::From(v)->track != TRACK_BIT_DEPOT;
src/signal.cpp lines 194–197 at 14ec60f · upstream ↗ 279 if (GetTileOwner(tile) != owner) continue; // do not propagate signals on others' tiles (remove for tracksharing)src/signal.cpp line 279 at 14ec60f · upstream ↗ 302 /* If no train detected yet, and there is not no train -> there is a train -> set the flag */ 303 if (!flags.Test(SigFlag::Train) && EnsureNoTrainOnTrackBits(tile, tracks).Failed()) flags. Set(SigFlag::Train); 304 } else { 305 if (tracks_masked == TRACK_BIT_NONE) continue; // no incidating track 306 if (!flags.Test(SigFlag::Train) && HasVehicleOnTile(tile, IsTrainAndNotInDepot)) flags.Set(SigFlag::Train);
src/signal.cpp lines 302–306 at 14ec60f · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-26.
Revised after a check by a second AI model, 2026-10-26.
- ^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.CodeOpenTTD 15.3 source
How we know
432 } else { 433 /* is it a bidir combo? - then do not count its other signal direction as exit */ 434 if (sig == SIGTYPE_COMBO && HasSignalOnTrackdir(tile, ReverseTrackdir(trackdir))) { 435 /* at least one more exit */ 436 if (flags.Test(SigFlag::MultiExit) && 437 /* no green exit */ 438 (!flags.Test(SigFlag::Green) || 439 /* only one green exit, and it is this one - so all other exits are red */ 440 (!flags.Test(SigFlag::MultiGreen) && GetSignalStateByTrackdir(tile, ReverseTrackdir(trackdir)) == SIGNAL_STATE_GREEN))) { 441 newstate = SIGNAL_STATE_RED; ⋮ 443 } else { // entry, at least one exit, no green exit 444 if (IsPresignalEntry(tile, TrackdirToTrack(trackdir)) && flags.Test(SigFlag::Exit) && !flags.Test(SigFlag::Green)) newstate = SIGNAL_STATE_RED;
src/signal.cpp lines 432–446 at 14ec60f · upstream ↗ 330 /* if it is a presignal EXIT in OUR direction and we haven't found 2 green exits yes, do special check */ 331 if (!flags.Test(SigFlag::MultiGreen) && IsPresignalExit(tile, track) && HasSignalOnTrackdir(tile, trackdir)) { // found presignal exit 332 if (flags.Test(SigFlag::Exit)) flags.Set(SigFlag::MultiExit); // found two (or more) exits 333 flags.Set(SigFlag::Exit); // found at least one exit - allow for compiler optimizations 334 if (GetSignalStateByTrackdir(tile, trackdir) == SIGNAL_STATE_GREEN) { // found green presignal exit 335 if (flags.Test(SigFlag::Green)) flags.Set(SigFlag::MultiGreen); 336 flags.Set(SigFlag::Green);
src/signal.cpp lines 329–338 at 14ec60f · upstream ↗ 448 /* only when the state changes */ 449 if (newstate != GetSignalStateByTrackdir(tile, trackdir)) { 450 if (IsPresignalExit(tile, TrackdirToTrack(trackdir))) { 451 /* for pre-signal exits, add block to the global set */ 452 DiagDirection exitdir = TrackdirToExitdir(ReverseTrackdir(trackdir)); 453 _globset.Add(tile, exitdir); // do not check for full global set, first update all signals
src/signal.cpp lines 448–454 at 14ec60f · upstream ↗ 307inline bool IsPresignalEntry(Tile t, Track track) ⋮ 309 return GetSignalType(t, track) == SIGTYPE_ENTRY || GetSignalType(t, track) == SIGTYPE_COMBO; ⋮ 312inline bool IsPresignalExit(Tile t, Track track) ⋮ 314 return GetSignalType(t, track) == SIGTYPE_EXIT || GetSignalType(t, track) == SIGTYPE_COMBO;
src/rail_map.h lines 307–315 at 14ec60f · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-26.
Checked by a second AI model, 2026-10-26.
- ^OpenTTD's changelog lists "path based signalling" in 0.7.0-beta1 (16 February 2009); 0.7.0 came out on 1 April 2009.DeveloperOpenTTD 15.3 source
How we know
5411### 0.7.0-beta1 (2009-02-16)changelog.md line 5411 at 14ec60f · upstream ↗ 5455- Feature: Path based signalling (r13926-13967)changelog.md line 5455 at 14ec60f · upstream ↗ 5296### 0.7.0 (2009-04-01)changelog.md line 5296 at 14ec60f · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-26.
Checked by a second AI model, 2026-10-26.
- ^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.CodeOpenTTD 15.3 source
How we know
422 /* Signal state of reserved path signals is handled by the reserve/unreserve process. */ 423 if (IsPbsSignal(sig) && (GetRailReservationTrackBits(tile) & TrackToTrackBits(track)) != TRACK_BIT_NONE) continue; ⋮ 425 /* determine whether the new state is red */ 426 if (flags.Test(SigFlag::Train)) { 427 /* train in the segment */ 428 newstate = SIGNAL_STATE_RED; 429 } else if (IsPbsSignal(sig) && flags.Any({SigFlag::Split, SigFlag::MultiEnter})) { 430 /* Turn path signals red if the segment has a junction or more than one way in. */ 431 newstate = SIGNAL_STATE_RED;
src/signal.cpp lines 422–431 at 14ec60f · upstream ↗ 381bool IsSafeWaitingPosition(const Train *v, TileIndex tile, Trackdir trackdir, bool include_line_end, bool forbid_90deg) ⋮ 383 if (IsRailDepotTile(tile)) return true; ⋮ 385 if (IsTileType(tile, MP_RAILWAY)) { 386 /* For non-pbs signals, stop on the signal tile. */ 387 if (HasSignalOnTrackdir(tile, trackdir) && !IsPbsSignal(GetSignalType(tile, TrackdirToTrack(trackdir)))) return true; ⋮ 390 /* Check next tile. For performance reasons, we check for 90 degree turns ourself. */ 391 CFollowTrackRail ft(v, GetAllCompatibleRailTypes(v->railtypes)); ⋮ 393 /* End of track? */ 394 if (!ft.Follow(tile, trackdir)) { 395 /* Last tile of a terminus station is a safe position. */ 396 if (include_line_end) return true; ⋮ 399 /* Check for reachable tracks. */ ⋮
src/pbs.cpp lines 381–417 at 14ec60f · upstream ↗ 2747 if (track != INVALID_TRACK && HasPbsSignalOnTrackdir(tile, TrackEnterdirToTrackdir(track, enterdir))) { 2748 do_track_reservation = true; 2749 changed_signal = true; 2750 SetSignalStateByTrackdir(tile, TrackEnterdirToTrackdir(track, enterdir), SIGNAL_STATE_GREEN);
src/train_cmd.cpp lines 2747–2750 at 14ec60f · upstream ↗ 2759 if (do_track_reservation) { 2760 res_dest = ExtendTrainReservation(v, &tracks, &dest_enterdir); 2761 if (res_dest.tile == INVALID_TILE) { 2762 /* Reservation failed? */ 2763 if (mark_stuck) MarkTrainAsStuck(v); 2764 if (changed_signal) SetSignalStateByTrackdir(tile, TrackEnterdirToTrackdir(best_track, enterdir), SIGNAL_STATE_RED);
src/train_cmd.cpp lines 2759–2764 at 14ec60f · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-26.
Checked by a second AI model, 2026-10-26.
- ^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.CodeOpenTTD 15.3 source
How we know
176 int cost = 0; 177 /* if there is one-way signal in the opposite direction, then it is not our way */ 178 if (IsTileType(tile, MP_RAILWAY)) { 179 bool has_signal_against = HasSignalOnTrackdir(tile, ReverseTrackdir(trackdir)); 180 bool has_signal_along = HasSignalOnTrackdir(tile, trackdir); 181 if (has_signal_against && !has_signal_along && IsOnewaySignal(tile, TrackdirToTrack(trackdir))) { 182 /* one-way signal in opposite direction */ 183 n.segment->end_segment_reason.Set(EndSegmentReason::DeadEnd);
src/pathfinder/yapf/yapf_costrail.hpp lines 176–183 at 14ec60f · upstream ↗ 237 if (has_signal_against && IsPbsSignal(GetSignalType(tile, TrackdirToTrack(trackdir)))) { 238 cost += n.num_signals_passed < Yapf().PfGetSettings().rail_look_ahead_max_signals ? Yapf().PfGetSettings().rail_pbs_signal_back_penalty : 0;
src/pathfinder/yapf/yapf_costrail.hpp lines 237–239 at 14ec60f · upstream ↗ 318inline bool IsOnewaySignal(Tile t, Track track) ⋮ 320 return GetSignalType(t, track) != SIGTYPE_PBS;
src/rail_map.h lines 318–321 at 14ec60f · upstream ↗ 261var = pf.yapf.rail_pbs_signal_back_penalty ⋮ 264def = 15 * YAPF_TILE_LENGTH
src/table/settings/pathfinding_settings.ini lines 261–264 at 14ec60f · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-26.
Checked by a second AI model, 2026-10-26.
- ^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.CodeOpenTTD 15.3 source
How we know
15/** Variant of the signal, i.e. how does the signal look? */ 16enum SignalVariant : uint8_t { 17 SIG_ELECTRIC = 0, ///< Light signal 18 SIG_SEMAPHORE = 1, ///< Old-fashioned semaphore signal
src/signal_type.h lines 15–19 at 14ec60f · upstream ↗ 22/** Type of signal, i.e. how does the signal behave? */ 23enum SignalType : uint8_t { 24 SIGTYPE_BLOCK = 0, ///< block signal 25 SIGTYPE_ENTRY = 1, ///< presignal block entry 26 SIGTYPE_EXIT = 2, ///< presignal block exit 27 SIGTYPE_COMBO = 3, ///< presignal inter-block 28 SIGTYPE_PBS = 4, ///< normal pbs signal 29 SIGTYPE_PBS_ONEWAY = 5, ///< no-entry signal
src/signal_type.h lines 22–29 at 14ec60f · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-26.
Checked by a second AI model, 2026-10-26.
- ^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.CodeOpenTTD 15.3 source
How we know
2505 return YapfTrainChooseTrack(v, tile, enterdir, tracks, path_found, do_track_reservation, dest, final_dest);src/train_cmd.cpp line 2505 at 14ec60f · upstream ↗ 609- Remove: NPF and pathfinder change settings (#12209)changelog.md line 609 at 14ec60f · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-26.
Checked by a second AI model, 2026-10-26.
- ^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.CodeOpenTTD 15.3 sourcerevised
How we know
16static const int YAPF_TILE_LENGTH = 100; ⋮ 18/** Length (penalty) of a corner with YAPF */ 19static const int YAPF_TILE_CORNER_LENGTH = 71;
src/pathfinder/pathfinder_type.h lines 16–19 at 14ec60f · upstream ↗ 124 inline int OneTileCost(TileIndex &tile, Trackdir trackdir) ⋮ 126 int cost = 0; 127 /* set base cost */ 128 if (IsDiagonalTrackdir(trackdir)) { 129 cost += YAPF_TILE_LENGTH; 130 switch (GetTileType(tile)) { 131 case MP_ROAD: 132 /* Increase the cost for level crossings */ 133 if (IsLevelCrossing(tile)) { 134 cost += Yapf().PfGetSettings().rail_crossing_penalty; ⋮ 136 break; ⋮ 138 default: ⋮
src/pathfinder/yapf/yapf_costrail.hpp lines 124–146 at 14ec60f · upstream ↗ 102 if (TrackFollower::Allow90degTurns() 103 && HasTrackdir(TrackdirCrossesTrackdirs(td1), td2)) { 104 /* 90-deg curve penalty */ 105 cost += Yapf().PfGetSettings().rail_curve90_penalty; 106 } else if (td2 != NextTrackdir(td1)) { 107 /* 45-deg curve penalty */ 108 cost += Yapf().PfGetSettings().rail_curve45_penalty;
src/pathfinder/yapf/yapf_costrail.hpp lines 102–108 at 14ec60f · upstream ↗ 450 uint platform_length = follower->tiles_skipped + 1; 451 /* We don't know yet if the station is our target or not. Act like 452 * if it is pass-through station (not our destination). */ 453 segment_cost += Yapf().PfGetSettings().rail_station_penalty * platform_length;
src/pathfinder/yapf/yapf_costrail.hpp lines 450–453 at 14ec60f · upstream ↗ 604 /* Reduce the extra cost caused by passing-station penalty (each station receives it in the segment cost). */ 605 extra_cost -= Yapf().PfGetSettings().rail_station_penalty * platform_length; 606 /* Add penalty for the inappropriate platform length. */ 607 extra_cost += PlatformLengthPenalty(platform_length);
src/pathfinder/yapf/yapf_costrail.hpp lines 604–607 at 14ec60f · upstream ↗ 153var = pf.yapf.rail_station_penalty ⋮ 156def = 10 * YAPF_TILE_LENGTH ⋮ 162var = pf.yapf.rail_slope_penalty ⋮ 165def = 2 * YAPF_TILE_LENGTH ⋮ 171var = pf.yapf.rail_curve45_penalty ⋮ 174def = 1 * YAPF_TILE_LENGTH ⋮ 180var = pf.yapf.rail_curve90_penalty ⋮ 183def = 6 * YAPF_TILE_LENGTH ⋮ 189var = pf.yapf.rail_depot_reverse_penalty ⋮ 192def = 50 * YAPF_TILE_LENGTH ⋮ 198var = pf.yapf.rail_crossing_penalty ⋮ 201def = 3 * YAPF_TILE_LENGTH
src/table/settings/pathfinding_settings.ini lines 153–201 at 14ec60f · upstream ↗ 279var = pf.yapf.rail_longer_platform_penalty ⋮ 282def = 8 * YAPF_TILE_LENGTH ⋮ 288var = pf.yapf.rail_longer_platform_per_tile_penalty ⋮ 291def = 0 * YAPF_TILE_LENGTH ⋮ 297var = pf.yapf.rail_shorter_platform_penalty ⋮ 300def = 40 * YAPF_TILE_LENGTH
src/table/settings/pathfinding_settings.ini lines 279–300 at 14ec60f · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-26.
Revised after a check by a second AI model, 2026-10-26.
- ^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.CodeOpenTTD 15.3 sourcerevised
How we know
62 int p0 = Yapf().PfGetSettings().rail_look_ahead_signal_p0; 63 int p1 = Yapf().PfGetSettings().rail_look_ahead_signal_p1; 64 int p2 = Yapf().PfGetSettings().rail_look_ahead_signal_p2; 65 this->sig_look_ahead_costs.clear(); 66 this->sig_look_ahead_costs.reserve(Yapf().PfGetSettings().rail_look_ahead_max_signals); 67 for (uint i = 0; i < Yapf().PfGetSettings().rail_look_ahead_max_signals; i++) { 68 this->sig_look_ahead_costs.push_back(p0 + i * (p1 + i * p2));
src/pathfinder/yapf/yapf_costrail.hpp lines 62–69 at 14ec60f · upstream ↗ 192 int look_ahead_cost = (n.num_signals_passed < this->sig_look_ahead_costs.size()) ? this->sig_look_ahead_costs[n.num_signals_passed] : 0; 193 if (sig_state != SIGNAL_STATE_RED) { 194 /* green signal */ 195 n.flags_u.flags_s.last_signal_was_red = false; 196 /* negative look-ahead red-signal penalties would cause problems later, so use them as positive penalties for green signal */ 197 if (look_ahead_cost < 0) { 198 /* add its negation to the cost */ 199 cost -= look_ahead_cost; ⋮ 201 } else { 202 /* we have a red signal in our direction 203 * was it first signal which is two-way? */ 204 if (!IsPbsSignal(sig_type) && Yapf().TreatFirstRedTwoWaySignalAsEOL() && n.flags_u.flags_s.choice_seen && has_signal_against && n.num_signals_passed == 0) { ⋮
src/pathfinder/yapf/yapf_costrail.hpp lines 192–229 at 14ec60f · upstream ↗ 111var = pf.yapf.rail_firstred_twoway_eol ⋮ 113def = true ⋮ 117var = pf.yapf.rail_firstred_penalty ⋮ 120def = 10 * YAPF_TILE_LENGTH ⋮ 126var = pf.yapf.rail_firstred_exit_penalty ⋮ 129def = 100 * YAPF_TILE_LENGTH
src/table/settings/pathfinding_settings.ini lines 111–129 at 14ec60f · upstream ↗ 207var = pf.yapf.rail_look_ahead_max_signals ⋮ 210def = 10 ⋮ 216var = pf.yapf.rail_look_ahead_signal_p0 ⋮ 219def = 500 ⋮ 225var = pf.yapf.rail_look_ahead_signal_p1 ⋮ 228def = -100 ⋮ 234var = pf.yapf.rail_look_ahead_signal_p2 ⋮ 237def = 5
src/table/settings/pathfinding_settings.ini lines 207–237 at 14ec60f · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-26.
Revised after a check by a second AI model, 2026-10-26.
- ^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).CodeOpenTTD 15.3 sourcerevised
How we know
50enum OrderType : uint8_t { 51 OT_BEGIN = 0, 52 OT_NOTHING = 0, 53 OT_GOTO_STATION = 1, 54 OT_GOTO_DEPOT = 2, 55 OT_LOADING = 3, 56 OT_LEAVESTATION = 4, 57 OT_DUMMY = 5, 58 OT_GOTO_WAYPOINT = 6, 59 OT_CONDITIONAL = 7, 60 OT_IMPLICIT = 8, 61 OT_END
src/order_type.h lines 50–61 at 14ec60f · upstream ↗ 67enum class OrderUnloadType : uint8_t { 68 UnloadIfPossible = 0, ///< Unload all cargo that the station accepts. 69 Unload = 1, ///< Force unloading all cargo onto the platform, possibly not getting paid. 70 Transfer = 2, ///< Transfer all cargo onto the platform. 71 NoUnload = 4, ///< Totally no unloading will be done. ⋮ 74/** 75 * Loading order types. ⋮ 77enum class OrderLoadType : uint8_t { 78 LoadIfPossible = 0, ///< Load as long as there is cargo that fits in the train. 79 FullLoad = 2, ///< Full load all cargoes of the consist. 80 FullLoadAny = 3, ///< Full load a single cargo of the consist. 81 NoLoad = 4, ///< Do not load anything.
src/order_type.h lines 67–81 at 14ec60f · upstream ↗ 128enum class OrderConditionVariable : uint8_t { 129 LoadPercentage = 0, ///< Skip based on the amount of load 130 Reliability = 1, ///< Skip based on the reliability 131 MaxSpeed = 2, ///< Skip based on the maximum speed 132 Age = 3, ///< Skip based on the age 133 RequiresService = 4, ///< Skip when the vehicle requires service 134 Unconditionally = 5, ///< Always skip 135 RemainingLifetime = 6, ///< Skip based on the remaining lifetime 136 MaxReliability = 7, ///< Skip based on the maximum reliability
src/order_type.h lines 128–136 at 14ec60f · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-26.
Revised after a check by a second AI model, 2026-10-26.
- ^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.CodeOpenTTD 15.3 source
How we know
1824 if (v->cargo.StoredCount() >= v->cargo_cap) { 1825 SetBit(cargo_full, v->cargo_type); 1826 } else { 1827 SetBit(cargo_not_full, v->cargo_type);
src/economy.cpp lines 1824–1828 at 14ec60f · upstream ↗ 1866 if (front->current_order.IsFullLoadOrder()) { 1867 if (front->current_order.GetLoadType() == OrderLoadType::FullLoadAny) { 1868 /* if the aircraft carries passengers and is NOT full, then 1869 * continue loading, no matter how much mail is in */ 1870 if ((front->type == VEH_AIRCRAFT && IsCargoInClass(front->cargo_type, CargoClass::Passengers) && front->cargo_cap > front->cargo.StoredCount()) || 1871 (cargo_not_full != 0 && (cargo_full & ~cargo_not_full) == 0)) { // There are still non-full cargoes 1872 finished_loading = false; ⋮ 1874 } else if (cargo_not_full != 0) { 1875 finished_loading = false;
src/economy.cpp lines 1866–1876 at 14ec60f · upstream ↗ 77enum class OrderLoadType : uint8_t { 78 LoadIfPossible = 0, ///< Load as long as there is cargo that fits in the train. 79 FullLoad = 2, ///< Full load all cargoes of the consist. 80 FullLoadAny = 3, ///< Full load a single cargo of the consist. 81 NoLoad = 4, ///< Do not load anything.
src/order_type.h lines 77–81 at 14ec60f · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-26.
Checked by a second AI model, 2026-10-26.
- ^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.CodeOpenTTD 15.3 source
How we know
1855 /* We loaded less cargo than possible for all cargo types and it's not full 1856 * load and we're not supposed to wait any longer: stop loading. */ 1857 if (!anything_unloaded && full_load_amount == 0 && reservation_left == 0 && !front->current_order.IsFullLoadOrder() && 1858 front->current_order_time >= std::max(front->current_order.GetTimetabledWait() - front->lateness_counter, 0)) { 1859 front->vehicle_flags.Set(VehicleFlag::StopLoading);
src/economy.cpp lines 1855–1859 at 14ec60f · upstream ↗ 1789 if (loaded == cap_left) { 1790 SetBit(full_load_amount, v->cargo_type); 1791 } else { 1792 ClrBit(full_load_amount, v->cargo_type);
src/economy.cpp lines 1789–1793 at 14ec60f · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-26.
Checked by a second AI model, 2026-10-26.
- ^OpenTTD's changelog lists "a form of timetabling for vehicles" in 0.6.0-beta1 (18 November 2007).WikiOpenTTD 15.3 source · OpenTTD Wiki, "Timetable"
How we know
6040### 0.6.0-beta1 (2007-11-18)changelog.md line 6040 at 14ec60f · upstream ↗ 6079- Feature: Introduce a form of timetabling for vehicles (r10236)changelog.md line 6079 at 14ec60f · upstream ↗ Timetables were introduced in OpenTTD 0.6.0-beta2 (r10236).
Using timetables, first paragraph · archived copy ↗ Checked against the evidence by its author, an AI model, 2026-10-26.
Checked by a second AI model, 2026-10-26.
- ^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.WikiOpenTTD Wiki, "Timetable"
How we know
You can choose how much time a vehicle should spend in a station
Using timetables, first paragraph · archived copy ↗ 'Autofill' will measure how long your vehicle takes to travel each leg of the journey, and how long it spends in stations. … Timetables are useful for preventing vehicles from "bunching up".
Using timetables · archived copy ↗ Sets late counter to zero.
Timetable window, "Reset late counter" · archived copy ↗ Checked against the evidence by its author, an AI model, 2026-10-26.
Checked by a second AI model, 2026-10-26.
- ^Friday Facts #126 (2016) says Factorio's single train screen gained an "open ttd" inspired preview of the train, with a camera view and a map view.Dev blogFFF-126
How we know
Namely adding the "open ttd" inspired preview of the train. There is a switchable "camera view" and "map view".
Updated train screen · archived copy ↗ Checked against the evidence by its author, an AI model, 2026-10-26.
Checked by a second AI model, 2026-10-26.
- ^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.CodeOpenTTD 15.3 sourcerevised
How we know
2837 NWidget(NWID_VIEWPORT, INVALID_COLOUR, WID_VV_VIEWPORT), SetMinimalSize(226, 84), SetResize(1, 1),src/vehicle_gui.cpp line 2837 at 14ec60f · upstream ↗ 3039 this->GetWidget<NWidgetViewport>(WID_VV_VIEWPORT)->InitializeViewport(this, static_cast<VehicleID>(this->window_number), ScaleZoomGUI(_vehicle_view_zoom_levels[v->type]));src/vehicle_gui.cpp line 3039 at 14ec60f · upstream ↗ 3338 void OnMouseWheel(int wheel, WidgetID widget) override ⋮ 3340 if (widget != WID_VV_VIEWPORT) return; 3341 if (_settings_client.gui.scrollwheel_scrolling != SWS_OFF) { 3342 DoZoomInOutWindow(wheel < 0 ? ZOOM_IN : ZOOM_OUT, this);
src/vehicle_gui.cpp lines 3338–3343 at 14ec60f · upstream ↗ 3249 case WID_VV_LOCATION: // center main view 3250 if (_ctrl_pressed) { 3251 ShowExtraViewportWindow(TileVirtXY(v->x_pos, v->y_pos)); 3252 } else { 3253 const Window *mainwindow = GetMainWindow(); 3254 if (click_count > 1) { 3255 /* main window 'follows' vehicle */ 3256 mainwindow->viewport->follow_vehicle = v->index; 3257 } else { 3258 if (mainwindow->viewport->follow_vehicle == v->index) mainwindow->viewport->follow_vehicle = VehicleID::Invalid(); 3259 ScrollMainWindowTo(v->x_pos, v->y_pos, v->z_pos); ⋮ 3262 break;
src/vehicle_gui.cpp lines 3249–3262 at 14ec60f · upstream ↗ 4441STR_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 locationsrc/lang/english.txt line 4441 at 14ec60f · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-26.
Revised after a check by a second AI model, 2026-10-26.
- ^OpenTTD's block signal does what Factorio's rail signal does: it keeps a train out of a block another train is in.Dev blogOpenTTD 15.3 source · Friday Facts #81
How we know
426 if (flags.Test(SigFlag::Train)) { 427 /* train in the segment */ 428 newstate = SIGNAL_STATE_RED;
src/signal.cpp lines 426–428 at 14ec60f · upstream ↗ While normal signal prevents train from entering the occupied block
The definition · archived copy ↗ Checked against the evidence by its author, an AI model, 2026-10-26.
Checked by a second AI model, 2026-10-26.
- ^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.Dev blogOpenTTD 15.3 source · Friday Facts #81
How we know
443 } else { // entry, at least one exit, no green exit 444 if (IsPresignalEntry(tile, TrackdirToTrack(trackdir)) && flags.Test(SigFlag::Exit) && !flags.Test(SigFlag::Green)) newstate = SIGNAL_STATE_RED;
src/signal.cpp lines 443–444 at 14ec60f · upstream ↗ chain 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.
The definition · archived copy ↗ The 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.
The chain signal in detail · archived copy ↗ Checked against the evidence by its author, an AI model, 2026-10-26.
Checked by a second AI model, 2026-10-26.
- ^Friday Facts #81 (2015) says Factorio's chain signals have some similarities with "TTD pre signals" but work differently, so they were given a different name. The TTDPatch manual describes pre-signals as a new type of signal that its switches introduce to Transport Tycoon Deluxe; who had them first isn't shown here.Dev blogFriday Facts #81 · TTDPatch manual, Pre-signals
How we know
The 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.
The chain signal in detail · archived copy ↗ These switches introduce a new type of signal to TTD, the "pre-signal."
3.12 Pre-signals · archived copy ↗ Checked against the evidence by its author, an AI model, 2026-10-26.
Checked by a second AI model, 2026-10-26.
- ^OpenTTD's changelog lists "preliminary presignal support" in 0.1.1 (14 March 2004) and pre-signals, set by Ctrl-clicking an existing signal, in 0.2 (3 April 2004).DeveloperOpenTTD 15.3 source
How we know
7825### 0.1.1 (2004-03-14) ⋮ 7827- Feature: Preliminary presignal support
changelog.md lines 7825–7827 at 14ec60f · upstream ↗ 7693### 0.2 (2004-04-03) ⋮ 7695- Feature: Autoscroll (only works to left/right) 7696- Feature: Train checkpoints, instead of TTDPatch's nonstop handling 7697- Feature: TTDPatch compatible nonstop handling 7698- Feature: Refresh rate configuration setting 7699- Feature: Town directory sorting options 7700- Feature: Pre-signals (ctrl-click on existing signals to change signal type)
changelog.md lines 7693–7700 at 14ec60f · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-26.
Checked by a second AI model, 2026-10-26.
- ^An archived OpenTTD wiki page comparing OpenTTD with TTDPatch lists pre-signals and path-based signals among the features that "work exactly the same as in TTDPatch". The page is marked out of date and "only really of value as a historical document now".WikiOpenTTD Wiki, "Comparison of OpenTTD and TTDPatch features"
How we know
These features work exactly the same as in TTDPatch. … Presignals … Path Based Signals (PBS)
Features shared by TTDPatch and OpenTTD · archived copy ↗ This is only really of value as a historical document now.
banner · archived copy ↗ Checked against the evidence by its author, an AI model, 2026-10-26.
Checked by a second AI model, 2026-10-26.
- ^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.WikiFactorio Wiki: Train path finder · OpenTTD 15.3 sourcerevised
How we know
When 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.
Path finding penalties · archived copy ↗ 214 /* look-ahead signal penalty */ 215 if (!IsPbsSignal(sig_type) && look_ahead_cost > 0) { 216 /* add the look ahead penalty only if it is positive */ 217 cost += look_ahead_cost;
src/pathfinder/yapf/yapf_costrail.hpp lines 214–218 at 14ec60f · upstream ↗ 67 for (uint i = 0; i < Yapf().PfGetSettings().rail_look_ahead_max_signals; i++) { 68 this->sig_look_ahead_costs.push_back(p0 + i * (p1 + i * p2));
src/pathfinder/yapf/yapf_costrail.hpp lines 67–68 at 14ec60f · upstream ↗ 207var = pf.yapf.rail_look_ahead_max_signals ⋮ 210def = 10 ⋮ 216var = pf.yapf.rail_look_ahead_signal_p0 ⋮ 219def = 500 ⋮ 225var = pf.yapf.rail_look_ahead_signal_p1 ⋮ 228def = -100 ⋮ 234var = pf.yapf.rail_look_ahead_signal_p2 ⋮ 237def = 5
src/table/settings/pathfinding_settings.ini lines 207–237 at 14ec60f · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-26.
Revised after a check by a second AI model, 2026-10-26.
- ^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.CodeFactorio Wiki: Train path finder · factorio-data 2.1.23 · OpenTTD 15.3 sourcerevised
How we know
Base cost for a block/segment is the length of the segment (linear grid length along the center of the rail). … When the path includes a train stop -> Add a penalty of 2000.
Path finding penalties · archived copy ↗ Train stop at train's starting segment exit is no longer counted into penalty.
History, 0.18.1 · archived copy ↗ 320 train_stop_penalty = 2000,core/prototypes/utility-constants.lua line 320 at 39dbd0f · upstream ↗ 450 uint platform_length = follower->tiles_skipped + 1; 451 /* We don't know yet if the station is our target or not. Act like 452 * if it is pass-through station (not our destination). */ 453 segment_cost += Yapf().PfGetSettings().rail_station_penalty * platform_length;
src/pathfinder/yapf/yapf_costrail.hpp lines 450–453 at 14ec60f · upstream ↗ 153var = pf.yapf.rail_station_penalty ⋮ 156def = 10 * YAPF_TILE_LENGTH
src/table/settings/pathfinding_settings.ini lines 153–156 at 14ec60f · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-26.
Revised after a check by a second AI model, 2026-10-26.
Sources
Code
- OpenTTD contributors. 2026. “OpenTTD source code, release 15.3.” Commit 14ec60f. OpenTTD/OpenTTD @ 14ec60f · licence, all uses
- Software, Wube. 2026. “factorio-data: Factorio's Lua prototype definitions, version 2.1.23.” Commit 39dbd0f. wube/factorio-data @ 39dbd0f · licence, all uses
Primary
- OpenTTD contributors. n.d. “OpenTTD — About.” OpenTTD. original · archived · licence, all uses
- Rudge, Owen. 2024. “Happy 20th birthday OpenTTD!” OpenTTD. original · archived · licence, all uses
- Rudge, Owen. 2004. “I proudly present to you OpenTTD!” Transport Tycoon Forums. original · archived · licence, all uses
- John, Ted. n.d. “Ted John (IntelOrca) — projects.” original · archived · licence, all uses
- Kozelek, Tomáš. 2016. “Friday Facts #126 - Steam Status II and Trains Stuff.” Wube Software (factorio.com). original · archived · licence, all uses
- Software, Wube. 2015. “Friday Facts #81 - Chain signals.” Factorio blog. Wube Software. original · archived · licence, all uses
- TTDPatch team. n.d. “TTDPatch Manual: Pre-signals.” ttdpatch.net. original · archived · licence, all uses
Secondary
- OpenTTD Wiki contributors. n.d. “OpenTTD Wiki — Manual.” OpenTTD. original · archived · licence, all uses
- Smith, Graham. 2021. “Open Transport Tycoon Deluxe, one of the wonders of PC gaming, is coming to Steam.” Rock Paper Shotgun. original · archived · licence, all uses
- Obligement and Amiga Games Hit Parade. 2004. “Amiga Games Award 2004.” Obligement. original · archived · licence, all uses
- Denby, Lewis. 2011. “20 free PC games you must play.” PC Gamer. original · archived · licence, all uses
- OpenTTD Wiki contributors. n.d. “OpenTTD Wiki — Timetable.” OpenTTD. original · archived · licence, all uses
- OpenTTD Wiki contributors. n.d. “OpenTTD Wiki — Comparison of OpenTTD and TTDPatch features.” OpenTTD. original · archived · licence, all uses
- Official Factorio Wiki contributors. n.d. “Train path finder.” Official Factorio Wiki. original · archived · licence, all uses