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.

javascript
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#

PropertyTypeDescription
idstringUnique identifier
namestringDisplay name
descriptionstringDescription text
statsobjectPerformance and cost stats (all fields required)
appearance.colorstringHex color for train
elevationMultipliersobjectOptional. Per-elevation cost multiplier overrides — see Custom Elevation Cost Multipliers
allowAtGradeRoadCrossingbooleanOptional. Street-running: tracks can run on roads at grade (trams). Alias: canCrossRoads
allowGradeCrossingbooleanOptional. Tracks can cross roads at grade via auto-spawned gated crossings — see Grade Crossings
gradeCrossingBaseCostnumberOptional. Base build cost per grade crossing
gradeCrossingMaintenancePerDaynumberOptional. Maintenance per built crossing per in-game day
gradeCrossingMaintenancePerYearnumberOptional. Alias for gradeCrossingMaintenancePerDay expressed as a real-world yearly figure (identical charge — see Grade Crossings)
gradeCrossingTphLimitobjectOptional. Per-road-class TPH caps for grade crossings
gradeCrossingSpeedLimitobjectOptional. Per-road-class speed caps (m/s) through grade crossings — see Grade Crossings
gradeCrossingSpeedLimitApproachDistancenumberOptional. Meters of track around each crossing where the speed cap applies
portalCostnumberOptional. Surcharge per tunnel portal
rampCostnumberOptional. Surcharge per at-grade ↔ elevated ramp
maxOverpassSpannumberOptional. 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>

StatTypeDescription
maxSpeednumberTop speed (m/s)
maxAccelerationnumberAcceleration (m/s²)
maxDecelerationnumberDeceleration (m/s²)
maxLateralAccelerationnumberMaximum lateral acceleration (m/s²) for passenger comfort — determines how fast trains take curves. Real systems use ~0.1 g = 1.0 m/s²
maxCantMmnumber?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)
maxCantDeficiencyMmnumber?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
maxSlopePercentagenumberMaximum allowed track slope (%)
maxSpeedLocalStationnumberSpeed (m/s) through local stations the train doesn't stop at
crossoverSpeednumberFlat 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)
yardSpeedLimitnumber?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
stopTimeSecondsnumberTime spent stopped at each station (seconds)
turnaroundTimeSecondsnumber?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)
minTurnRadiusnumberMinimum turn radius for tracks (meters)
minStationTurnRadiusnumberMinimum turn radius through stations (meters)
parallelTrackSpacingnumberEdge-to-edge distance between parallel tracks (meters). Center-to-center = this + track width (1.435 m)
trackClearancenumberDistance from the edge of the first/last track to the edge of the tunnel (meters)
minCarsnumberMinimum cars per train
maxCarsnumberMaximum cars per train
carsPerCarSetnumberCars purchased together
capacityPerCarnumberPassengers per car
carLengthnumberCar length (meters)
trainWidthnumberTrain width (meters)
minStationLengthnumberMin station platform length (meters)
maxStationLengthnumberMax station platform length (meters)
carCostnumberCost per car
baseTrackCostnumberBase cost per meter of cut-and-cover double track (scaled by elevation, water, and track-count multipliers)
baseStationCostnumberBase cost of a cut-and-cover double-track station
trainOperationalCostPerHournumberOperating cost per train per hour
carOperationalCostPerHournumberOperating cost per car per hour
trackMaintenanceCostPerMeternumberTrack maintenance cost per meter per year
stationMaintenanceCostPerYearnumberBase station maintenance cost per station per year (parallel-track stations)
tphLimitnumberMaximum 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:

javascript
// 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#

javascript
// 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):

javascript
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
});
PropertyTypeDescription
allowGradeCrossingbooleanIf true, tracks for this type can cross roads at grade via auto-spawned gates.
gradeCrossingBaseCostnumberBase 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Ɨ.
gradeCrossingMaintenancePerDaynumberMaintenance 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.
gradeCrossingMaintenancePerYearnumberAlias 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.
gradeCrossingSpeedLimitApproachDistancenumberHow 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.
portalCostnumberBase 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.
rampCostnumberBase 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:

javascript
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:

TypeDepth RangeDescription
DEEP_BORE< -24mDeep tunnels
STANDARD_TUNNEL-24m to -10mStandard tunnels
CUT_AND_COVER-10m to -5mShallow covered tunnels
TRENCHED-5m to -1mOpen-top trench, no roof
AT_GRADE-1m to 0mGround level
RAMP1m to 4mLow open ramp / embankment
ELEVATED≄ 5mAbove-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:

javascript
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>

javascript
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
});