RNG Session
Introduction
In a deterministic setting, the output will always be the same given the same input. However, there are some situations where one would want to introduce some randomness to simulate the effects of unpredictable events or to provide some degree of variability to a system. Random number generators, also known as RNGs, are an important component in achieving this inside of Quantum.
RNG Session
Quantum provides the developer with RNGSession. This can be used in the simulation to generate pseudorandom numbers deterministically. The frame comes preloaded with a global session, inside its globals, but accessible directly via frame.RNG. A session will ALWAYS produce the same sequence of numbers unless the seed provided to it is different.
Usage:
C#
// Inside of the Quantum simulation
public override void OnInit(Frame frame)
{
int randomNumber = frame.RNG->Next(0, 100);
}
This session found in the Frame is seeded with the RuntimeConfig.Seed field. It is up to the user to set a specific Seed if desired. The default seed provided is 0 and its random sequence will always be the same while the same value is used. Next sessions ellaborate how to change seed before the game starts or while the game is running.
Changing the Seed before the Game starts
Every Quantum game is started with a specific set of arguments, which includes the RuntimeConfig which has the seed.
Thus, to change the initial game seed, find the script which is responsible for starting the Quantum game and change the Seed there.
For example, when using the Quantum Start UI, that place would be the QuantumStartUIConnect.ConnectAsync() method. When arguments are being defined, a random seed can be defined via RuntimeConfig.Seed = UnityEngine.Random.Range(int.MinValue, int.MaxValue);, for example. This guarantees that every time the game is run, a random seed is set, thus the random sequence will be different.
PS: notice that the explanation above involves changing a class that comes with the Quantum SDK. The best approach would be, instead, to have a custom implementation of the connectivity script, and change it instead. Otherwise, changing SDK files directly will result in those changed being overwritten when upgrading the Quantum SDK.
Changing Seed at Runtime
If necessary, the seed can be changed at runtime directly in the simulation by overwriting the session with a new one. It can be a very simple logic such as:
C#
// inside of simulation
public void ResetSeed(Frame frame)
{
int newSeed = 100;
frame.Global->RngSession = new Photon.Deterministic.RNGSession(newSeed);
}
RNG Sessions in Components
One issue that may arise with a shared global RNG Session (such as the one in frame.RNG) is when it is mixed with Prediction Culling: during prediction, the random numbers polled from the RNG Session will not include the use of the session by entities from outside of the culling radius. Then during a Verified frame, the session progresses differently now that all the entities are included. This causes the number sequence to be different due to the culling itself.
This can make entities behaviour "jitter" momentarily: say an entity chose to use a specific attack, chosen randomly using frame.RNG, but the mismatch due to the prediction culling causes the sequnece to be different on the Verified frame, making the entity use a different attack in the end.
This can be avoided by giving entities their own RNG session in a component, instead of sharing a global one. Each entity progresses their own session without affecting the result of others. An RNG session can be defined in a component:
C#
// DSL component
component MyComponent
{
RNGSession Session;
}
Then, define the session seed similarly to how it is gone with the frame sesssion:
C#
public void InitComponentWithSeed(MyComponent* component)
{
int newSeed = 100;
component->Session = new RNGSession(newSeed);
}
for more information, see: Avoiding RNG Issues
Cheating
Due to the nature of determinism, predicting randomness is quite trivial. For example, a hacker could read the simulation locally, then duplicate the RNGSession outside of the simulation and know the given sequence of numbers before anyone else. A common way to combat this is to frequently reset the seed to a number that cannot be easily predicted. For example, you could hash the player's input and use the result as the seed. This makes it very hard for anyone to control OR predict what the seed will be.
This can be useful to reduce the predictability of the RNG session, which could be a mechanism used by ill intentioned players in order to try and predict what is gonna happen since, again, a session always behave the same way, given a specific seed.
One example of how to make the RNG session constantly changing is to set it to the sum of all the player characters positions, every second or so. Since ill intentioned players cannot fully control these variables, it would be hard to know what is the seed and predict its random sequence in time. For example, every second iterate through all the player-controlled entities and sum their X + Y + Z positions to come with a final value, and set that as the seed by creating a new RNG session, as exemplified in this document.
A note on determinism
Important: Only advance RNG sessions from within Quantum Simulation code and never from Unity/view code. This is important due to the fact that the RNG session internally holds its state value which determines the sequence of random numbers.
This means that using frame.RNG->Next() (or a session contained within a component) and similar API advances the internal state, thus wrongly using it on view code would cause the game to desync (checksum error).