What I'm currently running with thanks to the above replies:
Client sends "commands" which are acked reliably (can't drop these, they're important for server replay) as: uint(frame n) + uint as n-(1->32) acks... These are small and regular.
Server can send deltas which it fires and forgets to which the client responds with fire and forget acks... subsequent delta's are from the last acked frame or 0 if the gap is too wide (on a frame: 0 delta the payload is treated as the full state)... These are compressed and less frequent.
Server can also send reliable messages which are acked by the client in the same way its commands are... These should be small and sent when they are ready (congestion control permitting)
So the client and server have a reliable 2 way message stream and the server also has an efficient (though not reliable, but eventually consistent) way to dump the deltas onto the client.
With the degradation plans of:
Client --message-> Server: You can only be so late before your input is discarded, you're going to be rubber-banded by server correction in some cases.
Client <-message-- Server: You will get this and it will happen but the effect may be so far in your past you don't see/hear any visual/audio representation.
Client <-state/snapshot-- Server: You'll get one of these sooner or later and the state will just be adjusted (jerkiness) if there is not a per-case, pre-programmed way for you to transition to it nicely.