Recursive AI

Getting started with OnePhysics

All onephysics docs

Install the OnePhysics preview for .NET 10, drop a ball on the ground, walk a character, cast a ray, and take a snapshot and a world hash, with the same world state, bit for bit, on every platform.

Install

OnePhysics targets net10.0, so you need the .NET 10 SDK. It is a preview: this guide uses 0.4.0-preview.1, and the API and results may change between previews.

Terminal
dotnet new console -o OnePhysicsStart
cd OnePhysicsStart
dotnet add package RecursiveAI.OnePhysics --version 0.4.0-preview.1

Its only dependency is OneMath, installed with it, for vectors and deterministic math. It works under NativeAOT, trimming and browser WebAssembly.

The 2D engine is the supported surface. The 3D engine is in the same package as a preview of its own, marked [Experimental("OPHYS0002")]: see the 3D preview.

A first world

C#
using RecursiveAI.OneMath;               // Vector2F, Vector2D
using RecursiveAI.OnePhysics;            // BodyType, events, exceptions
using RecursiveAI.OnePhysics.Physics2D;  // PhysicsWorld2D, Body, Character
 
var world = new PhysicsWorld2D();
world.AddBox(new Vector2F(20, 1), at: new Vector2D(0, -1), type: BodyType.Static);
var ball = world.AddCircle(0.5f, at: new Vector2D(0, 5));
ball.OnCollisionEnter += c => Console.WriteLine($"hit {c.Other} along {c.Normal}");
 
var player = world.AddCharacter(at: new Vector2D(3, 1));
for (int i = 0; i < 600; i++)
{
    player.Move(new Vector2F(0.01f, -0.1f), 1f / 60);
    world.Step(1f / 60);                        // fixed dt; events fire after the step, in a deterministic order
}
Console.WriteLine($"ball at {ball.Position}, player at {player.Position}");
if (world.Raycast(new Vector2D(0, 10), new Vector2F(0, -1), out var hit)) Console.WriteLine($"ray hit {hit.Body} at {hit.Point}");
 
byte[] saved = world.Snapshot();                // byte-exact; Restore(saved) continues bit for bit
ulong hash = world.ComputeHash();               // the same on every platform
Console.WriteLine($"snapshot {saved.Length} bytes, hash {hash:X16}");
Text
hit Body(world 1, #0, gen 1) along (-0, -1)
ball at (0, -5.037127994000912E-05), player at (8.99999986588955, 0.3999999836087227)
ray hit Body(world 1, #1, gen 1) at (0, 0.49994962872006)
snapshot 1804 bytes, hash C1C403756B09A31E

What each part does

  • AddBox, AddCircle and the other one-liners create a body with one shape. Sizes are full extents in meters, positions are double and everything else is float. Bodies are dynamic unless you pass BodyType.Static or Kinematic.
  • OnCollisionEnter fires after the step, in a fixed order, so handlers run the same way on every machine. The Body in the message is a small struct handle: world, slot and generation.
  • AddCharacter adds a character mover: Move(displacement, dt) moves it and resolves its contacts. Here it walks 6 m to the right and stands on the ground.
  • Step(dt) advances the world by a fixed time step. Use one dt for the whole session.
  • Snapshot() writes the whole world to bytes; Restore(bytes) continues from there bit for bit. ComputeHash() is a 64-bit hash of the state, the same on every platform.

Determinism

Only IEEE + - * / and square root decide a result, with OneMath’s own transcendental functions, fixed-order reductions and constraint coloring. The world hash after N steps is the same on x64, arm64 and WebAssembly, scalar or SIMD, one thread or all cores. Previews are not bit-compatible with each other: a snapshot or replay from one preview doesn’t carry to the next. See determinism.

Next