
Welcome to the technical frontline of the Metruvia Content Creator Scripting Border Guide. You have already mastered the physical side of modding: the file structures, the polygon budgets, and the IP compliance. Now, we enter the digital side—the code that breathes life into your assets.
In the localized, PC-only ecosystem of the Steam Workshop, the Transport Fever 2 engine allows for remarkable flexibility. PC modders have historically used Lua to completely overhaul UI systems, inject custom shaders, and read external files. However, on PlayStation 5 and Xbox Series X|S, this level of access is a critical security vulnerability.
When you check the “Console Compatible” box on Mod.io, your code is subjected to a strict Sandbox. Sony and Microsoft do not permit arbitrary code execution on their closed hardware. If your script attempts to call a restricted OS-level function, or if it introduces a custom UI element that a gamepad controller cannot navigate, your mod will be instantly rejected.
This definitive masterclass will deconstruct the Transport Fever 2 Lua Sandbox. We will define the exact blacklist of prohibited functions, explore the limitations of the UI API, and provide the architectural knowledge needed to write safe, console-compliant logic that enhances the game without violating hardware security protocols.
1. The Architecture of the Lua Sandbox
To understand why your code might be rejected, you must understand how Transport Fever 2 handles scripts. The game utilizes Lua 5.3 as its embedded scripting language. However, the environment your mod executes within is not standard Lua; it is a heavily restricted virtual container.
1.1 The Principle of Least Privilege
The sandbox operates on the “Principle of Least Privilege.” Your mod is only granted access to the specific internal game APIs necessary to function (game.config, api.engine, api.res). It is entirely blinded to the outside world. It cannot know what operating system it is running on, it cannot see the player’s network connection, and it cannot access the local file system beyond its own designated Virtual File System (VFS) boundaries.
1.2 Deterministic Execution
On consoles, stability is paramount. The engine requires scripts to be deterministic. This means that given the same input, your code must produce the same output every single time.
The Non-Deterministic Trap: Using standard Lua functions to get the current system time or random numbers from the OS.
The Solution: You must use the internal game-clock and the game’s seeded random number generator. If your mod creates a “random” chance for an industry to spawn, but that randomness varies between a PC player and a Console player in the exact same save-file context, the simulation will desync and crash.
2. The Blacklist: Triggers for Immediate Security Rejection
The Urban Games API proactively strips access to standard Lua libraries that pose security threats. Attempting to call any of the following functions will result in a fatal nil value error, an engine crash, and a permanent rejection from the Mod.io console pool.
2.1 The os.* Library (Operating System Access)
The os library interacts directly with the operating system. On a console, it is a vector for system-level exploitation.
| Restricted Function | Why it is Banned |
os.execute | Can be used to run external malicious binaries. |
os.remove / os.rename | Could potentially delete or corrupt system files. |
os.getenv | Could be used to scrape user information or system IDs. |
os.tmpname | Accesses temporary directories outside the game sandbox. |
While you can read configuration files packaged within your mod’s folder structure using the Transport Fever 2 specific file handlers, the standard Lua I/O library is restricted to prevent unauthorized data exfiltration.
Prohibited: io.open, io.popen, io.tmpfile.
The Consequence: You cannot write dynamic log files to a console’s SSD, nor can you read external configuration data from outside your specific mod directory.
2.3 Binary Extensions and package.loadlib
PC modders sometimes use .dll (Windows) or .so (Linux) files to extend Lua’s capabilities.
The Rule: Consoles do not run external binary code. Any attempt to use package.loadlib or require on a non-Lua file is an immediate violation of Sony and Microsoft developer agreements. Your mod must be strictly pure Lua.
3. Algorithmic Efficiency: The Watchdog Timer
Consoles have strict “Watchdog Timers” to prevent hard lockups. If your Lua script contains an inefficient loop that takes too long to execute, the console OS will assume the game has frozen and forcefully terminate the application.
3.1 Time Complexity and the CPU Budget
When parsing massive arrays (like checking every single vehicle on a map), you must optimize for complexity.
Safe (O(n)): A single loop iterating linearly through the vehicle list.
Dangerous (O(n²)): A loop inside a loop (e.g., checking every vehicle against every other vehicle).
If you have 1,000 vehicles, an O(n²) script requires 1,000,000 operations. On a console CPU already handling the game’s complex pathfinding graph, this spike will trigger the OS watchdog and close the game.
3.2 Garbage Collection (GC) Spikes
Lua uses automatic memory management. If you create and destroy thousands of temporary tables in a single frame, you trigger a “GC Spike.” This causes a rhythmic, unplayable stutter in the framerate.
The Strategy: Pre-allocate your tables. Instead of creating a new local myData = {} inside a loop, create it once outside the loop and simply clear or overwrite its indices for re-use.
4. The UI Barrier: Gamepad Navigation Meshes
The most common reason for mod rejection on Mod.io is the mishandling of the UI API.
4.1 Mouse vs. Controller
PC players use a mouse with free-roaming pixel accuracy. Console players use a controller, which relies on UI Nav-Meshes. The highlight (focus) jumps from one interactable element to the next based on predefined paths mapped by the developers.
4.2 The “Unreachable Button” Trap
If you use Lua to inject a custom “Auto-Schedule” button into the vehicle management window, a PC player simply clicks it. However, the console’s UI Nav-Mesh does not know your dynamic button exists. The controller highlight will skip right over it, effectively placing a feature on the screen that the player physically cannot use.
4.3 The Strict UI Prohibition
Because Urban Games cannot dynamically generate controller support for arbitrary UI elements created by modders, all custom UI alterations are prohibited for Console Compatible mods.
No custom windows.
No custom buttons in vanilla menus.
No interception of mouse-click events.
5. The mod.lua Lifecycle: State and Persistence
Understanding exactly when your code executes is the difference between a stable mod and a save-game breaker.
5.1 runFn(settings, modParams)
This function runs exactly once when the save game is loaded. It is used to modify global game configuration tables.
Caution: Changes made here are “baked” into the save file memory. If you change a vanilla value here, that change persists as long as the mod is active in the load order.
5.2 postRunFn(settings, modParams)
This block executes after all other mods have completed their runFn.
Why it Matters: If you want to modify a vehicle added by another mod (e.g., globally reducing emissions), you must do it in postRunFn. If you do it in runFn, your script will run before the other mod has initialized its vehicles, completely missing them.
5.3 The guiInit and guiUpdate Trap
These functions are traditionally used for UI logic. For console mods, these should remain empty or unused. Any logic placed here that attempts to draw custom UI panels to the screen will cause an immediate rejection.
6. Safe Scripting: Utilizing the api.engine
If you cannot use external libraries or custom UI, how do you add features safely? The answer lies in the Modifier Engine and Component System.
6.1 Modifier Scripts
You can safely iterate through the game’s internal resource repository to apply bulk changes without risking security.
6.2 The Scripting Component (api.type.Component)
Transport Fever 2 allows you to attach custom data to entities using components. This is the certified way to store mod-specific data (like tracking a vehicle’s custom mileage or defining an industry’s unique specialty) without breaking the core save file structure or relying on external text files.
7. Save-Game Integrity: severityRemove
Because scripts can alter the deep mathematical logic of a save file, you must tell the game how dangerous it is for a player to remove your mod.
NONE: Removing the mod has no effect (used for simple texture swaps).
WARNING: Removing it might delete a vehicle from the map, but it won’t crash the simulation.
CRITICAL: Mandatory for economy overhauls, script injections, or mods that add new cargo types. If this mod is removed, the save will crash. This tag visually warns console players before they accidentally destroy their playthrough.
8. The Diagnostic Matrix: Troubleshooting Console Crashes
When your code crashes locally during development, you must learn to read the stdout.txt log file to pinpoint the failure.
| Error Message | Meaning | Solution |
attempt to index a nil value | Your code tried to access a table or variable that doesn’t exist. | Wrap your logic in a “nil-check”: if myTable then... |
stack overflow | An infinite or overly deep recursive loop was detected. | Check your recursive functions to ensure they have a strict exit condition. |
attempt to call a nil value (global 'os') | You used a banned standard library. | Remove all os.* and io.* calls from your codebase. |
Out of memory (Lua) | You exceeded the Lua heap limit by generating massive tables instantly. | Refactor for memory efficiency and pre-allocate data structures. |
METRUVIA CONTENT CREATOR SERIES: THE SCRIPTING BORDER – LUA SANDBOX & UI LIMITS (Supporting Article 5)
Welcome to the technical frontline of the Metruvia Content Creator Series. You have already mastered the physical side of modding: the file structures, the polygon budgets, and the IP compliance. Now, we enter the digital side—the code that breathes life into your assets.
In the localized, PC-only ecosystem of the Steam Workshop, the Transport Fever 2 engine allows for remarkable flexibility. PC modders have historically used Lua to completely overhaul UI systems, inject custom shaders, and read external files. However, on PlayStation 5 and Xbox Series X|S, this level of access is a critical security vulnerability.
When you check the “Console Compatible” box on Mod.io, your code is subjected to a strict Sandbox. Sony and Microsoft do not permit arbitrary code execution on their closed hardware. If your script attempts to call a restricted OS-level function, or if it introduces a custom UI element that a gamepad controller cannot navigate, your mod will be instantly rejected.
This definitive masterclass will deconstruct the Transport Fever 2 Lua Sandbox. We will define the exact blacklist of prohibited functions, explore the limitations of the UI API, and provide the architectural knowledge needed to write safe, console-compliant logic that enhances the game without violating hardware security protocols.
1. The Architecture of the Lua Sandbox
To understand why your code might be rejected, you must understand how Transport Fever 2 handles scripts. The game utilizes Lua 5.3 as its embedded scripting language. However, the environment your mod executes within is not standard Lua; it is a heavily restricted virtual container.
1.1 The Principle of Least Privilege
The sandbox operates on the “Principle of Least Privilege.” Your mod is only granted access to the specific internal game APIs necessary to function (game.config, api.engine, api.res). It is entirely blinded to the outside world. It cannot know what operating system it is running on, it cannot see the player’s network connection, and it cannot access the local file system beyond its own designated Virtual File System (VFS) boundaries.
1.2 Deterministic Execution
On consoles, stability is paramount. The engine requires scripts to be deterministic. This means that given the same input, your code must produce the same output every single time.
- The Non-Deterministic Trap: Using standard Lua functions to get the current system time or random numbers from the OS.
- The Solution: You must use the internal game-clock and the game’s seeded random number generator. If your mod creates a “random” chance for an industry to spawn, but that randomness varies between a PC player and a Console player in the exact same save-file context, the simulation will desync and crash.
2. The Blacklist: Triggers for Immediate Security Rejection
The Urban Games API proactively strips access to standard Lua libraries that pose security threats. Attempting to call any of the following functions will result in a fatal nil value error, an engine crash, and a permanent rejection from the Mod.io console pool.
2.1 The os.* Library (Operating System Access)
The os library interacts directly with the operating system. On a console, it is a vector for system-level exploitation.
| Restricted Function | Why it is Banned |
os.execute | Can be used to run external malicious binaries. |
os.remove / os.rename | Could potentially delete or corrupt system files. |
os.getenv | Could be used to scrape user information or system IDs. |
os.tmpname | Accesses temporary directories outside the game sandbox. |
2.2 The io.* Library (File Input/Output)
While you can read configuration files packaged within your mod’s folder structure using the Transport Fever 2 specific file handlers, the standard Lua I/O library is restricted to prevent unauthorized data exfiltration.
- Prohibited:
io.open,io.popen,io.tmpfile. - The Consequence: You cannot write dynamic log files to a console’s SSD, nor can you read external configuration data from outside your specific mod directory.
2.3 Binary Extensions and package.loadlib
PC modders sometimes use .dll (Windows) or .so (Linux) files to extend Lua’s capabilities.
- The Rule: Consoles do not run external binary code. Any attempt to use
package.loadliborrequireon a non-Lua file is an immediate violation of Sony and Microsoft developer agreements. Your mod must be strictly pure Lua.
3. Algorithmic Efficiency: The Watchdog Timer
Consoles have strict “Watchdog Timers” to prevent hard lockups. If your Lua script contains an inefficient loop that takes too long to execute, the console OS will assume the game has frozen and forcefully terminate the application.
3.1 Time Complexity and the CPU Budget
When parsing massive arrays (like checking every single vehicle on a map), you must optimize for complexity.
- Safe (O(n)): A single loop iterating linearly through the vehicle list.
- Dangerous (O(n²)): A loop inside a loop (e.g., checking every vehicle against every other vehicle).
If you have 1,000 vehicles, an O(n²) script requires 1,000,000 operations. On a console CPU already handling the game’s complex pathfinding graph, this spike will trigger the OS watchdog and close the game.
3.2 Garbage Collection (GC) Spikes
Lua uses automatic memory management. If you create and destroy thousands of temporary tables in a single frame, you trigger a “GC Spike.” This causes a rhythmic, unplayable stutter in the framerate.
- The Strategy: Pre-allocate your tables. Instead of creating a new
local myData = {}inside a loop, create it once outside the loop and simply clear or overwrite its indices for re-use.
4. The UI Barrier: Gamepad Navigation Meshes
The most common reason for mod rejection on Mod.io is the mishandling of the UI API.
4.1 Mouse vs. Controller
PC players use a mouse with free-roaming pixel accuracy. Console players use a controller, which relies on UI Nav-Meshes. The highlight (focus) jumps from one interactable element to the next based on predefined paths mapped by the developers.
4.2 The “Unreachable Button” Trap
If you use Lua to inject a custom “Auto-Schedule” button into the vehicle management window, a PC player simply clicks it. However, the console’s UI Nav-Mesh does not know your dynamic button exists. The controller highlight will skip right over it, effectively placing a feature on the screen that the player physically cannot use.
4.3 The Strict UI Prohibition
Because Urban Games cannot dynamically generate controller support for arbitrary UI elements created by modders, all custom UI alterations are prohibited for Console Compatible mods.
- No custom windows.
- No custom buttons in vanilla menus.
- No interception of mouse-click events.
5. The mod.lua Lifecycle: State and Persistence
Understanding exactly when your code executes is the difference between a stable mod and a save-game breaker.
5.1 runFn(settings, modParams)
This function runs exactly once when the save game is loaded. It is used to modify global game configuration tables.
- Caution: Changes made here are “baked” into the save file memory. If you change a vanilla value here, that change persists as long as the mod is active in the load order.
5.2 postRunFn(settings, modParams)
This block executes after all other mods have completed their runFn.
- Why it Matters: If you want to modify a vehicle added by another mod (e.g., globally reducing emissions), you must do it in
postRunFn. If you do it inrunFn, your script will run before the other mod has initialized its vehicles, completely missing them.
5.3 The guiInit and guiUpdate Trap
These functions are traditionally used for UI logic. For console mods, these should remain empty or unused. Any logic placed here that attempts to draw custom UI panels to the screen will cause an immediate rejection.
6. Safe Scripting: Utilizing the api.engine
If you cannot use external libraries or custom UI, how do you add features safely? The answer lies in the Modifier Engine and Component System.
6.1 Modifier Scripts
You can safely iterate through the game’s internal resource repository to apply bulk changes without risking security.
Lua
-- Example: Console-Safe maintenance cost modifier
api.res.modelRep.addModifier("my_economic_modifier", function (fileName, data)
-- Always use nil-checks before altering tables
if data.metadata and data.metadata.maintenance then
data.metadata.maintenance.runningCosts = data.metadata.maintenance.runningCosts * 0.8
end
return data
end)
6.2 The Scripting Component (api.type.Component)
Transport Fever 2 allows you to attach custom data to entities using components. This is the certified way to store mod-specific data (like tracking a vehicle’s custom mileage or defining an industry’s unique specialty) without breaking the core save file structure or relying on external text files.
7. Save-Game Integrity: severityRemove
Because scripts can alter the deep mathematical logic of a save file, you must tell the game how dangerous it is for a player to remove your mod.
NONE: Removing the mod has no effect (used for simple texture swaps).WARNING: Removing it might delete a vehicle from the map, but it won’t crash the simulation.CRITICAL: Mandatory for economy overhauls, script injections, or mods that add new cargo types. If this mod is removed, the save will crash. This tag visually warns console players before they accidentally destroy their playthrough.
8. The Diagnostic Matrix: Troubleshooting Console Crashes
When your code crashes locally during development, you must learn to read the stdout.txt log file to pinpoint the failure.
| Error Message | Meaning | Solution |
attempt to index a nil value | Your code tried to access a table or variable that doesn’t exist. | Wrap your logic in a “nil-check”: if myTable then... |
stack overflow | An infinite or overly deep recursive loop was detected. | Check your recursive functions to ensure they have a strict exit condition. |
attempt to call a nil value (global 'os') | You used a banned standard library. | Remove all os.* and io.* calls from your codebase. |
Out of memory (Lua) | You exceeded the Lua heap limit by generating massive tables instantly. | Refactor for memory efficiency and pre-allocate data structures. |
METRUVIA CONTENT CREATOR SERIES: THE SCRIPTING BORDER – LUA SANDBOX & UI LIMITS (Supporting Article 5)
Welcome to the technical frontline of the Metruvia Content Creator Series. You have already mastered the physical side of modding: the file structures, the polygon budgets, and the IP compliance. Now, we enter the digital side—the code that breathes life into your assets.
In the localized, PC-only ecosystem of the Steam Workshop, the Transport Fever 2 engine allows for remarkable flexibility. PC modders have historically used Lua to completely overhaul UI systems, inject custom shaders, and read external files. However, on PlayStation 5 and Xbox Series X|S, this level of access is a critical security vulnerability.
When you check the “Console Compatible” box on Mod.io, your code is subjected to a strict Sandbox. Sony and Microsoft do not permit arbitrary code execution on their closed hardware. If your script attempts to call a restricted OS-level function, or if it introduces a custom UI element that a gamepad controller cannot navigate, your mod will be instantly rejected.
This definitive masterclass will deconstruct the Transport Fever 2 Lua Sandbox. We will define the exact blacklist of prohibited functions, explore the limitations of the UI API, and provide the architectural knowledge needed to write safe, console-compliant logic that enhances the game without violating hardware security protocols.
1. The Architecture of the Lua Sandbox
To understand why your code might be rejected, you must understand how Transport Fever 2 handles scripts. The game utilizes Lua 5.3 as its embedded scripting language. However, the environment your mod executes within is not standard Lua; it is a heavily restricted virtual container.
1.1 The Principle of Least Privilege
The sandbox operates on the “Principle of Least Privilege.” Your mod is only granted access to the specific internal game APIs necessary to function (game.config, api.engine, api.res). It is entirely blinded to the outside world. It cannot know what operating system it is running on, it cannot see the player’s network connection, and it cannot access the local file system beyond its own designated Virtual File System (VFS) boundaries.
1.2 Deterministic Execution
On consoles, stability is paramount. The engine requires scripts to be deterministic. This means that given the same input, your code must produce the same output every single time.
- The Non-Deterministic Trap: Using standard Lua functions to get the current system time or random numbers from the OS.
- The Solution: You must use the internal game-clock and the game’s seeded random number generator. If your mod creates a “random” chance for an industry to spawn, but that randomness varies between a PC player and a Console player in the exact same save-file context, the simulation will desync and crash.
2. The Blacklist: Triggers for Immediate Security Rejection
The Urban Games API proactively strips access to standard Lua libraries that pose security threats. Attempting to call any of the following functions will result in a fatal nil value error, an engine crash, and a permanent rejection from the Mod.io console pool.
2.1 The os.* Library (Operating System Access)
The os library interacts directly with the operating system. On a console, it is a vector for system-level exploitation.
| Restricted Function | Why it is Banned |
os.execute | Can be used to run external malicious binaries. |
os.remove / os.rename | Could potentially delete or corrupt system files. |
os.getenv | Could be used to scrape user information or system IDs. |
os.tmpname | Accesses temporary directories outside the game sandbox. |
2.2 The io.* Library (File Input/Output)
While you can read configuration files packaged within your mod’s folder structure using the Transport Fever 2 specific file handlers, the standard Lua I/O library is restricted to prevent unauthorized data exfiltration.
- Prohibited:
io.open,io.popen,io.tmpfile. - The Consequence: You cannot write dynamic log files to a console’s SSD, nor can you read external configuration data from outside your specific mod directory.
2.3 Binary Extensions and package.loadlib
PC modders sometimes use .dll (Windows) or .so (Linux) files to extend Lua’s capabilities.
- The Rule: Consoles do not run external binary code. Any attempt to use
package.loadliborrequireon a non-Lua file is an immediate violation of Sony and Microsoft developer agreements. Your mod must be strictly pure Lua.
3. Algorithmic Efficiency: The Watchdog Timer
Consoles have strict “Watchdog Timers” to prevent hard lockups. If your Lua script contains an inefficient loop that takes too long to execute, the console OS will assume the game has frozen and forcefully terminate the application.
3.1 Time Complexity and the CPU Budget
When parsing massive arrays (like checking every single vehicle on a map), you must optimize for complexity.
- Safe (O(n)): A single loop iterating linearly through the vehicle list.
- Dangerous (O(n²)): A loop inside a loop (e.g., checking every vehicle against every other vehicle).
If you have 1,000 vehicles, an O(n²) script requires 1,000,000 operations. On a console CPU already handling the game’s complex pathfinding graph, this spike will trigger the OS watchdog and close the game.
3.2 Garbage Collection (GC) Spikes
Lua uses automatic memory management. If you create and destroy thousands of temporary tables in a single frame, you trigger a “GC Spike.” This causes a rhythmic, unplayable stutter in the framerate.
- The Strategy: Pre-allocate your tables. Instead of creating a new
local myData = {}inside a loop, create it once outside the loop and simply clear or overwrite its indices for re-use.
4. The UI Barrier: Gamepad Navigation Meshes
The most common reason for mod rejection on Mod.io is the mishandling of the UI API.
4.1 Mouse vs. Controller
PC players use a mouse with free-roaming pixel accuracy. Console players use a controller, which relies on UI Nav-Meshes. The highlight (focus) jumps from one interactable element to the next based on predefined paths mapped by the developers.
4.2 The “Unreachable Button” Trap
If you use Lua to inject a custom “Auto-Schedule” button into the vehicle management window, a PC player simply clicks it. However, the console’s UI Nav-Mesh does not know your dynamic button exists. The controller highlight will skip right over it, effectively placing a feature on the screen that the player physically cannot use.
4.3 The Strict UI Prohibition
Because Urban Games cannot dynamically generate controller support for arbitrary UI elements created by modders, all custom UI alterations are prohibited for Console Compatible mods.
- No custom windows.
- No custom buttons in vanilla menus.
- No interception of mouse-click events.
5. The mod.lua Lifecycle: State and Persistence
Understanding exactly when your code executes is the difference between a stable mod and a save-game breaker.
5.1 runFn(settings, modParams)
This function runs exactly once when the save game is loaded. It is used to modify global game configuration tables.
- Caution: Changes made here are “baked” into the save file memory. If you change a vanilla value here, that change persists as long as the mod is active in the load order.
5.2 postRunFn(settings, modParams)
This block executes after all other mods have completed their runFn.
- Why it Matters: If you want to modify a vehicle added by another mod (e.g., globally reducing emissions), you must do it in
postRunFn. If you do it inrunFn, your script will run before the other mod has initialized its vehicles, completely missing them.
5.3 The guiInit and guiUpdate Trap
These functions are traditionally used for UI logic. For console mods, these should remain empty or unused. Any logic placed here that attempts to draw custom UI panels to the screen will cause an immediate rejection.
6. Safe Scripting: Utilizing the api.engine
If you cannot use external libraries or custom UI, how do you add features safely? The answer lies in the Modifier Engine and Component System.
6.1 Modifier Scripts
You can safely iterate through the game’s internal resource repository to apply bulk changes without risking security.
Lua
-- Example: Console-Safe maintenance cost modifier
api.res.modelRep.addModifier("my_economic_modifier", function (fileName, data)
-- Always use nil-checks before altering tables
if data.metadata and data.metadata.maintenance then
data.metadata.maintenance.runningCosts = data.metadata.maintenance.runningCosts * 0.8
end
return data
end)
6.2 The Scripting Component (api.type.Component)
Transport Fever 2 allows you to attach custom data to entities using components. This is the certified way to store mod-specific data (like tracking a vehicle’s custom mileage or defining an industry’s unique specialty) without breaking the core save file structure or relying on external text files.
7. Save-Game Integrity: severityRemove
Because scripts can alter the deep mathematical logic of a save file, you must tell the game how dangerous it is for a player to remove your mod.
NONE: Removing the mod has no effect (used for simple texture swaps).WARNING: Removing it might delete a vehicle from the map, but it won’t crash the simulation.CRITICAL: Mandatory for economy overhauls, script injections, or mods that add new cargo types. If this mod is removed, the save will crash. This tag visually warns console players before they accidentally destroy their playthrough.
8. The Diagnostic Matrix: Troubleshooting Console Crashes
When your code crashes locally during development, you must learn to read the stdout.txt log file to pinpoint the failure.
| Error Message | Meaning | Solution |
attempt to index a nil value | Your code tried to access a table or variable that doesn’t exist. | Wrap your logic in a “nil-check”: if myTable then... |
stack overflow | An infinite or overly deep recursive loop was detected. | Check your recursive functions to ensure they have a strict exit condition. |
attempt to call a nil value (global 'os') | You used a banned standard library. | Remove all os.* and io.* calls from your codebase. |
Out of memory (Lua) | You exceeded the Lua heap limit by generating massive tables instantly. | Refactor for memory efficiency and pre-allocate data structures. |
METRUVIA CONTENT CREATOR SERIES: THE SCRIPTING BORDER – LUA SANDBOX & UI LIMITS (Supporting Article 5)
Welcome to the technical frontline of the Metruvia Content Creator Series. You have already mastered the physical side of modding: the file structures, the polygon budgets, and the IP compliance. Now, we enter the digital side—the code that breathes life into your assets.
In the localized, PC-only ecosystem of the Steam Workshop, the Transport Fever 2 engine allows for remarkable flexibility. PC modders have historically used Lua to completely overhaul UI systems, inject custom shaders, and read external files. However, on PlayStation 5 and Xbox Series X|S, this level of access is a critical security vulnerability.
When you check the “Console Compatible” box on Mod.io, your code is subjected to a strict Sandbox. Sony and Microsoft do not permit arbitrary code execution on their closed hardware. If your script attempts to call a restricted OS-level function, or if it introduces a custom UI element that a gamepad controller cannot navigate, your mod will be instantly rejected.
This definitive masterclass will deconstruct the Transport Fever 2 Lua Sandbox. We will define the exact blacklist of prohibited functions, explore the limitations of the UI API, and provide the architectural knowledge needed to write safe, console-compliant logic that enhances the game without violating hardware security protocols.
1. The Architecture of the Lua Sandbox
To understand why your code might be rejected, you must understand how Transport Fever 2 handles scripts. The game utilizes Lua 5.3 as its embedded scripting language. However, the environment your mod executes within is not standard Lua; it is a heavily restricted virtual container.
1.1 The Principle of Least Privilege
The sandbox operates on the “Principle of Least Privilege.” Your mod is only granted access to the specific internal game APIs necessary to function (game.config, api.engine, api.res). It is entirely blinded to the outside world. It cannot know what operating system it is running on, it cannot see the player’s network connection, and it cannot access the local file system beyond its own designated Virtual File System (VFS) boundaries.
1.2 Deterministic Execution
On consoles, stability is paramount. The engine requires scripts to be deterministic. This means that given the same input, your code must produce the same output every single time.
- The Non-Deterministic Trap: Using standard Lua functions to get the current system time or random numbers from the OS.
- The Solution: You must use the internal game-clock and the game’s seeded random number generator. If your mod creates a “random” chance for an industry to spawn, but that randomness varies between a PC player and a Console player in the exact same save-file context, the simulation will desync and crash.
2. The Blacklist: Triggers for Immediate Security Rejection
The Urban Games API proactively strips access to standard Lua libraries that pose security threats. Attempting to call any of the following functions will result in a fatal nil value error, an engine crash, and a permanent rejection from the Mod.io console pool.
2.1 The os.* Library (Operating System Access)
The os library interacts directly with the operating system. On a console, it is a vector for system-level exploitation.
| Restricted Function | Why it is Banned |
os.execute | Can be used to run external malicious binaries. |
os.remove / os.rename | Could potentially delete or corrupt system files. |
os.getenv | Could be used to scrape user information or system IDs. |
os.tmpname | Accesses temporary directories outside the game sandbox. |
2.2 The io.* Library (File Input/Output)
While you can read configuration files packaged within your mod’s folder structure using the Transport Fever 2 specific file handlers, the standard Lua I/O library is restricted to prevent unauthorized data exfiltration.
- Prohibited:
io.open,io.popen,io.tmpfile. - The Consequence: You cannot write dynamic log files to a console’s SSD, nor can you read external configuration data from outside your specific mod directory.
2.3 Binary Extensions and package.loadlib
PC modders sometimes use .dll (Windows) or .so (Linux) files to extend Lua’s capabilities.
- The Rule: Consoles do not run external binary code. Any attempt to use
package.loadliborrequireon a non-Lua file is an immediate violation of Sony and Microsoft developer agreements. Your mod must be strictly pure Lua.
3. Algorithmic Efficiency: The Watchdog Timer
Consoles have strict “Watchdog Timers” to prevent hard lockups. If your Lua script contains an inefficient loop that takes too long to execute, the console OS will assume the game has frozen and forcefully terminate the application.
3.1 Time Complexity and the CPU Budget
When parsing massive arrays (like checking every single vehicle on a map), you must optimize for complexity.
- Safe (O(n)): A single loop iterating linearly through the vehicle list.
- Dangerous (O(n²)): A loop inside a loop (e.g., checking every vehicle against every other vehicle).
If you have 1,000 vehicles, an O(n²) script requires 1,000,000 operations. On a console CPU already handling the game’s complex pathfinding graph, this spike will trigger the OS watchdog and close the game.
3.2 Garbage Collection (GC) Spikes
Lua uses automatic memory management. If you create and destroy thousands of temporary tables in a single frame, you trigger a “GC Spike.” This causes a rhythmic, unplayable stutter in the framerate.
- The Strategy: Pre-allocate your tables. Instead of creating a new
local myData = {}inside a loop, create it once outside the loop and simply clear or overwrite its indices for re-use.
4. The UI Barrier: Gamepad Navigation Meshes
The most common reason for mod rejection on Mod.io is the mishandling of the UI API.
4.1 Mouse vs. Controller
PC players use a mouse with free-roaming pixel accuracy. Console players use a controller, which relies on UI Nav-Meshes. The highlight (focus) jumps from one interactable element to the next based on predefined paths mapped by the developers.
4.2 The “Unreachable Button” Trap
If you use Lua to inject a custom “Auto-Schedule” button into the vehicle management window, a PC player simply clicks it. However, the console’s UI Nav-Mesh does not know your dynamic button exists. The controller highlight will skip right over it, effectively placing a feature on the screen that the player physically cannot use.
4.3 The Strict UI Prohibition
Because Urban Games cannot dynamically generate controller support for arbitrary UI elements created by modders, all custom UI alterations are prohibited for Console Compatible mods.
- No custom windows.
- No custom buttons in vanilla menus.
- No interception of mouse-click events.
5. The mod.lua Lifecycle: State and Persistence
Understanding exactly when your code executes is the difference between a stable mod and a save-game breaker.
5.1 runFn(settings, modParams)
This function runs exactly once when the save game is loaded. It is used to modify global game configuration tables.
- Caution: Changes made here are “baked” into the save file memory. If you change a vanilla value here, that change persists as long as the mod is active in the load order.
5.2 postRunFn(settings, modParams)
This block executes after all other mods have completed their runFn.
- Why it Matters: If you want to modify a vehicle added by another mod (e.g., globally reducing emissions), you must do it in
postRunFn. If you do it inrunFn, your script will run before the other mod has initialized its vehicles, completely missing them.
5.3 The guiInit and guiUpdate Trap
These functions are traditionally used for UI logic. For console mods, these should remain empty or unused. Any logic placed here that attempts to draw custom UI panels to the screen will cause an immediate rejection.
6. Safe Scripting: Utilizing the api.engine
If you cannot use external libraries or custom UI, how do you add features safely? The answer lies in the Modifier Engine and Component System.
6.1 Modifier Scripts
You can safely iterate through the game’s internal resource repository to apply bulk changes without risking security.
Lua
-- Example: Console-Safe maintenance cost modifier
api.res.modelRep.addModifier("my_economic_modifier", function (fileName, data)
-- Always use nil-checks before altering tables
if data.metadata and data.metadata.maintenance then
data.metadata.maintenance.runningCosts = data.metadata.maintenance.runningCosts * 0.8
end
return data
end)
6.2 The Scripting Component (api.type.Component)
Transport Fever 2 allows you to attach custom data to entities using components. This is the certified way to store mod-specific data (like tracking a vehicle’s custom mileage or defining an industry’s unique specialty) without breaking the core save file structure or relying on external text files.
7. Save-Game Integrity: severityRemove
Because scripts can alter the deep mathematical logic of a save file, you must tell the game how dangerous it is for a player to remove your mod.
NONE: Removing the mod has no effect (used for simple texture swaps).WARNING: Removing it might delete a vehicle from the map, but it won’t crash the simulation.CRITICAL: Mandatory for economy overhauls, script injections, or mods that add new cargo types. If this mod is removed, the save will crash. This tag visually warns console players before they accidentally destroy their playthrough.
8. The Diagnostic Matrix: Troubleshooting Console Crashes
When your code crashes locally during development, you must learn to read the stdout.txt log file to pinpoint the failure.
| Error Message | Meaning | Solution |
attempt to index a nil value | Your code tried to access a table or variable that doesn’t exist. | Wrap your logic in a “nil-check”: if myTable then... |
stack overflow | An infinite or overly deep recursive loop was detected. | Check your recursive functions to ensure they have a strict exit condition. |
attempt to call a nil value (global 'os') | You used a banned standard library. | Remove all os.* and io.* calls from your codebase. |
Out of memory (Lua) | You exceeded the Lua heap limit by generating massive tables instantly. | Refactor for memory efficiency and pre-allocate data structures. |
9. Final Audit: The Scripting Certification Checklist
Before you hit “Publish” on the Mod.io API, run your codebase through this final security audit:
Banned Libraries: Search your repository for os., io., debug., and require. Delete any instances.
UI Check: Does your mod require the user to click a custom button to function? If so, it is strictly PC-only. Strip the custom UI elements for the console release.
Complexity Check: Are you running a loop-inside-a-loop on the entire entity list during initialization? Optimize your functions to O(n) to avoid watchdog timeouts.
Deterministic Logic: Are you using standard math.random? Switch to the game’s internal seeded random generator to prevent cross-platform desyncs.
Metadata Check: Is your severityRemove parameter set accurately based on how deeply your script alters the game?
By respecting the constraints of the Sandbox and focusing intensely on algorithmic efficiency, you ensure that your technical modifications are as stable as they are innovative.

