This document is about: QUANTUM 3
SWITCH TO

Quantum Inputs and Commands

This page accompanies the Photon Quantum Explained: Inputs vs Commands (Deterministic Multiplayer Guide) video and explains how player intentions are sent from Unity to the deterministic Quantum simulation.

Use this page when deciding whether a gameplay action should be represented as continuous player input or as an occasional command.

Photon Quantum Explained: Inputs vs Commands (Deterministic Multiplayer Guide)

Overview

Quantum uses deterministic simulation. Given the same initial state and the same sequence of inputs, every client calculates the same simulation result.

Because the simulation state is reproduced locally, Quantum does not need to continuously synchronize transforms, velocities, health values, or other gameplay state between clients.

Instead, clients send player intentions to the simulation through:

  • inputs, sent continuously for real-time controls;
  • commands, sent on demand for occasional actions that require additional data.

Inputs and commands both enter the deterministic simulation, but they have different delivery, prediction, bandwidth, and serialization characteristics.

This video covers:

  • how deterministic state synchronization works;
  • how to define Quantum input in QTN;
  • how Unity polls and submits input;
  • how to use Unity’s Input System with Quantum;
  • how to handle button state correctly;
  • how to use a Unity UI button as input;
  • how to consume input in a Quantum system;
  • how to validate input inside the simulation;
  • when commands are more appropriate than input;
  • how to define, serialize, register, send, and consume a command;
  • the practical differences between inputs and commands.

Video Timeline

Time Section
00:00 Introduction
00:28 State in a deterministic game engine
00:43 Creating input in Quantum
01:23 Sending input from Unity to Quantum with the Input System
04:21 Consuming input in a system
04:28 Input validation and cheat protection
05:09 Completing input consumption
05:28 Testing input in Unity
05:42 Using a UI button as input
07:39 Introduction to commands
08:10 Creating a command
08:42 Sending a command from Unity to the simulation
09:22 Consuming a command in the simulation
09:57 Inputs compared with commands

Deterministic Multiplayer State

Quantum runs the same deterministic simulation on every client.

At the same verified frame, all clients are expected to have the same:

  • entity state;
  • component values;
  • physics state;
  • gameplay state;
  • simulation result.

For this reason, Quantum does not continuously transmit full gameplay state between clients.

The exchanged data primarily represents what players intend to do.

For real-time gameplay, this normally means transmitting small input structures every simulation tick.

For less frequent actions that require more data, commands can be used instead.

Inputs

Inputs represent continuous player controls.

Typical examples include:

  • movement direction;
  • aiming direction;
  • jump state;
  • fire state;
  • acceleration;
  • steering;
  • blocking;
  • crouching.

Input is defined once in Quantum DSL and submitted for each simulation tick.

Defining Input in QTN

Create a QTN file or open an existing QTN input definition.

Define the input block:

c#

input {
    FPVector2 Direction;
    button Jump;
}

This example contains:

Field Purpose
Direction Two-dimensional movement direction.
Jump Button state used to detect jump presses and releases.

Use deterministic Quantum types such as FPVector2 inside the input definition.

Quantum Button Type

For button-like input, use Quantum’s button type instead of a plain bool or integer.

The button type tracks state transitions required by gameplay code, including concepts such as:

  • currently held;
  • pressed;
  • released;
  • first press;
  • first release.

Unity only needs to provide the current held state. Quantum handles the internal button state transitions across simulation frames.

Polling Input from Unity

Unity-side code collects local device input and passes it to Quantum.

Create a MonoBehaviour responsible for input polling.

Subscribe to CallbackPollInput from OnEnable:

c#

private void OnEnable()
{
    QuantumCallback.Subscribe(
        this,
        (CallbackPollInput callback) => PollInput(callback)
    );
}

Quantum invokes this callback according to the simulation’s input polling requirements.

The callback creates the Quantum input structure, populates it, and submits it to the simulation.

Input Source Independence

Quantum is agnostic about where input originates.

Input can come from:

  • Unity’s Input System;
  • Unity’s legacy input API;
  • touch controls;
  • UI buttons;
  • gamepads;
  • custom device integrations;
  • third-party input packages;
  • recorded input;
  • AI or automated test tools.

The important requirement is that Unity-side input is converted into the Quantum input structure before it enters the deterministic simulation.

Using Unity’s Input System

When using Unity’s Input System, wrap the namespace import in the relevant compilation symbols:

c#

#if ENABLE_INPUT_SYSTEM && QUANTUM_ENABLE_INPUTSYSTEM
using UnityEngine.InputSystem;
#endif

This prevents the code from depending on the Input System when the required packages or Quantum integration symbols are not enabled.

Configuring PlayerInput

During initialization:

  1. Check whether the GameObject already has a PlayerInput component.
  2. Add it if required.
  3. Assign the project’s input action asset.
  4. Enable the input actions.
  5. Cache the actions required by the Quantum input provider.

For this example, cache:

  • movement;
  • jump.

Caching input action references avoids repeatedly resolving them during input polling.

Reading Movement Input

Read the movement action as a Unity Vector2:

c#

Vector2 movement = moveAction.ReadValue<Vector2>();

Quantum expects FPVector2, so convert the value before assigning it:

c#

input.Direction = movement.ToFPVector2();

The conversion from floating-point Unity input to Quantum fixed-point input occurs before the value is shared with the deterministic simulation.

Do not perform arbitrary floating-point gameplay calculations inside the Quantum simulation.

Reading Button Input

For jump input, read the current held state:

c#

input.Jump = jumpAction.IsPressed();

Use IsPressed() rather than frame-specific Unity methods such as:

c#

WasPressedThisFrame()

Quantum’s simulation tick rate and Unity’s rendered frame rate are not necessarily the same.

A Unity frame-based press check can be missed if no Quantum input poll occurs during the specific Unity frame in which the press was reported.

By passing the held state, Quantum’s button type can calculate press and release transitions in simulation time.

Unity Frame Rate and Quantum Tick Rate

Unity’s frame rate can vary and is often higher than the Quantum tick rate.

For example:

  • Quantum simulation: 30 ticks per second;
  • Unity rendering: variable and potentially above 30 frames per second.

Input collection should account for this difference.

Avoid assuming that one Unity frame corresponds to one Quantum simulation tick.

Using a UI Button as Input

Input can also originate from a Unity UI button.

Create a local boolean:

c#

private bool _jump;

Add a public UI callback:

c#

public void JumpPressed()
{
    _jump = true;
}

Assign this method to the Unity Button’s OnClick event.

During input polling:

c#

input.Jump = _jump;
_jump = false;

This temporarily stores the Unity-side event until the next Quantum input poll consumes it.

Resetting _jump after polling ensures that the press is submitted once rather than remaining active indefinitely.

Submitting Input

After populating the input structure, submit it to Quantum:

c#

callback.SetInput(
    input,
    DeterministicInputFlags.Repeatable
);

The Repeatable flag allows the input to be reused when appropriate if a later input value has not arrived in time.

This is useful for continuous real-time controls such as movement or held buttons.

Consuming Input in a System

Input is consumed inside Quantum simulation code.

A common player setup stores a PlayerRef in a component such as:

c#

PlayerLink

The system retrieves the current input for that player:

c#

Input* input =
    frame.GetPlayerInput(filter.PlayerLink->Player);

The system can then use the deterministic input values to update the player entity.

For example:

  • apply movement force;
  • change velocity;
  • rotate the character;
  • trigger a jump;
  • fire a weapon.

Processing Movement

Before applying movement, validate or normalize the movement direction:

c#

FPVector2 direction = input->Direction;

if (direction.SqrMagnitude > FP.One)
{
    direction = direction.Normalized;
}

The exact implementation can vary, but the simulation should enforce the legal range of received input.

The validated direction can then be used by the character movement logic.

Processing Jump Input

Check the Quantum button state inside the simulation:

c#

if (input->Jump.WasPressed)
{
    // Apply upward force or jump velocity.
}

Quantum derives WasPressed from the button’s current and previous simulation state.

The simulation does not depend on Unity’s rendered-frame timing.

Input Validation and Cheat Protection

Quantum’s deterministic architecture prevents clients from directly authoring arbitrary simulation state, but input should still be validated.

A modified client could attempt to submit invalid input values.

For example, a malicious client could change a movement vector from a normal input range to an extremely large value.

Validate player intentions inside deterministic simulation code before using them.

Common checks include:

  • normalizing movement directions;
  • clamping aim values;
  • validating target ranges;
  • enforcing cooldowns;
  • checking resource costs;
  • verifying that actions are currently allowed;
  • rejecting impossible state transitions.

For movement, normalization ensures that the direction magnitude does not exceed the expected limit.

c#

FPVector2 direction =
    input->Direction.Normalized;

The simulation remains responsible for deciding the actual gameplay outcome.

Commands

Commands represent occasional player actions that are not suitable for continuous per-tick input.

Typical examples include:

  • teleporting to a selected location;
  • buying an item;
  • selecting a dialogue option;
  • choosing a character;
  • submitting a turn;
  • casting a spell with a target;
  • changing a loadout;
  • triggering a menu action;
  • sending configuration data.

Commands are submitted only when required.

They can contain more complex data than the compact input structure, but they must be explicitly serialized.

When to Use a Command

Consider a teleport action.

A teleport request may contain a complete world position calculated from:

  • a mouse click;
  • a raycast;
  • a map selection;
  • a UI interaction;
  • a random position;
  • another Unity-side process.

Adding this position to continuously transmitted input would increase input size even though teleporting occurs only occasionally.

A command is more appropriate because it is sent only when the teleport action is requested.

Creating a Command

Create a Quantum command from the simulation folder:

Create > Quantum > Command

Name it:

CommandTeleport

Add the destination field:

c#

public FPVector3 Position;

The command represents the player’s request to teleport to the specified deterministic position.

Serializing the Command

Commands must serialize their fields explicitly.

Inside the overridden serialization method, serialize the position:

c#

public override void Serialize(
    BitStream stream)
{
    stream.Serialize(ref Position);
}

The exact method signature depends on the generated Quantum command template and SDK version.

Every field that must reach the simulation needs to be included in command serialization.

Sending a Command from Unity

Create a Unity MonoBehaviour:

TeleportPlayerToRandomPosition

In its update logic:

  1. Detect the left mouse button.
  2. Calculate a random position within a selected radius.
  3. Convert the Unity position to FPVector3.
  4. Add vertical offset if required.
  5. Create and send the command.

Conceptually:

c#

var position =
    Random.insideUnitSphere * 5f;

position.y += 2f;

QuantumRunner.Default.Game.AddCommand(
    new CommandTeleport {
        Position = position.ToFPVector3()
    }
);

The Unity-side code creates the intention. The Quantum simulation decides how to process it.

Registering the Command

Custom commands must be added to the command factory.

Open:

CommandSetup.User.cs

Register CommandTeleport in the command factory configuration.

If the command is not registered, Quantum cannot reconstruct the serialized command when it arrives in the simulation.

Consuming Commands in the Simulation

The simulation checks the commands submitted by players.

A typical workflow is:

  1. Iterate over players.
  2. Check whether a player submitted CommandTeleport.
  3. Read the command payload.
  4. Find the entity controlled by that player.
  5. Validate the requested position.
  6. Update the entity’s deterministic transform.

Conceptually:

c#

foreach (var player in frame.PlayerConnected)
{
    var command =
        frame.GetPlayerCommand(player);

    if (command is CommandTeleport teleport)
    {
        // Find the player's entity.
        // Validate teleport.Position.
        // Apply the position.
    }
}

The exact command retrieval API depends on the Quantum version and system setup.

Validating Commands

Commands should be validated just like inputs.

A command being reliably delivered does not mean its requested action should automatically be accepted.

For a teleport command, validate:

  • whether teleporting is currently allowed;
  • whether the destination is inside the playable area;
  • whether the destination overlaps invalid geometry;
  • whether the player has sufficient resources;
  • whether the ability is on cooldown;
  • whether the requested distance is legal.

The simulation remains authoritative over the result.

Inputs Compared with Commands

Criteria Input Command
Frequency Sent every simulation tick and required continuously. Sent only on demand.
Reliability Late input may be repeated, replaced, or zeroed according to the input pipeline and flags. Reliably delivered and confirmed through the command pipeline.
Bandwidth Higher continuous usage because the structure is sent every tick. Lower for infrequent actions because data is sent only when triggered.
Prediction Other clients can predict repeatable input before verified input arrives. The local client can react immediately, but remote clients cannot predict the exact tick and payload before the command arrives.
Data size Should remain small and is optimized for frequent transmission. Can contain larger or more varied payloads.
Best use case Continuous real-time control such as movement, aiming, firing, or jumping. Discrete actions such as purchasing, teleporting, spell targeting, or submitting a turn.
Typical game type Real-time action games such as shooters, platformers, racing, or action games. Turn-based games, menu interactions, and infrequent gameplay events.
Serialization Defined through the QTN input structure and handled by Quantum. Fields must be serialized explicitly.

Choosing Between Input and Command

Use an input when the action:

  • may occur every simulation tick;
  • must be predicted by remote clients;
  • represents continuous control;
  • can be expressed with a small fixed data structure;
  • should support repeatable input behavior.

Use a command when the action:

  • occurs occasionally;
  • requires a larger or variable payload;
  • represents a discrete decision;
  • does not need to be predicted every tick;
  • should be sent only when triggered.

Example Classification

Gameplay Action Recommended Mechanism
Movement direction Input
Aim direction Input
Jump held state Input
Fire button Input
Vehicle steering Input
Buy item Command
Select spawn point Command
Teleport to selected position Command
Submit turn Command
Change character class Command
Cast a targeted spell Command
Confirm menu selection Command

Some systems may use both.

For example, a spell system could use:

  • input for continuous aiming;
  • a command for confirming a target and submitting a larger payload.
  1. Define a compact input structure in QTN.
  2. Collect device or UI state in Unity.
  3. Convert Unity values to deterministic Quantum types.
  4. Use held-state values for Quantum buttons.
  5. Submit input through CallbackPollInput.
  6. Retrieve input using the corresponding PlayerRef.
  7. Validate values inside the simulation.
  8. Apply the validated gameplay result.
  1. Create a Quantum command.
  2. Add the required deterministic fields.
  3. Serialize every field.
  4. Register the command in CommandSetup.User.cs.
  5. Create the command from Unity when the action occurs.
  6. Submit it through the active Quantum game.
  7. Retrieve it in simulation code.
  8. Validate the request.
  9. Apply the deterministic result.

Key Concepts

Concept Description
Deterministic simulation The same state and input produce the same result on every client.
Input Compact player intention submitted continuously per simulation tick.
Command On-demand player intention containing explicitly serialized data.
QTN input block DSL definition of the input structure.
button Quantum input type that tracks held, pressed, and released states.
CallbackPollInput Unity-side callback used to populate Quantum input.
DeterministicInputFlags.Repeatable Allows input reuse according to the deterministic input pipeline.
PlayerRef Identifies the player whose input or command is being processed.
Input validation Simulation-side checks that constrain player-provided values.
Command serialization Explicit definition of how command fields are transmitted.
Command factory Registration used to reconstruct custom command types.

Best Practices

Use this checklist when implementing inputs and commands:

  • Keep input structures small.
  • Use input for continuous real-time controls.
  • Use commands for occasional actions with larger payloads.
  • Use deterministic types inside input and command definitions.
  • Convert Unity floating-point input before it enters simulation logic.
  • Use IsPressed() for Unity Input System buttons.
  • Let Quantum’s button type calculate press and release transitions.
  • Account for differences between Unity frame rate and Quantum tick rate.
  • Buffer UI button presses until the next input poll.
  • Submit input through CallbackPollInput.
  • Use Repeatable only where repeating the previous input is appropriate.
  • Validate all input values inside the simulation.
  • Validate command payloads before applying their effects.
  • Serialize every custom command field.
  • Register every custom command type.
  • Keep the simulation responsible for the final gameplay result.

Common Pitfalls

Pitfall Why It Is a Problem
Using Unity WasPressedThisFrame() for Quantum input The Unity frame containing the press may not align with a Quantum input poll.
Sending large data in the input structure Input is transmitted continuously and should remain compact.
Using commands for movement Commands are not intended for continuous predicted control.
Using input for rare large payloads The data would be transmitted every tick unnecessarily.
Performing floating-point gameplay logic inside the simulation Unity floating-point calculations are not part of Quantum’s deterministic type system.
Trusting movement vectors without validation Modified clients could submit values outside the expected range.
Forgetting to serialize a command field The receiving simulation will not reconstruct the complete payload.
Forgetting to register a custom command Quantum cannot create the command from the received data.
Automatically accepting a command Reliable delivery does not make the requested gameplay action valid.
Treating Unity frame rate as the simulation tick rate Input presses and releases may be missed or duplicated.
Resetting a buffered UI press before Quantum polls it The simulation may never receive the action.

Summary

Inputs and commands are the two main mechanisms for sending player intentions into the Quantum simulation.

Inputs are compact, continuous, and suitable for predictable real-time controls such as movement, aiming, and jumping. They are defined in QTN, populated through CallbackPollInput, and consumed every simulation tick.

Commands are sent only when required and can carry larger, explicitly serialized payloads. They are suitable for discrete actions such as teleporting, buying an item, selecting a target, or submitting a turn.

Both inputs and commands must be validated inside the deterministic simulation. Clients provide intentions, while the simulation determines the valid gameplay result.

Back to top