RSS Feed IDS on YouTube IDS on Facebook IDS on YouTube

The Super Jump Bug – Debugging and Frustration

posted on March 18, 2014 at 12:30 pm by Justin Dallal

Every developer seems to have that bug. You know, THAT bug. The elusive one that only seems to creep up during playtesting or while testing another feature? This isn’t specific to game development either. Web service developers or even operating system kernel developers have all seen, at least once in their careers, a bug that only seems to pop up when they aren’t looking for it. And it’s not just bad; it’s breaking. Debugging something you can’t figure out how to reproduce is an exercise in balancing sanity with sobriety.

With Minecart Madness, we had the Super Jump bug. We coined that name because the player would jump incredibly high for seemingly no reason. And it didn’t always happen. And we couldn’t figure out why. But it came up when we were debugging issues with track placement. It came up with we were debugging issues with obstacles on the track. It came up when we were figuring out how to handle tunneling. It kept happening. Why?

Filing a Good Bug

Super Jump Bug filing into IDS bug database

Super Jump Bug filing into our bug database

By profession, I’m a tester. My job is to break things and tell you why it’s broken. A really good bug filing should contain the following information:

  • First version where the bug was reproducible
  • Last version where the bug was not reproducible
  • Clear, concise reproduction steps with the minimum steps needed to cause bug
  • Any special machine settings (resolution dependence, high/low memory situation, specific phone, etc.)
  • Callstack if it’s a crash, but can also be useful if it’s not to show exactly where in code the bug is happening
  • Severity, i.e. how breaking of a bug it is
  • Extra manifestations, i.e. does this happen in all situations? For example, if your cross-platform Unity game is crashing on the iPhone, does it also crash in the same situation on Android?

With that in mind, let’s look back at this bug filing. Of the seven tenets listed above, this contains exactly zero. Each of these useful chunks of information rely on a single other event to occur: consistent reproduction. We couldn’t figure out how to make it happen. It just seemed to happen sometimes. Hence, the repro steps above.

What is “Breaking?”

As with all bugs, the Super Jump Bug had to go through a triage process where we decide if the bug is breaking enough to warrant fixing. These discussions usually involve expected amount of time to fix, expected degradation of the user experience, frequency of occurrences, and risk of fix. For the Super Jump Bug, we didn’t know the fix and we’d already attempted to reproduce it and fix it unsuccessfully. Through playtests, users would hit this bug as well.  When a user would hit the bug, he/she would notice immediately and ask what happened. We told those users that we were aware of it and not to worry. We were worried. The feeling of having to downplay a bug you know is breaking and can’t solve is crushing. With that in mind, we did not “Won’t Fix” this bug, if only to satisfy ourselves of knowing we solved it.

Procrastination as a Tool

Like most developers, we decided to just let this bug sit. And sit. And sit. Sometimes, as we’re all aware, one of two things can happen to bugs we let sit. First, the bug “magically” fixes itself. You change some specific values for core mechanics like jumping or movement, increase or decrease your net resolution, and suddenly, the bug isn’t there. It most likely is, but it is being masked or mitigated by your change. A bug that users can’t see, for an indie dev, is not a bug. We don’t have time to make things nice. We have to ship. The second thing that can happen while procrastinating is a revelation. A sudden moment of clarity. For a small moment in time, you become Neo. You can see the code and you realize the bug without ever thinking about it. That is how we solved the Super Jump Bug.

What is Jumping?

All mechanics in games need to feel like an extension to the players mind. Never has this been more true than in touch based mechanics. We touch the character and we want him to jump as we imagine in our minds, run as fast or slow as we picture, or target exactly where we point. Curiously, our minds don’t exactly imagine reality based physics. Not until high school physics classes do we realize that our assumptions about how things move are often wrong. But, this is a game, so we are expected to break with reality. Indeed, jumping should feel as the user would expect, not as it would be on Earth. Therefore, our jump algorithm doesn’t function as a NBA player leaping on the court does.

We break jumping into two parts. First, the initial hop. This happens with just a single touch and is the minimum jump height. This allows for targeted little jumps to cover the smallest of crevices. After that, for a specific amount of frames, we provide an additional upward velocity until either the user releases his/her finger from the screen or our acceptable amount of frames are exhausted. This provides the aforementioned feeling of the character on screen being an extension of the player’s finger, and therefore, mind. This creates a jumping experience that is fun and satisfying. More importantly, though, it makes the player feel liable for all successes and failures regarding hit and missed jumps.

What was the bug?

The code for the jump looked like this in revision 160:

velocity.Y -= _amountTouches == 1 ? HopHeight : fUpwardJumpAcceleration;

Revision 161, after the bug was fixed, contained the following instead:

velocity.Y = _amountTouches == 1 ? -HopHeight : _velocity.Y - fUpwardJumpAcceleration;

Of course, you see the changes, but do you see the bug? Hint: the bug is not on this line at all. To understand the bug, we need to know that, after landing, we reset _amountTouches to 0 in the Minecart’s update method. By doing this in the subsequent update, the Minecart is effectively running a frame behind on determining if it is in the air or on the ground. It goes to follow that a second HopHeight jump can occur.

Reproducing a Bug After Fixing It

Often, we determine a fix for a bug before the bug can be accurately reproduced. Only after conjuring a fix can we deduce a sequence of valid repro steps. For this bug, based on the fix in the earlier revision, the repro steps were as follows:

  1. Jump like any user would, but do not remove finger
  2. Land, keeping finger on screen
  3. Jump again by holding finger (we wait for touch events, never the absence)
  4. Repeat 3-4 times.

Step 3 is by design. We will never not let a user jump while on the ground. After 3-4 iterations of these steps, a large double jump would occur. Every. Time.

Learning and Moving Forward

The Super Jump Bug was a single bug with a single line fix affecting only certain users who happened to touch the screen for long enough in edge cases. However, it highlights that debugging may not be a scientific process. We can see that, because we are changing variables a full update behind, using the built-in debugger wouldn’t have gotten us closer. We tried. But, you can’t hold the screen AND hit break points in the emulator. In fact, reliance on the given tools was a hindrance to our progress for bug #107, the Super Jump Bug.

Brian Kernighan, one of the inventors of C, once wrote, “Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.” Intimate familiarity with each and every line of your code is paramount to shipping a quality product. Copy/paste is not a design pattern and will lead to bugs. We can confidently say that if we had borrowed another jump algorithm, this would have never gotten fixed. Code simply. Document thoroughly. Be patient. The product and your users will thank you.


No comments!
Add Comment Register

Leave a Reply

Your email address will not be published. Required fields are marked *

− 1 = seven

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>