popTiming

Commute timing: the time-of-day ranges that weight when pops leave home and work, the student and airport dampenings, and the hour-to-demand-tier table.

api.popTiming. 14 members. Overrides are module globals shared by every city for the rest of the session. reloadMods clears every override before mod scripts run again. A city load does not. The active ranges and dampenings are read when pops get departure times: city load, the empty-map reset, demand.addPops and the save-load demand overlay. Pops already placed keep their times. The hour-to-demand-tier table is a fixed constant and ignores range edits.

MemberReturnsSummary
getCommuteTimeRanges()TimeOfDayRange[]The active time-of-day ranges.
setCommuteTimeRanges(ranges)voidReplace the whole range table, or restore the defaults with null.
resetCommuteTimeRanges()voidDrop the range override. The built-in table is active again.
getCommuteTimeRange(key)TimeOfDayRange or undefinedThe first active range whose key matches.
setCommuteTimeRange(key, partial)booleanMerge partial into the first active range with that key.
resetCommuteTimeRange(key)booleanRestore one range to its built-in values. The others are left as they are.
getCurrentCommuteTimeRange()TimeOfDayRange or undefinedThe first active range whose start <= current game hour < end.
getStudentDampening()numberThe dampening applied to pops whose job id starts with UNI_.
setStudentDampening(value)voidFlatten the departure curve of pops whose job id starts with UNI_.
getAirportDampening()numberThe dampening applied to pops whose job id starts with AIR_.
setAirportDampening(value)voidFlatten the departure curve of pops whose job id starts with AIR_.
resetCommuteDampening()voidRestore both dampenings: student 0.3, airport 0.5.
getHourDemandLevels()Record<number, DemandLevel>The hour-to-demand-tier table. Train scheduling reads it to pick the train count of each route with a trainSchedule that is not in timetable mode.
getDemandLevel(hour?)DemandLevel or nullThe demand tier for an hour, or for the current game hour when hour is omitted.

Demand Windows#

Default Demand Windows#

The built-in table that resetCommuteTimeRanges restores. Hours are game hours, start inclusive, end exclusive. Home demand weights home-to-work departures, work demand weights work-to-home departures.

KeyHoursHome demandWork demand
LateNight0 to 30.150.15
EarlyMorning3 to 60.30.3
EarlyMorningRush6 to 710.3
PeakMorningRush7 to 102.50.3
LateMorningRush10 to 1110.8
Midday11 to 150.80.8
EarlyEveningRush15 to 160.81
PeakEveningRush16 to 190.32.5
LateEveningRush19 to 200.31
EarlyNight20 to 230.30.3
Night23 to 240.150.15

getCommuteTimeRanges#

api.popTiming.getCommuteTimeRanges(): TimeOfDayRange[]

The active time-of-day ranges.

Returns: TimeOfDayRange[]. the live array: the array passed to setCommuteTimeRanges, or the built-in default table when no override is set; an empty array on an internal error. Mutating the default table changes the defaults the reset members restore.

js
for (const r of api.popTiming.getCommuteTimeRanges()) {
    console.log(`${r.key} ${r.start}-${r.end}h: home x${r.homeDemandMultiplier}, work x${r.workDemandMultiplier}`);
}

setCommuteTimeRanges#

api.popTiming.setCommuteTimeRanges(ranges: TimeOfDayRange[] | null): void

Replace the whole range table, or restore the defaults with null.

Every range is validated before any is applied. The first bad entry rejects the call. The array is stored by reference. key and name are not validated. getCommuteTimeRange, setCommuteTimeRange, resetCommuteTimeRange and getCurrentCommuteTimeRange act on the first match. Duplicate keys and overlapping hours are not rejected. Commute generation rounds fractional bounds outward to whole hours and takes the higher multiplier where ranges overlap. Logs [Modding API] Setting <n> custom commute time ranges (development builds only) or [Modding API] Resetting commute time ranges to defaults (development builds only). Applies to pops that get departure times after the call: city load, the empty-map reset, demand.addPops and the save-load demand overlay. Pops already placed keep their times.

Parameter ranges (TimeOfDayRange[] | null): TimeOfDayRange[] with numeric start, end, homeDemandMultiplier, workDemandMultiplier; start at least 0 and below 24, end greater than start and at most 24; or null to reset.

Errors:

  • neither array nor null: logs [Modding API] setCommuteTimeRanges requires an array or null, does nothing.
  • a field that is not a finite number: logs [Modding API] Invalid time range - must have numeric start, end, homeDemandMultiplier, and workDemandMultiplier, does nothing.
  • hours out of bounds: logs [Modding API] Invalid time range - start must be 0-23, end must be greater than start and <= 24, does nothing.

Side effects: replaces the commute-time override global.

Changed: fractional start and end are rounded outward to whole hours in commute generation; before, a fractional start made the whole range a no-op (PR #1090).

js
api.popTiming.setCommuteTimeRanges([
  { key: 'Off', name: 'Off Peak', start: 0, end: 7, homeDemandMultiplier: 0.25, workDemandMultiplier: 0.25 },
  { key: 'Morning', name: 'Morning', start: 7, end: 10, homeDemandMultiplier: 2.5, workDemandMultiplier: 0.3 },
  { key: 'Day', name: 'Day', start: 10, end: 24, homeDemandMultiplier: 0.5, workDemandMultiplier: 1 },
]);

resetCommuteTimeRanges#

api.popTiming.resetCommuteTimeRanges(): void

Drop the range override. The built-in table is active again.

Logs [Modding API] Resetting commute time ranges to defaults (development builds only). Applies to pops that get departure times after the call: city load, the empty-map reset, demand.addPops and the save-load demand overlay. Pops already placed keep their times.

Side effects: clears the commute-time override global.

Changed: reloadMods clears the range and dampening overrides before mod scripts re-run; before, overrides survived a reload (PR #1090).

js
api.popTiming.setCommuteTimeRange('PeakMorningRush', { start: 8, end: 11 });
api.popTiming.resetCommuteTimeRanges(); // every range back to the built-in table

getCommuteTimeRange#

api.popTiming.getCommuteTimeRange(key: string): TimeOfDayRange | undefined

The first active range whose key matches.

Parameter key (string): range key, for example 'PeakMorningRush'.

Returns: TimeOfDayRange | undefined. the live range object; undefined when no range has that key or on an internal error.

js
const rush = api.popTiming.getCommuteTimeRange('PeakMorningRush');
if (rush) console.log(`Morning rush ${rush.start}:00 to ${rush.end}:00`);

setCommuteTimeRange#

api.popTiming.setCommuteTimeRange(key: string, partial: Partial<TimeOfDayRange>): boolean

Merge partial into the first active range with that key.

Omitted fields keep their values. A key inside partial is ignored. The merged end must be greater than the merged start. A call on the default table turns it into an override copy, so later reset members restore the built-in values. Logs [Modding API] Updated commute time range <key> (development builds only) on success. Applies to pops that get departure times after the call: city load, the empty-map reset, demand.addPops and the save-load demand overlay. Pops already placed keep their times.

ParamTypeDefaultDescription
keystringrequirednon-empty range key.
partialPartial<TimeOfDayRange>requiredany of start (at least 0, below 24), end (above 0, at most 24), homeDemandMultiplier, workDemandMultiplier, name.

Returns: boolean. true when a range with that key existed and was updated; false otherwise.

Errors:

  • empty or non-string key: logs [Modding API] setCommuteTimeRange requires a non-empty string key, returns false.
  • partial not an object: logs [Modding API] setCommuteTimeRange requires an object of fields to update, returns false.
  • non-numeric numeric field: logs [Modding API] setCommuteTimeRange <field> must be a number, returns false.
  • start out of range: logs [Modding API] setCommuteTimeRange start must be in [0, 24), returns false.
  • end out of range: logs [Modding API] setCommuteTimeRange end must be in (0, 24], returns false.
  • merged end <= start: logs [Modding API] setCommuteTimeRange end must be greater than start, returns false.
  • unknown key: logs [Modding API] setCommuteTimeRange: no range with key "<key>", returns false.

Side effects: replaces the commute-time override global with a copied table.

Changed: the merged range is checked for end greater than start (PR #1090).

js
api.popTiming.setCommuteTimeRange('PeakMorningRush', { start: 8, end: 11 });
api.popTiming.setCommuteTimeRange('PeakEveningRush', { workDemandMultiplier: 3 });

resetCommuteTimeRange#

api.popTiming.resetCommuteTimeRange(key: string): boolean

Restore one range to its built-in values. The others are left as they are.

Logs [Modding API] Reset commute time range <key> to default (development builds only) on success. Applies to pops that get departure times after the call: city load, the empty-map reset, demand.addPops and the save-load demand overlay. Pops already placed keep their times.

Parameter key (string): key of a built-in range that is also present in the active table.

Returns: boolean. true when restored; false when the key is not in the built-in table or not in the active table.

Errors: key not in the built-in table or not in the active table: logs [Modding API] resetCommuteTimeRange: no default range with key "<key>", returns false.

Side effects: replaces the commute-time override global with a copied table.

js
api.popTiming.setCommuteTimeRange('PeakEveningRush', { workDemandMultiplier: 3 });
api.popTiming.resetCommuteTimeRange('PeakEveningRush');

getCurrentCommuteTimeRange#

api.popTiming.getCurrentCommuteTimeRange(): TimeOfDayRange | undefined

The first active range whose start <= current game hour < end.

Returns: TimeOfDayRange | undefined. a shallow copy of the range; undefined when no range covers the hour or on an internal error.

js
const range = api.popTiming.getCurrentCommuteTimeRange();
console.log(range ? `Now: ${range.name}` : 'No range covers this hour');

Commute Curve Dampening#

Pops whose job id starts with UNI_ (university students) or AIR_ (airport workers) get a flatter departure curve than regular commuters. The dampening is a factor from 0 to 1 that moves each hour's weight toward the day's mean: weight * (1 - d) + mean * d. At 0 the pop follows the demand windows unchanged; at 1 every hour is equally likely.

Pop typeJob id prefixDefaultGetterSetter
University studentsUNI_0.3getStudentDampeningsetStudentDampening
Airport workersAIR_0.5getAirportDampeningsetAirportDampening

Airport curves are also mirrored after dampening: each hour's home and work weights are replaced by their average, so flows in both directions match. Dampening is read when pops get departure times, like the demand windows.

getStudentDampening#

api.popTiming.getStudentDampening(): number

The dampening applied to pops whose job id starts with UNI_.

Returns: number. 0 to 1; 0.3 when no override is set; 0 on an internal error.

js
console.log(`Student dampening: ${api.popTiming.getStudentDampening()}`); // 0.3 by default

setStudentDampening#

api.popTiming.setStudentDampening(value: number | null): void

Flatten the departure curve of pops whose job id starts with UNI_.

Each hour's weight becomes raw * (1 - value) + mean * value, so 0 is the raw curve and 1 is uniform. Logs [Modding API] Setting student dampening to <value> (development builds only) or [Modding API] Resetting student dampening (development builds only). Applies to pops that get departure times after the call: city load, the empty-map reset, demand.addPops and the save-load demand overlay. Pops already placed keep their times.

Parameter value (number | null): 0 to 1, or null to restore 0.3.

Errors: out of range, NaN or not a number: logs [Modding API] setStudentDampening requires a number between 0 and 1, or null, does nothing.

Side effects: replaces the student dampening global.

js
// 0 keeps the raw departure curve for UNI_ pops, 1 spreads it evenly; null resets
api.popTiming.setStudentDampening(0.6);

getAirportDampening#

api.popTiming.getAirportDampening(): number

The dampening applied to pops whose job id starts with AIR_.

Returns: number. 0 to 1; 0.5 when no override is set; 0 on an internal error.

js
const d = api.popTiming.getAirportDampening();
console.log(`AIR_ pops flattened by ${d}`);

setAirportDampening#

api.popTiming.setAirportDampening(value: number | null): void

Flatten the departure curve of pops whose job id starts with AIR_.

Each hour's weight becomes raw * (1 - value) + mean * value. Airport pops also average the home and work weights per hour, so both directions flow equally. Logs [Modding API] Setting airport dampening to <value> (development builds only) or [Modding API] Resetting airport dampening (development builds only). Applies to pops that get departure times after the call: city load, the empty-map reset, demand.addPops and the save-load demand overlay. Pops already placed keep their times.

Parameter value (number | null): 0 to 1, or null to restore 0.5.

Errors: out of range, NaN or not a number: logs [Modding API] setAirportDampening requires a number between 0 and 1, or null, does nothing.

Side effects: replaces the airport dampening global.

js
// airports run around the clock: flatten AIR_ departures completely
api.popTiming.setAirportDampening(1);

resetCommuteDampening#

api.popTiming.resetCommuteDampening(): void

Restore both dampenings: student 0.3, airport 0.5.

Logs [Modding API] Resetting commute dampening to defaults (development builds only). Applies to pops that get departure times after the call: city load, the empty-map reset, demand.addPops and the save-load demand overlay. Pops already placed keep their times.

Side effects: clears both dampening globals.

js
api.popTiming.setStudentDampening(0);
api.popTiming.resetCommuteDampening(); // student and airport back to their defaults

Demand Levels#

getHourDemandLevels#

api.popTiming.getHourDemandLevels(): Record<number, DemandLevel>

The hour-to-demand-tier table. Train scheduling reads it to pick the train count of each route with a trainSchedule that is not in timetable mode.

The table is a constant: hours 0 to 2 and 23 'veryLow'; 3 to 5 and 20 to 22 'low'; 6, 10 to 15 and 19 'medium'; 7 to 9 and 16 to 18 'high'. Range edits do not change it.

Returns: Record<number, DemandLevel>. a shallow copy keyed by hour 0 to 23; an empty object on an internal error.

js
const levels = api.popTiming.getHourDemandLevels();
const peakHours = Object.keys(levels).filter((h) => levels[h] === 'high');
console.log(`High-demand hours: ${peakHours.join(', ')}`); // 7-9 and 16-18

getDemandLevel#

api.popTiming.getDemandLevel(hour?: number): DemandLevel | null

The demand tier for an hour, or for the current game hour when hour is omitted.

The current hour is floor((elapsedSeconds mod 86400) / 3600).

Parameter hour (number): integer 0 to 23; omitted means the current game hour.

Returns: DemandLevel | null. 'veryLow', 'low', 'medium' or 'high'; null for an invalid hour or on an internal error.

Errors: non-integer or out-of-range hour: logs [Modding API] getDemandLevel: hour must be an integer in [0, 23], returns null.

js
api.hooks.onHourChange((hour) => {
    if (api.popTiming.getDemandLevel(hour) === 'high') console.log(`Hour ${hour}: peak schedules running`);
});

TimeOfDayRange#

One commute window of the time-of-day table.

FieldDescription
keyidentifier used by the per-range members. The type names the 11 built-in keys; the implementation accepts any string.
namedisplay label; built-in ranges hold the i18n key, for example timeOfDay.peakMorningRush.
startfirst hour, inclusive; at least 0, below 24; fractions allowed.
endlast hour, exclusive; above start, at most 24; fractions allowed.
homeDemandMultiplierweight for home-to-work departures in this window; ratio.
workDemandMultiplierweight for work-to-home departures in this window; ratio.

The built-in values are in the Default Demand Windows table on the popTiming page.

type TimeOfDayRange = ReturnType<typeof getTimeOfDayRanges>[number]

DemandLevel#

'high' | 'medium' | 'low' | 'veryLow'. The tier a route's trainSchedule maps to a train count for each hour.

type DemandLevel = 'high' | 'medium' | 'low' | 'veryLow'