As with most things in game development, there are a number of ways to tackle this issue. The most common way I have seen is to have the client-side physics be completely "dumb."
The simulation on the client side is merely reacting to data delivered by the server and inputs from the client side are sent to the server, interpreted and distributed to all connected clients as data. You can easily do very basic cheat detection during this "interpretation" step by ensuring that the move the client is attempting to make fits within the rules of your game. For instance, in the context of your billiards game, if the server currently has
ball1's position as (x:26, y:50) in the game world and suddenly fields a request to move
ball1 from (x:35, y:50), you know something strange is going on. Now "something strange" could be either network latency, dropped packets or player tampering and it's up to you to decide how to determine the cause and handle it.
This article by Gaffer on Games (really great source), can explain in much more detail than I ever could, but these are the essential steps:
- Player presses a key or invokes an action.
- Action is sent to the server.
- Server interprets the action for the player.
- Server checks if the action is valid.
- If valid, server applies action to all relevant game objects.
- Server sends updated data on all affected objects to all connected players.
With this implementation, the drawback is that because real game data is only flowing one way (server to player), and network transmission is never perfect, the client has to do some guesstimating. This is called client-side prediction and is covered in the section of the same title in the article linked above.
Even though you're not working with the UnrealEngine, their Networking Overview article does an excellent job of explaining how their server-client system works and how they tackled certain challenges that exist in managing multiple objects and states over network.
Hope this helps!