---
title: "Debug running game physics live in C#"
url: https://recursiveai.org/onescope/cookbook/attach-to-a-running-game/
description: "Attach OneScope to an instrumented OnePhysics game, inspect its live replay, and seek through the received steps without pausing the host."
version: "1.0.0"
---

# Debug running game physics live 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/attach-to-a-running-game/attach-to-a-running-game.zip)

This recipe works only for games built on OnePhysics with the probe setup below. The viewer reconstructs their physics while the game runs.

```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.

```shell
onescope attach
onescope attach 12345
```

Replace `12345` with your host’s PID from the first command. The second opens its physics world in a viewer window.

Unzip [the example](https://recursiveai.org/onescope/cookbook/attach-to-a-running-game/attach-to-a-running-game.zip), then `dotnet run`.

With no probe-equipped hosts running, bare `onescope attach` prints:

```json
{"probes":[],"removedStale":[],"unreadable":[]}
```

With a host running, `probes` contains its PID, socket, module, library version, and format version.

## Run a host and attach a viewer

The download runs a small host and an in-process viewer connection: one circle starts at `(0, 4)`, the viewer attaches at step zero, then the host advances ten steps.

The project pins `RecursiveAI.OneScope.Probe` and `.Attach` to 1.0.0, `RecursiveAI.OnePhysics` to 1.0.0, and `RecursiveAI.OnePhysics.Scope` to 1.1.0, the module version bundled with the tool.

To add the host and viewer APIs to your own console project:

```shell
dotnet add package RecursiveAI.OneScope.Probe
dotnet add package RecursiveAI.OneScope.Attach
dotnet add package RecursiveAI.OnePhysics
dotnet add package RecursiveAI.OnePhysics.Scope
```

```csharp
using System.Buffers;
using OneScope.Attach;
using OneScope.Probe;
using RecursiveAI.OneMath;
using RecursiveAI.OnePhysics.Physics2D;
using RecursiveAI.OnePhysics.Scope;

var world = new PhysicsWorld2D();
world.AddCircle(0.5f, new Vector2D(0, 4));
var feed = new LiveFeedStream(world);
using var probe = ScopeProbe.Attach(
    new ProbeInfo("onephysics", "1.0.0", 2), new Snapshots(feed));
feed.Record = probe.Record;
using var recording = world.StartRecording(feed);

if (args.Contains("--host"))
{
    Console.WriteLine($"Attach with: onescope attach {Environment.ProcessId}");
    for (int i = 0; i < 3600; i++)
    {
        world.Step(1f / 60f);
        probe.Poll();
        Thread.Sleep(16);
    }
    return;
}

// The simulation thread services the snapshot request at a step boundary.
var connection = Task.Run(() => LiveSession.Connect(
    probe.SocketPath ?? throw new InvalidOperationException("Probe could not listen."), [new OnePhysicsModule()]));
while (!connection.IsCompleted)
{
    probe.Poll();
    Thread.Sleep(1);
}
using var session = connection.GetAwaiter().GetResult();
Console.WriteLine($"Attached at step {session.History.StartStep}");
for (int i = 0; i < 10; i++)
{
    world.Step(1f / 60f);
    probe.Poll();
}
var deadline = DateTime.UtcNow.AddSeconds(5);
while (session.History.EndStep < 10 && DateTime.UtcNow < deadline)
{
    session.Pump();
    Thread.Sleep(1);
}
if (session.History.EndStep != 10) throw new TimeoutException("Missing live records.");
session.History.Seek(10);
Console.WriteLine($"Viewer step: {session.History.CurrentStep}");
Console.WriteLine($"Host and viewer hashes match: {world.ComputeHash() == session.History.StateHash}");

sealed class Snapshots(LiveFeedStream feed) : ISnapshotSource
{
    public void WriteSnapshot(IBufferWriter<byte> writer) => feed.WriteSnapshot(writer);
}
```

```text
Attached at step 0
Viewer step: 10
Host and viewer hashes match: True
```

`ScopeProbe.Attach` can leave `SocketPath` null when it cannot listen; the example checks that before connecting. `LiveSession.Connect` waits for the host’s next snapshot and throws on a timeout or an incompatible module.

## Variations

To keep the sample host running while you open a separate viewer, run this in the download folder:

```shell
dotnet run -- --host
```

It prints an attach command with its actual PID. Run that command in another terminal before the host finishes. The sample runs for 3,600 steps with a 16 ms delay between steps.

For a terminal-only attach, append `--headless --run sources` to the printed command. It returns JSON describing the source, including its live state and the first and newest received steps.

## Related recipes

- [Visualize navigation meshes and crowds](https://recursiveai.org/onescope/cookbook/visualize-navmesh-and-crowds/)
- [Write a custom replay viewer module](https://recursiveai.org/onescope/cookbook/write-a-onescope-module/)

## The short answer

To debug a running OnePhysics game in C#, record the world into a `LiveFeedStream`, forward the feed’s records to `ScopeProbe.Record`, and let the probe obtain snapshots through `ISnapshotSource`. Start the OneScope viewer with `onescope attach` and the host’s PID. The viewer reconstructs the simulation locally from the snapshot and the following replay records.

The distinction matters: attaching does not stop a managed thread at a breakpoint or expose arbitrary objects from the game’s heap. It opens the physics state that the module knows how to reconstruct. Your host must cooperate by recording and servicing the probe. An uninstrumented process has no stream for OneScope to consume.

## How do I debug running game physics live in C#?

Keep the probe and recording alive for the duration of the simulation. In the full program, `StartRecording(feed)` supplies the library’s replay bytes, `feed.Record = probe.Record` connects that stream to the probe, and `Snapshots.WriteSnapshot` supplies the initial world state. The viewer loads the OnePhysics module to interpret those bytes. The probe itself does not interpret the replay.

Call `probe.Poll()` from the simulation thread at step boundaries, including when no new records are being emitted. A new viewer needs the host to take its snapshot there. Connecting from a worker does not remove that requirement: a host that never polls can leave the connection waiting until it times out. The sample services the pending connection before advancing, which makes its initial step reproducible.

The viewer’s first available step is the host’s step at the snapshot. It cannot rewind into the part of the game that happened before attachment. Once records arrive, you can seek among the steps already received. The viewer reports the library’s own world hash, so compare hashes at the **same step**, not at the same wall-clock time. The host may already be further ahead when you read the result.

These behaviors are described in [live attach](https://recursiveai.org/onescope/docs/live-attach/). The sample uses a separate connection task only to demonstrate both sides inside one runnable console project; a game and the viewer normally live in separate processes.

## How can I debug a live simulation without stopping the game?

Inspect the viewer’s reconstructed world rather than pausing the host. Following a live source moves the viewer cursor to the newest received step. Seeking away from the newest step turns following off; seeking back to it or using the `follow` operation turns it on. This lets you inspect an earlier contact while the host continues to produce records.

A slow viewer does not turn the stream into an unlimited buffer. The host can drop a viewer that falls behind. The viewer also has a record-byte cap; reaching it closes the connection and retains what was received. If the host exits or closes the probe, the received history remains usable. Reattaching starts a new session from a fresh snapshot, not from the old connection’s last step.

Use a fork when you want to experiment with an impulse or another declared physics change. Pokes apply to forks, not to the running host. See the [shell operation reference](https://recursiveai.org/onescope/docs/shell/) for live-state fields and the follow, fork, and poke operations.
