Constants
Constants
| Member | Returns | Summary |
|---|---|---|
api.modifyConstants(overrides) | void | Deep-merge overrides into the game rules object RULES and push a snapshot to the sim host worker when one is running. Full text below the table. |
api.modifyColors(overrides) | void | Deep-merge overrides into the map visual constants object GAME_COLORS. The top-level object keeps its identity; nested objects on an override path are replaced by merged copies. |
api.modifyPathfindingRules(overrides) | void | Merge numeric overrides into the commute pathfinding rules PATHFINDING_RULES in place. |
api.modifyConstants(overrides): Deep-merge overrides into the game rules object RULES and push a snapshot to the sim host worker when one is running. The top-level RULES object keeps its identity; every nested object on an override path is replaced by a merged copy, so a nested reference taken earlier (const cc = api.utils.getConstants().CONSTRUCTION_COSTS) goes stale.
modifyConstants#
api.modifyConstants(overrides: Partial<typeof RULES>): voidDeep-merge overrides into the game rules object RULES and push a snapshot to the sim host worker when one is running. The top-level RULES object keeps its identity; every nested object on an override path is replaced by a merged copy, so a nested reference taken earlier (const cc = api.utils.getConstants().CONSTRUCTION_COSTS) goes stale.
Nested objects merge key by key. Arrays and scalars replace the existing value. Unknown keys are added without a warning. STARTING_MONEY also sets the store's current money and logs [Modding API] Auto-synced STARTING_MONEY to store (development builds only). DEFAULT_TICKET_COST also sets the store's current ticket price and, when the first fare group is flat, that group's fare, and logs [Modding API] Auto-synced DEFAULT_TICKET_COST to store (development builds only). Logs [Modding API] Modifying game constants: (development builds only) with the top-level keys.
Parameter overrides (Partial<typeof RULES>): partial RULES tree; must be an object.
Errors:
overridesmissing or not an object: logs[Modding API] Invalid overrides - must be an object, changes nothing.- exception: logs
[Modding API] Error modifying constants:.
Hooks fired: onTicketPriceChanged (only when DEFAULT_TICKET_COST is present and differs from the current price).
Side effects: top-level RULES keys reassigned; constants snapshot sent to the sim host worker when one is running; store money, transitCost and fareGroups written for the two synced keys.
Changed: WALKING_SPEED_STRAIGHT_LINE and WALKING_SPEED_ACCURATE_PATH overrides reach the commute worker with the next commute simulation (PR #1090).
api.modifyConstants({ CONSTRUCTION_COSTS: { TUNNEL: { SINGLE_MULTIPLIER: 0.5 } } });
// other CONSTRUCTION_COSTS.TUNNEL keys keep their valuesmodifyColors#
api.modifyColors(overrides: Partial<typeof GAME_COLORS>): voidDeep-merge overrides into the map visual constants object GAME_COLORS. The top-level object keeps its identity; nested objects on an override path are replaced by merged copies.
Nested objects merge key by key. Arrays and scalars replace the existing value. Unknown keys are added without a warning. Top-level keys of GAME_COLORS: ROAD_VISUALS, ROAD_STRUCTURES, AREA_VISUALS, MODE_COLORS, BUILDING_FOUNDATION_DEPTHS, TRACK_SPEED_COLORS, TRACK_TPH_COLORS, ROUTE_UTILIZATION_COLORS, ROUTE_RELIABILITY_COLORS, SECTION_DELAY_COLORS, TRACK_ELEVATIONS, TRACK_ARROWS, MIN_TRACK_BASE_PIXEL_WIDTH, JOURNEY_WALK_DOTS, TRACK_DETAIL, MIN_ROAD_PIXEL_WIDTH, MIN_LINE_WIDTH_MODIFIER, LINE_WIDTH_MODIFIER, OFFSET_MULTIPLIER, JOURNEY_LINE_WIDTH_MODIFIER, STATION_MARKER_WIDTH_MODIFIER, TRAIN_PATH_WIDTH_MODIFIER, PICKABLE_TRACK_WIDTH_MODIFIER, SIMPLE_ROUTE_ZOOM, PORTOLAN, STATION_GROUP_DISTANCE_THRESHOLD, STATION_MARKER_HOVER_RESIZE, STATION_MARKER, EDIT_ROUTE_MARKER, RAIL_CROSSING_MARKER_SIZE_PX. The rail, tie and catenary shader reads TRACK_DETAIL once when its module loads; later TRACK_DETAIL overrides do not reach it. Logs [Modding API] Modifying colors: (development builds only) with the top-level keys.
Parameter overrides (Partial<typeof GAME_COLORS>): partial GAME_COLORS tree; must be an object.
Errors:
overridesmissing or not an object: logs[Modding API] Invalid colors overrides - must be an object, changes nothing.- exception: logs
[Modding API] Error modifying colors:.
Side effects: top-level GAME_COLORS keys reassigned.
api.modifyColors({ TRACK_SPEED_COLORS: [{ speedKmh: 50, hex: '#22C55E' }, { speedKmh: 130, hex: '#A855F7' }] });
// the whole array is replaced; a one-entry array leaves one entrymodifyPathfindingRules#
api.modifyPathfindingRules(overrides: PathfindingRulesOverrides): voidMerge numeric overrides into the commute pathfinding rules PATHFINDING_RULES in place.
The commute worker receives the merged rules with the next commute simulation. Nested objects (PERCEIVED_TIME, DRIVING_TIMES) are mutated key by key. Every leaf must be a finite number. Keys named __proto__, constructor or prototype are skipped without a log. Logs [Modding API] Modifying pathfinding rules: (development builds only) with the top-level keys.
Parameter overrides (PathfindingRulesOverrides): nested partial of PATHFINDING_RULES; must be an object.
Errors:
overridesmissing or not an object: logs[Modding API] Invalid pathfinding rules overrides - must be an object, changes nothing.overridesis an array: logs[Pathfinding Rules] Ignoring invalid overrideswith the expectation, changes nothing.- key not in
PATHFINDING_RULES: logs[Pathfinding Rules] Ignoring unknown key PATHFINDING_RULES.<path>, skips it. - non-object or array value for an object key: logs
[Pathfinding Rules] Ignoring PATHFINDING_RULES.<path>with the expectation, skips it. - non-finite or non-numeric leaf: logs
[Pathfinding Rules] Ignoring PATHFINDING_RULES.<path>with the expectation, skips it. - exception: logs
[Modding API] Error modifying pathfinding rules:.
Side effects: PATHFINDING_RULES mutated in place; the merged rules sent to the commute worker with each commute batch.
// riders walk up to 60 minutes to a station and take up to 2 transfers
api.modifyPathfindingRules({ MAX_WALK_TO_FROM_STATION: 60 * 60, MAX_TRANSFERS: 2 });PathfindingRulesOverrides#
Nested partial of PATHFINDING_RULES accepted by modifyPathfindingRules. Each leaf must be a finite number.
type PathfindingRulesOverrides = DeepPartial<typeof PATHFINDING_RULES>Rules with their own behaviour#
Simulation cadence: TICKS_PER_UPDATE#
The simulation advances in fixed ticks of 0.5 in-game seconds (GAME_SECONDS_PER_TICK in shared data, a module constant outside RULES, so mods cannot change it). Ticks run in batches, one update each. The game loop's real-time interval scales with the batch size: a bigger batch runs more ticks per update, and updates come at longer intervals. The target rate of game time is the same either way.
RULES.TICKS_PER_UPDATE has an entry per speed tier (slow, normal, fast, ultrafast), each with four values:
| Key | What it controls |
|---|---|
gameState | Ticks of train, signal, and station simulation per update. Also sets the real-time interval between updates. |
trainCoords | Kept for compatibility; currently has no effect. Train positions are recalculated once, on each update's final tick. |
popMovement | Ticks between pop movement updates inside a batch. Pops are also advanced on each update's final tick and at the start of every commute interval. |
popMovementGeojson | Kept for compatibility; currently has no effect. The pop movement map layer is rebuilt only on an update's final tick, throttled by wall-clock time. |
window.SubwayBuilderAPI.modifyConstants({
TICKS_PER_UPDATE: {
ultrafast: {
gameState: 16, // Half the default batch of 32
trainCoords: 8,
popMovement: 8,
popMovementGeojson: 16
}
}
});Changes apply on the next update, without a reload. The loop picks up the new interval without the player touching the speed controls.
Every value must be a whole number from 1 to RULES.MAX_TICKS_PER_UPDATE (default 1000). A larger value is clamped to the cap and a fraction of 1 or more is rounded down, each with a console warning. Anything else (zero, a negative number, a fraction below 1, a string, NaN) falls back to that key's shipped default with a console warning. The cap is moddable: raising MAX_TICKS_PER_UPDATE permits bigger batches, and each update runs its whole batch synchronously.
Game speed: GAME_SECONDS_PER_SECOND#
RULES.GAME_SECONDS_PER_SECOND sets how many in-game seconds pass per real second at the paced speed tiers (slow, normal, fast). Unlike TICKS_PER_UPDATE, this changes how fast game time passes.
ultrafast is not paced: it runs the simulation back to back, as fast as the machine allows, so its value (500) does not set its speed. On a large save that is a few thousand game seconds per real second, and more on a small one.
window.SubwayBuilderAPI.modifyConstants({
GAME_SECONDS_PER_SECOND: {
normal: 50, // Double-speed "normal" tier
fast: 500 // 1 day = about 3 real minutes
}
});Values are read live on every game-loop fire, so changes apply at once, without the player touching the speed controls. Every value must be a positive finite number; anything else falls back to that tier's shipped default with a console warning. `actions.setSpeedMultiplier` writes to these same values: modifyConstants sets absolute numbers. setSpeedMultiplier multiplies the values captured at its first call and rounds the result, so a later call for a tier overwrites a modifyConstants change made to that tier after the first call.
Capacity warnings: WARNING_CONSTS#
RULES.WARNING_CONSTS controls train capacity warnings: a train boarding passengers with no more empty spots than the active threshold can raise a warning for that station (at most one per station each time the game builds warnings), and the warning expires after CAPACITY_WARNING_DURATION game seconds. Which threshold is active depends on the player's "Capacity Warning Threshold" feature flag: CAPACITY_WARNING_THRESHOLD (50) when enabled, CAPACITY_WARNING_THRESHOLD_OLD (100) when disabled (the default).
window.SubwayBuilderAPI.modifyConstants({
WARNING_CONSTS: {
CAPACITY_WARNING_THRESHOLD_OLD: 25, // Only warn when trains are nearly full
CAPACITY_WARNING_DURATION: 60 * 30 // Keep warnings around for 30 game-minutes
}
});Track drawing snap radius: PIXEL_SNAP_THRESHOLD#
RULES.PIXEL_SNAP_THRESHOLD is the radius in screen pixels within which the cursor snaps to existing track nodes and lines while drawing. It is read live on every pointer move while drawing. During the tutorial, track snapping uses a fixed radius of 30 px instead. The same rule also sets the radius for picking an existing waypoint dot while editing a route, where the tutorial radius does not apply.
window.SubwayBuilderAPI.modifyConstants({
PIXEL_SNAP_THRESHOLD: 20 // Twice the default 10 px radius
});Station marker bullets: MARKER_ROUTE_BULLETS#
RULES.MARKER_ROUTE_BULLETS sizes the row of route bullets shown under station and station-group names on the map. The game projects the row's total width from the bullet text (TEXT_SIZE_MULTIPLIER, CHAR_WIDTH_RATIO) plus ICON_GAP_PX between bullets, and hides the whole row when the projection exceeds MAX_ROW_WIDTH_PX: the default cutoff of 300px hides the row at stations served by very many routes.
window.SubwayBuilderAPI.modifyConstants({
MARKER_ROUTE_BULLETS: {
MAX_ROW_WIDTH_PX: 500, // Show the bullet row at busier stations
ICON_GAP_PX: 4 // More breathing room between bullets
}
});The values are read when the marker model is rebuilt: when the zoom rounded to a tenth changes, and on other changes such as the network, the selection, edit mode, the route line width setting, station layer visibility or the theme. Panning alone does not rebuild it.
Station crowd ambience: SOUND_SETTINGS#
RULES.SOUND_SETTINGS controls the crowd noise heard when the camera is zoomed in near a constructed station with routes. Volume falls off linearly with the distance between the camera center and the closest qualifying station plus a zoom-derived altitude term (so zooming out fades it too), reaching zero at NEAR_STATIONS_THRESHOLD meters and peaking at CROWD_MAX_VOLUME.
window.SubwayBuilderAPI.modifyConstants({
SOUND_SETTINGS: {
CROWD_MAX_VOLUME: 0.5, // Louder platforms
NEAR_STATIONS_THRESHOLD: 2000 // Audible from further away
}
});The values are read live, so changes apply the next time the camera moves.
Delay recovery dwell: DWELL.DELAY_RECOVERY_MIN_FRACTION#
RULES.DWELL.DELAY_RECOVERY_MIN_FRACTION lets a late train cut its scheduled dwell short to claw back time. The value is a fraction of the train type's stop time plus the station type's extra dwell: the shipped default of 1 means no recovery at all. A lower value lets a late train dwell for as little as that fraction of the scheduled stop. The cut never goes below the passenger-exchange (dynamic dwell) time for that stop, and never recovers more than the train is actually behind schedule, so a train cannot depart ahead of its original timetable. Values are clamped to 0 through 1.
window.SubwayBuilderAPI.modifyConstants({
DWELL: {
DELAY_RECOVERY_MIN_FRACTION: 0.5 // A late train can dwell for half its scheduled stop
}
});The value is read each time a stop's dwell is computed, so a change applies to the next stop a train makes.
Bond financing: BOND_TYPES#
RULES.BOND_TYPES holds everything behind the three bond offerings (SMALL, MEDIUM, LARGE). Keys per bond: displayName (shown in the financial dashboard), principal, interestRate (daily, charged hourly at 1/24 on the remaining principal), principalPaymentRate (fraction of the original principal repaid per game hour) and requiredDailyRevenue (operational revenue over the previous game day required to qualify).
window.SubwayBuilderAPI.modifyConstants({
BOND_TYPES: {
SMALL: {
displayName: '$200M Bond', // Rename to match the new principal
principal: 200_000_000,
interestRate: 0.02, // Cheaper starter loans
requiredDailyRevenue: 5_000_000 // Easier to qualify for
}
}
});Changes apply to bonds issued after the override; an already-issued bond keeps the principal, interest rate and hourly payment it was issued with, while its displayed name is read live from BOND_TYPES. The bond ids (small, medium, large) key issued bonds in save data and are fixed. Keys added to BOND_TYPES are never read.
Read the current bond types (names included) with window.SubwayBuilderAPI.gameState.getBondTypes().
The station marks (STATION_MARKER)#
Two station marks share these values: the dot drawn for most stops, and the interchange pill drawn for a stop whose lines fill a bundle of the schematic diagram. The pill stands exactly where that dot would have been and hides it.
window.SubwayBuilderAPI.modifyColors({
STATION_MARKER: {
DOT_DIAMETER: 9, // Bigger station dots
PILL_DIAMETER: 9, // Fatter interchange pills (MAX_DIAMETER caps them at 9)
BORDER_WIDTH: 2, // A heavier ring, on the dots and the pills alike
BORDER_DARK: '#1b1b3a' // The ring on white dots and on every pill
}
});DOT_DIAMETER (8) is the ordinary station dot's diameter in screen pixels at zoom 14. At other zooms it scales with PORTOLAN.RIBBON_WIDTH_STOPS, so dots thicken and thin with the route lines; the zoom scaling is capped at MAX_DIAMETER (9) and the route width scale then multiplies the result. SELECTED_DOT_DIAMETER (10) is the enlarged dot on the open station, the selected route's stops and journey stops. PILL_DIAMETER (8) is the interchange pill's height and twice its corner radius; a pill is a dot stretched along its bundle. Wider zooms scale the pill down by PORTOLAN.MARKER_ICON_SCALE_STOPS, and MAX_DIAMETER caps it too. Also here: GROUP_DOT_SPLIT_SPREAD_PX, GROUP_DOT_SPLIT_MIN_ZOOM, GROUP_DOT_SPLIT_ZOOM_STEP and PILL_OFF_CORRIDOR_M, which decide when a station complex splits into separate dots and when a pill falls back to a dot, plus FADE_MS and LABEL_ZOOM; the Map Visuals Reference below lists each.
BORDER_WIDTH is in screen pixels. Both marks are sized in screen pixels at zoom 14 and scale from there. The dot's ring is drawn at BORDER_WIDTH at every zoom. The pill's ring is baked into an image that the marker scale ramp then shrinks, so the game draws it into that image divided by the scale (rounded to 0.1) it is about to be shown at.
BORDER_DARK rings every white dot, every diagram pill, and every dot in the dark theme. BORDER_LIGHT rings every other dot in the light theme: route-colored, black and gray dots.
Every STATION_MARKER value is read live, with no reload. FADE_MS and the dots' BORDER_WIDTH apply the next time the marker layers render. The dot diameters, the dot ring colors and LABEL_ZOOM show when the station marker model next rebuilds, which happens when any of its inputs changes (among them the zoom rounded to a tenth, the network, the selection, the theme and the Route line width setting).
The route diagram (PORTOLAN)#
With Show interlined routes turned on (Settings), a built diagram and no route edit in progress, zooming out swaps the track-accurate route lines for a schematic diagram: routes that share a corridor are merged into one bundle of parallel ribbons, with an interchange marker where a station's lines fill the bundle. Zooming back in past PORTOLAN.ROUTE_ZOOM hands the map back to lines that follow the actual rails.
The diagram's geometry is computed by the game when the network changes, and mods cannot edit it. GAME_COLORS.PORTOLAN controls how that geometry is presented across zoom levels: where the handoff sits, how wide a ribbon draws, how big a marker gets, and how far a bundle spreads. Everything except BAND_GAP_SCALE is read as the map redraws, so it applies without a reload. BANDS min values are also read when the diagram is built, to slice it, so a change there takes full effect at the next build.
The interchange marker's own size and ring are not here: they belong to the station marks it replaces, under STATION_MARKER above. MARKER_ICON_SCALE_STOPS scales the pill; STATION_MARKER.PILL_DIAMETER is what it scales. The plain dots follow RIBBON_WIDTH_STOPS instead.
window.SubwayBuilderAPI.modifyColors({
PORTOLAN: {
ROUTE_ZOOM: 14, // Hand back to track-accurate lines earlier
MARKER_ZOOM: 12, // Hold the interchange markers back until closer in
RIBBON_WIDTH_STOPS: [
// Arrays are replaced wholesale: pass every stop, in ascending zoom order
{ zoom: 10, width: 1.5 },
{ zoom: 14, width: 4 }
]
}
});Values between two stops are interpolated linearly; outside the listed stops the nearest stop's value holds flat, so a two-stop table is a ramp with a floor and a ceiling. A single stop is a constant.
BAND_GAP_SCALE is informational: the game never reads it. Curved route lettering on the ribbons follows OFFSET_SCALE_STOPS; route bullets and straight labels follow a fixed ramp from zoom 11 to 14, so changing OFFSET_SCALE_STOPS moves the ribbons without moving the bullets and straight labels.
Game Rules Reference#
Every rule in RULES, its default value, and what it does. Generated from the game's rule definitions, so it matches the shipped defaults.
| Key | Default | Description |
|---|---|---|
MIN_SINGLE_TRACK_LENGTH | 10 | Minimum length in meters of a track in a track group with only one lane of tracks. |
MIN_TRACK_LENGTH | 10 | Minimum length in meters of a parallel/quad track when auto-crossovers are enabled. |
MIN_ADVANCED_TRACK_LENGTH | 10 | Override for the minimum track length when advanced mode is enabled — power users get the shorter limit on any lane type. |
MAX_TRACK_LENGTH | 10000 | Maximum length in meters of a single track. |
COORD_PRECISION | 6 | Decimal places coordinates are rounded to before being stored (6 is roughly 0.1 meters). Must be a whole number from 0 to 15. Existing geometry is not re-rounded when this changes. |
PIXEL_SNAP_THRESHOLD | 10 | Radius in screen pixels within which drawn track endpoints snap to existing tracks, stations, and endpoints. |
STATION_LENGTH_BUFFER | 4 | Extra meters of platform required on top of the raw car-length times car-count. |
TRACK_WIDTH | 1.435 | Width in meters of a single track. |
SCISSORS_CROSSOVER_LENGTH | 40 | Length in meters of a scissors crossover between parallel tracks. |
OFFSET_TRANSITION_LENGTH | 50 | Maximum length in meters of a lateral track-spacing transition (station flare). e.g. quad rails returning from an island-platform express station's widespacing to normal running spacing. |
CANT_MAX_APPLIED_MM | 150 | Fallback maximum applied cant (superelevation) in millimeters on running track, used when a train type does not define stats.maxCantMm. Real-world mainline practice is ~150mm. |
CANT_RAMP_MM_PER_S | 45 | Maximum rate of cant change in millimeters per second of travel time. Lower = longer cant transitions and slower curve entries next to flat track. |
CANT_MAX_STOPPED_MM | 150 | Maximum applied cant in millimeters on track where trains may come to a stop (junction and station approaches). Vehicle cant above this only materializes on open running track. |
CANT_STOP_ZONE_LENGTH_M | 200 | Length in meters of the approach to a junction or station treated as a possible stopping zone for the cant cap. |
CANT_STATION_MAX_MM | 0 | Maximum applied cant in millimeters through station tracks — platforms must be level. |
CANT_STREET_RUNNING_MAX_MM | 0 | Maximum applied cant in millimeters for street-running track — rail set into a roadway is flat. |
OFFSET_TRANSITION_RADIUS_MULTIPLIER | 10 | How forgivingly curvature inside a spacing-transition zone is judged for speed limits (higher = more forgiving). |
STATION_HEIGHT | 4 | Distance in meters from the station floor to the roof. Used to check if stations are colliding with each other. |
TUNNEL_HEIGHT | 5 | Distance in meters from the bottom to the top of the tunnel. |
V_MERGE_SIGNAL_WINDOW_LENGTH | 200 | Length in meters of the signal window of a v-merge signal. |
DIAMOND_SIGNAL_WINDOW_LENGTH | 10 | Length in meters of the signal window of a diamond signal, for each track where the center is where the diamond meets. |
GRADE_CROSSING_SIGNAL_RADIUS | 200 | Half-width in meters of each grade-crossing signal's track coverage. |
MAX_JERK | 0.3 | Maximum change in acceleration of a train in m/s³. |
ADDITIONAL_WARNING_WINDOW_LENGTH | 2 | Length in meters of the first warning window behind a train. This is a gap used to keep trains from crashing into each other. |
ADDITIONAL_EXTRA_WARNING_WINDOW_LENGTH | 100 | Length in meters of the second warning window used to stop trains from running into each other. This is a gap used to keep trains from crashing into each other. |
DEFAULT_TICKET_COST | 3 | Default transit fare in dollars for a new game. |
COMMUTE_INTERVAL_LENGTH | 900 | Commuters are released in intervals of this many seconds. |
SCHEDULE_TRANSITION_WINDOW_MAX_LENGTH | 5400 | Maximum time in seconds it can take a route to add/remove trains as the demand windows change. This only effects very long routes where it can take a while to add/remove trains. |
SCHEDULING_SIMULATION_INTERVAL_MINUTES | 10 | Granularity in minutes of the train-scheduling analysis/debug simulator. |
MAX_ROUTE_SIMULATION_DURATION | 86400 | Won't simulate a route longer than this many seconds, to prevent infinite loops in route time estimation. |
MAX_WAIT_TIME | 840 | Maximum time in seconds a train waits at a terminal. |
STARTING_MONEY | 3000000000 | Money in dollars the player starts a new game with. |
STARTING_TRAIN_CARS | 30 | Train cars the player starts a new game with. |
FARE_MULTIPLIER | 365 | For every dollar payed in fares, the player earns this many dollars. |
ACCEPTABLE_SPEED_MARGIN | 0.01 | Margin of error for the train's speed when calculating movements. |
MIN_ELEVATION | -100 | Minimum construction elevation in meters. |
MAX_ELEVATION | 20 | Maximum construction elevation in meters. |
TIME_TO_KEEP_COMMUTE_DATA | 86400 | How long in seconds to keep completed commutes data. |
STUCK_TRAIN_TIMEOUT | 900 | If a train hasn't moved for this many seconds, consider it stuck and remove it. |
IS_DELAYED_THRESHOLD | 60 | A train running this many seconds behind schedule is shown as delayed, or ahead of it as early. Display only, no effect on the simulation. |
MAX_TERMINUS_ALTERNATE_DETOUR | 400 | Max extra distance in meters (arrival + departure combined) an alternate terminus platform may add over the canonical platform before it is excluded as a turnback target. |
COMMUTE_DATA_FILTER_INTERVAL | 3600 | Filter completed commutes every this many seconds. |
INFRASTRUCTURE_COST_CHARGE_INTERVAL | 300 | How often in game seconds to charge infrastructure maintenance costs. |
EXTRA_TRACK_STATION_MAINTENANCE_COST_MUTLIPLIER | 0.25 | Maintenance cost multiplier for every extra track lane in a station. |
OPERATIONAL_COST_CHARGE_INTERVAL | 900 | How often in game seconds to charge operational costs. |
TRAIN_SCHEDULE_TRANSITION_WINDOW | 1800 | Transition window in seconds before and after demand level changes. |
BUILDING_FOUNDATION_GAP | 3 | Minimum gap in meters between the bottom of a building foundation and the tunnel. |
WALKING_SPEED | 1 | Walking speed in m/s when using a straight-line approximation instead of the actual path. |
WALKING_SPEED_ACCURATE_PATH | 1.5 | Walking speed in m/s when using accurate path distance. |
GRADE_CROSSING_APPROACH_SECONDS | 20 | Gates start closing this many seconds before a train is predicted to arrive at the crossing. |
GRADE_CROSSING_CLEAR_SECONDS | 15 | Gates remain closed for this many seconds after a train's tail clears the crossing (post-pass hold). |
MAX_ROUTE_NAME_LENGTH | 16 | Maximum number of characters in a route bullet name. |
MAX_ROUTE_FULL_NAME_LENGTH | 48 | Maximum number of characters in a route's optional full name. |
MAX_TICKS_PER_UPDATE | 1000 | Ceiling on every TICKS_PER_UPDATE value. Raise it to allow bigger batches, at the risk of long game-loop stalls. Must be a whole number of at least 1. |
CONSTRUCTION_COSTS#
Multipliers and line items for construction and maintenance costs.
| Key | Default | Description |
|---|---|---|
CONSTRUCTION_COSTS.TBM_MIN_SEGMENT_COST | 40000000 | Fixed TBM mobilization charge in dollars applied to every contiguous deep-bore blueprint segment. |
CONSTRUCTION_COSTS.TUNNEL#
Track cost multipliers by lane count.
| Key | Default | Description |
|---|---|---|
CONSTRUCTION_COSTS.TUNNEL.SINGLE_MULTIPLIER | 0.75 | Cost multiplier for single-lane track. |
CONSTRUCTION_COSTS.TUNNEL.QUAD_MULTIPLIER | 1.5 | Cost multiplier for quad-lane track. |
CONSTRUCTION_COSTS.STATION#
Station cost multipliers.
| Key | Default | Description |
|---|---|---|
CONSTRUCTION_COSTS.STATION.SINGLE_MULTIPLIER | 0.75 | Cost multiplier for single-track stations. |
CONSTRUCTION_COSTS.STATION.QUAD_MULTIPLIER | 1.5 | Cost multiplier for quad-track stations. |
CONSTRUCTION_COSTS.STATION.LENGTH_COST_FACTOR | 0.25 | Fraction of station cost that scales with capacity in cars (0 = disabled, 1 = fully linear). |
CONSTRUCTION_COSTS.ELEVATION_MULTIPLIERS#
Cost multipliers by construction elevation type.
| Key | Default | Description |
|---|---|---|
CONSTRUCTION_COSTS.ELEVATION_MULTIPLIERS.DEEP_BORE | 4.5 | Deep-bore tunnel cost multiplier. |
CONSTRUCTION_COSTS.ELEVATION_MULTIPLIERS.STANDARD_TUNNEL | 2 | Standard tunnel cost multiplier. |
CONSTRUCTION_COSTS.ELEVATION_MULTIPLIERS.CUT_AND_COVER | 1 | Cut-and-cover cost multiplier (baseline). |
CONSTRUCTION_COSTS.ELEVATION_MULTIPLIERS.TRENCHED | 0.5 | Open-top trench cost multiplier. |
CONSTRUCTION_COSTS.ELEVATION_MULTIPLIERS.AT_GRADE | 0.35 | At-grade (surface) cost multiplier. |
CONSTRUCTION_COSTS.ELEVATION_MULTIPLIERS.RAMP | 0.5 | Low open ramp / embankment (1-5 m) cost multiplier. |
CONSTRUCTION_COSTS.ELEVATION_MULTIPLIERS.ELEVATED | 0.8 | Elevated viaduct cost multiplier. |
CONSTRUCTION_COSTS.WATER_MULTIPLIERS#
Multipliers for construction over water, applied on top of the base elevation multiplier.
| Key | Default | Description |
|---|---|---|
CONSTRUCTION_COSTS.WATER_MULTIPLIERS.DEEP_BORE | 1.44444 | Deep-bore over water. |
CONSTRUCTION_COSTS.WATER_MULTIPLIERS.STANDARD_TUNNEL | 1.5 | Standard tunnel over water. |
CONSTRUCTION_COSTS.WATER_MULTIPLIERS.CUT_AND_COVER | 3 | Cut-and-cover over water. |
CONSTRUCTION_COSTS.WATER_MULTIPLIERS.TRENCHED | 100 | Trenched cannot be built over water. |
CONSTRUCTION_COSTS.WATER_MULTIPLIERS.AT_GRADE | 100 | At-grade cannot be built over water. |
CONSTRUCTION_COSTS.WATER_MULTIPLIERS.RAMP | 100 | Low ramp / embankment cannot be built over water. |
CONSTRUCTION_COSTS.WATER_MULTIPLIERS.ELEVATED | 2.5 | Elevated viaduct over water. |
CONSTRUCTION_COSTS.ELEVATION_THRESHOLDS#
Elevation boundaries in meters between construction types.
| Key | Default | Description |
|---|---|---|
CONSTRUCTION_COSTS.ELEVATION_THRESHOLDS.DEEP_BORE | -100 | Below this elevation is deep bore. |
CONSTRUCTION_COSTS.ELEVATION_THRESHOLDS.STANDARD_TUNNEL | -24 | Standard tunnel threshold. |
CONSTRUCTION_COSTS.ELEVATION_THRESHOLDS.CUT_AND_COVER | -10 | Cut-and-cover threshold. |
CONSTRUCTION_COSTS.ELEVATION_THRESHOLDS.TRENCHED | -4 | Trenched threshold. |
CONSTRUCTION_COSTS.ELEVATION_THRESHOLDS.AT_GRADE | -1 | Boundary between trenched and at-grade. |
CONSTRUCTION_COSTS.ELEVATION_THRESHOLDS.RAMP | 0 | Boundary between at-grade and the open ramp / embankment. |
CONSTRUCTION_COSTS.ELEVATION_THRESHOLDS.ELEVATED | 5 | At or above this elevation is an elevated viaduct. |
CONSTRUCTION_COSTS.GRADE_CROSSING#
Grade crossing cost multipliers.
CONSTRUCTION_COSTS.GRADE_CROSSING.ROAD_CLASS_MULTIPLIER#
Cost multiplier by road class.
| Key | Default | Description |
|---|---|---|
CONSTRUCTION_COSTS.GRADE_CROSSING.ROAD_CLASS_MULTIPLIER.minor | 1 | Minor road crossing. |
CONSTRUCTION_COSTS.GRADE_CROSSING.ROAD_CLASS_MULTIPLIER.medium | 1.25 | Medium road crossing. |
CONSTRUCTION_COSTS.GRADE_CROSSING.ROAD_CLASS_MULTIPLIER.major | 1.5 | Major road crossing. |
CONSTRUCTION_COSTS.GRADE_CROSSING.ROAD_CLASS_MULTIPLIER.highway | 2 | Highway crossing. |
CONSTRUCTION_COSTS.GRADE_CROSSING.LANE_COUNT_MULTIPLIER#
Cost multiplier by number of track lanes crossing the road.
| Key | Default | Description |
|---|---|---|
CONSTRUCTION_COSTS.GRADE_CROSSING.LANE_COUNT_MULTIPLIER.1 | 0.75 | Single lane. |
CONSTRUCTION_COSTS.GRADE_CROSSING.LANE_COUNT_MULTIPLIER.2 | 1 | Two lanes. |
CONSTRUCTION_COSTS.GRADE_CROSSING.LANE_COUNT_MULTIPLIER.4 | 1.5 | Four lanes. |
CONSTRUCTION_COSTS.BUILDING_OVERPASS#
Costs and limits for elevated tracks spanning over buildings.
| Key | Default | Description |
|---|---|---|
CONSTRUCTION_COSTS.BUILDING_OVERPASS.VERTICAL_CLEARANCE | 3 | Vertical clearance in meters required between building roof and track deck. |
CONSTRUCTION_COSTS.BUILDING_OVERPASS.COST_PREMIUM | 3 | Cost premium of a special long-span structure vs a standard elevated segment. |
CONSTRUCTION_COSTS.BUILDING_OVERPASS.STANDARD_SPAN | 35 | Reference span in meters; superstructure cost scales with span / STANDARD_SPAN. |
CONSTRUCTION_COSTS.BUILDING_OVERPASS.PIER_COST_PER_METER_HEIGHT | 200000 | Straddle-pier cost per meter of building height, per building crossed. |
CONSTRUCTION_COSTS.BUILDING_OVERPASS.MIN_BUILDING_HEIGHT | 3 | Floor for building height in meters when height data is missing or zero. |
YARDS#
Train yard rules (yards-enabled saves only).
| Key | Default | Description |
|---|---|---|
YARDS.BERTH_OVERHEAD_M | 10 | Clearance in meters added to each train length when packing trains onto berth tracks. |
YARDS.MIN_YARD_TRACK_LENGTH_M | 50 | Minimum length in meters of a drawn yard track. |
YARDS.DEFAULT_YARD_SPEED_LIMIT | 4.47 | Speed cap in m/s on yard trackage (berths, leads, throat connectors) for train types that do not set their own yardSpeedLimit. Defaults to 4.47 m/s (10 mph) - non-revenue yard moves run at walking pace. |
YARDS.YARD_TRACK_COST_MULTIPLIER | 0.5 | Construction cost multiplier for yard track relative to running track. Yard trackage is priced exactly like running track of the same length, elevation and lane count, then scaled by this — non-revenue storage track is cheaper to build than mainline. |
STREET_RUNNING#
Rules for street-running (tram) tracks.
| Key | Default | Description |
|---|---|---|
STREET_RUNNING.ROAD_SNAP_DISTANCE | 15 | Distance in meters within which drawn tracks snap to a road. |
STREET_RUNNING.ROAD_SNAP_BREAKAWAY_DISTANCE | 35 | Distance in meters at which a snapped track breaks away from the road. |
STREET_RUNNING.COST_MULTIPLIER | 3.5 | Construction cost multiplier for street-running track. |
STREET_RUNNING.EXCLUSIVE_ROW_COST_MULTIPLIER | 1.5 | Additional cost multiplier for an exclusive right-of-way. |
STREET_RUNNING.ELEVATION_TOLERANCE | 0.01 | Tracks within this many meters of elevation 0 are considered at road level (street-running). |
ROAD_SPEED_LIMITS#
Speed limits in m/s by road class, used only for street-running trains.
| Key | Default | Description |
|---|---|---|
ROAD_SPEED_LIMITS.highway | 29.1 | Highway speed limit. |
ROAD_SPEED_LIMITS.major | 20.1 | Major road speed limit. |
ROAD_SPEED_LIMITS.medium | 15.6 | Medium road speed limit. |
ROAD_SPEED_LIMITS.minor | 11.2 | Minor road speed limit. |
MARKER_ROUTE_BULLETS#
Sizing for the route bullet row shown under station and station-group markers on the map.
| Key | Default | Description |
|---|---|---|
MARKER_ROUTE_BULLETS.TEXT_SIZE_MULTIPLIER | 0.8 | Bullet text size as a fraction of the bullet icon size. |
MARKER_ROUTE_BULLETS.CHAR_WIDTH_RATIO | 0.62 | Approximate width of one bullet character as a fraction of the font size, used to project bullet widths. |
MARKER_ROUTE_BULLETS.ICON_GAP_PX | 2 | Horizontal gap in pixels between bullets in the row. |
MARKER_ROUTE_BULLETS.MAX_ROW_WIDTH_PX | 300 | The entire bullet row is hidden when its projected width exceeds this many pixels. |
SOUND_SETTINGS#
Station crowd ambience settings.
| Key | Default | Description |
|---|---|---|
SOUND_SETTINGS.NEAR_STATIONS_THRESHOLD | 1000 | Distance in meters around the camera center within which stations are checked for crowd ambience. Volume falls off linearly with distance and reaches zero here. |
SOUND_SETTINGS.CROWD_MAX_VOLUME | 0.25 | Maximum volume (0-1) of the station crowd ambience, reached when fully zoomed in on a constructed station with routes. |
WARNING_CONSTS#
Thresholds and lifetimes for train capacity warnings.
| Key | Default | Description |
|---|---|---|
WARNING_CONSTS.CAPACITY_WARNING_THRESHOLD | 50 | A train with fewer than this many empty spots triggers a capacity warning, when the reduced-threshold feature flag is enabled. Lower = warns only when nearly full. |
WARNING_CONSTS.CAPACITY_WARNING_THRESHOLD_OLD | 100 | The capacity warning threshold used when the reduced-threshold feature flag is disabled (the default). |
WARNING_CONSTS.CAPACITY_WARNING_DURATION | 900 | How long in game seconds a capacity warning is kept before it expires. |
BOND_TYPES#
The bond offerings players can issue for financing. Interest accrues on the remaining principal. Already-issued bonds keep the terms they were issued with.
BOND_TYPES.SMALL#
Small bond ($100M at 6%).
| Key | Default | Description |
|---|---|---|
BOND_TYPES.SMALL.displayName | $100M Bond | Display name shown in the financial dashboard. |
BOND_TYPES.SMALL.principal | 100000000 | Amount borrowed in dollars. |
BOND_TYPES.SMALL.interestRate | 0.06 | Daily interest rate on the remaining principal (0.06 = 6% per day), charged hourly at 1/24 of the daily rate. |
BOND_TYPES.SMALL.principalPaymentRate | 0.001 | Fraction of the original principal repaid every game hour. |
BOND_TYPES.SMALL.requiredDailyRevenue | 10000000 | Previous day's revenue in dollars required to issue this bond. |
BOND_TYPES.MEDIUM#
Medium bond ($500M at 4%).
| Key | Default | Description |
|---|---|---|
BOND_TYPES.MEDIUM.displayName | $500M Bond | Display name shown in the financial dashboard. |
BOND_TYPES.MEDIUM.principal | 500000000 | Amount borrowed in dollars. |
BOND_TYPES.MEDIUM.interestRate | 0.04 | Daily interest rate on the remaining principal (0.04 = 4% per day), charged hourly at 1/24 of the daily rate. |
BOND_TYPES.MEDIUM.principalPaymentRate | 0.0008 | Fraction of the original principal repaid every game hour. |
BOND_TYPES.MEDIUM.requiredDailyRevenue | 100000000 | Previous day's revenue in dollars required to issue this bond. |
BOND_TYPES.LARGE#
Large bond ($1B at 2%).
| Key | Default | Description |
|---|---|---|
BOND_TYPES.LARGE.displayName | $1B Bond | Display name shown in the financial dashboard. |
BOND_TYPES.LARGE.principal | 1000000000 | Amount borrowed in dollars. |
BOND_TYPES.LARGE.interestRate | 0.02 | Daily interest rate on the remaining principal (0.02 = 2% per day), charged hourly at 1/24 of the daily rate. |
BOND_TYPES.LARGE.principalPaymentRate | 0.0006 | Fraction of the original principal repaid every game hour. |
BOND_TYPES.LARGE.requiredDailyRevenue | 200000000 | Previous day's revenue in dollars required to issue this bond. |
GAME_SECONDS_PER_SECOND#
How many in-game seconds pass per real second at each speed tier. Values must be positive finite numbers; bad values fall back to the shipped defaults.
| Key | Default | Description |
|---|---|---|
GAME_SECONDS_PER_SECOND.slow | 1 | In-game seconds per real second at slow speed (real time). |
GAME_SECONDS_PER_SECOND.normal | 25 | In-game seconds per real second at normal speed. |
GAME_SECONDS_PER_SECOND.fast | 250 | In-game seconds per real second at fast speed. |
GAME_SECONDS_PER_SECOND.ultrafast | 500 | In-game seconds per real second at ultrafast speed. |
TICKS_PER_UPDATE#
How many simulation ticks are batched into one game-loop update, per speed tier. Every value must be a whole number of at least 1. Raising them trades smoothness for CPU cost; it does not change how fast game time passes, because the interval between updates scales with gameState.
TICKS_PER_UPDATE.slow#
Tick batching at the slow speed tier.
| Key | Default | Description |
|---|---|---|
TICKS_PER_UPDATE.slow.gameState | 1 | Ticks of train, signal, and station simulation run per update at slow speed. The real-time interval between updates scales with this. |
TICKS_PER_UPDATE.slow.trainCoords | 1 | Ticks between train coordinate recalculations at slow speed. Coordinates also refresh on the final tick of every update. |
TICKS_PER_UPDATE.slow.popMovement | 2 | Ticks between updates to pops travelling through the network at slow speed. |
TICKS_PER_UPDATE.slow.popMovementGeojson | 2 | Ticks between rebuilds of the pop movement map layer at slow speed. Should not be lower than gameState. |
TICKS_PER_UPDATE.normal#
Tick batching at the normal speed tier.
| Key | Default | Description |
|---|---|---|
TICKS_PER_UPDATE.normal.gameState | 1 | Ticks of train, signal, and station simulation run per update at normal speed. The real-time interval between updates scales with this. |
TICKS_PER_UPDATE.normal.trainCoords | 1 | Ticks between train coordinate recalculations at normal speed. Coordinates also refresh on the final tick of every update. |
TICKS_PER_UPDATE.normal.popMovement | 2 | Ticks between updates to pops travelling through the network at normal speed. |
TICKS_PER_UPDATE.normal.popMovementGeojson | 2 | Ticks between rebuilds of the pop movement map layer at normal speed. Should not be lower than gameState. |
TICKS_PER_UPDATE.fast#
Tick batching at the fast speed tier.
| Key | Default | Description |
|---|---|---|
TICKS_PER_UPDATE.fast.gameState | 16 | Ticks of train, signal, and station simulation run per update at fast speed. The real-time interval between updates scales with this. |
TICKS_PER_UPDATE.fast.trainCoords | 8 | Ticks between train coordinate recalculations at fast speed. Coordinates also refresh on the final tick of every update. |
TICKS_PER_UPDATE.fast.popMovement | 8 | Ticks between updates to pops travelling through the network at fast speed. |
TICKS_PER_UPDATE.fast.popMovementGeojson | 16 | Ticks between rebuilds of the pop movement map layer at fast speed. Should not be lower than gameState. |
TICKS_PER_UPDATE.ultrafast#
Tick batching at the ultrafast speed tier.
| Key | Default | Description |
|---|---|---|
TICKS_PER_UPDATE.ultrafast.gameState | 48 | Ticks of train, signal, and station simulation run per update at ultrafast speed. The real-time interval between updates scales with this. |
TICKS_PER_UPDATE.ultrafast.trainCoords | 24 | Ticks between train coordinate recalculations at ultrafast speed. Coordinates also refresh on the final tick of every update. |
TICKS_PER_UPDATE.ultrafast.popMovement | 24 | Ticks between updates to pops travelling through the network at ultrafast speed. |
TICKS_PER_UPDATE.ultrafast.popMovementGeojson | 48 | Ticks between rebuilds of the pop movement map layer at ultrafast speed. Should not be lower than gameState. |
Pathfinding Rules Reference#
Every rule in PATHFINDING_RULES, its default value, and what it does. Also generated from the game's rule definitions.
| Key | Default | Description |
|---|---|---|
MAX_TRANSFERS | 4 | Maximum number of transfers per transit journey. Routes can be split into multiple internal RAPTOR routes, so a transfer can also mean two legs on the same route. |
MAX_WALK_TO_FROM_STATION | 2700 | Maximum walking time from origin/destination to the first/last station, in seconds. Defines the walk catchment area of a station (scaled by station type catchment multipliers). |
MAX_DRIVE_TO_FROM_STATION | 420 | Maximum driving time from origin to the first station for drive-to-station (park & ride) access, in seconds. Roughly 4-5 km at urban speeds. |
MAX_TRANSFER_WALKING_TIME | 600 | Maximum walking time between stations when transferring, in seconds. |
FUTURE_CYCLE_TIME_OFFSET | 7200 | How many seconds in advance train departure times are pre-calculated for pathfinding. |
MIN_FUTURE_CYCLES | 2 | Minimum number of future service cycles to calculate. Ensures loop-around journeys work on very long routes (over 2 hours). |
DRIVING_COST_PER_KM | 0.65 | Perceived cost of driving per kilometer in dollars, not including parking. Based on US BTS data, slightly reduced to nerf cars. |
MIN_SENSIBLE_DRIVING_DISTANCE | 1000 | Minimum sensible driving distance in meters. Shorter drives get an extra penalty for the hassle of using a car on a very short trip. |
PARKING_TIME | 180 | Time spent parking at each end of a car journey in seconds (finding a spot and walking from it). |
PARKING_COST | 5 | Cost of parking in dollars per day (average in urban areas). |
ARRIVAL_GAP | 50 | Pops arrive at the station this many seconds before their train departs. |
RANGE_QUERY_WINDOW | 1800 | rRAPTOR departure range window in seconds. The pathfinder considers journeys departing up to this much after the requested time, so a later direct train can beat an immediate two-leg trip. Larger windows are more accurate but cost more per query. |
MAX_RANGE_DEPARTURES | 24 | Cap on how many departure times a single range query evaluates. |
INCOME_MEAN | 60000 | Mean of the normal distribution used to generate pop incomes, in dollars per year. |
INCOME_STD_DEV | 25000 | Standard deviation of the pop income distribution, in dollars per year. |
MINIMUM_INCOME | 15000 | Floor for generated pop incomes, in dollars per year. |
MAXIMUM_INCOME | 200000 | Ceiling for generated pop incomes, in dollars per year. |
HOURS_WORKED_PER_YEAR | 1860 | Hours worked per year, used to convert annual income into an hourly value of time for mode choice. |
AIRPORT_INCOME_MULTIPLIER | 1.5 | Income multiplier for airport pops (air travelers skew wealthier). |
AIRPORT_PARKING_COST_MULTIPLIER | 5 | Airport pops pay this many times the normal PARKING_COST. |
COLLEGE_INCOME_MULTIPLIER | 0.6 | Income multiplier for college pops (students earn less). |
MIN_TRANSIT_CHOICE | 10 | If fewer than this many people at a demand point choose transit, they are all moved to their second choice (driving or walking) to reduce transit pop count and improve performance. |
PERCEIVED_TIME#
Weights for how pops perceive each component of a transit journey, relative to a minute riding the train (1.0). Journeys are ranked and priced into mode choice by this perceived ("generalized") time. Values come from the Wardman et al. 2026 meta-analysis (Transportation Research Part A).
| Key | Default | Description |
|---|---|---|
PERCEIVED_TIME.WALK_MULTIPLIER | 1.39 | How much worse a minute of walking feels than a minute riding. Meta-model implied multiplier for urban metro riders in the modern (smartphone) era. |
PERCEIVED_TIME.AIRPORT_WALK_MULTIPLIER | 1.87 | Walk weight for airport pops, replacing WALK_MULTIPLIER (luggage makes walking feel worse — clock time is unchanged). |
PERCEIVED_TIME.WAIT_MULTIPLIER | 1.37 | How much worse a minute of waiting at a station feels than a minute riding. |
PERCEIVED_TIME.DEPARTURE_SHIFT_MULTIPLIER | 0.4 | Cost per second of departing later than requested (displacement time), e.g. a pop waiting at home for a better train. |
PERCEIVED_TIME.CONGESTED_DRIVING_MULTIPLIER | 1.33 | Perception premium on a minute driven in heavy metropolitan congestion. |
PERCEIVED_TIME.CONGESTION_FULL_AT_MULTIPLIER | 2 | Traffic multiplier at which the whole car trip counts as congested. Keep in sync with DRIVING_TIMES.HIGH_DEMAND. |
PERCEIVED_TIME.PARKING_SEARCH_MULTIPLIER | 1.6 | Perception premium on time spent parking (finding a spot, walking from it). |
DRIVING_TIMES#
Traffic multipliers applied to driving times by time-of-day demand level. A value of 1.5 means drives take 50% longer than free-flow.
| Key | Default | Description |
|---|---|---|
DRIVING_TIMES.VERY_LOW_DEMAND | 0.8 | Overnight traffic multiplier (faster than free-flow baseline). |
DRIVING_TIMES.LOW_DEMAND | 0.9 | Early morning / late evening traffic multiplier. |
DRIVING_TIMES.LOW_MEDIUM_DEMAND | 1 | Midday baseline traffic multiplier. |
DRIVING_TIMES.MEDIUM_DEMAND | 1.25 | Shoulder-peak traffic multiplier. |
DRIVING_TIMES.HIGH_DEMAND | 1.5 | Rush hour traffic multiplier. |
Map Visuals Reference#
Every value in GAME_COLORS, its default, and what it does. Also generated from the game's own definitions. Most deck.gl map layers and the station markers receive the map zoom rounded to a tenth, so a zoom threshold they read takes effect on the rounded zoom; the interchange pills read the exact zoom.
| Key | Default | Description |
|---|---|---|
MIN_TRACK_BASE_PIXEL_WIDTH | 1.5 | Minimum on-screen width in pixels of track lines when zoomed out. |
MIN_ROAD_PIXEL_WIDTH | 0.5 | Minimum on-screen width in pixels of road lines, so roads stay visible when zoomed far out instead of shrinking to sub-pixel widths. |
MIN_LINE_WIDTH_MODIFIER | 1 | The modifier applied to line widths when zoomed out. Output is track width times this value. Currently unused by the game. |
LINE_WIDTH_MODIFIER | 2 | The modifier applied to line widths when zoomed in. Output is track width times this value. Currently unused by the game. |
OFFSET_MULTIPLIER | 1.5 | How much space should be between parallel lines. Currently unused by the game. |
JOURNEY_LINE_WIDTH_MODIFIER | 2 | The modifier applied to journey line widths. Output is track width times this value. Currently unused by the game. |
STATION_MARKER_WIDTH_MODIFIER | 5 | The modifier applied to station marker widths. Output is track width times this value. Currently unused by the game. |
TRAIN_PATH_WIDTH_MODIFIER | 1.25 | The modifier applied to train path widths. Output is track width times this value. Currently unused by the game. |
PICKABLE_TRACK_WIDTH_MODIFIER | 3 | The modifier applied to track widths on the track speeds, elevations, types, and TPH layers to make tracks easier to click. |
SIMPLE_ROUTE_ZOOM | 13 | The zoom level where the map switches from showing routes by track to either simple or interlined routes. |
STATION_GROUP_DISTANCE_THRESHOLD | 150 | Distance in meters within which nearby stations are combined into one station group marker. |
STATION_MARKER_HOVER_RESIZE | 1.15 | The factor by which station markers are resized when hovered. |
RAIL_CROSSING_MARKER_SIZE_PX | 20 | Width and height in screen pixels of the traffic-light marker drawn where two railways cross at grade. Read as the map redraws. |
ROAD_STRUCTURES#
Rendering of road structures: how bridge and tunnel road segments are drawn differently from surface roads. All values are read as the map redraws.
| Key | Default | Description |
|---|---|---|
ROAD_STRUCTURES.TUNNEL_ALPHA | 128 | Alpha (0 to 255) of tunnel road segments, drawn semi-transparent so they read as underground rather than surface roads. |
ROAD_STRUCTURES.BRIDGE_OPACITY | 0.5 | Opacity (0 to 1) of road bridges (casing and fill), semi-transparent so tracks built parallel underneath a bridge remain visible. |
ROAD_STRUCTURES.BRIDGE_CASING_WIDTH_EXTRA | 4 | Extra width in meters of the bridge casing beyond the road width, forming the border that makes a bridge read as elevated. |
MODE_COLORS#
Colors for travel modes, used in charts and the demand map key.
| Key | Default | Description |
|---|---|---|
MODE_COLORS.transit | "#0000FF" | Transit trips. |
MODE_COLORS.driving | "#FF0000" | Driving trips. |
MODE_COLORS.walking | "#00FF00" | Walking trips. |
MODE_COLORS.unknown | "#6e6e6e" | Fallback for trips with an unknown mode. |
TRACK_ARROWS#
The directional arrows drawn along tracks and routes when the track arrows layer is on and the camera is zoomed in. Track arrows sit on the rails themselves; route arrows are the bigger set drawn on route lines. All values are read as the map redraws.
| Key | Default | Description |
|---|---|---|
TRACK_ARROWS.ENABLED_ZOOM | 15.5 | The zoom at which the arrows appear. Further out they are too small to read. |
TRACK_ARROWS.SEGMENT_INTERVAL | 5 | Draw an arrow every this many subdivided segments. Larger means more spacing between arrows. |
TRACK_ARROWS.LENGTH | 2 | Arrow length as a fraction of a segment. Larger means bigger arrows. |
TRACK_ARROWS.TRACK_WIDTH | 4 | Width in meters of the arrows drawn on tracks. |
TRACK_ARROWS.TRACK_WIDTH_MIN_PIXELS | 4 | Minimum on-screen width in pixels of the arrows drawn on tracks. |
TRACK_ARROWS.ROUTE_WIDTH | 6 | Width in meters of the arrows drawn on routes. |
TRACK_ARROWS.ROUTE_WIDTH_MIN_PIXELS | 6 | Minimum on-screen width in pixels of the arrows drawn on routes. |
PORTOLAN#
The Portolan schematic diagram for visually interlined routes: the merged, offset route ribbons the map draws instead of track-accurate lines when visual interlining is on and the camera is zoomed out. These control how the diagram is presented, not the geometry itself, which comes from the diagram build.
| Key | Default | Description |
|---|---|---|
PORTOLAN.ROUTE_ZOOM | 15 | Above this zoom the map draws its own track-accurate route lines instead of the diagram, and the diagram interchange markers stop drawing. Deliberately higher than SIMPLE_ROUTE_ZOOM: the ribbons stay legible much further in than the plain simplified lines they replace, so the diagram is worth holding onto longer. Read as the map redraws. |
PORTOLAN.MARKER_ZOOM | 11 | The zoom at which the diagram interchange markers appear. They all appear at once rather than ranking in: a half-drawn set of stops reads as missing data, not as the important ones. They shrink at the outer end instead, via MARKER_ICON_SCALE_STOPS. Read as the map redraws. |
PORTOLAN.CATERPILLARS#
The caterpillars: the route bullets chained along the ribbons between stations, and the route-name lettering that runs along a ribbon when a name does not fit in a bullet. These only show when visual interlining is enabled.
| Key | Default | Description |
|---|---|---|
PORTOLAN.CATERPILLARS.ENABLED | true | Whether the caterpillars draw at all. Off hides the bullets and the route-name lettering; the ribbons and interchange pills stay. Read as the map redraws. |
PORTOLAN.CATERPILLARS.MIN_ZOOM | 12 | The zoom at which the caterpillars appear. Further out the strip is too small to read and the bullets would only clutter the ribbons. Read as the map redraws. |
PORTOLAN.CATERPILLARS.MAX_BULLET_CHARS | 1 | Longest route name, in characters, that still draws as a badge riding the caterpillar chain. One-character names and two-digit codes keep the 1:1 disc; anything else up to this length draws as a word pill just under BULLET_HEIGHT, and longer names become the route-name lettering running along the ribbon. Read as the map redraws. |
PORTOLAN.CATERPILLARS.BULLET_HEIGHT | 10 | Bullet height in CSS pixels at full zoom. Everything else about a bullet grows from this: non-circular shapes widen from it to hold their glyphs, and a route name too long for a disc becomes a word pill at nine tenths of this height. Drawn bullets are cached per height, so a change applies as bullets rasterise. |
PORTOLAN.CATERPILLARS.BULLET_FONT | "600 7.5px system-ui, sans-serif" | Canvas font shorthand for the glyphs inside the bullets. Sized against the default BULLET_HEIGHT, so scale the pixel size along with it. Drawn bullets are cached per font, so a change applies as bullets rasterise. |
PORTOLAN.CATERPILLARS.TEXT_FAMILY | "system-ui, sans-serif" | Font family of the route-name lettering riding the ribbons. Also the face the glyph advances are measured with, so the lettering is always spaced by the font it draws in. |
PORTOLAN.CATERPILLARS.TEXT_WEIGHT | "italic 700" | CSS font weight, with optional style, of the route-name lettering - italic the way a road map sets a route name apart from the upright station names around it. |
PORTOLAN.CATERPILLARS.HOVER_ALPHA | 100 | Opacity, 0-255, of a caterpillar under the pointer - every bullet and letter of the hovered route fades to this, the click-me feedback for opening its route details. Read as the map redraws. |
PORTOLAN.CATERPILLARS.TEXT_SIZE_STOPS#
Route-name lettering size in pixels by zoom, interpolated linearly and held flat outside the listed stops. Read as the map redraws.
[
{
"zoom": 12,
"size": 10
},
{
"zoom": 16,
"size": 13
}
]PORTOLAN.BANDS#
Zoom bands, widest-zoom first. The diagram build emits one full copy of the network per band, progressively less detailed as you zoom out, and exactly one band may draw at a time - two bands drawn together doubles every ribbon. A camera at zoom z draws the first band whose min is at or below z, so min is what selects a band and max is documentation. The keys are fixed by the build format; changing them leaves the renderer asking for band data that was never emitted. Changing min re-slices the diagram, so it takes effect on the next diagram build rather than immediately.
[
{
"key": 15,
"min": 15,
"max": 24
},
{
"key": 14,
"min": 14,
"max": 15
},
{
"key": 13,
"min": 13,
"max": 14
},
{
"key": 0,
"min": 0,
"max": 13
}
]PORTOLAN.BAND_GAP_SCALE#
Slot-pitch multiplier per band, keyed by band key: how far apart two routes sharing a corridor sit in that band, relative to the innermost band. Reference values only. The build has already baked them into each vertex offset, so this table is for reasoning and assertions and the renderer must not apply it again - doing so narrows every bundle twice over.
{
"0": 0.75,
"13": 0.85,
"14": 0.95,
"15": 1
}PORTOLAN.OFFSET_SCALE_STOPS#
How far a ribbon sits from its bundle centre, as a fraction of the offset baked into the build, interpolated linearly by zoom and held flat outside the listed stops. Bundles narrow as you zoom out so they never swamp the map. Note that the route-name lettering rides the ribbons using a z11 to z14 ramp fixed by the build, so moving these stops drifts the lettering off the line it labels. Read as the map redraws.
[
{
"zoom": 11,
"scale": 1
},
{
"zoom": 14,
"scale": 0.8
}
]PORTOLAN.RIBBON_WIDTH_STOPS#
On-screen ribbon width in pixels by zoom, interpolated linearly and held flat outside the listed stops. Multiplied by the per-mode width from the diagram build style, so a metro ribbon and a commuter-rail ribbon keep their relative weights. The station dots also ride this curve: their diameter scales by the ribbon width relative to its zoom-14 value, so the marks thicken with the lines. Read as the map redraws.
[
{
"zoom": 7,
"width": 1
},
{
"zoom": 10,
"width": 1.5
},
{
"zoom": 13,
"width": 2
},
{
"zoom": 15,
"width": 3
},
{
"zoom": 16,
"width": 3.6
}
]PORTOLAN.MARKER_ICON_SCALE_STOPS#
Size of every station mark by zoom - the diagram interchange pills AND the ordinary station dots, which share the ramp so the two marks never disagree in size - as a fraction of full size, interpolated linearly and held flat outside the listed stops. It dips further at the outer end than OFFSET_SCALE_STOPS does, because a pill has to stop swamping the ribbons it lies across as well as keep reaching them. Read as the map redraws.
[
{
"zoom": 11,
"scale": 0.38
},
{
"zoom": 12,
"scale": 0.5
},
{
"zoom": 14,
"scale": 1
}
]STATION_MARKER#
The station mark: the dots the map draws on stations, and the schematic diagram interchange pills, which replace those dots on an interchange and sit beside them elsewhere. Both marks are their configured diameter at zoom 14 and scale with the camera from there - the dots by the ribbon-width curve, the pills by their own icon ramp - and they share the ring.
| Key | Default | Description |
|---|---|---|
STATION_MARKER.DOT_DIAMETER | 8 | Diameter in screen pixels of the ordinary station dot at zoom 14. At other zooms it follows the route lines: the diameter scales by PORTOLAN.RIBBON_WIDTH_STOPS relative to the ribbon width at zoom 14, so dots thicken and thin with the lines they punctuate. Read as the map redraws. |
STATION_MARKER.SELECTED_DOT_DIAMETER | 10 | Diameter in screen pixels, at zoom 14, of the enlarged dot on emphasised stops: the open station, the stops of the selected route, and journey stops. Follows the ribbon-width curve at other zooms, the same way as DOT_DIAMETER. Read as the map redraws. |
STATION_MARKER.PILL_DIAMETER | 8 | Height in screen pixels of the diagram interchange pill at full size (zoom 14 and in), which is also twice its corner radius - a pill is a dot stretched along its bundle, so this is its dot diameter. Scaled down at wider zooms by PORTOLAN.MARKER_ICON_SCALE_STOPS. Read as the map redraws. |
STATION_MARKER.MAX_DIAMETER | 9 | Ceiling in screen pixels on a station mark as it grows zooming in. The ordinary dot stops growing once the ribbon-width curve would push it past this; the whole curve is clamped there, so the enlarged selected dot stops at the same zoom and keeps its proportion (its ceiling is this times SELECTED_DOT_DIAMETER over DOT_DIAMETER). The pill height is capped at this too, though the shipped pill ramp already tops out at PILL_DIAMETER. Read as the map redraws. |
STATION_MARKER.BORDER_WIDTH | 1 | Ring thickness in screen pixels, held at this width at every zoom. The diagram pill draws its ring into an image that the zoom ramp then shrinks, so the game thickens the line inside that image by whatever the ramp is about to take off. Read as the map redraws. |
STATION_MARKER.BORDER_DARK | "black" | The ring on a mark that has to separate from a pale background: every white interchange dot and every diagram pill, in both themes, plus route-colored dots in the dark theme. Any CSS color. Read as the map redraws. |
STATION_MARKER.BORDER_LIGHT | "white" | The ring on a route-colored dot in the light theme, where the ring is the page showing through rather than an outline. Any CSS color. Read as the map redraws. |
STATION_MARKER.GROUP_DOT_SPLIT_SPREAD_PX | 12 | Two stations of one complex share a dot while they sit within this many screen pixels of EACH OTHER at the current zoom (joining transitively, so a chain of close stations is one dot). Because the gap is measured on screen, a close pair holds its merged dot deeper into the zoom than a far pair, and one complex can be part-merged: the close pair one dot, the outlier its own, every dot styled for its members (or replaced by their diagram interchange pill) under the complex’s one merged label. Raise to keep dots merged longer; set very high to never split. Read as the map redraws. |
STATION_MARKER.GROUP_DOT_SPLIT_MIN_ZOOM | 12 | Below this zoom a complex always draws one merged dot, however far its stations spread - zoomed out, separate dots for one named stop read as clutter. At or past it, GROUP_DOT_SPLIT_SPREAD_PX decides dot by dot. Set above 16 to disable splitting entirely (16 is where the map stops drawing groups at all). Read as the map redraws. |
STATION_MARKER.PILL_OFF_CORRIDOR_M | 15 | How far, in meters, a station mark may sit off the corridor its diagram interchange pill was measured on before the pill stops drawing and the stop falls back to the ordinary circular dot. A pill is a plate lying across a specific bundle at a specific angle - it may slide ALONG that corridor (the engine itself walks terminus markers along the track), but a mark moved sideways off it (a relocated visual centre, a complex centre between bundles) would show a plate crossing nothing. Read as the map redraws. |
STATION_MARKER.FADE_MS | 250 | How long station dots, labels and diagram interchange pills take to fade in and out when they appear or disappear (zoom thresholds, label collision, pills standing in for dots), in milliseconds. 0 makes them pop instantly. Read as the map redraws. |
STATION_MARKER.LABEL_ZOOM | 14 | The zoom past which every station may draw its name, and label space is settled purely by collision: any name that fits without overlapping a higher-priority label shows. At or below this zoom only the top of the importance ranking labels at all (interchanges and complexes, from zoom 12), and past it that ranking still decides who wins contested space - it just never vetoes a label into empty space. Read as the map redraws. |
STATION_MARKER.GROUP_DOT_SPLIT_ZOOM_STEP | 2 | How many zoom levels apart the dot clustering re-evaluates. The camera zoom is rounded DOWN to the nearest step above GROUP_DOT_SPLIT_MIN_ZOOM before the on-screen gaps are measured, so the merge picture holds still across each whole band instead of a pair of dots merging for only a sliver of zooms and coming apart again. Lower it (down to fractions of a level) to re-cluster more responsively at the cost of more churn while zooming. Read as the map redraws. |
ROAD_VISUALS#
Per-road-class rendering config: line width in meters and the zoom window [start, end] over which the road fades in while zooming in. Highways and major roads stay visible further out than smaller roads.
{
"highway": {
"width": 12,
"zoomWindow": [
6.5,
7
]
},
"major": {
"width": 8,
"zoomWindow": [
6.5,
7
]
},
"medium": {
"width": 6,
"zoomWindow": [
12,
12.5
]
},
"minor": {
"width": 4,
"zoomWindow": [
12,
12.5
]
}
}AREA_VISUALS#
Zoom fade windows and final opacities for the flat map area layers. zoomWindow is the [start, end] zoom range over which the layer fades in while zooming in (null means always shown at full opacity). parksLarge covers parks of at least 100,000 square meters. commercial covers both commercial and college areas (one tile layer, so they share a fade and opacity; their colors come from the map theme).
{
"buildings": {
"zoomWindow": [
12,
12.5
],
"opacity": 0.65
},
"parksLarge": {
"zoomWindow": null,
"opacity": 0.8
},
"parksSmall": {
"zoomWindow": [
12,
12.5
],
"opacity": 0.8
},
"commercial": {
"zoomWindow": [
12.25,
12.75
],
"opacity": 0.7
}
}BUILDING_FOUNDATION_DEPTHS#
Depth bands (in meters) and per-theme colors for the building foundation depth map layer. maxDepth Infinity means no upper bound.
[
{
"minDepth": 10,
"maxDepth": 14,
"colors": {
"light": "#009c27",
"dark": "#006b1b"
},
"label": "10-14m"
},
{
"minDepth": 14,
"maxDepth": 18,
"colors": {
"light": "#ff9124",
"dark": "#cc7319"
},
"label": "14-18m"
},
{
"minDepth": 18,
"maxDepth": 22,
"colors": {
"light": "#ff3d24",
"dark": "#cc3119"
},
"label": "18-22m"
},
{
"minDepth": 22,
"maxDepth": 26,
"colors": {
"light": "#8B0000",
"dark": "#5c0000"
},
"label": "22-26m"
},
{
"minDepth": 26,
"maxDepth": 30,
"colors": {
"light": "#d324ff",
"dark": "#a91acc"
},
"label": "26-30m"
},
{
"minDepth": 30,
"maxDepth": Infinity,
"colors": {
"light": "#CBC3E3",
"dark": "#9d96b5"
},
"label": "30m+"
}
]TRACK_SPEED_COLORS#
Track speed color mapping for the track speeds layer: speeds in km/h to colors.
[
{
"speedKmh": 10,
"hex": "#3B82F6"
},
{
"speedKmh": 25,
"hex": "#06B6D4"
},
{
"speedKmh": 50,
"hex": "#22C55E"
},
{
"speedKmh": 75,
"hex": "#F97316"
},
{
"speedKmh": 90,
"hex": "#F43F5E"
},
{
"speedKmh": 130,
"hex": "#A855F7"
}
]TRACK_TPH_COLORS#
Track TPH color mapping for the track TPH layer: total trains-per-hour through a track (summed across all routes) to colors.
[
{
"tph": 0,
"hex": "#94a3b8"
},
{
"tph": 10,
"hex": "#3B82F6"
},
{
"tph": 20,
"hex": "#22C55E"
},
{
"tph": 30,
"hex": "#F97316"
},
{
"tph": 40,
"hex": "#F43F5E"
},
{
"tph": 50,
"hex": "#A855F7"
}
]ROUTE_UTILIZATION_COLORS#
Route utilization color mapping for the route stats utilization view: percent of offered train capacity in use to colors, interpolated between stops.
[
{
"percent": 0,
"hex": "#22C55E"
},
{
"percent": 25,
"hex": "#EAB308"
},
{
"percent": 50,
"hex": "#F97316"
},
{
"percent": 75,
"hex": "#cb0025"
},
{
"percent": 100,
"hex": "#ba00cb"
}
]ROUTE_RELIABILITY_COLORS#
Route reliability color mapping for the reliability panel: percent of arrivals that were on time to colors, interpolated between stops.
[
{
"percent": 0,
"hex": "#cb0025"
},
{
"percent": 50,
"hex": "#F97316"
},
{
"percent": 75,
"hex": "#EAB308"
},
{
"percent": 90,
"hex": "#84CC16"
},
{
"percent": 100,
"hex": "#22C55E"
}
]SECTION_DELAY_COLORS#
Section delay color mapping for the reliability map mode: seconds of lateness a route section adds to colors, interpolated between stops and clamped past the last one.
[
{
"seconds": 0,
"hex": "#22C55E"
},
{
"seconds": 10,
"hex": "#84CC16"
},
{
"seconds": 20,
"hex": "#EAB308"
},
{
"seconds": 40,
"hex": "#F97316"
},
{
"seconds": 60,
"hex": "#cb0025"
}
]TRACK_ELEVATIONS#
Elevation steps (in meters) and per-theme colors for the track elevations layer.
[
{
"depth": 20,
"colors": {
"light": "#0100cb",
"dark": "#0100cb"
},
"label": "20m"
},
{
"depth": 10,
"colors": {
"light": "#00b7cb",
"dark": "#00b7cb"
},
"label": "10m"
},
{
"depth": 0,
"colors": {
"light": "#14cb00",
"dark": "#14cb00"
},
"label": "0m"
},
{
"depth": -10,
"colors": {
"light": "#cbad00",
"dark": "#cbad00"
},
"label": "-10m"
},
{
"depth": -20,
"colors": {
"light": "#cb0025",
"dark": "#cc3119"
},
"label": "-20m"
},
{
"depth": -30,
"colors": {
"light": "#ba00cb",
"dark": "#ba00cb"
},
"label": "-30m"
}
]