> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pioneeroptimisation.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Decisions

> Request a decision, apply the returned action, read typed payloads.

## Action definitions

An action definition is a decision point with a fixed set of candidate actions. Define it in the dashboard, where its default action becomes the fallback that code generation bakes into `PioActions.All`. Register the same key in `PioConfig.Actions` with a `FallbackAction`, the action your game uses when the backend is unreachable.

## Requesting

`PIO.GetDecision(key)` requires an initialised SDK and a registered key; it throws `ArgumentException` for an unregistered key and `InvalidOperationException` before `Init` completes. Gate calls on `PIO.IsReady`. Each call auto-emits a decision event and increments a per-action counter included in future state snapshots.

## Reading the result

Branch on `Source`; there are exactly three, and the SDK never leaves you without an answer.

```csharp Samples~/BasicIntegration/Assets/Scripts/GuideExamples.cs theme={null}
var d = await PIO.GetDecision("Tutorial");
if (this == null) return;
switch (d?.Source)
{
    case DecisionSource.Worker:     // model-sampled action
    case DecisionSource.Fallback:   // backend unreachable: your registered fallback
        Apply(d.Action);
        break;
    case DecisionSource.Baseline:   // control group: Action is null
    default:                        // null result: tracking is disabled
        Apply("Short");             // your control experience
        break;
}
```

A `Fallback` decision arrives when the backend is unreachable, times out past `DecisionTimeoutMs`, or authentication has not succeeded. A `Baseline` decision means this player is in the control group: `Action` is null, and you show your control experience. The SDK records the arm on every decision either way. The call itself returns null only when tracking is disabled. Cancelling your own token rethrows `OperationCanceledException` rather than falling back.

## Where decisions run

Decisions run on the device whenever it can execute your model, and in the cloud when it cannot. There is no setting for this. After initialisation, the SDK downloads the current model and serves decisions locally, with no network round-trip. Any decision the device cannot serve goes to the cloud, which uses the same model, the same option limits and the same retirements.

`Source` is `Worker` for both, because the model sampled the action either way. Your branching code does not change.

A decision goes to the cloud when:

* the model has not finished downloading yet, which is common for the first decisions after install
* the platform has no on-device runtime, for example an x86 Android emulator
* the downloaded model failed its integrity check
* the model needs a newer SDK than the one in your build
* the on-device run failed, in which case the same decision is retried in the cloud

To see which path served a decision, set `EnableDebugLogging` on `PioConfig`. Each decision then logs a `decision routing` line naming the reason. A cloud-served decision costs a network round-trip and is bounded by `DecisionTimeoutMs`. That round-trip is the usual cause of a decision that is slower than expected.

## Payloads

An action option can carry a payload: difficulty curves, copy, discount percentages, edited in the dashboard and delivered with the decision. The model still chooses among the same option keys; the payload is opaque to training. You retune numbers server-side with no client release and no retraining.

```csharp Samples~/BasicIntegration/Assets/Scripts/GuideExamples.cs theme={null}
// The option key selects the arm; the numbers ride the payload,
// edited in the dashboard with no client release. Every getter is
// total: missing payload, missing key, or wrong type returns the
// default you pass.
var difficulty = await PIO.GetDecision("FirstLevelDifficulty");
if (this == null) return;
if (difficulty == null) return; // Tracking disabled: nothing to read.
var enemyHealth = difficulty.GetFloat("enemy_health", 1f);
var introCopy = difficulty.GetString("intro_copy", "Welcome");
```

Every getter is total. A missing payload, missing key, or type mismatch returns the default you pass, so a client built against an older schema keeps running. Fallback decisions carry no payload; their getters return your defaults.

For compile-time names and types, generate typed payload classes with `PIO/Generate Code Constants`; the [API reference](/api-reference) documents the generated shapes and the schema-drift warning that keeps them honest.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.