public void HandleMovement(IPhysicsProxy physics, Vector3 movement)
        {
            var xyMovement = Vector3.Normalize(new Vector3(movement.X, movement.Y, 0));

            var xyMagSquared = xyMovement.LengthSquared();

            if (float.IsNaN(xyMagSquared) || xyMagSquared <= 0)
            {
                return;
            }

            if (this.state == ControllerState.Walking)
            {
                var velocityChange = GetVelocityChange(physics, xyMovement, desiredGroundSpeed, deltaGroundSpeed);

                if (velocityChange.LengthSquared() > 0)
                {
                    physics.AddVelocity(velocityChange);
                }
            }
            else if (this.state == ControllerState.Falling)
            {
                /*
                 *  goal: allow, at maximum, desiredAirSpeed velocity while falling
                 *  edge case: when jumping from movement, or sliding off edge, don't limit speed down to desiredAirSpeed
                 */


                Vector3 currentVelocity = physics.GetVelocity();

                var likeness = Vector3.Dot(xyMovement, Vector3.Normalize(currentVelocity));

                if (float.IsNaN(likeness))
                {
                    likeness = 0f;
                }

                var currentVelocityInDesiredDirection = currentVelocity * likeness;

                var deltaMagnitude = desiredAirSpeed - currentVelocityInDesiredDirection.Length();

                if (deltaMagnitude != 0)
                {
                    var change = xyMovement * MathExt.Clamp(deltaMagnitude, 0, deltaAirSpeed);

                    if (change.LengthSquared() > 0)
                    {
                        physics.AddVelocity(change);
                    }
                }
            }
            else
            {
                // TODO: handle sprinting and sliding movement
                // TODO: desired feel during sliding is 0 friction in the movement direction
                return;
            }
        }
        // BUG: can get 'stuck' at the top edge of a ramp
        private Vector3 GetVelocityChange(IPhysicsProxy physics, Vector3 movement, float desiredMagnitude, float deltaMagnitude)
        {
            Vector3 currentVelocity = physics.GetVelocity();

            movement *= desiredMagnitude;
            // TODO: figure out a better limiting method? velocity going up slopes is limited
            Vector3 velocityChange = (movement - currentVelocity);

            velocityChange.X = MathExt.Clamp(velocityChange.X, -deltaMagnitude, deltaMagnitude);
            velocityChange.Y = MathExt.Clamp(velocityChange.Y, -deltaMagnitude, deltaMagnitude);
            velocityChange.Z = 0;

            return(velocityChange);
        }