I am aware of snapshot interpolation with the use of delta compression as being a technique used to minimise the amount of data sent in a modern FPS game like Quake 3, but what other techniques are there?
When we implemented our networking engine we exploited a number of compression techniques:
- First we write all of our snapshot bit-wise: bools are only 1 bit instead of 1 byte (or more depending on compiler). We wrote a bitstream class that reads and writes data to a stream. This saves a fair bit of data all alone when packing flags down. For an example of a decent C++ BitStream class have a look at the RakNet source recently made available by Occulus here: https://github.com/OculusVR/RakNet
- We found the smallest way to represent data, bitwise - if an integer can only be in the range of 0 to 15, for example, we only store it in the snapshot as 4 bits. We perform this range packing on signed and unsigned integers, minimizing the amount of data being sent at the cost of having to write the data to our snapshot stream bit-wise. There is CPU cost here, but the data bandwidth is greatly reduced. We do this for enumerations as well - if there are only two states, it only takes one bit, for example. We exploit templates here to make this easy to write in code.
- We establish some data types that can be quantized - data that we are satisfied to lose some decimal precision to reduce the over-the-wire bandwidth. Combined with the range packing we can compress floating point numbers down from 32 bits to a smaller, range and precision specific level. Again we exploit templates here to make this easy to write in code.
- Delta compression - you have identified this already, but it makes a significant impact.
- We group blocks of data where the delta compression allows us to group a number of related, unchanging fields under a single dirty bit to indicate if they have changed or not. This reduces the cost of sending a bit for each field indicating it has NOT changed. There is less of a win here, and one has to be careful about how you group them, but infrequently changing fields that generally change in tandem are good candidates.
- We take the entire packet and compress it using a quick compression engine - lz4, for example. This reduces the data even more - every thing helps.
- We don't send ANY data for objects that are too far away to be part of the local player's simulation and bulk-synchronize those objects when the player enters proximity. Some objects are excluded from this system such as mission objects, keys and locks, for example.
- We don't send ANY data that can be reliably reproduced on the client. For example, we send animation state and time instead of the position of each bone - the client can reliably pose the character with that information, saving us the bandwidth of sending the transform of every bone (and anything attached to those bones - hit boxes, effects, etc). We extend this out as far as we can. As one comment pointed out, "the best compression is not sending it at all".
I'm sure there are others, but these ideas should help.