A setup snapshot starts a new playback crowd at the current step. It holds the time step, crowd options, navigation mesh and crowd state.
dotnet add package RecursiveAI.OnePathRequires .NET 10 (net10.0); .NET 9 and earlier aren’t supported.
using System.Buffers; using RecursiveAI.OnePath.Replay; // recorder is your live CrowdRecorder. var snapshot = new ArrayBufferWriter<byte>(); recorder.WriteSnapshot(snapshot); var player = new CrowdPlayer(snapshot.WrittenSpan); Console.WriteLine(player.Crowd.StepCount);
10Unzip the example, then dotnet run.
With the full program’s live recorder at step 10, the snippet takes a current setup snapshot and starts another player at that step. The program also saves that snapshot as setup.bin in the example zip.
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 creates the live recorder used by the snippet at the top and writes its snapshot to setup.bin.
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)); for (int i = 0; i < 10; i++) recorder.Step(); var snapshot = new ArrayBufferWriter<byte>(); recorder.WriteSnapshot(snapshot); File.WriteAllBytes("setup.bin", snapshot.WrittenSpan.ToArray()); var player = new CrowdPlayer(File.ReadAllBytes("setup.bin")); Console.WriteLine($"Starts at step: {player.Crowd.StepCount}"); recorder.Step(); player.Step(); Console.WriteLine($"Continued at step: {player.Crowd.StepCount}"); Console.WriteLine($"Hash matches: {player.Crowd.StateHash() == crowd.StateHash()}");
dotnet run prints:
Starts at step: 10 Continued at step: 11 Hash matches: True
Malformed setup or replay bytes throw CrowdReplayException; edits to the mesh while recording make recorder.Step() throw InvalidOperationException.
Variations
Read the time step restored from the setup. This prints True.
// After creating player in the full program: Console.WriteLine(player.Dt == recorder.Dt);
Related recipes
- Record and play a crowd simulation replay in C#
- Find a crowd replay state hash mismatch in C#
- Publish live crowd simulation replay records in C#
- Restore a crowd’s saved state
The short answer
Call CrowdRecorder.WriteSnapshot to write the current setup, then pass those bytes to new CrowdPlayer(...). The player restores a new mesh and crowd at the saved step. It does not rewind the existing crowd. Use this when a consumer joins a running simulation and needs its present state before receiving future commands.
How do I start crowd replay from the current simulation state?
The full program first records movement for ten steps. It then writes the setup to an ArrayBufferWriter<byte> and saves those bytes as setup.bin. Constructing the player restores step 10. One more step on both sides produces step 11 with matching state hashes, because neither side receives another command between those steps.
The setup is more than a list of positions. It contains the recorder’s time step, start-step number, crowd options, a full navigation-mesh snapshot and the crowd’s saved state. CrowdPlayer reconstructs them together. It also checks that the restored crowd’s step count equals the setup’s start step. You do not reset numbering to zero when attaching mid-run.
Is a replay snapshot the same as crowd rollback?
No. A player owns a newly restored crowd and mesh. Restoring saved state into an existing crowd is a separate rollback workflow. A setup snapshot is useful when the consumer does not already have the same mesh and simulation options, because those are embedded in the setup.
WriteSnapshot captures the crowd as it is now. By contrast, a retained replay’s Setup is the state from when its recorder was constructed. If that recorder has advanced, exporting its replay does not turn its original setup into a current snapshot. Choose current snapshot for a new live attachment; choose the replay’s original setup when you intend to play all its saved steps.
How do I continue playback after a snapshot?
The next commands must describe changes after the captured state. Apply those operations in order and advance the player once for each corresponding host step. Do not resend commands already reflected in the snapshot; that would apply them twice. The example uses an unchanged destination after the snapshot, so a single step on each side is enough to demonstrate continuation.
For a live consumer, take the snapshot at a consistent boundary: the crowd must be exactly as it was after the last record sent to that consumer. Serialize snapshot creation and recorder operations on one thread. The live-record documentation describes that boundary. A snapshot does not subscribe the player to later changes automatically, and it supplies no network connection. Your application must deliver subsequent records and retain their order. Query filters are not serialized; the restored crowd uses none, so filtered runs are not reproduced by this setup alone.