public static void ParseActions(byte[] actions, int id, out SlamObservation observation)
        {
            int offset    = 0;
            var covisible = new List <SlamObservation.CovisibleInfo>();

            observation = new SlamObservation(covisible)
            {
                Orientation = Quaternion.identity,
            };
            SlamPoint obsPoint = observation;

            obsPoint.id    = id;
            obsPoint.color = Color.gray;
            while (offset != actions.Length)
            {
                Debug.Assert(offset <= actions.Length, $"[SlamObservationPackageObject.ParseActions] offset ({offset}) out of range");
                ActionType type = (ActionType)actions[offset++];
                if (type == ActionType.Create)
                {
                    obsPoint.isNew = true;
                }
                if (type == ActionType.Create || type == ActionType.Move)
                {
                    obsPoint.position       = BitConverterEx.ToVector3(actions, offset, ref offset);
                    observation.Orientation = BitConverterEx.ToQuaternion(actions, offset, ref offset);
                }
                if (type == ActionType.Tint)
                {
                    obsPoint.color = BitConverterEx.ToRGBColor32(actions, offset, ref offset);
                }
                if (type == ActionType.Remove)
                {
                    obsPoint.color     = Color.red;
                    obsPoint.isRemoved = true;
                }
                if (type == ActionType.Connect)
                {
                    covisible.Add(
                        new SlamObservation.CovisibleInfo()
                    {
                        id = BitConverterEx.ToInt32(actions, offset, ref offset),
                        sharedPointsCount = BitConverterEx.ToInt32(actions, offset, ref offset)
                    });
                }
                if (type == ActionType.Message)
                {
                    int countOfMsgBytes = BitConverterEx.ToInt32(actions, offset, ref offset);
                    if (countOfMsgBytes >= MAX_MESSAGE_LENGTH_IN_BYTES)
                    {
                        throw new Exception();
                    }
                    obsPoint.message = countOfMsgBytes > 0 ? Encoding.ASCII.GetString(actions, offset, countOfMsgBytes) : "";
                    offset          += sizeof(byte) * MAX_MESSAGE_LENGTH_IN_BYTES;
                }
            }
            observation.Point = obsPoint;
        }
        public static void ParseActions(byte[] actions, int id, out SlamPoint point, out SlamLine?fuse)
        {
            int offset = 0;

            point    = new SlamPoint();
            fuse     = null;
            point.id = id;
            bool wasMoved = false;

            while (offset != actions.Length)
            {
                Debug.AssertFormat(offset <= actions.Length, "[SlamPointsPackageObject.ParseActions] offset ({0}) out of range", offset);
                ActionType type = (ActionType)actions[offset++];
                if (type == ActionType.Create || type == ActionType.Move)
                {
                    point.position = BitConverterEx.ToVector3(actions, offset, ref offset);
                    wasMoved       = true;
                }
                if (type == ActionType.Create)
                {
                    point.isNew        = true;
                    point.defaultColor = BitConverterEx.ToRGBColor32(actions, offset, ref offset);
                    point.color        = Color.blue;
                }
                if (type == ActionType.Tint)
                {
                    point.color       = BitConverterEx.ToRGBColor32(actions, offset, ref offset);
                    point.justColored = !wasMoved;
                }
                if (type == ActionType.Remove)
                {
                    point.color     = Color.red;
                    point.isRemoved = true;
                }
                if (type == ActionType.Fuse)
                {
                    point.color = Color.magenta;
                    SlamLine fuseLine = new SlamLine()
                    {
                        pointId1  = point.id,
                        pointId2  = BitConverterEx.ToInt32(actions, offset, ref offset),
                        color1    = BitConverterEx.ToRGBColor32(actions, offset, ref offset),
                        color2    = BitConverterEx.ToRGBColor32(actions, offset, ref offset),
                        isRemoved = true,
                    };
                    if (fuseLine.pointId1 != -1 && fuseLine.pointId2 != -1)
                    {
                        fuse = fuseLine;
                    }
                }
                if (type == ActionType.Message)
                {
                    int countOfMsgBytes = BitConverterEx.ToInt32(actions, offset, ref offset);
                    if (countOfMsgBytes >= MAX_MESSAGE_LENGTH_IN_BYTES)
                    {
                        throw new Exception();
                    }
                    point.message = countOfMsgBytes > 0 ? Encoding.ASCII.GetString(actions, offset, countOfMsgBytes) : "";
                    offset       += sizeof(byte) * MAX_MESSAGE_LENGTH_IN_BYTES;
                }
            }
        }