A prediction fork lets you simulate movement without changing the original space.
dotnet add package RecursiveAI.OneGame.PhysicsRequires .NET 10 (net10.0); .NET 9 and earlier aren’t supported.
Fixed and FVec3 are onegame’s deterministic fixed-point number and vector types.
using OneGame.Core; using OneGame.Physics; var space = new PhysicsSpace3D(); var body = space.AddSphere( Fixed.One, FVec3.Zero); var predicted = space.Fork(); predicted.SetVelocity(body, new FVec3(60, 0, 0)); predicted.Step(resimulating: true); Console.WriteLine( predicted.Position(body).X > 0); Console.WriteLine(space.Position(body).X);
Prints that the prediction moved, then the original X position.
True 0
Unzip the example, then dotnet run.
The full program
The download disables gravity to isolate one movement input. It saves confirmed state, forks the space, applies a velocity and steps once. It then restores that confirmed snapshot and replays the same input.
using OneGame.Core; using OneGame.Physics; var space = new PhysicsSpace3D(new PhysicsOptions3D { Gravity = FVec3.Zero }); var body = space.AddSphere(Fixed.One, FVec3.Zero); byte[] confirmed = space.Snapshot(); var predicted = space.Fork(); predicted.SetVelocity(body, new FVec3(60, 0, 0)); predicted.Step(resimulating: true); ulong firstHash = predicted.Hash; Console.WriteLine( $"Authority steps: {space.StepCount}"); Console.WriteLine( $"Prediction steps: {predicted.StepCount}"); Console.WriteLine( $"Authority X: {space.Position(body).X}"); Console.WriteLine( "Prediction moved: " + (predicted.Position(body).X > 0)); predicted.Restore(confirmed); predicted.SetVelocity(body, new FVec3(60, 0, 0)); predicted.Step(resimulating: true); Console.WriteLine( "Replay matches: " + (predicted.Hash == firstHash));
dotnet run prints:
Authority steps: 0 Prediction steps: 1 Authority X: 0 Prediction moved: True Replay matches: True
Networked clients don’t get this physics state on their own; your game sends confirmed state and keeps the prediction fork.
Variations
Try a different input after restoring the same confirmed state. Append this to the full program:
predicted.Restore(confirmed); predicted.SetVelocity(body, new FVec3(-60, 0, 0)); predicted.Step(resimulating: true); Console.WriteLine($"Alternative moves left: {predicted.Position(body).X < 0}"); Console.WriteLine($"Authority X: {space.Position(body).X}");
Alternative moves left: True Authority X: 0
Related recipes
- Sphere cast in a 3D physics world in C#
- Query a 3D sphere overlap in C#
- Save and restore a 3D physics snapshot in C#
The short answer
Call PhysicsSpace3D.Fork() to create an independent structural copy. Apply predicted inputs to that copy, then advance it with Step(resimulating: true). When confirmed state arrives, restore it into the prediction space and replay the inputs that still need prediction. The full program demonstrates that cycle with one velocity input and checks that repeating it reproduces the same physics hash.
How does client-side prediction work?
Prediction runs a local version of a simulation before the authoritative result arrives. The useful separation here is between the authoritative space and the independently stepped predicted space. The example prints zero authoritative steps and one prediction step. The body moves in the prediction, but the authoritative body stays at X = 0.
Fork preserves handles and pending events. The sample therefore uses the same BodyRef in both spaces: it identifies the body copied from the original scene. This does not make later changes shared. Setting velocity on the predicted body and advancing the predicted space does not advance the original. Keep your references associated with the scene from which the fork was made rather than treating arbitrary body references from unrelated spaces as interchangeable.
In a game, capture inputs as simulation decisions that can be applied again. This example’s input is a request for velocity along X. It deliberately removes gravity so the printed movement isolates that request. A full game may instead replay commands or character displacements, but the physics fork is still only the physics portion of that game’s state.
Networked clients don’t get this physics state on their own. Your game sends confirmed state to them and keeps the prediction fork. Creating a fork alone does not install client prediction, acknowledge network inputs or synchronize the rest of your entities. The physics guide states that ownership explicitly.
How do client-side prediction and server reconciliation work together?
Reconciliation starts from a confirmed state, not from whatever state the prediction currently has. The example keeps confirmed, a full snapshot taken before the input. After the first predicted step, it restores those bytes, reapplies the velocity and steps again. The resulting hash matches the first prediction because the example repeats the same starting state and operation.
In a networked game, use the authority’s confirmed tick to decide which later inputs remain to be replayed, as described in Gabriel Gambetta’s explanation of server reconciliation. Do not blindly replay an input the confirmed state already includes. This recipe does not define a network message format or input acknowledgment scheme; it demonstrates the space operations needed once your game has made that decision.
Step(resimulating: true) suppresses callbacks but retains polled events. It does not erase the event stream. Read replayed events according to your simulation’s rules, and avoid repeating presentation effects merely because the same tick was simulated again. The distinction matters when an effect should appear once while the underlying physics must be recomputed.
Use the same physics settings and input order for the replay. The space exposes a fixed tick length, and a standalone space advances only when you call Step. A space registered with AddTo is already stepped after entities by its world system; do not add an extra ordinary step to that same authoritative tick.