> For the complete documentation index, see [llms.txt](https://kinematicsoup.gitbook.io/reactor/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://kinematicsoup.gitbook.io/reactor/examples/validators.md).

# Validating Client Transform and Property Updates

## Summary

Shows how to use validators to do server-side validation of transform and property updates from clients for their owned entities. Server-side validators are used to prevent clients from cheating. An example script shows how to register a transform validator to restrict what transform values the client can set, and a property validor to restrict which properties ids, types, and value the client can set. A second example shows how to use a transform validator to limit the player's movement speed, and send corrective position deltas to the client in and RPC for the client to correct its position.

## Description

Validators can be used to validate the transform and property updates that players send for entities they own. See [Tutorial 10](/reactor/tutorials/authoritative_client.md) for more information on entity ownership.

The following script shows how to use register a transform validator and a property validator. The transform validator prevents the client from setting the y-position below zero and the scale from being modified. It checks if the update will modify the scale by checking `transform.IsDirty(ksReadOnlyTransformState.DirtyFlag.SCALE)`. You can check for position or rotation changes by changing the `SCALE` flag to `POSITION` or `ROTATION`.

The property validator prevents the client from setting properties outside a specific id range and validates the property type. Property validators are called once for each property. When you need to validate multiple properties together, it is best to do that in an Update handler when the properties have changed. This script sets a bool when property 100 or 101 change, and in an update handler if the bool is set, checks if the difference between property 100 and 101 is greater than 10 and if it is, changes property 101 to be 10 away from property 100.

```c#
using KS.Reactor.Server;
using KS.Reactor;

public class seValidator : ksServerEntityScript
{
    private bool m_checkProperties;

    // Called after all other scripts on all entities are attached.
    public override void Initialize()
    {
        Room.OnUpdate[0] += Update;
        Entity.OnValidateOwnerTransform += ValidateTransform;
        Entity.OnValidateOwnerProperty += ValidateProperty;
    }

    // Called when the script is detached.
    public override void Detached()
    {
        Room.OnUpdate[0] -= Update;
        Entity.OnValidateOwnerTransform -= ValidateTransform;
        Entity.OnValidateOwnerProperty -= ValidateProperty;
    }

    private void Update()
    {
        if (m_checkProperties)
        {
            m_checkProperties = false;

            // If the difference between property 100 and property 101 is greater than 10, set property 100 to be 10
            // away from property 100.
            float prop100 = Properties[100];
            float prop101 = Properties[101];
            if (ksMath.Abs(prop101 - prop100) > 10f)
            {
                Properties[101] = prop100 + (prop101 > prop100 ? 10 : -10);
            }
        }
    }

    private ksValidationResult ValidateTransform(ksReadOnlyTransformState transform, float deltaTime)
    {
        // Don't allow the y-position to go below zero, and don't allow the scale to be modified.
        if (transform.Position.Y >= 0f || transform.IsDirty(ksReadOnlyTransformState.DirtyFlag.SCALE))
        {
            return ksValidationResult.PASS;
        }
        // If the y-position is below zero, apply the transform changes manually with the y-position set to zero, and
        // fail validation.
        Transform.Position = new ksVector3(transform.Position.X, 0f, transform.Position.Y);
        Transform.Rotation = transform.Rotation;
        return ksValidationResult.FAIL;
    }

    private ksValidationResult ValidateProperty(uint propertyId, ksMultiType value, float deltaTime)
    {
        // Don't allow the owner the set properties outside the range 100-200.
        if (propertyId < 100 || propertyId >= 200)
        {
            return ksValidationResult.FAIL;
        }
        // Properties 100 and 101 must be floats, and property 100 must be within the range -100 to 100.
        if (propertyId == 100 || propertyId == 101)
        {
            if (value.Type != ksMultiType.Types.FLOAT || (propertyId == 100 && (value < -100f || value > 100f)))
            {
                return ksValidationResult.FAIL;
            }
            // When property 100 or 101 changes, set a bool to do additional validation in the next Update.
            m_checkProperties = true;
        }
        return ksValidationResult.PASS;
    }
}
```

Returning `ksValidationResult.FAIL` or `ksValidationResult.ABORT` from a validator will prevent the value from being applied. You can then manually set the transform or property to somtehing else from within the validator. If there are multiple validator functions registered, the value will not be applied if any return `ksValidationResult.FAIL` or `ksValidationResult.ABORT`. `ksValidationResult.ABORT` prevents further validator functions from being called, whereas `ksValidationResult.FAIL` does not.

The `deltaTime` parameter is the amount of time in seconds over which the property was updated on the client that sent the update. This can be used in calculations for limiting movement speed.

If the server rejects an update, the owner client is not notified. If you need to inform the client and take corrective action, you should do so using an RPC.

The following server scripts show how to limit the player's movement speed and send corrective position deltas to the client for the client to correct its position.

```c#
using KS.Reactor.Server;
using KS.Reactor;

public class seSpeedLimiter : ksServerEntityScript
{
    // Maximum speed the player can move for calculations using the client time deltas.
    [ksEditable]
    public float MaxClientSpeed = 8;
    // Maximum speed the player can move for calculations using the server time deltas. It's a little bit higher than
    // MaxClientSpeed because it's expected for the client to sometimes exceed their max speed in server time when a lot
    // of updates arrive at once.
    [ksEditable]
    public float MaxServerSpeed = 10;

    // A correction delta is sent to the client when the difference between the position they sent and the position we
    // set is at least this large.
    [ksEditable]
    public float MinCorrectionDistance = .05f;

    // Sum of correction deltas the client has not acknowledged.
    private ksVector3 m_unackedCorrectionDelta;

    private ksVector3 m_lastServerPosition;
    private ksVector3 m_lastClientPosition;

    public override void Initialize()
    {
        m_lastServerPosition = Transform.Position;
        m_lastClientPosition = Transform.Position;
        Room.OnUpdate[-1] += Update;
        Entity.OnValidateOwnerTransform += ValidateTransform;
    }

    public override void Detached()
    {
        Room.OnUpdate[-1] -= Update;
        Entity.OnValidateOwnerTransform -= ValidateTransform;
    }

    private ksValidationResult ValidateTransform(ksReadOnlyTransformState transform, float deltaTime)
    {
        m_lastClientPosition = transform.Position;

        // Enforce the maximum speed in cline time. deltaTime is the amount of time passed on the client. This value
        // comes from the client, so to prevent clients from cheating by sending larger time deltas, we also enforce a
        // speed limit using the server time in an update handler.
        if (LimitSpeed(Transform.Position, transform.Position, deltaTime, MaxClientSpeed))
        {
            return ksValidationResult.PASS;
        }
        Transform.Rotation = transform.Rotation;
        Transform.Scale = transform.Scale;
        return ksValidationResult.FAIL;
    }

    private void Update()
    {
        if (Entity.Owner == null || Time.FramesUntilSync != 0)
        {
            return;
        }

        // Enforce the maximum speed in server time.
        LimitSpeed(m_lastServerPosition, Transform.Position, Time.Delta * Time.FramesPerSync, MaxServerSpeed);
        m_lastServerPosition = Transform.Position;

        // Calculate the difference between the server position and the last position the client sent with the
        // unacknowledged correction deltas added to it.
        ksVector3 correctionDelta = Transform.Position - (m_lastClientPosition + m_unackedCorrectionDelta);

        // If the correction delta exceeds the minimum correction distance, inform the client of the correction delta
        // with an RPC, and add the delta to the total unacknowledged correction deltas.
        if (correctionDelta.MagnitudeSquared() >= MinCorrectionDistance * MinCorrectionDistance)
        {
            Entity.CallRPC(Entity.Owner, 0, correctionDelta);
            m_unackedCorrectionDelta += correctionDelta;
        }
    }

    // Prevent the player from exceeding a max speed.
    private bool LimitSpeed(ksVector3 startPosition, ksVector3 endPosition, float deltaTime, float maxSpeed)
    {
        ksVector3 delta = endPosition - startPosition;
        float maxDistance = maxSpeed * deltaTime;
        float distanceSquared = delta.MagnitudeSquared();
        if (distanceSquared < maxDistance * maxDistance)
        {
            return true;
        }

        // Set the delta length to the max distance and apply it.
        delta *= maxDistance / ksMath.Sqrt(distanceSquared);
        Transform.Position = startPosition + delta;
        return false;
    }

    // Subtract deltas from the total unacknowledged correction delta when the owning player sends acknowledgement RPCs.
    [ksRPC(0)]
    private void AcknowledgeDelta(ksIServerPlayer player, ksVector3 delta)
    {
        if (player == Entity.Owner)
        {
            m_unackedCorrectionDelta -= delta;
        }
    }
}
```

The following client script applies the correction deltas received from the server and acknowledges them by sending them back in an RPC. This script applies the entire correction delta at once. To make it smoother, you could apply the delta over multiple frames, in which case you would need to send only the amount of the delta you applied in an acknowledgent RPC each frame that you apply part of the delta.

```c#
using UnityEngine;
using KS.Reactor.Client.Unity;
using KS.Reactor;

public class ceCorrector : ksEntityScript
{
    // Apply the correction delta sent by the server to our position, and send acknowledge the delta by sending it back
    // to the server in an RPC.
    [ksRPC(0)]
    private void ApplyCorrectionDelta(Vector3 delta)
    {
        transform.position += delta;
        Entity.CallRPC(0, delta);
    }
}
```
