High-level simulation system requirements
Part of the simulation documentation.
This is a description of some concerns that the simulation system is designed for. Any code that interacts with the simulation system must be careful not to violate these requirements.
Players should be able to download maps and mods (which can contain simulation scripts) and run them without any risk of compromising their computer's security - the scripts should be sandboxed within the game engine.
(This is independent of any concerns about cheating in multiplayer games.)
Obviously anything like file IO APIs should be carefully controlled (and preferably should not be exposed to simulation scripts at all). Functions that expose engine functionality to scripts should validate all inputs, and should not trust the rest of the engine to be secure with unrestricted input.
It should be impossible for a script to cause the game to crash in any way - the code needs to protect itself against malicious scripts. All crashes should be considered security issues and fixed, even if they seem like harmless null pointer dereferences.
debug_assert is not adequate for preventing crashes, since users will just click 'continue' and expose themselves to whatever dangers it was trying to protect against.
In extreme unrecoverable cases like resource exhaustion, the game should (safely) terminate.
By "determinism", we mean that given a certain simulation state, and a certain sequence of inputs, and a certain set of scripts and data files, the subsequent simulation states are precisely determined. That is, the simulation must not be affected by external inputs, timings, hardware, OS, compiler settings, etc. That's necessary for the multiplayer system to work, otherwise players will have out-of-sync views of the world and their views will diverge.
Guaranteeing determinism in JS seems to be very hard to do perfectly, e.g. scripts could trigger out-of-memory errors that vary between instances of SpiderMonkey. It would also require much stricter isolation between simulation and GUI, making any interactions more complex.
The goal should instead be to ensure non-malicious scripts will be deterministic, by minimising the opportunities for non-determinism.
Math.random should be replaced with a network-synchronised RNG. Trigonometric functions should be carefully examined, and modified or removed, if they are not consistent between platforms. Any engine functions exposed to scripts must take care of determinism themselves.
Floating-point computations in C++ are considered unacceptable here (different compiler optimisations will subtly change their behaviour); all values passed to and from scripts should be integers and fixed-point numbers, and all intermediate computations exposed to simulation code should be integers and fixed-point. (Scripts themselves can use JS's floating-point Number type.)
Error situations (can't open data file, can't allocate new JS object, etc) may cause non-deterministic behaviour, but the error must be logged so we can debug out-of-sync errors.
There are two main ways that players can cheat:
- Accessing information they shouldn't have access to (e.g. details of their enemies' units that are hidden in the fog-of-war)
- Modifying the world in ways they shouldn't be able to (e.g. giving themselves extra resources, or causing an enemy's unit to decide to turn around and walk away)
It's impossible to prevent people reading information, so we can just try to make it harder (e.g. by expecting people to run officially validated binaries with some anti-debugger tricks etc). The simulation system is concerned with preventing unfair state modifications.
Since multiplayer is based on synchronised simulation, we assume that all players are running the same basic simulation code, and we can control how it responds to inputs received over the network. (If they're not all the same, they'll go out of sync and the match will stop.)
We can't stop people sending arbitrary commands over the network, so our job is to make sure the simulation code responds to those inputs in a way that makes it hard to cheat. In our model, the GUI is untrusted and can send arbitrary commands to the simulation; the only thing guaranteed by the engine is that each command will be associated with a player ID, which correctly matches the network client the command was received from. The simulation code therefore has sole responsibility for validating the actions against the player ID - make sure players can only move their own units, can only attack enemy units, can only build buildings in clear terrain and when they have sufficient resources, etc. The GUI might do some similar checks to provide immediate feedback to the user, but that must never be relied on.