Since you used Quake3 as an example, I will focus on how they are done there. The first thing you need to understand is that the “game state” in relation to client-server games does not apply to the entire internal state of the object, including the current state of the AI, collision functions, timers, etc. The game server actually gives the client less. Just the position of the objects, orientation, model, frame in the animation of the model, speed and type of physics. The last two are used to make the movement smoother, allowing the client to simulate a ballistic movement, but more on that.
Each game frame that occurs approximately 10 times per second, the server runs physics, logic, and timers for all objects in the game. Each object then calls the API function to update its new position, frame, etc., and also to update whether it has been added or deleted in this frame (for example, a deleted image because it hit the wall). In fact, Quake 3 has an interesting mistake in this regard - if the shot moves during the physics phase and hits the wall, it becomes deleted, and the only update that the client receives is deleted, not the previous flight to the wall, so the client sees the shot disappears in the air 1/10 second before striking the wall.
With this little information about an object, it is fairly easy to distinguish between new information and old information. In addition, only objects that really change call the update API, so objects that remain unchanged (for example, walls or inactive platforms) do not even require such a diff. In addition, the server can additionally save the sent information without sending objects to the client that are not visible to the client until they appear. For example, in Quake2, a level is divided into viewing areas, and one area (and all objects inside it) is considered “out of sight” from another if all the doors between them are closed.
Remember that the server does not need the client to have the full state of the game, only the scene graph, and this requires much simpler serialization and absolutely no pointers (in Quake it is actually stored in one array of static size, which also limits the maximum number of objects in Game).
In addition, there is also user interface data for things like player health, ammunition, etc. Again, each player receives their own health and ammunition sent to them, and not those that are on the server. There is no reason for the server to share this data.
Update: To make sure that I get the most accurate information, I double-checked the code. This is based on Quake3, not Quake Live, so some things may vary. The information sent to the client is essentially encapsulated in a single structure called snapshot_t . This contains a single playerState_t for the current player and an array of 256 entityState_t for visible game objects, as well as a few extra integers and an array of bytes representing a bit mask of the "visible areas".
entityState_t , in turn, consists of 22 integers, 4 vectors, and 2 trajectories. A “trajectory” is a data structure used to represent the movement of an object through space when nothing happens to it, for example. ballistic movement or direct flight. These are 2 integers, 2 vectors and one enumeration, which can be conceptually stored as a small integer.
playerState_t slightly larger, contains approximately 34 integers, approximately 8 integer arrays ranging in size from 2 to 16 each and 4 vectors. It contains everything from the current frame of the weapon animation, through the player’s inventory, to the sound the player makes.
Since the structures used have the given sizes and, well, the structure, creating a simple manual function "diff", comparing each of the members, is simple enough for each. However, as far as I can tell, entityState_t and playerState_t are only sent in whole, not in parts. The only thing that is "delta" ed is which objects are dispatched as part of the entity array in snapshot_t .
While a snapshot can contain up to 256 objects, the game itself can contain up to 1024 objects. This means that only 25% of objects can be updated from the point of view of the client in one frame (any other will lead to the shameful error "packet overflow"). The server simply keeps track of which objects had significant movement and sends them. This is much faster than executing the actual diff - just sending any object that is called “updating” by itself, and is inside the bit mask of the player’s visible area. But theoretically, a handwritten analysis of the structure would not be much more difficult to do.
For the health of the team, while Quake3 does not seem to do this, so I can only guess how it is done in Quake Live. There are two options: either send all playerState_t structures, since there are no more than 64 of them, or add another array to playerState_t to store the HP command, since it will be only 64 integers. The latter is much more likely.
To maintain an array of objects in synchronization between the client and server, each object has an entity index from 0 to 1023 and is sent as part of the message from the server to the client. When a client receives an array of 256 objects, it passes through the array, reads the index field from each and updates the object by the read index in its locally stored array of objects.