Deterministic Randomness with RNGSession
This page accompanies the Photon Quantum RNGSession Explained: Deterministic Randomness Without Desyncs video and explains how to generate random values safely inside the deterministic Quantum simulation.
Use this page when implementing gameplay systems that require variability, such as random character selection, critical-hit calculations, AI decisions, procedural events, or randomized spawn behavior.
Overview
Randomness and determinism are not mutually exclusive.
A deterministic random-number generator produces a repeatable sequence of values from an initial seed. If every client starts with the same seed and advances the generator in the same order, every client receives the same random values.
Quantum provides RNGSession for deterministic random-number generation inside the simulation.
The simulation exposes a global RNG session through the frame. Projects can also store additional RNGSession instances in components when random streams need to be isolated by entity or system.
This video covers:
- why deterministic games still need random values;
- how to use the global frame RNG;
- how to select and spawn a random entity prototype;
- why the configured seed affects local and online matches;
- how to assign a match seed before the simulation starts;
- how to replace the global RNG session during simulation;
- when entity-specific RNG sessions are useful;
- how Prediction Culling can affect a shared RNG stream;
- how to store
RNGSessionin a component; - how to inspect RNG state in the Quantum State Inspector;
- the difference between deterministic randomness and secure randomness;
- why simulation RNG must only be advanced by simulation code.
Video Timeline
| Time | Section |
|---|---|
| 00:00 | Introduction |
| 00:47 | Spawning a random character |
| 01:19 | Seed behavior in the local Debug Runner |
| 01:43 | Configuring a seed with Quantum Start UI |
| 02:15 | Production approach for match seeds |
| 02:32 | Replacing the global RNG session |
| 02:58 | Component-based RNG sessions |
| 04:13 | Component-based RNG example |
| 05:43 | Reducing predictability of RNG sequences |
| 06:25 | When not to use the simulation RNG |
Why Deterministic Games Need Randomness
Many gameplay systems require variation.
Examples include:
- selecting a random character;
- determining whether an attack is critical;
- choosing an AI action;
- selecting a spawn location;
- varying procedural encounters;
- generating randomized item drops;
- changing movement or behavior intervals.
Using a non-deterministic random source independently on each client would produce different results and cause a desynchronization.
Instead, Quantum uses a seeded deterministic generator.
Given the same:
- seed;
- simulation state;
- call order;
- number of RNG calls;
the generator returns the same sequence on every client.
RNGSession
RNGSession is Quantum’s deterministic random-number generator state.
Calling a method such as Next() does two things:
- It returns a deterministic random value.
- It advances the internal RNG state.
Because the state changes after each call, all clients must advance a shared RNG session in exactly the same order.
Quantum includes a global session that is accessible through the simulation frame:
c#
frame.RNG
Depending on the API context, it can be accessed as a pointer:
c#
frame.RNG->Next(0, 10);
Spawning a Random Character
The first example selects a random character prototype when a player joins.
Add an array of entity prototype references to:
RuntimeConfig.User.cs
c#
public AssetRef<EntityPrototype>[] CharacterPrototypes;
This allows multiple entity prototypes to be assigned from Unity.
The array becomes part of the match configuration.
Selecting a Random Prototype
Inside the system’s player-added callback, retrieve the runtime configuration and generate an array index:
c#
var config = frame.RuntimeConfig;
int randomIndex = frame.RNG->Next(
0,
config.CharacterPrototypes.Length
);
var prototypeRef =
config.CharacterPrototypes[randomIndex];
frame.Create(prototypeRef);
Next uses an inclusive lower bound and an exclusive upper bound.
For an array with three entries, the possible indexes are:
0, 1, 2
Passing the array length as the upper bound prevents the generated index from exceeding the valid range.
Validating the Prototype Array
Before selecting a random entry, ensure that the array exists and contains at least one prototype.
Conceptually:
c#
if (config.CharacterPrototypes == null ||
config.CharacterPrototypes.Length == 0)
{
return;
}
This prevents an invalid random range and an out-of-bounds array lookup.
The Role of the Seed
A deterministic RNG sequence begins from a seed.
The same seed produces the same sequence, provided the RNG is advanced in the same order.
If the seed remains 0, repeated local sessions can select the same character and produce the same random sequence each time.
This is expected deterministic behavior.
Local Debug Runner Seed
The local Quantum Debug Runner can expose a setting such as:
Use Random Seed
When enabled, the local test session starts with a different seed.
This is useful during local development, but it does not automatically configure seeds for production matchmaking or online sessions started through another connection flow.
Quantum Start UI
The standard Quantum Start UI can be added through:
Tools > Quantum > Setup > Add Start UI to Current Scene
The connection flow constructs the runtime configuration before the match begins.
A match-specific seed should be assigned as part of that connection or matchmaking setup.
Assigning the Seed Before a Match
When using a custom menu, lobby, or connection manager, assign the runtime seed before starting the Quantum game.
Conceptually:
c#
runtimeConfig.Seed = UnityEngine.Random.Range(
int.MinValue,
int.MaxValue
);
This produces a match-specific seed on the Unity side before the deterministic simulation begins.
Only one agreed runtime configuration should be used to start the match. Do not let each client independently choose a different seed.
In a production flow, the seed should normally be selected by the authoritative match-creation or matchmaking process and shared as part of the session configuration.
Avoid Editing Package Scripts Directly
The video demonstrates the seed assignment near the connection setup in:
QuantumStartUIConnection.cs
Avoid permanently modifying package-managed scripts directly.
Package upgrades can overwrite those changes.
Prefer one of these approaches:
- duplicate and customize the connection script;
- extend the provided connection flow where supported;
- create a custom connection manager;
- inject the runtime configuration through your matchmaking layer.
Replacing the Global RNG Session
The global RNG session can be replaced from inside deterministic simulation code:
c#
frame.Global->RngSession =
new RNGSession(newSeed);
The replacement seed must itself be deterministic.
Every client must calculate the same newSeed at the same simulation frame.
Valid sources can include:
- deterministic component values;
- verified frame numbers;
- deterministic match state;
- a command processed identically by all clients;
- a pre-agreed value from runtime configuration.
Do not use Unity-only values, local time, device state, or independently generated client values.
Effects of Replacing the Global RNG
Replacing the global session resets the future random sequence.
Any system using the global RNG after that point receives values from the new sequence.
This can affect:
- AI decisions;
- procedural events;
- random damage;
- spawn choices;
- item drops;
- any other system using
frame.RNG.
Reset the global session only when the project deliberately wants to change all downstream global random behavior.
For isolated behavior, a component-based RNG session is usually safer.
Global RNG Session
The global RNG is convenient when:
- call order is stable;
- every relevant entity is simulated consistently;
- the random value is part of global match flow;
- the system does not need an isolated sequence;
- skipped or reordered calls cannot occur.
Examples can include:
- selecting the initial map variation;
- choosing a global round modifier;
- generating a match-wide event;
- creating seeds for isolated local RNG streams.
Risks of One Shared RNG Stream
A shared RNG stream depends on every call occurring in the same order.
Changing the number or order of calls changes every value that follows.
For example, if one system makes an additional call, later systems receive different values.
This can make unrelated systems influence each other:
- AI system A calls the global RNG.
- Spawn system B calls the global RNG.
- A change causes AI system A to call the RNG twice.
- Spawn system B now receives a different value.
Separate RNG sessions can reduce this coupling.
Component-Based RNG Sessions
RNGSession can be stored in a Quantum component.
This creates a random stream associated with a specific entity.
Example component definitions:
c#
component RandomNumberProvider
{
RNGSession RNG;
}
component RandomNumberStorage
{
FP Number;
}
RandomNumberProvider stores the entity-specific RNG state.
RandomNumberStorage stores a generated result for the demonstration.
Because component data is part of the simulation frame, the RNG state is:
- deterministic;
- rollback-compatible;
- inspectable;
- isolated from unrelated entities.
Prediction Culling and RNG
Prediction Culling can skip selected entities during predicted frames.
Entities outside the relevant prediction area may not run their normal simulation logic until a verified frame or until they become relevant again.
This matters when many entities share the global RNG.
Consider this sequence:
- A distant entity normally calls the global RNG.
- Prediction Culling skips that entity during a predicted frame.
- The predicted simulation does not make that RNG call.
- Another predicted entity calls the global RNG and receives a different position in the sequence.
- The verified simulation later includes the skipped call.
- Predicted and verified random decisions differ.
Rollback can correct the final state, but the predicted result may visibly change.
For example, an AI entity might select one attack during prediction and another after verification.
Isolating RNG by Entity
An entity-specific RNG session prevents another entity’s call pattern from shifting its sequence.
Each entity advances only its own generator.
This is useful for:
- prediction-culled entities;
- AI agents;
- procedural entity behavior;
- independently simulated environmental objects;
- systems whose execution can be skipped or scheduled differently;
- reducing coupling between unrelated random decisions.
The entity-specific RNG state is stored in a component and rolls back with the entity.
Initializing a Component RNG
When creating the entity, generate a deterministic seed from the global RNG:
c#
var provider = new RandomNumberProvider();
int uniqueSeed = frame.RNG->Next(
int.MinValue,
int.MaxValue
);
provider.RNG = new RNGSession(uniqueSeed);
frame.Add(entity, provider);
frame.Add(entity, new RandomNumberStorage());
The global session is used once to derive the local seed.
After initialization, the entity advances its own RNG session independently.
Unique and Deterministic Seeds
The generated seed is deterministic because it comes from the global Quantum RNG.
It is entity-specific because a new seed is pulled for each created entity.
The result depends on deterministic creation and call order.
If stronger separation is required, the seed can also incorporate deterministic identifiers such as:
- an entity creation index;
- a player reference;
- a stable configuration ID;
- a deterministic spawn sequence number.
Any seed derivation must use deterministic data and deterministic operations.
Creating an RNG System
Create a system that filters entities containing both RNG components:
c#
namespace Quantum
{
public unsafe class RNGSystem :
SystemMainThreadFilter<RNGSystem.Filter>
{
public struct Filter
{
public EntityRef Entity;
public RandomNumberProvider*
RandomNumberProvider;
public RandomNumberStorage*
RandomNumberStorage;
}
public override void Update(
Frame frame,
ref Filter filter)
{
if (frame.Number % 100 == 0)
{
var randomNumber =
filter.RandomNumberProvider
->RNG.Next(0, 100);
filter.RandomNumberStorage
->Number = randomNumber;
}
}
}
}
Every 100 simulation frames, the system advances the entity’s RNG and stores a new value.
Frame-Based Update Interval
The condition:
c#
frame.Number % 100 == 0
runs the randomization logic every 100 simulation frames.
The real-time interval depends on the simulation rate.
For example:
| Simulation Rate | Interval |
|---|---|
| 30 Hz | Approximately 3.33 seconds |
| 60 Hz | Approximately 1.67 seconds |
| 100 Hz | 1 second |
Use frame intervals when the behavior should remain tied to deterministic simulation time.
Practical Uses for Local RNG
The stored number is only a demonstration.
A component RNG can drive real gameplay behavior such as:
- choosing an AI attack;
- selecting a movement direction;
- determining an idle duration;
- selecting a patrol destination;
- choosing an animation state identifier;
- determining a deterministic loot result;
- selecting a projectile pattern;
- scheduling an environmental event.
Keep the resulting gameplay decision in deterministic simulation state.
Inspecting RNG State
Open the Quantum State Inspector while the simulation is running.
For each entity, inspect:
- the internal
RNGSessionstate; - the current generated number;
- other components affected by the random result.
Each entity should advance independently when using its own component-based session.
This can help diagnose:
- unexpected RNG advancement;
- shared-stream coupling;
- systems calling RNG too often;
- entities using the wrong session;
- random behavior changing after rollback.
Choosing the Correct RNG Scope
| Scope | Recommended Use |
|---|---|
| Global frame RNG | Match-wide decisions and initial seed generation. |
| Component RNG | Entity-specific behavior and prediction-sensitive systems. |
| System-specific RNG in frame-backed state | Random behavior isolated to one deterministic subsystem. |
| Unity random source | Local presentation effects that never affect simulation state. |
| Secure backend random source | High-stakes results that must not be predictable by clients. |
Predictability Is Not Desynchronization
Deterministic randomness prevents clients from producing different results.
It does not make the random sequence cryptographically secure.
A player who knows:
- the algorithm;
- the seed;
- the exact call order;
may be able to predict future values.
This is a security and game-design concern, not a determinism failure.
High-Stakes Randomness
For low-risk gameplay variation, deterministic seeded RNG is often sufficient.
Examples include:
- cosmetic variations;
- ordinary AI choices;
- procedural movement;
- non-competitive environmental behavior.
For high-stakes outcomes, consider whether clients should be able to know or derive the result.
Examples include:
- valuable loot;
- wagering mechanics;
- competitive hidden information;
- economy-critical rewards;
- security-sensitive matchmaking results.
For these cases, a trusted backend or authoritative service may need to generate or commit the result before it enters the deterministic simulation.
Reseeding from Game State
The video discusses changing the random stream using dynamic deterministic game state.
For example, a new seed could be derived from player positions or other changing simulation values.
This can make casual prediction of the next value more difficult, provided that the same deterministic calculation is performed on every client.
However, this is not cryptographic security.
A client that can inspect the complete simulation state may also be able to reproduce the seed calculation.
Use deterministic game-state reseeding as a gameplay variation technique, not as a substitute for secure server-side randomness.
Advancing RNG Only in Simulation Code
Every call to Next() mutates the RNG session state.
A simulation RNG session must only be advanced from deterministic simulation code.
Do not take a simulation-owned RNGSession and advance it from:
- a MonoBehaviour;
- an Entity View Component;
- UI code;
- camera code;
- local particle logic;
- local audio logic.
A local-only RNG advancement would not occur identically on every client and could produce desynchronization if the changed session state is part of simulation data.
Unity-Side Cosmetic Randomness
Unity can still use a separate local random source for presentation-only effects.
For example, Unity view code can independently select:
- a non-gameplay particle variant;
- a decorative sound pitch;
- a camera shake variation;
- an ambient visual effect.
The result must not influence deterministic gameplay state.
The important rule is:
Never advance or modify a Quantum simulation RNG session from Unity or view code.
Presentation-only randomness should use a separate Unity-side random source and remain cosmetic.
Do Not Use Unity Random in Simulation Code
Do not call:
c#
UnityEngine.Random
from Quantum simulation systems.
Unity’s random state is not part of the deterministic Quantum frame and is not rolled back with the simulation.
Use:
frame.RNG;- a component-stored
RNGSession; - another RNG session stored in deterministic frame data.
RNG Call Order
RNG results depend on call order.
Avoid code where the number of calls can unintentionally change based on non-deterministic or presentation-only conditions.
Unsafe examples include:
- calling RNG based on Unity frame rate;
- calling RNG from view visibility;
- calling RNG based on local camera distance;
- calling RNG from platform-specific code;
- calling RNG from unordered external data.
RNG calls should depend only on deterministic simulation state.
Recommended Global RNG Workflow
- Assign a match seed before the simulation starts.
- Share the same
RuntimeConfigwith all players. - Use
frame.RNGfor match-wide deterministic decisions. - Keep the call order stable.
- Avoid unnecessary global RNG calls.
- Derive isolated RNG seeds when entities or systems need independent streams.
- Store all ongoing RNG state in frame-backed simulation data.
Recommended Component RNG Workflow
- Define an
RNGSessionfield in a QTN component. - Create the entity.
- Generate a deterministic seed.
- Initialize the component RNG.
- Advance it only from Quantum simulation code.
- Store generated results in components.
- Inspect the state with the Quantum State Inspector.
- Use separate sessions for unrelated random behaviors when necessary.
Key Concepts
| Concept | Description |
|---|---|
| Deterministic randomness | Repeatable random sequence generated from a seed. |
RNGSession |
Quantum struct containing deterministic RNG state. |
| Global frame RNG | Shared session available through frame.RNG. |
| Seed | Initial value that determines the generated sequence. |
| Component RNG | Entity-specific RNG state stored in a component. |
| RNG advancement | Mutation of generator state caused by calls such as Next(). |
| Prediction Culling | Optimization that can skip selected entities during predicted frames. |
| RNG stream coupling | One system’s RNG calls shifting the values received by another system. |
| Runtime configuration seed | Match seed assigned before starting the simulation. |
| State Inspector | Tool for inspecting component and RNG state during simulation. |
| Predictability | Ability to calculate future values when seed and call order are known. |
| Secure randomness | Randomness generated so clients cannot feasibly predict the result. |
Best Practices
Use this checklist when working with deterministic randomness:
- Use
RNGSessionfor gameplay randomness inside Quantum. - Assign a match-specific seed before starting an online game.
- Ensure every client uses the same runtime configuration.
- Let the match-creation flow choose the seed once.
- Avoid editing package-owned connection scripts directly.
- Use the global RNG for match-wide decisions.
- Use component RNG sessions for entity-specific behavior.
- Isolate random streams that may be affected by Prediction Culling.
- Store RNG sessions in frame-backed state.
- Generate local RNG seeds using deterministic values.
- Keep RNG call order stable.
- Advance RNG only from Quantum simulation code.
- Use Unity random sources only for presentation-only effects.
- Validate prototype arrays before selecting random entries.
- Inspect RNG state when diagnosing prediction or rollback differences.
- Use backend-generated results when randomness must be hidden from clients.
Common Pitfalls
| Pitfall | Why It Is a Problem |
|---|---|
| Using the default seed for every match | Every match can produce the same random sequence. |
| Letting each client generate its own match seed | Clients start with different random sequences and desynchronize. |
Using UnityEngine.Random inside simulation code |
Unity random state is not deterministic Quantum frame state. |
| Advancing a simulation RNG from a MonoBehaviour | Other clients do not perform the same state change. |
| Using one global RNG for unrelated systems | Additional calls in one system shift values used by another. |
| Using the global RNG for prediction-culled entities | Skipped predicted calls can change the predicted random sequence. |
Forgetting that Next() mutates state |
Additional diagnostic or conditional calls alter all later values. |
| Replacing the global RNG with a local value | The replacement seed may differ between clients. |
| Treating deterministic RNG as secure RNG | A known seed and call order may make future values predictable. |
| Reseeding from visible game state for security | Clients may still reproduce the deterministic calculation. |
| Using random simulation results only in Unity view state | Rollback cannot restore presentation-only gameplay decisions. |
| Selecting from an empty prototype array | Produces an invalid random range or array access. |
Related Links
Summary
RNGSession provides deterministic random-number generation for Quantum simulation code.
The global frame RNG is useful for match-wide decisions and for generating initial seeds. Component-based RNG sessions isolate random streams for individual entities and are especially useful when Prediction Culling or differing execution paths could shift a shared global sequence.
The match seed should be chosen before the simulation begins and shared through the runtime configuration. All RNG state that affects gameplay must remain inside deterministic, rollback-compatible simulation data.
Deterministic randomness guarantees repeatable results across clients. It does not provide cryptographic security or make the sequence impossible to predict. For sensitive outcomes, use an authoritative source of randomness and pass the agreed result into the deterministic simulation.
Back to top- Overview
- Video Timeline
- Why Deterministic Games Need Randomness
- RNGSession
- Spawning a Random Character
- Selecting a Random Prototype
- Validating the Prototype Array
- The Role of the Seed
- Local Debug Runner Seed
- Quantum Start UI
- Assigning the Seed Before a Match
- Avoid Editing Package Scripts Directly
- Replacing the Global RNG Session
- Effects of Replacing the Global RNG
- Global RNG Session
- Risks of One Shared RNG Stream
- Component-Based RNG Sessions
- Prediction Culling and RNG
- Isolating RNG by Entity
- Initializing a Component RNG
- Unique and Deterministic Seeds
- Creating an RNG System
- Frame-Based Update Interval
- Practical Uses for Local RNG
- Inspecting RNG State
- Choosing the Correct RNG Scope
- Predictability Is Not Desynchronization
- High-Stakes Randomness
- Reseeding from Game State
- Advancing RNG Only in Simulation Code
- Unity-Side Cosmetic Randomness
- Do Not Use Unity Random in Simulation Code
- RNG Call Order
- Recommended Global RNG Workflow
- Recommended Component RNG Workflow
- Key Concepts
- Best Practices
- Common Pitfalls
- Related Links
- Summary