PurrDiction

State Handling

STATE is the single source of truth for your simulation. PurrDiction records, reconciles, and interpolates states so clients stay responsive while remaining consistent with server authority.


Requirements

  • STATE must be a struct implementing IPredictedData<STATE> (which extends IMath<STATE> and IPackedAuto).
  • Provide deterministic math on your state via IMath<T> methods (the default linear interpolation uses Add, Scale, Negate).

What Goes in STATE

  • Simulationโ€‘critical data only: positions, rotations, velocities, timers, flags; anything that affects future ticks.
  • Do not store transient view data; compute those in UpdateView.

Lifecycle and Reconciliation

  • Simulation loop writes currentState each tick.
  • PurrDiction saves snapshots and reconciles against server frames as they arrive.
  • On rollback:
    • ReadState loads the verified snapshot.
    • Rollback(tick) applies it and calls SetUnityState(state) for external components.
    • UpdateRollbackInterpolationState(delta, accumulateError) adjusts view smoothing.

Unity bridging:

  • protected override void GetUnityState(ref STATE state) โ€” Read Unity โ†’ STATE when needed.
  • protected override void SetUnityState(STATE state) โ€” Write STATE โ†’ Unity on rollback.

View vs Verified

  • viewState is the smoothed state to render this frame.
  • verifiedState exposes the most recent authoritative snapshot, when present.
  • Override UpdateView(STATE viewState, STATE? verified) to drive visuals.
  • Call ResetInterpolation() to clear accumulated error (e.g., teleports).

Example: Minimal STATE

public struct MyState : IPredictedData<MyState>
{
    public Vector3 pos; public Quaternion rot;
    public void Dispose() {}
    // IMath<MyState> default ops come from packer codegen; override Interpolate if non-linear
}

public class Mover : PredictedIdentity<MyState>
{
    protected override MyState GetInitialState() => new MyState { pos = transform.position, rot = transform.rotation };
    protected override void GetUnityState(ref MyState s) { s.pos = transform.position; s.rot = transform.rotation; }
    protected override void SetUnityState(MyState s) { transform.SetPositionAndRotation(s.pos, s.rot); }
    protected override void Simulate(ref MyState s, float dt) { /* mutate s deterministically */ }
    protected override void UpdateView(MyState view, MyState? verified) { transform.SetPositionAndRotation(view.pos, view.rot); }
}