Skip to content
HN On Hacker News ↗

The backend that runs every game we make, ten years and counting

▲ 16 points • 6 comments • by MikeHer • 2w ago • HN discussion ↗

Pangram verdict · v3.3

We believe this text is mainly AI, with some human-written content.

97 %

AI likelihood · overall

AI
3% human-written 97% AI-generated
SEGMENTS · HUMAN 0 of 1
SEGMENTS · AI 1 of 1
WORD COUNT 1,792
PEAK AI % 99% · §1
Analyzed
Sep 24
backend: pangram/v3.3
Segments scanned
1 windows
avg 1792 words each
Distribution
3 / 97%
human / AI fraction
Verdict
AI
Pangram v3.3

Article text · 1,792 words · 1 segments analyzed

Human AI-generated
§1 AI · 99%

Writing · 22 September 2026 · by Mike Hergaarden Seven games, eleven platforms, one self-built backend for crash reports, remote config, cloud auth and console ports. One developer, ten years, now in C#. Every game I worked on in the last ten years talks to the same backend. Marooners, Verdun, Tannenberg, Isonzo, Crash Drive 2 and Crash Drive 3, and since this year a seventh game that another studio built on it. Steam, Epic, the Windows Store, PlayStation 4 and 5, Xbox One and Series, Switch, iOS, Android, WebGL. All of it reports to one Google App Engine project in PHP, backed by Firestore. I called it the GDT, short for Game Development Toolkit. The name sounds generic, but so is the tool: backend, admin, a small player portal for account linking and closed tests, and the Unity package, and it took on a new job with every game. The PHP version ran ten years without an outage a player noticed, and has now been replaced by a C# version. This piece covers the PHP years; the screenshots are from the C# version, and a section near the end covers what is new. Our hosting bill for all of those games together was between 10 and 50 euros a month, depending on how many people were playing, most of them in the free mobile games. If nobody played, it would cost nothing. For the premium games the cost per paying player was negligible. I built it alone, and there has been almost nothing to maintain: Google kept the old PHP runtimes alive, the few moves to a newer one broke nothing, and it just runs. Keeping it simple, PHP included, has paid off tremendously. It is also the part of our work nobody sees. Players see the game, reviewers see the game, and the thing that made every launch and every port possible sits behind an admin login. I have wanted to show it around for years. So here it is. Games on it7, six of ours and one by another studioPlatforms11In use2016 to nowBackendPHP on Google App Engine, Firestore, about 20,000 lines; C# on Cloud Run since September 2026Monthly cost10 to 50 euros, all games togetherBuilt and maintained byone developer The Crash Drive 3 dashboard, four years after launch. Of the last three hundred players to sign in, four in ten were on Android, a quarter on iPhone, a quarter on Switch. That mix is why we ship everywhere: the people who paid on Steam or Switch always have someone to drive into. Where it started Before this I wrote small PHP scripts for every game, lots of them, whenever a game needed something online: live news in the main menu, a counter, a version check, a little event. They were ugly and I kept writing them again from scratch. What changed that was the Marooners console release. We were taking a party game made by six friends to PlayStation 4, Xbox One and PC, and I was the porting team, with Matt on testing and UI. Once the game left our hands I had no idea what happened inside it. On PC you get a Steam forum post when something breaks. On console you get silence, and later a certification failure. The first attempt was Piwik with a Redis backend. I knew the database would be the bottleneck and Redis is very fast, so I had a freelancer set it up, because I had no idea how Redis worked. I never got it running properly. Power tends to bring complexity you did not ask for, and every time something went wrong I was reading someone else’s architecture instead of shipping. That is the one thing I cannot stand in a tool. I want to build a feature in the morning and have it live in the afternoon, and anything that gets in the way of that has to go. So I threw it out and built my own on App Engine and Firestore, in PHP, with no schema and no framework. I applied for WBSO, the Dutch R&D tax credit, for that rebuild. The money was modest, but writing it down as a serious project made me treat it as one. A lot of it was built over a Christmas holiday. It was already working in Marooners, so dropping it into Verdun after the break took days rather than weeks, and someone on the WW1 team asked whether I had worked straight through the holidays. From there it grew one game at a time. The WW1 games were made by WW1 Game Series, the company Jos, Matt and I founded, with a team that grew to twenty-five over the years. Crash Drive 2 was me again, with Matt on testing and UI. Crash Drive 3 was four of us. Every release added a feature or a platform, and every feature was built so that the older games kept working. The API root is still called api_v1; a new game would start on api_v2 the day backwards compatibility became impossible. That day never came. What the backend does People hear “analytics backend” and picture a dashboard with a line going up. It does a lot more than that, for teams of anywhere between two and twenty-five people: 🐛 Crash and error reporting. Exceptions, crash dumps, bug reports with screenshots and log files, in the dashboard for that game. 🔐 Cloud authentication. Steam tickets, Epic tokens, Windows Store, PSN. One endpoint, one user record per platform. Bans work per game or across all our games at once. A banned Steam player also gets a game ban on their Steam profile, and helping Steam out that way is satisfying. 🎛 Remote config. Typed settings per game that the client caches for a day. Toggle analytics, set minimum versions, schedule a cash boost weekend, change a balance number without a patch. 📈 Online stats. Global counters that only go up, so a tampered client cannot rewrite history. Crash Drive 3 players have driven 149 million kilometers, drifted 35 million of them, and pushed 38 million barrels off cliffs. 🧾 DLC and ownership checks for Steam and Epic, so the game does not have to trust the client. 👥 Friends, invites and sessions across platforms, with Photon webhooks underneath. 🗳 Polls, vouchers, key sets per platform for press and testers, and a link tracker with clicks and, because the game reports back, actual conversions. That is how we learned that a link in Discord converts around ten times better than the same link on Twitter. 🏗 Builds. Jenkins is wired into the dashboard. One button starts a build, the result lands on the Steam latest branch by itself, another button makes it public. The build log keeps the timing of every step, which is how we found what made builds slow and kept an eye on it afterwards. ⏱ Benchmarks. The game runs a scene or a scenario, posts the numbers, and they land in the dashboard comparable across builds. 💬 Discord. New reviews, curator mentions, sales milestones, crash spikes, cheaters, new builds, commits. Every Steam review lands in a channel with the reviewer’s playtime and whether Steam counts it toward the score. A player with 210 hours in Crash Drive 2 wrote “i truly miss playing this game!” and the whole team saw it within the hour. A negative review gets the same treatment, so we can respond almost immediately. 🧪 Closed testing. For the WW1 games and again for Crash Drive 3, we invited players who owned an earlier game of ours to test the next one. They sign in on the player portal, the backend checks on Steam that they own it and have played it enough, sends them to HelloSign for the NDA, confirms it came back signed, links their Discord and gives them the tester role, and hands out a Steam key. Hundreds of testers without a single spreadsheet. 📜 A commit history page, first SVN and later Git, fed by a post-commit hook, plus Favro, Trello and UserEcho. The backend already knew everything, so it was cheaper to put those there than anywhere else. All of that is roughly 20,000 lines of PHP, and this is how it fits together: The decisions that mattered Alert on the rate, not the count The most useful rule in the whole system: the crash alert does not look at the number of exceptions. It fires when exceptions per active user go over a threshold, currently 0.05, or when errors per session go over 1.5. On launch day that means thousands of players before anything triggers. Two years later, with a few hundred people online, the same rule still catches a broken patch within the hour. I have not had to retune it in years. What this bought us, over and over, is speed on mysterious bugs. A crash that only happens on one platform, on one build, for a handful of players, shows up in the logs page with a screenshot and a log file within the hour. We fix it, press build, the build lands on the Steam latest branch, we press once more to make it public, and the alert goes quiet. Sales milestones that scale with the game Under 100 owners the backend pings Discord every 10 sales. Under 1,000, every 100. Under 100,000, every 5,000. After that, every 100,000. On launch day the whole team watches the first hundred sales come in one at a time, and a year later nobody is being spammed. The messages come from a bot we call the Milestone penguin, and the penguin still visits: Crash Drive 2 passed 2.95 million unique players in March 2026, and that only counts players since the backend arrived. Before then it had done over 100 million plays on the web and over ten million Android downloads, when all I had was a store dashboard and a guess. I did not expect a small feature like this to do so much for morale. Buckets, not events The whole design bends around the database. It was called Datastore when I started and Firestore later, and either way it is not MySQL: no joins, limited queries, and a price per read and per write. Storing every event as its own row is too heavy that way. A game with thousands of players would write millions of rows a day, and reading them back for one chart would cost more than the hosting. So analytics are batched on the client and aggregated on the server into hourly and daily buckets, one row per hour per thing you count, with a running average. A dashboard for a game with thousands of daily players is a few hundred Firestore reads, cached hourly.