Ok, good to know!
TCP always waits for a return packet that contains an ACK, even if it may contain no data. In UDP, this would be the same: waiting for a return packet with no data. If you don't see that return packet in a while (say, 2x RTT time) then re-transmit. If you still don't see the return packet, back off on the waiting time, and re-transmit; repeat until you give up.
Yes, this "feels" like the correct way. I noticed the ENet uses a default ping interval of 500ms that always is running, no matter what which I think sends ACKS also (Just started poking around in the ENet sources, so might be wrong here). I also saw the TNL talk from GDC a few years back (2008?) where the guy said that they always send the same package size at the same interval so that routers, etc. don't de-prioritize their data when their traffic volume goes down (paraphrasing here).
Another option is to keep the packet rate flowing -- say you want to always send at least 1 packet a second, even if there are no messages to send. For games, this is good, because it allows you to quickly detect dropped players, and it will keep any NAT translation table alive. Some broken NAT gateways may time out UDP connections as quickly as a dozen or so seconds if there's no reply.
Yes, I already do this - and I actually have a question on this related to the problem in my original post in this thread: Internally in my own little implementation I have different messages types, each defined as a class with a byte id assigned to it, which has two methods "pack" and "unpack", a very common pattern. Now these messages can be sent "Unreliable-drop-late" or "Reliable-in-order" but also actually "Reliable-out-of-order" (didn't bother mentioning it in the original post), so I've come up with another edge case I'm not sure how to solve, and I hope I can explain this properly:
Finally, you want to separate "messages" (the units of information you communicate) from "packets" (the UDP datagrams you send over the network.) Typically, it's more efficient to acknowledge and sequence number datagrams, and then complete/re-transmit messages based on what you know about what you put into each datagram.
So assume that I sent packages back and forth, and then send my last "Load Map" message, which doesn't arrive at the client, now this message is for arguments sake "Reliable-out-of-order". Now if I re-send this message in a new packet, there is a super teeny-tiny chance that the *previous* package with this same message will arrive (case of broken routers, over-load, network slowdown, whatever it may be) and since the next package is sent with the same message, but a new packet/datagram sequence number, they could actually both arrive, one after the other, and it would look like I got two "Load Map" messages.
Basically what I'm thinking of: How would I de-dupe a reliable-out-of-order message that arrives in two different packages. Now I can come up a million solutions to this, like attaching the "original" packet sequence the message was transmitted inside of originally, but that spirals out of control if the next packet is also dropped, then we have two possible culprits, etc.
Now maybe this is not a huge issue, and I'm not sure I even need reliable-out-of-order messages. Unreliable are never re-sent so there is no need to de-dupe them, and Reliable-in-order are sent with an internal event sequence numbers (which is cleverly bit-packed so that in most cases it only takes up (7 + (ReliableEvents-1)) bits extra space, so they are easy to de-dupe as they have internal seq. numbers, but reliable-out-of-order with the silly edge case of actually having the package you "thought" was lost be delivered anyway.