📜 ⬆️ ⬇️

Dirty game tricks



[When the schedule draws in and the project is already time to release, programmers can resort to dirty tricks to finally push the game out the door. This article contains nine examples of such "crutches" from real life.]

Usually programmers are methodical and neat creatures, striving with all their might for a clean and beautiful code. But when the stakes are high, the ideal schedule falls apart, and it's time to release the game, the principle of “finish at any cost” may be more important than elegance.
')
In such cases, a harassed and reprocessing programmer will most likely ignore the optimal approach, replacing it with a less acceptable solution just to end the game. We collected nine stories of real developers about those moments when they could not meet the schedule and they had to resort to tricks to save the project.

Take me in the best perspective


About four years ago [ca. Lane: Article written in 2009] I worked as a programmer in a multi-platform project for the PlayStation 2, Xbox and GameCube. Terms of development came to an end, it is not surprising that hacks began to penetrate into the code. In the PS2 version, there was a lately discovered problem that was very difficult to track down: during a long test of stability of the first level with a character standing motionless, the game “crashed”. Unfortunately, this error occurred only in a disk assembly for retail sales, in which there was no debugging information. Fearing refusal to publish from Sony’s technical compliance check (TRC) department, we worked hard on the solution.

To narrow the boundaries of the error, we constantly rendered the border around the edge of the screen with separate colors for different parts of the code. For example, the blue indicated the render setting, green indicated the player control update. Since the crashes did not always occur, the lead engineer and I manually burned through a bunch of disks and ran them on a variety of PS2 consoles. (It should be noted that the company was an independent developer, it did not have an IT department and a cool recording equipment.) The test cycle — changing the code, writing disks, starting and waiting to narrow the approximate boundaries of the error — took long hours. This was the last stage of development, and we could only perform a strictly limited number of such cycles.



Spending the weekend in the office waiting for a crash, we were killing time for World of Warcraft . Suddenly, one of us noticed that the game does not “fall out” if you turn the camera 90 degrees to the right. At first, programmers rightfully dismissed it as an accident, hiding a deeper error. Correcting this behavior would not eliminate the root of the problem. However, we still fixed it! When we ran out of time, we created the last PS2 disc with the camera turned at the beginning of the level (the versions for the two other platforms were already ready) and sent it for testing. The game passed all the tests of the owners of the platforms and was promptly released to the market.

I would not call this solution a “code trick,” but it was definitely “dirty.” The mystique of this fix has not yet been disclosed, but fortunately, I have never heard that users complain about such a problem.

- Mark Cooke

Self-determination crisis


This situation is familiar to all game developers: today we send our game for Xbox 1 to “gold”. The team tests the game all day, wanting to make sure everything is in order. All are joyful and calm, for us the work is already finished.

After lunch, we create the latest build with some of the latest game balance settings and start the last testing session, after which a catastrophe happens: the game stubbornly crashes! We all run to our working machines, run the debugger and try to figure out what is happening. This is not something trivial, like assert, and not even something that is relatively difficult to track, such as dividing by zero. It seems that there is “garbage” in several fragments of memory, but a memory check reports that everything is in order. What's happening?

Many hours later, our dreams of releasing on time are shattered. We try to find the error and find that one data file is loaded with incorrect data. Wrong data? How is this possible? Our resource system manages each resource with a 64-bit identifier, consisting of the CRC32 full file name and CRC32 of the entire file contents. In the same way, we merged the same game resource files into one. We had tens of thousands of files and two years of development, while conflicts never occurred. Never.

Up to this very moment.

It turned out that one of the innocent small fixes that the designers checked after dinner, led to the fact that the text file had the same name and CRC as the other resource file, despite the fact that they were completely different!

When we discovered the problem, our heart went into our heels. There was no chance to change the resource indexing system in such a short period of time. Even if we had stayed to work all night, we could not have known if everything would be stable in the morning.

Just as quickly as despair gripped us, there was an awareness of how we can correct the error in time and be in time for release to gold. We opened a text file that was to blame for the conflict, added a space at the end and saved it. They looked at each other with broad smiles on their faces and said:

"Sent!"

- Noel Llopis (Noel Llopis)

Drunk driving


I was given the task to work on a transport management system, which was an important part of our game. Fortunately, we could take most of the code from another studio. Unfortunately, the code did not always seem perfect, as is the case with almost all code that you don’t write yourself. I discovered this nice piece of code that gets a variable from the engine's loop, but it does it in the most blunt way possible (see Listing 1).

//************************************************** // Function: AGameVehicle::Debug_GetFrameCount // //!       ;  . //! //! \   . //************************************************** UINT AGameVehicle::Debug_GetFrameCount() { BYTE* pEngineLoop = (BYTE*)(&GEngineLoop); pEngineLoop += sizeof( Array<FLOAT> ) + sizeof( DOUBLE ); INT iFrameCount = *((INT*)pEngineLoop); return iFrameCount; 

The funny thing about this piece of incredibly disgusting code is that the object had a simple function for getting the number of frames. But even if it were not there, the person who wrote this code could easily add it on their own! Needless to say, when I indicated an error, this code was not used in my game or in the one from which it was taken. And if this is not an example of why it is worth doing code analysis, then I really do not know what other examples are needed.

- Austin McGee

Decimal code


Working at [company X], I thought that we would never get to the end of [the project]. At one of the levels we had an object that was supposed to be hidden. We did not want to re-export the level and we did not use names with checksums. Therefore, right in the middle of the engine code, we had something like this. And the game came out with this code.

 if( level == 10 && object == 56 ) { HideObject(); 

Somewhere a year later a very upset artist approached us, using our engine, and asked why at his level, the tenth, the object was not displayed. Why indeed?

- Anonymous

All signs say no no


This problem arose when developing a new Wolfenstein Raven Software. I wrote the controller support setting for the Xbox 360. It turned out that when integrating with Live, you need to know which controller is sending input events. The Doom 3 input code we used was almost completely copied from Quake 3 , so it was a fairly simple system.

In the existing event system, each event was passed with two integer arguments and a null pointer in case additional parameters are needed. Therefore, the task was to associate the controller ID with each input event. It seems to be no problem - we just pack the controller ID into one of the integer event arguments, right? And of course, both arguments were already taken.

Therefore, another idea came to us: we simply use our fast frameless memory allocator without fragmentation to allocate memory, write the controller identifier into it, and then pass a pointer to it in the event pointer.

It turned out that the event system after event handling clears itself with the help of free () a zero event pointer. Of course, this behavior was completely incompatible with our multiheap approach. Moreover, the code contained old Doom 3 code fragments that relied on the free () call in the event code, so we could not just remove the call without making non-trivial changes to the entire code base. And if you add a third integer parameter? Then you have to change several hundred function calls.

Deadline was already on the nose, and I did not have time to solve the problem. Therefore, I decided on the unthinkable - I packed the controller ID in the pointer parameter. In the four-line comments, fully typed "caps", I marked it as a terrible hack, and loaded into the code base. And this crutch worked without problems until the entire input system was replaced with something a little better.

- David Dynerman

Sticky crooks


image

Here is the story of the project from the early days of PS2. We had a lot of problems with collisions / borders, which were usually resolved at the last minute by rewriting the character collision system. It was changed to a “collider” model - a set of spheres that handled collisions much better than our hierarchical tree of oriented boundary contours (these were times before the appearance of the Havok engine).

However, we constantly had a rare "bug" that drove crazy. We called it “stickiness” - every time a player’s character was near the wall and slid along it, the sphere of the collider suddenly decided that he was on the other side of the wall and did not allow it to detach from it (that is, the character stuck to the surface).

It was one of those damned errors "everything seems to be simple." You just need to find out why the sphere believes that it is on the other side, and fix it. The problem is that you need to keep track of what happens in all cases of collision detection before this problem occurs.

When we were unable to solve the problem with convenient conditional checkpoints in the code that tracked strange responses, or a change in position that confused the collider, we simply reduced the frame rate so that the “bug” simply did not occur. (A real solution to this problem would require blood, sweat, tears and other fluids, without which you can usually do without, but that’s another story.)

We sent the game for testing and constantly received an answer that the game with such an error would not be released. They even came up with one “solution” - to enlarge the scope of the sphere so as to guarantee the absence of problems, but this cannot be called a correct correction. We redirected the game with this “bug” to the team of artists so that they would correct it, and earn themselves precious time to find the true, deep problem.

Testers began to send us feedback like "the player faces an invisible wall, this should not be." This problem is of the same level of seriousness (we again narrowed the border of the collider and the character came close to the wall because “stickiness” occurred very rarely and it was difficult to reproduce it), but after a couple of such cycles, for which we fixed the rest of the game, the solution , requiring blood, sweat and tears, was finally found, and the game continued on its way to the circulation and to the store shelves.

I will not say that I am proud of this cycle of solving problems by avoiding and twisting, but at least we managed to temporarily remove this burden from our shoulders. We always said “oh, no, we already sent the game for testing yesterday,” and continued to solve the damn problem.

- Anonymous

Me and my patches


There is an old joke, like this:

Patient: "Doctor, it hurts me when I do like this."

Doctor: "Then don't do that."

It's funny, but isn't it a wise word in a certain situation? Imagine my pain when I worked on porting a third-person third-person shooter from a PC to the first PlayStation.

Let's start with the fact that PS1 did not have support for floating point numbers, so the porting was done by recompiling the code, replacing all floating point values ​​with fixed point values. And everything worked out pretty well, until it came to recognizing collisions.

In the PC version of the game, the geometry of the levels worked well, but when converted to fixed-point values, microscopic differences between the floating point and the fixed point resulted in all seams, T-shaped connections and other problems. This difficulty made itself felt: the main character (by the name of Damp) simply fell through these tiny holes into the emptiness below the level.

We fixed all the holes found by adjusting the geometry so that Damp no longer collapsed. But when the game was sent to the publisher for testing, he suddenly informed us about a variety of “falling through the world” errors. Every day there was a new list of places through which holes Damp could fall through. We eliminated them by correcting the geometry, and the next day we were informed about a dozen other places. This went on for several days. The publisher’s testing department hired one worker whose only task was to jump around the world ten hours a day to find places to fall into.

The problem was that the geometry was bad. It was not dense and seamless. It was enough for the PC, but not for the PS1, where the math with a fixed comma greatly complicated the problem. The ideal solution would be the correction of geometry, making it seamless.

However, this is a time-consuming task that could not be completed on time with our limited resources, so we relied on the testing department to look for problem areas for us.

The problem in this case was that they constantly found such places. Every day brought even more pain. Every day - new variations of the old "bug". It seemed that it would never end.

Finally, it dawned on us: the true problem is not in the holes of geometry. The problem is that Damp falls into these holes. Having understood this, I was able to write a very fast and simple fix, which looked like this:

 IF (Damp   ()) THEN   

In fact, the code was a bit more complicated (see Listing 2).

Listing 2: me and my patches

 damp_old = damp_loc; move_damp(); if (NoCollision()) { damp_loc = damp_old; 

Thousands of mistakes were fixed in one fell swoop. Now, instead of falling through the level, passing over the holes, Damp just twitched slightly. We found out the cause of our suffering and stopped "doing so." The publisher fired his jumper tester and the game was released.

That is, released after some time. Inspired by the success of the “if A == bad then NOT A” approach, I used this tool to patch a few more errors. Almost all of them were associated with collision recognition code. Toward the end of the development, the errors became more and more specific, and the corrections were more like “do not do exactly_this_action” (see Listing 3: this is the real code left in the game).

Code Listing 3: me and my patches

 if (damp_aliencoll != old_aliencoll && strcmpi("X4DOOR",damp_aliencoll->enemy->ename)==0 && StartArena == 6 && damp_loc.y<13370) { damp_loc.y = damp_old.y; //    damp'   . ( x  y) damp_loc.x = damp_old.x; damp_aliencoll = NULL; //     

What does this code do? The problem arose when Damp dealt with a certain type of door at a certain level and in a certain place, and instead of correcting the root cause, I made sure that when I touched the door, Damp moved away from it and pretended that nothing had happened. Problem solved.

Looking back in time, I find this code rather awesome. He patched bugs, not corrected them. Unfortunately, the real solution would be the alteration of the geometry of the entire game and the collision recognition system, taking into account the peculiarities of the PS1 calculations. From the very beginning, the schedule was very aggressive, so it always seemed to us that the end was near. Naturally, in such a situation, a quick patch always had an advantage over a complex and costly solution to problems.

But everything did not go so smoothly. It required hundreds of patches, then the patches themselves became the causes of the problems, and we had to add more patches to change the behavior of the patches in very specific cases. New bugs appeared, and I again defeated them with patches. In the end I won, but the price was delaying the release of the game for several months and my daily 14-hour work all these months.

This experience set me against “patches.” Now I always strive to get to the root of the error even with a simple and seemingly safe patch. I want my code to be healthy. When you go to the doctor and say “it hurts me when I do it like this,” then you expect him to find out the cause of the pain and cure it. Pain, like mistakes in code, can be a symptom of something much more serious. Moral: treat your code the way a doctor should treat you.

- Mick West

I'm terrible in anger


I once worked at THQ Relic Entertainment's studio on The Outfit , which you can remember as one of the first games for the Xbox 360. We started with a PC (single-threaded) engine and wanted to turn it into a complete game for a new-generation multi-core console in about 18 months . Approximately three months before the release of the game, she worked at 360 at a speed of about 5 FPS. It was obvious that the game needed serious optimization.

Having measured the performance, I realized that in addition to the slowness and “PC-style” of the code, there are also a lot of problems with content. Some models were too detailed, shaders too expensive, and at some levels too many characters.

It is difficult to convince a team of a hundred people that programmers cannot simply “fix” the performance of the engine, and that it is necessary to change the work patterns to which many are accustomed. They needed to realize that the performance of the game is a problem for everyone, and I came up with the best way to show it with a joke, in which there was some truth.

The decision took about an hour. A colleague-programmer made four photos of my face: I am happy, calm, a little angry, tearing my hair out. I put the photo in the corner of the screen and tied it to the frame rate. When the game was over 30fps, I was happy, when below 20, I was angry.

After these changes, the approach to the FPS problem completely changed: instead of “Oh, yes, this is a problem for programmers” “Hmm, if I use this model, Nick will get angry! Better to optimize it a little. ” People constantly saw how the changes they make affect the frame rate, and as a result we released the game with 30fps.

- Nick Waanders

Antihero programming


I just graduated from college, I was young and naive. We were just going to the beta stage of my first professional project - PC games of the late 90s. It was an exciting rollercoaster ride, as often happens with projects. All the content was ready, and the game looked good. However, we found one problem: it was not possible to fit in the amount of available memory.

Since most of the memory was occupied by models and textures, the artists and I sought to reduce the volume they occupied as much as possible. We zoomed out images, simplified models and compressed textures. Sometimes artists helped us, sometimes they opposed them with all their might.

We cut megabytes by megabytes, and after a few days of insane tension, we reached a point where it was impossible to do anything. Although we have cut some of the important content, we could not free up more memory. Exhausted, we measured the current amount of used memory. He was one and a half megabytes more than the maximum permissible!

At this moment, one of the most experienced programmers of our team who survived many years of development experience in the “good old days” decided to take the decision in their own hands. He called me to his office and we began work, which I initially presented as another exhausting marathon to free up memory.

Instead, he opened the source file and showed me this line:

 static char buffer[1024*1024*2]; 

“See?” He asked me. And then he deleted it with one click. Done!

Probably, he saw the horror in my eyes, so he explained that he himself allocated these two megabytes of memory at the very beginning of development. From his own experience, he knew that it was almost never possible to keep within the right amount of memory, and that many projects failed because of this. Therefore, he always allocates a decent block of memory to free it if necessary.

He left the office and announced that he had reduced the amount of memory to what was needed, and everyone honored him as the hero of the project.

No matter how shocked I was then with such a “barbaric” approach, I must admit that I now fully support him. While I have not reached such a way of thinking in which I will be able to use it, but I see that, once in a difficult situation, it is never too bad to have some memory left for a rainy day. I wonder how time and experience change our views.

- Noel Llopis (Noel Llopis)

Source: https://habr.com/ru/post/328552/


All Articles