Quake 3 Source Code Review: Network Model (2012)
fabiensanglard.net
fabiensanglard.net
I'm still working three days a week, because it's not like I hated my job, but it was quite demanding at times.
I wrestled with the idea of just reducing hours for way too long. If you do too, just do it.
I have to sacrifice sleep for anything I want.
still a lift though
Reaching a point where you have more money than time is surprisingly easy to do without becoming financially stable in any sense. From my experience the key discipline has to be making my own food at home; it sets a baseline for my thinking on consumption.
When entities are culled due to Potentially Visible Sets (PVS), a player moving fast enough near a vis brush may appear to pop into existence out of thin air. This occurs because of latency. A simple fix would be for the QVM code to extrapolate the player’s position into future frames to determine whether the entity should be culled.
The original Q3 netcode lacked lag compensation for hitscan weapons. As a result, players often had to "lead" their crosshair to register proper hits. This issue was later addressed by the Unlagged mod, which introduced lag compensation.
Input commands had a back buffer to handle lost input frames, but instead of applying them with a delay, the inputs were applied to entities instantly. Consequently, other players' entities sometimes appeared "choppy" when the respective player had a poor connection.
That being said, they got almost everything right:
* Snapshots with entities: Efficiently delivering game state updates to clients.
* Lerping entities: Smoothing movement on the client side using a snapshot buffer.
* Delta encoding: Transmitting only changes in entity states within snapshots.
* Huffman encoding: Compressing snapshots for reduced bandwidth usage.
I'm forced to try to piece together a mental model by finding crumbs on the web, with the current situation:
https://github.com/minism/fps-netcode
https://playmorepromode.com/guides/cpma-client-settings
https://www.esreality.com/?a=post&id=2548041#pid2548771
https://old.reddit.com/r/QuakeChampions/comments/9jd3wv/for_...
PS: the modern way of enjoying Q3 is https://github.com/ec-/Quake3e or CNQ3, these days. Don't know about https://github.com/ioquake/ioq3
- w/ Unlagged, the Server now stores a history buffer (g_activeSnapshots[]) of player states. When processing a action shot, it uses the timestamp sent by the client (cmd.serverTime) to interpolate or rewind states for collision checks (G_EvaluateTrajectory()). This allows the server rewinds player positions to the moment a client action occurred (eg: firing a shot)
- Unlagged reduces choppy movements by making clients use a snapshot buffer (cg.snap, cg.nextSnap) to interpolate positions with CG_InterpolateEntityPosition(). Extrapolation uses velocity vectors from the last known snapshot (currentState.pos.trDelta).
- Unlagged tries to compensate for high latency by rewinding entities to their positions at the time of the shot (ClientThink_real() and G_RewindEntities()). Uses a circular buffer (via lagometerSnapshot_t) to store past states.
- Position corrections (cg.predictedPlayerState) are interpolated over several frames using VectorLerp() to fix prediction mismatches between client and server.
- Commands are stored in a circular buffer (ucmds[]) and replayed when missing packets are detected (ClientThink_cmd()). This attempts to mitigate packet loss by buffering input commands for later execution
This results in visible "misfires" where projectiles didn't behave as expected
Reading this just took me back and made me realize even though we have progressed so far with tech stack and new technologies(LLMs), there is so much joy in just writing plain C code. To me its just feels like working on a classic old 68 mustang and getting your hands dirty. What a joy!
I am a former Quake 3 champion, have a lot of experience dealing with Quake 3 servers :)
One thing to keep in mind is a lot of mods were frowned upon or flat out not allow at all, especially as you can imagine with playing competitive. Once PunkBuster did finally get release for Q3, there was also a bit of hesitation from ranked players on making any non-typical modifications to the game out of fear of being banned. That said, aimbot was a huge issue with Q3 and rarely did I witness PunkBuster do its job.
iD tends to be very tight lipped about things from my limited interactions with them, even in person at Quake Cons..but I do know they were all hanging out on the same forums and places the players were at - so my hunch is they were aware of it.
Most of the popular mods were rolled in as game modes though.
I’ll credit Quake for helping/exposing me to networking, filesystems, and shaders at a young age - one of the few games that truly encouraged (à la Quake Console) making modifications and setting booleans to configure settings. That was enough at the time to encourage me to teach myself JavaScript, PHP, and SQL - as I had some web projects in mind that I needed to execute on for the Quake clan I founded. This later became freelance, which later became a side gig through school. I was also grateful for the various sponsorships and earnings at the time so I could keep my equipment up to date and pay for storage.
Fast forward a bit, and I’ve mostly worked in SecEng at a number of great companies, most of them popular FinTech and FAANGs..I’m extremely fortunate and have been able to work at all my favorite companies. I’d like to think at minimum, Quake prepared me professionally be meticulous in my craft, study human pattern behaviors, and know when to take risk.
Lastly, I can (hopefully) better answer your question in a few years as I am just now working through switching careers completely to become a Motion Designer. There’s something extremely appealing to me to be able to work with 3D + Engineering, while still being able to solve problems AND feel creative at the same time.
..maybe this desire stems from Quake, maybe not. Either way, Quake has a permanent location in my heart.
As a note, I recently played for the first time in over 10 years. I got banned until 2029 from the main RA3 sever most people play on because I achieved 60%+ accuracy in a match and people thought I was cheating.
I also got the aim bot achievement from Steam as a result, which only 3.6% of other players have.
Back in the day I had a very tiny sponsorship with HyperGlide where I tested their products before the launch. They are credited for making the first hard surface mouse pad. I only received mouse pads, but I was happy to receive this.
I'm pretty sure that isochronous data transfer is a fairly important part of the current USB/Thunderbolt spec.
So need sophisticated logic to filter what needs to be updated in a given frame based on relevance, to fit within a packet,
And of course now you need cloud systems for matchmaking, telemetry, etc.
But then again, they're probably just a bot.
I occasionally do some random spelunking for fun and while there's practically no code left, there are still a few small remnants like the odd comment or function signature (or partial signature) that are still there, and some that even date back to Quake 1!
PS: I had 7 ms ping* to a Q3 server at Stanford but I still sucked, unlike one of my college roommates who stuck to Q II and the railgun and was quite good.
* Verio/NTT 768kbps SDSL for $70/month in 2000-2001
From me to Australia should be ~37 milliseconds if we look at the speed of light, but it's closer to 175 milliseconds (meaning a ping of ~350). Nevermind the latency of being on wifi adds to that.
https://www.pingdom.com/blog/theoretical-vs-real-world-speed...
Ping is largely a product of distance and the speed (200km/s). It's not the distance a bird would fly but it can be close to it sometimes. And then the internet is a collection of separate networks that are not fully connected, so even if your target is in the next building your traffic might go through another bigger city as that is where the ISPs peer.
- Network conditions
- High port/traffic oversubscription ratio
- QoS/packet service classification, i.e., discriminatory tweaks that stop, slow, or speed up certain kinds of traffic contrary to the principles of net neutrality
- Packet forwarding rate compared to physical link speed
- Network gear, client, and server tuning and (mis)configuration
- Signal booster/repeater latency
- And too many more to enumerate exhaustively
As such, point-to-point local- and internet-spanning configuration troubleshooting and optimization is best decided empirically through repeated experimentation, sometimes assisted by netadmin tools when there is access to intermediary infrastructure.
In my case, I was 3-4 hops away and 34 mi / 55 km straight line distance, 110 / 177 driving, and most importantly roughly around 142 / 230 of cable distance approximately by mapping paths near highways in Google Earth. I doubt the network path CalREN/CENIC was used because it never showed up in hops in traceroute (although there was nothing preventing intermediaries from encapsulating and transiting flows across other protocols and networks), but it definitely went through PAIX.
* Per technology, zero-distance minimum delay is a function of the single maximum channel bit rate and data size + lower layer encapsulating protocol(s) overhead which was probably UDP + IP + 1 or more lower layers such as Ethernet, ATM, ISDN/frame relay BRI/PRI, DSL, or POTS modems. With a 1 Gbps link using a billion 1 Hz|baud channels, it's impossible to have a single bit packet latency lower than 1 second.
What happens is the game will buffer two "snapshots" which contain a list of entities (players, weapons, throwables, etc.) and it will linear interpolate the entities between the two states for a certain period of time (typically the snapshot frequency).
The server might have a "tick rate" of 20, meaning a snapshot is created for a client every 50ms (1000ms / 20 tick rate).
The client will throw that snapshot onto a buffer and wait for a second one to be received. Once the client is satisfied with the number of snapshots in the buffer, it will render entities and position them between the two snapshots. Clients translate the entities from the position of the first snapshot to the position of the second snapshot.
Therefore even with 5ms ping, you might actually be seeing entities at 55ms in the past.
Reliable & unreliable messages over UDP. Robust message fragmentation & reassembly. P2P networking / NAT traversal. Encryption.
[1]: https://fabiensanglard.net/quakeSource/quakeSourceNetWork.ph...
(Regarding the original question, yes, of course there are sequence numbers. The “recommended readings” at the bottom of the page are worthwhile.)
Sometimes I want to take it down out of shame but at the same time it is a good reminder of where I am coming from and a motivation to keep on improving.
Although, to be fair, now I speak good.