Rami Ismail (ramiismail.com)                             

Event Schedule

Biography

Rami Ismail is the Business & Development Guy at Vlambeer, a Dutch independent game studio known best for Nuclear Throne, Ridiculous Fishing, Super Crate Box, LUFTRAUSERS, GUN GODZ, Serious Sam: The Random Encounter & Radical Fishing.

Through his work at Vlambeer, Rami has come to realize that the marketing & business facets of many independent game developers could use some help. As such, he created the free presskit-creation tool presskit() and is working on side projects such as distribute() and gamedev.world.

Believing sharing knowledge openly is the cornerstone of independent development, Rami has spoken on a variety of subjects at dozens of game events around the world, ranging from the Game Developers Conference to Fantastic Arcade & from University seminars to incubator mentorship.

He is a avid opponent of game cloning after Vlambeer's Radical Fishing got cloned. He is also a proponent of searching for new, beautiful things in places no-one is looking for them and thus organized Fuck This Jam, a gamejam focused around making a game in a genre you hate. Rami also worked closely with the Indie MEGABOOTH team to enable indie studios to showcase at the larger game conventions, runs the #1reasontobe panel at GDC, and helps as an advisor on events such as Devcom, Train Jam, PocketGamer, and NASSCOM GDC.

Rami has received several awards and recognitions for his work promoting game development around the world, including the IndieCade Game Changer award for the decennial jubileum of the festival.

 
 
 

RAMI IS CURRENTLY IN THE NETHERLANDS.
YOU CAN REACH HIM AT RAMI@VLAMBEER.COM, , , OR BY CALLING +31 (0) 621206363.

Nuclear Throne Daily Runs & Why Assumptions Are Bad Programming

Yesterday, we launched one of the most important Nuclear Throne updates yet. In Update #55, we introduced Daily Runs, a challenge every player can take once a day in a pre-generated level. This way, players can compete with players around the world on the same level, but only get one shot at getting a high score. Obviously, this adds a lot of replayability and challenge to the game for those who enjoy that kind of challenge, without changing the game for those that don’t like to compete with others.

Sadly, there was a problem: for many players, the HUD just kind of disappeared halfway through the game. That doesn’t make for fair competition.

For all of you that think of going into programming, the lesson today is *never make assumptions*. I am obviously not the main programmer on Nuclear Throne, so for me a lot of the code, structure and specifics are not as natural as they are to J.W. Since J.W. was mostly busy today, I decided to take a shot at hotfixing the issue.

The Issue
The core issue was that the HUD was disappearing at random moments, without any clear indication as to why that happened. When we started hunting the bug, the first thing we focused on was trying to create a reproducable method of triggering the bug. Using the developer cheats, we’d try walking around in different worlds and the like. The only way we could get the bug to happen frequently was the ‘trailer cheat’. It was originally created for our trailer creator, Kert Gartner, who needed a way to record video while not being quite amazing at Nuclear Throne. Not only does it bring you to the next world, it spawns a lot of radiation, a big weapon chest and some other stuff – so you immediately look badass for the trailer recording.

The Pause Button
We concluded that one of the ways the bug could be triggered was by pausing the game during the end of the level transition. That was a clue – from what we could tell, the bug would only occur while transitioning between levels. We had something to chase.

So we focused on was figuring out whether the code that draws the HUD was still being executed after the bug.

In Nuclear Throne, the HUD is drawn by two functions: scrDrawHUD, which draws popup text and pickup prompts, which then also calls scrDrawPlayerHUD for each active player. scrDrawPlayerHUD then draws the HUD for the player with the player number provided – number 1 for player 1 and 2 for player 2. We checked in scrDrawHUD, and realised quickly that the HUD was still being drawn. We shifted focus: what if, despite the HUD being drawn, the player HUD was not being drawn? We went back, logged gameplay and realised that this was indeed the case. If we had just been more specific, logging scrDrawPlayerHUD instead of scrDrawHUD, we would’ve noticed that right away. Bad assumption.

The code that calls scrDrawPlayerHUD first checks with the ‘main controller’ if the player exists. Our ‘main controller’ object holds all sorts of important information about the player, including what the ID of the player instance is. Game Maker assigns objects and instances an ID in chronological order (the first created object is ID 0, the 2nd is ID 1, and so on).

If the ID number was higher than 0, we could expect the object to have been created (and thus gameplay to have started properly). If the game is over and the player returns to the main menu, we reset the ‘main controller’ and the value gets set back to -1. Each frame, we check if the value for each player is 0 or higher – if it is, the player was created, so the game has started, and we draw the HUD.

In other words, it would be a good idea to keep track of the value of that player ID in the main controller. We set up a realtime log so we could see the value change while we played, and we started playing in windowed mode. When the bug happened again, we noticed two things: the game suddenly forced itself back to full screen, and the player ID value had reset to -1.

Figuring out why the game goes fullscreen.
Confused, we moved on to see if maybe the surfaces were broken or uneven: that might force the game back to fullscreen.

Surfaces are basically render targets. Normally when we render images, sprites or anything, we do that to a buffer that gets drawn to the screen at the end of the frame. Surfaces are basically a sort of imaginary piece of glass we can draw things on, and then we can later overlay those on the screen. Every shadow in the game is drawn to a surface, and the darkness in dark worlds are also drawn on a seperate surface. Since we’ve had a longstanding bug of the darkness disappearing when resizing windows, we thought maybe the solution could be found here.

The good news: when something changes about the window context – so toggling from full screen to windowed, resizing, turning on or off AA – the surfaces are immediately lost. Chances were that the problem was quite simply that the window was being modified, the surface lost and thus the HUD (and the darkness) no longer drawn. We started looking around, quickly found the bug that caused the darkness to stop being drawn and then searched for the HUD surface.

The problem was: there is no HUD surface. The HUD is being drawn directly to the screen, like most of gameplay. We were back to square one, and already hours underway. All we had learned was that the screen changed, and the player ID was reset to -1.

We searched for code that modified the player ID, and found a number of places where that happens. We painstakingly rewrote code, optimized things and fixed dozens of little problems, but we couldn’t find any place where the player ID was modified. What if, instead of being modified, the controller was being reset? That’d be odd, because it’d mean all information would be lost or modified mid-game.

Or would it? We checked and it turns out most information required to run the game is stored in the Player instance itself, rather than in the ‘main controller’. The controller is used only at the end of the game, and when the two got disconnected you wouldn’t notice it until after restarting – when suddenly you’d be playing a different character, your score was incorrect or your daily challenge run was uploaded incorrectly.

In other words, it could be reset. That’d also explain the screen resize – the main controller is the very first object created at game bootup and sets the screen context. It was time to confirm that the controller was being reset, so we caused the game to display an error each time the main controller was created anew. Within moments, it became clear that that was indeed the culprit: the main controller wasn’t being deleted, it was being overwritten with a new copy of itself.

Big Weapon Chests
We obviously couldn’t just remove or work around that code: it’s being used to initialise the game, and it’d be impossible to change it. We needed to find what caused it to be created anew in the first place, rather than fixing the code in the creation of the controller. Luckily, it’s not hard to find out what object causes something to happen.

The debugger gave us a quite unexpected result: the object that caused the controller to be created anew was the Big Weapon Chest.

So we checked the Big Weapon Chest. There wasn’t a lot of code in there, and we caused the game to throw errors every time it was created. We quickly realised that this was every time we cheated to the next level, and the bug would only occur once every few levels. We were clearly on the wrong track – the chest might be part of the problem, but it wasn’t causing the problem. While the Big Weapon Chest would always be around when the bug occurred, it would be created as part of the cheat without any problems.

But why would the Big Weapon Chest sometimes create a new controller and overwrite the old one? It didn’t make any sense, and since we were stuck anyway, we decided to dig a bit deeper.

This is where I went for dinner, which – because I’d been working all day tracing this bug – also counted as breakfast.

We started looking into where Big Weapon Chests were created instead. Obviously, they were created when we cheated ahead, and under certain in-game circumstances. Specifically, Big Weapon Chests cannot be spawned directly – a normal weapon chest is changed into a big one when certain conditions are fulfilled.

Since the timing of the problem was hard, we decided to step through the code instruction by instruction, seeing what would happen when a big chest was created. The problem was that the code that is normally run when an object is created wasn’t run when the Big Weapon Chest would coincide with the bug. That was another clue: the Big Weapon Chest wasn’t created normally.

The solution!
So we started looking at the one place where the Big Weapon Chest was created through a non-standard method: the scrPopulateChests function, which fills the level with all sorts of chests, cannisters and the like after generation is complete. In it, a weapon chest can be transformed into a big weapon chest using a function called instance_change().

We decided to start ‘stepping through’ the code line by line from the point where the game decides to replace a normal weapon chest with a Big Weapon Chest. The code we were stepping through was the following:

with WeaponChest
{
if random(4) < GameCont.nochest
   {
      curse = 0
      with instance_change(BigWeaponChest,false)
         event_perform(ev_create,0)
      exit;
   }
}

This code is not super complicated. It basically does this:

With the current WeaponChest,
{
   Roll a four-sided dice, and if the number is less than the amount of times players did not pick up a chest,
   {
      Uncurse this WeaponChest,
      Change it into a BigWeaponChest, deleting this chest directly instead of normally,
         Run the 'creation' event on the BigWeaponChest that we just changed from a WeaponChest,
      That's all!
   }
}

It ran through every of those lines perfectly, but at the event_perform(ev_create, 0) object, something odd happened. Instead of executing the ‘creation’ code of a BigWeaponChest, it executed the ‘creation’ code of our main controller. It reset all the values to their default values as they are in the main menu, and thus also our player ID to -1. That’s why the HUD wasn’t being drawn anymore.

But why did it do that? A quick scouring of the Game Maker documentation and a chat with Michael Dailly from YoYoGames provided the answer.

Unlike instance_create(), which creates an instance, instance_change() command doesn’t return a reference to the object created. That means that while:

with instance_create(BigWeaponChest)

will allow you to both create and modify a new BigWeaponChest,

with instance_change(BigWeaponChest)

should technically give an error and fail.

Instead, instance_change() always returns a value of zero. So, instead of what we thought the code did, this is what it actually does:

With the current WeaponChest,
{
   Roll a four-sided dice, and if the number is less than the amount of times players did not pick up a chest,
   {
      Uncurse this WeaponChest,
      Change it into a BigWeaponChest, deleting this chest directly instead of normally,
         Run the 'creation' event on the object that is stored in Object ID 0,
      That's all!
   }
}

Object ID 0 is the first object that is created in the game. In our case, as mentioned before, that’s our ‘main controller’. The BigWeaponChest reset all the values to the base values because we misunderstood the subtle difference between instance_create(), which we usually use, and instance_change(). Instead of creating a Big Weapon Chest, we were creating a new ‘main controller’!

The solution was relatively simple: just remove the word ‘with’.

with WeaponChest
{
   if random(4) < GameCont.nochest
   {
      curse = 0
      instance_change(BigWeaponChest,false)
         event_perform(ev_create,0)
      exit;
   }
}

which translates to

With the current WeaponChest,
{
   Roll a four-sided dice, and if the number is less than the amount of times players did not pick up a chest,
   {
      Uncurse this WeaponChest,
      Change it into a BigWeaponChest, deleting this chest directly instead of normally,
         Run the 'creation' event the current WeaponChest (which now happens to be a BigWeaponChest)
      That's all!
   }
}

Either way, this entire bug stemmed from a misunderstanding of how instance_change() works. We assumed it’d work the same way as instance_create() does, and it doesn’t. Well, there goes Wednesday – maybe I’ll try my hand at one of those Daily Runs myself.