Count complete trips to the basement and back to ground floor
A few points.

if with only the “then” case is usually written with when, which is in general more handy since it allows several forms inside its body. So you could write (when (finished-basement-trip-p floor new-floor) (incf count)).

In a loop you can define variables that change at each iteration with the clause for variable = expression, and this in many cases avoid the use of nested let clauses, that make more difficult to read the program. Another useful iteration clause is for variable = expression1 then expression2, which assigns to variabile the value of expression1 during the first iteration, and in all the others the value of expression2.

In a loop, if you need to count something and then return its value, you can use the “numeric accumulation” clause count, analogous to the sum clause, in which you count how many times a boolean espression is true.


Here is a proposed simplification. Note that in my version I do not use local functions since I think the program is easy enough to read without them. This is just a personal opinion.
(defun count-basement-trips (travel)
  (let ((floor 0))
    (loop for c across travel
          for up = (char= c #\u)
          do (incf floor (if up 1 -1))
          count (and (zerop floor) up))))

Here's the buggy version suggested by @user235906 in a comment that got deleted. @Renzo points out it gives an incorrect answer for the test string &quot;ud&quot;:
(defun count-basement-trips (travel)
  (loop for c across travel
        for up? = (char= c #\u)
        for floor = 0 then (incf floor (if up? 1 -1))
        for completed-trip? = (and up? (zerop floor))
        count completed-trip?))

And here's the suggested fix:
(defun count-basement-trips (travel)
  (loop for c across travel
        for up? = (char= c #\u)
        for change = (if up? 1 -1)
        for floor = change then (incf floor change)
        for completed-trip? = (and up? (zerop floor))
        count completed-trip?))