PurrDiction

Predicted Identities

Predicted identities are regular Unity components that PurrDiction simulates deterministically. The system snapshots and reconciles their STATE against server authority, while your code focuses on clean, singleโ€‘playerโ€‘style logic.

Variants

  • PredictedIdentity<STATE>
    • For stateful systems without direct player input (AI, timers, pooled objects).
    • Implement: GetInitialState, GetUnityState, SetUnityState, Simulate, LateSimulate (optional), UpdateView (optional).
  • PredictedIdentity<INPUT, STATE>
    • Adds an INPUT pipe for local/remote control. Implement: GetFinalInput, UpdateInput (optional), SanitizeInput, Simulate(INPUT, ref STATE, float), ModifyExtrapolatedInput (optional).
  • StatelessPredictedIdentity
    • For pure event/logic systems that donโ€™t carry custom state. Override Simulate(float delta).

Both INPUT and STATE must be structs that implement the appropriate prediction interfaces (IPredictedData, IPredictedData<T>).


Lifecycle Hooks

  • LateAwake() โ€” Called once after fresh spawn initialization. Viewโ€‘only setup is appropriate here.
  • SimulationStart() โ€” First tick only, before simulation begins. Good for caching or oneโ€‘time transitions.
  • Simulate(...) โ€” Deterministic simulation each tick. Use only STATE/INPUT and deterministic data.
  • LateSimulate(...) โ€” Optional late pass after Simulate each tick (e.g., composing derived values).
  • Destroyed() โ€” Called when despawning/cleanup occurs (pool or destroy).
  • ResetState() โ€” Clears ownership/IDs and resets interpolation and internal caches for pooling.

Unity bridging:

  • GetUnityState(ref STATE state) โ€” Read Unity components into STATE.
  • SetUnityState(STATE state) โ€” Apply STATE back to Unity components after rollback.

View & interpolation:

  • UpdateView(STATE viewState, STATE? verified) โ€” Render using the current interpolated viewState. verified holds the last server snapshot, when present.
  • ResetInterpolation() โ€” Clear internal smoothing/error.

Ownership and Control

  • owner โ€” Optional PlayerID who owns this identity.
  • isOwner โ€” True if owner == PredictionManager.localPlayer on this client.
  • isController โ€” True for the owner on clients; on server, also true for bots/AI cases.
  • OnViewOwnerChanged(oldOwner, newOwner) โ€” Viewโ€‘only callback when ownership changes; do not mutate simulation here.

Use these flags for visuals (camera, highlights, UI). Keep simulation deterministic and independent of local presentation.


Example Skeleton (Stateful with Input)

public struct MyInput : IPredictedData {
    public Vector2? input; public void Dispose() {}
}

public struct MyState : IPredictedData<MyState> {
    public Vector3 pos; public Quaternion rot; public void Dispose() {}
}

public class MyPredicted : PredictedIdentity<MyInput, 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 GetFinalInput(ref MyInput i)
    { i.input = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical")); }

    protected override void SanitizeInput(ref MyInput i)
    { 
        if (!i.input.HasValue) return;
        var v = Vector2.ClampMagnitude(new Vector2(i.input.Value.x, i.input.Value.y), 1f); 
        i.input = v; 
    }

    protected override void Simulate(MyInput i, ref MyState s, float dt)
    {
        if (!i.input.HasValue) return;
        transform.position += new Vector3(i.input.Value.x, 0, i.input.Value.y) * dt * 5f;
    }

    protected override void UpdateView(MyState view, MyState? verified)
    { transform.SetPositionAndRotation(view.pos, view.rot); }
}