This is an attempt to take a clean-sheet look at the problem of spawning railcars for use in the simulator.
Definitions:
- "Session": A gameplay context, be it an activity, timetable, or a route/path/consist combination selected for explore the route mode. Sessions define one or more trains.
- "Train": An entity in the Open Rails world that consists of one or more railcars connected in sequence. Trains can divide and combine with other trains. A string of wagons parked in a siding (for the purposes of this specification) make up a train. When you couple to them, you are combining their "train" with your own. All attributes of a train are defined by the loaded activity or timetable session.
- "Car": Any rail vehicle, which is at all times attached to a single train - trains cannot share cars. While MSTS required that all articulated rail vehicles be modeled as separate .eng and .wag files, and therefore separate cars, we can expect this limitation to be abolished for the eventual Open Rails rolling stock format. Some attributes of a car are facts that are defined in its rolling stock file; others will be overlaid by the active session.
Objectives:
- We want to design a mechanism to spawn trains and their attached cars, each with their desired attributes.
- "Modability": We want to make it easy for players to customize activities and timetables to use their own equipment.
- "Variability": We want mechanisms that can select among alternative sequences of railcars, so as to vary the gameplay and make it more interesting.
- "Reusability": It should be possible to share sequences of railcars and attributes among multiple trains, so as to reduce the input burden on the session designer.
Assumptions:
- We are dealing only with the problem of assigning attributes to cars. Speculation on how Open Rails would make use of them is welcome, but largely outside of the scope of this proposal.
- We will not explicitly link or group cars together, except by assigning common attribute-value combinations to them. It is up to the simulator's other subsystems to interpret this data.
- We will share mechanisms and file formats, but not data, between the various types of sessions (activity, timetable, explorer), as I suspect few players think it important to share consists between the different modes. In particular, we will be breaking Kuju's link (the "consist" format and directory) between player train definitions in activity mode and player trains in explorer mode.
By shedding Kuju's shared consist database, we are essentially moving toward the RailWorks model, where train and car information are strictly part of the session format. However, we will be adding our own mechanisms to spice up our sessions with some unpredictability.
With that said, here is my proposed data model. Attributes are on a "including but (where they make sense) not limited to" basis.
Sessions contain:
- A starting date (time is determined by the first active train)
- The state of the weather
- A collection of one or more trains to spawn immediately, or at some future point
Trains contain:
- A dispatcher symbol (if active when spawned)
- A priority class (if active when spawned)
- A maximum authorized speed (if active when spawned)
- A start time or trigger
- A spawn location
- Instructions to perform, such as passenger stops to make, locations to travel to, and pick ups and drop offs to make (unless in explorer mode)
- A sequence of one or more cars and their attributes
Cars contain:
- A reference to the rolling stock definition (.eng, .wag, or future format) to load into the simulator
- A starting orientation (forward or reversed)
- Initial states for pertinent controls, such as the reverser and pantograph
- A starting fuel or water level (for locomotives)
- A starting coal level (for tenders)
- A starting load (for freight wagons)
- An acceleration tolerance (durability) (for freight and passenger wagons)
- Data tag(s) used by by the current active session to specify and track objectives
- For example, activity sessions might tag certain cars for drop off at a particular siding.
- Timetable sessions would tag cars for use in $attach and $detach commands, succeeding the current method of associating cars with their originating consists.
- For example, activity sessions might tag certain cars for drop off at a particular siding.
Anticipated structures:
Native activities should store their train information in a new "train" file to be distributed as part of the activity package. Likewise for timetables, although encoding this information into one of the csv rows is also an option. Explore the route "trains" will continue to be referred to as "consists" and the sole train attribute will be the display name in the menu. In any case, all train formats contain the same information: a list of cars plus their corresponding initial state attributes.
There will be another format, call it a "car list," that fulfills the variability and reusability objectives. It will resemble a stripped-down train file without any of the train attributes. Car lists can apply any attributes to their items, which they string together, and they can contain cars or references to other car lists. In addition, "car list randomizers" can select from their mutually exclusive items on probabilistic basis, while "car list pools" can select one from a depleting pool of items. Through nesting, any conceivable combination of randomized cars, plus their attributes, is possible.
Car lists can be shared among multiple trains, and they, too, should be distributed as part of the activity or timetable package. Attributes that a train applies to a car list will apply to all items generated by the car list, on an additive basis - if a car list applies attribute "X=1" to its cars while its parent train applies attribute "Y=2" to the car list, the cars generated by the car list will have attributes "X=1;Y=2". Trains will not be permitted to reference other trains.
Consists (trains for explore the route mode) will be created by the player or can be distributed along with rolling stock packages. Consists will also support car lists.
Regardless of how a train is constructed, the end result made available to the rest of the simulator is a flat linear list of engines or wagons and their corresponding attributes.
How current data formats fit into this model:
- Activities (.act) and timetables (.timetable-or) define sessions. So does the player when he or she selects a route, path, and consist for explore the route mode.
- These sessions define collections of trains.
- The consist file (.con) defines:
- A maximum speed, which applies to all trains spawned using this consist.
- A durability value, which applies to every car of all trains spawned using this consist.
- A list of cars, minus any attributes, that is duplicated for every train spawned using this consist.
- A maximum speed, which applies to all trains spawned using this consist.
JSON formats:
Now, let's move on to the data formats. In general, all of the structures are going to involve list of items, where items may be references to rolling stock (engines and wagons) or "segments," the proper name I've decided upon for the "car list" concept described earlier.
At this time, I will not define train formats for use by activities and timetables, but their construction should be fairly self-explanatory.
Consist:
A consist represents a train "template" for use in explore the route mode. Its only properties are a display name for use in the main menu and a list of items.
// myexplorer.consist-or { "DisplayName": "My Explorer Consist", "Items": [ ... ] }
Item:
An item is a JSON block that represents a reference to a rolling stock wagon or a segment. Items can be repeated, can be flipped, and can introduce session-specific attributes in an additive relationship.
{ "Item": "/MSTS/TRAINS/TRAINSET/dash9/dash9.eng", "Count": 2, "Flip": true, "Session": { "Attribute1": "value1", "Attribute2": "value2", ... } }
Segment:
A segment is a reusable block of wagons or other segments that can be referenced by multiple trains. (I picked the "segment" name because its meaning has not been overloaded by real-world railways, and it conveys a sense of "something that's part of something larger.") Segments are self-contained files, which permits nesting.
The "list" segment type combines all of its items in a linear order:
// mylist.segment-or { "Type": "list", "Items": [ ... ] }
The "random" segment type selects one of its items given an accompanying list of probabilistic weights:
// myrandom.segment-or { "Type": "random", "Items": [ ... ], "Probabilities": [ /* The first weight corresponds to the first item, the second weight the second, etc. */ 0.2, 0.3, ... ] }
The "fallback" segment type is intended for activity designers to gracefully handle missing equipment. It attempts to load the first item, and if it contains an unknown path it falls back to the second item, and if that item contains an unknown path it falls back to the third item, and so on.
// myfallback.segment-or { "Type": "fallback", "Items": [ ... ] }
The "pool" segment type simulates a fixed equipment pool. When first loaded, it randomly selects an item, and then when loaded again, it selects another distinct item, and then yet another item, and so on. When its "pool" of items is exhausted, it restarts from the first selected item.
// mypool.segment-or { "Type": "pool", "Items": [ ... ] }
Conclusion:
In my opinion, this data model puts Open Rails on a sound footing to introduce new features and deeper models of rail operations, including native replacements for the consist and activity formats inherited from MSTS.
______________________________________________
Original post follows:
I'd like to revive the topic of a native consist format for OR. There was a big discussion created about this some years ago, but it ended inconclusively, and also kind of got side-tracked by a content manager proposal. Here, I'd like to focus on a concrete proposal for a greenfield, JSON-based consist format with additional OR-exclusive goodies.
It would be our very first native data format - but by my reading of the TrainCfg parser code, it shouldn't be too difficult to pour some virtual concrete, so to speak, once we've settled on the particulars. And it also wouldn't touch any of the low-level graphics subsystems that are currently up in the air due to the ongoing Monogame migration.
As a reminder, the MSTS .con format consists of the following information:
- Consist name
- Consist durability
- Maximum permitted speed
- Serial number (unused)
- Next free wagon UID (unused)
- A list of wagons, each with:
- A UID
- The TRAINSET folder name
- The .eng or .wag filename (without the extension) in an "EngineData" or "WagonData" block, respectively
- A flag indicating a forward or reverse orientation
- A UID
And here is a sample:
SIMISA@@@@@@@@@@JINX0D0t______ Train ( TrainCfg ( "GP38-2 lumber" Serial ( 1 ) MaxVelocity ( 36.65728 1.00000 ) NextWagonUID ( 4 ) Durability ( 1.00000 ) Engine ( UiD ( 0 ) EngineData ( GP38 GP38 ) ) Wagon ( WagonData ( US2WoodChipper US2WoodChipper ) UiD ( 2 ) ) Wagon ( WagonData ( US2FCarYE2 US2FCarYE2 ) UiD ( 3 ) ) Wagon ( WagonData ( US2EmpLoggerCar US2EmpLoggerCar ) UiD ( 1 ) ) ) )
My proposal would largely mirror Kuju's data fields, with some minor alterations:
- Drop the unused "Serial" and "NextWagonUID" fields
- Drop the "UID" field for each individual wagon; have Open Rails auto-generate these if they are truly necessary
- Consolidate folder and wagon name into a single "path" field - but continue to omit the file extension, because in the future, native "engine-or" and "wagon-or" formats should supersede their MSTS counterparts
The new format would use the "consist-or" extension and it would supersede any .con file with an identical base name, in all contexts (activity, timetable, explorer).
{ "name": "GP38-2 lumber", "maxVelocityMpS": 36.65728, "durability": 1.0, "formation": [ { "type": "engine", "path": "GP38/GP38", "flip": false }, { "type": "wagon", "path": "US2WoodChipper/US2WoodChipper", "flip": false }, { "type": "wagon", "path": "US2FCarYE2/US2FCarYE2", "flip": false }, { "type": "wagon", "path": "US2EmpLoggerCar/US2EmpLoggerCar", "flip": false } ] }
Going beyond a 1:1 MSTS replacement, I would also like to propose the following additions:
Consists should be able to refer to other consists.
{ "type": "consist", "path": "gp38", "flip": false }
There should be a special "formation" type that instructs Open Rails to select from a number of alternatives on a probabilistic basis.
{ "type": "formation", "formation": [ { "type": "consist", "path": "gp38", "flip": false, "probability": 0.7 }, { "type": "consist", "path": "dash9", "flip": false, "probability": 0.3 } ] }
(Formation types should be able to be recursively defined, although perhaps we wouldn't expose this in the eventual consist editor.)
As a stepping-stone to an eventual content manager, it should be possible to refer to content in another installation profile. Rather than specifying an absolute filesystem path, I suggest using the "Name" field as entered by the user as the identifier:
{ "type": "consist", "path": "Senator Vincent", "profile": "PRR Eastern Region", "flip": false }
Any and all feedback is welcome!