> For the complete documentation index, see [llms.txt](/llms.txt)


# C# State Machine Definition Reference

Specifications for states, operations, continuing processing, rewards, and random numbers written in C#



You define the data a game holds and how it handles player operations in a C# class. This page is the reference for that format and its APIs.

You use **LocalStateMachineKit** to generate definitions. It generates the GSL to register with GS2-StateMachine from the C# definition. When you use speculative execution, it also generates C# classes for calling operations from Unity.

- Steps to actually build and run one: [Tutorial]()
- How to use the generated C# classes from your game: [StateMachine Speculative Execution Session API]()
- Specifying output locations and SDK references: [Generation Settings and Registration]()

You choose where the generated GSL runs with [`hostProfile`](#project) in the generation settings. This page assumes the default, `LocalReplayV1`.

| `hostProfile` | Execution model | Differences from this page |
| --- | --- | --- |
| `LocalReplayV1` (default) | StateMachine speculative execution. The client runs operations, and the server replays the operation history to verify it | None |
| `RealtimeRoomV1` | Runs inside a Realtime room | Classes, records, Start, and Step are written the same way. Uses the Room API instead of [GameRewards](#rewards) and [GameRandom](#random) |

For `RealtimeRoomV1` inputs, the Room API, sync fields and control, and execution rules inside a room, see the [Room Definition Reference]().

## Definition Components {#definition}

A definition consists of the following elements.

| Element | Meaning | Examples |
| --- | --- | --- |
| State | Game data carried over to the next operation | Item counts, HP, the selected route |
| Operation | A method describing an input accepted from outside and how it is handled | Use an item, choose a route |
| Result | Data kept when the game finishes | Final score, whether it was cleared |
| Step | A value returning the state after processing and what to do next | Wait for an operation, continue processing, finish |

"State" here means the data that is stored. "Which operations are accepted now" is specified separately with `Step`.

Keep the three types of `Step<state type, result type, type of reasons for rejecting input>` consistent throughout the definition. The examples below abbreviate this long type name as `Step` with `using Step = ...`.

### Complete Definition Example

This is the definition of `Counter`, which holds a number `Count`. `Add` adds the specified number, and `Finish` finishes. `Add` and `Finish` are operation names defined in this example.

```csharp
using Gs2.LocalStateMachine.Authoring;
using Step = Gs2.LocalStateMachine.Authoring.Step<
    GameRules.CounterState, GameRules.CounterResult, GameRules.CounterError>;

namespace GameRules;

public record CounterState(int Count);
public record CounterResult(int Count);
public enum CounterError { InvalidAmount = 1001 }

public static class Counter
{
    public static Step Start() =>
        Step.WaitFor(nameof(Add), new CounterState(0)).Or(nameof(Finish));

    public static Step Add(CounterState state, int amount)
    {
        if (amount < 1 || amount > 10)
            return Step.Ignore(CounterError.InvalidAmount);

        var next = state with { Count = state.Count + amount };
        return Step.Stay(next);
    }

    public static Step Finish(CounterState state) =>
        Step.End(new CounterResult(state.Count), state);
}
```

`Start` runs once at the beginning. In this example, it sets Count to 0 and accepts Add and Finish. After Add is processed, the same two operations are still accepted; after Finish is processed, the machine finishes.

## Declaration Rules {#declarations}

### Rules Class

```csharp
public static class Counter
{
    // Declare Start, operations, automatic steps, and helper functions
}
```

The class name becomes the state machine name. Put one rules class in one source folder. The state and result records and enums may be split into separate files within the same folder.

Only static methods can be declared in the class. Fields, properties, constructors, and type parameters cannot be used.

### Methods

`Step` below refers to `Step<TState, TResult, TCode>` with the same state, result, and reason types.

| Kind | Declaration form | When it is called |
| --- | --- | --- |
| Start | `public static Step Start()` | When the game starts. Required |
| Operation | `public static Step Add(TState state, int amount)` | When an Add input is received from outside |
| Automatic step | `private static Step Resolve(TState state)` | When specified by `Step.Next` |
| Helper function | `private static int Calculate(int value)` | When called as an ordinary C# function from a method in the definition |

The difference between operations and automatic steps is whether they wait for external input. Automatic steps run without waiting for input. To proceed to a method that returns `Step`, use `WaitFor` or `Next`; don't call it directly like `Add(...)`.

Methods must be synchronous static methods. The following declarations cannot be used.

- Overloads of methods with the same name
- async or generic methods
- ref / out / in parameters, params, optional parameters

For names of types, methods, parameters, and so on, use ASCII letters, digits, and `_`, starting with a letter or `_`. Reserved names in the generated code and name collisions are checked at generation time.

### Who Passes Arguments

`Start` takes no arguments. In other methods that return `Step`, the first parameter is the state type.

```csharp
public static Step Add(CounterState state, int amount, GameRewards rewards)
```

With this declaration, the caller passes only `amount`. The runtime passes the current `state` and `rewards` for rewards. In the generated Unity class, you call `game.Add(3)`.

| Parameter | Who passes the value | Where it can be used |
| --- | --- | --- |
| State as the first parameter | Runtime | Required in operations and automatic steps |
| `GameRewards` | Runtime | Operations and automatic steps. At most one each |
| `GameRandom<TStream>` | Runtime | Operations and automatic steps. At most one each |
| Any other data | The caller of the operation | Public operations only |

You cannot pass new external input to an automatic step. To carry values from an operation to an automatic step, include them in the state. The input name `revision` is reserved and cannot be used.

To use random numbers or rewards during initialization, create the initial state in Start and proceed with `Next` to an automatic step that has the required parameters.

## Data Types {#types}

### State, Result, and Operation Input

| Type | Handling |
| --- | --- |
| `int` | Signed 32-bit integer |
| `long` | Signed 64-bit integer. Handled without narrowing to int32 or floating point |
| `float` / `double` | Handled at their respective floating-point precision. NaN and Infinity cannot be stored or sent |
| `bool` | Boolean |
| `string` | A non-null string |
| enum | An enum declared in the source with an underlying type of `int` |
| record | A data type with properties of the types above. Use the declaration form below |

Make states and results records that declare properties as parenthesized parameters, as follows. This declaration form is called a positional record. For operation inputs, you can specify the basic types in the table, enums, and records.

```csharp
public record CounterState(int Count);
public record CounterResult(int Count);
public record MoveInput(int X, int Y);
```

Create the updated state with `new` or `with` and pass it to Step.

```csharp
var next = state with { Count = state.Count + amount };
return Step.Stay(next);
```

Record properties cannot contain other records, arrays, collections, or null. Inheritance, type parameters, additional members, property default values, and `record struct` are also not supported.

### Kinds of Enums {#enum-kinds}

| Use | Declaration example | Numeric rules |
| --- | --- | --- |
| Ordinary enums for state, input, etc. | `enum Choice { None, Small, Large }` | Don't specify numeric values explicitly |
| `TCode`: Ignore reasons | `enum Error { InvalidAmount = 1001 }` | Explicitly assign a positive int32 value to each member. No duplicates |
| `TStream`: random number uses | `enum RandomSource { Drop = 1 }` | Explicitly assign an int32 value of 0 or more to each member. No duplicates |

Manage error codes and random stream IDs as values whose meaning does not change after operation begins. A single rules class uses only one enum type for random numbers.

### SDK Data in Reward Requests

SDK objects used to build rewards are handled separately from the states and operation inputs above. You can use the nullable types, arrays, and nested Models that the SDK declares. You cannot store SDK objects in `TState` or operation inputs. For details, see [GameRewards](#rewards).

## Supported C# Syntax {#language}

The C# syntax you can use in a definition is as follows.

| Category | Supported |
| --- | --- |
| Declarations and variables | Local variables of supported types, assignment, constants |
| Creating data | `new` and `with` for positional records; creating SDK requests, Models, and arrays |
| Branching | if, conditional expressions, switch expressions with constants, when, and a final `_` |
| Loops | for, while, break, continue |
| Functions | Private helper functions in the same class, named arguments, return |
| Logical operations | bool negation and comparison, short-circuit evaluation of `&&` / `||` |
| Numeric operations | checked int32 / int64 addition, subtraction, multiplication, division, and remainder; float / double arithmetic; supported numeric conversions; compound assignment to local variables |
| Numeric functions | int32 `Math.Min` / `Math.Max` |
| SDK nullable values | `HasValue`, `Value`, `GetValueOrDefault`, `??` |

General collections, foreach, nested records, async, and calls to arbitrary external APIs are not supported. Integer arithmetic is generated with overflow checking. Loops and function calls have execution limits.

## Processing Scope of a Single Operation {#execution}

When an operation is received from outside, its method runs. If it returns `Next`, the specified automatic step also runs right after. This whole sequence is treated as a single input.

- When it reaches `WaitFor` or `Stay`, it waits for the next input with the updated state.
- When it reaches `End`, it keeps the final state and result and finishes.
- If it reaches `Ignore` along the way, this input's state changes, random number advances, and reward requests are rolled back, and it waits in the same state as before the input. The input is not added to the operation history either.

Processing completion here is the result of local execution. Server verification happens separately when you send the operation history with [Session Confirm](#confirmation).

## Step API {#step}

Step is a return value that specifies what happens next. Below, `TState` is the state type, `TResult` is the result type, and `TCode` is the enum of reasons for rejecting input.

| API | What happens next | State kept |
| --- | --- | --- |
| `WaitFor` | Waits for the specified operations | The state passed as the argument |
| `Stay` | Waits for the same combination of operations as before the input | The state passed as the argument |
| `Next` | Immediately runs the specified automatic step | Passes the argument's state to the subsequent step |
| `End` | Finishes | The argument's state. If omitted, the current state |
| `Ignore` | Rolls back this input and waits again | The state before the input |

Specify the targets of `WaitFor`, `Or`, and `Next` with `nameof(...)` pointing to a method in the same rules class. String literals, variables, and Start cannot be specified.

### WaitFor / Or

Specifies the operations accepted next.

```csharp
static Step WaitFor(string target, TState state);
Step Or(string target);
```

Makes `state` the next state and waits for input of the public operation specified by `target`. To accept multiple operations, chain them as `WaitFor(...).Or(...).Or(...)`.

```csharp
return Step.WaitFor(nameof(Add), nextState).Or(nameof(Finish));
```

`Or` is chained to a WaitFor call. The same operation cannot be specified more than once.

### Stay

Updates the state and keeps accepting the same operations.

```csharp
static Step Stay(TState state);
```

Makes `state` the next state and waits with **the same combination of operations as just before this input was accepted**. It does not mean calling the running function again.

```csharp
return Step.Stay(nextState);
```

For example, if Add and Finish were being accepted, returning Stay from Add keeps accepting both. It cannot be used in `Start` or its automatic steps before the first wait for input is reached.

### Next

Runs the next step without waiting for input.

```csharp
static Step Next(string target, TState state);
```

Passes `state` and immediately runs the private automatic step specified by `target`.

```csharp
return Step.Next(nameof(Resolve), nextState);
```

An example declaration of the target is `private static Step Resolve(CounterState state)`. You can also specify random number and reward parameters if needed. Public operations, `Start`, and methods that require new player input cannot be targets.

Whether processing is adopted or rolled back follows [Processing Scope of a Single Operation](#execution). Cycles of `Next` are rejected at generation time. To wait for input repeatedly, use `WaitFor` or `Stay`.

### End

Finishes the game and keeps a result.

```csharp
static Step End(TResult result);
static Step End(TResult result, TState state);
```

Keeps the result and finishes the state machine. After finishing, no new operations are accepted.

| Arguments | Behavior |
| --- | --- |
| `result` only | Keeps the state at that point and sets the result |
| `result, state` | Makes the specified state the final state and sets the result |

```csharp
return Step.End(new CounterResult(nextState.Count), nextState);
```

Merely creating a local variable `nextState` does not update the kept state. To keep the updated state, pass it as the second argument. When finishing immediately in `Start`, also specify the initial state as the second argument.

Finishing and requesting rewards are independent. You don't need to return End to request rewards.

### Ignore

Rolls back an input that the game rules cannot accept, with a reason.

```csharp
static Step Ignore(TCode code);
```

Does not adopt this input and returns a reason. Specify a member of the error enum directly as `code`. It cannot be specified from a variable.

```csharp
return Step.Ignore(CounterError.InvalidAmount);
```

The scope of the rollback follows [Processing Scope of a Single Operation](#execution). It cannot be used in `Start` or in automatic steps before the first wait for input.

## GameRewards: Requesting Rewards {#rewards}

Specify rewards determined in the game as GS2 SDK grant requests. Add `GameRewards` to the parameters of an operation or automatic step. You can use it in the middle of the game as well.

### Grant(request)

```csharp
void Grant<TRequest>(TRequest request) where TRequest : class;
```

You can pass to `request` only the grant requests that the SDK registers in `AcquireAction`. Requests for read APIs or consume APIs are rejected at generation time.

You can find grant operations in each service's transaction actions under **Acquire Action**. For example, check what to grant in [Inventory's acquire actions](#acquire-action) and use the corresponding SDK Request type. Whether the SDK you use supports that type is checked at generation time.

In the following example, each time Add increases Count, the same number of parts is requested. Write the usings at the top of the definition file and Add inside the rules class.

```csharp
using Gs2.Gs2Inventory.Request;
using Gs2.Gs2Inventory.Model;

// Operation inside the Counter class
public static Step Add(CounterState state, int amount, GameRewards rewards)
{
    if (amount < 1 || amount > 10)
        return Step.Ignore(CounterError.InvalidAmount);

    var next = state with { Count = state.Count + amount };
    rewards.Grant(new AcquireSimpleItemsByUserIdRequest()
        .WithNamespaceName("rewards")
        .WithInventoryName("bag")
        .WithAcquireCounts(new[] {
            new AcquireCount().WithItemName("parts").WithCount(amount)
        }));
    return Step.Stay(next);
}
```

`rewards`, `bag`, and `parts` are the names of the resources to grant to. When using a real service, specify registered names. The caller still calls `game.Add(3)`, and the runtime passes GameRewards.

### Values You Can Specify

Use the SDK's Request and Model types as they are.

| SDK value | Handling |
| --- | --- |
| `int?` / `long?` | Keeps the respective integer range |
| `float?` / `double?` | Keeps the respective precision. NaN and Infinity cannot be sent |
| bool, strings, null | Specify according to the SDK's declarations |
| Arrays and nested Models | Build them with the SDK types |

You can use `new`, object initializers, `With`, property reads and writes, and array creation and indexing. You can also build them with variables and private helper functions.

Grant saves the content at the time it is called. Changing the original object afterwards does not change the already requested reward. The recipient, `TimeOffsetToken`, and `DuplicationAvoider` are set by the runtime, so don't specify them in the definition. Whether 0, negative numbers, or null are allowed follows the specification of the grant API.

For reference settings when using SDK types, see [SDK DLL References](#sdk-references). `bindings.json` is not required.

### Requesting vs. Paying Out

Grant requests a reward. It does not increase local inventory on the spot. Requests from inputs that end in Ignore are rolled back.

The actual payout happens after the operation history is sent to the server and verified. **Accepting input locally, server verification, and applying rewards are separate stages.** For how to confirm, see [Session Confirm](#confirmation).

### Grant(id, amount): Specifying with an Enum

```csharp
void Grant<T>(T id, int amount) where T : Enum;
```

For Inventory SimpleItems only, you can also specify a reward enum and a count. For example, define `enum Reward { Parts }` and call `rewards.Grant(Reward.Parts, amount)`. Configure where to grant separately in [bindings.json](#bindings).

In this form, amount is int32. If it is 0, no request is issued; a negative value is a runtime error. The numeric ranges and constraints differ from the form that passes an SDK request.

## GameRandom: Random Numbers {#random}

Generates random numbers whose results the server can reproduce. Add `GameRandom<TStream>` to the parameters of an operation or automatic step. `TStream` is an enum representing the use of the random numbers.

```csharp
RandomStream Stream(TStream stream);
int RangeInclusive(int min, int max);
```

Choose the use with `Stream`, and get an int32 in a range that includes both ends with `RangeInclusive`. For example, `RangeInclusive(2, 4)` returns 2, 3, or 4. `min > max` is a runtime error.

In the following example, each Add increases Count by 2 to 4. Write the enum outside the rules class and Add inside the class.

```csharp
public enum RandomSource { Count = 1 }

// Operation inside the Counter class
public static Step Add(CounterState state, GameRandom<RandomSource> random)
{
    var amount = random.Stream(RandomSource.Count).RangeInclusive(2, 4);
    var next = state with { Count = state.Count + amount };
    return Step.Stay(next);
}
```

In this case, the caller calls `game.Add()`. Random number arguments are not passed. Use a separate enum member for each use, and keep IDs whose meaning does not change after operation begins. For the numeric rules, see [Kinds of Enums](#enum-kinds).

`System.Random` and `UnityEngine.Random` cannot be used. Generation and replay use the runtime's seed and usage state. In the case of Ignore, the [common rollback rules](#execution) apply.

## Validation and Failure Behavior {#validation}

| Situation | Behavior |
| --- | --- |
| Unsupported syntax or types, inconsistent transition targets | Diagnostics are shown at generation time. Source diagnostics include the file name, line, and column |
| Violation of game-specific acceptance conditions | Check inside the operation and return a reason with `Step.Ignore` |
| Mismatches in input types, numeric ranges, enum candidates, required record fields, or revision | Also checked in the generated runtime code. The input is not adopted, as an internal Retry, which is distinguished from game-specific Ignore reasons |
| Calling an operation from the Session that is not accepted while waiting | Exception |
| Runtime errors such as division by zero | Treated as errors. Unlike Ignore, returning to the pre-input state and continuing is not guaranteed |
| Generation failure | The previously successful output is kept |

Ignore and runtime errors are recovered differently. Don't represent ordinary input rejection with exceptions; use Ignore.



