8x16 and whatever else unreg wants to know

Are you new to 6502, NES, or even programming in general? Post any of your questions here. Remember - the only dumb question is the question that remains unasked.

Moderator: Moderators

tepples
Posts: 22705
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: 8x16 and whatever else unreg wants to know

Post by tepples »

Can the data be compressed? If the data cannot be compressed because you need fine-grained random access, can the last tile of the bank remain unused?
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: 8x16 and whatever else unreg wants to know

Post by tokumaru »

unregistered wrote:Before our game starts (is playable), it sets MMC1 into fixed-$C000 mode. So that would nullify the reset_stub's usefulness in each PRG bank, I think; but, that's just me thinking... am I right? :)
Your reset code (where you set the fixed $C000 mode) won't run if the cart boots in a bank without a reset stub. The reset stub is necessary precisely to guarantee that your reset code gets called.

Can't you sacrifice 1 or 2 tiles to make room for the reset stub? Or implement some simple compression of the CHR data (e.g. good old RLE) to gain ~25% (~4KB) of the space back without sacrificing any tiles?
unregistered
Posts: 1318
Joined: Thu Apr 23, 2009 11:21 pm
Location: cypress, texas

Re: 8x16 and whatever else unreg wants to know

Post by unregistered »

Thank you tokumaru for explaining why I was wrong; that's really helpful for me. :) Thank you very much tepples and tokumaru for helping me to not follow the last chr file with .align $1000. :D Some of my sister's 4KiB CHR files don't use the last tile. Haven't built the game yet, but it seems like SXROM is coming together nicely now. :mrgreen: :)
unregistered
Posts: 1318
Joined: Thu Apr 23, 2009 11:21 pm
Location: cypress, texas

Re: 8x16 and whatever else unreg wants to know

Post by unregistered »

Is there a way to copy the contents of block 16 to block 32? Was blessed to think maybe we could use .incbin $C000, $000, $4000 inside block 32; but, that wouldn't work because... maybe it could work cause that would only happen when the rom is assembled. Do all versions of MMC1's SXROM start with the P bit of "CHR bank 0" at 0? Or would that not matter cause each bank contains the reset_stub and so reset will run? Hmm, maybe that .incbin $C000, $000, $4000 won't work cause it only loads from files... but, when our game is assembled it does work with memory .db.

I just would like to not have to copy all of block 16, our fixed bank is $C000 in bank 15, into block 32, because then I'd have to change the code in both blocks when debugging. Do you understand my desire? :)

edit: Is block 16 the same as bank 15? Because the banks start with bank 0. :)
tepples
Posts: 22705
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: 8x16 and whatever else unreg wants to know

Post by tepples »

The value in the P bit of CHR bank 0 isn't guaranteed at power-on either. This means some of the MMC1 init code, including setting the P bit of CHR bank 0, will need to exist in the last 16 KiB bank of both 256 KiB outer banks. The way to accomplish that is similar to how you put the reset stub in the last 16 bytes of every 16 KiB bank.

To avoid having to duplicate everything in bank 15 into bank 31, you could put only those subroutines that operate on data in the first outer bank into the first outer bank's fixed bank, and only those subroutines that operate on data in the second outer bank into the second outer bank's fixed bank. The practicality of that may depend on how complex your NMI handler is. Switching banks in NMI on MMC1 is very tricky. You might want to use a simplified NMI handler in the second outer bank.

The overall code might look like this:

Code: Select all

; This goes at $FFF0 in all 16K banks
resetpart1:
  sei
  ldx #$FF
  stx resetpart1+2  ; Clear shift register; set fixed $C000
  txs               ; Initialize stack pointer
  jmp resetpart2
  .addr nmi_handler, resetstub1, irq_handler

; This goes at the same place in both fixed banks, such as $FF80
resetpart2:
  inx        ; now X is 0
  stx $2000  ; disable NMI handler
  stx $2001  ; disable rendering

  ; Set CHR window to bank 0
  stx $A000
  stx $A000
  stx $A000
  stx $A000
  stx $A000

  ; Set mode to $0E: vertical mirroring, fixed $C000, 8K CHR
  lda #1
  stx $8000  ; X writes a 0, A writes a 1
  sta $8000
  sta $8000
  sta $8000
  stx $8000
  jmp resetpart3

; And this goes in your primary fixed bank
resetpart3:
  cld
  bit $2002
  @vsync1:
    bit $2002
    bpl @vsync1
  @vsync2:
    bit $2002
    bpl @vsync2

  ; Hardware init continues here, including choosing a PRG bank
  ; at $8000 by writing to $E000
unregistered
Posts: 1318
Joined: Thu Apr 23, 2009 11:21 pm
Location: cypress, texas

Re: 8x16 and whatever else unreg wants to know

Post by unregistered »

Thank you very much tepples! It is also really good to understand that we don't necessarily need the lsrs when switching banks. :D I believe this is going to work! :mrgreen: :D
unregistered
Posts: 1318
Joined: Thu Apr 23, 2009 11:21 pm
Location: cypress, texas

Re: 8x16 and whatever else unreg wants to know

Post by unregistered »

.incbin $C000, $000, $4000 won't work because I need to put a .base $C000 at the top of block 32. Is there a way to specify the block number? Then it wouldn't matter what the P bit of "CHR bank 0" is at startup, cause my reset is always run thanks to reset_stub (and that P bit is set inside of reset). Is that good thinking? :?

That would be pretty cool to be able to write something like .incbin ]16, $000, $4000, inside of block 32, with the "]" indicating that a block number follows. That's the only character not used in asm6, I think.

Guess that I don't have to put anything inside block 32 now because I don't need to set P bit of "CHR bank 0" to 1 now. ...Maybe I need to reread tokumaru's explaination and think about how that logic could affect the need of applying tepples' wisdom. :) Lunch time. :)
unregistered
Posts: 1318
Joined: Thu Apr 23, 2009 11:21 pm
Location: cypress, texas

Re:

Post by unregistered »

Kasumi[color=#FF8080], [url=https://forums.nesdev.com/viewtopic.php?p=91421&sid=afaed212db3f60a1730db7676f794c05#p91421]on page 32[/url],[/color] wrote:
My goal is to see this collision detection work well.
I don't quite see the relation to collision detection.

But sure, I can sure talk about it. It's actually really easy and fast as long as you don't handle slopes, and don't have things like tiles you can jump up through or tiles you can drop down through.

You only really need to check 6 points to be TOTALLY safe, or 4 if the shape of your character never changes and he is smaller than the size of the collision tile for both X and Y. If he's say... 24 pixels tall with 16x16 collision tiles you have to check one more point. You also need to check more if your character moves more pixels in a frame than the size of your tile. For instance, he's capable of moving 9 pixels in a frame, but your collision tiles are only 8 wide.

Obviously, you first gotta find out whether or not the tile you're working with is collision enabled. I can't especially help with that unless you make a post about your current tile format. Post about that, and I can write a book for you about that.

I personally keep 4 screens of 32x32 metatiles in a page of RAM, which are updated when the game scrolls to a new screen. This means all my screen decompression happens once and only on a frame when the character passes a screen boundary.

The collision detection and parts of my code that tell the PPU what to draw always pull from this RAM, where it is much easier to figure out the math for me than pulling directly from the data in ROM.

I load the 32x32 tile from RAM, I find out the 16x16 tile I want in it. Then I know if I should eject or not.

It works exactly like this:

Code: Select all

;Ignore the <'s, it's a nesasm thing.
	lda <xhigh;Zero Page RAM I put any high X position into
	ror a;puts the lowest bit of the high x pos into the carry
	
	lda <yhigh;Zero Page RAM I put any high y position into
	ror a;puts the lowest bit of the high y pos into the carry
	;and puts the x bit into the high bit of the accumulator
	
	ror a;Now the low x and y bits are in the high bits accumulator
	
	and #%11000000;Removes the other junk in the accumulator
	
	sta <reservedE;Temp RAM. The highest bits of it now contain
	;which screen I'll read from RAM. Each screen is 64 bytes long, because it contains 8x8 32x32 metatiles. 
	
	lda <xlow
	lsr a
	lsr a
	lsr a
	lsr a
	lsr a;divide by 32, since I'm trying to get a 32x32 metatile. 
	;The other bits of precision don't matter.
	
	ora <reservedE;This combines my "which screen" bits with the X offset in RAM.
	
	sta <reservedE
	
	lda <ylow
	lsr a
	lsr a
	and #%00111000;This takes away all unnecessary bits from
	;my y position
	ora <reservedE;I know have the offset in RAM that I need to load my 32x32 tile from.

	tay;Y contains the address of the 32 x 32 metatile to look up

	lda $0500,y;loads 32 x 32
	tay;Y contains the 32 x 32 tile number
The tricky part is actually getting the 32x32 tiles into RAM in the first place, but you may not even need to do that depending on how your data is stored.

I then use a series small series of branches to determine which part of the 32x32 tile I'm in. Topleft, Topright, bottomleft, or bottomright. Once I know that, I load the tile and use its format to determine whether it's a collision or not.

Here's how I eject when I know the current tile is collision enabled

Code: Select all

;Ejecting up/left
lda lowbyte
and #%00001111
clc
adc #$01
sta offset

rts
You want to eject left while traveling right. So imagine the right point of your character has just entered the first pixel of a tile. It's anded position would be 0. In this case, you obviously want to eject one. If you're going super fast, you'll end up further in the tile, so the and will be higher and you'll always eject one more than that. Easy. jsr to the routine, subtract offset from your character's position. (It should be set to 0 if the collision routine detects the current tile is not a collision tile.) You can even use the offset variable the routine returns to find out whether or not there WAS a collision if you want to do that to stop a walking animation or something.

Code: Select all

;Ejecting down/right
lda lowbyte
and #%00001111
eor #%00001111
clc
adc #$01
sta offset

rts
This one is mostly the same, except it has an eor. Say you're traveling left and hit the first pixel of a tile. That's actually pixel $0F, but you only want to eject one! The eor makes $0F equal to one, then it works the same as before! As before, just write a 0 to offset if you've discovered the current tile is not solid. Jsr to routine, then add offset to player's position.

This works for any size tile that's a power of two. Just change the ands and eor to the number of bits your tile takes up.

If you're traveling left, move the player using the current speed value. You may be in a wall. You check the top and bottom left points of your character, and eject right. (add offset) If you're traveling right, check the top and bottom right points of your character and eject left. (subtract offset) you'll never be in the wall for a frame like Mario, and this will certainly be fast enough for how few tiles you'll need to check.

For ejecting up while falling you check the bottom left and right points and subtract offset from the player's position.
1.) How many points does one need to check if the sprite is 8 pixels wide and she is colliding into an 8 pixel wall, but she moves more than 8 pixels per frame sometimes. I been attempting to solve this for a while. Now, two points are checked and she used to never enter the wall, but she is ejected 8 pixels away from the wall every other time it checks collision. :?

2.) The code I was blessed to discover for checking collisions while moving left ejecting collisions to the left or right, uses an eor, but it makes sense to me:

Code: Select all

  lda altoX ;<our major X variable
  and #00000111b ;and with 7 because our collision deals with 8x8 tiles :)  ;always clears bit3
  ldx FORWARD ;<00 for left, 01 for right, and ff for neither
            ;it is verified that FORWARD won't be ff if this code is reached :)
  bne + ;if going right we don't want to invert bits 0 through 2
    eor #00000111b
+ ;some other code here
  sta t16 ;a temp variable

  lda altoX
  ldx FORWARD
  beq +
    clc
    sbc t16
    jmp ++
+ sec
  adc t16
++
sta altoX
Left out some of the code because it doesn't matter for what my goal is. I just want to know how many points should be checked. I'm not hoping you'll solve my problem; just hope you'll answer my question. :) And I wanted to post a solution to left collision correction that works, for me at least.


3.) Your usage of ror to move the low bits of yhigh and xhigh into the carry is really brilliant Kasumi! :D

p.s. I hope I haven't posted this earlier.


edit: Sorry, I was wrong, we chechcheck two points underneath her and two points on the direction she is moving. And awhile ago I wrote code that would check two more points, 8 pixels beyond the points checked... was blessed with getting that to work and she collided as I mentioned above... then I thought we could skip checking those points and and #$08 t16 based on a variable set earlier in the frame (that's why it is important that bit3 was cleared in the code I just shared). Now she goes through the 8 pixel wall when moving left, but collides with the wall when moving right; every other collision when moving right she is ejected 8 pixels too far. Maybe I'm setting the variable incorrectly! :idea: But, maybe not because this pulsing away from the wall 8 pixels happened when 4 points were checked in the direction she is moving.

edit2: Sorry, noticed that my summary of our code was wrong so created a strike-through and followed that with a valid summary, I think. :)

final edit.
Last edited by unregistered on Wed Apr 11, 2018 2:08 pm, edited 2 times in total.
User avatar
Kasumi
Posts: 1293
Joined: Wed Apr 02, 2008 2:09 pm

Re: 8x16 and whatever else unreg wants to know

Post by Kasumi »

You have to check just enough pixels that the character can't pass through a single tile.

Say your tilesize is 16x16. You have a character that is 18 pixels tall. They can move through a single tile if they're positioned perfectly and you only check the top and bottom points.

Image
(All example images in this post are zoomed to 400%) Black is one 16x16 tile, red is the character, and yellow are the points checked.

That image shows what happens if you only check the top and bottom points. If your character is tall enough, a tile can pass through their middle!

So you have to check the top point always. Then 16 pixels below that.
Image
Note even when the top point is one above the tile, the collision still works. Move it down and it still works. This ensures there will always be at least ONE point for every tile the player might hit on the grid. You still need to check the bottom, though! (Unless the bottom is exactly 16 pixels below the top point.) Otherwise the character could move their bottom through tiles!

This image shows which points you'd have to check for characters of lots of heights:
Image
Top. Then 16 pixels below that. (Then 16 pixels below that. As many times until 16 pixels below the last point is outside the character.) Then you have to check the bottom point. This will eliminate any blindspot.
unregistered
Posts: 1318
Joined: Thu Apr 23, 2009 11:21 pm
Location: cypress, texas

Re: 8x16 and whatever else unreg wants to know

Post by unregistered »

Thank you Kasumi! Your graphic making abilities are very scrupulous and excellent! :D
unregistered
Posts: 1318
Joined: Thu Apr 23, 2009 11:21 pm
Location: cypress, texas

Re: 8x16 and whatever else unreg wants to know

Post by unregistered »

If anyone ever wants to learn how to write to CHR-RAM read this excellent reply by tokumaru.
unregistered
Posts: 1318
Joined: Thu Apr 23, 2009 11:21 pm
Location: cypress, texas

Re: 8x16 and whatever else unreg wants to know

Post by unregistered »

Ok, so my nes rom is successfully built! :mrgreen: :) But, the nametables aren't filled and the CHR RAM is empty. My tracelog file starts out:

Code: Select all

FCEUX 2.2.3 - Trace Log File
f1      A:00 X:FF Y:00 S:C2 P:nvubdIzc                              $BFF0:78        SEI
f1      A:00 X:FF Y:00 S:C2 P:nvubdIzc                              $BFF1:A2 FF    LDX #$FF
f1      A:00 X:FF Y:00 S:C2 P:nvubdIzc                              $BFF3:9A        TXS
f1      A:00 X:FF Y:00 S:FF P:NvubdIzc $BFF4:8E 00 80  STX $8000 = $00
f1      A:00 X:FF Y:00 S:FF P:NvubdIzc $BFF7:4C A0 FF  JMP $FFA0
f1      A:00 X:FF Y:00 S:FF P:NvubdIzc $FFA0:00        BRK
f1      A:00 X:FF Y:00 S:FC P:NvubdIzc    $D1BA:00        BRK
etc.
It keeps hitting the 00 at $D1BA. Maybe I set up the header or the blocks wrong. In the header it has
|iNES header| 4E 45 53 1A |Number of PRG-ROM blocks| 20 |Number of CHR-ROM blocks| 00 |ROM control bytes: Vertical mirroring, yes SRAM, no trainer, Mapper #1| 13 00 |Filler| 00 00 00 00 00 00 00 00

For my PRG-ROM blocks I just added 15 more blocks after the first 14 and started bank!15, the last bank in the the second outer bank, with .base $C000... and also had to change the .org $C000 in my main fixed bank, that follows it, to .base $C000.

Ohhh!! Maybe I need to place the other 15 blocks after my main fixed bank! :idea:
unregistered
Posts: 1318
Joined: Thu Apr 23, 2009 11:21 pm
Location: cypress, texas

Re: 8x16 and whatever else unreg wants to know

Post by unregistered »

Hahha!! It totally works now; thank you so much tepples for your wise teachings!! :mrgreen: :D

edit: Back to collision now. :)
unregistered
Posts: 1318
Joined: Thu Apr 23, 2009 11:21 pm
Location: cypress, texas

Re: 8x16 and whatever else unreg wants to know

Post by unregistered »

Ok, honestly, there is one problem, the .chr file isn't being read correctly because I'm using one label in multiple banks to mark the 4KiB background files. I'm using CHR4kb = $, but some of the files start at $B000 and others start at $AFF0. The last time CHR4kb is assigned it gets $AFF0, so that is why the chr file, that starts at $B000, that I'm trying to load into CHR-RAM is off by one tile and making all of level 1 look crazy. My question is: is there another simple way to make this work other than rearranging the banks so that CHR4kb is always assigned the same value? I thought that CHR4kb would be updated depending on which bank was loaded into $8000-$BFFF. :oops:
unregistered
Posts: 1318
Joined: Thu Apr 23, 2009 11:21 pm
Location: cypress, texas

Re: 8x16 and whatever else unreg wants to know

Post by unregistered »

Rearranged the banks containing CHR4kb = $ so that they are all assigned the same value and our game looks like it used to before CHR-RAM was enabled. I'm so thankful! :mrgreen: :D

edit: Also want to say that now the nesdev wiki makes sense, to me, when it says, "Valid addresses are $0000-$3FFF," at the end of the PPUADDR section. Honestly, I always thought that was a mistake; but after writing to CHR-RAM ($0000-$1FFF) it is nice to know that that statement is valid when CHR-RAM is enabled. Want to write that here to help some others to understand that too. :)
Post Reply