====== Create Timetables ====== Timetables define the **AI service pattern** on a map: * which trains spawn (compositions) * which route they drive (stops) * on which platforms they stop * when and how often services repeat * how depot and dispatching logic supports the timetable traffic Timetables are usually created in your map's **Map.lua** inside: * `function :loadTimetables()` This page explains the full **timetable workflow** using the TestMap example. Related: [[manual:subwaysim:map_construction:create_maplua|Create Map.lua]] ---- ===== Prerequisites ===== Before creating timetables, ensure: * stations are fully defined in `loadStations()` * platform numbers / lengths / directions are final * the map loads correctly without timetables first (recommended) Timetables reference station objects, so stations must exist before you build schedules. ---- ===== Timetables in Map.lua (Where they “live”) ===== A typical `loadTimetables()` builds: * `self.timetables` — list of actual service entries (the ones that spawn trains) * templates per line/direction (used for cloning) * optional lookup tables (`self.templatesByLine`, `self.templatesByDirection`) * depot spaces (`self.depots`) * dispatching strategies (`self.dispatchingStrategies`) At the end, the map registers them via: controlCenter:setTimetableList(self.timetables, self.dispatchingStrategies, self.depots); ---- ===== 6) Timetables ===== Timetables define: * which trains spawn (compositions) * what route they drive (stops) * on which platforms they stop * how often services repeat ==== 6.1 Timetable:new() ==== A timetable template is created with: Timetable:new("U1", 0) ^ Parameter ^ Description ^ | `"U1"` | Line name used for UI and routing logic. | | `0` | Variant / index value (map-specific usage). | Templates are usually created per direction: * Direction 1: TS → TSA * Direction 2: TSA → TS ==== 6.2 Train Compositions ==== Train compositions define **which vehicle sets may be used** when this timetable spawns AI trains. Each composition is referenced by its `contentName`, exactly as it is registered in the ContentManager (e.g. in `Vehicle.lua` or `Composition.lua`). Example: contentName = "Berlin_HK_2x" The name must match **exactly**. If a composition is not registered or the name is incorrect, no train will spawn for it. ==== Adding Compositions to a Timetable ==== Compositions are added directly to the timetable template: :addTrainComposition("Berlin_HK_2x") A weight can optionally be provided: :addTrainComposition("Berlin_HK_1x", 0.5) If no weight is specified, a default weight of `1.0` is assumed. ==== Composition Weights ==== Weights control how likely a composition is selected **relative to other compositions** within the same timetable. They only affect **AI spawning behavior** for this timetable. ^ Weight ^ Meaning ^ | **1.0** | Standard usage. The composition is commonly selected. | | **0.5** | Reduced probability. The composition is used less frequently. | | **0.0** | The composition is excluded from AI spawning for this timetable. | ==== Important Behavior of Weight = 0.0 ==== A weight of `0.0` prevents the composition from being selected when AI trains are spawned by this timetable. The composition itself remains a valid registered vehicle and may still: * exist in the content system * appear in the vehicle selection menu This behavior is intentional and allows a composition to be: * available to the player * but excluded from AI traffic on a specific line or timetable ==== 6.3 addStop() (Route Definition) ==== Stops define the actual route of a timetable. Each stop is a table passed into `addStop({ ... })`. Example: :addStop({ station = self.stations.TS, platform = 2, departure = 0, speedLimit = 70, routeSettingMaxETA = 0.5, }) ^ Field ^ Type ^ Description ^ | station | Station | Reference to a station defined in `loadStations()`. | | platform | number | Platform number the train uses at this station. Must exist in BP_StationDefinition. | | departure | number | Minutes after service start/spawn when the train departs this stop. | | speedLimit | number | Speed limit applied after departing this stop (signal logic dependent). | | routeSettingMaxETA | number (optional) | How many minutes before departure the route (Fahrstraße) should be requested/set. | | altPlatform | table (optional) | Alternative platforms that may be used if the primary platform is unavailable. | ==== 6.4 altPlatform (Alternative Platforms) ==== Example: altPlatform = { "2", } ⚠️ Use string values (`"1"`, `"2"`) because platform identifiers are typically handled as strings in routing/dispatch contexts. This allows AI to select another platform if: * the preferred platform is blocked * dispatching assigns an alternative ==== 6.5 routeSettingMaxETA (Route Pre-Setting) ==== Example: routeSettingMaxETA = 0.5 Meaning: * the route will be requested/updated roughly **0.5 minutes before departure** This can help avoid early route locking and improves traffic handling at busy stations. ==== 6.6 clone() + DayMask (Creating Services) ==== A timetable template does not spawn trains by itself. It must be cloned into **real timetable entries** and inserted into `self.timetables`. For day-based schedules, use `DayMask`: local DM = DayMask; ^ DayMask ^ Meaning ^ | DM.Weekdays | Monday to Friday | | DM.Weekends | Saturday and Sunday | | DM.Sat | Saturday only | | DM.Sun | Sunday only | | DM.Always | Every day | The typical workflow is: * Create a timetable template (e.g. `self.TestLine_Dir1`) * Clone it for a start time (optionally with a day mask) * Either insert a single trip, or generate a repeating service ==== 6.6.1 Repeating Services (Interval Based) ==== This creates a repeating service between two times: TableUtil.insertList( self.timetables, self.TestLine_Dir1:clone(daytime(04, 30), DM.Weekdays):repeatUntil(daytime(23, 30), 10) ); TableUtil.insertList( self.timetables, self.TestLine_Dir2:clone(daytime(04, 35), DM.Weekdays):repeatUntil(daytime(23, 35), 10) ); ^ Call ^ Description ^ | clone(daytime(HH, MM), DayMask) | Creates the first entry at a given time (filtered by the day mask). | | repeatUntil(daytime(HH, MM), interval) | Repeats every X minutes until the end time. | | TableUtil.insertList(list, result) | Inserts all generated entries into `self.timetables`. | Result: * a full service pattern is generated automatically ==== 6.6.2 Single Timetable Entries (Manual Trips) ==== If you want to schedule **individual trips** (first/last train, gaps, special runs), insert a single cloned entry: table.insert(self.timetables, self.TestLine_Dir1:clone(daytime(12, 07), DM.Weekdays)); table.insert(self.timetables, self.TestLine_Dir2:clone(daytime(12, 12), DM.Weekdays)); This creates exactly one departure at the given time. Use this approach when you need full control over: * exact departure times * exceptions or gaps * different patterns on different days ==== 6.7 Useful Variations (Based on the TestMap) ==== The following patterns are commonly used when building more advanced schedules. They are shown here using the **TestMap stations** (`TS`, `TSD`, `TSA`, `DP`). ==== 6.7.1 Short Runs (Start or Terminate Early) ==== Sometimes a service should start later or terminate earlier than the full route. This is useful for: * depot in/out runs * special services * partial line operations Example: terminate at `TSD` (short turn / depot related movement): local TS_to_TSD = self.TestLine_Dir1:clone(0, nil, true):terminateAtStation("TSD", true); table.insert(self.timetables, TS_to_TSD:clone(daytime(05, 10), DM.Weekdays)); Example: start at `TSD` (depot insertion into service): local TSD_to_TSA = self.TestLine_Dir1:clone(0, nil, true):startAtStation("TSD", true); table.insert(self.timetables, TSD_to_TSA:clone(daytime(05, 20), DM.Weekdays)); ==== 6.7.2 Platform Overrides (TestMap Example) ==== If you want to use the same template but spawn on a different platform, you can override platform numbers after cloning. Example: force first stop to use platform 1 instead of 2 at TS: local TS_Platform1 = self.TestLine_Dir1:clone(0, nil, true); TS_Platform1:getFirstStop().platform = 1; table.insert(self.timetables, TS_Platform1:clone(daytime(06, 00), DM.Weekdays)); This is useful if: * multiple platforms exist * you want different patterns at different times of day * you temporarily reroute services during testing ==== 6.7.3 Service Runs (Non-Passenger Moves) ==== A service run is a trip that should not be treated as a normal passenger service. Example: a depot-related move to TS marked as service run: local DP_to_TS_SR = self.TestLine_Dir2:clone(0, nil, true) :startAtStation("DP", true) :terminateAtStation("TS", true) :setIsServiceRun(true); table.insert(self.timetables, DP_to_TS_SR:clone(daytime(04, 10), DM.Weekdays)); ==== 6.7.4 Force Unique Stop Lists (Safe Editing) ==== If you modify stop properties (platforms, PIS text, flags), it can be helpful to ensure the stop list is unique: local Variant = self.TestLine_Dir1:clone(0, nil, true); Variant:forceUniqueStopList(); This prevents accidental shared stop references when creating multiple variants. ---- ===== 7) Depots and Dispatching ===== This section defines: * depot storage spaces (optional) * turnaround / dispatch behavior at stations ==== 7.1 Depots (Parking Tracks / Depot Spaces) ==== Depots define **where AI trains are allowed to park** when they are not in service. They are also used by the ControlCenter for dispatching and (later) career mode. In the TestMap, the depot station is: * `DP` (Depot) And it provides these depot tracks: * 51, 52, 53, 54 (long depot tracks) * 60 (short depot / test track) ==== 7.1.1 Depot Table Structure ==== `self.depots` is a table that groups depot tracks into named blocks (groups). Each group contains a list of `Depot_DepotSpace` entries. ^ Field ^ Meaning ^ | station | Station reference (must exist in `self.stations`) | | platform | Track / platform ID as defined in the BP_StationDefinition | | direction | Which direction trains should park/spawn facing (1 or 2) | | noParkingTimetable | If true: no dedicated parking timetable should be generated for this track (useful to keep a track free) | ==== 7.1.2 TestMap Example (DP 51–54 + 60) ==== ---@type table self.depots = { -- Main depot area (long tracks) ["DP_51_54"] = { { station = self.stations.DP, platform = "51", direction = 2, noParkingTimetable = false }, { station = self.stations.DP, platform = "52", direction = 2, noParkingTimetable = false }, { station = self.stations.DP, platform = "53", direction = 2, noParkingTimetable = false }, { station = self.stations.DP, platform = "54", direction = 2, noParkingTimetable = false }, }, -- Short depot / test track (useful to keep free or for special moves) ["DP_60"] = { { station = self.stations.DP, platform = "60", direction = 2, noParkingTimetable = true }, }, }; Notes: * The group keys (`"DP_51_54"`, `"DP_60"`) are just identifiers for readability. * `platform` must match the platform numbers inside your **BP_StationDefinition** for DP. * Use `noParkingTimetable = true` if you want to keep a track free (e.g. for turnarounds or testing). ==== 7.2 Dispatching Strategies ==== Dispatching Strategies define how trains are handled **outside of normal passenger service**. They are used to: * turn trains around at terminal stations, * spawn trains from depots, * send trains back to depots, * resolve platform conflicts, * and keep traffic flowing when timetables alone are not sufficient. Dispatching is handled by the **ControlCenter** and works in addition to normal timetables. ==== 7.2.1 Basic Structure ==== Dispatching strategies are defined as a table, grouped by station: ---@type table self.dispatchingStrategies = { [self.stations.WA] = { -- strategies for this station }, } Each station can have **multiple strategies**. They are evaluated **top to bottom**, so order matters. ==== 7.2.2 When Dispatching Is Used ==== Dispatching strategies are evaluated when: * a timetable ends at a station, * a train needs to turn around, * no suitable train is available for a departure, * or a train must be moved to or from a depot. If no strategy matches, the train will remain idle. ==== 7.2.3 Strategy Fields ==== Each dispatching strategy can define the following fields: ^ Field ^ Description ^ | sourceStation | Station where the train currently is. Use `nil` for depot spawns. | | targetStation | Station the train should serve next. Use `nil` for depot despawn. | | sourcePlatforms | Allowed platforms the train may come from. | | targetPlatforms | Allowed platforms the train may go to. | | depotName | Name of the depot (as defined in the depots table). | | minLayover | Minimum minutes the train must wait before reuse. | | keepLine | Try to keep the train on the same line. | | replaceFirstPlatform | Replace the first stop platform if needed. | | replaceLastPlatform | Replace the last stop platform if needed. | | overrideFirstPlatform | Force a specific first platform. | | overrideLastPlatform | Force a specific last platform. | | timetable | Optional hidden timetable used for movements. | Not all fields are required for every strategy. ==== 7.2.4 Pattern A — Simple Turnaround ==== This is the most common case: A train arrives at a station and turns around to serve the opposite direction. Example (Test Map): { sourceStation = self.stations.Kbo, targetStation = self.stations.Kbo, sourcePlatforms = { "1" }, targetPlatforms = { "2" }, minLayover = 3, } What happens: * a train arriving on platform 1 * waits at least 3 minutes * and departs again from platform 2 No depot is involved. ==== 7.2.5 Pattern B — Turnaround with Internal Movement ==== Some stations require a **shunting move** to turn a train. In this case, a hidden timetable is attached: { sourceStation = self.stations.Go, targetStation = self.stations.Go, sourcePlatforms = { "1" }, targetPlatforms = { "2" }, minLayover = 3, timetable = Timetable:new("", 0) :setIsServiceRun(true) :addStop({ station = self.stations.Go, platform = "6", departure = 2, turnAround = true, }) :addStop({ station = self.stations.Go, platform = "2", departure = 3, }), } This allows: * temporary use of siding or crossover tracks * clean turnarounds without blocking passenger platforms ==== 7.2.6 Pattern C — Spawning Trains from a Depot ==== When no train is available, dispatching can **fetch a train from a depot**. Example: { sourceStation = nil, targetStation = self.stations.WA, targetPlatforms = { "1", "2" }, depotName = "WA_06_09", overrideFirstPlatform = "3", timetable = Timetable:new("", 0) :setIsServiceRun(true) :addStop({ station = self.stations.WA, platform = "7", departure = -5, }) :addStop({ station = self.stations.WA, platform = "3", departure = -3, }), } Key points: * `sourceStation = nil` means the train comes from a depot * the depot name must match the depots table * negative departure times happen **before** the actual service ==== 7.2.7 Pattern D — Sending Trains to a Depot ==== After service ends, trains can be removed from traffic. Example: { sourceStation = self.stations.WA, sourcePlatforms = { "1", "2" }, targetStation = nil, depotName = "WA_11_18", timetable = Timetable:new("", 0) :setIsServiceRun(true) :addStop({ station = self.stations.WA, platform = "3", departure = 2, }) :addStop({ station = self.stations.WA, platform = "11", departure = 6, }), } Here: * the train leaves passenger service * moves into the depot * and is no longer available for dispatching ==== 7.2.8 Hidden Timetables ==== Timetables inside dispatching strategies: * are not shown to the player * are always marked as `setIsServiceRun(true)` * are used only for internal movements They allow precise control over: * routing * speed limits * platform usage * turnarounds ==== 7.2.9 Strategy Order ==== Dispatching strategies are evaluated **in order**. Recommended structure per station: 1. normal turnarounds 2. depot spawn strategies 3. depot despawn strategies 4. fallback strategies This avoids unnecessary depot movements and keeps traffic stable. ==== 7.2.10 Common Pitfalls ==== * depot names not matching the depots table * missing `setIsServiceRun(true)` on hidden timetables * conflicting platform definitions * wrong strategy order If dispatching behaves unexpectedly, always check the order first. ---- ===== 8) Career Mode (Optional) ===== Career Mode is optional. If you don’t want career mode features on your map, you can leave `loadCareerMode()` empty. If you *do* want career mode, this function defines: * where the player is allowed to take over a train * optional route closures (for scenario logic) * which train compositions the player is likely to get * which timetable templates are used for pathfinding ==== 8.1 Takeover Stations (Where the Player Can Start) ==== `self.cmTakeoverStations` is a list of stations where career mode allows a takeover. For the TestMap, we keep it simple: -- Initializes data for career mode (optional) function TestMap:loadCareerMode() -- Stations where the player can take over in career mode self.cmTakeoverStations = { self.stations.TS, -- TestStation (main terminus) self.stations.TSA, -- TestStation Anfang (other terminus) self.stations.DP, -- Depot (optional takeover) }; end Notes: * You reference the stations from `self.stations` (created in `loadStations()`). * If a station is missing here, the player won’t be offered takeovers there. ==== 8.2 Route Closures (Optional) ==== Route closures allow you to define sections that may be blocked in career mode. This is mainly for scenario systems and future gameplay logic. Each closure uses: * `stationSource` → start of the closed section * `stationTarget` → end of the closed section * a DayMask (e.g. `DayMask.Always`, `DayMask.Weekdays`, `DayMask.Weekends`) Example for the TestMap (optional): function TestMap:loadCareerMode() self.cmTakeoverStations = { self.stations.TS, self.stations.TSA, self.stations.DP, }; -- Optional: example closure between TS and TSA on weekends self.cmRouteClosures = { { stationSource = self.stations.TS, stationTarget = self.stations.TSA, tempClosure = DayMask.Weekends, }, }; end If you don’t need closures, simply omit `self.cmRouteClosures`. ==== 8.3 cmGroups (Train Pool + Probability) ==== `self.cmGroups` defines which train compositions can appear in career mode. Each group represents a **probability set** for vehicle selection. You can define **multiple groups**. Each group has: ^ Field ^ Meaning ^ | frequency | Probability factor in the range **0.0 – 1.0** | | compositions | List of composition `contentName` strings | Important: * `frequency = 1.0` means **100 % chance** for this group to be considered. * `frequency = 0.0` disables the group entirely. * Values between 0.0 and 1.0 reduce the chance accordingly. * The value is **not relative** to other groups. ==== Example (TestMap) ==== function TestMap:loadCareerMode() self.cmTakeoverStations = { self.stations.TS, self.stations.TSA, self.stations.DP, }; -- Career mode vehicle selection pool self.cmGroups = { -- Main vehicle pool (always available) { frequency = 1.0, compositions = { "Berlin_HK_2x", "Berlin_A3L92_4x", }, }, -- Optional / rare vehicles { frequency = 0.4, compositions = { "Berlin_HK_1x", "Berlin_A3L92_3x", }, }, }; end Result: * HK_2x and A3L92_4x are **always available** in career mode. * HK_1x and A3L92_3x appear **only occasionally**, depending on the random selection. ==== 8.4 Pathfinding Templates ==== Career mode also needs timetable templates that can be used for route finding. For the TestMap, we reference our two templates: function TestMap:loadCareerMode() self.cmTakeoverStations = { self.stations.TS, self.stations.TSA, self.stations.DP, }; self.cmGroups = { { frequency = 1, compositions = { "Berlin_HK_2x", "Berlin_A3L92_4x", }, }, }; -- Templates that can be used for pathfinding self.pathfindingTemplates = { self.TestLine_Dir1, self.TestLine_Dir2, }; end If you forget this, career mode may not be able to plan valid routes on your map. ---- ===== 9) Registering Stations and Timetables ===== The final step is registering runtime data with the ControlCenter: controlCenter:setStationList(self.stations); controlCenter:setTimetableList(self.timetables, self.dispatchingStrategies, self.depots); ^ Call ^ Description ^ | setStationList | Registers all stations for routing, UI and spawning logic. | | setTimetableList | Registers AI services plus dispatching and depot logic. | If this function is missing or incomplete: * AI traffic will not work * stations may not be recognized for routing ---- ===== Next Steps ===== After your timetables are configured: * build and test your mod → [[manual:subwaysim:map_construction:build_mod|Build Mod (.pak)]] {{page>manual:footer}}