A pose cache adds recorded positions and rotations as reference outlines; write a pose cache in C# to create your own. OneScope debugs recorded simulation steps, such as physics; a fork is a branched copy of a recording.
dotnet tool install --global RecursiveAI.OneScope.ToolRequires .NET 10 (net10.0); .NET 9 and earlier aren’t supported.
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.
# A ghost is a read-only pose reference. onescope open run.oppr --headless \ --run 'open {"path":"ref.ospc"}' \ --run 'ghost {"source":1,"on":true}'
The replay is source 0, the cache is source 1. Both input files are in the download.
Unzip the example, then dotnet run.
The final operation returns:
{ "ghosts": [ { "source": 1, "name": "ref.ospc", "step": 0, "startStep": 0, "endStep": 0, "views": [ 0, 1 ] } ] }
The full program
run.oppr is included: a generated 2D OnePhysics recording, not a capture from a user’s game. It has a ball of radius 0.5 meters starting at (0, 4), a static ground box at (0, −1), and 120 recorded steps. The ground is 20 × 1 meters. The example does not need a running game.
ref.ospc is a generated, single-frame 2D cache with one sphere named “Ball”: id 2, radius 0.5 meters, position (0, 4, 0), identity rotation, at step 0. It names no originating module, so any source with matching dimensions can receive its overlay. This reference is a visual marker, not a recording from another physics engine. Its stored pose is held at later steps.
The C# launcher installs the published tool into obj, opens both files, enables the overlay, seeks to step 30, and disables it.
using System.Diagnostics; using System.Text.Json; string toolDir = Path.GetFullPath("obj/onescope-tool"); string tool = Path.Combine(toolDir, OperatingSystem.IsWindows() ? "onescope.exe" : "onescope"); if (!File.Exists(tool)) Run("dotnet", "tool", "install", "RecursiveAI.OneScope.Tool", "--version", "1.0.0", "--tool-path", toolDir); var results = Open("run.oppr", "open {\"path\":\"ref.ospc\"}", "ghost {\"source\":1,\"on\":true}", "seek {\"step\":30}", "ghost {\"source\":1,\"on\":false}"); Console.WriteLine("Attached: " + results[2]); Console.WriteLine("Detached: " + results[^1]); string Run(string command, params string[] arguments) { var start = new ProcessStartInfo(command) { RedirectStandardOutput = true, RedirectStandardError = true }; foreach (string argument in arguments) start.ArgumentList.Add(argument); using var process = Process.Start(start)!; var output = process.StandardOutput.ReadToEndAsync(); var errors = process.StandardError.ReadToEndAsync(); process.WaitForExit(); if (process.ExitCode != 0) throw new InvalidOperationException(output.Result + errors.Result); return output.Result; } JsonElement[] Open(string file, params string[] operations) { var arguments = new List<string> { "open", file, "--headless", "--size", "640x480", "--artifacts", "output" }; foreach (string operation in operations) arguments.AddRange(["--run", operation]); return Run(tool, arguments.ToArray()) .Split('\n', StringSplitOptions.RemoveEmptyEntries) .Select(line => JsonDocument.Parse(line).RootElement.Clone()).ToArray(); }
dotnet run prints:
Attached: {"ghosts":[{"source":1,"name":"ref.ospc","step":0,"startStep":0,"endStep":0,"views":[0,1]}]} Detached: {"ghosts":[{"source":1,"name":"ref.ospc","step":0,"startStep":0,"endStep":0,"views":[]}]}
A pose-cache source is read-only: fork fails with ghost_source, poke with read_only, and diff and diff_shared refuse it.
Related recipes
- Save and reopen a debugging session
- Compare library versions
- Compare shared entities
- Write a reference pose cache
The short answer
Open the simulation replay first, then open the .ospc pose cache. OneScope recognizes the cache as a ghost source. When an eligible source is already open, the ghost is drawn over its views as translucent outlines rather than getting its own views. Use ghost with source and on to attach or detach the overlay.
The cache is not another simulator. It contains recorded transforms for a fixed set of entities and has no behavior to re-simulate. This makes it suitable for looking at reference positions beside a real source, but not for applying impulses or comparing simulated internal state. The sample’s stationary sphere makes that distinction visible: seeking the replay changes the simulation while the single reference pose stays fixed.
How do I overlay reference simulation poses on a replay?
Check the cache’s dimensionality and originating module before opening it. If the cache names a module, its automatic overlay target must be an existing source of that module. If it names none, any source with the same dimensions qualifies. A 2D reference does not automatically become a 3D overlay. Without a matching source, the cache receives views like an ordinary source instead.
Opening over an eligible source attaches the outlines to every view of that source. The ghost result lists the view numbers receiving the overlay; in this example they are 0 and 1. Pass view to the operation if you want to attach it to a particular view. Use the returned view and source ids rather than assuming another session has the same numbering.
The ghost scrubs on the shared time axis. Before its recorded range it holds the first pose, and after the range it holds the last pose. A held endpoint does not mean the reference engine simulated that later state. Check startStep and endStep when interpreting a visible mismatch near a recording boundary.
Does a reference overlay rescale units or compare state?
No. Positions are drawn exactly as stored. If the reference uses millimeters and the replay uses meters, matching the dimensions alone will not align them. Convert positions and shape sizes before writing the cache, and set its unit metadata consistently. There is no automatic unit rescaling when the ghost is drawn over another source.
A visual overlap is not an equality result. Ghosts have no pokes or tracks and cannot participate in diff or diff_shared. They carry poses rather than the inspector state and simulation inputs those comparisons need. Use a real module-backed source when you need shared-entity or version comparisons.
A cache may also record an originating replay name. When that is a plain filename existing beside the cache, the shell can offer open_real to open it with the named module and move it to the ghost’s cursor. A path containing directories does not enable that offer. The example intentionally leaves this field empty; its replay is opened explicitly. See ghost source behavior for the matching and replay-offer rules.