November 2025 Development Update
[p]Hello Railroaders![/p][p][/p][p]Can you believe it’s been almost two years since Railroader was released in Early Access? Some of you have been with us since before it was called Railroader! Many of you have joined us along the way, and some of you have only just become familiar with the game. Whether you’re an old head or a greenhorn, we want to sincerely thank you for your support. We’ve been fortunate to hear from many of you over the last couple years how much Railroader has meant to you. Our small dev team feels a lot of responsibility as stewards of the game.[/p]
[p][/p]
[p]In our last development update we covered some of the features we’ve been working on to support the Maintenance of Way (MoW) update, including enhanced car loads, expanded industry at Hewitt, and the interchange at Almond.[/p]
[p][/p]
[p]What have we been up to since then, you ask? We’ve been thinking a lot about modding![/p]
[p]
[/p]
[h3]Building Toward Official Modding Support[/h3][p][/p][p]Modding has been a remarkable, unofficial part of Railroader since not long after it was released. We’ve been amazed at what modders have created, and continue to create for the game. While official modding support is on our roadmap, it’s something we have thought of as one of the last big features before Railroader leaves Early Access, simply because we don’t want to break mods, and we don’t want modding support to prevent us from making necessary changes to the base game. In working on the car loads feature, however, it became clear that to build it right we would need to build toward official modding support.[/p][p][/p][p]Before we get into details, it’s important to acknowledge that modding is very broad topic. Mods run the gamut: they change parts of cars, introduce new cars and engines, add or change track or industries, scenery, augment the UI. Some even aim to create whole new maps.[/p][p][/p][p]As much as we want to provide support and tooling for all of these facets of modding, the reality is that that’s not possible at this point with our limited resources. Map modding in particular is one that we’d love to support but due to the richness of the game and complex features like signals/CTC, map progression, etc., let alone the editing UI, it’s not practical to support map modding without some pretty significant limitations, which we don’t see as a great solution.[/p][p][/p][p]Our plan for this update in terms of modding support is that it will focus on two areas of modding, but we plan to continue to consider how we can expand our support for modding in the future.[/p]
[p][/p][p]
[/p]
[h3]1. Mod Loading and Dependency Management[/h3][p][/p][p]Railroader has unofficially supported adding new equipment to the game through asset packs since day one – it’s part of our internal development workflow – but there have been some critical aspects missing, namely dependency management and support for encoding mods in save games and handling them in multiplayer.[/p][p][/p][p]To improve this, we have been working on revamping asset packs with the following: (technical details incoming!)[/p][p][/p]
[/p][h3]2. Relay[/h3][p][/p][p]This is a big one: Railroader now has its own scripting language! It’s called Relay.[/p][p][/p][p]Relay scripts can be used to:[/p]
[p][/p]
[/p]
[h3]Relay Modding Examples[/h3]
[p][/p]
[p]Here are a few examples of Relay in action. Remember that this is still in development – we welcome your feedback![/p][p][/p]
[p]You could create a mod to change the prices of cars, or adjust their capacity using a [c]Data.rl[/c] file:[/p]
[p][/p]
[p]
[/p]
[p][c]func onDefine(data) {[/c][/p][p][c] // Change the price on the lightweight steel passenger car:[/c][/p][p][c] data.modify("rr/pb-osgbrad-lightweight-steel-1915", (obj) => {[/c][/p][p][c] obj\["definition"]\["basePrice"] = 900[/c][/p][p][c] })[/c][/p][p][/p][p][c] // Create a "high capacity" variant:[/c][/p][p][c] data.modifyAs("rr/pb-osgbrad-lightweight-steel-1915",[/c][/p][p][c] "imtzo/pb-osgbrad-lightweight-steel-1915-highcap",[/c][/p][p][c] (obj) => {[/c][/p][p][c] var metadata = obj\["metadata"][/c][/p][p][c] var definition = obj\["definition"][/c][/p][p][c] metadata\["description"] = "High Capacity Steel Coach"[/c][/p][p][c] var slot0 = definition\["loadSlots"]\[0][/c][/p][p][c] slot0\["maximumCapacity"] += 20[/c][/p][p][c] definition\["basePrice"] += 2000[/c][/p][p][c] })[/c][/p][p][c]}[/c][/p][p][/p][p]
[/p][p]You could create a model with customizable parts. Suppose I had created a new engine with a toggle-able superheater detail model.[/p][p][/p][p]In a script component on the engine:[/p][p][/p][p]
[/p][p][c]var observer = null[/c][/p][p][/p][p][c]func onModelLoad(car) {[/c][/p][p][c] var superheater = car.components\["Superheater Detail Model"][/c][/p][p][c] [/c][/p][p][c] // Observe the property and update component enabled states:[/c][/p][p][c] observer = car.properties.observe("showSuperheater", true, (value) => {[/c][/p][p][c] superheater.enabled = value[/c][/p][p][c] })[/c][/p][p][c]}[/c][/p][p][/p][p][c]func onModelUnload(car) {[/c][/p][p][c] observer?.dispose()[/c][/p][p][c] observer = null[/c][/p][p][c]}[/c][/p][p][/p]
[p]
[/p][p]And in [c]Main.rl[/c] in the asset pack:[/p][p]
[/p][p][/p]
[p][c]from railroader import ui[/c][/p][p][/p][p][c]func onModLoad(ctx) {[/c][/p][p][c] // Add a toggle to the customize UI:[/c][/p][p][c] ui.extend("carInspector.equipment.customize")[/c][/p][p][c] .matching({"identifier": "imtzo/my-engine"})[/c][/p][p][c] .with((builder) => {[/c][/p][p][c] builder.fieldToggle("Superheater",[/c][/p][p][c] () => { return car.properties\["showSuperheater"] },[/c][/p][p][c] (value) => { car.properties\["showSuperheater"] = value }[/c][/p][p][c] })[/c][/p][p][c]}[/c][/p][p]
[/p][p]These are only a couple examples. Relay can also be used to create windows and add slash-commands to the console. A calculator summoned with [c]/calc[/c]?[/p][p][/p][p]
[/p][p][/p][p]Perhaps you can see why this has taken some time to build. We’re very excited to see what you build with it, and see it as being useful not only for modders, but also some of the more advanced railroads – you know who you are – who can use some basic scripting to help automate parts of your railroad![/p][p][/p][p]We’re still hard at work on this, although we hope to get to a point where we can release an experimental branch soon. While this experimental might be a little rough around the edges, we think it is important to get this work out there and get feedback to shape it before it is released on the main branch.[/p][p][/p][p]Of course this release has a load of other enhancements, but those will need to wait for another post. Until then, thank you as always for your patience and support![/p][p][/p][p]
[/p][p]Previous update posts for this update cycle:[/p][p][dynamiclink][/dynamiclink][/p][p][dynamiclink][/dynamiclink][/p]
[p][/p]
[p]In our last development update we covered some of the features we’ve been working on to support the Maintenance of Way (MoW) update, including enhanced car loads, expanded industry at Hewitt, and the interchange at Almond.[/p]
[p][/p]
[p]What have we been up to since then, you ask? We’ve been thinking a lot about modding![/p]
[p]
[/p]
[h3]Building Toward Official Modding Support[/h3][p][/p][p]Modding has been a remarkable, unofficial part of Railroader since not long after it was released. We’ve been amazed at what modders have created, and continue to create for the game. While official modding support is on our roadmap, it’s something we have thought of as one of the last big features before Railroader leaves Early Access, simply because we don’t want to break mods, and we don’t want modding support to prevent us from making necessary changes to the base game. In working on the car loads feature, however, it became clear that to build it right we would need to build toward official modding support.[/p][p][/p][p]Before we get into details, it’s important to acknowledge that modding is very broad topic. Mods run the gamut: they change parts of cars, introduce new cars and engines, add or change track or industries, scenery, augment the UI. Some even aim to create whole new maps.[/p][p][/p][p]As much as we want to provide support and tooling for all of these facets of modding, the reality is that that’s not possible at this point with our limited resources. Map modding in particular is one that we’d love to support but due to the richness of the game and complex features like signals/CTC, map progression, etc., let alone the editing UI, it’s not practical to support map modding without some pretty significant limitations, which we don’t see as a great solution.[/p][p][/p][p]Our plan for this update in terms of modding support is that it will focus on two areas of modding, but we plan to continue to consider how we can expand our support for modding in the future.[/p]
[p][/p][p]
[/p]
[h3]1. Mod Loading and Dependency Management[/h3][p][/p][p]Railroader has unofficially supported adding new equipment to the game through asset packs since day one – it’s part of our internal development workflow – but there have been some critical aspects missing, namely dependency management and support for encoding mods in save games and handling them in multiplayer.[/p][p][/p][p]To improve this, we have been working on revamping asset packs with the following: (technical details incoming!)[/p][p][/p]
- [p]Asset packs now have strong identifiers in the format: domain/identifier. All built-in asset packs use the domain rr.[/p]
- [p]Asset packs now have versions.[/p]
- [p]Asset packs can define versioned dependencies and will be loaded in dependency order.[/p]
- [p]Definitions (equipment, scenery, etc.) in a pack must have unique identifiers within the domain. If the game defines rr/ls-280-c46 (the C-46 Consolidation), you could define your-name/ls-280-c46. This simplifies creating variants of existing equipment and helps avoid conflicts.[/p]
- [p]Asset packs with a matching name no longer override other[/p]
- [p]Including mod information in save games.[/p]
[/p][h3]2. Relay[/h3][p][/p][p]This is a big one: Railroader now has its own scripting language! It’s called Relay.[/p][p][/p][p]Relay scripts can be used to:[/p]
[p][/p]
- [p]Manipulate definition data during the loading process, similar to Factorio’s modding system. For example, a [c]Data.rl[/c] script in an asset pack can change values on any equipment in the game, add or remove components, or create whole new variants of existing equipment. All without having to duplicate the asset files.[/p]
- [p]Add scripts to control components on equipment in response to property changes. Example: show or hide a ‘stakes’ model on a flat car based on whether it is appropriate for the car’s current load.[/p]
- [p]Extend the UI: add controls to existing windows, create new windows, or create console slash commands. [c]Main.rl[/c] scripts are used for this.[/p]
[/p]
[h3]Relay Modding Examples[/h3]
[p][/p]
[p]Here are a few examples of Relay in action. Remember that this is still in development – we welcome your feedback![/p][p][/p]
[p]You could create a mod to change the prices of cars, or adjust their capacity using a [c]Data.rl[/c] file:[/p]
[p][/p]
[p]
[/p]
[p][c]func onDefine(data) {[/c][/p][p][c] // Change the price on the lightweight steel passenger car:[/c][/p][p][c] data.modify("rr/pb-osgbrad-lightweight-steel-1915", (obj) => {[/c][/p][p][c] obj\["definition"]\["basePrice"] = 900[/c][/p][p][c] })[/c][/p][p][/p][p][c] // Create a "high capacity" variant:[/c][/p][p][c] data.modifyAs("rr/pb-osgbrad-lightweight-steel-1915",[/c][/p][p][c] "imtzo/pb-osgbrad-lightweight-steel-1915-highcap",[/c][/p][p][c] (obj) => {[/c][/p][p][c] var metadata = obj\["metadata"][/c][/p][p][c] var definition = obj\["definition"][/c][/p][p][c] metadata\["description"] = "High Capacity Steel Coach"[/c][/p][p][c] var slot0 = definition\["loadSlots"]\[0][/c][/p][p][c] slot0\["maximumCapacity"] += 20[/c][/p][p][c] definition\["basePrice"] += 2000[/c][/p][p][c] })[/c][/p][p][c]}[/c][/p][p][/p][p]
[/p][p]You could create a model with customizable parts. Suppose I had created a new engine with a toggle-able superheater detail model.[/p][p][/p][p]In a script component on the engine:[/p][p][/p][p]
[/p][p][c]var observer = null[/c][/p][p][/p][p][c]func onModelLoad(car) {[/c][/p][p][c] var superheater = car.components\["Superheater Detail Model"][/c][/p][p][c] [/c][/p][p][c] // Observe the property and update component enabled states:[/c][/p][p][c] observer = car.properties.observe("showSuperheater", true, (value) => {[/c][/p][p][c] superheater.enabled = value[/c][/p][p][c] })[/c][/p][p][c]}[/c][/p][p][/p][p][c]func onModelUnload(car) {[/c][/p][p][c] observer?.dispose()[/c][/p][p][c] observer = null[/c][/p][p][c]}[/c][/p][p][/p]
[p]
[/p][p]And in [c]Main.rl[/c] in the asset pack:[/p][p]
[/p][p][/p]
[p][c]from railroader import ui[/c][/p][p][/p][p][c]func onModLoad(ctx) {[/c][/p][p][c] // Add a toggle to the customize UI:[/c][/p][p][c] ui.extend("carInspector.equipment.customize")[/c][/p][p][c] .matching({"identifier": "imtzo/my-engine"})[/c][/p][p][c] .with((builder) => {[/c][/p][p][c] builder.fieldToggle("Superheater",[/c][/p][p][c] () => { return car.properties\["showSuperheater"] },[/c][/p][p][c] (value) => { car.properties\["showSuperheater"] = value }[/c][/p][p][c] })[/c][/p][p][c]}[/c][/p][p]
[/p][p]These are only a couple examples. Relay can also be used to create windows and add slash-commands to the console. A calculator summoned with [c]/calc[/c]?[/p][p][/p][p]
[/p][p]Previous update posts for this update cycle:[/p][p][dynamiclink][/dynamiclink][/p][p][dynamiclink][/dynamiclink][/p]