it does use two's complement. even though it could be referred to as the "-128 bit," it's simpler to just refer to it as the sign bit, given that if it is 1, you know that the number is negative.@@kapsacek
They should, cause I can tell I understood nothing... Might be partially that I'm not a native English speaker and he talks pretty fast tho. Edit: Rewatched. I think I got it, somewhat xD
As I programmer, I once made a database manager similar to Excel. To account for any weird bugs, I set the cursor position to line 2,147,483,647. And let's say shit got crazy. But I was able to debug the bugs and fix them!
After reading about this on meatfighter, I wondered why the array is 4 wide. Is it just because 4 divides 8? The final color is literally never used, because NES sprites in fact have just three colors, not four. It's a little odd.
The palette is also used for the purposes of background tiles which do have 4 unique colors. When initially loading the game board all 4 colors are used, I believe.
in some cases tables are addressed by words (16 bits), not bytes, so table entries must have an even number of bytes in order for consecutive entries to be addressable individually. not sure if that's exactly what's going on here.
@@goddeath7454the NES is an 8-bit 6502 (ok, a knockoff 6502 without BCD mode) machine with no word alignment constraints. The reason the table is structured this was is a speed optimization - both because a multiply by 4 is about twice as fast as a multiply by 3 (since the 6502 has a left shift but lacks a general multiply instruction), and because it better matches how the palette is actually mapped in video memory, so it simplifies uploading. Also, storing arrays backwards is another somewhat common 6502 optimization, because counting down to zero instead of up to N saves a compare instruction in loops. Assuming that down represents increasing memory addresses in the illustration, it looks like they could have done that here, since I'd expect color 0 of each palette to be the same as it corresponds to the shared background color.
It's a lovely coincidence that the glitched colours start with the first palette outside of the table. * Although interestingly it's actually mathematically guaranteed that the first glitched palette is like this: (lvl - 10 >= 128) → ((lvl * 4) mod 256) / 4 ⇒ min. lvl = 128 + 10 → lvl mod 64 ⇒ first glitched colours at 128 + table_length and because 128 mod 64 = 0, first fake colours at index table_length
me reading through these comments as if I know exactly what all of them mean (i have never studied anything beyond scratch coding and kindergarten level html)
Programmer mistake. They used the branch operation BMI, branch on Minus, which presumes signed bytes. They should have used the branch operation BCC, branch on carry clear, which is unsigned.
@@hydrantdude3642 They probably didn't even care about levels so high that seemingly nobody would ever reach them. The game's success has surely surpassed their wildest dreams.
@@hydrantdude3642 if they use an unsigned byte does that mean they cannot use the same algorithm of seeing if the subtraction becomes negative to get the table column? Meaning the game code will be different. Wonder how the tetris kill screen would become if they were to use BCC
@@azka9075 There are no "unsigned bytes", we have a byte 00-FF and 80-FF causes BMI (branch on minus) to be true always. If you want the behavior of unsigned bytes you need to use other branch instructions like BCC (branch on carry clear).
The asterisk lost me. What's the "Table offsets" in this context? Does "138 multiplied by 4" mean the column is actually 138*4th, or somewhere around 34.5 as I don't think it has no more than integers? Where the number 64 comes from? I was following along till that part. Hope that part was explained better.
entries from the table are loaded by offsetting the address of the start of the table by a number. this number can contain values 0-255 as it is only one byte. each table entry takes up 4 addresses, so those 256 possible values mean 64 table entries worth of data, after which it returns to the beginning. so if you had to get the 5th entry, you would do 5 * 4 = offset of 20. if you had to get the 200th entry, you would do 200 * 4 = 800, but the offset has a maximum of 256, so 800 mod 256 = offset of 32, which is the same result as if you looked up the 8th, 72nd, or 136th entry.
I take that to mean that the table extends far beyond what is used ingame. In reality the table has 4 rows (4 colors per palette) and 64 columns (palettes). When the game searches for a color in a palette, it doesn't think of a color in terms of what row/column it's in. Ex, a green at second row & column 1 would be "the 66th color". For every row, it has to go through IDs 10 to 63 till it loops back around. So, when the game searches for a 138th color, it goes thru 2 rows (64+64= 128 entries) before it ends up at: third row & column 9. (138 mod 64 = 10, ie. when 138 is divided by 64 the remainder is 10.) This explains how we got in the 10th column, but I'm still not sure when 138x4 comes in...
@@anjoliebarrios8906 I drew the palettes in rows/columns for visual clarity but the data itself is just a continuous list, not an array. the first 4 entries of the list are the lvl 0 palette, next 4 are the lvl 1 palette, etc. the multiplying by 4 is to get the correct list index. you multiply by 4 using the command ASL, arithmetic shift left, twice. this shifts the bits of the currently loaded byte left, and drops whatever's at the top. therefore, the top two bits of the level number are lost and the value mod 64 is all that matters, hence there are only 64 possible palettes.
it's kinda poetic that the first glitch pallette happens to read exactly the pallette after the last regular one. like it goes 0,1,2,3,4,5,6,7,8,9,0,1,2,3,...repeats...,10
Yes. Code read as colors is: -A redundant piece of code for incrementing the row of the playfield being drawn -The entire routine to pick the next spawned piece -Part of a table of which orientation IDs are which piece.
@@dehydratedair code is just a sequence of bytes that can be read as anything you want. to read a byte as color, discard the top 2 bits, second two bits indicate brightness, bottom 4 bits indicate hue.
Pretty good job with your notes despite one error! I too think the carry flag could've been used instead because the code is dealing with unsigned bytes.
If the results are 128 or greater it glitches out because 128 or 128< its double its more than 255 which is a limit that appears in killscreens like Pac-Man,any level higher than 255 has the left of the maze glitchy
As the game was implemented in assembler code, it obviously did not have a simple "mod" function. Otherwise the color would have been determined by simply using a "level mod 10" expression which would always return a value between 0 and 9. However, does anyone know why the devs. did not use the common solution of bitmasking with the bitmask of decimal 10 (binary 1010)? Thus, simply ANDing the level-number with binary 1010 would have resulted in a value between 0 and 9, again achieving the same behavior as the mod function. So why did the devs not do that? Did the processor or the specific assembler language not allow that? Or was this operation still to expensive? I'd really would like to know. 🙂 (I am a senior dev. but I only write highlevel code. Since my student days over 10 years back I haven't touched either C or assembler and therefore I am very curious.)
masking down only works on powers of 2. any byte & 0x0F will return the low nybble of the byte. you can do the same with 0x07 to get bottom 3 bits, 0x03 bottom 2 bits, etc. as forchune said, doing that with decimal 10 or 0x0A does not work; masking returns every bit from the original number which is also a 1 in the masking number, which is the 2nd and 4th bit in the case of 0x0A. now, your approach would actually work if you used 'and 0x0F' on a number stored in BCD, because it would mask down to the low nybble, and BCD are numbers where each nybble is a decimal digit. so you could do this on the score or the lines, and the game actually does use exactly this method in several places. The level, however, is not a number stored in BCD, it's just in binary, so you can't use it here.
@@hydrantdude3642 Awesome - thanks for the explanation! I totally forgot about the encoding and assumed it would work just as with any decimal /radix-10. It's always impressive to look at all the issues which arise from such low-level implementations - especially when the computation power is so limited as back in the day (or with some even modern embedded systems).
I don't know about the instruction set of the processor, but what seems certain is that finding the new color palette for a level is not an operation you'd need to optimize. It would only happen every time you level up, so you can just use any algorithm and be done with it.
Calculating "mod 10" by continuously subtracting 10 is rather naive, but understandable considering that they assumed the max level reached will be 29. Still I'd call this lazy, I would have expected a company like Nintendo to have a library for such standard functions for the 6502 which they could just have pulled in. So I thought of a way how to properly implement this. This was my first idea: mod10: cmp #$a0 bcc nosuba0 sbc #$a0 nosuba0: cmp #$50 bcc nosub50 sbc #$50 nosub50: cmp #$28 bcc nosub28 sbc #$28 nosub28: cmp #$14 bcc nosub14 sbc #$14 nosub14: cmp #$0a bcc nosub0a sbc #$0a nosub0a: rts I.e. instead of 10, start with conditionally subtracting 160 then 80, 40, 20, and 10. But while easy and straightforward, I don't like all these conditions and branches. So I think this solution is better: mod10: tay and #$0f sta v0 tya lsr lsr lsr lsr lsr bcc no6 adc #2 no6: asl adc v0 sec minus10: sbc #10 bcs minus10 adc #10 rts Iow: Add the lower and upper nibble together, whereas the lowest bit of the upper nibble counts as a 6 (not 1). That should give us a number between 0 and 35 so we can calculate mod 10 with a maximum of 3 subtractions. But maybe we're lucky that they were lazy, since in the end we get these interesting glitches.
I mean the actual problem is that they used BMI instead of BCC, not that their routine was slow. updatePaletteForLevel: lda player1_levelNumber @mod10: cmp #$0A bmi @copyPalettes sec sbc #$0A jmp @mod10 if they just changed it to updatePaletteForLevel: lda player1_levelNumber @mod10: cmp #$0A bcc @copyPalettes sbc #$0A jmp @mod10 it would work perfectly fine (the sec was redundant given the result of the compare) Your first solution is efficient, at the cost of a longer program. Second solution is going a bit over my head but it sure looks clever. Could you explain the number theory of it a bit further? I'm not understanding how adding the nybbles together preserves the result mod 10.
@@hydrantdude3642 Sure, but if I'd make a more generic routine which is applicable for all kind of games, I rather would also fix the slowness. The second solution may look complicated, but it's a simple idea: 128/16=8, 64/16=4, 32/16=2, so mod 10 doesn't change when these 3 bits are divided by 16. The exception is 16/16 which is 1 but 16 mod 10 is 6, hence we need the adjustment. This also works for higher nibbles, i.e. 2^n always ends in 8 for n mod 4 = 3, always in 4 for n mod 4 = 2, always in 2 for n mod 4 = 1 and always in 6 when n mod 4 = 0 and n > 0. So this idea would be more worthwhile for bigger numbers where just subtracting 10 in a loop becomes prohibitive. E.g. for 16 bit we have to add 4 nibbles, which can be actually done with just 2 additions (i.e. adding the lower and upper byte and then the two resulting nibbles and carry). You can find a different but similar idea for 32 bit numbers at homepage.cs.uiowa.edu/~jones/bcd/mod.shtml#exmod10. This page also has some nice solutions for other modulo (like 7, which is used in NES Tetris for the tetrimino selection).
Before I forget: the solution on the above website for mod 7 may be nice for 32-bit numbers, but for 8 bit I find it not very practical (we only could skip the first 2 steps, so we still need 11 shifts and 3 additions in total). Luckily, I came up with this solution: mod7: tax and #$07 sta $60 txa lsr lsr lsr clc adc $60 ; worst case 7+31=38 tax and #$07 sta $60 txa lsr lsr lsr clc adc $60 ; worst case 6+4=10 cmp #7 bcc end sbc #7 end: rts Let me know if you know something better.
Question Without modifying the memory allocated by that variable, could it be a possible fix turning the type from "signed int" into "unsigned int" and change the check operation from "subtract 10 till negative" into "actual_level mod 10" and search the result in the table? If we use "mod" operations, negative numbers doesn't show up, and so we can ignore them This should work for every level... well technically at level 256 the colors will be selected from column 0 instead of column 6 and subsequent levels will carry away this problem (level 257 column 1, and so on...) BUT, at least, using this method will prevent any weird access outside of the 0-9 table, since the variable that is responsible for doing so can't have any value outside of 0-9 (since we are using mod 10 operation) What do you think about this? And plz consider I am new to programming, so if I made some kind of mistake, plz tell me
The thing is, the CPU of an NES doesn't have a division or modulo operation. You have to program it yourself with a subtraction loop as described here. Also, at the assembly level, there are no types, so you can't change the type from signed to unsigned. Since NES games were generally directly written in assembly (compilers weren't good enough back then), the programmer works without types. It's just raw memory; what it means is decided entirely by the instructions you use on them. This means that the key issue here is the way the loop was written. When the CPU executes a subtract operation, in addition to giving you the result it also sets some flags. The programmer checked the N flag ("negative"), which means the result has the highest bit set, i.e. if interpreted as a signed number, it is negative. He could have checked the C flag ("carry") instead, which means that the subtraction went below zero when interpreted as an unsigned operation. Then the code would have worked for any level number. However, since the code only *needs* to work for the levels the game designers actually thought reachable, using the N flag probably felt more natural when thinking about the code.
A byte can hold 256 different values, as it contains 8 bits, and 2^8 = 256. A common way to divide those values between positive and negative numbers is to use the top bit as the sign of the number, where 1 is negative and 0 is positive. This means that binary 10000000, which is decimal 128, is the first negative number. It is interpreted as -128. (I made an error in the video calling it -127)
a byte has 256 possible values. this is because it is made up of 8 bits. each bit has 2 possible states, 0 or 1. with 2 bits, you get 4 possible states - 00 01 10 11. with 3 bits you get 8, with 4, 16, etc. of these 256 possible states, if you want to read them as negative/positive, you have to divide those 256 between the positive and negative numbers. this is typically done through assigning the first 128 values to 0 through 127 and the last 128 values to -128 through -1.
I don't know about the language they used (Google says turbo pascal), but if it behaves like C, the modulus of a negative number is [bizarrely] negative. Even if it weren't, there would be a skip where the int rolled from 127 (equal to 7 mod 10), to -128 (equal to 2 mod 10).
As an architecture student I was taught standard drafting handwriting, which was used for architectural drawings before fonts were computerized - everyone had to be taught to write the same so that drawings looked consistent when many different people worked on them. My drafting teacher in high school showed it to me, have found it very enjoyable to practice writing in since then. If you for some reason do actually want a font like it, mr-hand is a pretty good one I've seen architecture firms use.
@@hydrantdude3642 "As an architecture student I was taught standard drafting handwriting" - I honestly didn't even know this was a thing! Good for y'all! God Speed to, Y'all!
if a signed 8 bit int meet a signed short the short what is 127+1 8 bit -128 the short no its 128 the 8 bit whats -128 -1 the short -129 the 8 bit no its 127 like for part 2
I think, 128 should be equal to -128 not -127, isn't it?
Correct; I got a bit confused. The sign of the byte is indicated by the top bit, meaning that positives only reach up to 01111111 or 127. Thank you!
Ah the old off-by-one errors. Tormenting computer scientists since Turing himself.
I got 37
Does NES use two's complement to store negatives?
it does use two's complement. even though it could be referred to as the "-128 bit," it's simpler to just refer to it as the sign bit, given that if it is 1, you know that the number is negative.@@kapsacek
We need to add nes Tetris code into the public school curriculum
They should, cause I can tell I understood nothing... Might be partially that I'm not a native English speaker and he talks pretty fast tho.
Edit: Rewatched. I think I got it, somewhat xD
🤣
YES;-)
Here after blue scuti crashed tetris! Way above my head, but we'll done!
This is one of the most instructive videos I’ve ever seen
Learn programming, it's fun!
I recommend Common Lisp, the best programming language in the world!
As I programmer, I once made a database manager similar to Excel. To account for any weird bugs, I set the cursor position to line 2,147,483,647. And let's say shit got crazy.
But I was able to debug the bugs and fix them!
Wow
cool and understandable explanation, thank you! also you have nice handwriting, i am jealous.
After reading about this on meatfighter, I wondered why the array is 4 wide. Is it just because 4 divides 8? The final color is literally never used, because NES sprites in fact have just three colors, not four. It's a little odd.
The palette is also used for the purposes of background tiles which do have 4 unique colors. When initially loading the game board all 4 colors are used, I believe.
in some cases tables are addressed by words (16 bits), not bytes, so table entries must have an even number of bytes in order for consecutive entries to be addressable individually. not sure if that's exactly what's going on here.
Because the NES CPU doesn't have a multiply instruction, but it does have bit shifts, so it's faster to multiply by 4 than by 3
@@goddeath7454the NES is an 8-bit 6502 (ok, a knockoff 6502 without BCD mode) machine with no word alignment constraints. The reason the table is structured this was is a speed optimization - both because a multiply by 4 is about twice as fast as a multiply by 3 (since the 6502 has a left shift but lacks a general multiply instruction), and because it better matches how the palette is actually mapped in video memory, so it simplifies uploading.
Also, storing arrays backwards is another somewhat common 6502 optimization, because counting down to zero instead of up to N saves a compare instruction in loops. Assuming that down represents increasing memory addresses in the illustration, it looks like they could have done that here, since I'd expect color 0 of each palette to be the same as it corresponds to the shared background color.
This is a weird way of explaining the concept of an overflow but it certainly makes enough sense for a
It's a lovely coincidence that the glitched colours start with the first palette outside of the table.
* Although interestingly it's actually mathematically guaranteed that the first glitched palette is like this:
(lvl - 10 >= 128) → ((lvl * 4) mod 256) / 4
⇒ min. lvl = 128 + 10 → lvl mod 64
⇒ first glitched colours at 128 + table_length and because 128 mod 64 = 0, first fake colours at index table_length
i see
me reading through these comments as if I know exactly what all of them mean (i have never studied anything beyond scratch coding and kindergarten level html)
My only question is why the hell is the level number a signed byte?
Programmer mistake. They used the branch operation BMI, branch on Minus, which presumes signed bytes. They should have used the branch operation BCC, branch on carry clear, which is unsigned.
@@hydrantdude3642 They probably didn't even care about levels so high that seemingly nobody would ever reach them. The game's success has surely surpassed their wildest dreams.
In assembler there's no such thing as signed or unsigned bytes, only signed or unsigned operators used on those bytes.
@@hydrantdude3642 if they use an unsigned byte does that mean they cannot use the same algorithm of seeing if the subtraction becomes negative to get the table column? Meaning the game code will be different. Wonder how the tetris kill screen would become if they were to use BCC
@@azka9075 There are no "unsigned bytes", we have a byte 00-FF and 80-FF causes BMI (branch on minus) to be true always. If you want the behavior of unsigned bytes you need to use other branch instructions like BCC (branch on carry clear).
Great video! Succinct and helpful, with good visual aid
The asterisk lost me. What's the "Table offsets" in this context? Does "138 multiplied by 4" mean the column is actually 138*4th, or somewhere around 34.5 as I don't think it has no more than integers? Where the number 64 comes from?
I was following along till that part. Hope that part was explained better.
entries from the table are loaded by offsetting the address of the start of the table by a number. this number can contain values 0-255 as it is only one byte. each table entry takes up 4 addresses, so those 256 possible values mean 64 table entries worth of data, after which it returns to the beginning. so if you had to get the 5th entry, you would do 5 * 4 = offset of 20. if you had to get the 200th entry, you would do 200 * 4 = 800, but the offset has a maximum of 256, so 800 mod 256 = offset of 32, which is the same result as if you looked up the 8th, 72nd, or 136th entry.
I take that to mean that the table extends far beyond what is used ingame. In reality the table has 4 rows (4 colors per palette) and 64 columns (palettes).
When the game searches for a color in a palette, it doesn't think of a color in terms of what row/column it's in. Ex, a green at second row & column 1 would be "the 66th color". For every row, it has to go through IDs 10 to 63 till it loops back around.
So, when the game searches for a 138th color, it goes thru 2 rows (64+64= 128 entries) before it ends up at: third row & column 9.
(138 mod 64 = 10, ie. when 138 is divided by 64 the remainder is 10.)
This explains how we got in the 10th column, but I'm still not sure when 138x4 comes in...
@@anjoliebarrios8906 I drew the palettes in rows/columns for visual clarity but the data itself is just a continuous list, not an array. the first 4 entries of the list are the lvl 0 palette, next 4 are the lvl 1 palette, etc. the multiplying by 4 is to get the correct list index.
you multiply by 4 using the command ASL, arithmetic shift left, twice. this shifts the bits of the currently loaded byte left, and drops whatever's at the top. therefore, the top two bits of the level number are lost and the value mod 64 is all that matters, hence there are only 64 possible palettes.
it's kinda poetic that the first glitch pallette happens to read exactly the pallette after the last regular one.
like it goes 0,1,2,3,4,5,6,7,8,9,0,1,2,3,...repeats...,10
*Int8 is a data type that represents an 8-bit signed integer, with a range of -128 to 127
Is it true that the colors are being chosen based on reading different parts of the games' code at that point?
Yes. Code read as colors is:
-A redundant piece of code for incrementing the row of the playfield being drawn
-The entire routine to pick the next spawned piece
-Part of a table of which orientation IDs are which piece.
@@hydrantdude3642how are those even readable as colors
@@dehydratedair code is just a sequence of bytes that can be read as anything you want. to read a byte as color, discard the top 2 bits, second two bits indicate brightness, bottom 4 bits indicate hue.
@@hydrantdude3642 i thought itd lead to nonexistent colors but makes sense now
I thought i wasn't going to understand this but somehow it made sense thanks.
Pretty good job with your notes despite one error! I too think the carry flag could've been used instead because the code is dealing with unsigned bytes.
Still have no idea why what or where. I don't even know who i am after watching it.
who came from Tetris crash video like
imagine if people made this in nes games so they could change the colours of characters and add a lot more colours in levels
That confused me even more lol
Me too
If the results are 128 or greater it glitches out because 128 or 128< its double its more than 255 which is a limit that appears in killscreens like Pac-Man,any level higher than 255 has the left of the maze glitchy
Programming back then isn't what it is now... Now they would've just used (level % 10) instead.
Is this the true 11th palette color for Tetris in level 10. 1:40
roughly. best I could do with my sharpies.
Sorry to sound like That Guy™, but as a signed 8-bit integer, 128 is equivalent to −128, not to −127.
yep already been pointed out
As the game was implemented in assembler code, it obviously did not have a simple "mod" function. Otherwise the color would have been determined by simply using a "level mod 10" expression which would always return a value between 0 and 9.
However, does anyone know why the devs. did not use the common solution of bitmasking with the bitmask of decimal 10 (binary 1010)? Thus, simply ANDing the level-number with binary 1010 would have resulted in a value between 0 and 9, again achieving the same behavior as the mod function.
So why did the devs not do that? Did the processor or the specific assembler language not allow that? Or was this operation still to expensive? I'd really would like to know. 🙂
(I am a senior dev. but I only write highlevel code. Since my student days over 10 years back I haven't touched either C or assembler and therefore I am very curious.)
Anding with 0b1010 will only result in 0b1010 (10), 0b1000 (8), 0b0010 (2) or 0b0000 (0), it's not equivalent to a modulo operation
masking down only works on powers of 2. any byte & 0x0F will return the low nybble of the byte. you can do the same with 0x07 to get bottom 3 bits, 0x03 bottom 2 bits, etc. as forchune said, doing that with decimal 10 or 0x0A does not work; masking returns every bit from the original number which is also a 1 in the masking number, which is the 2nd and 4th bit in the case of 0x0A.
now, your approach would actually work if you used 'and 0x0F' on a number stored in BCD, because it would mask down to the low nybble, and BCD are numbers where each nybble is a decimal digit. so you could do this on the score or the lines, and the game actually does use exactly this method in several places. The level, however, is not a number stored in BCD, it's just in binary, so you can't use it here.
@@hydrantdude3642 Awesome - thanks for the explanation! I totally forgot about the encoding and assumed it would work just as with any decimal /radix-10.
It's always impressive to look at all the issues which arise from such low-level implementations - especially when the computation power is so limited as back in the day (or with some even modern embedded systems).
These processors don't even have a mod instruction. If you want to divide, you write your own division algorithm.
I don't know about the instruction set of the processor, but what seems certain is that finding the new color palette for a level is not an operation you'd need to optimize. It would only happen every time you level up, so you can just use any algorithm and be done with it.
what the -128 is happening with the captions?
Man, a time befire high level abstractions
I still don’t get it but I will say that I like the flat colors maybe more than the regular ones.
So it keeps subtracting 10 until a negative number is reached? If it was level 96 it would keep subtracting till it was -4?
it keeps subtracting until the next subtraction would be negative. it'd stop at 6 for level 96.
@@hydrantdude3642 that makes sense. Thanks for explaining.
yeah
Calculating "mod 10" by continuously subtracting 10 is rather naive, but understandable considering that they assumed the max level reached will be 29. Still I'd call this lazy, I would have expected a company like Nintendo to have a library for such standard functions for the 6502 which they could just have pulled in. So I thought of a way how to properly implement this. This was my first idea:
mod10:
cmp #$a0
bcc nosuba0
sbc #$a0
nosuba0:
cmp #$50
bcc nosub50
sbc #$50
nosub50:
cmp #$28
bcc nosub28
sbc #$28
nosub28:
cmp #$14
bcc nosub14
sbc #$14
nosub14:
cmp #$0a
bcc nosub0a
sbc #$0a
nosub0a:
rts
I.e. instead of 10, start with conditionally subtracting 160 then 80, 40, 20, and 10. But while easy and straightforward, I don't like all these conditions and branches. So I think this solution is better:
mod10:
tay
and #$0f
sta v0
tya
lsr
lsr
lsr
lsr
lsr
bcc no6
adc #2
no6:
asl
adc v0
sec
minus10:
sbc #10
bcs minus10
adc #10
rts
Iow: Add the lower and upper nibble together, whereas the lowest bit of the upper nibble counts as a 6 (not 1). That should give us a number between 0 and 35 so we can calculate mod 10 with a maximum of 3 subtractions.
But maybe we're lucky that they were lazy, since in the end we get these interesting glitches.
I mean the actual problem is that they used BMI instead of BCC, not that their routine was slow.
updatePaletteForLevel:
lda player1_levelNumber
@mod10: cmp #$0A
bmi @copyPalettes
sec
sbc #$0A
jmp @mod10
if they just changed it to
updatePaletteForLevel:
lda player1_levelNumber
@mod10: cmp #$0A
bcc @copyPalettes
sbc #$0A
jmp @mod10
it would work perfectly fine (the sec was redundant given the result of the compare)
Your first solution is efficient, at the cost of a longer program. Second solution is going a bit over my head but it sure looks clever. Could you explain the number theory of it a bit further? I'm not understanding how adding the nybbles together preserves the result mod 10.
@@hydrantdude3642 Sure, but if I'd make a more generic routine which is applicable for all kind of games, I rather would also fix the slowness.
The second solution may look complicated, but it's a simple idea: 128/16=8, 64/16=4, 32/16=2, so mod 10 doesn't change when these 3 bits are divided by 16. The exception is 16/16 which is 1 but 16 mod 10 is 6, hence we need the adjustment.
This also works for higher nibbles, i.e. 2^n always ends in 8 for n mod 4 = 3, always in 4 for n mod 4 = 2, always in 2 for n mod 4 = 1 and always in 6 when n mod 4 = 0 and n > 0. So this idea would be more worthwhile for bigger numbers where just subtracting 10 in a loop becomes prohibitive. E.g. for 16 bit we have to add 4 nibbles, which can be actually done with just 2 additions (i.e. adding the lower and upper byte and then the two resulting nibbles and carry).
You can find a different but similar idea for 32 bit numbers at homepage.cs.uiowa.edu/~jones/bcd/mod.shtml#exmod10. This page also has some nice solutions for other modulo (like 7, which is used in NES Tetris for the tetrimino selection).
Before I forget: the solution on the above website for mod 7 may be nice for 32-bit numbers, but for 8 bit I find it not very practical (we only could skip the first 2 steps, so we still need 11 shifts and 3 additions in total). Luckily, I came up with this solution:
mod7:
tax
and #$07
sta $60
txa
lsr
lsr
lsr
clc
adc $60 ; worst case 7+31=38
tax
and #$07
sta $60
txa
lsr
lsr
lsr
clc
adc $60 ; worst case 6+4=10
cmp #7
bcc end
sbc #7
end:
rts
Let me know if you know something better.
Question
Without modifying the memory allocated by that variable, could it be a possible fix turning the type from "signed int" into "unsigned int" and change the check operation from "subtract 10 till negative" into "actual_level mod 10" and search the result in the table?
If we use "mod" operations, negative numbers doesn't show up, and so we can ignore them
This should work for every level... well technically at level 256 the colors will be selected from column 0 instead of column 6 and subsequent levels will carry away this problem (level 257 column 1, and so on...)
BUT, at least, using this method will prevent any weird access outside of the 0-9 table, since the variable that is responsible for doing so can't have any value outside of 0-9 (since we are using mod 10 operation)
What do you think about this? And plz consider I am new to programming, so if I made some kind of mistake, plz tell me
The thing is, the CPU of an NES doesn't have a division or modulo operation. You have to program it yourself with a subtraction loop as described here.
Also, at the assembly level, there are no types, so you can't change the type from signed to unsigned. Since NES games were generally directly written in assembly (compilers weren't good enough back then), the programmer works without types. It's just raw memory; what it means is decided entirely by the instructions you use on them.
This means that the key issue here is the way the loop was written. When the CPU executes a subtract operation, in addition to giving you the result it also sets some flags. The programmer checked the N flag ("negative"), which means the result has the highest bit set, i.e. if interpreted as a signed number, it is negative. He could have checked the C flag ("carry") instead, which means that the subtraction went below zero when interpreted as an unsigned operation. Then the code would have worked for any level number.
However, since the code only *needs* to work for the levels the game designers actually thought reachable, using the N flag probably felt more natural when thinking about the code.
@CornedBee ty for the explanation, and yeah, thinking about that, the "CARRY flag check" would have been a good solution
Your little drawings are so cute🥺
Then what causes the super long green level like that shown in the 102m StackRabbit game?
The level counter gets stuck there. I have another video explaining that bug.
th-cam.com/video/akTKMNvi97A/w-d-xo.html
@@hydrantdude3642 Yeah I got that vid recommended a few days ago, still, thanks for the reply, your content is great.
So in other words they should have been testing for an unsigned underflow rather than is-negative.
pretty much. they checked the negative flag instead of the carry flag.
Can someone explain to me why does it interpret 128 as -127?
A byte can hold 256 different values, as it contains 8 bits, and 2^8 = 256. A common way to divide those values between positive and negative numbers is to use the top bit as the sign of the number, where 1 is negative and 0 is positive. This means that binary 10000000, which is decimal 128, is the first negative number. It is interpreted as -128. (I made an error in the video calling it -127)
What?!?
they didn't know about modulo?
modulo is not an operation available on the ricoh 2A03 processor used in the NES.
why is 128 read as -128?
a byte has 256 possible values. this is because it is made up of 8 bits. each bit has 2 possible states, 0 or 1. with 2 bits, you get 4 possible states - 00 01 10 11. with 3 bits you get 8, with 4, 16, etc. of these 256 possible states, if you want to read them as negative/positive, you have to divide those 256 between the positive and negative numbers. this is typically done through assigning the first 128 values to 0 through 127 and the last 128 values to -128 through -1.
I'm cooked
Was MOD not available to just do Level# MOD 10?
I don't know about the language they used (Google says turbo pascal), but if it behaves like C, the modulus of a negative number is [bizarrely] negative. Even if it weren't, there would be a skip where the int rolled from 127 (equal to 7 mod 10), to -128 (equal to 2 mod 10).
Not to say they couldn't have accounted for that, but it's a common error.
Interesting video, but I understood nothing.
Well, I would use mod instead of loop + minus. Unless they don't have mod instruction.
there is no mod instruction.
but how are those exact palettes always chosen i dont get it
why wouldn't they always be the same? it's being read from ROM which doesn't change, just a part of it which wasn't supposed to be used for colors.
was there no mod instruction??
no
Why does lv235 last forever?
I have a video on that: th-cam.com/video/akTKMNvi97A/w-d-xo.html
This is so cool wtf
can make 138 an extra level to play or something, rather than just fix it, make it like Easter eggs
😯...LEVEL 18
18-10 = 8 👀
Is it possible for you to make coding tutorials?
I only know how to code in assembly lmao
Wowi
Thanks!
I don’t even know what is this
????
Your penmanship is astounding! You should put out a font!
As an architecture student I was taught standard drafting handwriting, which was used for architectural drawings before fonts were computerized - everyone had to be taught to write the same so that drawings looked consistent when many different people worked on them. My drafting teacher in high school showed it to me, have found it very enjoyable to practice writing in since then. If you for some reason do actually want a font like it, mr-hand is a pretty good one I've seen architecture firms use.
@@hydrantdude3642 "As an architecture student I was taught standard drafting handwriting" - I honestly didn't even know this was a thing! Good for y'all! God Speed to, Y'all!
O
if a signed 8 bit int meet a signed short the short what is 127+1 8 bit -128 the short no its 128 the 8 bit whats -128 -1 the short -129 the 8 bit no its 127 like for part 2
-127 is bit limit so thats why
I just got extremely confused when I clicked this video cause we have the same pfp lmfao
my man
All i hear was "beep boop beep beep boop". 🤖