Skip to content
Tuxedo CatSTUDIO
Menu

Streams and Decision Keys

Use a Stream to separate a gameplay system’s random decisions, use Sequential mode when order matters, and use Keyed mode when a specific semantic decision should remain deterministic regardless of unrelated call order.

Probability Director uses Streams to organize random decisions.

A Stream answers:

Which source of random decisions should this gameplay system use?

A Decision Key answers:

Which specific deterministic decision am I asking a Keyed Stream to make?

You do not need to understand RNG internals to use either one.


GameInstance
|
+--> Stream: Combat.Damage
|
+--> Stream: Loot.DropTable
|
+--> Stream: World.Encounters

Each Stream has its own configuration and behavior.

A Stream can operate in one of two main Decision Modes:

Sequential
= give me the next result
Keyed
= give me the result for this explicit decision identity

A Stream is a registered Probability Director random source identified by an FProbabilityStreamId.

Example Stream IDs:

Combat.Damage
Combat.Critical
Loot.DropTable
World.Encounters
NPC.Names

Streams are explicit.

There is no hidden default Stream.

If gameplay needs a Stream, your game should create/register it intentionally.


Separate Streams keep unrelated gameplay systems from affecting each other.

Example:

Stream Responsibility
Combat.Damage Damage rolls
Combat.Critical Critical-hit decisions
Loot.DropTable Loot selection
World.Encounters Encounter selection
NPC.Names Procedural names

Suppose all of those systems shared one Sequential Stream.

Then adding one extra loot roll could change later combat or encounter results.

Using separate Streams prevents that kind of accidental coupling.


Probability Director Streams belong to a GameInstance.

That means two different GameInstance contexts have separate Stream registries.

Example:

PIE Context A
-> Combat.Damage
PIE Context B
-> Combat.Damage

Those are two separate Runtime Streams even though the Stream ID is the same.


If the Probability Director Runtime Inspector does not show a Stream you expected, first check that you selected the correct Runtime Context.

A common debugging mistake is looking at one PIE GameInstance while the Stream exists in another.


5. Stream Registration Does Not Consume Randomness

Section titled “5. Stream Registration Does Not Consume Randomness”

Creating/registering a Stream does not itself perform a random decision.

Conceptually:

Create Stream
-> configure RNG source
-> configure Decision Mode
-> configure Persistence Policy
-> register Stream

No random sample is produced simply because the Stream exists.

Random state is used only when an operation actually requires randomness.


A Stream has several important configuration choices.

Setting What it controls
Stream ID Which gameplay Stream this is
Seed Source How the Stream is initialized
Decision Mode Sequential or Keyed behavior
Persistence Policy What happens across save/load

These settings answer different questions.


SeedSource controls how the Stream gets its initial random identity/state.

Probability Director supports:

Seed Source Main purpose
Secure Fresh secure random initialization
Fixed Reproducible deterministic initialization
Imported Initialization from approved imported state/data

Use Secure when you want a fresh random Stream that is not intended to be reproduced from a visible seed.

Example:

Stream:
Loot.DropTable
Seed Source:
Secure

Secure Streams keep their internal source material private.

The Blueprint API, Runtime diagnostics, and Inspector do not expose:

  • Secure campaign/root material.
  • Entropy bytes.
  • Generator keys.
  • Nonces.
  • Raw generator state.

Use Fixed when you want reproducible behavior.

Example:

Stream:
Test.CombatDamage
Seed Source:
Fixed
Fixed Seed:
12345

The same compatible configuration can reproduce the same deterministic sequence.

Useful for:

  • Automated tests.
  • Reproducing bugs.
  • Controlled simulations.
  • Deterministic development scenarios.

A Fixed Seed is deterministic and nonsecret.

However, the Runtime Inspector and diagnostics intentionally hide the actual value.

They may show:

Seed Source:
Fixed

but not:

Fixed Seed:
12345

If you need the Fixed Seed value, inspect the configuration that created the Stream.


Imported is for workflows where Probability Director receives approved imported state or initialization data through the supported native contract.

Use it only when your integration specifically requires imported deterministic state.

Do not use Imported simply because you want a reproducible Stream.

For normal reproducibility, use Fixed.


You want… Use
Fresh gameplay randomness Secure
Reproducible testing Fixed
Reproduce a controlled bug Fixed
Approved imported state workflow Imported

Every Stream has a Decision Mode.

The two main modes are:

  • Sequential.
  • Keyed.

This setting changes how gameplay requests random results.


Sequential means:

Give me the next random result from this Stream.

Each operation can advance the Stream’s Sequential state.

Example:

Combat.Damage
|
+--> Roll 1
+--> Roll 2
+--> Roll 3

Call order matters.


Suppose:

Stream:
Combat.Damage
Mode:
Sequential

Your game performs:

Attack A
-> Roll Damage
Attack B
-> Roll Damage
Attack C
-> Roll Damage

Each operation consumes the next appropriate result from the Stream.

If you insert another advancing random operation before Attack B:

Attack A
-> Roll Damage
New Extra Roll
Attack B
-> Roll Damage

later results can change.

That is normal Sequential behavior.


Sequential is a good fit when:

  • Order naturally matters.
  • You want a stream of random results.
  • Gameplay consumes randomness as events occur.
  • Replaying the same exact call order should reproduce the same Fixed-seed sequence.

Examples:

Gameplay system Why Sequential fits
Repeated damage rolls Each attack consumes the next result
Card shuffle sequence Order of draws matters
Continuous encounter rolls Each request advances the sequence
Procedural variation stream Results are consumed over time

16. Sequential Mode Is Sensitive to Call Order

Section titled “16. Sequential Mode Is Sensitive to Call Order”

This is the most important rule for Sequential Streams.

If the call order changes, later results may also change.

Example:

Original:
A -> B -> C

becomes:

A -> X -> B -> C

If X consumes Sequential randomness, the results for B and C may be different.

That is not a bug.

It is how Sequential state works.


17. Not Every Successful Sequential Operation Must Advance

Section titled “17. Not Every Successful Sequential Operation Must Advance”

Some operations can succeed without consuming random state.

Example:

Only one possible outcome

Probability Director may return that outcome without drawing randomness.

Diagnostics can report:

Sequential Effect:
Did Not Advance

So do not assume:

Successful operation = Stream definitely advanced.

Use the Inspector when this distinction matters.


Keyed means:

Give me the deterministic result for this explicit decision identity.

Instead of relying on “the next result,” the caller provides a Decision Key.

Conceptually:

Stream
+
Decision Key
=
Deterministic decision

A Decision Key describes the semantic identity of one Keyed decision.

The important fields are:

Field Meaning
Namespace Broad system/category
Stable Subject ID What object/entity the decision belongs to
Decision What decision is being made
Occurrence Which repeated occurrence of that same decision

Suppose a chest needs a deterministic loot rarity.

Namespace:
Loot
Stable Subject ID:
Chest.Region3.0042
Decision:
Rarity
Occurrence:
0

This means:

For chest Chest.Region3.0042, make the Rarity decision in the Loot namespace, occurrence 0.


If you make the exact same compatible Keyed request again:

Namespace:
Loot
Stable Subject ID:
Chest.Region3.0042
Decision:
Rarity
Occurrence:
0

you should expect the same deterministic result.

That is the point of Keyed mode.


Use Occurrence when the same semantic decision needs more than one deterministic result.

Example:

Occurrence 0
Occurrence 1
Occurrence 2

These represent separate deterministic coordinates.


Suppose one chest can make three independent deterministic loot picks.

Namespace:
Loot
Stable Subject ID:
Chest.Region3.0042
Decision:
Item

Use:

Pick Occurrence
First item 0
Second item 1
Third item 2

This gives each item choice its own deterministic identity.


24. Keyed Mode Has No Hidden Occurrence Counter

Section titled “24. Keyed Mode Has No Hidden Occurrence Counter”

Probability Director does not secretly do this:

First call -> hidden occurrence 0
Second call -> hidden occurrence 1
Third call -> hidden occurrence 2

The caller supplies Occurrence explicitly.

This keeps Keyed behavior predictable.


A hidden Keyed cursor would make deterministic identity harder to reason about.

With explicit Occurrence:

Loot
Chest.0042
Item
Occurrence 2

always means the same semantic coordinate under the compatible deterministic contract.

The result does not depend on how many unrelated Keyed decisions happened before it.


Suppose your game requests:

Decision A
Decision B
Decision C

Later you change the code to:

Decision A
Decision X
Decision B
Decision C

If B and C keep the same Decision Keys, they can remain stable.

That is one of the biggest advantages of Keyed mode.


Sequential Keyed
Main idea Next result Result for explicit identity
Call order matters Yes Not in the same way
Uses Decision Key No Yes
Uses explicit Occurrence No Yes
Hidden Keyed cursor N/A No
Good for streams of events Yes Sometimes
Good for stable semantic decisions Less ideal Yes

Use Sequential when your design means:

Give me the next random result.

Use Keyed when your design means:

Give me the deterministic result for this specific gameplay decision.


A good Decision Key uses stable semantic information.

Good:

Namespace:
Loot
Stable Subject ID:
Chest.World01.Room12.Chest04
Decision:
Rarity
Occurrence:
0

Good:

Namespace:
Character
Stable Subject ID:
NPC.Village.Blacksmith
Decision:
FirstName
Occurrence:
0

Good:

Namespace:
Quest
Stable Subject ID:
Quest.Main.FindArtifact
Decision:
RewardVariant
Occurrence:
0

Avoid temporary process-specific identity.

Bad:

Stable Subject ID:
0x000001F428A0...

Bad:

Stable Subject ID:
PointerToChest

Bad:

Stable Subject ID:
ActorAddress_123456

Memory addresses and temporary pointers are not stable gameplay identity.


StableSubjectId should identify the gameplay subject consistently.

Examples:

Subject Good Stable Subject ID
Chest Chest.World01.Room12.Chest04
NPC NPC.Village.Blacksmith
Quest Quest.Main.FindArtifact
Encounter location Encounter.Forest.NorthClearing
Item definition Item.Weapon.IronSword

The exact naming scheme belongs to your game.

What matters is that it is stable and semantic.


Namespace groups related decisions.

Examples:

Loot
Combat
Character
Quest
World
AI

Use Namespace to prevent unrelated systems from accidentally sharing the same decision identity.


Decision describes what is being decided.

Examples:

Rarity
Critical
DamageVariant
FirstName
EncounterType
RewardVariant

Keep it stable and descriptive.


Think of a Keyed decision as:

Namespace
+
Stable Subject ID
+
Decision
+
Occurrence

Example:

Loot
+
Chest.World01.Room12.Chest04
+
Rarity
+
0

That combination identifies one deterministic decision.


Suppose each chest should keep the same rarity if the player reloads or approaches the chest in a different order.

Keyed is a strong fit.

Stream:
Loot.DropTable
Mode:
Keyed

Decision:

Namespace:
Loot
Stable Subject ID:
Chest.Cave.0017
Decision:
Rarity
Occurrence:
0

The result is tied to the chest decision rather than the order in which chests were opened.


Suppose combat should simply consume a sequence of fresh damage rolls.

Sequential is a natural fit.

Stream:
Combat.Damage
Mode:
Sequential

Each attack requests:

Resolve Sequential Numeric Dice Total

and the Stream advances when the operation consumes Sequential random state.


37. Example: Why Keyed Can Be Better for Procedural World Decisions

Section titled “37. Example: Why Keyed Can Be Better for Procedural World Decisions”

Suppose world generation contains:

Village A
Village B
Village C

If generation order can change during development, a Sequential Stream may cause downstream results to shift.

Keyed decisions can instead use:

Namespace:
World
Stable Subject ID:
Village.B
Decision:
MarketType
Occurrence:
0

Then Village.B can keep the same deterministic MarketType even if Village.A generation happens earlier or later.


Do not use Keyed just because deterministic behavior sounds useful.

Sequential may be simpler when:

  • Call order is naturally part of the gameplay.
  • You genuinely want a random sequence.
  • There is no meaningful stable semantic subject.
  • Explicit Decision Key management would add unnecessary complexity.

Avoid Sequential when the design requires one decision to remain stable regardless of unrelated call-order changes.

Example:

“This specific chest should always get the same deterministic rarity for this campaign.”

That is usually a better Keyed use case.


The public Stream lifecycle includes:

Operation Purpose
Create Stream Register/configure a Stream
Has Stream Check whether it exists
Get Stream Configuration Read its configuration
Get Stream IDs List registered Streams
Remove Stream Remove a Stream

Use these APIs intentionally.

There is no hidden fallback Stream.


Use Create Stream when the owning gameplay system establishes a Stream.

Example:

GameInstance initialization
|
+--> Create Combat.Damage
+--> Create Loot.DropTable

Create it before gameplay attempts to use it.


Use Has Stream when code needs to verify that a Stream exists.

Example:

Has Stream: Loot.DropTable
|
+--> True -> continue
|
+--> False -> handle missing setup

Do not use Has Stream as a substitute for proper lifecycle ownership.

The system responsible for the Stream should still know when it is supposed to be created.


Use Get Stream Configuration when you need to inspect the registered configuration.

Useful for:

  • Debugging.
  • Validation.
  • Developer tools.
  • Confirming Seed Source.
  • Confirming Decision Mode.
  • Confirming Persistence Policy.

Use Get Stream IDs when you need to enumerate registered Streams.

Typical use cases:

  • Developer tooling.
  • Debug UI.
  • Runtime inspection.
  • Internal validation.

Gameplay should usually already know the semantic Stream IDs it owns.


Use Remove Stream when a Stream’s lifecycle genuinely ends.

Example:

Temporary gameplay mode starts
-> Create temporary Stream
Mode ends
-> Remove Stream

Do not remove/recreate a persistent gameplay Stream casually if its deterministic continuity matters.


A useful design rule is:

Every Stream should have one clear gameplay owner.

Example:

Stream Owning system
Combat.Damage Combat system
Loot.DropTable Loot system
World.Encounters World encounter system
NPC.Names Character generation system

That owner should define:

  • When the Stream is created.
  • How it is configured.
  • When it is removed.
  • Which operations are allowed to use it.

47. Avoid One Global “Everything” Stream

Section titled “47. Avoid One Global “Everything” Stream”

You can technically route many systems through one Stream, but that creates unnecessary coupling.

Avoid:

Game.Random

for every random decision in the project.

Prefer:

Combat.Damage
Loot.DropTable
World.Encounters

where systems need independent random progression.


PersistencePolicy controls what happens to Stream state across save/load.

The main policies are:

Policy Meaning
Ephemeral Runtime-only state
PersistState Preserve approved deterministic state
RotateOnSave Prepare state and commit rotation only after successful save

Persistence Policy is independent of Decision Mode and Seed Source.


A Stream can combine settings in different ways.

Seed Source Decision Mode Persistence Policy Example use
Secure Sequential PersistState Gameplay sequence that continues across saves
Fixed Sequential Ephemeral Reproducible test Stream
Fixed Keyed Ephemeral Deterministic semantic test decisions
Secure Keyed PersistState Supported gameplay workflow requiring persisted campaign identity/state

Choose each setting for its own purpose.


Do not assume:

PersistState = Sequential

Persistence and Decision Mode answer different questions.

Decision Mode
= how decisions are derived
Persistence Policy
= what happens across save/load

Read Persistence and Save Game Integration before choosing a persistence policy for production gameplay.


Keyed mode still requires explicit:

Namespace
Stable Subject ID
Decision
Occurrence

Persistence does not create a hidden occurrence counter.

If the game needs:

Occurrence 5

the caller supplies:

Occurrence = 5

explicitly.


Streams and Decision Keys intentionally do not expose raw RNG internals.

The public APIs do not require access to:

  • Raw random bytes.
  • Generator keys.
  • Nonces.
  • Secure root material.
  • Raw generator state.
  • Numeric Sequential cursor.

Use semantic Stream operations instead.


The Probability Director Runtime Inspector is useful for checking Stream configuration and recent retained activity.

For a selected Stream it can help answer:

Question Inspector area
Does this Stream exist? Streams
Secure or Fixed? Streams / Details
Sequential or Keyed? Streams
Which Persistence Policy? Streams
Which Key was used? Recent Decisions / Decision Details
Did Sequential state advance? Sequential Effect
Why did a Keyed result repeat? Decision Details

The Inspector does not expose Secure state or a numeric Sequential position.


54. Example: Debugging a Repeated Keyed Result

Section titled “54. Example: Debugging a Repeated Keyed Result”

You expected two different results, but both calls returned the same value.

Check the Decision Keys.

If both calls used:

Namespace:
Loot
Stable Subject ID:
Chest.0042
Decision:
Item
Occurrence:
0

the repeated result is expected.

If you intended two separate item decisions, use:

Occurrence:
0

and:

Occurrence:
1

55. Example: Debugging a Changed Sequential Result

Section titled “55. Example: Debugging a Changed Sequential Result”

Suppose a Fixed Sequential test used to return:

A
B
C

and after a code change it returns:

A
X
B
C

Check whether the new X operation consumed Sequential state.

If it did, later results shifting is expected.

Use the Inspector’s:

Sequential Effect

to help verify retained operations.


If gameplay reports an invalid Stream:

1. Check the Stream ID spelling
2. Confirm Create Stream ran
3. Confirm the Stream was not removed
4. Confirm the correct GameInstance
5. Check the Inspector Streams table

Common problem:

Sample Loot.DropTable

runs before:

Create Loot.DropTable

Use clear semantic Stream IDs.

Good:

Combat.Damage
Combat.Critical
Loot.DropTable
World.Encounters
NPC.Names

Less useful:

Stream1
Random2
MainRNG
Stuff

A good Stream ID should make its gameplay responsibility obvious in:

  • Code.
  • Blueprint.
  • Inspector.
  • Diagnostics.
  • Exports.

Use readable semantic values.

Good:

Namespace: Loot
Subject: Chest.Cave.0017
Decision: Rarity
Occurrence: 0

Good:

Namespace: NPC
Subject: NPC.Village.Blacksmith
Decision: FirstName
Occurrence: 0

Avoid values tied to temporary Runtime implementation details.


Question Recommendation
I just need the next random result Sequential
Call order should control the sequence Sequential
This exact gameplay object needs a stable deterministic result Keyed
Generation order may change but decisions should stay stable Keyed
I need multiple results for one semantic decision Keyed + explicit Occurrence
I need reproducibility for tests Fixed Seed Source
I want fresh Secure gameplay randomness Secure Seed Source
State should not survive save/load Ephemeral
State should continue across save/load PersistState
Save success should control state rotation RotateOnSave

There is not.

Create and use Streams explicitly.


“Create Stream consumes the first random number.”

Section titled ““Create Stream consumes the first random number.””

It does not.

Registration itself does not consume randomness.


It does not.

Fixed is a Seed Source.

Persistence Policy is separate.


It does not.

Secure RNG and storage encryption are separate systems.


“Keyed means Probability Director tracks the next occurrence for me.”

Section titled ““Keyed means Probability Director tracks the next occurrence for me.””

It does not.

Occurrence is explicit.


“The same Keyed request repeating is a bug.”

Section titled ““The same Keyed request repeating is a bug.””

Usually not.

Same compatible key + same occurrence is intentionally deterministic.


“Sequential results should stay the same if I add another random call.”

Section titled ““Sequential results should stay the same if I add another random call.””

Not necessarily.

Sequential mode is call-order sensitive.


Do not use pointers or memory addresses.

Use stable semantic game identity.


“One Stream for the whole game is simpler.”

Section titled ““One Stream for the whole game is simpler.””

Sometimes initially, but it can couple unrelated gameplay systems.

Separate Streams are usually easier to reason about.


Stream:
Combat.Damage
Mode:
Sequential

Use when each attack consumes the next damage result.


Stream:
Loot.DropTable
Mode:
Keyed
Namespace:
Loot
Stable Subject ID:
Chest.World01.Room04.Chest02
Decision:
Rarity
Occurrence:
0

Use when that chest should have a stable deterministic rarity.


Decision:
Item
Occurrence:
0
Occurrence:
1
Occurrence:
2

Use one explicit Occurrence per deterministic item choice.


Stream:
Test.Dice
Seed Source:
Fixed
Decision Mode:
Sequential
Persistence:
Ephemeral

Use a known Fixed Seed so the test sequence can be reproduced.


Stream
= registered source of random decisions
Sequential
= next result
= call order matters
Keyed
= explicit semantic decision
= stable Decision Key
= explicit Occurrence
= no hidden cursor
Secure
= fresh Secure initialization
Fixed
= reproducible deterministic initialization
Persistence Policy
= Ephemeral
= PersistState
= RotateOnSave

Resolution Rules adds an authoring policy layer separate from the registered Stream’s existing Decision Mode:

  • Sequential means the asset requests existing EProbabilityDecisionMode::Sequential behavior.
  • PersistentKeyed is V1.11 authoring terminology that maps to existing EProbabilityDecisionMode::Keyed; it is not a third Runtime decision mode.

The registered Stream must exist and have the matching existing Decision Mode. Persistent Keyed calls require a FProbabilityDecisionKey with a non-None Namespace, non-empty StableSubjectId, non-None Decision, Occurrence >= 0, and KeySchemaVersion == 1. There is no hidden cursor or hidden occurrence, and Keyed calls do not advance Sequential state.

V1.11 source policy is separate from decision policy:

  • InheritConfiguredStream accepts the supported source configured on the registered Stream.
  • RequireFixed requires a Fixed source.
  • RequireSecure requires a Secure source.

These are constraints only. They do not create Streams, choose seeds, or change Stream configuration. Imported remains unsupported for V1.11 runtime Resolution Rules resolution. Secure roots, entropy, keys, nonces, and generator state are never exposed.

See DataAssets.md for reusable authored configuration and API.md for resolver functions.

  • API Overview — Blueprint/C++ Stream lifecycle and sampling APIs.
  • Persistence and Save Game Integration — Ephemeral, PersistState, and RotateOnSave.
  • Probability Director Runtime Inspector — inspecting registered Streams and recent decisions.
  • Diagnostics and Warnings — invalid Stream and Decision Key diagnostics.
  • Security Boundaries — Secure state, redaction, and Decision Key safety.
  • Monte Carlo and RNG Health — isolated simulation versus gameplay Streams.