A little history

Giveamay was a daily giveaway event held every May (2021–2024) on my friend mscupcakes’ Discord server. In 2021 I built a Discord bot to manage it: it picked a random user who had chatted that day, announced them at 7PM, and let them accept or decline the prize via reaction. Prizes were stored in a Google Sheet curated by mscupcakes and referenced by the bot.

What it all started with.

In 2022, I wanted to spice things up with a video. I pitched a spinning wheel animation featuring mscupcakes, and Benalki kindly provided the art. I used Unity, a familiar engine, and based the texture generator on an old Tmpl8 tool. The bot generated a JSON with participants, which I rendered as segments on a wheel. Unity spun the wheel and picked the winner, producing a video per day.


It looked great but this was by far the worst Giveamay year. Unity would constantly mess up at various parts throughout the process. Because I generate the texture with another application, Unity has to pick those changes up in the editor afterwards. Unity would sometimes refuse to do this if it was already in focus, and never update the texture, meaning that some days it would show the texture of the previous day, meaning the bot would congratulate a different winner from the video. I noticed that when I would alt-tab out of it, wait a few seconds, and alt-tab into Unity, the texture would correctly update. So, seperate from everything else, the bot would start an executable which job was quite literally bring Unity to the foreground and background a couple of times. I had to babysit the process daily. The people liked the end results, but it left me very stressed all month long.

A cupcake

So for 2023, I decided not to rely on Unity. It meant I had to generate the frames and audio myself, and merge those with something like ffmpeg. I wanted to make a battle royale kind of thing, with cupcakes representing each participant. They would punch eachother constantly until one would end up alone, making them the winner. The interesting part here is that the winner is pre-determined by the Discord bot, so how do you know beforehand which cupcake to put the correct names on?

That’s the neat part, I didn’t! Instead, I simulated a battle-royale session with each cupcake without any rendering whatsoever, storing the state of the whole simuation of each frame at 60 frames per second. I would store the position and state of the cupcake (are they healthy or not?) and at what time they would die. Once only one cupcake is left, I would stop the simulation. At this point, I knew the winning cupcake, so I would attach the information from the Discord bot to it, and render the stored state of the simulation. The rest of the cupcakes would get a random person.


This year was so much better. It worked flawlessly. No crashes, no surprises, just scheduled rendering on a remote server. With full control over the pipeline, 2023 was a stress-free success. Only regret: the font rendering wasn’t great, but people didn’t seem to mind.

So, let’s one up it AGAIN.

I knew I wanted to go even further the following year. I wanted to do more in 2023, but didn’t allocate enough time to do so. So at the end of 2023, I started programming the 2024 version of Giveamay. This year, it would all be live. You wouldn’t have to start a video in Discord to see who won. Everyone would see the same output at the same time, on a website. It would still be the same concept as last year, a Battle Royale game, but I wanted everyone to be able to focus on their own character in the game. Additionally, I wanted mscupcakes to play a bigger role in this one.

Firstly, I wanted to extend on the previous year’s idea with something I was familiar with, the way League of Legends did their replays. League of Legends stores network packets into a replay file, which the game then just runs as-is, tricking it into thinking it’s running a live game. This method has a massive benefit, if the game can handle the packets, then the replay can too. This means that playing a game live should look pretty similar to a replay being played from start to end.

The simulation

So I decided to create two projects that shared the same code, one that played the simulation, and one that generated the simulation. That’s because the generator would have to be able to do some of the tasks the simulation viewer would be doing. I’m not usually one to immediately reach for inheritence, but it seemed like the SimulationGenerator would extend the Simulation’s behaviour. So each frame, the viewer (which just runs the Simulation) plays the currently generated events, and the SimulationGenerator generates those events as well afterwards in the same Step function.

The Simulation

The SimulationState is in this image is a unique pointer pointing at either a SimulationGenerationState or a SimulationPlaybackState.

The State

The playback state holds the current state of the player and the map, as well as all the events to be played (in chronological order). Finally, the playback has a playback time, which is the current time the state is in, so that it can interpolate events at any framerate. The SimGenState inherits all of these, and uses the states from the playback part to determine what to write to next. Besides that, it holds a couple of details to ensure the generation is still valid. On the next frame, Simulation::Step() takes that new data and simulates until the current frame of the generation state. The SimulationGenerator::Step() then creates new events.

The view cone

Every single step of the generator, it goes over every player that is still alive, and tries to figure out if they need to take a new action. It effectively goes over the following list in order:

  1. Look for any player to attack
  2. Walk through door. (I’ll get to this one in a second..)
  3. Look for any treasure to open
  4. Look for a door to open
  5. Wander/Look around

Each frame, try to find anything to do on the list. Any higher priority item would override. Sometimes an item of the same priority could override another, which is when it makes more sense to tackle that one. If you have two players to consider to attack, whoever is closest is the highest priority between the two.

Each player has a viewcone, and can see anything within that cone. Player 12 in the previous image is tracing a couple of rays to a door and an offscreen objective, but can only see a single door. Since that’s the highest priority it can take, it’s likely it will go for that one. Doors were funny, since I had an issue at one point where players would open doors, then turn around and do something else entirely. So whenever opening a door became a high priority, I made sure that walking through it became the next priority.

Sometimes the players would see two treasures. In that case, each player would remember previously seen treasures, and open that one next if it has nothing better to do. Opening a treasure makes you faster, makes your viewcone wider, and gives this player a higher chance of killing someone.

When a player attacks, once they get into range, they instantly remove the other player from the game. If two players attack eachother, they meet up in the same frame, and therefore could kill eachother at the same time. I resolve this by the walking speed, whoever walks faster, is the one that gets the killing blow.

The generation stops if there are 1 or no survivors, or whenever the game is taking too long (can’t have everyone sitting around for 4 minutes of course). The amount of times needed to generate games under these conditions weren’t too high and you’d have a game within a second of generating. Funnily enough, writing to a json file was the slowest part of the procedure 😂

Bringing in mscupcakes

So, I wanted to bring in mscupcakes, make her a participant regardless of whether she chatted that day or not. Additionally, I wanted her to be pretty central to the playthrough. Usually, like last year, you would generate once, and be done with it. But with my idea, I wanted to specifically change one of the players to a specific mscupcakes cupcake, which would be faster and stronger. This cupcake needed to win until they were second place, and then they were forced to lose to whoever the bot chose should win.

I liked the idea, but I was a bit stressed about it. This meant that I had to generate a lot more games, to ensure that cupcake makes it down to second place. Gave it a try and was surprised that generation of games still took under 10 seconds, after generating a couple hundreds of games. This was a very nice surprise, and meant that I could continue my plan.

Finally, it all came together into the following: (Please click the right side to focus the page for sound and music)

And that’s it! There’s of course a lot more to it, but I wanted to be concise. If you wanted to know more about the rendering, serialization, light generation, sound, building c++ games for the web, javascript interoperability, feel free to contact me through LinkedIn or Github!