I was working on a new integration with Discord. However, I didn't understand how to implement a multiplayer system with Godot as the client and a Node server because, as far as I knew, you had to pay for a server on AWS or switch engines (Colyseus or Photon). Then I used Claude Code and Cloudflare to build a multiplayer server with Workers and Pages. Everything about this service was new to me, and it was incredible: it solved all my problems with serving my app to users on Discord.
I tried a new architecture with Cloudflare: a single Worker serves both the game bundles and the server, with everything ready for rooms, players, settings, and so on.
The new architecture was overwhelming, but it let me build a new feature: persistent user progress. Then I combined it with Supabase to add SSO and a database for user badges and more.
The game is called Se Pudrió Todo: a party card game set in Argentina. Every round, one player is the judge, everyone else plays their funniest answer, and the table can throw red and yellow cards like in a football match. It runs as a Discord Activity, so you play it inside a voice call with your friends, and it also works in any browser.
The architecture
The whole idea fits in five lines:
- The Godot client is only a view. It sends commands ("play this card", "vote") and draws whatever state the server sends back. No rules live in the client.
- One Worker serves everything: the game files, the API and the WebSocket. One deploy, one address.
- One Durable Object per room is the authoritative server. The rules engine is pure TypeScript (state + command → new state), the room keeps its own SQLite, and alarms handle the timers.
- Each player gets a filtered snapshot. A card game has hidden information, so the server decides what each player can see: nobody gets another player's hand.
-
Inside Discord, everything goes through the
discordsays.comproxy, and WebSocket is the only real-time option. That one rule shaped the whole design.
Why not a regular game server
Colyseus, Photon or a Node server on AWS all mean a machine running 24/7 that I have to pay for and maintain. A Durable Object is already a room: it's isolated, it keeps its state, and it hibernates when nobody is playing. I didn't have to switch engines or rent a server. I just had to stop thinking of "the server" as one big process.
Fitting Godot into a Worker
The Godot web export was bigger than the file size limit for Worker static assets. The fix was simple: I upload the build only compressed (Brotli and gzip), name each file by its hash, and the Worker sends the version the browser accepts with a one-year cache. A deploy that only changes GDScript only changes the game pack, so players don't download the engine again.
Accounts with Supabase
Accounts are optional: you never need one to play. When you sign in (Google or Discord), the Worker only notes which account the game belongs to. The client never touches the database: only the Worker talks to Supabase, with a secret key, through database functions. When a game ends, the room sends the result, and Supabase stores your progress, badges and favorite cards. I designed the schema for several games from day one, because this won't be my last one.
How it came together, day by day
I looked at the git history to see the order in which each tool came in. It all happened in one week, and during the first three days I also played about five games with friends:
- Day 1: the Godot project automated from the terminal, the game rules, research on Discord Activities and the architecture decision. Then the rules engine on a Durable Object, the Worker serving the web build, the Discord Activity with a tunnel for local testing, and the first deploy. By the end of the day, people could also play in the browser as signed guests, and the game was on itch.io. That night, the first game with friends.
- Day 2: fixes from my friends' feedback (more below), Discord on phones (real screen sizes, safe areas and the tiny picture-in-picture view), and the deck moved to a Google Sheet.
- Day 3: Jev scored every card pair, and I trimmed the deck to the best ones. Then bots that live in the room, invitation previews and rematches. Discord avatars, Rich Presence and invites. And Supabase: accounts, badges, a profile and sign-in with Google or Discord.
- Day 4: security (rate limits, Turnstile, a Content Security Policy), rooms that survive a deploy, automatic reconnection, faster loading with a service worker, and a landing page.
- Day 5: infrastructure as code with Terraform, public rooms, an all-ages Discord app for the App Directory, and error tracking.
- Day 6: the whole game in English.
Five games with friends
Before polishing anything, I wanted to know if the idea was fun. During the first three days, I played about five games with friends, and every game ended with their opinions. Each comment went straight into the backlog, and most were fixed before the next game:
- The play button was far from the table, in the bottom-left corner. Now it shows up in the middle, where your eyes already are.
- The judge wanted to flip any face-down answer, not always in order.
- The shuffle "felt bad": cards you had just thrown away came back right away. Now the deck avoids repeats.
- The cards needed more motion: dealing, flipping and picking.
- Some friends wanted to play with the keyboard only.
- With three players, voting on a red card made no sense, so the red card started removing the answer directly.
Real people found things that no automated test would catch.
Balancing the deck: a Google Sheet and Jev
A card game lives or dies by its cards, so I needed a way to edit hundreds of them without touching JSON. All the cards live in a Google Sheet, and a script syncs it with the game in both directions, so the sheet is always the source of truth. Each card has a theme and an "In the game" checkbox, and a Balance tab compares the deck with its targets: 35% Argentina, 25% vices and partying, 20% dark humor and 20% adult jokes.
To score the cards, I used Jev, TypeSafe's System One model, through OpenRouter. Jev crosses every black card with every white card and rates each pair on four axes: does it make sense, is it funny, does it feel Argentine, and is it witty? Each card gets the average of its pairs, and the final score (MIX) counts wit and Argentine flavor twice. Then a script keeps the best cards of each theme in the game.
The last word is still mine: when I mark a card as "Updated", no script changes its score or whether it's in the game.
The lesson
The hard part wasn't the code. It was deciding where the rules live. Once that was clear (the server is the truth and the client is a view), and once I wrote it down as an architecture decision, vibe coding with Claude Code moved fast, because every new feature had an obvious place to go. And play it with real people as soon as you can: five games with friends taught me more than any test.
So if you think multiplayer means paying for a server or switching engines, look at edge platforms first. A Durable Object per room was all my game needed.
Play it: https://se-pudrio-todo.stevearagonsite.com/en
Join the Discord server: https://discord.gg/UudNQzBAkw

Top comments (0)