I was developing a real-time multiplayer game a long time ago (It was a rip-off of the arcade game Killer Queen which then got ported to PC and Switch as Killer Queen Black).
The most important thing IMO when developing a multiplayer game is that you do not let clients make decisions. This is such a painfully easy way to prevent one avenue of cheating, yet some FPS games (like PUBG) allow it (or at least, did allow it in the past) and so one way of cheating was for the client to tell the server it scored a hit despite the target not being line of sight. And I don't mean "the target was running behind a wall and latency allowed the hit", I mean "The target has been inside this house, not exposed to any windows, and still got hit" or "The target was on the other side of a 40-foot tall hill and couldn't possibly have gotten hit".
The second is that when possible, you don't send any state to clients that they don't need to see. This is harder in a real-time game like an FPS, but in, say, a Poker game, it's easy.
My implementation was pretty simple. The simulation ran at 60 ticks per second. Both client and server would store the last 120 tick states (The state of the game was pretty small, less than 1 kilobyte). Clients would send their user inputs and the tick number of the input. When the server got it, it would go back to that tick and re-simulate the game to the current tick assuming the user kept that input. It would also relay that input to other players, which would also roll back and re-simulate.
In this model, clients have zero authority. The only messages they can send are player inputs and pings. The simulation is deterministic, and I used TCP, so theoretically, the server would never have to send a full state.
To avoid players "jumping" on the screen when they change direction, you render their location from the previous tick. It makes their location 1 frame behind, but it smooths out latency correction.
To prevent players from using lag to cheat (ie, waiting to see what other players do, then send inputs with old tick numbers that would move them into an advantageous position), I would simply reject any inputs from more than 30 ticks (1/2 second) behind, send a message to the client that they're lagging, and send a full copy of the game state to the client to resync them so that the player sees what the server sees after their input was ignored.
Overall, it was pretty simple to implement, prevented cheating, and handled latency well enough.
The problem is, I got bored after writing all that code and never actually fleshed out the game mechanics.