Types

Data types returned by gameState getters and carried by hook payloads.

Station#

A stop derived from a station track group; a group whose platforms are all yard berths forms none. The store's setStations action fills nearbyStations and backfills maxCars.

Used by api.hooks.onStationBuilt, api.hooks.onTrainArrivedAtStation, api.hooks.onStationSelected, api.gameState.getStations.

ts
type Station = {
    /** Station id; equals the owning track group's id. */
    id: string;
    /** Competitive multiplayer company id inherited from the track group. */
    companyId?: string;
    /** Display name: `'Turnaround'` for a pocket station. */
    name: string;
    /** [lng, lat] midpoint of the track group's centerline. */
    coords: [number, number];
    /** Ids of the track group's tracks. */
    trackIds: string[];
    /** Owning track group id; same value as `id`. */
    trackGroupId: string;
    /** Ids of the group's station track nodes (platform lines), yard berths excluded. */
    stNodeIds: string[];
    /** `'constructed'` | `'blueprint'` | `'preview'`. */
    buildType: BuildType;
    /** Ids of routes whose stNodes include one of this station's. */
    routeIds: string[];
    /** Declared TrainTypeId; the station builder sets no value for it. */
    trainType?: TrainTypeId;
    /** StationTypeId; kept from the previous derivation, else `'standard'`. */
    stationType?: StationTypeId;
    /** Stations reachable on foot with `walkingTime` in seconds … */
    nearbyStations?: { stationId: string; walkingTime: number }[];
    /** Unix time in ms (`Date.now()`) of the first derivation, kept across rebuilds. */
    createdAt?: number;
    /** Cars the platform fits, rounded down to whole car sets … */
    maxCars?: number;
    /** True for a pocket (zero-platform) station where routes reverse and nobody boards. */
    noBoarding?: boolean;
};
FieldTypeDefaultDescription
idstringrequiredStation id; equals the owning track group's id.
companyIdstringnoneCompetitive multiplayer company id inherited from the track group; undefined otherwise.
namestringrequiredDisplay name: 'Turnaround' for a pocket station; else kept from the previous derivation; else generated from nearby road names, 'Station' when no road name qualifies; a rename through the store's updateStationName overwrites it.
coords[number, number]required[lng, lat] midpoint of the track group's centerline.
trackIdsstring[]requiredIds of the track group's tracks.
trackGroupIdstringrequiredOwning track group id; same value as id.
stNodeIdsstring[]requiredIds of the group's station track nodes (platform lines), yard berths excluded.
buildTypeBuildTyperequired'constructed' or 'blueprint' or 'preview'; taken from the first station track node.
routeIdsstring[]requiredIds of routes whose stNodes include one of this station's; getStations({ includeTempRoutes: false }) drops temp routes from it.
trainTypeTrainTypeIdnoneDeclared TrainTypeId; the station builder sets no value for it.
stationTypeStationTypeIdnoneStationTypeId; kept from the previous derivation, else 'standard'; the station details panel's type picker sets it through the store's updateStationType.
nearbyStations{ stationId: string; walkingTime: number }[]noneStations reachable on foot with walkingTime in seconds (walk-graph or straight-line time divided by the target's walk speed multiplier), within the target's transfer radius (the station type's … See Station.nearbyStations below.
createdAtnumbernoneUnix time in ms (Date.now()) of the first derivation, kept across rebuilds.
maxCarsnumbernoneCars the platform fits, rounded down to whole car sets, at least one car set and at most the train type's maxCars; from platform length and the group's track type; absent when the centerline measures 0 m.
noBoardingbooleannoneTrue for a pocket (zero-platform) station where routes reverse and nobody boards; absent otherwise.

Station.nearbyStations#

Stations reachable on foot with walkingTime in seconds (walk-graph or straight-line time divided by the target's walk speed multiplier), within the target's transfer radius (the station type's transferRadiusOverride, else PATHFINDING_RULES.MAX_TRANSFER_WALKING_TIME 600 s times its transferRadiusMultiplier) and a 6000 m search box. Sorted ascending.

Route#

A transit line: ordered station track nodes, the paths between them, timings, schedule and bullet styling.

Used by api.hooks.onRouteCreated, api.gameState.getRoutes.

ts
type Route = {
    /** UUID assigned at creation. */
    id: string;
    /** Competitive multiplayer company id; absent otherwise. */
    companyId?: string;
    /** Null on player routes; the parent route id on a temporary route created for one train … */
    tempParentId: string | null;
    /** On yard temp routes: `'pull-out'` (berth to route) or `'put-in'` (route to berth). */
    yardDeadhead?: 'pull-out' | 'put-in';
    /** Put-in temp routes: berth slot the train parks on, 0 = deepest. */
    parkSlot?: number;
    /** Short label shown in the route bullet. */
    bullet: string;
    /** Optional long name. */
    fullName?: string;
    /** Station track nodes in stop order; empty from route creation (a duplicate copies its source's). */
    stNodes: StationTrackNode[];
    /** Consecutive stNode pairs with the track path between them. */
    stCombos: RouteStCombo[];
    /** Player-placed via points the path must run through. */
    waypoints?: RouteWaypoint[];
    /** Route color; CSS color string. */
    color: string;
    /** Bullet text color; CSS color string. */
    textColor: string;
    /** True for a two-tone bullet; undefined when off. */
    bordered?: boolean;
    /** Per stNode visit: `arrivalTime` and `departureTime` in seconds from route start. */
    stComboTimings: RouteStComboTiming[];
    /** A flat train count; 0 at creation. */
    idealTrainCount: number;
    /** Trains per demand period (`highDemand`, `mediumDemand`, `lowDemand`, ...). */
    trainSchedule?: RouteTrainSchedule;
    /** Timetable scheduling: `mode` (`'frequency'` | `'timetable'`) and `periods`. */
    timetableSchedule?: RouteTimetableSchedule;
    /** Bullet shape: `'circle'` | `'diamond'` | `'square'` | `'rounded-square'` | `'triangle'`. */
    shape?: RouteShape;
    /** Bullet font style: `'mono'` | `'bolder'` | `'lighter'` | `'italic'`. */
    font?: RouteBulletFont;
    /** Bullet label size: `'small'` shrinks the glyphs within the badge. */
    textSize?: RouteBulletTextSize;
    /** Diagram label style: `'bullet'` | `'text'`. */
    catStyle?: RouteCatStyle;
    /** Alternate-platform terminus entries: `arrivalStComboIndex`, `departureStComboIndex`, `stationId`, `platforms`. */
    terminusPlatformAlternates?: TerminusPlatformAlternates[];
    /** Player allowlist of platform stNode ids per terminus station id. */
    allowedTerminusPlatforms?: { [stationId: string]: string[] } | null;
    /** Cars per train; the train type's `defaultCars` (falling back to `minCars`) at creation. */
    carsPerTrain?: number;
    /** TrainTypeId the route runs; DEFAULT_TRAIN_TYPE_ID when none was chosen. */
    trainType?: TrainTypeId;
    /** Single yard id from route creation … */
    yardId?: string;
    /** Supplying yards, each `{ yardId, share }` with `share` the fraction (0..1] of that yard's berths. */
    yardAssignments?: RouteYardAssignment[];
    /** Unix time in ms (`Date.now()`) at creation or duplication. */
    createdAt?: number;
    /** Set while a track edit, or a save load with missing tracks, left the route without a path. */
    disruption?: RouteDisruption | null;
};
FieldTypeDefaultDescription
idstringrequiredUUID assigned at creation.
companyIdstringnoneCompetitive multiplayer company id; absent otherwise.
tempParentIdstring or nullrequiredNull on player routes; the parent route id on a temporary route created for one train (a reroute after a route edit, or a yard pull-out or put-in).
yardDeadhead'pull-out' or 'put-in'noneOn yard temp routes: 'pull-out' (berth to route) or 'put-in' (route to berth); absent otherwise.
parkSlotnumbernonePut-in temp routes: berth slot the train parks on, 0 = deepest; when absent, berth claims and the parking check use slot 0 and the arriving train stops centered on the berth.
bulletstringrequiredShort label shown in the route bullet.
fullNamestringnoneOptional long name. A route duplicated from another gets <fullName or bullet> 2.
stNodesStationTrackNode[]requiredStation track nodes in stop order; empty from route creation (a duplicate copies its source's); set to the confirmed stop list when a new or edited route is confirmed; route regeneration drops stops whose station no longer exists.
stCombosRouteStCombo[]requiredConsecutive stNode pairs with the track path between them; empty from route creation (a duplicate copies its source's); rebuilt whenever the route's path is regenerated; emptied while the route is disrupted.
waypointsRouteWaypoint[]nonePlayer-placed via points the path must run through; dropped when their track or stop pair goes away.
colorstringrequiredRoute color; CSS color string.
textColorstringrequiredBullet text color; CSS color string.
borderedbooleannoneTrue for a two-tone bullet; undefined when off.
stComboTimingsRouteStComboTiming[]requiredPer stNode visit: arrivalTime and departureTime in seconds from route start; estimated by route regeneration from a tick simulation and reused while path and speeds are unchanged.
idealTrainCountnumberrequiredA flat train count; 0 at creation. Train spawning ignores it. With no trainSchedule, the train scheduler's yard cap falls back to it, and so do yard fleet sizing and track-usage estimates outside timetable mode.
trainScheduleRouteTrainSchedulenoneTrains per demand period (highDemand, mediumDemand, lowDemand, ...); absent (outside timetable mode) means a spawn target of 0 at every hour.
timetableScheduleRouteTimetableSchedulenoneTimetable scheduling: mode ('frequency' or 'timetable') and periods; absent until the route is first switched to timetable mode; switching back keeps it with mode: 'frequency'.
shapeRouteShapenoneBullet shape: 'circle' or 'diamond' or 'square' or 'rounded-square' or 'triangle'; route creation writes 'circle' when none is chosen, and an absent value draws as a circle.
fontRouteBulletFontnoneBullet font style: 'mono' or 'bolder' or 'lighter' or 'italic'; undefined for the default bold face.
textSizeRouteBulletTextSizenoneBullet label size: 'small' shrinks the glyphs within the badge; undefined draws at the default size.
catStyleRouteCatStylenoneDiagram label style: 'bullet' or 'text'; absent means automatic by name length.
terminusPlatformAlternatesTerminusPlatformAlternates[]noneAlternate-platform terminus entries: arrivalStComboIndex, departureStComboIndex, stationId, platforms; one per terminus the route visits once and reverses at, when another constructed platform there is reachable within RULES.MAX_TERMINUS_ALTERNATE_DETOUR of the canonical paths.
allowedTerminusPlatforms{ [stationId: string]: string[] } or nullnonePlayer allowlist of platform stNode ids per terminus station id; a missing key, an empty list, or a list matching none of the terminus's alternates allows every platform.
carsPerTrainnumbernoneCars per train; the train type's defaultCars (falling back to minCars) at creation.
trainTypeTrainTypeIdnoneTrainTypeId the route runs; DEFAULT_TRAIN_TYPE_ID when none was chosen.
yardIdstringnoneSingle yard id from route creation; read as one assignment with share 1 only when yardAssignments is absent; the store's yard-assignment actions clear it when they write yardAssignments.
yardAssignmentsRouteYardAssignment[]noneSupplying yards, each { yardId, share } with share the fraction (0..1] of that yard's berths; absent or empty on yardless routes.
createdAtnumbernoneUnix time in ms (Date.now()) at creation or duplication.
disruptionRouteDisruption or nullnoneSet while a track edit, or a save load with missing tracks, left the route without a path: reason text and sinceSeconds game time; null or absent otherwise.

Route Schedules#

RouteTrainSchedule#

ts
type RouteTrainSchedule = {
    /** Number of trains for high demand periods */
    highDemand: number;
    /** Number of trains for medium demand periods */
    mediumDemand: number;
    /** Number of trains for low demand periods */
    lowDemand: number;
    /** Number of trains for very low demand periods. */
    veryLowDemand?: number;
};
FieldTypeDefaultDescription
highDemandnumberrequiredNumber of trains for high demand periods
mediumDemandnumberrequiredNumber of trains for medium demand periods
lowDemandnumberrequiredNumber of trains for low demand periods
veryLowDemandnumbernoneNumber of trains for very low demand periods. Optional for backwards compatibility with old saves; falls back to lowDemand when absent.

RouteTimetableSchedule#

ts
type RouteTimetableSchedule = {
    /** `'frequency'` (train counts per demand tier from `trainSchedule`) or `'timetable'` (headways per period). */
    mode: SchedulingMode;
    /** Time periods with their target headways. */
    periods: ScheduleTimePeriod[];
};
FieldTypeDefaultDescription
modeSchedulingModerequired'frequency' (train counts per demand tier from trainSchedule) or 'timetable' (headways per period).
periodsScheduleTimePeriod[]requiredTime periods with their target headways.

ScheduleTimePeriod#

ts
type ScheduleTimePeriod = {
    /** Period id. */
    id: string;
    /** Display name. */
    name: string;
    /** First hour of the period, inclusive. */
    startHour: number;
    /** Hour the period ends, exclusive. */
    endHour: number;
    /** Target headway in seconds. */
    headwaySeconds: number;
};
FieldTypeDefaultDescription
idstringrequiredPeriod id.
namestringrequiredDisplay name.
startHournumberrequiredFirst hour of the period, inclusive.
endHournumberrequiredHour the period ends, exclusive.
headwaySecondsnumberrequiredTarget headway in seconds. 0 = no service.

RouteScheduleSnapshot#

Copy of a route's schedule fields passed to onScheduleChange.

Built by pickRouteSchedule: trainSchedule is a shallow copy and timetableSchedule.periods is an array of copied period objects, so editing the snapshot does not touch the store.

Used by api.hooks.onScheduleChange.

ts
type RouteScheduleSnapshot = {
    /** Number of trains the route is set to run. */
    idealTrainCount: number;
    /** Copy of the demand-tier train counts (`highDemand`, `mediumDemand`, `lowDemand`, `veryLowDemand`). */
    trainSchedule?: RouteTrainSchedule;
    /** Copy of the timetable `mode` and `periods` (each period copied). */
    timetableSchedule?: RouteTimetableSchedule;
};
FieldTypeDefaultDescription
idealTrainCountnumberrequiredNumber of trains the route is set to run; trains.
trainScheduleRouteTrainSchedulenoneCopy of the demand-tier train counts (highDemand, mediumDemand, lowDemand, veryLowDemand); undefined when the route has none.
timetableScheduleRouteTimetableSchedulenoneCopy of the timetable mode and periods (each period copied); undefined when the route has none.

Route Parts#

StationTrackNode#

A node where two station tracks connect

ts
type StationTrackNode = {
    /** Station track node id; routes list these as stops (`Route.stNodes`). */
    id: string;
    /** [lng, lat] of the node. */
    center: [number, number];
    /** The track IDs that connect to this node */
    trackIds: [string, string];
    /** Build state of the node. */
    buildType: BuildType;
    /** True when this node joins two yard tracks, or two station tracks the player designated as a yard berth … */
    isYard?: boolean;
};
FieldTypeDefaultDescription
idstringrequiredStation track node id; routes list these as stops (Route.stNodes).
center[number, number]required[lng, lat] of the node.
trackIds[string, string]requiredThe track IDs that connect to this node
buildTypeBuildTyperequiredBuild state of the node.
isYardbooleannoneTrue when this node joins two yard tracks, or two station tracks the player designated as a yard berth (Track.yardBerth). See StationTrackNode.isYard below.

StationTrackNode.isYard#

True when this node joins two yard tracks, or two station tracks the player designated as a yard berth (Track.yardBerth). Yard nodes exist only as deadhead endpoints (trains pulling out of or returning to a yard). They are never route stops, never count as station platforms, and the route editor renders them only as disabled "yard" markers.

RouteStCombo#

ts
type RouteStCombo = {
    /** The ID of the starting station track node */
    startStNodeId: string;
    /** The ID of the ending station track node */
    endStNodeId: string;
    /** The path of the route between the two station track nodes */
    path: TrackRoutePath[];
    /** Summed `length` of the path; m. */
    distance: number;
};
FieldTypeDefaultDescription
startStNodeIdstringrequiredThe ID of the starting station track node
endStNodeIdstringrequiredThe ID of the ending station track node
pathTrackRoutePath[]requiredThe path of the route between the two station track nodes
distancenumberrequiredSummed length of the path; m.

TrackRoutePath#

ts
type TrackRoutePath = {
    /** Track id. */
    trackId: string;
    /** If trains on this route travel in the opposite direction of the track */
    reversed: boolean;
    /** Track length; m. */
    length: number;
    /** The IDs of the signals on part or all of this path */
    signals: TrackRoutePathSignal[];
};
FieldTypeDefaultDescription
trackIdstringrequiredTrack id.
reversedbooleanrequiredIf trains on this route travel in the opposite direction of the track
lengthnumberrequiredTrack length; m.
signalsTrackRoutePathSignal[]requiredThe IDs of the signals on part or all of this path

TrackRoutePathSignal#

ts
type TrackRoutePathSignal = {
    /** Signal id. */
    signalId: string;
    /** Part of the track the signal covers: `{ start, end }` progress in m, or `'all'`. */
    areaCovered: SignalTrackCoverage;
};
FieldTypeDefaultDescription
signalIdstringrequiredSignal id.
areaCoveredSignalTrackCoveragerequiredPart of the track the signal covers: { start, end } progress in m, or 'all'.

RouteStComboTiming#

To track the time it takes to reach a station from when the route started

ts
type RouteStComboTiming = {
    /** Station track node of this stop. */
    stNodeId: string;
    /** The order the station track node is in the route so if a train visits an stNode more than once they don't … */
    stNodeIndex: number;
    /** Seconds from route start to arrival at this stop. */
    arrivalTime: number;
    /** Seconds from route start to departure from this stop. */
    departureTime: number;
};
FieldTypeDefaultDescription
stNodeIdstringrequiredStation track node of this stop.
stNodeIndexnumberrequiredThe order the station track node is in the route so if a train visits an stNode more than once they don't get mxed up
arrivalTimenumberrequiredSeconds from route start to arrival at this stop.
departureTimenumberrequiredSeconds from route start to departure from this stop.

RouteWaypoint#

A player-placed via point on a route leg: pathfinding between fromStNodeId

and toStNodeId (an ordered pair of adjacent stops on the route cycle) must traverse trackId. Waypoints on the same leg are visited in the order they were added. A waypoint whose track is demolished, or whose two stops are no longer adjacent after a stop edit, is dropped on the next route regeneration.

ts
type RouteWaypoint = {
    /** Waypoint id. */
    id: string;
    /** Snapped position on the track, where the waypoint marker renders. */
    coords: [number, number];
    /** Track the leg must run over. */
    trackId: string;
    /** Stop the leg starts at. */
    fromStNodeId: string;
    /** Stop the leg ends at. */
    toStNodeId: string;
};
FieldTypeDefaultDescription
idstringrequiredWaypoint id.
coords[number, number]requiredSnapped position on the track, where the waypoint marker renders.
trackIdstringrequiredTrack the leg must run over.
fromStNodeIdstringrequiredStop the leg starts at.
toStNodeIdstringrequiredStop the leg ends at.

TerminusPlatformAlternates#

Paired alternate paths for a multi-platform terminus station. Each platform

choice substitutes BOTH the stCombo arriving at the terminus AND the next stCombo departing from it, keeping the chain stCombos[i].endStNodeId === stCombos[i+1].startStNodeId intact.

For the loop terminus the arrival is the last stCombo and departure is stCombos[0] (wraps around).

ts
type TerminusPlatformAlternates = {
    /** Index in route.stCombos of the stCombo arriving at the terminus */
    arrivalStComboIndex: number;
    /** Index in route.stCombos of the stCombo departing from the terminus */
    departureStComboIndex: number;
    /** ID of the terminus station */
    stationId: string;
    /** Each entry is one platform the train can use at this terminus. */
    platforms: Array<{…}>;
};
FieldTypeDefaultDescription
arrivalStComboIndexnumberrequiredIndex in route.stCombos of the stCombo arriving at the terminus
departureStComboIndexnumberrequiredIndex in route.stCombos of the stCombo departing from the terminus
stationIdstringrequiredID of the terminus station
platformsArray<{ platformStNodeId: string; arrival: { startStNodeId: string; endStNodeId: string; path: TrackRoutePath[]; distance: number }; departure: { startStNodeId: string; endStNodeId: string; path: TrackRoutePath[]; distance: number }; }>requiredEach entry is one platform the train can use at this terminus. The first entry is always the canonical platform (matches the actual stCombos[i] and stCombos[i+1] content). Trailing entries are the alternates.

RouteYardAssignment#

One supplying-yard assignment on a route: which yard, and how much of it.

ts
type RouteYardAssignment = {
    /** Supplying yard id. */
    yardId: string;
    /** Fraction (0..1] of the yard's berths this route may claim. */
    share: number;
};
FieldTypeDefaultDescription
yardIdstringrequiredSupplying yard id.
sharenumberrequiredFraction (0..1] of the yard's berths this route may claim; 1 = whole yard.

RouteDisruption#

Set when a route's path could not be rebuilt after a track edit (a track it

ran on was deleted or flipped with no alternative path). A disrupted route keeps its stops, schedule and styling but has no stCombos, spawns no trains, and is automatically re-pathed on every subsequent track change until a valid path exists again (see regenerateRoutes / setTracks).

ts
type RouteDisruption = {
    /** Human-readable cause, e.g. */
    reason: string;
    /** Game time (elapsedSeconds) when the disruption began */
    sinceSeconds: number;
};
FieldTypeDefaultDescription
reasonstringrequiredHuman-readable cause, e.g. "No path found between Foo and Bar"
sinceSecondsnumberrequiredGame time (elapsedSeconds) when the disruption began

FareGroup#

A set of routes priced by one fare system and one transfer policy. Reconciliation keeps every route whose tempParentId is null in exactly one group.

Used by api.hooks.onFareGroupsChanged, api.actions.addFareGroup, api.actions.updateFareGroup, api.gameState.getFareGroups.

ts
type FareGroup = {
    /** Fare group id. */
    id: string;
    /** Player-editable display name; empty string shows the localized default. */
    name: string;
    /** `'flat'` | `'route'` | `'distance'` | `'zone'`. */
    fareSystem: FareSystem;
    /** Fare when `fareSystem` is `'flat'`: charged once per journey in the group … */
    flatFare: number;
    /** Per-route price keyed by route id when `fareSystem` is `'route'`. */
    routeFares: Record<string, number>;
    /** Ordered route ids in the group. */
    routeIds: string[];
    /** `'all-paid'` | `'count-within-group'` | `'count-all-groups'` | `'free-within-group'` (legacy … */
    transferPolicy: FareTransferPolicy;
    /** Distance system: flag-fall charge, paid once per trip in the group … */
    boardingCharge: number;
    /** Distance system: price per km ridden; $/km. */
    perKmRate: number;
    /** Distance system: `'straight-line'` or `'track'`. */
    distanceMode: FareDistanceMode;
    /** Distance and zone systems: maximum fare per trip. */
    fareCap: number;
    /** Zone system: ordered fare zones; index + 1 is the displayed zone number and the ring order. */
    zones: FareZone[];
    /** Zone system: `'count'` (pay per distinct zone) or `'combination'` (pay the contiguous span … */
    zonePricingMode: FareZonePricingMode;
    /** Zone system: fare for the first charged zone. */
    zoneBaseFare: number;
    /** Zone system: amount added per additional charged zone. */
    zonePerZoneFare: number;
    /** Deprecated early-save flag superseded by `transferPolicy`. */
    chargeOnInterGroupTransfer?: boolean;
    /** Off-peak discount. */
    offPeakFareMultiplier?: number;
};
FieldTypeDefaultDescription
idstringrequiredFare group id.
namestringrequiredPlayer-editable display name; empty string shows the localized default.
fareSystemFareSystemrequired'flat' or 'route' or 'distance' or 'zone'.
flatFarenumberrequiredFare when fareSystem is 'flat': charged once per journey in the group (waived on a credited 'count-all-groups' entry), or on every boarding under 'all-paid'; $.
routeFaresRecord<string, number>requiredPer-route price keyed by route id when fareSystem is 'route'; $; a route without an entry uses flatFare.
routeIdsstring[]requiredOrdered route ids in the group.
transferPolicyFareTransferPolicyrequired'all-paid' or 'count-within-group' or 'count-all-groups' or 'free-within-group' (legacy, read as 'count-within-group').
boardingChargenumberrequiredDistance system: flag-fall charge, paid once per trip in the group, waived when the trip enters the group on a 'count-all-groups' transfer credit, and paid on every boarding under 'all-paid'; $.
perKmRatenumberrequiredDistance system: price per km ridden; $/km.
distanceModeFareDistanceModerequiredDistance system: 'straight-line' or 'track'.
fareCapnumberrequiredDistance and zone systems: maximum fare per trip; $; 0 means no cap.
zonesFareZone[]requiredZone system: ordered fare zones; index + 1 is the displayed zone number and the ring order.
zonePricingModeFareZonePricingModerequiredZone system: 'count' (pay per distinct zone) or 'combination' (pay the contiguous span, minimum two zones when the group has two or more).
zoneBaseFarenumberrequiredZone system: fare for the first charged zone; a trip touching no zone pays it; $.
zonePerZoneFarenumberrequiredZone system: amount added per additional charged zone; $.
chargeOnInterGroupTransferbooleannoneDeprecated early-save flag superseded by transferPolicy; read only when transferPolicy is absent (true reads as 'count-within-group', false as 'count-all-groups').
offPeakFareMultipliernumbernoneOff-peak discount. See FareGroup.offPeakFareMultiplier below.

FareGroup.offPeakFareMultiplier#

Off-peak discount. Every dollar amount the group charges (flat fare, route fares, boarding charge, per-km rate, fare cap, zone fares) is multiplied by this in hours whose demand level isn't 'high' (see getHourDemandLevel). It applies when journeys are priced (buildJourneyFareIndex), so the fare panel's base-rate editor still shows peak prices. Absent or 1 means no discount, which matches existing groups and saves.

FareZone#

One polygon zone of a zone-based fare group, ordered by zone number. The zone tools and addFareZone/updateFareZone clip a zone against the group's other zones; where zones still overlap, a station takes the first zone in order.

Used by api.actions.addFareZone.

ts
type FareZone = {
    /** Zone id. */
    id: string;
    /** MultiPolygon coordinates ([lng, lat]): polygons … */
    polygons: [number, number][][][];
};
FieldTypeDefaultDescription
idstringrequiredZone id.
polygons[number, number][][][]requiredMultiPolygon coordinates ([lng, lat]): polygons, each a list of rings whose first ring is the outer boundary and later rings are holes.

Track#

One rail segment: geometry, elevation, build state and speed-model metadata.

Used by api.hooks.onTrackBuilt, api.hooks.onBlueprintPlaced, api.gameState.getTracks, api.gameState.calculateBlueprintCost, api.gameState.getBlueprintTrackLength.

ts
type Track = {
    /** Track id. A drag preview uses `'preview-centerline'` plus `'preview-track-<n>'` lanes. */
    id: string | 'preview-centerline' | 'preview-track-1' | 'preview-track-2';
    /** [lng, lat] polyline in travel order. */
    coords: [number, number][];
    /** Elevation at `coords[0]`; m; negative below grade. */
    startElevation: number;
    /** Elevation at the last coordinate; m; same scale as `startElevation`. */
    endElevation: number;
    /** `'constructed'` | `'blueprint'` | `'preview'`. */
    buildType: BuildType;
    /** Render state: `'constructed'` | `'blueprint'` | `'preview'` | `'preview-center'` | `'preview-connected'`. */
    displayType: 'constructed' | 'blueprint' | 'preview' | 'preview-center' | 'preview-connected';
    /** `'station'` | `'scissors-crossover'` | `'express-station'` | `'yard'` | null. */
    type: 'station' | 'scissors-crossover' | 'express-station' | 'yard' | null;
    /** True on both half-tracks of a station platform line the player converted to a yard berth. */
    yardBerth?: boolean;
    /** True on a `'station'`-type track whose platform has screen doors. */
    platformScreenDoors?: boolean;
    /** True where trains may reverse. */
    reversable: boolean;
    /** False on scissors-crossover diagonals, true on tracks from regular construction. */
    interactable: boolean;
    /** Polyline length of `coords`; m. */
    length: number;
    /** TrackTypeId: decides which train types may run it, and its train type's stats set the track's speed limits … */
    trackType?: TrackTypeId;
    /** Share of `length` over water, 0 to 100 … */
    waterIntersectionPercentage?: number;
    /** Unix time in ms (`Date.now()`) when the generator created the track. */
    createdAt?: number;
    /** CurveType the generator used; `'straight'` unless a curve was fitted. */
    curveType?: CurveType;
    /** Analytic centerline curvature: `startCurvature`, `endCurvature` (1/m), `length` (m) … */
    curveGeometry?: {
        /** Curvature at the start; 1/m, signed. */
        startCurvature: number;
        /** Curvature at the end; 1/m, signed. */
        endCurvature: number;
        /** Centerline arc length (m) the curvatures are parameterized over. */
        length: number;
        /** Optional piecewise-linear curvature profile. */
        profile?: { position: number; curvature: number }[];
    };
    /** True on tracks from track construction, yard generation and `api.build`. */
    cantEnabled?: boolean;
    /** Meters at each end (`start`, `end`) covered by a lane-spacing transition … */
    offsetTransitionZones?: {
        /** Transition length at the start of the track. */
        start?: number;
        /** Transition length at the end of the track. */
        end?: number;
    };
    /** Street-running data: `roadSegmentIds`, `speedLimitMps` (m/s), optional `roadName`. */
    streetRunning?: {
        /** Ids of the road segments the track runs along. */
        roadSegmentIds: string[];
        /** Speed limit of the road; m/s. */
        speedLimitMps: number;
        /** Name of the road, when it has one. */
        roadName?: string;
        /** Declared on the type; the game writes and reads no value. */
        isExclusiveROW?: boolean;
    };
};
FieldTypeDefaultDescription
idstring or 'preview-centerline' or 'preview-track-1' or 'preview-track-2'requiredTrack id. A drag preview uses 'preview-centerline' plus 'preview-track-<n>' lanes; a scissors-crossover preview uses 'preview-scissors-1' and 'preview-scissors-2'.
coords[number, number][]required[lng, lat] polyline in travel order.
startElevationnumberrequiredElevation at coords[0]; m; negative below grade. See Track.startElevation below.
endElevationnumberrequiredElevation at the last coordinate; m; same scale as startElevation.
buildTypeBuildTyperequired'constructed' or 'blueprint' or 'preview'.
displayType'constructed' or 'blueprint' or 'preview' or 'preview-center' or 'preview-connected'requiredRender state: 'constructed' or 'blueprint' or 'preview' or 'preview-center' or 'preview-connected'. gameState.getBlueprintTrackLength and gameState.calculateBlueprintCost count only tracks with 'blueprint'.
type'station' or 'scissors-crossover' or 'express-station' or 'yard' or nullrequired'station' or 'scissors-crossover' or 'express-station' or 'yard' or null; in a scissors group only the crossing diagonals carry 'scissors-crossover'.
yardBerthbooleannoneTrue on both half-tracks of a station platform line the player converted to a yard berth; the track keeps its 'station' or 'express-station' type; toggled by the store.
platformScreenDoorsbooleannoneTrue on a 'station'-type track whose platform has screen doors: the local-station speed cap (getMaxSpeedLocalStation) is skipped and the track runs at the train type's full maxSpeed instead, same as 'express-station' track. Toggled per station by the store; absent = doors off, cap applies.
reversablebooleanrequiredTrue where trains may reverse. Track construction sets it on station and yard tracks; scissors-crossover building sets it on its diagonals and on the rails it joins; generated yard layouts set it on their tracks; a save-load heal recomputes it on rails without a type.
interactablebooleanrequiredFalse on scissors-crossover diagonals, true on tracks from regular construction; snapping skips tracks where it is false.
lengthnumberrequiredPolyline length of coords; m.
trackTypeTrackTypeIdnoneTrackTypeId: decides which train types may run it, and its train type's stats set the track's speed limits, TPH limit and grade-crossing TPH limits.
waterIntersectionPercentagenumbernoneShare of length over water, 0 to 100; computed by the store from the ocean-depth index for blueprint tracks written without one; writes that skip undo history, and cities without an ocean-depth index, leave it unset.
createdAtnumbernoneUnix time in ms (Date.now()) when the generator created the track; building a blueprint keeps it.
curveTypeCurveTypenoneCurveType the generator used; 'straight' unless a curve was fitted; absent on scissors-crossover diagonals and api.build tracks.
curveGeometryobjectnoneAnalytic centerline curvature: startCurvature, endCurvature (1/m), length (m), optional profile of { position 0..1, curvature }; absent on tracks built without one, older saves among them. See Track.curveGeometry below.
cantEnabledbooleannoneTrue on tracks from track construction, yard generation and api.build; absent on older saves, which keep the legacy curve-speed formula, and on scissors-crossover diagonals.
offsetTransitionZonesobjectnoneMeters at each end (start, end) covered by a lane-spacing transition, where the speed model multiplies the curve radius by RULES.OFFSET_TRANSITION_RADIUS_MULTIPLIER. See Track.offsetTransitionZones below.
streetRunningobjectnoneStreet-running data: roadSegmentIds, speedLimitMps (m/s), optional roadName. Full text below the table.

streetRunning: Street-running data: roadSegmentIds, speedLimitMps (m/s), optional roadName. Set by track construction when street running is on and both track ends sit at 0 m elevation, from the road matched along the track or, failing that, the road under the snapped cursor (then roadSegmentIds is empty). See Track.streetRunning below.

Track.startElevation#

Elevation at coords[0]; m; negative below grade. The cost model classifies the mean of startElevation and endElevation of a group's first priced track against RULES.CONSTRUCTION_COSTS.ELEVATION_THRESHOLDS: deep bore at or below -24, standard tunnel at or below -10, cut and cover at or below -4, trenched at or below -1, at grade at or below 0, ramp below 5, elevated at or above 5.

Track.curveGeometry#

FieldTypeDefaultDescription
startCurvaturenumberrequiredCurvature at the start; 1/m, signed.
endCurvaturenumberrequiredCurvature at the end; 1/m, signed.
lengthnumberrequiredCenterline arc length (m) the curvatures are parameterized over.
profile{ position: number; curvature: number }[]noneOptional piecewise-linear curvature profile: signed curvature κ at normalized positions 0..1. Present for compound curves such as spiral-arc-spiral connectors; absent means κ interpolates linearly from start to end.

Track.offsetTransitionZones#

FieldTypeDefaultDescription
startnumbernoneTransition length at the start of the track; m.
endnumbernoneTransition length at the end of the track; m.

Track.streetRunning#

FieldTypeDefaultDescription
roadSegmentIdsstring[]requiredIds of the road segments the track runs along; empty when only the road under the cursor matched.
speedLimitMpsnumberrequiredSpeed limit of the road; m/s.
roadNamestringnoneName of the road, when it has one.
isExclusiveROWbooleannoneDeclared on the type; the game writes and reads no value.

Train#

A running or parked train: position along its route, motion, windows, timings and capacity.

Used by api.hooks.onTrainSpawned, api.hooks.onTrainArrivedAtStation, api.hooks.onTrainSelected, api.gameState.getTrains.

ts
type Train = {
    /** UUID assigned when the train is generated, unless the generator was given an id. */
    id: string;
    /** Competitive multiplayer company id; absent otherwise. */
    companyId?: string;
    /** Id of the route the train runs, temp routes included. */
    routeId: string;
    /** `cars` times the train type's `carLength`. */
    length: number;
    /** Car count: the route's `carsPerTrain`, else the train type's `carsPerCarSet`. */
    cars?: number;
    /** TrainTypeId of the generated train. */
    trainType?: TrainTypeId;
    /** Progress through the route: `index` of the current stCombo, `gapFromHeadToEndOfRoute` (m) … */
    currentStComboInfo: {
        /** The index of the current station track route combo */
        index: number;
        /** The train should stop when the head of the train is this distance from the end of the current station … */
        gapFromHeadToEndOfRoute: number;
        /** The game time when the train arrived the first station track node of the current station track route combo */
        timeAtStop: number | null;
        /** The game time when the train departed the first station track node of the current station track route combo */
        timeAtStopEnd: number | null;
        /** The ID of the station track node that this train is headed to */
        endStNodeId: string | null;
        /** True when the train has to change direction to depart the stop it's currently dwelling at … */
        reversesAtStop?: boolean;
        /** The dynamic dwell computed for the in-progress stop (see DynamicDwellSnapshot). */
        dynamicDwell?: DynamicDwellSnapshot;
    };
    /** `speed` in m/s and `acceleration` in m/s^2. */
    motion: {
        /** Current speed; m/s. */
        speed: number;
        /** Current acceleration; m/s^2. */
        acceleration: number;
    };
    /** Per window type: occupied tracks, head and tail progress along the stCombo, coords and signal ids. */
    windows: AllTrainWindowCollections;
    /** Per stNode visit: actual and expected arrival and departure times in game seconds, null until known. */
    timings: TrainStNodeTiming[];
    /** `maxCapacity` in passengers: `cars` times the train type's `capacityPerCar`. */
    specs: {
        /** Passenger capacity: `cars` times the train type's `capacityPerCar`. */
        maxCapacity: number;
    };
    /** `totalSeconds` in service and `lastChargedAt` (game seconds) for operating-cost billing. */
    operationalTime: {
        /** Total seconds this train has been in service */
        totalSeconds: number;
        /** Last time the operational cost was charged */
        lastChargedAt: number;
    };
    /** Per-train substitute stCombos keyed by stCombo index, applied over the route for alternate-platform termini. */
    stComboOverrides?: { [index: number]: RouteStCombo } | null;
    /** Which demand periods the train runs: `highDemand`, `mediumDemand`, `lowDemand`, `veryLowDemand` booleans. */
    operatingSchedule?: {
        /** Runs in high-demand hours. */
        highDemand: boolean;
        /** Runs in medium-demand hours. */
        mediumDemand: boolean;
        /** Runs in low-demand hours. */
        lowDemand: boolean;
        /** Runs in very-low-demand hours. */
        veryLowDemand: boolean;
    };
    /** Declared on the type; the game writes no value, so it is always absent. */
    assignedSlot?: number;
    /** `lastMovementTime` (game seconds) and `lastStComboIndex` of the last detected movement. */
    stuckDetection?: {
        /** Last time this train moved (changed stComboIndex or had speed > 0) */
        lastMovementTime: number;
        /** Last stComboIndex when movement was detected */
        lastStComboIndex: number;
    };
    /** Set while stabled at a yard: `yardId`, `berthStNodeId`, optional `slot` (0 = deepest). */
    parked?: { yardId: string; berthStNodeId: string; slot?: number; } | null;
};
FieldTypeDefaultDescription
idstringrequiredUUID assigned when the train is generated, unless the generator was given an id.
companyIdstringnoneCompetitive multiplayer company id; absent otherwise.
routeIdstringrequiredId of the route the train runs, temp routes included.
lengthnumberrequiredcars times the train type's carLength; m.
carsnumbernoneCar count: the route's carsPerTrain, else the train type's carsPerCarSet.
trainTypeTrainTypeIdnoneTrainTypeId of the generated train.
currentStComboInfoobjectrequiredProgress through the route: index of the current stCombo, gapFromHeadToEndOfRoute (m), timeAtStop and timeAtStopEnd (game seconds or null), endStNodeId, optional reversesAtStop and dynamicDwell. See Train.currentStComboInfo below.
motionobjectrequiredspeed in m/s and acceleration in m/s^2; both 0 at generation. See Train.motion below.
windowsAllTrainWindowCollectionsrequiredPer window type: occupied tracks, head and tail progress along the stCombo, coords and signal ids.
timingsTrainStNodeTiming[]requiredPer stNode visit: actual and expected arrival and departure times in game seconds, null until known.
specsobjectrequiredmaxCapacity in passengers: cars times the train type's capacityPerCar. See Train.specs below.
operationalTimeobjectrequiredtotalSeconds in service and lastChargedAt (game seconds) for operating-cost billing; 0 and the generation time at creation. See Train.operationalTime below.
stComboOverrides{ [index: number]: RouteStCombo } or nullnonePer-train substitute stCombos keyed by stCombo index, applied over the route for alternate-platform termini; null or absent otherwise.
operatingScheduleobjectnoneWhich demand periods the train runs: highDemand, mediumDemand, lowDemand, veryLowDemand booleans; absent when unset. See Train.operatingSchedule below.
assignedSlotnumbernoneDeclared on the type; the game writes no value, so it is always absent.
stuckDetectionobjectnonelastMovementTime (game seconds) and lastStComboIndex of the last detected movement; absent when unset. See Train.stuckDetection below.
parked{ yardId: string; berthStNodeId: string; slot?: number; } or nullnoneSet while stabled at a yard: yardId, berthStNodeId, optional slot (0 = deepest); the movement tick returns a parked train unchanged; null or absent while running.

Train.currentStComboInfo#

FieldTypeDefaultDescription
indexnumberrequiredThe index of the current station track route combo
gapFromHeadToEndOfRoutenumberrequiredThe train should stop when the head of the train is this distance from the end of the current station track route combo This is dependent on the train's length and the length of the ending station tracks
timeAtStopnumber or nullrequiredThe game time when the train arrived the first station track node of the current station track route combo
timeAtStopEndnumber or nullrequiredThe game time when the train departed the first station track node of the current station track route combo
endStNodeIdstring or nullrequiredThe ID of the station track node that this train is headed to
reversesAtStopbooleannoneTrue when the train has to change direction to depart the stop it's currently dwelling at (a terminus or mid-route turnback). Full text below the table.
dynamicDwellDynamicDwellSnapshotnoneThe dynamic dwell computed for the in-progress stop (see DynamicDwellSnapshot). Full text below the table.

reversesAtStop: True when the train has to change direction to depart the stop it's currently dwelling at (a terminus or mid-route turnback); adds the train type's turnaroundTimeSeconds to the dwell. Computed at arrival in generateTrain; absent on trains from older saves (treated as false for the in-progress dwell only).

dynamicDwell: The dynamic dwell computed for the in-progress stop (see DynamicDwellSnapshot). Stamped by updatePopMovements on the first pop pass after arrival; undefined until then, on older saves' in-progress dwells, and whenever RULES.DWELL.DYNAMIC_DWELL_ENABLED is off; all of these fall back to the flat scheduled dwell. The dwell used is max(scheduled, dynamicDwell.seconds): dynamic dwell only ever extends a stop.

Train.motion#

FieldTypeDefaultDescription
speednumberrequiredCurrent speed; m/s.
accelerationnumberrequiredCurrent acceleration; m/s^2.

Train.specs#

FieldTypeDefaultDescription
maxCapacitynumberrequiredPassenger capacity: cars times the train type's capacityPerCar.

Train.operationalTime#

FieldTypeDefaultDescription
totalSecondsnumberrequiredTotal seconds this train has been in service
lastChargedAtnumberrequiredLast time the operational cost was charged

Train.operatingSchedule#

FieldTypeDefaultDescription
highDemandbooleanrequiredRuns in high-demand hours.
mediumDemandbooleanrequiredRuns in medium-demand hours.
lowDemandbooleanrequiredRuns in low-demand hours.
veryLowDemandbooleanrequiredRuns in very-low-demand hours.

Train.stuckDetection#

FieldTypeDefaultDescription
lastMovementTimenumberrequiredLast time this train moved (changed stComboIndex or had speed > 0)
lastStComboIndexnumberrequiredLast stComboIndex when movement was detected

Train Parts#

TrainStNodeTiming#

The in-game time when the train arrived and departed from a station track node

ts
type TrainStNodeTiming = {
    /** Station track node this timing is for. */
    stNodeId: string;
    /** The order the station track node is in the route so if a train visits an stNode more than once they don't … */
    stNodeIndex: number;
    /** Game seconds (`elapsedSeconds`) the train arrived. */
    arrivalTime: number | null;
    /** Game seconds the train departed; null until it has. */
    departureTime: number | null;
    /** Next scheduled arrival in game seconds, from the route's timings. */
    expectedArrivalTime: number | null;
    /** Next scheduled departure in game seconds. */
    expectedDepartureTime: number | null;
    /** Adjusted expected arrival time accounting for delays from previous stations */
    adjustedExpectedArrivalTime: number | null;
    /** Adjusted expected departure time accounting for delays from previous stations */
    adjustedExpectedDepartureTime: number | null;
    /** Expected arrival times for future cycles of the route */
    futureCycleArrivalTimes?: number[];
    /** Expected departure times for future cycles of the route */
    futureCycleDepartureTimes?: number[];
};
FieldTypeDefaultDescription
stNodeIdstringrequiredStation track node this timing is for.
stNodeIndexnumberrequiredThe order the station track node is in the route so if a train visits an stNode more than once they don't get mixed up
arrivalTimenumber or nullrequiredGame seconds (elapsedSeconds) the train arrived; null until it has.
departureTimenumber or nullrequiredGame seconds the train departed; null until it has.
expectedArrivalTimenumber or nullrequiredNext scheduled arrival in game seconds, from the route's timings; null when there is none.
expectedDepartureTimenumber or nullrequiredNext scheduled departure in game seconds; null when there is none.
adjustedExpectedArrivalTimenumber or nullrequiredAdjusted expected arrival time accounting for delays from previous stations
adjustedExpectedDepartureTimenumber or nullrequiredAdjusted expected departure time accounting for delays from previous stations
futureCycleArrivalTimesnumber[]noneExpected arrival times for future cycles of the route
futureCycleDepartureTimesnumber[]noneExpected departure times for future cycles of the route

DynamicDwellSnapshot#

One stop's computed dynamic dwell (RULES.DWELL, see dwellModel.ts): the

passenger-exchange seconds plus the per-term breakdown the train details panel renders. Stamped onto currentStComboInfo by updatePopMovements on the first pop pass of a dwell; cleared implicitly when generateTrain mints the next stop's currentStComboInfo.

ts
type DynamicDwellSnapshot = {
    /** Total passenger-exchange dwell in seconds (function time + flow terms) … */
    seconds: number;
    /** True when the raw model output exceeded MAX_DWELL_SECONDS (running far outside the calibrated envelope). */
    clampedAtMax: boolean;
    /** Head-count that alighted here. */
    alighting: number;
    /** Head-count boarding here (after capacity limiting). */
    boarding: number;
    /** Standing through-riders (through load beyond the train's seats). */
    standees: number;
    /** Fixed mechanical door-cycle + dispatch seconds. */
    functionTime: number;
    /** Alighting-flow seconds. */
    alightTime: number;
    /** Boarding-flow seconds. */
    boardTime: number;
    /** Opposing-stream friction seconds (0 on both-side platforms, where the streams split). */
    frictionTime: number;
    /** Standee-crowding seconds. */
    crowdTime: number;
    /** 2 when the platform track had slabs on both sides (Spanish solution), else 1. */
    platformSides: 1 | 2;
};
FieldTypeDefaultDescription
secondsnumberrequiredTotal passenger-exchange dwell in seconds (function time + flow terms), capped at RULES.DWELL.MAX_DWELL_SECONDS.
clampedAtMaxbooleanrequiredTrue when the raw model output exceeded MAX_DWELL_SECONDS (running far outside the calibrated envelope).
alightingnumberrequiredHead-count that alighted here.
boardingnumberrequiredHead-count boarding here (after capacity limiting).
standeesnumberrequiredStanding through-riders (through load beyond the train's seats).
functionTimenumberrequiredFixed mechanical door-cycle + dispatch seconds.
alightTimenumberrequiredAlighting-flow seconds.
boardTimenumberrequiredBoarding-flow seconds.
frictionTimenumberrequiredOpposing-stream friction seconds (0 on both-side platforms, where the streams split).
crowdTimenumberrequiredStandee-crowding seconds.
platformSides1 or 2required2 when the platform track had slabs on both sides (Spanish solution), else 1.

AllTrainWindowCollections#

One window per TrainWindowType: train is the track span the train occupies; warning and warningExtra are the wider spans train movement also tracks, drawn by the train warning debug layers.

ts
type AllTrainWindowCollections = { [key in TrainWindowType]: TrainWindowCollection; }

TrainWindowCollection#

ts
type TrainWindowCollection = {
    /** Tracks the window covers, each with head and tail progress along it and the signals it triggered. */
    tracks: TrainWindowTrack[];
    /** The progress of the train's head along the current station track route combo */
    headStComboProgress: number;
    /** The progress of the train's tail along the current station track route combo */
    tailStComboProgress: number;
    /** [lng, lat] line of the window for the map layers. */
    coords: [number, number][] | null;
    /** Ids of the signals inside the window. */
    signalIds: string[];
};
FieldTypeDefaultDescription
tracksTrainWindowTrack[]requiredTracks the window covers, each with head and tail progress along it and the signals it triggered.
headStComboProgressnumberrequiredThe progress of the train's head along the current station track route combo
tailStComboProgressnumberrequiredThe progress of the train's tail along the current station track route combo
coords[number, number][] or nullrequired[lng, lat] line of the window for the map layers; null when it is not drawn.
signalIdsstring[]requiredIds of the signals inside the window.

TrainWindowTrack#

ts
type TrainWindowTrack = {
    /** Track id. */
    trackId: string;
    /** Progress of the window's head along the track. */
    headProgress: number;
    /** Progress of the window's tail along the track. */
    tailProgress: number;
    /** Signals on this track the window has triggered. */
    triggeredSignalIds: string[];
};
FieldTypeDefaultDescription
trackIdstringrequiredTrack id.
headProgressnumberrequiredProgress of the window's head along the track; m.
tailProgressnumberrequiredProgress of the window's tail along the track; m.
triggeredSignalIdsstring[]requiredSignals on this track the window has triggered.

Bond#

A loan the player took: principal, remaining balance and the figures used to service it every game hour.

Used by api.gameState.getBonds.

ts
type Bond = {
    /** UUID assigned at issue. */
    id: string;
    /** `id` of the BOND_TYPES entry: `'small'`, `'medium'`, `'large'`, or `'custom'` for a slider-issued bond. */
    bondTypeId: string;
    /** Amount borrowed; $. */
    principal: number;
    /** Principal still owed; $; reduced by `monthlyPayment` each game hour and by manual payments. */
    remainingPrincipal: number;
    /** Daily interest fraction on `remainingPrincipal` (SMALL default 0.06 = 6% per game day). */
    interestRate: number;
    /** Principal repaid every game hour; $; `principal` times the type's `principalPaymentRate`. */
    monthlyPayment: number;
    /** Game time at issue; game seconds (`elapsedSeconds`). */
    issuedAt: number;
    /** Game time of the last hourly charge, the issue time before the first. */
    lastPaymentAt: number;
};
FieldTypeDefaultDescription
idstringrequiredUUID assigned at issue.
bondTypeIdstringrequiredid of the BOND_TYPES entry: 'small', 'medium', 'large', or 'custom' for a slider-issued bond.
principalnumberrequiredAmount borrowed; $.
remainingPrincipalnumberrequiredPrincipal still owed; $; reduced by monthlyPayment each game hour and by manual payments; the bond is removed at 0.
interestRatenumberrequiredDaily interest fraction on remainingPrincipal (SMALL default 0.06 = 6% per game day); charged every game hour at 1/24.
monthlyPaymentnumberrequiredPrincipal repaid every game hour; $; principal times the type's principalPaymentRate.
issuedAtnumberrequiredGame time at issue; game seconds (elapsedSeconds).
lastPaymentAtnumberrequiredGame time of the last hourly charge, the issue time before the first; game seconds.

StationGroup#

A derived cluster of stations chained within 150 m of each other, with the player's manual edits applied; a lone station forms a one-station group, and every rebuild leaves out pocket (no-boarding) stations. Rebuilt by every setStations call.

Used by api.hooks.onStationGroupSelected, api.gameState.getStationGroups, api.gameState.getStationGroupForStation.

ts
type StationGroup = {
    /** Group id; kept from the matching previous group, else a fresh UUID. */
    id: string;
    /** `customName` when set; else a name two or more members share … */
    name: string;
    /** Member station ids. */
    stationIds: string[];
    /** [lng, lat]: `manualCenter` when set; else the mean of member centers, or … */
    center: [number, number];
    /** Bounding box of member centers: `minLng`, `maxLng`, `minLat`, `maxLat`. */
    bounds: {
        /** Westmost member center longitude; degrees. */
        minLng: number;
        /** Eastmost member center longitude; degrees. */
        maxLng: number;
        /** Southmost member center latitude; degrees. */
        minLat: number;
        /** Northmost member center latitude; degrees. */
        maxLat: number;
    };
    /** Station ids the player added by hand; carried over from the matched previous group. */
    manualAdditions?: string[];
    /** Station ids the player removed by hand … */
    manualRemovals?: string[];
    /** Player-picked marker position [lng, lat]. */
    manualCenter?: [number, number];
    /** Player-given name; cleared on a one-station group. */
    customName?: string;
};
FieldTypeDefaultDescription
idstringrequiredGroup id; kept from the matching previous group, else a fresh UUID.
namestringrequiredcustomName when set; else a name two or more members share, else the name of the member with the most routes; a one-station group uses its station's name.
stationIdsstring[]requiredMember station ids.
center[number, number]required[lng, lat]: manualCenter when set; else the mean of member centers, or, for a two-station group whose platform centerlines cross, their intersection.
boundsobjectrequiredBounding box of member centers: minLng, maxLng, minLat, maxLat; degrees. See StationGroup.bounds below.
manualAdditionsstring[]noneStation ids the player added by hand; carried over from the matched previous group.
manualRemovalsstring[]noneStation ids the player removed by hand; a removed station does not auto-merge with that group's members again until the group is reset.
manualCenter[number, number]nonePlayer-picked marker position [lng, lat]; overrides the computed center.
customNamestringnonePlayer-given name; cleared on a one-station group.

StationGroup.bounds#

FieldTypeDefaultDescription
minLngnumberrequiredWestmost member center longitude; degrees.
maxLngnumberrequiredEastmost member center longitude; degrees.
minLatnumberrequiredSouthmost member center latitude; degrees.
maxLatnumberrequiredNorthmost member center latitude; degrees.

DemandData#

The city's demand model: demand points and the pops commuting between them, both as Maps.

Used by api.gameState.getDemandData.

ts
type DemandData = {
    /** Map of point id to demand point: `location` [lng, lat], `jobs`, `residents`, `popIds` … */
    points: ExtendedDemandPointMap;
    /** Map of pop id to pop: `size` people, `residenceId`, `jobId`, driving data … */
    popsMap: ExtendedPopMap;
};
FieldTypeDefaultDescription
pointsExtendedDemandPointMaprequiredMap of point id to demand point: location [lng, lat], jobs, residents, popIds, plus derived mode shares and walk-graph snap fields (walkNode, walkSnapM).
popsMapExtendedPopMaprequiredMap of pop id to pop: size people, residenceId, jobId, driving data, departure times in seconds from midnight and commute summaries.

Demand Parts#

ExtendedDemandPointMap#

ts
type ExtendedDemandPointMap = Map<string, ExtendedDemandPoint>;

A Map keyed by id; values are ExtendedDemandPoint.

ExtendedDemandPoint#

ts
type ExtendedDemandPoint = {
    /** Demand point id; pops name it as `residenceId` or `jobId`. */
    id: string;
    /** [lng, lat] of the point. */
    location: [number, number];
    /** People who work here. */
    jobs: number;
    /** People who live here. */
    residents: number;
    /** Ids of the pops that live or work here. */
    popIds: string[];
    /** Mode split of the people living here (home-to-work leg). */
    residentModeShare?: ModeChoice;
    /** Mode split of the people working here (home-to-work leg). */
    workerModeShare?: ModeChoice;
    /** Nearest walk-graph node id. */
    walkNode?: number;
    /** Distance from the point to `walkNode`; m. */
    walkSnapM?: number;
};
FieldTypeDefaultDescription
idstringrequiredDemand point id; pops name it as residenceId or jobId.
location[number, number]required[lng, lat] of the point.
jobsnumberrequiredPeople who work here.
residentsnumberrequiredPeople who live here.
popIdsstring[]requiredIds of the pops that live or work here.
residentModeShareModeChoicenoneMode split of the people living here (home-to-work leg).
workerModeShareModeChoicenoneMode split of the people working here (home-to-work leg).
walkNodenumbernoneNearest walk-graph node id. Derived at load by walkAccess.snapDemandPoints and never persisted. Absent when the city has no walk graph or the point is off-network (straight-line fallback).
walkSnapMnumbernoneDistance from the point to walkNode; m.

ExtendedPopMap#

ts
type ExtendedPopMap = Map<string, ExtendedPop>;

A Map keyed by id; values are ExtendedPop.

ExtendedPop#

ts
type ExtendedPop = {
    /** Pop id. */
    id: string;
    /** People this pop stands for; a pop commutes as one group. */
    size: number;
    /** Demand point id of the home end. */
    residenceId: string;
    /** Demand point id of the work end; `AIR_` and `UNI_` prefixes mark airport and university jobs … */
    jobId: string;
    /** Driving time between home and work; s. */
    drivingSeconds: number;
    /** Driving distance between home and work; m. */
    drivingDistance: number;
    /** Optional: GeoJSON LineString coordinates for driving route [lng, lat] pairs */
    drivingPath?: [number, number][];
    /** Time in seconds from midnight when the pop leaves for work */
    homeDepartureTime: number;
    /** Time in seconds from midnight when the pop leaves for home */
    workDepartureTime: number;
    /** The most recent commute the pop made or is on currently */
    lastCommute?: {
        /** Candidate transit journeys fed to mode choice (same order as `modeChoice.transitByPath`). */
        transitPaths: RaptorPath[];
        /** The walk-only option: `distance` m, `time` s. */
        walking: { distance: number; time: number; };
        /** Mode split of this commute; people. */
        modeChoice: ModeChoice;
    };
    /** Both directions' mode splits, each overwritten only by its own direction. */
    commutes?: {
        homeToWork?: CommuteSummary;
        workToHome?: CommuteSummary;
    };
    /** Sim-host slim mirror only (see helpers/popMovements/slimPops.ts). */
    legacyCommute?: CommuteSummary;
};
FieldTypeDefaultDescription
idstringrequiredPop id.
sizenumberrequiredPeople this pop stands for; a pop commutes as one group.
residenceIdstringrequiredDemand point id of the home end.
jobIdstringrequiredDemand point id of the work end; AIR_ and UNI_ prefixes mark airport and university jobs, which get flatter departure curves (popTiming dampening).
drivingSecondsnumberrequiredDriving time between home and work; s.
drivingDistancenumberrequiredDriving distance between home and work; m.
drivingPath[number, number][]noneOptional: GeoJSON LineString coordinates for driving route [lng, lat] pairs
homeDepartureTimenumberrequiredTime in seconds from midnight when the pop leaves for work
workDepartureTimenumberrequiredTime in seconds from midnight when the pop leaves for home
lastCommuteobjectnoneThe most recent commute the pop made or is on currently See ExtendedPop.lastCommute below.
commutes{ homeToWork?: CommuteSummary; workToHome?: CommuteSummary; }noneBoth directions' mode splits, each overwritten only by its own direction. lastCommute is a single slot that the evening commute overwrites the morning one in, which made mode-share displays swing between the peaks; these don't. Read them via getDisplayCommute rather than directly.
legacyCommuteCommuteSummarynoneSim-host slim mirror only (see helpers/popMovements/slimPops.ts). See ExtendedPop.legacyCommute below.

ExtendedPop.lastCommute#

FieldTypeDefaultDescription
transitPathsRaptorPath[]requiredCandidate transit journeys fed to mode choice (same order as modeChoice.transitByPath); saves drop them for pops not travelling.
walking{ distance: number; time: number; }requiredThe walk-only option: distance m, time s.
modeChoiceModeChoicerequiredMode split of this commute; people.

ExtendedPop.legacyCommute#

Sim-host slim mirror only (see helpers/popMovements/slimPops.ts): the undirected legacy summary getDisplayCommute would have reconstructed from lastCommute.transitPaths, captured at slim time because the slim copy drops those paths. Absent on full pops and never persisted: saves are written from the host's full popsMap.

CommuteSummary#

A mode split plus the inputs that produced it, for one direction of travel.

lastCommute keeps only the split, so anything wanting to re-derive it (the income distribution chart) had to guess at the inputs and read them off transitPaths, which saves drop for idle pops. Keeping the handful of numbers that fed getModeChoice means displays reproduce the split exactly, with no dependence on paths surviving the save.

ts
type CommuteSummary = {
    /** Mode split of this leg; people. */
    modeChoice: ModeChoice;
    /** Perceived transit time fed to getModeChoice. */
    transitTime?: number;
    /** Fare charged for the chosen journey. */
    transitCost?: number;
    /** The candidate journeys fed to getModeChoice with how many commuters chose each … */
    transitOptions?: Array<{ riders: number; perceivedTime: number; fareCost: number }>;
    /** Traffic multiplier for this direction's departure hour. */
    drivingTimeMultiplier: number;
};
FieldTypeDefaultDescription
modeChoiceModeChoicerequiredMode split of this leg; people.
transitTimenumbernonePerceived transit time fed to getModeChoice. Omitted when the pop had no transit option.
transitCostnumbernoneFare charged for the chosen journey. Omitted only for pre-summary saves.
transitOptionsArray<{ riders: number; perceivedTime: number; fareCost: number }>noneThe candidate journeys fed to getModeChoice with how many commuters chose each (same order as modeChoice.transitByPath). Present only when there was more than one candidate; with a single journey, transitTime/transitCost above already reproduce the split.
drivingTimeMultipliernumberrequiredTraffic multiplier for this direction's departure hour.

ModeChoice#

ts
type ModeChoice = {
    /** People who walk. */
    walking: number;
    /** People who drive. */
    driving: number;
    /** People who ride transit. */
    transit: number;
    /** People with no mode decided. */
    unknown: number;
    /** How `transit` splits across the candidate journeys fed to getModeChoice … */
    transitByPath?: number[];
};
FieldTypeDefaultDescription
walkingnumberrequiredPeople who walk.
drivingnumberrequiredPeople who drive.
transitnumberrequiredPeople who ride transit.
unknownnumberrequiredPeople with no mode decided.
transitByPathnumber[]noneHow transit splits across the candidate journeys fed to getModeChoice (same order as lastCommute.transitPaths). See ModeChoice.transitByPath below.

ModeChoice.transitByPath#

How transit splits across the candidate journeys fed to getModeChoice (same order as lastCommute.transitPaths): lower-income commuters may pick a cheaper, slower journey while higher-income ones pay for a faster one. Present only when the commute had more than one candidate journey. Aggregated ModeChoice objects (demand points, city totals) drop it.

RaptorPath#

ts
type RaptorPath = {
    /** Legs in travel order: train rides, walks and driving access legs. */
    segments: RaptorPathSegment[];
    /** Journey span in seconds: first segment departure to final arrival. */
    totalTime: number;
    /** Transfers between trains. */
    transfers: number;
    /** Generalized ("perceived") time in seconds. */
    perceivedTime?: number;
    /** Walk weight applied to this path's perceivedTime when it differs from the baseline WALK_MULTIPLIER. */
    perceivedWalkMultiplier?: number;
    /** Unweighted real-time components behind perceivedTime, for UI display */
    timeBreakdown?: RaptorTimeBreakdown;
    /** Fare in dollars for this journey, computed AFTER pathfinding from the fare groups of the routes it boards … */
    fareCost?: number;
};
FieldTypeDefaultDescription
segmentsRaptorPathSegment[]requiredLegs in travel order: train rides, walks and driving access legs.
totalTimenumberrequiredJourney span in seconds: first segment departure to final arrival. Includes riding, walking, and platform waits at transfers; excludes the origin wait before the journey starts (pops time their departure to the schedule, so that wait happens at home and is free).
transfersnumberrequiredTransfers between trains.
perceivedTimenumbernoneGeneralized ("perceived") time in seconds. See RaptorPath.perceivedTime below.
perceivedWalkMultipliernumbernoneWalk weight applied to this path's perceivedTime when it differs from the baseline WALK_MULTIPLIER: AIRPORT_WALK_MULTIPLIER (Table 8 S8) for airport pops so UI breakdowns sum to the total.
timeBreakdownRaptorTimeBreakdownnoneUnweighted real-time components behind perceivedTime, for UI display
fareCostnumbernoneFare in dollars for this journey, computed AFTER pathfinding from the fare groups of the routes it boards (routing is fare-blind). See RaptorPath.fareCost below.

RaptorPath.perceivedTime#

Generalized ("perceived") time in seconds: each real-time component weighted by how commuters experience it (PATHFINDING_RULES.PERCEIVED_TIME). Journeys are ranked by this, and it's what mode choice prices against driving/walking. Optional because paths in old save files predate it; fall back to totalTime.

RaptorPath.fareCost#

Fare in dollars for this journey, computed AFTER pathfinding from the fare groups of the routes it boards (routing is fare-blind). Set by the commute worker; optional because journey-planner paths and paths in old saves predate it. The UI falls back to computing it live, the sim to the legacy flat transitCost.

RaptorPathSegment#

ts
type RaptorPathSegment = {
    /** Route ridden on this leg; `'walking'` for a walk leg. */
    routeId: string | 'walking';
    /** Station id where the leg starts, or `'origin'` for the first walk. */
    fromStopId: RaptorStationId;
    /** Station id where the leg ends, or `'destination'` for the last walk. */
    toStopId: RaptorStationId;
    /** [lng, lat] where the leg starts. */
    fromStopCoords: [number, number];
    /** [lng, lat] where the leg ends. */
    toStopCoords: [number, number];
    /** In game-time seconds */
    departureTime: number;
    /** In game-time seconds */
    arrivalTime: number;
    /** Whether this segment is walking (not a train) */
    isWalking?: boolean;
    /** Whether this segment is driving (access leg) */
    isDriving?: boolean;
    /** Walk leg priced as a straight line: the place had no walk-graph node (off-network, or an airport terminal … */
    directWalk?: boolean;
};
FieldTypeDefaultDescription
routeIdstring or 'walking'requiredRoute ridden on this leg; 'walking' for a walk leg.
fromStopIdRaptorStationIdrequiredStation id where the leg starts, or 'origin' for the first walk.
toStopIdRaptorStationIdrequiredStation id where the leg ends, or 'destination' for the last walk.
fromStopCoords[number, number]required[lng, lat] where the leg starts.
toStopCoords[number, number]required[lng, lat] where the leg ends.
departureTimenumberrequiredIn game-time seconds
arrivalTimenumberrequiredIn game-time seconds
isWalkingbooleannoneWhether this segment is walking (not a train)
isDrivingbooleannoneWhether this segment is driving (access leg)
directWalkbooleannoneWalk leg priced as a straight line: the place had no walk-graph node (off-network, or an airport terminal, which opts out because its road network doesn't model how pedestrians actually cross it). Renderers draw these … See RaptorPathSegment.directWalk below.

RaptorPathSegment.directWalk#

Walk leg priced as a straight line: the place had no walk-graph node (off-network, or an airport terminal, which opts out because its road network doesn't model how pedestrians actually cross it). Renderers draw these straight instead of resolving a road polyline, keeping the drawn path consistent with the priced time on every thread.

RaptorTimeBreakdown#

All in real (clock) seconds; weights are applied when computing perceivedTime

ts
type RaptorTimeBreakdown = {
    /** Time on board trains */
    inVehicle: number;
    /** Access/egress/transfer walking */
    walk: number;
    /** Park-and-ride driving access */
    drive: number;
    /** Platform waits between segments (includes the ARRIVAL_GAP buffer) */
    wait: number;
    /** How much later the journey departs than the pop requested */
    departureShift: number;
};
FieldTypeDefaultDescription
inVehiclenumberrequiredTime on board trains
walknumberrequiredAccess/egress/transfer walking
drivenumberrequiredPark-and-ride driving access
waitnumberrequiredPlatform waits between segments (includes the ARRIVAL_GAP buffer)
departureShiftnumberrequiredHow much later the journey departs than the pop requested

Ridership Stats#

Returned by api.gameState.getRidershipStats(). The code declares this shape inline; RidershipStats is its name on this page.

ts
type RidershipStats = {
    /** `totalRiders` divided by 0.25; people per game hour. */
    totalRidersPerHour: number;
    /** People (summed commute `size`) who finished a transit journey in the last 900 game seconds. */
    totalRiders: number;
    /** Window length; game hours; always 0.25. */
    timeWindowHours: number;
};
FieldTypeDescription
totalRidersPerHournumbertotalRiders divided by 0.25; people per game hour.
totalRidersnumberPeople (summed commute size) who finished a transit journey in the last 900 game seconds.
timeWindowHoursnumberWindow length; game hours; always 0.25.

Line Metrics#

Returned by api.gameState.getLineMetrics() as an array. The code declares this shape inline; LineMetric is its name on this page.

ts
type LineMetric = {
    /** Route id. */
    routeId: string;
    /** Route bullet text. */
    routeBullet: string;
    /** Route color; CSS color string. */
    routeColor: string;
    /** Trains whose `routeId` is the route. */
    trainCount: number;
    /** 3600 / (last `stComboTimings` departureTime / `trainCount`). */
    trainsPerHour: number | null;
    /** People per game hour who finished a commute on the route in the last 900 game seconds. */
    ridersPerHour: number;
    /** `ridersPerHour` times the global ticket price times RULES.FARE_MULTIPLIER (365). */
    revenuePerHour: number;
};
FieldTypeDescription
routeIdstringRoute id.
routeBulletstringRoute bullet text.
routeColorstringRoute color; CSS color string.
trainCountnumberTrains whose routeId is the route.
trainsPerHournumber or null3600 / (last stComboTimings departureTime / trainCount); null with no trains, no timings or a zero final departureTime.
ridersPerHournumberPeople per game hour who finished a commute on the route in the last 900 game seconds.
revenuePerHournumberridersPerHour times the global ticket price times RULES.FARE_MULTIPLIER (365); fare groups are ignored; $.

Route Financials#

Returned by api.gameState.getRouteFinancials() as an array. The code declares this shape inline; RouteFinancials is its name on this page.

ts
type RouteFinancials = {
    /** Route id. */
    routeId: string;
    /** Route bullet text. */
    routeBullet: string;
    /** Route color; CSS color string. */
    routeColor: string;
    /** Completed game hours, oldest first, at most 336 (14 game days). */
    hourly: Array<{ timestamp: number; revenue: number; expenses: number }>;
    /** Amounts of the hour in progress; they move to `hourly` when the hour completes. */
    currentHour: { revenue: number; expenses: number };
    /** Sums of the last 24 entries of `hourly` plus `currentHour` (when nonzero). */
    last24Hours: { revenue: number; expenses: number; profit: number };
};
FieldTypeDescription
routeIdstringRoute id.
routeBulletstringRoute bullet text.
routeColorstringRoute color; CSS color string.
hourlyArray<{ timestamp: number; revenue: number; expenses: number }>Completed game hours, oldest first, at most 336 (14 game days); timestamp is the hour's start in game seconds; hours with no revenue and no expenses are skipped; $.
currentHour{ revenue: number; expenses: number }Amounts of the hour in progress; they move to hourly when the hour completes; $.
last24Hours{ revenue: number; expenses: number; profit: number }Sums of the last 24 entries of hourly plus currentHour (when nonzero); profit is revenue minus expenses; $.

Mode Choice Stats#

Returned by api.gameState.getModeChoiceStats(). The code declares this shape inline; ModeChoiceStats is its name on this page.

ts
type ModeChoiceStats = {
    /** People whose displayed commute walks; summed over pops. */
    walking: number;
    /** People whose displayed commute drives; summed over pops. */
    driving: number;
    /** People whose displayed commute rides transit. */
    transit: number;
    /** People with no displayed commute (whole pop `size`) plus the unknown share of simulated pops. */
    unknown: number;
};
FieldTypeDescription
walkingnumberPeople whose displayed commute walks; summed over pops.
drivingnumberPeople whose displayed commute drives; summed over pops.
transitnumberPeople whose displayed commute rides transit; summed over pops.
unknownnumberPeople with no displayed commute (whole pop size) plus the unknown share of simulated pops.

Station Ridership#

Returned by api.gameState.getStationRidership(). The code declares this shape inline; StationRidership is its name on this page.

ts
type StationRidership = {
    /** People (summed `size`) in snapshot commutes touching the station. */
    total: number;
    /** People in those commutes for which the station appears in more than one leg … */
    transfers: number;
    /** Per route: `popCount` people (each route once per commute … */
    byRoute: Array<{ routeId: string; popCount: number; percent: number }>;
};
FieldTypeDescription
totalnumberPeople (summed size) in snapshot commutes touching the station.
transfersnumberPeople in those commutes for which the station appears in more than one leg, boards a leg after the first or alights a leg before the last; 0 when stationId is null.
byRouteArray<{ routeId: string; popCount: number; percent: number }>Per route: popCount people (each route once per commute, legs touching the station only when stationId is set), percent of total; sorted by popCount descending.

Route Ridership#

Returned by api.gameState.getRouteRidership(). The code declares this shape inline; RouteRidership is its name on this page.

ts
type RouteRidership = {
    /** People (summed `size`) in snapshot commutes with a leg on the route. */
    total: number;
    /** Per station: `popCount` people (boarded or alighted there on the route, once per commute) … */
    byStation: Array<{ stationId: string; popCount: number; percent: number }>;
};
FieldTypeDescription
totalnumberPeople (summed size) in snapshot commutes with a leg on the route.
byStationArray<{ stationId: string; popCount: number; percent: number }>Per station: popCount people (boarded or alighted there on the route, once per commute), percent of total (0 when total is 0); sorted by popCount descending.

Blueprint Cost#

Returned by api.gameState.calculateBlueprintCost(). The code declares this shape inline; BlueprintCost is its name on this page.

ts
type BlueprintCost = {
    /** Full construction cost; $; includes cost lines the breakdown omits. */
    totalCost: number;
    /** Partial cost lines; $: `trackCost` (track plus yard trackage), `stationCost`, `buildingDemolitionCost`. */
    breakdown: { trackCost: number; stationCost: number; buildingDemolitionCost: number; };
};
FieldTypeDescription
totalCostnumberFull construction cost; $; includes cost lines the breakdown omits.
breakdown{ trackCost: number; stationCost: number; buildingDemolitionCost: number; }Partial cost lines; $: trackCost (track plus yard trackage), stationCost, buildingDemolitionCost.

Common Enums#

String values used across the types above. Id types are plain strings that registries extend; the values listed are the built-in ones.

ts
/** Build state of a track, station or route part. */
type BuildType = 'constructed' | 'blueprint' | 'preview';

/** Track geometry. */
type CurveType = 'hobby' | 'straight' | 'quadratic-start' | 'quadratic-end' | 'cubic' | 'euler-quadratic-start' | 'euler-quadratic-end' | 'euler-cubic';

/** A key of the train type registry (`TRAIN_TYPES`), so any string a mod registers with `trains.registerTrainType`. */
type TrainTypeId = string; // built in: 'heavy-metro', 'light-metro', 'light-rail', 'commuter-rail', 'commuter-rail-diesel'

/** A track type named in some train type's `compatibleTrackTypes`. */
type TrackTypeId = string;

/** A key of the station type registry (`STATION_TYPES`), so any string a mod registers with `stations.registerStationType`. */
type StationTypeId = string; // built in: 'standard'

/** Route bullet shape; absent draws a circle. */
type RouteShape = 'circle' | 'diamond' | 'square' | 'rounded-square' | 'triangle';

/** Bullet text styling. */
type RouteBulletFont = 'default' | 'mono' | 'bolder' | 'lighter' | 'italic';

/** How the schematic diagram labels a route along its ribbon. */
type RouteCatStyle = 'bullet' | 'text';

/** How fares are priced within a fare group. */
type FareSystem = 'flat' | 'route' | 'distance' | 'zone';

/** How transfers are priced for a fare group (the policy of the group being boarded). */
type FareTransferPolicy = 'all-paid' | 'count-within-group' | 'count-all-groups' | 'free-within-group';

/** How a distance-based fare group measures the distance a journey leg is charged for. */
type FareDistanceMode = 'straight-line' | 'track';

/** How a zone-based fare group counts zones for pricing. */
type FareZonePricingMode = 'count' | 'combination';

/** Keys of `AllTrainWindowCollections`: the occupied-track windows each train holds. */
type TrainWindowType = 'warningExtra' | 'warning' | 'train';

/** Mode of a route's timetable schedule: `frequency` runs the demand-tier counts in `trainSchedule`, `timetable` the headways per period. */
type SchedulingMode = 'frequency' | 'timetable';

/** Part of a track a signal covers: progress `start` to `end` in m, or the whole track. */
type SignalTrackCoverage = { start: number; end: number; } | 'all';

/** Station id of a journey leg end, or `origin` and `destination` for the walks at either end. */
type RaptorStationId = string | 'origin' | 'destination';

/** How challenging a city is to build a successful network in, shown as a badge on the city-select screen. */
type CityDifficulty = 'very-easy' | 'easy' | 'medium' | 'hard' | 'very-hard';

/** Country code of a city; the city picker filters and shows flags by it. */
type Country = 'US' | 'UK' | 'CA' | 'IE' | 'FR' | 'JP' | 'NZ' | 'CH' | 'AU';

BuildType#

Values: 'constructed', 'blueprint', 'preview'. Build state of a track, station or route part.

CurveType#

Values: 'hobby', 'straight', 'quadratic-start', 'quadratic-end', 'cubic', 'euler-quadratic-start', 'euler-quadratic-end', 'euler-cubic'. Track geometry. New track is hobby or straight; the other values appear only in saves from older versions.

TrainTypeId#

string; built in: 'heavy-metro', 'light-metro', 'light-rail', 'commuter-rail', 'commuter-rail-diesel'. A key of the train type registry (TRAIN_TYPES), so any string a mod registers with trains.registerTrainType.

TrackTypeId#

Type: string. A track type named in some train type's compatibleTrackTypes. Each built-in train type runs on the track type of the same id; commuter-rail-diesel also runs on commuter-rail track.

StationTypeId#

string; built in: 'standard'. A key of the station type registry (STATION_TYPES), so any string a mod registers with stations.registerStationType.

RouteShape#

Values: 'circle', 'diamond', 'square', 'rounded-square', 'triangle'. Route bullet shape; absent draws a circle.

RouteBulletFont#

Values: 'default', 'mono', 'bolder', 'lighter', 'italic'. Bullet text styling.

RouteCatStyle#

Values: 'bullet', 'text'. How the schematic diagram labels a route along its ribbon.

FareSystem#

Values: 'flat', 'route', 'distance', 'zone'. How fares are priced within a fare group.

FareTransferPolicy#

Values: 'all-paid', 'count-within-group', 'count-all-groups', 'free-within-group'. How transfers are priced for a fare group (the policy of the group being boarded).

FareDistanceMode#

Values: 'straight-line', 'track'. How a distance-based fare group measures the distance a journey leg is charged for.

FareZonePricingMode#

Values: 'count', 'combination'. How a zone-based fare group counts zones for pricing.

TrainWindowType#

Values: 'warningExtra', 'warning', 'train'. Keys of AllTrainWindowCollections: the occupied-track windows each train holds.

SchedulingMode#

Values: 'frequency', 'timetable'. Mode of a route's timetable schedule: frequency runs the demand-tier counts in trainSchedule, timetable the headways per period.

SignalTrackCoverage#

Type: { start: number; end: number; } or 'all'. Part of a track a signal covers: progress start to end in m, or the whole track.

RaptorStationId#

Type: string or 'origin' or 'destination'. Station id of a journey leg end, or origin and destination for the walks at either end.

CityDifficulty#

Values: 'very-easy', 'easy', 'medium', 'hard', 'very-hard'. How challenging a city is to build a successful network in, shown as a badge on the city-select screen.

Country#

Values: 'US', 'UK', 'CA', 'IE', 'FR', 'JP', 'NZ', 'CH', 'AU'. Country code of a city; the city picker filters and shows flags by it.