---
title: "Write a pose cache in C#"
url: https://recursiveai.org/onescope/cookbook/write-reference-pose-cache/
description: "Create a OneScope pose cache from positions and rotations in C#, write an .ospc file, and open the recorded poses as a read-only ghost source."
version: "1.0.0"
---

# Write a pose cache in C#

**OneScope** is a free, open-source viewer for simulation recordings, with a desktop window or command-line control, from Recursive AI. MIT or Apache-2.0.

[NuGet](https://www.nuget.org/packages/RecursiveAI.OneScope.Sdk) · [GitHub](https://github.com/recursive-ai-oss/onescope-csharp) · [Docs](https://recursiveai.org/onescope/docs/) · [Download example (.zip)](https://recursiveai.org/onescope/cookbook/write-reference-pose-cache/write-reference-pose-cache.zip)

A pose cache records positions and rotations without simulation state. OneScope debugs recorded simulation steps, such as physics; a fork is a branched copy of a recording.

```shell
dotnet add package RecursiveAI.OneScope.Sdk
```

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

```csharp
using System.Buffers;
using OneScope.Sdk;
var bytes = new ArrayBufferWriter<byte>();
PoseCache.Create(
    info: new(Dimensions: 2, Unit: LengthUnit.Metre,
        StartStep: 0, ModuleId: "", LibraryVersion: "",
        Scene: "", Replay: "", Label: ""),
    entities: [new(Id: new(2), Name: "",
        Shape: PoseShape.Point, Size: default)],
    // Position x,y,z; rotation x,y,z,w.
    poses: [0, 4, 0, 0, 0, 0, 1]).Write(bytes);
File.WriteAllBytes("reference.ospc",
    bytes.WrittenSpan.ToArray());
```

Writes `reference.ospc`; there is no console output. `Dimensions: 2` means 2D, and `new(2)` is entity id 2. Keep position Z at 0 for 2D drawing, as specified by the [module drawing contract](https://recursiveai.org/onescope/docs/module-authoring/); the file still stores all three position components.

Unzip [the example](https://recursiveai.org/onescope/cookbook/write-reference-pose-cache/write-reference-pose-cache.zip), then `dotnet run`.

## The full program

The snippet at the top writes a point at (0, 4, 0) with identity rotation. This full program gives it a sphere shape of radius 0.5 meters and the name “Ball”, and sets the label to “Reference”. Both use entity id 2 in 2D at step 0; all origin strings are empty. Neither needs an input file.

```csharp
using System.Buffers;
using System.Numerics;
using OneScope.Sdk;

var cache = PoseCache.Create(
    info: new(Dimensions: 2, Unit: LengthUnit.Metre,
        StartStep: 0, ModuleId: "", LibraryVersion: "",
        Scene: "", Replay: "", Label: "Reference"),
    entities: [new(Id: new(2), Name: "Ball",
        Shape: PoseShape.Sphere, Size: new Vector3(0.5f, 0, 0))],
    // Position x,y,z; rotation x,y,z,w. Z is 0 for 2D.
    poses: [0, 4, 0, 0, 0, 0, 1]);
var bytes = new ArrayBufferWriter<byte>();
cache.Write(bytes);
File.WriteAllBytes("reference.ospc",
    bytes.WrittenSpan.ToArray());
Console.WriteLine("Wrote reference.ospc");
```

`dotnet run` prints:

```text
Wrote reference.ospc
```

Invalid cache parts passed to `Create` raise `ArgumentException`; malformed file bytes passed to `Parse` raise `ScopeFormatException` naming the input and expected format.

## Variations

**Open the generated file as a ghost (a read-only pose reference).** Install the viewer tool, then open the file:

The examples use `--headless`, which renders offscreen but still needs a graphics device and driver. On Linux, a Vulkan software renderer such as lavapipe can supply it; `ONEGAME_GPU_ADAPTER=software` selects software rendering. Remove `--headless` to open the window.

```shell
dotnet tool install --global RecursiveAI.OneScope.Tool
onescope open reference.ospc --headless \
  --run 'ghost'
```

The `ghost` result is:

```json
{
  "ghosts": [
    {
      "source": 0,
      "name": "reference.ospc",
      "step": 0,
      "startStep": 0,
      "endStep": 0,
      "views": []
    }
  ]
}
```

There are no overlay views because no compatible simulation source is open. The cache has its own views. To overlay it, [open a replay first](https://recursiveai.org/onescope/cookbook/overlay-reference-pose-cache/).

**Record an absent entity.** This complete replacement program writes two frames starting at step 10; the sphere is absent in the second frame:

```csharp
using System.Buffers;
using System.Numerics;
using OneScope.Sdk;

var cache = PoseCache.Create(
    info: new(Dimensions: 2, Unit: LengthUnit.Metre,
        StartStep: 10, ModuleId: "", LibraryVersion: "",
        Scene: "", Replay: "", Label: "Brief clip"),
    entities: [new(Id: new(2), Name: "Ball",
        Shape: PoseShape.Sphere, Size: new Vector3(0.5f, 0, 0))],
    // Each frame: position x,y,z; rotation x,y,z,w.
    poses: [0, 4, 0, 0, 0, 0, 1,
     float.NaN, 0, 0, 0, 0, 0, 0]);
var bytes = new ArrayBufferWriter<byte>();
cache.Write(bytes);
File.WriteAllBytes("brief.ospc", bytes.WrittenSpan.ToArray());
Console.WriteLine($"Frames: {cache.FrameCount}");
Console.WriteLine($"Present at frame 1: {cache.TryGetPose(1, 0, out _, out _)}");
```

```text
Frames: 2
Present at frame 1: False
```

## Related recipes

- [Save and reopen a debugging session](https://recursiveai.org/onescope/cookbook/save-and-reopen-debug-session/)
- [Compare library versions](https://recursiveai.org/onescope/cookbook/compare-library-versions/)
- [Compare shared entities](https://recursiveai.org/onescope/cookbook/compare-shared-entities/)
- [Overlay a reference pose cache](https://recursiveai.org/onescope/cookbook/overlay-reference-pose-cache/)

## The short answer

Use `OneScope.Sdk.PoseCache.Create` with cache metadata, a fixed entity list, and a flat array of pose floats. Call `Write` with an `IBufferWriter<byte>`, then save those bytes. OneScope opens the resulting `.ospc` file as a ghost: a read-only sequence of poses that can be scrubbed or drawn over a compatible simulation source.

This is a pose export, not a full simulation replay. It stores where things are and how they are rotated, not the inputs or internal world state needed to simulate them again. Use it for known positions from a reference implementation or a baked clip. The example uses literal data so the output is reproducible without another engine or a recording service.

## How do I write a simulation pose cache in C#?

Define entities once, with unique nonzero ids, display names, shapes and sizes. The same entity list applies to every frame. For a sphere, the size’s X component is the radius. For a box, the vector contains half extents; for a capsule, X is radius and Y is half the segment length along local Y. A point does not require a solid shape.

Supply seven floats per entity per frame: position X, Y, Z, then rotation X, Y, Z, W. Place every entity’s pose for one frame before the next frame’s poses. Frame zero belongs to `StartStep`; each following frame advances one step. The example writes one frame beginning at zero. For several entities, accidentally grouping all frames of entity A before all frames of entity B would change which transforms are read together.

The metadata declares 2D or 3D coordinates and a length unit. Store positions and sizes in that unit. The viewer does not rescale a ghost to match another source, so normalize the exported data yourself when the reference and target simulations use different units. Empty origin strings are allowed; they do not prevent loading the cache.

## How do I represent missing entities and check cache limits?

Keep the fixed entity entry, but put NaN in position X for a frame where it is absent. Rotation is then ignored. For a present entity, values must be finite; a zero rotation reads as the identity. The second program demonstrates absence and checks it with `TryGetPose`, which returns false for that entity and frame.

The format permits 1 to 65,536 entities and 1 to 4,194,304 frames, but the file limit is 256 MiB. Each entity contributes 28 pose bytes per frame, before metadata and entity definitions. Those count limits do not mean their maximum product fits into one file. Text fields are limited to 1,024 UTF-8 bytes. See the [pose-cache format](https://recursiveai.org/onescope/docs/pose-cache/) for the exact field order and limits.

The reader checks lengths against the remaining data before allocation. A file must end after exactly the declared poses; truncation or extra pose bytes are not silently accepted. `Parse` provides format errors with the input name and offset. When opening the cache in the viewer, remember that a successful parse establishes a readable pose file, not equivalence with a simulation: ghosts cannot be forked, poked, or used in state diffs.
