This week I’ve made some progress on the game’s core loop engine.
That doesn’t sound terribly impressive, because the core loop of most games is pretty simple, under the hood:
while(true){
update(everything)
}
It’s more complicated than all that - maybe there’s separate “update” and “display” steps - but ultimately a game engine has a bunch of objects and a loop that checks what they’re up to every couple of milliseconds.
Generally, game engines provide this for you, which is nice of them.
But, at least in its current iteration, Groovelet is server-authoritative. All of the gameplay logic is happening on my side. The client just opens a socket connection to the server and waits for input to see what happens in game.
There’s still an loop occurring on the client. In the case of JavaScript, this is the event loop, but on top of the in-game loop I need to maintain a distinct loop for every connected user on the server side.
The reason to go to all of this trouble? Well, the biggest reason is that this is (at least intended to be) a simple, slow, asynchronous multiplayer game.
Multiplayer-games are usually mostly client-authoritative. Most popular multiplayer games hand almost all of the logic to the client (where the user can pay for the CPU cycles) and simply handle message passing and verification between users. Computer A says “I moved 10 feet to the left” and Computer B receives that and moves you 10 feet to the left. This is how things need to be when games are extremely CPU-intensive, because doing all of that processing on the server side is simply too expensive. This, however, introduces a handful of extremely difficult problems, including but not limited to:
synchronization (the varying clients need to juggle and agree upon a shared state) - what happens if you say you moved 10 feet to the left, but the other computer got the message out of order, late, or not at all?
verification (the client needs to send messages consistent with a valid game state) - what happens if you cheat and say you moved 1000 feet to the left?
Synchronization is tough - what happens when messages don’t get sent? How do we validate that states are accurately lined up between systems? What happens if one state gets out of sync with another state?
Verification is also tough - it’s really hard to know which messages represent an honest client who’s not cheating at the game without fully simulating game state. Instead, you end up building a rules engine that attempts to place reasonable boundaries on the messages that are being set to keep all players within the realm of reason - and invoking other, darker technologies like invasive DRM or complex obfuscation to try to keep your players from cheating.
Server-authoritative architectures make both of these problems easier to handle - synchronization is still an issue but you have one absolute source of truth rather than a set of equally-un-trustable clients. Verification is still an issue but you, again, know exactly what the game state is and thus can throw away any move that seems even remotely invalid.
Server-authoritative architectures produce multiplayer games that are extremely difficult to pirate or cheat (without a complete re-implementation), because the game’s logic never leaves the impenetrable bastion of the server.
The divide between “server authoritative” and “client authoritative” is not a massive gulf - just about every modern MMO has parts of their stack that are server authoritative (“your level”, “your items”) and parts that are client authoritative (“the game’s UI, your position”).
A recent example of a game that went all in on the Server Authoritative angle was EA’s 2013 SimCity. They moved their whole city simulation engine to the cloud.
This was, actually - and I say this with some trepidation, because it might be an unpopular opinion - this was a fabulous idea and a technological triumph. Imagine the possibilities! For one thing, your city could be live, always running, you could check in on how it’s going on your phone. That’s neat, right? The idea that your city just keeps on existing, even when you’re not around? Suddenly, multiplayer city management could be as easy as just having two people logged in to the same view of a city! With a little bit of care, you and your friends could build sprawling, interconnected metropolises!
Users hated it. For a lot of extremely good reasons. For one thing, the team didn’t have time to lean into any of the strengths of the architecture. The game didn’t ship with a whole lot of really satisfying, interconnected multiplayer cities - instead, your city would just sort of peripherally have unpredictable and frustrating effects on cities around it.
Running the whole simulation in the cloud presented a big restriction on how big your city could be - no matter how powerful your computer was, your footprint in the cloud needed to be the same.
Running the whole simulation at the same time as other players and keeping that “fair” took away one of the greatest abilities of the core game - the ability to speed up time.
The game allowed no offline cities at all - which users perceived as extremely intrusive and user-unfriendly DRM rather than a necessary consequence of a groundbreaking architectural decision.
pictured: this is about as big as a SimCity city could get
Oh, and there were more problems: the huge launch of a brand new and mostly untested cloud product predictably resulted in massive server outages and most of the people who bought the game couldn’t play it at all on launch day.
After a disastrous year and a whole lot of extra work, the team launched a client-authoritative version of the game so that players could play without having to connect to their cloud.
It really didn’t help EA’s case that Cities: Skylines came out shortly afterwards and was simply the Most SimCity.
I’m kind of disappointed that SimCity didn’t work out - a massively multiplayer SimCity is such a big, cool dream that I’m sad that it didn’t launch with some of the features that could have made it really work.
I have a lot of theories about how a fully server-authoritative simulation game could be made fun and practical. I mean, that’s… that’s why we’re here, right? That is why this blog and this project both exist.
Like, okay - being stuck on a fixed time signature, online-only, small-city? These are huge drawbacks. As an exchange all you get are some bullshit neighboring cities from Rando McDowell? And the effects you can have on your neighbors are minimized because what if they’re a troll? At that point what we’ve built is just massively multiplayer solitaire, but worse. Your “neighbors” could be replaced by a random number generator in a single-player game to similar effect.
On the other hand, what if cities were allowed to grow in size as more Mayors joined in, all sharing the same plot? That’s fairly practical given the architecture at play, right? You and three of your friends could share a plot four times the size, and have deep and possibly WILDLY DESTRUCTIVE interactions with your friends, and that’s good because if they demolish your favorite village to make room for a three lane highway that they needed to build, that’s actually a pretty amazing gameplay interaction. It also has a built-in loop that brings in more players - if you want more room to work with, you’re going to need to recruit some friends.
Maybe it’s a free-for-all, or maybe users have their own territories and votes on overall city policy, like they did with Simcity 2000 Network Edition, which was a wildly clever game (which also didn’t do terribly well, but I believe that was largely because it was a total bear to set up and so, so slow).
One Million Two Hundred And Nine Thousand Six Hundred Seconds
On to my weekend progress with the engine: now, when a user gets past the opening crawl and selects a Game, their client builds a websocket connection with the server, authenticates against it, and starts a background simulation process against that Game. This adds a new server class to my backend! Now a “Simulation” node has joined the fray. So long as a simulation process has at least one socket connected to it, it’ll keep on updating its Game in real time - currently at a very sluggish simulation step every 45 seconds. Every turn, the client receives an estimate as to approximately how many milliseconds remain until the next turn, so that it can display a count-down timer between users and their next simulation step. When the simulation step is completed, a new socket message will be sent to the client.
“45 seconds/simulation step” places some big constraints on how the gameplay is going to go.
I had an initial model (and whole-ass server architecture) running at 1 second/simulation step, which was fast enough that the game could feel sort of realtime-adjacent - the idea was to play music at 60bpm or 120bpm and have the world hop along to the beat like a giant MMO Crypt of the Necrodancer (if you haven’t played it, you’re missing out, it’s great) - but actually building any game functionality against that engine turned out to be malbolge hard. I still have a lot of that code on my computer, and I definitely plan to return to it eventually, but I want to finish something before the heat death of the universe so I’m taking a break to focus on building something that simulates at a much slower pace. I am but a simple man.
That, unfortunately, changes EVERYTHING. Some market research indicates that the market of users who are willing to play a real-time grid-based musical simulation game with music that runs at less than 33 bpm is “one guy, named Garchibald, who has an emotional disorder”. A longer update window means that I’m actually building something with a much more turn-based flow, as each cycle has to feel like a meaningful decision all on its lonesome - more Civ, more board game.
But that’s… actually a really cool and largely unexplored design space? A massively multiplayer… card game. Huh. I like that. Big players in the space include “Hearthstone” and the newly released Magic: The Gathering online TCG, but those are competitive, head-to-head affairs and bear little resemblance to what I’m shooting for.
The slower, more measured turns make the backend simulation engine a breeze to write, in comparison. I finished the new “engine” in one week - compare with the 1-second engine which took 8 months to write and was only barely working. (If you’re reading this and feeling a bit judgy about my development speed, remember that I have a full-time job, as well. Also I am a bit of an idiot.)
I’m hoping to take some inspiration from some of my favorite card games (digital and physical) of late - Slay the Spire, Cultist Simulator, 7 Wonders, Century: Spice Road, Wingspan, these kind of things.
Woop, did Groovelet just become a… card game? Well, that feels like a bit of a change.
I’m not going to lie, this isn’t a sudden and capricious decision - I’ve been planning this change (to a more forgiving simulation rate) ever since I cracked open a whole new GitHub repo 5 weeks ago.
Anyways, now that I have a GAME TICK, maybe my next step should be literally any kind of gameplay!