I've spent a lot of time away from NES programming and I'm trying to come back and get a better feel for it.
I'm working on a ball and paddle game and I'm running into problems right away.
My lame collision works as it is. But, if I add so much as a NOP, it hangs upon collision. It also fills the second ram page with what looks like nonsense. What could make it so fragile? I'm not doing a million things between vblanks.
.inesprg 1
.inesmap 0
.inesmir 1
.ineschr 1
.bank 1
.org $FFFA
.dw VBlank_Routine ; address to execute on VBlank
.dw Start ; address to execute on reset
.dw 0 ; no whatever
.bank 0
.org $0000
VBlankOrNo: .db 0
Direction: .db 0 ;ball direction 0=left
.org $0300 ; OAM Copy location $0300
Sprite0_Y: .db 0 ; sprite #1's Y value
Sprite0_T: .db 0 ; sprite #1's Tile Number
Sprite0_S: .db 0 ; sprite #1's special byte
Sprite0_X: .db 0 ; sprite #1's X value
Sprite1_Y: .db 0 ; sprite #1's Y value
Sprite1_T: .db 0 ; sprite #1's Tile Number
Sprite1_S: .db 0 ; sprite #1's special byte
Sprite1_X: .db 0 ; sprite #1's X value
Sprite2_Y: .db 0 ; sprite #1's Y value
Sprite2_T: .db 0 ; sprite #1's Tile Number
Sprite2_S: .db 0 ; sprite #1's special byte
Sprite2_X: .db 0 ; sprite #1's X value
; this would just go on and on for however many sprites you have
.org $8000 ; code starts at $8000 or $C000
;-------------------------------------------------------------------------
reset:
sei ; ignore IRQs
cld ; disable decimal mode
ldx #$40
stx $4017 ; disable APU frame IRQ
ldx #$ff
txs ; Set up stack
inx ; now X = 0
stx $2000 ; disable NMI
stx $2001 ; disable rendering
stx $4010 ; disable DMC IRQs
stx $2005
stx $2005
; Optional (omitted):
; Set up mapper and jmp to further init code here.
; First of two waits for vertical blank to make sure that the
; PPU has stabilized
vblankwait1:
bit $2002
bpl vblankwait1
; We now have about 30,000 cycles to burn before the PPU stabilizes.
; Use it to clear RAM. X is still 0...
txa
clrmem:
sta $000,x
sta $100,x
sta $200,x
sta $300,x
sta $400,x
sta $500,x
sta $600,x
sta $700,x ; Remove this if you're storing reset-persistent data
inx
bne clrmem
;-------------------------------------------------------------------------
VBlank_Routine:
;start of function to execute on VBlank
inc VBlankOrNo
; add one (1) to VBlankOrNo, will be 1 if VBlank, 0 if not.
rti ; RTI is (Interrupt RETurn or ReTurn from Interrupt)
Start:
ldx #$00 ; clear X ;; start of pallete loading code
lda #$3F ; have $2006 tell
sta $2006 ; $2007 to start
lda #$00 ; at $3F00 (pallete).
sta $2006
loadpal: ; this is a freaky loop
lda tilepal, x ; that gives 32 numbers
sta $2007 ; to $2007, ending when
inx ; X is 32, meaning we
cpx #32 ; are done.
bne loadpal ; if X isn't =32, goto "loadpal:" line.
;; end of pallete loading code
lda #$20
sta $2006 ; give $2006 both parts of address $2020.
sta $2006
load_name_tables:
; Jam some text into the first name table (at $2400, thanks to mirroring)
ldy #$00
ldx #$04
lda #low(ourMap)
sta $10
lda #high(ourMap)
sta $11
lda #$20
sta $2006
lda #$00
sta $2006
load_name_tables_loop:
lda [$10],y
sta $2007
iny
bne load_name_tables_loop
inc $11
dex
bne load_name_tables_loop
setup_Y_X:
ldx #$0
stx Sprite0_T
stx Sprite0_S
stx Sprite1_S
stx Sprite2_S
stx Sprite1_X
inx
stx Sprite1_T
inx
stx Sprite2_T
lda #$70
sta Sprite0_Y
sta Sprite2_Y
adc #$7
sta Sprite1_Y
lda #$80
sta Sprite2_X
lda #%10001000 ;
sta $2000 ;
lda #%00011110 ; Our typical PPU Setup code.
sta $2001 ;
; ---------------------------------------------------
; Setup is done
; ---------------------------------------------------
infinite: ; a label to start our infinite loop
WaitForVBlank:
lda VBlankOrNo ; A = VBlankOrNO
cmp #1 ; if A == 1 then is VBlank
bne WaitForVBlank ; if not VBlank, then loop and do again
dec VBlankOrNo ; 1-- or VBlankOrNO - 1 . VBlankOrNo will be 0 again.
; vblank part is done
lda #3 ; load sprite DMA offset
sta $4014 ; write sprite offset to PPU
lda #$01 ; these
sta $4016 ; lines
lda #$00 ; setup/strobe the
sta $4016 ; keypad.
lda $4016 ; load Abutton Status
and #1
lda $4016 ; load Bbutton Status
and #1
lda $4016 ; load Select Status
and #1
lda $4016 ; load Start Status
and #1
lda $4016 ; load UP Status
and #1 ; just check the least significant bit aka 00000001
beq Load_Down ; if it wasn't pushed go to the next button
dec Sprite0_Y ; if it was pushed move the sprite UP
Load_Down:
lda $4016 ; load DOWN Status
and #1 ; just check the least significant bit aka 00000001
beq Load_Left ; if it wasn't pushed go to the next button
inc Sprite0_Y ; if it was pushed move the sprite Down
Load_Left:
lda $4016 ; load LEFT Status
and #1
lda $4016 ; load RIGHT Status
and #1
LDA Direction ; zero on start
BNE Go_Right ; if 1 go_right
LDA Sprite0_X
ADC #8
CMP Sprite2_X ; see if sprite 1 is on same x and sprite 0
BNE NO_COL ; if it isn't keep going left
LDA Sprite0_Y
SEC ; set carry for subtraction
SBC Sprite2_Y ; subtract sprite 1 y from sprite 0 y to see if it is greater or less
BPL POS ; if 1 is less than 0 jump POS
CLC ; clear carry for addition
ADC #8 ; if greater see if it's within 7
BPL COL ; if it is, change direction to "right"
JMP NO_COL ; if not, keep on going left
POS:
SBC #8 ; if less see if it's withing 7
SEC ; set carry for subtraction
BMI COL ; if it is, change direction to "right"
JMP NO_COL ; if not, keep on going left
COL:
INC Direction ; set direction to "right"
Go_Right:
INC Sprite2_X ; moves right
LDA Sprite2_X
CMP #247 ; see if we hit the right side of the screen
BNE infinite ; if we didn't start the loop over
dec Direction ; if we did, set direction to "left"
JMP infinite
NO_COL:
DEC Sprite2_X ; moves left
nop
jmp infinite
tilepal: .incbin "pong.pal" ; a label for our pallete data
ourMap: .incbin "pong.map" ; assuming our.map is the binary map file.
.bank 2
.org $0000
.incbin "pong.bkg"
.incbin "pong.spr"
I just copied the start up portion from the web. So if it is a typo, it isn't mine.
I added "JMP Start" after the bne clrmem, but NOP's still crash it.
VBlank_Routine:
php
;start of function to execute on VBlank
inc VBlankOrNo
; add one (1) to VBlankOrNo, will be 1 if VBlank, 0 if not.
plp
rti ; RTI is (Interrupt RETurn or ReTurn from Interrupt)
.bank 1
.org $FFFA
.dw VBlank_Routine ; address to execute on VBlank
.dw Start ; address to execute on reset
.dw 0 ; no whatever
You used "Start" as the reset point, so none of the code before it (i.e. all the initialization) is running. Change that "Start" to "reset", which is where you want the program to start. You do need that JMP to skip over the VBlank_Routine though. Better yet, move the VBlank_Routine to after the infinite loop.
EDIT: Also, pointing your NMI vector to "0" isn't a good idea either. In case an IRQ happens, the CPU will try to execute RAM variables as if they were code... not good. Just have it point to a location in ROM with an RTI instruction, so that in case of an IRQ it simply returns.
I can't believe I missed that obvious mistake. Explains why the NMI routine wasn't breaking it when it was being executed (in theory) after the start code.
I guess it would have been easier to debug if the rest of the files were supplied to assemble it.
JunoMan wrote:I can't believe I missed that obvious mistake. Explains why the NMI routine wasn't breaking it when it was being executed (in theory) after the start code.
I guess it would have been easier to debug if the rest of the files were supplied to assemble it.
I can upload the files somewhere if anyone can use them.
I changed "Start" to reset but the problem persists.
The code currently looks like this:
.inesprg 1
.inesmap 0
.inesmir 1
.ineschr 1
.bank 1
.org $FFFA
.dw VBlank_Routine ; address to execute on VBlank
.dw reset ; address to execute on reset
.dw 0 ; no whatever
.bank 0
.org $0000
VBlankOrNo: .db 0
Direction: .db 0 ;ball direction 0=left
.org $0300 ; OAM Copy location $0300
Sprite0_Y: .db 0 ; sprite #1's Y value
Sprite0_T: .db 0 ; sprite #1's Tile Number
Sprite0_S: .db 0 ; sprite #1's special byte
Sprite0_X: .db 0 ; sprite #1's X value
Sprite1_Y: .db 0 ; sprite #1's Y value
Sprite1_T: .db 0 ; sprite #1's Tile Number
Sprite1_S: .db 0 ; sprite #1's special byte
Sprite1_X: .db 0 ; sprite #1's X value
Sprite2_Y: .db 0 ; sprite #1's Y value
Sprite2_T: .db 0 ; sprite #1's Tile Number
Sprite2_S: .db 0 ; sprite #1's special byte
Sprite2_X: .db 0 ; sprite #1's X value
; this would just go on and on for however many sprites you have
.org $8000 ; code starts at $8000 or $C000
;-------------------------------------------------------------------------
reset:
sei ; ignore IRQs
cld ; disable decimal mode
ldx #$40
stx $4017 ; disable APU frame IRQ
ldx #$ff
txs ; Set up stack
inx ; now X = 0
stx $2000 ; disable NMI
stx $2001 ; disable rendering
stx $4010 ; disable DMC IRQs
stx $2005
stx $2005
; Optional (omitted):
; Set up mapper and jmp to further init code here.
; First of two waits for vertical blank to make sure that the
; PPU has stabilized
vblankwait1:
bit $2002
bpl vblankwait1
; We now have about 30,000 cycles to burn before the PPU stabilizes.
; Use it to clear RAM. X is still 0...
txa
clrmem:
sta $000,x
sta $100,x
sta $200,x
sta $300,x
sta $400,x
sta $500,x
sta $600,x
sta $700,x ; Remove this if you're storing reset-persistent data
inx
bne clrmem
;-------------------------------------------------------------------------
JMP Start
VBlank_Routine:
;start of function to execute on VBlank
inc VBlankOrNo
; add one (1) to VBlankOrNo, will be 1 if VBlank, 0 if not.
rti ; RTI is (Interrupt RETurn or ReTurn from Interrupt)
Start:
ldx #$00 ; clear X ;; start of pallete loading code
lda #$3F ; have $2006 tell
sta $2006 ; $2007 to start
lda #$00 ; at $3F00 (pallete).
sta $2006
loadpal: ; this is a freaky loop
lda tilepal, x ; that gives 32 numbers
sta $2007 ; to $2007, ending when
inx ; X is 32, meaning we
cpx #32 ; are done.
bne loadpal ; if X isn't =32, goto "loadpal:" line.
;; end of pallete loading code
lda #$20
sta $2006 ; give $2006 both parts of address $2020.
sta $2006
load_name_tables:
; Jam some text into the first name table (at $2400, thanks to mirroring)
ldy #$00
ldx #$04
lda #low(ourMap)
sta $10
lda #high(ourMap)
sta $11
lda #$20
sta $2006
lda #$00
sta $2006
load_name_tables_loop:
lda [$10],y
sta $2007
iny
bne load_name_tables_loop
inc $11
dex
bne load_name_tables_loop
setup_Y_X:
ldx #$0
stx Sprite0_T
stx Sprite0_S
stx Sprite1_S
stx Sprite2_S
stx Sprite1_X
inx
stx Sprite1_T
inx
stx Sprite2_T
lda #$70
sta Sprite0_Y
sta Sprite2_Y
adc #$7
sta Sprite1_Y
lda #$80
sta Sprite2_X
lda #%10001000 ;
sta $2000 ;
lda #%00011110 ; Our typical PPU Setup code.
sta $2001 ;
; ---------------------------------------------------
; Setup is done
; ---------------------------------------------------
infinite: ; a label to start our infinite loop
WaitForVBlank:
lda VBlankOrNo ; A = VBlankOrNO
cmp #1 ; if A == 1 then is VBlank
bne WaitForVBlank ; if not VBlank, then loop and do again
dec VBlankOrNo ; 1-- or VBlankOrNO - 1 . VBlankOrNo will be 0 again.
; vblank part is done
lda #3 ; load sprite DMA offset
sta $4014 ; write sprite offset to PPU
;nop
lda #$01 ; these
sta $4016 ; lines
lda #$00 ; setup/strobe the
sta $4016 ; keypad.
lda $4016 ; load Abutton Status
and #1
lda $4016 ; load Bbutton Status
and #1
lda $4016 ; load Select Status
and #1
lda $4016 ; load Start Status
and #1
lda $4016 ; load UP Status
and #1 ; just check the least significant bit aka 00000001
beq Load_Down ; if it wasn't pushed go to the next button
dec Sprite0_Y ; if it was pushed move the sprite UP
Load_Down:
lda $4016 ; load DOWN Status
and #1 ; just check the least significant bit aka 00000001
beq Load_Left ; if it wasn't pushed go to the next button
inc Sprite0_Y ; if it was pushed move the sprite Down
Load_Left:
lda $4016 ; load LEFT Status
and #1
lda $4016 ; load RIGHT Status
and #1
LDA Direction ; zero on start
BNE Go_Right ; if 1 go_right
LDA Sprite0_X
ADC #8
CMP Sprite2_X ; see if sprite 1 is on same x and sprite 0
BNE NO_COL ; if it isn't keep going left
LDA Sprite0_Y
SEC ; set carry for subtraction
SBC Sprite2_Y ; subtract sprite 1 y from sprite 0 y to see if it is greater or less
BPL POS ; if 1 is less than 0 jump POS
CLC ; clear carry for addition
ADC #8 ; if greater see if it's within 7
BPL COL ; if it is, change direction to "right"
JMP NO_COL ; if not, keep on going left
POS:
SBC #8 ; if less see if it's withing 7
SEC ; set carry for subtraction
BMI COL ; if it is, change direction to "right"
JMP NO_COL ; if not, keep on going left
COL:
INC Direction ; set direction to "right"
Go_Right:
INC Sprite2_X ; moves right
LDA Sprite2_X
CMP #247 ; see if we hit the right side of the screen
BNE infinite ; if we didn't start the loop over
dec Direction ; if we did, set direction to "left"
JMP infinite
NO_COL:
DEC Sprite2_X ; moves left
nop
jmp infinite
tilepal: .incbin "pong.pal" ; a label for our pallete data
ourMap: .incbin "pong.map" ; assuming our.map is the binary map file.
.bank 2
.org $0000
.incbin "pong.bkg"
.incbin "pong.spr"
Another thing that appears to be wrong (but I'm not sure because I'm not a NESASM user) is that you state that there is only one PRG bank (.inesprg 1) but in fact you have two. I can't see anything else wrong.
What exactly isn't working right when you run it in an emulator?
tokumaru wrote:Another thing that appears to be wrong (but I'm not sure because I'm not a NESASM user) is that you state that there is only one PRG bank (.inesprg 1) but in fact you have two. I can't see anything else wrong.
What exactly isn't working right when you run it in an emulator?
I saw something in another post about NESASM treating banks as 32K instead of 16K, or some such thing. It assembles so I would guess it's correct.
The demo consists of a ball and a paddle. The ball collides with the paddle and bounces in the unmodified code. If I add *any* opcode at seemingly any point in the code, the demo freezes when the ball hits the paddle.[/quote]
Skidlz wrote:I saw something in another post about NESASM treating banks as 32K instead of 16K, or some such thing.
It doesn't really matter how NESASM treats them, iNES files always counts PRG banks in 16KB units. Anyway, it's a fact that you are trying to assemble 32KB of PRG ROM, and if ".inesprg 1" writes a 1 to the PRG bank count field in the header, it's wrong. Try using a 2 here and see if it makes any difference.
It assembles so I would guess it's correct.
Well, NESASM is notorious for outputting glitched binaries instead of showing errors. It is known to mess up with addressing modes and such and not telling the user anything. Don't assume it's correct just because it assembles.
BTW, what are you using to test your ROM? Make sure to test on different emulators, and that new and reliable emulators (such as Nestopia and Nintendulator) are in that list. Please tell us if different emulators behave differently.
It doesn't really matter how NESASM treats them, iNES files always counts PRG banks in 16KB units. Anyway, it's a fact that you are trying to assemble 32KB of PRG ROM, and if ".inesprg 1" writes a 1 to the PRG bank count field in the header, it's wrong. Try using a 2 here and see if it makes any difference.
I get it. If it's writing to the iNES header, all that matters is what ends up in the header.
Well I changed it to ".inesprg 2" and it wont do anything at all.
BTW, what are you using to test your ROM? Make sure to test on different emulators, and that new and reliable emulators (such as Nestopia and Nintendulator) are in that list. Please tell us if different emulators behave differently.
I use FCEUXD/FCEU and it has always worked. I know that doesn't mean much so I downloaded Nestopia.
Nestopia gives the error "CPU Jam!" when I add a NOP. It also refuses to run with ".inesprg 2"