---
title: "Record and play crowd replay in C#"
url: https://recursiveai.org/onepath/cookbook/record-and-play-crowd-replay/
description: "Record simulation commands and time steps, then rebuild the crowd and play them back from a file."
version: "1.2.0"
---

# Record and play crowd replay in C#

**OnePath** is a free, open-source pathfinding library for .NET from Recursive AI. MIT or Apache-2.0, no native dependencies.

[NuGet](https://www.nuget.org/packages/RecursiveAI.OnePath) · [GitHub](https://github.com/recursive-ai-oss/onepath-csharp) · [Docs](https://recursiveai.org/onepath/docs/) · [Download example (.zip)](https://recursiveai.org/onepath/cookbook/record-and-play-crowd-replay/record-and-play-crowd-replay.zip)

A replay saves crowd commands and steps for later playback.

```shell
dotnet add package RecursiveAI.OnePath
```

Requires [.NET 10](https://dotnet.microsoft.com/download/dotnet/10.0) (net10.0); .NET 9 and earlier aren’t supported.

`setup.bin` comes from the example zip and is created by the full program below.

```csharp
using RecursiveAI.OnePath.Replay;

var crowd = new CrowdPlayer(
    File.ReadAllBytes("setup.bin")).Crowd;
var recorder =
    new CrowdRecorder(crowd, 1.0 / 30,
        keepSteps: true);
recorder.Step();
File.WriteAllBytes("replay.bin",
    recorder.ToReplay().ToBytes());
var replay = CrowdReplay.Read(
    File.ReadAllBytes("replay.bin"));
var player = new CrowdPlayer(replay.Setup.Span);
foreach (var step in replay.Steps)
{
    player.Apply(step.Span);
    player.Step();
}
Console.WriteLine(replay.FindMismatch() is null);
```

```text
True
```

Unzip [the example](https://recursiveai.org/onepath/cookbook/record-and-play-crowd-replay/record-and-play-crowd-replay.zip), then `dotnet run`.

`setup.bin` in the zip contains the square floor and one moving crowd member used below, before the first step. The walker added before `WriteSnapshot` is already in this snapshot’s setup. In the full program’s retained replay, the recorder was constructed before the addition, so the addition is a command instead. The snippet writes `replay.bin`, plays its steps, and checks every recorded hash.

## The full program

The program creates its own floor geometry and crowd in code; it needs no external assets. `RecursiveAI.OneMath`, used for the geometry types, is installed automatically as a dependency of OnePath. It regenerates the binary input used by the snippet at the top.

```csharp
using System.Buffers;
using RecursiveAI.OneMath;
using RecursiveAI.OnePath;
using RecursiveAI.OnePath.Replay;

var floor = new[]
{
    new Triangle3D(new(0, 0, 0), new(0, 0, 40), new(40, 0, 0)),
    new Triangle3D(new(40, 0, 0), new(0, 0, 40), new(40, 0, 40))
};
var crowd = NavMesh.Build(floor, NavSettings.Humanoid).CreateCrowd();
var recorder = new CrowdRecorder(crowd, 1.0 / 30, keepSteps: true);
var walker = recorder.Add(new(5, 0, 5));
recorder.SetDestination(walker, new(30, 0, 30));

var setup = new ArrayBufferWriter<byte>();
recorder.WriteSnapshot(setup);
File.WriteAllBytes("setup.bin", setup.WrittenSpan.ToArray());
for (int i = 0; i < 10; i++) recorder.Step();
File.WriteAllBytes("replay.bin", recorder.ToReplay().ToBytes());
var replay = CrowdReplay.Read(File.ReadAllBytes("replay.bin"));
var player = new CrowdPlayer(replay.Setup.Span);
foreach (var step in replay.Steps)
{
    player.Apply(step.Span);
    player.Step();
}
Console.WriteLine($"Played {player.Crowd.StepCount} steps");
Console.WriteLine($"Final hash matches: {player.Crowd.StateHash() == crowd.StateHash()}");
Console.WriteLine($"All hashes match: {replay.FindMismatch() is null}");
```

`dotnet run` prints:

```text
Played 10 steps
Final hash matches: True
All hashes match: True
```

Malformed setup or replay bytes throw `CrowdReplayException`; edits to the mesh while recording make `recorder.Step()` throw `InvalidOperationException`.

## Variations

Route other changes through the recorder too. Insert these lines before the stepping loop; the printed checks remain true.

```csharp
// After adding walker in the full program, before the loop:
recorder.Stop(walker);
recorder.Teleport(walker, new(6, 0, 6));
recorder.SetDestination(walker, new(25, 0, 25));
```

## Related recipes

- [Find a crowd replay state hash mismatch in C#](https://recursiveai.org/onepath/cookbook/find-crowd-replay-hash-mismatch/)
- [Start crowd replay from the current simulation state in C#](https://recursiveai.org/onepath/cookbook/start-crowd-playback-from-snapshot/)
- [Publish live crowd simulation replay records in C#](https://recursiveai.org/onepath/cookbook/publish-live-crowd-replay-records/)
- [Restore a crowd’s saved state](https://recursiveai.org/onepath/cookbook/crowd-rollback/)

## The short answer

Use `CrowdRecorder` for changes and steps, keep the steps in memory, and save `ToReplay().ToBytes()`. Read that file with `CrowdReplay.Read`. `CrowdPlayer` restores its setup, applies each step’s operations, and advances by the recorded time step. This is a simulation replay, not a recording of rendered frames or a video file.

## How do I record and replay a simulation in C#?

The complete program builds a flat square from two triangles. Both the navigation mesh and the crowd’s initial state are embedded in the setup, so playback does not rebuild the floor from your original geometry files. Each recorded step holds the commands issued before it and the state hash after it. Apply the commands before calling `player.Step()`, as the full program does.

Recording starts when the recorder is constructed. Existing crowd members belong to the setup; later additions belong to the command stream. The `keepSteps: true` argument is necessary for file export. The default is live-only recording, so calling `ToReplay` with that default throws `InvalidOperationException`.

## Why does a simulation replay diverge?

Route additions, removals, destination changes, stops, teleports and time steps through the recorder. Calling the crowd member’s own methods changes the live simulation without adding those commands to the recording. That can leave the replay with valid bytes but different state hashes. `FindMismatch()` plays the recorded run and returns the first differing step index, or null if all recorded hashes match.

Mesh edits and query filters are not recorded. The recorder rejects a step if the mesh has changed since recording began. A query filter is a delegate, and the restored crowd uses none. Do not rely on an unrecorded filter to reproduce the run. Thread-count settings are not saved either; the documentation says results do not depend on them.

## Should I record inputs or simulation events?

Record the commands that actually change this crowd, rather than keyboard state or screen coordinates. A destination command includes the destination position and the crowd member’s ID. That makes playback independent of whatever UI generated the command. Preserve command order: a destination change after an addition must not arrive before the addition.

The [recording format](https://recursiveai.org/onepath/docs/replay/) specifies little-endian integers and doubles stored by bit pattern. The saved setup includes the time step, options, navigation mesh and crowd state. You do not supply a new frame delta during playback: `CrowdPlayer.Step()` uses the setup’s value. Changes after the last recorded step are not a completed replay step; step the recorder before exporting commands you want played.
