Quickstart — C++ Usage
Resolve the current GameInstance’s UProbabilityDirectorSubsystem, register your named Streams once during stable initialization, reuse those Streams through the public Sequential or Keyed APIs, check every returned status, and centralize game-specific Stream IDs and Decision Key conventions in a small project helper or Blueprint Function Library.
This guide shows the simplest C++ pattern for using Probability Director in gameplay code and organizing it cleanly as your project grows.
The main workflow is:
Get the current GameInstance subsystem | vCreate/register your Streams during stable initialization | vKeep using those same Stream IDs | vCall Sequential or Keyed operations | vCheck returned Status/Result | vRemove Streams only when their real lifecycle endsThere is no process-global Probability Director singleton and no hidden default Stream.
1. Include the Runtime Subsystem
Section titled “1. Include the Runtime Subsystem”For stateful Runtime operations:
#include "ProbabilityDirectorSubsystem.h"Your C++ gameplay code normally works through:
UProbabilityDirectorSubsystemwhich is owned by the current UGameInstance.
2. Get the Correct Subsystem
Section titled “2. Get the Correct Subsystem”A simple helper:
#include "ProbabilityDirectorSubsystem.h"#include "Engine/GameInstance.h"#include "Engine/World.h"
UProbabilityDirectorSubsystem* GetProbabilityDirector(UObject* WorldContextObject){ if (!WorldContextObject) { return nullptr; }
UWorld* World = WorldContextObject->GetWorld(); if (!World) { return nullptr; }
UGameInstance* GameInstance = World->GetGameInstance(); if (!GameInstance) { return nullptr; }
return GameInstance->GetSubsystem<UProbabilityDirectorSubsystem>();}At a call site:
UProbabilityDirectorSubsystem* Probability = GetProbabilityDirector(this);
if (!Probability){ return;}3. Do Not Make a Process-Global Subsystem Pointer
Section titled “3. Do Not Make a Process-Global Subsystem Pointer”Avoid:
static UProbabilityDirectorSubsystem* GProbabilityDirector;Probability Director is GameInstance-scoped.
In PIE you can have:
GameInstance A -> Probability Director A
GameInstance B -> Probability Director BA static/global pointer can easily refer to the wrong context or outlive its owner.
4. Where to Create Streams
Section titled “4. Where to Create Streams”Create long-lived gameplay Streams during stable initialization.
Good owners include:
- Your
UGameInstance-owned manager. - A GameInstance subsystem.
- A persistent gameplay service.
- Another clearly authoritative long-lived owner.
Avoid creating/removing a Stream around every random call.
5. Basic Stream Registration Shape
Section titled “5. Basic Stream Registration Shape”Probability Director uses:
FProbabilityStreamConfigto describe a Stream.
Conceptually:
FProbabilityStreamConfig Config;
// Configure:// - Stream ID// - Seed Source// - Decision Mode// - Persistence Policy// - Fixed Seed when Fixed is selected
Probability->CreateStream(Config);Use the actual returned status/result declared by your installed ProbabilityDirectorSubsystem.h.
Do not assume registration succeeded merely because the Stream ID is non-empty.
6. Stream IDs
Section titled “6. Stream IDs”A Stream is identified by:
FProbabilityStreamIdExample:
FProbabilityStreamId StreamId;StreamId.Name = TEXT("Combat.Damage");Use semantic IDs.
Good:
Combat.DamageCombat.CriticalLoot.DropTableWorld.EncountersNPC.NamesAvoid temporary object names and pointer-based identity.
7. Centralize Stream IDs
Section titled “7. Centralize Stream IDs”Do not duplicate string literals throughout the project.
Instead, create one project namespace/helper.
Example:
namespace MyGameProbabilityStreams{ inline FProbabilityStreamId CombatDamage() { FProbabilityStreamId Id; Id.Name = TEXT("Combat.Damage"); return Id; }
inline FProbabilityStreamId LootDropTable() { FProbabilityStreamId Id; Id.Name = TEXT("Loot.DropTable"); return Id; }}Then gameplay uses:
const FProbabilityStreamId StreamId = MyGameProbabilityStreams::CombatDamage();This avoids spelling drift.
8. Choosing Stream Configuration
Section titled “8. Choosing Stream Configuration”| Setting | Meaning |
|---|---|
| Stream ID | Which gameplay system owns the random source |
| Seed Source | Secure, Fixed, or Imported initialization |
| Decision Mode | Sequential or Keyed |
| Persistence Policy | Ephemeral, PersistState, or RotateOnSave |
Each setting solves a different problem.
9. Choosing Secure vs. Fixed
Section titled “9. Choosing Secure vs. Fixed”| Need | Use |
|---|---|
| Normal fresh gameplay randomness | Secure |
| Reproducible automated test | Fixed |
| Reproduce a deterministic bug | Fixed |
| Approved imported state | Imported |
A Fixed Seed is deterministic and nonsecret.
Secure internal material remains private.
10. Choosing Sequential vs. Keyed
Section titled “10. Choosing Sequential vs. Keyed”Use this rule:
Sequential = "Give me the next result."
Keyed = "Give me the result for this exact semantic decision."11. Sequential Distribution Example
Section titled “11. Sequential Distribution Example”A concrete Runtime sampling example:
#include "ProbabilityDirectorSubsystem.h"
void UMyGameplayComponent::SampleGameplayValue(){ UProbabilityDirectorSubsystem* Probability = GetProbabilityDirector(this);
if (!Probability) { return; }
const FProbabilityStreamId StreamId = MyGameProbabilityStreams::CombatDamage();
FProbabilityNormalDistribution Distribution;
// Configure Distribution through the public fields/constructor // provided by the installed Runtime API.
double Sample = 0.0;
const FProbabilityDistributionSamplingStatus Status = Probability->TrySampleSequentialNormal( StreamId, Distribution, Sample);
// Check Status according to the public contract // before using Sample.}The important pattern is:
Get subsystem-> use already-created Stream ID-> call operation-> check Status-> then use output12. Sequential Dice Example
Section titled “12. Sequential Dice Example”For a numeric Dice total, the public Runtime shape is:
Probability->ResolveSequentialNumericDicePipeline( StreamId, Pipeline, FlatModifier, DiceBias, OutResult);Use the public result/status contract declared by the installed Runtime header.
Do not treat OutResult as a successful roll unless the returned operation status says it succeeded.
13. Keyed Operation Shape
Section titled “13. Keyed Operation Shape”A Keyed operation uses:
FProbabilityDecisionKeyand a Stream configured for Keyed Decision Mode.
Example shape:
FProbabilityDecisionKey DecisionKey;
// Fill through the approved public construction/validation path:// Namespace// Stable Subject ID// Decision// Occurrence
double Sample = 0.0;
const FProbabilityDistributionSamplingStatus Status = Probability->TrySampleKeyedNormal( StreamId, DecisionKey, Distribution, Sample);The caller owns Occurrence.
There is no hidden Keyed counter.
14. Decision Key Design
Section titled “14. Decision Key Design”| Field | Purpose |
|---|---|
| Namespace | Broad gameplay system |
| Stable Subject ID | Stable subject/entity identity |
| Decision | What is being decided |
| Occurrence | Which repeated deterministic occurrence |
Example:
Namespace:Loot
Stable Subject ID:Chest.Forest.0042
Decision:Rarity
Occurrence:015. Do Not Use Pointers as Key Identity
Section titled “15. Do Not Use Pointers as Key Identity”Avoid:
FString::Printf(TEXT("%p"), ChestActor);for a Stable Subject ID.
Pointer addresses:
- Change between runs.
- Are process-specific.
- Do not represent gameplay identity.
Use stable semantic IDs instead.
Example:
Chest.World01.Room12.Chest0416. Explicit Occurrence
Section titled “16. Explicit Occurrence”If one semantic subject needs several deterministic decisions:
| Decision | Occurrence |
|---|---|
| First loot item | 0 |
| Second loot item | 1 |
| Third loot item | 2 |
Your application owns that mapping.
Probability Director does not silently increment it.
17. Sequential Call Order
Section titled “17. Sequential Call Order”Sequential mode is deliberately call-order sensitive.
Example:
Old:A -> B -> CAfter code change:
A -> X -> B -> CIf X consumes Sequential random state, B and C can change.
If B/C need stable semantic identity independent of X, consider Keyed mode or a separate Stream.
18. Separate Unrelated Streams
Section titled “18. Separate Unrelated Streams”Avoid putting unrelated systems on one Sequential Stream.
Instead of:
Game.Randomprefer:
Combat.DamageLoot.DropTableWorld.EncountersThis reduces accidental call-order coupling.
19. Check Registration with HasStream
Section titled “19. Check Registration with HasStream”Use:
Probability->HasStream(StreamId)when you need to verify lifecycle state.
Example:
if (!Probability->HasStream(StreamId)){ // Expected Stream was not initialized for this GameInstance. return;}Use this for validation/debugging.
Do not turn every gameplay function into “create if missing.”
One owner should initialize the Stream.
20. Read Stream Configuration
Section titled “20. Read Stream Configuration”Use:
GetStreamConfigurationwhen you need to verify the actual registered contract.
Useful when:
- A Keyed call is rejected.
- You expected Sequential but registered Keyed.
- You expected Fixed but registered Secure.
- You need to inspect Persistence Policy.
Treat registered configuration as authoritative.
21. Enumerate Streams
Section titled “21. Enumerate Streams”Use:
GetStreamIdsprimarily for:
- Developer tools.
- Debug menus.
- Validation.
- Tests.
Gameplay systems should normally know their owned IDs directly.
22. RemoveStream
Section titled “22. RemoveStream”Use:
RemoveStreamwhen the Stream’s real lifecycle ends.
Do not remove/recreate a long-lived Stream:
- After every operation.
- On every level transition.
- Because one Actor was destroyed.
Remember: the Stream belongs to the current GameInstance.
23. Build a Small Project Probability Layer
Section titled “23. Build a Small Project Probability Layer”For a larger C++ game, create a thin project layer around Probability Director.
Example:
MyGameProbability/ MyGameProbabilityStreams.h MyGameProbabilityKeys.h MyGameProbabilityLibrary.h MyGameProbabilityLibrary.cppUse it to centralize:
- Stream IDs.
- Decision namespaces.
- Stable Subject ID conventions.
- Stream initialization.
- Common operations.
- Project-specific error handling.
24. Why Add Your Own Layer?
Section titled “24. Why Add Your Own Layer?”Probability Director exposes generic APIs such as:
Resolve Sequential Numeric Dice TotalTry Sample Keyed NormalYour game thinks in terms such as:
Roll Weapon DamageChoose Chest RarityChoose EncounterGenerate NPC NameA thin wrapper makes gameplay code easier to read.
25. C++ Blueprint Function Library
Section titled “25. C++ Blueprint Function Library”If you want game-specific C++ helpers that are also available in Blueprint, derive from:
UBlueprintFunctionLibraryExample:
#pragma once
#include "CoreMinimal.h"#include "Kismet/BlueprintFunctionLibrary.h"#include "MyGameProbabilityLibrary.generated.h"
UCLASS()class MYGAME_API UMyGameProbabilityLibrary : public UBlueprintFunctionLibrary{ GENERATED_BODY()
public:
UFUNCTION( BlueprintCallable, Category="My Game|Probability", meta=(WorldContext="WorldContextObject")) static bool InitializeGameProbability( UObject* WorldContextObject);};The implementation can resolve the current UGameInstance Probability Director subsystem and create the Streams owned by your game.
26. World Context in Your Own BFL
Section titled “26. World Context in Your Own BFL”Use Unreal’s World Context pattern so your wrapper resolves the correct GameInstance.
Example helper:
UProbabilityDirectorSubsystem*UMyGameProbabilityLibrary::ResolveProbabilityDirector( UObject* WorldContextObject){ if (!WorldContextObject) { return nullptr; }
UWorld* World = WorldContextObject->GetWorld(); if (!World) { return nullptr; }
UGameInstance* GameInstance = World->GetGameInstance(); if (!GameInstance) { return nullptr; }
return GameInstance->GetSubsystem<UProbabilityDirectorSubsystem>();}Do not store one global subsystem pointer inside the BFL.
27. BFL Initialization Pattern
Section titled “27. BFL Initialization Pattern”Your project function can conceptually do:
InitializeGameProbability(WorldContext) | +--> Resolve subsystem | +--> Has Combat.Damage? | | | +--> No -> Create configured Combat.Damage | +--> Has Loot.DropTable? | +--> No -> Create configured Loot.DropTableThis gives your project one controlled initialization entry point.
28. Do Not Silently Auto-Create from Every Wrapper
Section titled “28. Do Not Silently Auto-Create from Every Wrapper”Avoid a wrapper like:
RollWeaponDamage(){ if (!HasStream(CombatDamage)) { CreateStream(CombatDamage); }
// roll...}That hides lifecycle problems.
Better:
InitializeGameProbability -> owns Stream creation
RollWeaponDamage -> expects Combat.Damage to exist -> returns/records clear failure if it does not29. BFL Stream ID Helper Example
Section titled “29. BFL Stream ID Helper Example”You can also expose project IDs to Blueprint:
UFUNCTION( BlueprintPure, Category="My Game|Probability")static FProbabilityStreamId GetCombatDamageStreamId();Implementation:
FProbabilityStreamIdUMyGameProbabilityLibrary::GetCombatDamageStreamId(){ FProbabilityStreamId Id; Id.Name = TEXT("Combat.Damage"); return Id;}Now both C++ and Blueprint can use the same canonical ID.
30. BFL Project-Specific Wrapper Example
Section titled “30. BFL Project-Specific Wrapper Example”A useful wrapper might be:
Roll Weapon Damageinstead of exposing every caller to all low-level Dice configuration.
Conceptually:
bool UMyGameProbabilityLibrary::RollWeaponDamage( UObject* WorldContextObject, int32 NumDice, int32 FlatModifier, int64& OutDamage){ UProbabilityDirectorSubsystem* Probability = ResolveProbabilityDirector(WorldContextObject);
if (!Probability) { return false; }
const FProbabilityStreamId StreamId = GetCombatDamageStreamId();
// Build your approved Dice pipeline. // Call ResolveSequentialNumericDicePipeline. // Check the returned public status. // Copy the successful final outcome to OutDamage.
return true;}Keep the wrapper thin.
Let Probability Director continue to own the actual probability/Dice logic.
31. BFL Key Builder Example
Section titled “31. BFL Key Builder Example”A project can expose:
Make Chest Loot Keyto Blueprint/C++.
Conceptually:
FProbabilityDecisionKey MakeChestLootKey( const FString& StableChestId, FName Decision, int32 Occurrence);The helper should enforce your game convention:
Namespace = LootStable Subject ID = supplied stable chest IDDecision = supplied semantic decisionOccurrence = supplied explicit occurrenceThis avoids every caller inventing a slightly different Key format.
32. Recommended Namespace Constants
Section titled “32. Recommended Namespace Constants”Centralize Keyed namespaces too.
Example:
namespace MyGameProbabilityNamespaces{ static const FName Loot(TEXT("Loot")); static const FName Combat(TEXT("Combat")); static const FName World(TEXT("World"));}Use whatever type the current public FProbabilityDecisionKey fields require.
The important design point is one canonical project convention.
33. Handle Results Explicitly
Section titled “33. Handle Results Explicitly”Do not write:
double Value = 0.0;Probability->TrySampleSequentialNormal(..., Value);UseValue(Value);without checking the returned status.
Preferred:
double Value = 0.0;
const auto Status = Probability->TrySampleSequentialNormal( StreamId, Distribution, Value);
if (!/* Status success according to public contract */){ return;}
UseValue(Value);A default-looking output can still be a valid result.
Status is authoritative.
34. Do Not Infer Stream Mode from the Function You Wanted to Call
Section titled “34. Do Not Infer Stream Mode from the Function You Wanted to Call”Suppose you intended:
Loot.DropTable = Keyedbut it was actually registered Sequential.
Do not assume the Stream is Keyed because your caller chose a Keyed API.
Query the registered configuration when debugging.
The registered Stream contract wins.
35. Persistence Policy
Section titled “35. Persistence Policy”Choose one of:
| Policy | Meaning |
|---|---|
| Ephemeral | Runtime only |
| PersistState | Restore approved deterministic state |
| RotateOnSave | Commit prepared rotation after host save success |
Persistence Policy is separate from:
- Seed Source.
- Decision Mode.
Read Persistence and Save Game Integration before choosing production save behavior.
36. Fixed Stream Test Pattern
Section titled “36. Fixed Stream Test Pattern”A useful automated/manual test:
1. Create Fixed Sequential Stream with known Seed.2. Perform N operations.3. Record results.4. Destroy/recreate equivalent clean runtime.5. Create same Stream/configuration.6. Repeat exact operation order.7. Compare results.This verifies your integration is reproducing the intended deterministic path.
37. Keyed Test Pattern
Section titled “37. Keyed Test Pattern”For Keyed behavior:
1. Create Fixed/compatible Keyed Stream.2. Build one semantic Decision Key.3. Call operation.4. Call same operation again with same Key.5. Confirm deterministic repetition.6. Change Occurrence.7. Confirm it addresses a different deterministic coordinate.Do not evaluate “random quality” from one individual Key.
38. Use the Runtime Inspector During Development
Section titled “38. Use the Runtime Inspector During Development”During PIE:
Tools → Director Suite → Runtime Inspectors → Probability Director Runtime InspectorUse it to verify:
- Stream registration.
- Seed Source.
- Decision Mode.
- Persistence Policy.
- Recent retained decisions.
- Sequential Effect.
- Sanitized Keyed identity.
It is read-only and does not expose raw generator state.
39. Common Problem — Subsystem Is Null
Section titled “39. Common Problem — Subsystem Is Null”Possible causes:
| Cause | Fix |
|---|---|
| Object has no valid World | Call from valid Runtime context |
| World has no GameInstance | Wait for appropriate lifecycle |
| Object belongs to teardown/stale context | Re-resolve from current owner |
| Cached pointer outlived GameInstance | Stop using global/static cache |
Always null-check the resolved subsystem.
40. Common Problem — Stream Not Found
Section titled “40. Common Problem — Stream Not Found”Check:
Did initialization run?Is Stream ID exact?Correct GameInstance?Was Stream removed?Did gameplay call before initialization?Use:
HasStream(StreamId)and the Runtime Inspector.
41. Common Problem — Duplicate Registration
Section titled “41. Common Problem — Duplicate Registration”If initialization can run more than once:
if (!Probability->HasStream(StreamId)){ // Create through your centralized initialization path.}Do this in the owner/initializer.
Do not spread duplicate-protection throughout unrelated gameplay functions.
42. Common Problem — Sequential Results Shifted
Section titled “42. Common Problem — Sequential Results Shifted”Check whether a new operation was inserted earlier on the same Stream.
If unrelated systems are coupled:
- Split them into separate Streams.
- Or move stable semantic decisions to Keyed mode.
43. Common Problem — Keyed Result Repeats
Section titled “43. Common Problem — Keyed Result Repeats”If the same:
NamespaceStable Subject IDDecisionOccurrencewas used, repetition is expected.
Change Occurrence only when the gameplay semantics say this is another occurrence.
44. Common Problem — Keyed Result Changes
Section titled “44. Common Problem — Keyed Result Changes”Compare the actual Key fields.
A changed:
- Namespace.
- Stable Subject ID.
- Decision.
- Occurrence.
means a different deterministic coordinate.
Also confirm the same Stream/configuration is being used.
45. Common Problem — Pointer-Based Stable Subject ID
Section titled “45. Common Problem — Pointer-Based Stable Subject ID”Do not use:
reinterpret_cast<uintptr_t>(Actor)or an address-derived string.
Instead give the subject a stable gameplay identity.
Examples:
Chest.Forest.0042NPC.Village.BlacksmithQuest.Main.FindArtifact46. Common Problem — Recreating Streams on Map Load
Section titled “46. Common Problem — Recreating Streams on Map Load”Because the subsystem is GameInstance-scoped, map travel does not necessarily mean the Stream lifecycle ended.
Keep long-lived Stream ownership outside transient Level/Actor setup when appropriate.
47. Common Problem — Wrapper Becomes a Second Probability System
Section titled “47. Common Problem — Wrapper Becomes a Second Probability System”Your helper layer should not:
- Implement its own random generator.
- Duplicate Probability Director Dice math.
- Duplicate bias math.
- Maintain a secret second Stream counter.
- Reimplement persistence.
Your wrapper should express your game semantics and call Probability Director.
48. Suggested C++ Organization
Section titled “48. Suggested C++ Organization”Source/MyGame/ Probability/ MyGameProbabilityStreams.h MyGameProbabilityKeys.h MyGameProbabilityLibrary.h MyGameProbabilityLibrary.cpp MyGameProbabilityManager.h MyGameProbabilityManager.cppPossible responsibilities:
| File | Responsibility |
|---|---|
| Streams | Canonical Stream IDs |
| Keys | Key namespace/identity builders |
| Library | Blueprint-friendly wrappers |
| Manager | Startup/lifecycle ownership |
Keep it small until the project actually needs more.
49. Recommended Initialization Flow
Section titled “49. Recommended Initialization Flow”GameInstance created | vYour probability owner initializes | +--> Create Combat.Damage +--> Create Loot.DropTable +--> Create World.Encounters | vGameplay beginsThen gameplay code only consumes the already-registered Streams.
50. Recommended Gameplay Flow
Section titled “50. Recommended Gameplay Flow”Gameplay event | vResolve current Probability Director subsystem | vUse canonical Stream ID | vBuild Decision Key if Keyed | vCall public operation | vCheck Status | +--> Success -> use result | +--> Failure -> handle/log according to game policy51. What to Log
Section titled “51. What to Log”Good project logging:
Probability operation failedStream: Combat.DamageOperation: Resolve DamageStatus: <public readable status>Avoid logging private internals such as:
- Secure root.
- Raw RNG state.
- Generator keys/nonces.
- Raw persistence payloads.
Use the Runtime Inspector for approved diagnostic details.
52. Blueprint Exposure from Your C++ Layer
Section titled “52. Blueprint Exposure from Your C++ Layer”Use your UBlueprintFunctionLibrary when designers should call game-semantic operations.
Examples:
My Game | Probability | Roll Weapon DamageMy Game | Probability | Choose Chest RarityMy Game | Probability | Get Combat Damage Stream IDMy Game | Probability | Make Chest Loot KeyThis can make Blueprint graphs much cleaner than exposing project-specific conventions everywhere.
53. Keep Generic Operations Available Too
Section titled “53. Keep Generic Operations Available Too”Do not necessarily hide all Probability Director nodes.
Your BFL should provide convenience for common game semantics while advanced systems can still use the official public API directly where appropriate.
Example:
Common weapon -> Roll Weapon Damage wrapper
Special procedural tool -> direct Probability Director distribution API54. Quick C++ Reference
Section titled “54. Quick C++ Reference”| Need | API/concept |
|---|---|
| Get Runtime system | UGameInstance::GetSubsystem<UProbabilityDirectorSubsystem>() |
| Register Stream | CreateStream |
| Check Stream | HasStream |
| Read config | GetStreamConfiguration |
| Enumerate | GetStreamIds |
| Remove | RemoveStream |
| Next result | Sequential API |
| Semantic deterministic result | Keyed API + FProbabilityDecisionKey |
| Reproducibility | Fixed Seed Source |
| Fresh RNG | Secure Seed Source |
| Blueprint project wrappers | UBlueprintFunctionLibrary |
| Runtime debugging | Probability Director Runtime Inspector |
55. Recommended Rules
Section titled “55. Recommended Rules”- Resolve the subsystem from the current GameInstance.
- Never use a process-global Probability Director pointer.
- Create long-lived Streams during stable initialization.
- Centralize Stream IDs.
- Separate unrelated Sequential systems.
- Use Keyed identity for semantic deterministic decisions.
- Own Occurrence explicitly.
- Check every public operation’s result/status.
- Keep your project wrapper thin.
- Use the Inspector rather than exposing private RNG internals.
56. Reusable Resolution Rules Data Assets
Section titled “56. Reusable Resolution Rules Data Assets”For reusable authored numeric behavior, reference or load an UNumericDiceResolutionRules Data Asset. The asset is shared immutable configuration; it does not contain Stream state or a Decision Key.
#include "ProbabilityDirectorResolutionRules.h"#include "ProbabilityDirectorSubsystem.h"
UNumericDiceResolutionRules* Rules = /* project-owned asset reference */;FProbabilityResolutionContext Context;Context.StreamId = MyRegisteredStreamId;
FProbabilityNumericResolutionOverrides Overrides;Overrides.AdditionalFlatModifier = CurrentModifier;Overrides.bHasAdditionalDiceBias = true;Overrides.AdditionalDiceBiasPercent = 15;
FProbabilityNumericDiceResolution Total;const FProbabilityResolutionRulesStatus Status = Probability->TryResolveNumericRulesTotal(Rules, Context, Overrides, Total);
if (Status.IsSuccess()){ const int64 Value = Total.FinalOutcome;}Use TryResolveNumericRulesSuccessCount with FProbabilityNumericSuccessCountResolution when the asset uses Success Count, and read SuccessCount only after success. Always check FProbabilityResolutionRulesStatus before using outputs. Flat Modifier composition is checked additive composition; AdditionalDiceBiasPercent is optional and exact. The Data Asset is never mutated.
For Persistent Keyed resolution, set Context.DecisionPolicy = EProbabilityResolutionDecisionPolicy::PersistentKeyed, enable Context.bOverrideDecisionPolicy, and supply a valid FProbabilityDecisionKey with stable Namespace, StableSubjectId, Decision, nonnegative Occurrence, and KeySchemaVersion 1.
See DataAssets.md for the asset boundary and StreamsAndDecisionKeys.md for stream ownership.