---
title: "Getting started with OneScope"
url: https://recursiveai.org/onescope/docs/getting-started/
description: "Install the onescope tool, record a short OnePhysics replay, and open it headless: seek to a step, inspect the world and one body, and capture a frame."
version: "1.0.0"
---

# Getting started with OneScope

Install the onescope tool, record a short OnePhysics replay, and open it headless: seek to a step, inspect the world and one body, and capture a frame.

## Install

OneScope runs on .NET 10, so you need the .NET 10 SDK or runtime. This guide uses the 1.0.0 tool.

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

In the README’s words: “The shell is a onegame program: it opens a window on Linux, macOS and Windows, or runs headless and renders offscreen.” The physics and navigation modules are bundled, so OnePhysics and OnePath files open with no extra options.

## Record a replay

Any OnePhysics or OnePath replay will do. This one comes from a short OnePhysics program: five balls dropped on the ground, recorded for 240 steps. The output below is from a run in `/tmp/Bounce`.

```shell
dotnet new console -o Bounce
cd Bounce
dotnet add package RecursiveAI.OnePhysics --version 1.0.0
```

```csharp
using RecursiveAI.OneMath;
using RecursiveAI.OnePhysics;
using RecursiveAI.OnePhysics.Physics2D;

var world = new PhysicsWorld2D();
world.AddBox(new Vector2F(20, 1), at: new Vector2D(0, -1), type: BodyType.Static);

using (var file = File.Create("run.oppr"))
using (world.StartRecording(file))
{
    for (int i = 0; i < 5; i++)
        world.AddCircle(0.5f, at: new Vector2D(i - 2, 4 + i));
    for (int step = 1; step <= 240; step++)
    {
        world.Step(1f / 60);
        if (step == 120) Console.WriteLine($"step 120, hash {world.ComputeHash():x16}");
    }
}
Console.WriteLine($"recorded {world.StepCount} steps to run.oppr");
```

```text
step 120, hash 96f47843cd094b33
recorded 240 steps to run.oppr
```

## Seek, inspect, capture

In the README’s words: “run operations after opening, print each result as JSON, and exit.” Each `--run` is one operation, and each result is one line. `--headless` opens no window and renders offscreen.

```shell
onescope open run.oppr --headless --run 'seek {"step":120}' --run 'inspect {"entity":1}' --run 'capture_frame {"path":"frame.png"}'
```

```text
{"source":0,"name":"run.oppr","path":"/tmp/Bounce/run.oppr","module":"onephysics","dimensions":2,"unit":{"name":"m","metres":1},"startStep":0,"steps":240,"axes":[{"id":"time","name":"Time","length":240,"position":0}],"overlays":[{"id":0,"name":"Shapes","defaultOn":true},{"id":1,"name":"Bounds","defaultOn":false},{"id":2,"name":"Contacts","defaultOn":true},{"id":3,"name":"Joints","defaultOn":true},{"id":4,"name":"Centres of mass","defaultOn":false},{"id":5,"name":"Joint limits","defaultOn":true}],"tracks":[{"id":"contact","name":"Contacts begun","kind":"event"},{"id":"sleep","name":"Sleep and wake","kind":"event"},{"id":"joint_break","name":"Joints broken","kind":"event"},{"id":"awake","name":"Awake bodies","kind":"series"},{"id":"contacts","name":"Contact points","kind":"series"}],"pokes":[{"id":"impulse","name":"Impulse","role":"drag","params":[{"name":"target","kind":"entity"},{"name":"impulse","kind":"vector"}]},{"id":"set_velocity","name":"Set velocity","params":[{"name":"target","kind":"entity"},{"name":"velocity","kind":"vector"}]},{"id":"set_position","name":"Set position","role":"drag","params":[{"name":"target","kind":"entity"},{"name":"position","kind":"vector"}]},{"id":"wake","name":"Wake","params":[{"name":"target","kind":"entity"}]}],"label":"run.oppr","step":0,"hash":"5a09a642111ebae2","keyframeInterval":64,"scannedThrough":240,"views":2}
{"step":120,"start":0,"steps":240,"sources":[{"source":0,"step":120,"hash":"96f47843cd094b33"}]}
{"source":0,"step":120,"entity":1,"kind":"world","name":"World","properties":[{"group":"World","name":"Step","kind":"integer","value":120},{"group":"World","name":"Completed steps","kind":"integer","value":120},{"group":"World","name":"Time step","kind":"number","value":0.01666666753590107},{"group":"World","name":"Hash","kind":"text","value":"96f47843cd094b33"},{"group":"Counters","name":"Awake bodies","kind":"integer","value":5},{"group":"Counters","name":"Awake islands","kind":"integer","value":1},{"group":"Counters","name":"Asleep islands","kind":"integer","value":0},{"group":"Counters","name":"Broadphase pairs","kind":"integer","value":0},{"group":"Counters","name":"Manifolds","kind":"integer","value":5},{"group":"Counters","name":"Contact points","kind":"integer","value":5},{"group":"Counters","name":"Joints","kind":"integer","value":0},{"group":"Counters","name":"Sleep transitions","kind":"integer","value":0},{"group":"Counters","name":"Wake transitions","kind":"integer","value":0}]}
{"path":"/tmp/Bounce/artifacts/frame.png","width":1280,"height":720,"scale":1,"sha256":"2a687c60112c6d36122c9ad2746427dffefeb295bd34802f57d7be398a100d87","renderer":"NVIDIA GeForce RTX 4070","target":"shell","step":120,"hashes":["96f47843cd094b33"]}
```

The hash at step 120, `96f47843cd094b33`, is the one the program printed from `ComputeHash()`: the README says “the world hash it reports is the library’s own.” The frame is the whole shell, drawn offscreen at 1280×720:

![The onescope shell at step 120 of 240: the entity list, a 2D and a 3D view of five balls resting on the ground, the world inspector, the timeline with contact events and the event log](https://recursiveai.org/onescope/frame-step-120.webp)

To inspect a body, list the entities first. Entity 1 is the world; the bodies have their own ids. The first line of this run, the `open` result, is the same as above and left out here.

```shell
onescope open run.oppr --headless --run 'seek {"step":120}' --run 'entities {"limit":3}' --run 'inspect {"entity":72057598332895233}'
```

```text
{"source":0,"step":120,"entities":[{"id":1,"parent":null,"kind":"world","name":"World"},{"id":72057598332895232,"parent":1,"kind":"body","name":"Body 0 (Static)"},{"id":72057598332895233,"parent":1,"kind":"body","name":"Body 1 (Awake)"}],"count":7,"truncated":true}
{"source":0,"step":120,"entity":72057598332895233,"kind":"body","name":"Body 1 (Awake)","properties":[{"group":"Transform","name":"Position","kind":"vector","value":[-2.2531161,-0.00011538295,0]},{"group":"Transform","name":"Angle","kind":"number","value":0.5006986856460571},{"group":"Motion","name":"Linear velocity","kind":"vector","value":[-0.2670705,0,0]},{"group":"Motion","name":"Angular velocity","kind":"number","value":0.5342166423797607},{"group":"Body","name":"Type","kind":"text","value":"Dynamic"},{"group":"Body","name":"Mass","kind":"number","value":0.7853981852531433},{"group":"Body","name":"Awake","kind":"bool","value":true},{"group":"Solver","name":"Island","kind":"integer","value":0},{"group":"Solver","name":"Sleep time","kind":"number","value":0},{"group":"Solver","name":"Constraints","kind":"integer","value":1},{"group":"Solver","name":"Constraint colours","kind":"integer","value":1048576},{"group":"Solver","name":"External impulse","kind":"vector","value":[0,0,0]}]}
```

## What each part does

- **`open`** loads the file with the module that owns it and prints what the source declares: its time axis, overlays, timeline tracks and the pokes a fork accepts.
- **`seek`** moves to a step. Time is a step index; in the README’s words, “Seeking to step *k* lands on exactly the state that playing straight to *k* gives, however you got there.”
- **`entities`** and **`inspect`** read the world at the current step: the entity tree, then one entity’s properties, grouped as the inspector shows them.
- **`capture_frame`** writes a PNG under `./artifacts` (`--artifacts <dir>` moves it) and reports its SHA-256, the renderer and the step. Two runs of this guide wrote byte-identical frames; the changelog says headless captures “are byte-identical from run to run on one renderer.”
- The replay file is only read. The README: “The original file and the running program are never changed.”

## Drive it over the Model Context Protocol

A shell opened with `--live` serves the same operations on a Unix socket, and other `onescope` commands drive it:

```shell
onescope open run.oppr --headless --live /tmp/onescope.sock &
onescope seek '{"step":120}' --socket /tmp/onescope.sock
```

```text
{"step":120,"start":0,"steps":240,"sources":[{"source":0,"step":120,"hash":"96f47843cd094b33"}]}
```

`onescope mcp --socket <socket>` bridges that socket to stdio for an agent. The README’s client configuration:

```json
{ "mcpServers": { "onescope": { "command": "onescope", "args": ["mcp", "--socket", "/tmp/onescope.sock"] } } }
```

## Next

- [The shell and its operations](https://recursiveai.org/onescope/docs/shell/): every operation, its arguments and its JSON result
- [Time, history and forks](https://recursiveai.org/onescope/docs/history/)
- [Live attach](https://recursiveai.org/onescope/docs/live-attach/): watch a running program that calls the probe
- [Writing a module](https://recursiveai.org/onescope/docs/module-authoring/): add your own library
