Determinism

Documentation Unreal Engine AI Determinism

Tick one box and an enemy fights the same fight every time: the same actions in the same order, from a seed you choose.


Every roll an enemy makes is built from one number. Give it the same number and it makes the same choices, in the same order, every run.

Set it up

1 minute
  1. 1

    Tick Deterministic on the AI Config

    Open the Enemy AI Config and find the Determinism category at the bottom. Tick Deterministic and Default Seed appears.
    Determinism
    Deterministic
    Default Seed
    1923
    Start Barrier Seconds
    0.5
    Any seed works. Change it for a different but equally repeatable fight.
  2. 2

    Leave Default State Tree empty

    Scroll up to Behavior and clear Default State Tree. An empty field runs the built-in brain.
    Behavior
    Default State Tree
    None
    Decision Context Params
    5 members
  3. 3

    Give the project a fixed frame rate

    Project Settings > Engine > General Settings > Framerate. Tick Use Fixed Frame Rate and set Fixed Frame Rate to 60.
    Project Settings
    ▾Framerate
    Use Fixed Frame Rate
    Fixed Frame Rate30.0
    Movement and animation advance by frame time, so a varying frame rate moves enemies to different places.
  4. 4

    Play it twice

    Press Play, watch the opening of the fight, stop, and play again. The same enemies commit to the same attacks in the same order.
A State Tree cannot repeat. Unreal seeds a State Tree's randomness from the machine's clock and offers no way to hand it a seed, so transition delays and random state picks land differently every run. Drive a deterministic enemy with the built-in brain instead.

What repeats, and what only nearly repeats

Every choice repeats: which action an enemy picks, how long its cooldowns run, which way it strafes, when it sets off on patrol, which spot in a wander area it walks to, which target it picks.
Where enemies end up standing repeats closely rather than exactly. Character movement, animation and pathfinding belong to Unreal, and they accumulate arithmetic error no seed reaches. Over a thirty second fight with nineteen enemies, positions drift by a fraction of a millimetre while every decision stays identical.
So a scripted boss, a tutorial fight, a QA repro or a puzzle encounter all work. A replay system that expects frame-exact positions does not, and no plugin above the engine can make it so.

Seed one enemy, or a whole level

Three places supply the seed. The nearest one wins.
LevelWhereUse it for
This enemySet Seed on its SEC Combat ControllerPinning one boss to a fixed script while the rest of the level varies
This levelSet World Combat Seed on the SEC Determinism subsystemA roguelike run, where one number re-rolls the whole encounter
This configDefault Seed on the AI ConfigThe everyday case, set once and left alone
Call Set World Combat Seed as a run begins and every deterministic enemy builds its choices on that number. Enemies already fighting restart their randomness on their next decision; enemies spawned later pick it up when they start. An enemy holding its own Set Seed ignores a level reseed and keeps running where it was.
Two enemies sharing one config still fight differently from each other, because each mixes the seed with a hash of its pawn's name. One config with one seed gives a room full of enemies distinct, repeatable behavior.
AdvancedHanding a seed back
Clear Seed on the controller drops that enemy's own seed and hands it back to whichever seed the level or its config supplies. Clear World Combat Seed does the same at the level.
Get Root Seed reports the number an enemy's choices are built from, after all three levels have been weighed and the pawn's name mixed in. It reads 0 for an enemy that is not deterministic, which is a quick way to check a config took.
AdvancedRenaming a placed enemy changes its behavior
The name hash is stable across runs: a placed enemy carries its name in the level, and a spawned one is named by a counter that starts fresh with the level.
Rename a placed enemy in the World Outliner and it draws different numbers from the same seed. Worth knowing if a fight you tuned plays differently after a tidy-up.

The start barrier

A level takes a variable number of frames to settle after it loads. A brain that starts inside that window starts a few frames earlier or later each run, which is enough to send a repeatable fight down a different path.
Start Barrier Seconds holds every deterministic enemy's brain until the same moment on the world clock, so they all begin thinking on one frame. It ships at 0.5 seconds, counted from the start of play. Set it to 0 for an encounter that opens on a trigger, where the settling window is long past.
AdvancedPicking the number
Too short and the level has not settled, so the fight still forks. Too long and enemies stand still at the start of play for no reason. Half a second suits a level that loads quickly; raise it if two runs part company in the first seconds, and check the result with the trace recorder rather than by eye.
The barrier applies to enemies present when play begins. An enemy spawned later starts as soon as it is possessed.

Prove it repeats

The trace recorder writes down what every enemy did, frame by frame, and compares two runs for you. Its SEC.Determinism console commands are development tools, so a Shipping build does not have them.

Record two runs and compare them

2 minutes
  1. 1

    Turn the recorder on

    In the console, SEC.Determinism.AutoTrace 30. Every level that starts from now records its first thirty seconds.
  2. 2

    Play twice

    Press Play, wait for the recording to finish, stop. Do it again. The two runs land in Saved/SECDeterminism as Run_0001.csv and Run_0002.csv.
  3. 3

    Compare them

    SEC.Determinism.Compare reads the two most recent runs. It either reports a match across every row, or names the first row where they disagree: the enemy, the frame, the value that diverged and by how much.
Each row holds one enemy on one frame: its seed, combat role, brain phase, running action, target and focus, awareness state, steering direction, velocity and position at full precision.
The column that diverges first tells you where to look. Action or Role first means a decision parted company. PosX first, with every decision column still matching, means the enemies chose identically and Unreal's movement put them a fraction apart.
AdvancedDriving the recorder by hand
SEC.Determinism.Trace 30 starts a recording at once, for a fight that opens partway through a level.
SEC.Determinism.Compare 1 2 compares two runs by number. A third argument is a tolerance: SEC.Determinism.Compare 1 2 0.01 treats numbers within a hundredth as equal, which reads past arithmetic noise to the first divergence that would show on screen.
Start Trace, Stop Trace and Get Trace Run Index sit on the SEC Determinism subsystem, for a Blueprint that records on a cue.
Your own input has to match between runs. Move the player differently the second time and the traces part company for a reason that has nothing to do with the AI. Record a fight the enemies drive themselves, or stay off the controls.

When two runs still differ

The trace recorder answers each of these.
CheckWhat it looks like
Deterministic is tickedGet Root Seed reads 0 on an enemy that should be seeded
Default State Tree is emptyThe Action column parts company a second or two into the fight
Fixed Frame Rate is onTwo runs record different sample counts for the same duration
Start Barrier is long enoughThe first divergence lands in the opening frames, before the fight is under way
Nothing else drives the enemyA Blueprint calling Set Seed, or a random node in an ability graph
AdvancedRandomness the plugin does not own
A Random Float in Range node in an ability graph of your own rolls outside the seed. The plugin repeats what it rolls, and nothing else.
Unreal's perception system spreads its sight queries across frames against a wall-clock budget, so a level with many enemies watching one target can resolve who noticed first differently between runs. If the Awareness column is where two traces part, raise the sight budget in DefaultEngine.ini:
[/Script/AIModule.AISense_Sight]
MaxTracesPerTick=64
MaxAsyncTracesPerTick=64
MinQueriesPerTimeSliceCheck=1000
MaxTimeSlicePerTick=1.0
That lifts the caps past what your enemy count generates, so every query resolves every update. It costs perception time at high enemy counts.
MultiplayerMultiplayer
The AI runs on the server, so the server is the only machine that reads a seed. Nothing here replicates, and none of it is lockstep or rollback: two machines running the same seed are not kept in step with each other.
A multiplayer game gets a server that plays the same encounter the same way twice, which covers a QA repro and a scripted boss.

Where to go next