Life Bar made of sprites demo

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
Life Bar made of sprites demo
by on (#103810)
This demo is pretty simple, you can simply select your HP and maximum HP, and a lifebar is drawn with sprites, which is a good alternative to status bars in platformers (makes scrolling more convenient).

The particularity is that it's coded entirely in C, thanks to Shiru's devkit. This would not have been possible without it, so all thanks goes to him.
Re: Life Bar made of sprites demo
by on (#103852)
Is this .7z not working for anyone else? Give me an operation failed when I try to extract it.
Re: Life Bar made of sprites demo
by on (#103853)
Worked for me with Linux Mint 14's default decompressing tool
Re: Life Bar made of sprites demo
by on (#103854)
Doesn't work here. Something weird with the file. :(
Re: Life Bar made of sprites demo
by on (#103855)
The 7z command-line utility claims it's compressed with "LZMA2" instead of "LZMA"
Re: Life Bar made of sprites demo
by on (#103860)
It works with Winrar for me.
Anyway, very nice demo. It's really smooth. :D
Re: Life Bar made of sprites demo
by on (#103867)
About the "LZMA2" issue: Just a guess, but have you tried upgrading to newer 7-Zip?
Re: Life Bar made of sprites demo
by on (#103872)
Maybe we could "downgrade" to classic .zip so that basically everyone can unpack it? It might get a few K bigger, which would make downloading take a whole fraction of a second longer to do.
Re: Life Bar made of sprites demo
by on (#103875)
Yeah, for the size of files that we usually deal with on this board, the .7z format is most useful only for cases where solid archives are a big win. This usually involves archives of several nearly identical files, such as NTSC and PAL versions of a single ROM, or a generic ROM along with specially numbered ROMs for those who won, placed, or showed in a contest.
Re: Life Bar made of sprites demo
by on (#103935)
Sorry to burst your bubble, but there seems to be an issue. The first 4 points seem to be off somehow. To see, set the total (right side) to 520 and then count your current (left side) up from 0-5. I think the issue is from numbers 512 and up. However, I couldn't pinpoint it due to another bug. If your current amount is at 000 and your total is 511, then you try to move the total up to 512, it freezes the ROM. Lol, anyone need a playtester?
Re: Life Bar made of sprites demo
by on (#103946)
blargg wrote:
Maybe we could "downgrade" to classic .zip so that basically everyone can unpack it? It might get a few K bigger, which would make downloading take a whole fraction of a second longer to do.

Also emulators usually can open ROMs in ZIP files but not 7Z files, so if it was in a ZIP you wouldn't even need to extract it, but these days making a 7Z file is easier than making a ZIP file (even if just due to UI convenience) not to mention it compresses better.

Of course, for a NES ROM there's the question about whether it's even needed to compress it...
Re: Life Bar made of sprites demo
by on (#103947)
@sik : I didn't compress just the ROM, but also the sources, with all my project where I haven't lost them yet.

@Heavy : No problem, you don't have to be sorry. Yes I also had random crashes before I don't know if it is a bug in my code or in the compilation. I'd have to see where in the code it hangs to figure it out.
Apparently, it takes up to 40% of the CPU time just to display the status bar. Of course in an actual game this would never be acceptable, and yes, honesty I don't know how Shiru did to make fully playable games with CC65. Congratulations to him.
However once you've coded something in C you don't ever want to return to assembly ! So I don't know what to do now haha.
Re: Life Bar made of sprites demo
by on (#103948)
I guess that all these divide by 10 and modulo by 10 take up most of the CPU time. As you don't really need to calculate this every frame, only when the value has changed, it won't be much of problem in an actual game. May even add some style, as a slowdown while getting hit makes sort of feedback. See Battletoads and Double Dragons that freezes a bit when you hit someone, because it playing PCM samples.

C also could be used for prototyping purposes, as long as the code fits the ROM - you just create something that maybe doesn't work smoothly, then rewrite the most time consuming parts into assembly code, piece by piece.
Re: Life Bar made of sprites demo
by on (#103950)
Yes, you are totally right.
I could also write the "itoa" routine in assembly once I know the main engine is working.

And yes, prototyping in C then porting in assembly could sometimes be faster in development than doing everything in assembly, since you can know exactly how to write your code in assembly before actually doing it.
Re: Life Bar made of sprites demo
by on (#103951)
Checklist for reimplementing this in assembly language:
  • 16-bit by 16-bit multiplication (try 6502.org)
  • 16-bit by 16-bit division (try 6502.org)
  • Efficient binary to decimal conversion (try wiki.nesdev.com)

Bregalad wrote:
And yes, prototyping in C then porting in assembly could sometimes be faster in development than doing everything in assembly, since you can know exactly how to write your code in assembly before actually doing it.

Better yet, prototype in Python or ActionScript and then port to assembly. It worked for Streemerz, it worked for the CHR optimization tools in my NES graphics editor, and I hope it'll work for my current project.
Re: Life Bar made of sprites demo
by on (#103955)
Porting from Python or ActionScript is essentially writing a whole new program from scratch, in a very different language for a very different platform. It is very different from prototyping in C on the NES right away. Not to mention that you won't have to 'port' everything if you want, you may leave pieces of code in C for release version.
Re: Life Bar made of sprites demo
by on (#103961)
You can also use Lua support in NintendulatorDX to embed prototype code directly into your NES ROM (if you use CA65). I haven't used it personally just yet, but I remember wishing something like that had existed when I was working on STREEMERZ. Of course you have to know what you're doing to do that, since all Lua code executes in 0 cycles from emulators point of view. :)
Re: Life Bar made of sprites demo
by on (#103968)
Shiru wrote:
Porting from Python or ActionScript is essentially writing a whole new program from scratch, in a very different language for a very different platform. It is very different from prototyping in C on the NES right away. Not to mention that you won't have to 'port' everything if you want, you may leave pieces of code in C for release version.

I think the idea is to be able to test possible algorithms before settling on what to do for the real code.
Re: Life Bar made of sprites demo
by on (#103976)
Bregalad wrote:
Apparently, it takes up to 40% of the CPU time just to display the status bar. Of course in an actual game this would never be acceptable, and yes, honesty I don't know how Shiru did to make fully playable games with CC65. Congratulations to him.
However once you've coded something in C you don't ever want to return to assembly ! So I don't know what to do now haha.


It's easier to keep on top of performance issues is you are testing and checking it all the time. While working on my last project I toggled the PPU's greyscale bit when the code was finished executing so I could visually profile it. It's really easy to accidentally write something in CC65 that has horrible performance, so if you're watching all the time you will know which code it was when it happens. The most dramatic case for me was unrolling a single loop turning something that took 70% of the frame into about 5%. If you pay attention to what the compiler is doing, often times you can sufficiently optimize the C code without having to do rewrite the routine in assembly.

It saved me so much time to work in C. For me, the productivity bump vs assembly is massive, and it's really easy to write little parts in assembly as needed anyway.
Re: Life Bar made of sprites demo
by on (#103992)
Pehaps it's me but I think there is something seriously wrong with CC65's multiply and divide function. This is the only explication I can find for the 5/520 bug Heavy reported.

Doing it 3/512 makes things even worse.
Re: Life Bar made of sprites demo
by on (#103999)
Which line it is, with the division that you think does not work properly?
Re: Life Bar made of sprites demo
by on (#104063)
Line 78 :
Code:
        int npixels = (life_current * 8 * LIFEBAR_SIZE) / life_max;


The bugs are clearly caused by an incorrect value of npixels, but a correct behaviour of the code that follows this line.

I tried various types such as int, unsigned int, char and unsigned char, all of them apparently results in some kind of bugs.
Re: Life Bar made of sprites demo
by on (#104067)
Well, there is int (16 bit signed), and 512*8*8=32768. So life_current >512 certainly will not work properly.
Re: Life Bar made of sprites demo
by on (#104068)
Holy shit, overflow from 16-bit already ??
Oh well thanks for pointing this out Shiru. Unsigned int should work though. I shall fix this and update a fixed version.

EDIT :
I can't seem to fix this issue properly. It will work if I cast into long (32 bit) but this is a chep-suck-ass solution, considering the number never overflows from unsigned 16-bit. (<65535)

However whenever I type :
Code:
unsigned char npixels = (unsigned)(life_current * 8 * LIFEBAR_SIZE) / life_max;

Or even :
Code:
unsigned char npixels = ((unsigned)(life_current * 8 * LIFEBAR_SIZE)) / life_max;

Compiles the same as without any cast.

In other words, CC65's division routine can't put the MSB of an unsigned type to good use.

Now I don't know if this is some part of C standard or what, considering normally the only difference between signed and unsigned was supposedly when comparisons were made, but now that I thought about it when doing divisions it has a whole different meaning too.

PS : Now that I think the bugs occured not for life_current > 512 but for life_max > 512.
Oh well what a headache. It's almost as bad as if I had to code it in assembly :(
Re: Life Bar made of sprites demo
by on (#104070)
What happens if you replace a lot of these literal 8 values with SCANLINES_PER_SPRITE, and then mark the constants as unsigned?
Code:
#define SCANLINES_PER_SPRITE 8U
#define LIFEBAR_SIZE 8U

If you're using the top half of int's range, you probably want to keep everything explicitly unsigned as opposed to relying on C type conversion rules that a lot of us haven't memorized.
Re: Life Bar made of sprites demo
by on (#104071)
I would't use just 'unsigned', who knows what a particular compiler uses there.

I also doubt that there is problems in math code in CC65, as they would surface way earlier, considering number of much larger projects done with it. In the past there were many cases when I started to think that I ran into a compiler bug (not CC65) only to realize later that it was my logic mistake.
Re: Life Bar made of sprites demo
by on (#104072)
Bregalad wrote:
Now I don't know if this is some part of C standard or what, considering normally the only difference between signed and unsigned was supposedly when comparisons were made, but now that I thought about it when doing divisions it has a whole different meaning too.


Signed vs unsigned makes a big difference whenever you use multi-byte types. Even on processors with multi-byte types there are separate instructions for signed/unsigned multiplication and division.
Re: Life Bar made of sprites demo
by on (#104103)
Bregalad wrote:
Now I don't know if this is some part of C standard or what, considering normally the only difference between signed and unsigned was supposedly when comparisons were made, but now that I thought about it when doing divisions it has a whole different meaning too.

There are a lot more of differences since it doesn't specify what encoding signed values use. You can only use arithmetic operators and you must guarantee it won't underflow or overflow, otherwise the result is undefined (though most processors are two's complement so operations tend to result the same most of the time, but it isn't the case for e.g. DSPs).

Also: are you sure int isn't 16-bit? We're talking about an 8-bit processor, after all =P
Re: Life Bar made of sprites demo
by on (#104107)
Int is 16-bit, therefore unsigned int should allow numbers up to 65535, and no bug should ever occur (999 * 8 * 8 = 63936 < 65535).
I'll try to use Us after the constant like tepples said when I'll get home.

Quote:
I would't use just 'unsigned', who knows what a particular compiler uses there.

It's part of C standard, int is implicit.
You can use 'auto', 'static', 'signed', 'unsigned', 'short' and 'long' alone which all are shortcuts for 'auto int', 'static int', ....
Re: Life Bar made of sprites demo
by on (#104112)
I seem to remember that newer C standards allow the implicit int only when the word before it is a type specifier (signed, unsigned, short, long), not a storage (static, auto, register, volatile).
Re: Life Bar made of sprites demo
by on (#104117)
tepples wrote:
I seem to remember that newer C standards allow the implicit int only when the word before it is a type specifier (signed, unsigned, short, long), not a storage (static, auto, register, volatile).


Yeah that was a C99 thing.
Re: Life Bar made of sprites demo
by on (#104120)
I tried the U method tepples mentioned, unfortunately this still does not fix the bug.
Casting to long seems to be the only method that works, and I'm pretty sure CC65 is to blame for this.

After all this bug does not occur when life is greater than 512 (which would cause an overflow of signed int, but not unsigned int), but occurs when life is really small and life max greater than 512.
I suspect this bug was not a signed/unsigned thing after all. I'm not even sure CC65 features signed multiply or divides in the first place.

I'll try to see to trigger this bug and go step-by-step in the divide function with FCEUX and see what happen, and if I can explain the bug somehow.
Re: Life Bar made of sprites demo
by on (#104126)
I wouldn't expect from a hobbyist compiler for an old CPU to strictly follow any standarts. There are many compilers that ignore various parts of standarts by one or other reason, for example... CC65 with volatile keyword, maybe? In fact I had problems with using just 'unsigned' in the past, not with CC65 though, so it is a possible problem.
Re: Life Bar made of sprites demo
by on (#104223)
Shiru wrote:
I wouldn't expect from a hobbyist compiler for an old CPU to strictly follow any standarts. There are many compilers that ignore various parts of standarts by one or other reason, for example... CC65 with volatile keyword, maybe? In fact I had problems with using just 'unsigned' in the past, not with CC65 though, so it is a possible problem.

Integer promotions in C can be suprising at times, and IIRC if one compares signed ints to unsigned ints they would be promoted to unsigned int, and then negative values appears to be greater to positive values thanks to 2 complement. So the moral is: be careful when mixing signed and unsigned values in an expression.
Re: Life Bar made of sprites demo
by on (#104232)
Bregalad wrote:
I tried the U method tepples mentioned, unfortunately this still does not fix the bug.
Casting to long seems to be the only method that works, and I'm pretty sure CC65 is to blame for this.

After all this bug does not occur when life is greater than 512 (which would cause an overflow of signed int, but not unsigned int), but occurs when life is really small and life max greater than 512.
I suspect this bug was not a signed/unsigned thing after all. I'm not even sure CC65 features signed multiply or divides in the first place.

I'll try to see to trigger this bug and go step-by-step in the divide function with FCEUX and see what happen, and if I can explain the bug somehow.

If I remember integer conversion rules correctly, it should perform a uint / uint division there, as a single unsigned int should force the computation to unsigned int.

For reference, here's the cc65 routine to perform 16x16 -> 16 division. Signed division is negated appropriately and then performed on the unsigned quantities (btw, $8000 is negated, too, which seems incorrect). These built-in routines can be found at libsrc/runtime/ btw.

Code:
;---------------------------------------------------------------------------
; 16by16 division. Divide sreg by ptr4. Result is in sreg, remainder in ptr1
; (see mult-div.s from "The Fridge").
; This is also the entry point for the signed division

udiv16:   lda   #0
    sta   ptr1+1
    ldy   #16
        ldx     ptr4+1
        beq     udiv16by8a

L0:   asl   sreg
    rol   sreg+1
    rol   a
    rol   ptr1+1

    pha
    cmp   ptr4
    lda   ptr1+1
    sbc   ptr4+1
    bcc   L1

    sta   ptr1+1
    pla
    sbc   ptr4
    pha
    inc   sreg

L1:   pla
    dey
    bne   L0
    sta   ptr1
    rts
Re: Life Bar made of sprites demo
by on (#104233)
There is a comment in the cc65 crt source that $8000 is improperly negated (in div.s), so that's a known issue I think (couldn't find it in external documentation though, just noticed it in the code).
Re: Life Bar made of sprites demo
by on (#104251)
Jarhmander wrote:
Shiru wrote:
I wouldn't expect from a hobbyist compiler for an old CPU to strictly follow any standarts. There are many compilers that ignore various parts of standarts by one or other reason, for example... CC65 with volatile keyword, maybe? In fact I had problems with using just 'unsigned' in the past, not with CC65 though, so it is a possible problem.

Integer promotions in C can be suprising at times, and IIRC if one compares signed ints to unsigned ints they would be promoted to unsigned int, and then negative values appears to be greater to positive values thanks to 2 complement. So the moral is: be careful when mixing signed and unsigned values in an expression.

Gets even worse:
Code:
uint8_t blah = 0x50;
uint8_t result = ~blah >> 4;

You'd think it'd store 0x0A? Except it stores 0xFA. Why? Because the ~ operator causes blah to be promoted to int =/
Re: Life Bar made of sprites demo
by on (#104300)
Sik wrote:
Gets even worse:
Code:
uint8_t blah = 0x50;
uint8_t result = ~blah >> 4;

You'd think it'd store 0x0A? Except it stores 0xFA. Why? Because the ~ operator causes blah to be promoted to int =/

This is confomant behavior. See 6.3.1.8 and 6.5.3.3 of ISO/IEC 9899:2011,
"The operand of the unary + or - operator shall have arithmetic type; of the ~ operator, integer type; of the ! operator, scalar type."
For that example specifially, see e.g. https://www.securecoding.cert.org/confl ... sion+rules

Edit: Maybe I misread the quote. It probably was meant to be an example for strange C conversion rules instead of errors in CC65 :)
Re: Life Bar made of sprites demo
by on (#104316)
Yeah, I was talking about how C can catch you off guard regarding implicit casting =P GCC does exactly what I said, and I don't think we can argue GCC is not complaint with that stuff.
Re: Life Bar made of sprites demo
by on (#104343)
Jsolo wrote:
Sik wrote:
Gets even worse:
Code:
uint8_t blah = 0x50;
uint8_t result = ~blah >> 4;

You'd think it'd store 0x0A? Except it stores 0xFA. Why? Because the ~ operator causes blah to be promoted to int =/

This is confomant behavior. See 6.3.1.8 and 6.5.3.3 of ISO/IEC 9899:2011,
"The operand of the unary + or - operator shall have arithmetic type; of the ~ operator, integer type; of the ! operator, scalar type."

The part you quoted doesn't actually talk about signedness, or integer promotion, it talks about the type of the operand of the ~ operator, which has to be an integer.

More meaningful quote would be this one:
Quote:
The result of the ~ operator is the bitwise complement of its (promoted) operand (that is,
each bit in the result is set if and only if the corresponding bit in the converted operand is
not set). The integer promotions are performed on the operand, and the result has the
promoted type. If the promoted type is an unsigned type, the expression ~E is equivalent
to the maximum value representable in that type minus E.

I.e. the operand, of type uint8_t, is promoted to int.

As for integer promotions:
Quote:
If an int can represent all values of the original type (as restricted by the width, for a
bit-field), the value is converted to an int; otherwise, it is converted to an unsigned
int. These are called the integer promotions.58) All other types are unchanged by the
integer promotions.
Re: Life Bar made of sprites demo
by on (#104347)
thefox wrote:
The part you quoted doesn't actually talk about signedness, or integer promotion, it talks about the type of the operand of the ~ operator, which has to be an integer.

You're right, I should have quoted the other part I referred to, the usual arithmetic conversions (and integer promotion rules, too!). The C Standard can be confusing at times, especially when you're half-asleep. :oops:

Concerning the original problem, it sounds awfully close to the following known bug in a 7 years old version of cc65,
Quote:
The subroutine that does divisions for longs overwrites the high byte of the divisor instead of the high byte of the result. This leads to errorneous results if the high byte of the divisor is not zero.

Your program hangs with max=256, too...your cc65 version is newer than 2.10, right? :roll:
Re: Life Bar made of sprites demo
by on (#104360)
Yes, Shiru's tutorials are with version 2.13.3