void Deserialize(byte[] bytes, int senderId)
    {
        VoosNetworkTypes.ShortToBytes stb = new VoosNetworkTypes.ShortToBytes();
        VoosNetworkTypes.UintToBytes  utb = new VoosNetworkTypes.UintToBytes();

        if (bytes.Length < 6)
        {
            return;
        }

        stb.byte0 = bytes[0];
        stb.byte1 = bytes[1];
        short positionCount = stb.data;

        utb.byte0 = bytes[1];
        utb.byte1 = bytes[2];
        utb.byte2 = bytes[3];
        utb.byte3 = 0;
        float timestamp = VoosNetworkTypes.DecompressFloatFromUint24(utb.data);

        if (bytes.Length < 5 + positionCount * size)
        {
            return;
        }
        for (int i = 0; i < positionCount; i++)
        {
            int offset = 5 + i * size;
            /// View Id
            stb.byte0 = bytes[offset];
            stb.byte1 = bytes[offset + 1];
            offset   += 2;

            short viewId = stb.data;
            if (actors.ContainsKey(viewId))
            {
                NetworkedActor na = actors[viewId];
                VoosActor      va = na.actor;

                if (va == null)
                {
                    diag.numNullActorEntries++;
                }
                if (va != null && va.reliablePhotonView.ownerId != senderId)
                {
                    diag.numSenderOwnerMismatches++;
                }

                if (va != null && va.reliablePhotonView.ownerId == senderId)
                {
                    va.lastUnreliableUpdateTime = Time.realtimeSinceStartup;
                    va.unrel = na;

                    float timeDelta = Mathf.Clamp(timestamp - na.lastRecvTimestamp, 0.01f, maxTimeDelta);
                    na.lastRecvTimestamp = timestamp;

                    UnreliableBitmask mask = (UnreliableBitmask)bytes[offset];
                    offset += 1;

                    Vector3 oldP = na.lastPosition;
                    /// Local Position
                    utb.byte0         = bytes[offset];
                    utb.byte1         = bytes[offset + 1];
                    utb.byte2         = bytes[offset + 2];
                    utb.byte3         = 0;
                    na.lastPosition.x = VoosNetworkTypes.DecompressFloatFromUint24(utb.data);
                    utb.byte0         = bytes[offset + 3];
                    utb.byte1         = bytes[offset + 4];
                    utb.byte2         = bytes[offset + 5];
                    utb.byte3         = 0;
                    na.lastPosition.y = VoosNetworkTypes.DecompressFloatFromUint24(utb.data);
                    utb.byte0         = bytes[offset + 6];
                    utb.byte1         = bytes[offset + 7];
                    utb.byte2         = bytes[offset + 8];
                    utb.byte3         = 0;
                    offset           += 9;
                    na.lastPosition.z = VoosNetworkTypes.DecompressFloatFromUint24(utb.data);
                    Vector3 predictionError = na.lastPosition - va.transform.position;

                    Quaternion oldQ = na.lastRotation;

                    // Full precision rotation for static
                    var   ftb = new VoosNetworkTypes.FloatToBytes();
                    float x   = ftb.DeserializeFrom(bytes, ref offset);
                    float y   = ftb.DeserializeFrom(bytes, ref offset);
                    float z   = ftb.DeserializeFrom(bytes, ref offset);
                    float w   = ftb.DeserializeFrom(bytes, ref offset);
                    na.lastRotation = new Quaternion(x, y, z, w);

                    /// Color
                    Color32 c = new Color32();
                    c.r     = bytes[offset];
                    c.g     = bytes[offset + 1];
                    c.b     = bytes[offset + 2];
                    c.a     = bytes[offset + 3];
                    offset += 4;
                    va.SetTint(c);

                    /// Linear V
                    utb.byte0           = bytes[offset];
                    utb.byte1           = bytes[offset + 1];
                    utb.byte2           = bytes[offset + 2];
                    utb.byte3           = 0;
                    na.linearVelocity.x = VoosNetworkTypes.DecompressFloatFromUint24(utb.data);
                    utb.byte0           = bytes[offset + 3];
                    utb.byte1           = bytes[offset + 4];
                    utb.byte2           = bytes[offset + 5];
                    utb.byte3           = 0;
                    na.linearVelocity.y = VoosNetworkTypes.DecompressFloatFromUint24(utb.data);
                    utb.byte0           = bytes[offset + 6];
                    utb.byte1           = bytes[offset + 7];
                    utb.byte2           = bytes[offset + 8];
                    utb.byte3           = 0;
                    na.linearVelocity.z = VoosNetworkTypes.DecompressFloatFromUint24(utb.data);

                    Rigidbody rb        = va.GetComponent <Rigidbody>();
                    bool      isDynamic = rb != null && va.GetEnablePhysics();

                    // Hard Snap if the teleport flag is on. Otherwise we smoothly interpolate.
                    if ((mask & UnreliableBitmask.Teleport) == UnreliableBitmask.Teleport)
                    {
                        predictionError       = Vector3.zero;
                        va.transform.position = na.lastPosition;
                        va.transform.rotation = na.lastRotation;

                        if (rb != null)
                        {
                            rb.velocity        = Vector3.zero;
                            rb.angularVelocity = Vector3.zero;
                        }
                    }

                    if (va.IsParented())
                    {
                        continue;
                    }

                    if (isDynamic)
                    {
                        if (SnapRigidbodyOnRecv)
                        {
                            rb.velocity = na.linearVelocity;
                            rb.MovePosition(na.lastPosition);
                            rb.MoveRotation(na.lastRotation);
                        }
                        else
                        {
                            const float catchUpEnterThresh = 1f;
                            const float catchUpExitThresh  = 0.5f;
                            float       errorDist          = predictionError.magnitude;
                            if (!va.GetReplicantCatchUpMode())
                            {
                                if (errorDist > catchUpEnterThresh)
                                {
                                    va.SetReplicantCatchUpMode(true);
                                }
                            }
                            else
                            {
                                if (va.debug)
                                {
                                    Util.Log($"in catch up mode... err dist: {errorDist}");
                                }
                                if (errorDist < catchUpExitThresh)
                                {
                                    va.SetReplicantCatchUpMode(false);
                                }
                            }

                            if (va.GetReplicantCatchUpMode())
                            {
                                // Let the fixed update directly lerp this.
                            }
                            else
                            {
                                // Best-effort corrective velocity only.
                                // TODO expose some tunable error -> correction velocity parameter?
                                rb.velocity = na.linearVelocity + predictionError;
                            }
                        }
                    }
                }
            }
            else
            {
                diag.numUnknownViewIds++;
            }
        }
    }