Train Types
Register and modify train types
The trains API lets you create new train types or modify existing ones.
Register a Train Type#
registerTrainType stores your object as-is ā there are no defaults filled in for missing stats. Every field in `stats` below is required; leaving one out produces NaN costs or broken train physics rather than an error.
window.SubwayBuilderAPI.trains.registerTrainType({
id: 'regional-rail',
name: 'Regional Rail',
description: 'High-capacity regional rail for longer distances',
stats: {
// Performance
maxSpeed: 35.76, // m/s (~80 mph)
maxAcceleration: 0.9, // m/s²
maxDeceleration: 1.3, // m/s²
maxLateralAcceleration: 1.0, // m/s² ā passenger comfort, sets curve speed limits
maxCantMm: 180, // mm ā max applied cant (superelevation); full mechanics need the Cant beta feature
maxCantDeficiencyMm: 150, // mm ā max unbalanced cant this rolling stock tolerates
maxSlopePercentage: 3.5,
maxSpeedLocalStation: 15, // m/s through local (non-stopping) stations
crossoverSpeed: 6.7, // m/s (~15 mph) ā scissors-crossover speed cap
yardSpeedLimit: 4.47, // m/s (~10 mph) ā yard-track speed cap (optional, defaults to DEFAULT_YARD_SPEED_LIMIT)
stopTimeSeconds: 40, // dwell time at each station stop
turnaroundTimeSeconds: 120, // extra dwell where the train reverses direction (optional, defaults to 120)
// Track geometry
minTurnRadius: 88, // meters
minStationTurnRadius: 1400, // meters
parallelTrackSpacing: 4.27, // meters, edge-to-edge between parallel tracks
trackClearance: 1.2, // meters, outermost track edge to tunnel wall
// Rolling stock
minCars: 4,
maxCars: 12,
carsPerCarSet: 2,
capacityPerCar: 110,
carLength: 26, // meters
trainWidth: 3.25, // meters
minStationLength: 104, // meters ā typically carLength Ć minCars
maxStationLength: 312, // meters ā typically carLength Ć maxCars
// Costs
carCost: 3_500_000,
baseTrackCost: 25_000, // per meter, cut-and-cover double track
baseStationCost: 55_000_000, // cut-and-cover double-track station
trainOperationalCostPerHour: 500,
carOperationalCostPerHour: 35,
trackMaintenanceCostPerMeter: 300, // per year
stationMaintenanceCostPerYear: 160_000,
// Capacity
tphLimit: 30,
},
// compatibleTrackTypes: ['regional-rail'],
appearance: {
color: '#8b5cf6'
},
portalCost: 15_000_000, // surcharge per at-grade ā cut-and-cover portal
rampCost: 5_000_000, // surcharge per at-grade ā elevated ramp
maxOverpassSpan: 50, // max clear span (m) when bridging over a building
});Registering an id that already exists (including built-ins like heavy-metro) replaces that train type wholesale. To tweak a built-in, use `modifyTrainType` instead ā it deep-merges.
Train Type Properties#
| Property | Type | Description |
|---|---|---|
id | string | Unique identifier |
name | string | Display name |
description | string | Description text |
stats | object | Performance and cost stats (all fields required) |
appearance.color | string | Hex color for train |
elevationMultipliers | object | Optional. Per-elevation cost multiplier overrides ā see Custom Elevation Cost Multipliers |
allowAtGradeRoadCrossing | boolean | Optional. Street-running: tracks can run on roads at grade (trams). Alias: canCrossRoads |
allowGradeCrossing | boolean | Optional. Tracks can cross roads at grade via auto-spawned gated crossings ā see Grade Crossings |
gradeCrossingBaseCost | number | Optional. Base build cost per grade crossing |
gradeCrossingMaintenancePerDay | number | Optional. Maintenance per built crossing per in-game day |
gradeCrossingMaintenancePerYear | number | Optional. Alias for gradeCrossingMaintenancePerDay expressed as a real-world yearly figure (identical charge ā see Grade Crossings) |
gradeCrossingTphLimit | object | Optional. Per-road-class TPH caps for grade crossings |
gradeCrossingSpeedLimit | object | Optional. Per-road-class speed caps (m/s) through grade crossings ā see Grade Crossings |
gradeCrossingSpeedLimitApproachDistance | number | Optional. Meters of track around each crossing where the speed cap applies |
portalCost | number | Optional. Surcharge per tunnel portal |
rampCost | number | Optional. Surcharge per at-grade ā elevated ramp |
maxOverpassSpan | number | Optional. Maximum clear span (m) an elevated track can bridge OVER a building as a single overpass ā piers can't land on the footprint. Crossings longer than this fall back to demolition. Lighter vehicles justify longer spans. Omit or 0 to disable overpassing entirely (e.g. street-running trams) |
{/ | `compatibleTrackTypes` | string[] | Track types this train can use | /}
Stats Properties#
<Callout type="warning"> Cant only works with the Cant (Superelevation) Mechanics beta feature enabled (Escape menu ā Beta Features). With it off, the dynamic cant model (ramps, joint continuity, stop zones, crossing pins) never runs and the cant stats are hidden from the in-game train type panel ā curves are simply assumed statically banked at maxCantMm, with none of the transition machinery. </Callout>
| Stat | Type | Description |
|---|---|---|
maxSpeed | number | Top speed (m/s) |
maxAcceleration | number | Acceleration (m/s²) |
maxDeceleration | number | Deceleration (m/s²) |
maxLateralAcceleration | number | Maximum lateral acceleration (m/s²) for passenger comfort ā determines how fast trains take curves. Real systems use ~0.1 g = 1.0 m/s² |
maxCantMm | number? | Maximum applied cant (superelevation) in millimeters ā with the Cant Mechanics beta flag OFF curves are simply assumed banked at this value (v = ā((cant + deficiency) / 150 Ć radius), no transitions); with it ON the full dynamic model applies (ramps, joint continuity, stop zones, crossings). Millimeters on running track for this vehicle class. Curve speed follows v = ā((cant + deficiency) / 150 Ć radius) (1 m/s² of lateral acceleration ā” 150 mm). Built-in types use 60 (tram) to 180 (commuter rail; only realistic on controllable lines ā see the stop cap below). Omitted ā the moddable rule CANT_MAX_APPLIED_MM (150). Cant builds up gradually along the track ā at most CANT_RAMP_MM_PER_S (45 mm) per second of travel ā is pinned to 0 at joints with flat or pre-cant-era track, must swing through zero at reverse-curve joints, is pinned to 0 where another track crosses at grade (diamond crossings are flat), and caps at CANT_MAX_STOPPED_MM (150) within CANT_STOP_ZONE_LENGTH_M (200 m) of junctions and stations, where trains may come to a stop. Stations cap at CANT_STATION_MAX_MM (0) and street running at CANT_STREET_RUNNING_MAX_MM (0) |
maxCantDeficiencyMm | number? | Always applies to newly built tracks. Maximum cant deficiency (unbalanced superelevation) in millimeters this rolling stock tolerates ā how far under the balancing cant it may run. Built-in types use 80 (tram) to 150 (commuter rail); tilting stock justifies higher values. Omitted ā derived as maxLateralAcceleration Ć 150 |
maxSlopePercentage | number | Maximum allowed track slope (%) |
maxSpeedLocalStation | number | Speed (m/s) through local stations the train doesn't stop at |
crossoverSpeed | number | Flat speed cap (m/s) applied to tracks of type scissors-crossover. Real scissors crossovers are limited by their tight diverging-route geometry; applied as a constant instead of deriving from the polyline curve radius. Built-in types use 6.7 m/s (~15 mph) |
yardSpeedLimit | number? | Flat speed cap (m/s) applied to tracks of type yard ā berths, leads, and throat connectors. Applied as a ceiling on the finished speed profile, so a yard curve tight enough to demand less than this still gets the lower curve-derived limit. Optional ā omitted (every built-in type omits it) falls back to the moddable rule YARDS.DEFAULT_YARD_SPEED_LIMIT (4.47 m/s ā 10 mph, the classic posted yard limit). A value that is not a positive finite number falls back to the same rule, so a mod can't stall its trains in the yard |
stopTimeSeconds | number | Time spent stopped at each station (seconds) |
turnaroundTimeSeconds | number? | Extra time (seconds) a train needs to change direction, added on top of stopTimeSeconds (and the station type's extraDwellTime) at any stop where the train reverses ā a terminus or a mid-route turnback. Reversals are detected from the route geometry (the direction of travel flips at the platform or on a turnback tail past it); trains passing straight through a loop station pay nothing. Optional ā omitted falls back to 120 (all built-in types use 120) |
minTurnRadius | number | Minimum turn radius for tracks (meters) |
minStationTurnRadius | number | Minimum turn radius through stations (meters) |
parallelTrackSpacing | number | Edge-to-edge distance between parallel tracks (meters). Center-to-center = this + track width (1.435 m) |
trackClearance | number | Distance from the edge of the first/last track to the edge of the tunnel (meters) |
minCars | number | Minimum cars per train |
maxCars | number | Maximum cars per train |
carsPerCarSet | number | Cars purchased together |
capacityPerCar | number | Passengers per car |
carLength | number | Car length (meters) |
trainWidth | number | Train width (meters) |
minStationLength | number | Min station platform length (meters) |
maxStationLength | number | Max station platform length (meters) |
carCost | number | Cost per car |
baseTrackCost | number | Base cost per meter of cut-and-cover double track (scaled by elevation, water, and track-count multipliers) |
baseStationCost | number | Base cost of a cut-and-cover double-track station |
trainOperationalCostPerHour | number | Operating cost per train per hour |
carOperationalCostPerHour | number | Operating cost per car per hour |
trackMaintenanceCostPerMeter | number | Track maintenance cost per meter per year |
stationMaintenanceCostPerYear | number | Base station maintenance cost per station per year (parallel-track stations) |
tphLimit | number | Maximum trains-per-hour ceiling for any route using this train type. Routes that traverse a grade crossing may be capped lower based on the crossing's occupancy window |
Removed: scissorsCrossoverCost is no longer a stat. Scissors crossovers are now priced automatically as the cost of their straight side tracks (the crossing "X" tracks are free). If an old mod still sets it, the field is silently ignored.
Modify Existing Train Type#
Modify built-in train types. Overrides are deep-merged into the existing definition, so you only supply the fields you want to change:
// Make heavy metro faster and cheaper
window.SubwayBuilderAPI.trains.modifyTrainType('heavy-metro', {
stats: {
maxSpeed: 30, // Faster top speed
carCost: 2_000_000 // Cheaper cars
},
appearance: {
color: '#ef4444' // Red instead of blue
}
});Get Train Types#
// Get all train types
const allTypes = window.SubwayBuilderAPI.trains.getTrainTypes();
console.log(Object.keys(allTypes)); // ['heavy-metro', 'light-metro', ...]
// Get specific train type
const heavyMetro = window.SubwayBuilderAPI.trains.getTrainType('heavy-metro');
console.log(heavyMetro.stats.maxSpeed);Grade Crossings#
Train types that should cross roads at grade (commuter rail, etc.) opt in via allowGradeCrossing and provide cost / throughput parameters at the top level of the train type (not under stats):
window.SubwayBuilderAPI.trains.registerTrainType({
id: 'regional-rail',
name: 'Regional Rail',
stats: { /* ... */ },
// compatibleTrackTypes: ['regional-rail'],
appearance: { color: '#8b5cf6' },
allowGradeCrossing: true,
gradeCrossingBaseCost: 300_000, // multiplied by road class, lane count, angle
gradeCrossingMaintenancePerDay: 5_000, // charged per in-game day per built crossing
gradeCrossingTphLimit: { // combined-direction TPH cap, one per road class
highway: null, // null = forbidden; can't cross highways at grade
major: 8,
medium: 9,
minor: 10,
},
gradeCrossingSpeedLimit: { // m/s speed cap through crossings, per road class
major: 11.2, // ~25 mph on major streets
medium: 13.4, // ~30 mph
minor: 17.9, // ~40 mph; omit a class for no cap there
},
gradeCrossingSpeedLimitApproachDistance: 150, // cap applies 150m before and after each crossing
});| Property | Type | Description | |
|---|---|---|---|
allowGradeCrossing | boolean | If true, tracks for this type can cross roads at grade via auto-spawned gates. | |
gradeCrossingBaseCost | number | Base build cost per crossing. Multiplied internally by road-class (minor / medium / major / highway), lane count (1 / 2 / 4), and an oblique-angle factor capped at 3Ć. | |
gradeCrossingMaintenancePerDay | number | Maintenance per built crossing, charged once per in-game day. Expressed on the same compressed-economy time scale as track/station maintenance, so tune it against those figures rather than real-world yearly dollars. | |
gradeCrossingMaintenancePerYear | number | Alias for gradeCrossingMaintenancePerDay for definitions that cite real-world yearly dollars. The compressed economy charges yearly costs once per in-game day (the same scale fares are multiplied onto), so the two fields are charged identically; gradeCrossingMaintenancePerDay wins when both are set. | |
gradeCrossingTphLimit | `{ highway, major, medium, minor: number \ | null }` | Combined-direction TPH ceiling on every grade crossing this train type uses, broken out by the crossing's road class. Busier roads typically get a tighter cap. A null entry means crossings of that road class are forbidden ā no crossing geometry is ever generated, and the track can't be built across such roads at grade. Required for train types with allowGradeCrossing: true; if the whole field is omitted, no per-crossing TPH limit is enforced. |
gradeCrossingSpeedLimit | { highway?, major?, medium?, minor?: number } | Speed limit (m/s) through grade crossings, per road class of the crossing. Applies to the rail within gradeCrossingSpeedLimitApproachDistance meters of the crossing point, measured along the track in both directions and across welded joints onto connected tracks ā rail farther than that keeps full line speed, even on the crossing's own track. Sparse and fully optional: omit a road class (or the whole field) and speeds at those crossings are unchanged, so train types written before this field existed behave exactly as before. | |
gradeCrossingSpeedLimitApproachDistance | number | How many meters of rail on each side of a grade crossing the gradeCrossingSpeedLimit covers. Measured along the track from the crossing point; where the window runs off a track end it continues across the joint onto whatever tracks connect there (and on through tracks shorter than the remainder). Omitted ā 0, meaning only the track segment containing the crossing itself is capped. The built-in Light Rail type uses 150. | |
portalCost | number | Base surcharge per portal ā the structural transition point at every underground boundary the blueprint crosses, excluding the deep-bore entry (which is covered by TBM mobilization). Portals fire at two thresholds: -5m (open ā cut-and-cover) and -10m (cut-and-cover ā standard tunnel). Each is charged once per crossing, whether at a TG junction or mid-TG. Scaled by the track-count multiplier of the connecting trench (single = 0.75, parallel = 1.0, quad = 1.5) ā same multipliers as TUNNEL.SINGLE_MULTIPLIER / TUNNEL.QUAD_MULTIPLIER. Omit (or set to 0) to disable the surcharge. | |
rampCost | number | Base surcharge per ramp ā the structural transition point where a blueprint track crosses the at-grade ā elevated boundary. Same detection model as portalCost (TG junctions and mid-TG transitions) and the same track-count scaling on the elevated-side TG. Real ramps need an abutment and an inclined approach, so the cost doesn't scale with viaduct length. Omit (or set to 0) to disable the surcharge. |
Custom Elevation Cost Multipliers#
Override elevation cost multipliers for specific train types:
window.SubwayBuilderAPI.trains.registerTrainType({
id: 'streetcar',
name: 'Streetcar',
description: 'Light rail for street running',
stats: {
baseTrackCost: 15_000
// ... other stats
},
// compatibleTrackTypes: ['streetcar'],
appearance: { color: '#f59e0b' },
// Override elevation multipliers
elevationMultipliers: {
AT_GRADE: 0.5, // Cheaper at grade (designed for streets)
TRENCHED: 0.4, // Even cheaper in open-top trench
RAMP: 0.4, // Same as trenched ā open low embankment
ELEVATED: 1.8, // More expensive elevated viaduct
CUT_AND_COVER: 1.2 // Slightly more expensive tunnels
// DEEP_BORE and STANDARD_TUNNEL use global defaults
}
});Elevation types:
| Type | Depth Range | Description |
|---|---|---|
DEEP_BORE | < -24m | Deep tunnels |
STANDARD_TUNNEL | -24m to -10m | Standard tunnels |
CUT_AND_COVER | -10m to -5m | Shallow covered tunnels |
TRENCHED | -5m to -1m | Open-top trench, no roof |
AT_GRADE | -1m to 0m | Ground level |
RAMP | 1m to 4m | Low open ramp / embankment |
ELEVATED | ā„ 5m | Above-ground viaduct |
Boundary values themselves belong to the structural types: elev ⤠-5 is CUT_AND_COVER (so -5m is cut-and-cover, not trenched) and elev ℠5 is ELEVATED (so 5m is elevated, not ramp). The ramp band ends just before 5.
TRENCHED, AT_GRADE, and RAMP are all "open air" ā moving between any of them requires no portal or viaduct-ramp surcharge. Portals (portalCost) fire at the two cut-tunnel boundaries: open ā CUT_AND_COVER (-5m) and CUT_AND_COVER ā STANDARD_TUNNEL (-10m). The STANDARD_TUNNEL ā DEEP_BORE boundary is not a portal ā deep-bore segments pay their structural entry through the TBM-mobilization mechanic instead. Viaduct ramps (rampCost) fire when crossing into ELEVATED.
Construction over water is not supported in TRENCHED, AT_GRADE, or RAMP elevations (info graphics show "N/A" in the water column).
At-Grade Road Crossing#
Enable at-grade street running for trams/streetcars:
window.SubwayBuilderAPI.trains.registerTrainType({
id: 'streetcar',
name: 'Streetcar',
description: 'Light rail for street running',
stats: { /* ... */ },
// compatibleTrackTypes: ['streetcar'],
appearance: { color: '#f59e0b' },
// Allow tracks to run on roads at grade
allowAtGradeRoadCrossing: true
// Or use the alias: canCrossRoads: true
});Note: Runways and taxiways still block at-grade tracks regardless of this setting.
Example: Light Rail System#
<Callout type="warning"> The game now ships a built-in light-rail train type (Light Rail, modeled after the Siemens S700). Registering a train type with id: 'light-rail' replaces that built-in wholesale ā use your own id (as below) unless replacing it is what you want, or tweak the built-in with modifyTrainType('light-rail', ...). </Callout>
window.SubwayBuilderAPI.trains.registerTrainType({
id: 'my-light-rail',
name: 'My Light Rail',
description: 'Modern light rail for urban transit',
stats: {
maxSpeed: 25,
maxAcceleration: 1.2,
maxDeceleration: 1.3,
maxLateralAcceleration: 0.9,
maxSlopePercentage: 6,
maxSpeedLocalStation: 12,
crossoverSpeed: 6.7,
stopTimeSeconds: 20,
turnaroundTimeSeconds: 90,
minTurnRadius: 25,
minStationTurnRadius: 100,
parallelTrackSpacing: 3.0,
trackClearance: 0.75,
minCars: 1,
maxCars: 4,
carsPerCarSet: 1,
capacityPerCar: 80,
carLength: 20,
trainWidth: 2.65,
minStationLength: 20,
maxStationLength: 80,
carCost: 1_500_000,
baseTrackCost: 20_000,
baseStationCost: 15_000_000,
trainOperationalCostPerHour: 200,
carOperationalCostPerHour: 30,
trackMaintenanceCostPerMeter: 150,
stationMaintenanceCostPerYear: 40_000,
tphLimit: 50,
},
// compatibleTrackTypes: ['my-light-rail'],
appearance: {
color: '#10b981'
},
elevationMultipliers: {
AT_GRADE: 0.7,
ELEVATED: 1.5
},
allowAtGradeRoadCrossing: true,
portalCost: 4_000_000, // cheaper portal since light-rail uses smaller trench
rampCost: 2_000_000, // cheaper ramp since light-rail uses smaller viaduct
maxOverpassSpan: 80, // lighter axle loads carry longer economical spans
});