---
title: "Visualize a navmesh and crowds in C#"
url: https://recursiveai.org/onescope/cookbook/visualize-navmesh-and-crowds/
description: "Record a OnePath crowd in C#, open its navigation mesh in OneScope, and inspect character paths, targets, and intermediate replay steps."
version: "1.0.0"
---

# Visualize a navmesh and crowds in C#

**OneScope** is a free, open-source viewer for deterministic libraries from Recursive AI. MIT or Apache-2.0.

[NuGet](https://www.nuget.org/packages/RecursiveAI.OneScope.Tool) · [GitHub](https://github.com/recursive-ai-oss/onescope-csharp) · [Docs](https://recursiveai.org/onescope/docs/) · [Download example (.zip)](https://recursiveai.org/onescope/cookbook/visualize-navmesh-and-crowds/visualize-navmesh-and-crowds.zip)

OnePath is Recursive AI’s navigation library. This recipe opens only its navigation formats, not arbitrary navmesh files.

```shell
dotnet tool install --global RecursiveAI.OneScope.Tool
```

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

Unzip [the example](https://recursiveai.org/onescope/cookbook/visualize-navmesh-and-crowds/visualize-navmesh-and-crowds.zip), then `dotnet run`.

This writes `crowd.oprp`; then open it:

```shell
onescope open crowd.oprp
```

Opens the example’s crowd replay in a window. `crowd.oprp` contains an 8 × 8 walkable floor and one character walking from `(1, 0, 1)` toward `(6, 0, 6)`.

## Record the crowd in C#

The full program creates the floor and records 30 steps. The project pins `RecursiveAI.OnePath` 1.2.0 and `RecursiveAI.OnePath.Scope` 1.0.0. `OneScope.Sdk` comes from the OnePath.Scope dependency; `OneMath` comes from OnePath.

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

```csharp
using RecursiveAI.OneMath;
using RecursiveAI.OnePath;
using RecursiveAI.OnePath.Replay;
using RecursiveAI.OnePath.Scope;
using OneScope.Sdk;

// An 8 by 8 floor; true means walkable. No external input files.
var cells = Enumerable.Repeat(true, 64).ToArray();
var mesh = NavMesh.Build2D(cells, 8, 8, 1,
    new Vector2D(0, 0), NavSettings.Humanoid with { AgentRadius = 0 });
var recorder = new CrowdRecorder(mesh.CreateCrowd(), 1.0 / 30, keepSteps: true);
var agent = recorder.Add(new Vector3D(1, 0, 1), Agent.Small);
recorder.SetDestination(agent, new Vector3D(6, 0, 6));
for (int i = 0; i < 30; i++) recorder.Step();
File.WriteAllBytes("crowd.oprp", recorder.ToReplay().ToBytes());
Console.WriteLine("Wrote crowd.oprp (30 steps)");

var source = new OnePathModule().Open(File.ReadAllBytes("crowd.oprp"), "crowd.oprp");
var history = new History(source);
history.Seek(30);
Console.WriteLine($"Replayed step: {history.CurrentStep}");
Console.WriteLine($"Hashes match: {history.StateHash == recorder.Crowd.StateHash()}");
```

```text
Wrote crowd.oprp (30 steps)
Replayed step: 30
Hashes match: True
```

Run `onescope open crowd.oprp` from that folder after the program finishes. A file the module cannot read fails to open; the shell returns an error rather than treating arbitrary bytes as a navigation mesh.

## Variations

Inspect the middle of the recording without opening a window:

```shell
onescope open crowd.oprp --headless   --run 'seek {"step":15}'
```

```json
{"step":15,"start":0,"steps":30,"sources":[{"source":0,"step":15,"hash":"251cab3c4833f883"}]}
```

Turn on path drawing explicitly when opening the replay:

```shell
onescope open crowd.oprp --headless   --run 'overlay {"overlay":"Paths","on":true}'
```

List the crowd’s characters at the final step:

```shell
onescope open crowd.oprp --headless --run 'seek {"step":30}' --run 'entities {"kind":"agent","limit":1}'
```

```json
{"source":0,"step":30,"entities":[{"id":4611686018427387904,"parent":13835058055282163713,"kind":"agent","name":"agent 0"}],"count":1,"truncated":false}
```

## Related recipes

- [Attach to a running physics game](https://recursiveai.org/onescope/cookbook/attach-to-a-running-game/)
- [Write a custom replay viewer module](https://recursiveai.org/onescope/cookbook/write-a-onescope-module/)

## The short answer

Record a OnePath crowd with `CrowdRecorder`, write the result of `ToReplay().ToBytes()` to an `.oprp` file, then open that file with `onescope open`. The OnePath module is bundled with OneScope, so this format does not need a custom `--module` argument. The viewer shows the navigation mesh and the crowd state together rather than showing a mesh with no connection to a recorded run.

The C# example builds a flat, fully walkable floor so the input is easy to change. The 64 Boolean cells are the geometry input, not a screenshot or an imported asset. The program adds a character through the recorder and sets its destination through the recorder too. That records the commands needed to reconstruct the movement, rather than merely collecting positions at presentation frames.

## How do I visualize a navmesh in C#?

Choose the file that matches what you want to inspect. An `.oprp` crowd replay is useful when a character takes an unexpected route, stops, or approaches a target differently than expected. It holds the mesh together with the crowd recording. Opening this example reports a two-dimensional source because its mesh is flat; a non-flat crowd mesh opens as three-dimensional.

A navigation build input (`.opbi`) answers a different question: what happened while the navigation mesh was built? Its `stage` axis exposes voxels, filtered spans, compact spans, regions, contours, polygons, and detail. These are positions inside the build, not elapsed simulation time. The [bundled-module documentation](https://recursiveai.org/onescope/docs/shell/#bundled-modules) lists the file types and the stage sequence.

A saved mesh is not interchangeable with a crowd replay or build input just because all three concern navigation. Use the library’s recorder or module writer for the input the viewer expects. Renaming another file’s extension does not add the missing recording or build geometry.

For the crowd example, start by leaving the navigation mesh and paths visible. The default overlay declarations also include targets. Velocities and character IDs are separate toggles, useful when several moving characters overlap. Each overlay controls a group of drawing items; it does not change the underlying simulation or the pathfinding result.

## How do I inspect a navmesh character’s path?

Seek to a specific step, list entities of kind `agent`, and inspect the returned entity ID. Use the IDs returned by the viewer rather than assuming that your application’s array index is the viewer’s entity ID. The shell accepts IDs as decimal strings as well as JSON integers, which matters for clients whose numeric type cannot represent every unsigned 64-bit integer.

Paths and targets are distinct observations. A destination is an order given to the character; a path is the route that the module draws for that state. Looking at both at the same recorded step avoids confusing an older order with a newer route. The sample replays to step 30 and compares the viewer’s world hash with the crowd’s hash at that step.

To test a different order, create a fork and use a declared crowd poke such as `set_target`, `stop_agent`, or `teleport_agent`. The shell’s [operation reference](https://recursiveai.org/onescope/docs/shell/) describes the arguments. A fork preserves the original recording, so you can compare the altered trajectory with the run you first opened. For grid searches rather than crowds, `.opgr` inputs expose an `iteration` axis for the search slices; that is a different view from an individual crowd character’s path.
