This page is a mirror of Tepples' nesdev forum mirror (URL TBD).
Last updated on Oct-18-2019 Download

To clear or not to clear (2011-10)


by on (#84589)
Note that those games does not clear the memory so this technique works.

One thing doesn't exclude the other though... they could just check for the signature bytes before clearing the memory.

I don't know if many commercial games clear the whole memory on start up, but I still think that just makes eventual bugs harder to detect. Most games are composed of "modules" that run one after the other (title screen, menus, main gameplay, bonus gameplay, etc), and each of those requires some cleaning up and initialization. By clearing the whole memory on reset you are just cutting some slack for the first few modules that run, because even if they forget to initialize some variables they might still work. However, if you run these same modules later in the game they might not function correctly.

So I'd just rather make sure that each module cleans everything up for itself, to make sure it will work well no matter when it runs. If they are defective in some way, I'll probably notice it right the first time they run and fix the problem right away.

by on (#84593)
tokumaru wrote:
I don't know if many commercial games clear the whole memory on start up, but I still think that just makes eventual bugs harder to detect. Most games are composed of "modules" that run one after the other (title screen, menus, main gameplay, bonus gameplay, etc), and each of those requires some cleaning up and initialization. By clearing the whole memory on reset you are just cutting some slack for the first few modules that run, because even if they forget to initialize some variables they might still work. However, if you run these same modules later in the game they might not function correctly.

So I'd just rather make sure that each module cleans everything up for itself, to make sure it will work well no matter when it runs. If they are defective in some way, I'll probably notice it right the first time they run and fix the problem right away.

I disagree. It might be OK to not clear the entire memory for the debug version of the game (in fact, I added diagnostic messages to NintendulatorDX when uninitialized RAM is read to detect these type of things), but for the release version it's better to clear it, so that if/when there are bugs, at least it will "bug" consistently.

by on (#84602)
thefox wrote:
at least it will "bug" consistently.

But that's exactly the part I disagree with... It's not consistent, because depending on the order things happen in your program, the same code can "bug" or not.

For example, say that in my game I reuse some RAM for the normal gameplay and for bonus levels, since they don't run at the same time. If I clear the memory and start the first level, the level might play well even if it doesn't initialize a certain byte. But then comes the bonus stage, and changes that specific byte to something else. When the second level starts, it might glitch up because of that different byte. And that might be hard to catch, because at the start of development we hardly go through the normal progression of the game, so you typically won't run a normal level after a bonus level. This is a bug you'll only see late in development, which will make it harder to locate because you programmed that part long ago.

About the diagnostic messages, they are certainly useful if the buggy code is the first to use the problematic RAM locations, but I imagine that in a lot of cases the buggy code could run after some other part of the program already modified the RAM, leaving it in a semi-random state.

I don't think there's a perfect solution for this, it's just my personal opinion that clearing RAM makes certain bugs harder to catch. That's just the way I work, and I'm OK if others work differently. What bothers me is when certain things are presented as "truths", instead of "options".

by on (#84604)
tokumaru wrote:
For example, say that in my game I reuse some RAM for the normal gameplay and for bonus levels, since they don't run at the same time.

In other words, you divide BSS into a "main" portion shared among modules and an "overlay" portion per module. One might make sure the entire "overlay" portion is set to something predictable when switching between "modules". That's sort of how calloc() works in C (and how malloc() works in hardened implementations of the standard library): it clears all dynamically allocated memory.

tokumaru wrote:
at the start of development we hardly go through the normal progression of the game

That might be another place where people work differently. Some people do various kinds of integration testing earlier than others.

tokumaru wrote:
I imagine that in a lot of cases the buggy code could run after some other part of the program already modified the RAM, leaving it in a semi-random state.

You said "semi-random" which gave me an idea: fill the entire overlay segment with CRC16 pseudorandom numbers (66 cycles per byte cleared) before starting each module in debug builds.

by on (#84611)
tepples wrote:
In other words, you divide BSS into a "main" portion shared among modules and an "overlay" portion per module.

Exactly! When you have only 2KB of RAM to work with, sometimes you have to max it out by using portions of it for more than one purpose. Although in my case it's mostly the "main engine" vs. "everything else"... =)

Quote:
That might be another place where people work differently. Some people do various kinds of integration testing earlier than others.

Fair enough. I usually have my program jump right into the part I'm testing though. I would hate to go through title screens and menus every time I assembled/compiled the game to test something related to the gameplay.

Quote:
You said "semi-random" which gave me an idea: fill the entire overlay segment with CRC16 pseudorandom numbers (66 cycles per byte cleared) before starting each module in debug builds.

I have used the PRNG of the game to fill the memory before, but there must be a better way to do this. I guess I could write some code to fill the overlay area with random values, but there's one thing that bugs me: Ideally, the values would be different every time the game is assembled, in order to avoid "lucky" values, but once bugs are found, I'd like to generate the same values that caused them, in order to properly debug the issue. I'm not sure what to do about that... Manually change the seed from time to time? With the CRC the values would change once I attempted to fix the bug...

EDIT: I guess I could use the CRC as the seed so that each build would generate different fill values, and if a bug is found I'd manually force that CRC as the seed until the bug is fixed.

by on (#84612)
I myself write my code to not care about start up and test each code so even if it's not initiated, will still work. Although I still clear everything to 0 on start up in the 2KB of RAM inside the console. Although some stuff you of course need to be initiated to something, like controller values to run a move program for some reason. Or the position of something. IMO not clearing RAM is fine on boot up, just don't write your code to expect it to be either, never guess anything. If your RAM isn't initialized, you'll have a harder time to see if there's overflows in some values though during development if you have something corrupting something 1 byte away because a loop runs one too many times.

by on (#84621)
By "CRC" I meant use the CRC itself as your PRNG. Set it up with a fixed seed that can be changed from day to day, but then feed zeroes into the loop and use the low byte as your PRNG.

3gengames wrote:
IMO not clearing RAM is fine on boot up, just don't write your code to expect it to be either, never guess anything.

But how do you know you're not expecting predictable values by accident?

3gengames wrote:
If your RAM isn't initialized, you'll have a harder time to see if there's overflows in some values though during development if you have something corrupting something 1 byte away because a loop runs one too many times.

Buffer overflows like that are one reason that I wrote shuffle. (The other was to detect leaked betas.) If you're shuffling the order of your big arrays in VRAM, and the misbehavior changes after you change the shuffle seed, you know it's a buffer overflow.

Split because this clearing vs. not clearing "holy war" had taken over the original topic about MMC1 setup.

by on (#84626)
tokumaru wrote:
thefox wrote:
at least it will "bug" consistently.

But that's exactly the part I disagree with... It's not consistent, because depending on the order things happen in your program, the same code can "bug" or not.

Surely it's more consistent though than depending on a semi-random value from power-on/reset?

Quote:
I don't think there's a perfect solution for this, it's just my personal opinion that clearing RAM makes certain bugs harder to catch.

I agree there's really no perfect solution, but personally I feel clearing is the lesser of two evils. For a perfect solution we would need a higher level language, something that has a way of actually defining modules, forces the programmer to initialize the module variables, etc.

tepples wrote:
You said "semi-random" which gave me an idea: fill the entire overlay segment with CRC16 pseudorandom numbers (66 cycles per byte cleared) before starting each module in debug builds.

Or just fill it with DEADBEEF or something, that ought to catch most of the bugs, especially if the release build clears to 0 instead (and thus usually behaves differently from the debug version in case there's a bug).

by on (#84680)
I ultimately agree with the assessment that both should be considered as options rather than right or wrong.

In testing, I actually find a purpose for clearing RAM. I'm looking through this huge list of variables in the RAM viewer on FCE, and for one specific byte. I can easily distinguish it when I see a bunch of zeroes surrounding a chunk of non-zero values. The (mostly) non-zero values are the RAM I'm writing to when the zero values are unused.

I would just say that before you say that you're done with your initialization routines, really go over them. I would even go to the point of noting all the ranges of RAM a particular "module" uses, and using breakpoints or something in FCEUXD to make sure that every byte in that range is initialized appropriately (can you do this with CDL files?).