It is currently Sat Oct 21, 2017 12:45 pm

All times are UTC - 7 hours





Post new topic Reply to topic  [ 15 posts ] 
Author Message
PostPosted: Fri Mar 31, 2017 11:30 am 
Offline
User avatar

Joined: Sat Sep 07, 2013 2:59 pm
Posts: 1402
Today, I tried out the cartridge of my game and I saw that the game gets suck during the credits screen and you cannot advance.
If you press Reset or if you press Power in a very short amount of time, it works.

The game does use sprite 0 split and nine sprites overflow split, even in the credits screen where it is not necessary, but I didn't want to change the logic for it.
However, I'm not sure why this works after a reset, but not during power on.

Can this have anything to do with the PPU not being ready yet or something like this? In a way that the background is already enabled, but sprites are not (despite me enabling both at the same time) and the game goes into an infinity loop waiting for sprite 0?

None of the popular emulators, fceux, Nestopia and Nintendulator, shows this behavior with the ROM. And it was fine when I last checked it with a PowerPak.


If there's no known way how I could reproduce the error on an emulator, does anybody on this forum have the means to test the game on an actual cartridge?
PowerPak or Everdrive would not suffice. It has to be an actual standalone 32 KB NROM cartridge with vertical mirroring.

_________________
Available now: My game "City Trouble".
Website: https://megacatstudios.com/products/city-trouble
Trailer: https://youtu.be/IYXpP59qSxA
Gameplay: https://youtu.be/Eee0yurkIW4
German Retro Gamer article: http://i67.tinypic.com/345o108.jpg


Top
 Profile  
 
PostPosted: Fri Mar 31, 2017 11:50 am 
Online

Joined: Sun Apr 13, 2008 11:12 am
Posts: 6291
Location: Seattle
Is there any memory or PPU/APU register that you didn't set to a known value in your powerup routine?


Top
 Profile  
 
PostPosted: Fri Mar 31, 2017 11:57 am 
Offline
User avatar

Joined: Sat Sep 07, 2013 2:59 pm
Posts: 1402
I don't know. Actually, I did all the stuff that needs to be done, according to the tutorials and the wiki.

Maybe not enough wait for vblank?

However, here's the (hopefully) relevant code:

Code:
.segment "CODE"

   ; Wait for vblank is needed more than once, so we declare it as a macro.
.macro WAIT_FOR_VBLANK
   .local @waitForVBlank

   ; Check the vblank flag to be sure that we wait for a vertical blank.
   BIT PpuStatus

@waitForVBlank:

   ; The vblank flag is cleared and we wait while the resulting value is positive.
   BIT PpuStatus
   BPL @waitForVBlank
.endmacro

   ; There are several PPU statuses that we need to wait for,
   ; so we declare it as a macro with a parameter.
.macro WAIT_FOR_PPU_STATUS value
   .local @waitForNotPpuStatus, @waitForPpuStatus

   ; Wait while the PPU status hasn't happened.
@waitForNotPpuStatus:

   ; Checks if the status has happened.
   LDA PpuStatus
   AND #value
   BNE @waitForNotPpuStatus

   ; Wait until the status has happened.
@waitForPpuStatus:

   ; Checks if the status has happened.
   LDA PpuStatus
   AND #value
   BEQ @waitForPpuStatus

   ; The status has happened now.
.endmacro

   ; Due to parallax scrolling, we set the scrolling position more than once.
.macro SET_SCROLLING_POSITION scrollingPosition, nameTable
   ; The horizontal scrolling position is set while vertical scrolling is always 0.
   LDA scrollingPosition
   STA PpuScroll
   LDA #0
   STA PpuScroll

   ; We set the name table by taking the horizontal PPU control register value
   ; and OR-connect it with the currently set name table value.
   ; In the PPU control register, the name tables are set at bit 1 and 0, so the OR works.
   LDA #PpuCtrlHorizontal
   ORA nameTable
   STA PpuCtrl
.endmacro

   ; The start of the reset function.
Reset:

   ; At first, general system initialization has to be done.

   ; Disable interrupt requests (IRQs).
   SEI

   ; Disable decimal mode since it isn't available on the NES anyway.
   CLD

   ; Disable APU frame IRQ.
   LDX #$40
   STX ApuFrameCounter

   ; Set up the stack.
   LDX #$FF
   TXS

   ; Set X to 0.
   INX

   ; Disable NMI, rendering and DMC IRQs.
   STX PpuCtrl
   STX PpuMask
   STX DmcFreq

   ; The PPU needs two waits for vblank to be ready.
   ; This is the first one.
   WAIT_FOR_VBLANK

   ; In the meantime, set the whole RAM memory (i.e. all variables) to zero.
   ; The RAM goes from address $0000 to $07FF.
   ; The current address value goes into a two bytes pointer.
   ; The values are set in two loops.

   ; X is still 0.

   ; The X and Y registers are used as counters for the loops and initialized with 0.
   TXA
   TAY

   ; A, X and Y all have the value 0 now.

   ; Pointer always remains 0.
   ; The address is calculated by Pointer + 1 and Y.
   STA Pointer
   STA Pointer + 1

@initializeRamOuterLoop:
@initializeRamInnerLoop:

   ; There is a certain RAM area that shall not be initialized with zeroes
   ; because this area shall be persistent when the Reset button is pressed.
   ; We check if we reached that RAM area.

   ; The high byte of the address is checked.
   LDA Pointer + 1
   CMP #>(__NO_RESET_LOAD__)
   BNE @noNoResetSkip

   ; The low byte is checked.
   CPY #<(__NO_RESET_LOAD__)
   BNE @noNoResetSkip

   ; If we reached the reset-persistent area,
   ; we change the address to the first value after the area.
   ; This way, the reset-persistent area doesn't get changed.
   LDY #<(__NO_RESET_LOAD__ + __NO_RESET_SIZE__)
   LDA #>(__NO_RESET_LOAD__ + __NO_RESET_SIZE__)
   STA Pointer + 1

@noNoResetSkip:

   ; Set the value of 0 to the current address.
   LDA #0
   STA (Pointer), Y

   ; Increment the low byte part of the address.
   INY

   ; If it is set back to zero, increment the high byte part
   ; and, of course, the counter.
   ; Otherwise, continue with the inner loop.
   BNE @initializeRamInnerLoop
   INC Pointer + 1
   INX

   ; The outer loop ends just before address $0800.
   CPX #$08
   BNE @initializeRamOuterLoop

   ; The RAM has now been initialized with all zeroes.

   ; The second wait for vblank.
   WAIT_FOR_VBLANK

   ; From now on, the system is properly initialized.

   ; We want to check whether the console is NTSC or PAL.
   ; This is needed only for the music.
   ; The gameplay isn't adjusted to this value.

   ; We set counters.
   LDX #52
   LDY #24

@ntscPalLoop:

   DEX
   BNE @ntscPalLoop
   DEY
   BNE @ntscPalLoop

   ; Now we AND-combine PpuStatus with $80.
   ; This tells us whether we have NTSC or PAL.
   ; 0 means the console is PAL, non-zero means it's NTSC.
   LDA PpuStatus
   AND #$80

   ; The FamiTone sound library gets initialized.
   ; The value in A is necessary for setting NTSC or PAL.
   JSR InitializeSound

   ; The argument stack pointer for CC65 is initialized.
   ; From now on, we can call functions written in C.
   LDA #<(__STACK_START__ + __STACK_SIZE__)
   STA sp
   LDA #>(__STACK_START__ + __STACK_SIZE__)
   STA sp + 1

   ; The reset-persistent data is checked and set to an initial value
   ; if the console was started with the Power button.
   JSR InitializeNoResetData

   ; General initializations of graphics, game logic and everything else.
   JSR Initialize

   ; The first thing that is supposed to run is the NMI.
   ; Therefore, we set the WaitForNmi variable to true.
   LDA #true
   STA WaitForNmi

   ; The PPU control register is set with its horizontal value.
   ; This means the NMI is enabled now.
   ; From now on, NMI should never be switched off again.
   ; The various bits in the value can change,
   ; but NMI itself should always be on.
   LDA #PpuCtrlHorizontal
   STA PpuCtrl

   ; The general game logic.
@gameLogic:

   ; When the program is waiting for the NMI to hit
   ; after the game logic has run already,
   ; then the game logic is not allowed to run again and is skipped.
   ; Only when NMI is done is the game logic actually entered.
   LDA WaitForNmi
   BNE @gameLogicEnd

   ; Next, the buttons of the controller are read.

   ; Before we read the controller input, we save the status from the previous frame.
   LDA ControllerInput
   STA PreviousControllerInput

   ; Latch the buttons of the controller.
   LDA #1
   STA ControllerPort
   LDA #0
   STA ControllerPort

   ; X is the counter for each button.
   LDX #8

@readControllerLoop:

   PHA

   ; Each button is read.
   LDA ControllerPort

   ; The lowest two bytes are masked off.
   AND #%00000011

   ; Compare to 1 which sets the carry flag
   ; to check if either or both bits are set.
   CMP #1

   ; Rotate the carry flag into the top of A.
   PLA

   ; Shift all other buttons to the right.
   ROR A

   ; There are eight buttons to read for the controller.
   ; The counter is decremented until the loop is over.
   DEX
   BNE @readControllerLoop

   ; The controller input is saved.
   STA ControllerInput

   ; Controller reading has ended.

   ; The next frame is calculated based on the current frame and the controller input.
   ; Due to the parallax scrolling, we have three frame processing functions.
   ; This is the first one.
   ; It needs to be finished until the status bar has been drawn.
   JSR ProcessNextFrameTop

   ; If PpuMaskValue is 0, we skip the top scrolling.
   LDA PpuMaskValue
   BEQ @skipScrollingTop

   ; Wait for the first sprite overflow (more than eight sprites on one scanline).
   WAIT_FOR_PPU_STATUS %00100000

   ; The scrolling position for the middle screen is done.
   SET_SCROLLING_POSITION ScrollingPositionMiddle, NameTableMiddle

@skipScrollingTop:

   ; The second frame processing function.
   ; This is the one that has the most time.
   ; It needs to be finished until the point for parallax scrolling is reached.
   JSR ProcessNextFrameMiddle

   ; If PpuMaskValue is 0, we skip the middle scrolling.
   LDA PpuMaskValue
   BEQ @skipScrollingBottom

   ; Wait for sprite 0 hit.
   WAIT_FOR_PPU_STATUS %01000000

   ; The scrolling position for the lower screen is done.
   ; If midframe scrolling is disabled,
   ; this is the only scrolling that is done at all.
   SET_SCROLLING_POSITION ScrollingPositionBottom, NameTableBottom

@skipScrollingBottom:

   ; The third frame processing function.
   JSR ProcessNextFrameBottom

   ; The next thing that needs to be executed is the NMI.
   LDA #true
   STA WaitForNmi

@gameLogicEnd:

   ; The program goes into an infinite loop where it processes the game logic whenever it's allowed.
   JMP @gameLogic

_________________
Available now: My game "City Trouble".
Website: https://megacatstudios.com/products/city-trouble
Trailer: https://youtu.be/IYXpP59qSxA
Gameplay: https://youtu.be/Eee0yurkIW4
German Retro Gamer article: http://i67.tinypic.com/345o108.jpg


Last edited by DRW on Fri Mar 31, 2017 12:02 pm, edited 1 time in total.

Top
 Profile  
 
PostPosted: Fri Mar 31, 2017 12:00 pm 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 19114
Location: NE Indiana, USA (NTSC)
Dynamic random access memory (DRAM) decays if not refreshed. OAM in the PPU is DRAM, and rendering refreshes it. Not rendering for longer than about a couple dozen scanlines (the length of vblank) causes OAM to decay to unpredictable values, including the position and identity of sprite 0. So if you disable rendering for a while (to set up your credit screen) and then wait for sprite 0 hit, you need to keep uploading display lists to OAM using $4014.


Top
 Profile  
 
PostPosted: Fri Mar 31, 2017 12:18 pm 
Offline
User avatar

Joined: Sat Sep 07, 2013 2:59 pm
Posts: 1402
I indeed do a bunch of stuff before rendering is turned on again before a game section:

Code:
TempNameTableMiddle = NameTableMiddle =
TempScrollingPositionMiddle = ScrollingPositionMiddle =
TempNameTableBottom = NameTableBottom =
TempScrollingPositionBottom = ScrollingPositionBottom =
    0;

/* The system sprites for the scrolling split
   are set to their correct locations. */
UpdateSystemSprites();

ClearSprites(FirstNonSystemSpriteIndex);

ClearPpu();

if (GameSectionIsLevel())
    InitializeLevel();
else
    InitializeTextScreen();

GameSectionIsInitialized = true;

/* The PPU mask variable is set
   with its default value.
   This means rendering is enabled
   once the value is written
   to the actual PPU mask.
   Enabling and disabling the PPU mask
   should be done only inside NMI.
   Otherwise, rendering the current data
   might happen mid-frame. */
PpuMaskValue = Bin(0,0,0,1,1,1,1,0);


However, nothing sprite-related writes to the actual PPU in this case.
ClearSprites and UpdateSystemSprites do nothing but writing stuff into a plain and simple array:
Code:
byte Sprites[256];

which is located at $0200. (The location is forced by the NES.cfg file and putting the array into a specific segment.)

The actual sprites in the PPU are only ever accessed in the NMI, and only in this way:
Code:
; The low byte of the sprites address gets sent to the PPU port.
LDA #<Sprites
STA OamAddr

; The high byte gets sent and we specify that the transfer is done immediately.
LDA #>Sprites
STA OamDma


So, can this still be the issue?

What about too few wait for vblank? I once heard of someone who uses a third one to be sure. Can this be the problem?

_________________
Available now: My game "City Trouble".
Website: https://megacatstudios.com/products/city-trouble
Trailer: https://youtu.be/IYXpP59qSxA
Gameplay: https://youtu.be/Eee0yurkIW4
German Retro Gamer article: http://i67.tinypic.com/345o108.jpg


Top
 Profile  
 
PostPosted: Fri Mar 31, 2017 12:23 pm 
Offline
User avatar

Joined: Sat Sep 07, 2013 2:59 pm
Posts: 1402
Besides, naturally, I never wait for sprite 0 and sprite overflow when rendering is disabled:

Code:
   ; The next frame is calculated based on the current frame and the controller input.
   ; Due to the parallax scrolling, we have three frame processing functions.
   ; This is the first one.
   ; It needs to be finished until the status bar has been drawn.
   JSR ProcessNextFrameTop

   ; If PpuMaskValue is 0, we skip the top scrolling.
   LDA PpuMaskValue
   BEQ @skipScrollingTop

   ; Wait for the first sprite overflow (more than eight sprites on one scanline).
   WAIT_FOR_PPU_STATUS %00100000

   ; The scrolling position for the middle screen is done.
   SET_SCROLLING_POSITION ScrollingPositionMiddle, NameTableMiddle

@skipScrollingTop:

   ; The second frame processing function.
   ; This is the one that has the most time.
   ; It needs to be finished until the point for parallax scrolling is reached.
   JSR ProcessNextFrameMiddle

   ; If PpuMaskValue is 0, we skip the middle scrolling.
   LDA PpuMaskValue
   BEQ @skipScrollingBottom

   ; Wait for sprite 0 hit.
   WAIT_FOR_PPU_STATUS %01000000

   ; The scrolling position for the lower screen is done.
   ; If midframe scrolling is disabled,
   ; this is the only scrolling that is done at all.
   SET_SCROLLING_POSITION ScrollingPositionBottom, NameTableBottom

@skipScrollingBottom:

   ; The third frame processing function.
   JSR ProcessNextFrameBottom

   ; The next thing that needs to be executed is the NMI.
   LDA #true
   STA WaitForNmi

_________________
Available now: My game "City Trouble".
Website: https://megacatstudios.com/products/city-trouble
Trailer: https://youtu.be/IYXpP59qSxA
Gameplay: https://youtu.be/Eee0yurkIW4
German Retro Gamer article: http://i67.tinypic.com/345o108.jpg


Top
 Profile  
 
PostPosted: Fri Mar 31, 2017 12:56 pm 
Offline
User avatar

Joined: Sat Sep 07, 2013 2:59 pm
Posts: 1402
And in case it's necessary, here's my NMI function:

Code:
.segment "CODE"

Nmi:

   ; The registers are put to the stack.
   ; This is done because NMI can trigger any time,
   ; even if the program is right in the middle of the game logic.
   ; In this case, we have to be able
   ; to restore the current status when NMI is left again.
   PHA
   TXA
   PHA
   TYA
   PHA

   ; If it's not time for the NMI to trigger
   ; because the game logic has still not finished,
   ; NMI is left again.
   ; Otherwise we go on.
   LDA WaitForNmi
   BEQ @end

   ; After the NMI was processed, the game logic may start again
   ; and NMI is disallowed until the game logic is over.
   ; Setting this value right here at the start
   ; would also prevent a second NMI to do anything
   ; in case the first NMI ever takes too long.
   ; (Which shouldn't happen, but it's still safe this way.)
   LDA #false
   STA WaitForNmi

   ; The actual PPU mask is set with the value that was saved for it.
   LDA PpuMaskValue
   STA PpuMask

   ; If it is 0, NMI is left again.
   BEQ @end

   ; Rendering the sprites.
   ; The NMI gets the sprites per direct memory access (DMA).
   ; So, we tell it where in memory they can be found:

   ; The low byte of the sprites address gets sent to the PPU port.
   LDA #<Sprites
   STA OamAddr

   ; The high byte gets sent and we specify that the transfer is done immediately.
   LDA #>Sprites
   STA OamDma

   ; Drawing the background, the attributes and the palettes.
   ; There is not enough time to draw the whole background every frame.
   ; That's why the following code may only draw whatever has changed.
   ; Important: Palette updates should ALWAYS be in the NMI, never outside it.
   ; Otherwise they might change the screen's color when rendering is turned off.
   ; So, don't call UpdatePpu outside of NMI when it contains palettes data.
   ; Updating the background and the attributes outside of NMI is o.k. though.
   JSR UpdatePpu

   ; Setting the scrolling position.

   ; At first, the status bar is drawn.
   ; So, the horizontal and vertical scrolling position are set to 0.
   LDA #0
   STA PpuScroll
   STA PpuScroll

   ; The status bar appears in nametable 0, so we set the horizontal value.
   LDA #PpuCtrlHorizontal
   STA PpuCtrl

   ; The rest of the scrolling is done outside NMI
   ; when the corresponding places
   ; (status bar end, parallax scrolling line)
   ; are reached.

@end:

   ; The sound update is done always,
   ; so that the sound wouldn't lag
   ; even if the game logic does.
   JSR FamiToneUpdate

   ; The registers are pulled from the stack,
   ; so that they get the same values as when NMI hit.
   PLA
   TAY
   PLA
   TAX
   PLA

   ; The NMI has ended and the program returns
   ; to the code that was interrupted by the NMI.
   RTI

_________________
Available now: My game "City Trouble".
Website: https://megacatstudios.com/products/city-trouble
Trailer: https://youtu.be/IYXpP59qSxA
Gameplay: https://youtu.be/Eee0yurkIW4
German Retro Gamer article: http://i67.tinypic.com/345o108.jpg


Top
 Profile  
 
PostPosted: Sat Apr 01, 2017 9:51 am 
Offline

Joined: Tue May 28, 2013 5:49 am
Posts: 807
Location: Sweden
I see a classic mistake in the OAM DMA:
Code:
   ; The low byte of the sprites address gets sent to the PPU port.
   LDA #<Sprites
   STA OamAddr
 
   ; The high byte gets sent and we specify that the transfer is done immediately.
   LDA #>Sprites
   STA OamDma

This code probably works, but the label usage and comments hints that the assumptions how these registers works are incorrect.
OamAddr ($2003) isn't used for the low byte of the source address, it's the start address of the destination in the OAM (OAM is only 256 byte so only one byte is needed for the address). OamDma ($4014) takes the high byte of the starting source address which is normally the page number of a RAM page (only full RAM pages can be used for shadow OAM as I understand it), so there is no need to specify the low part of the source address.

Before starting an OAM DMA you always write 0 to OamAddr to make sure the OAM DMA writes from the beginning of OAM. Then you write the RAM page number you are using as shadow OAM to OamDma to invoke the DMA.
If "Sprite" is changed to something that doesn't have $00 in it's upper byte, the code won't work as expected.
So the code should probably look something like this:
Code:
   ; Ensure OAM address $00 is selected as destination start address.
   LDA #$00
   STA OamAddr
 
   ; Set the RAM page to be used as source address and invoke OAM-DMA
   LDA #>Sprites
   STA OamDma


Top
 Profile  
 
PostPosted: Sat Apr 01, 2017 10:05 am 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10066
Location: Rio de Janeiro - Brazil
This mistake comes straight out of Nerdy Nights.


Top
 Profile  
 
PostPosted: Sat Apr 01, 2017 11:23 am 
Offline
User avatar

Joined: Sat Sep 07, 2013 2:59 pm
Posts: 1402
Indeed, it is out of Nerdy Nights.

However, this cannot be the specific mistake in my case because I'm fully aware that my sprite array has to be aligned to $100. And I took means that this is always guaranteed:

NES.cfg (only the relevant stuff):
Code:
MEMORY
{
   ZP:      type = rw, start = $0001,          size = $00FF,         file = "";
   RAM:     type = rw, start = $0200,   size = $0400,   file = "";
}

SEGMENTS
{
   ZEROPAGE: load = ZP,      type = zp;
   SPRITES:  load = RAM,     type = bss, align = $0100;
   FAMITONE: load = RAM,     type = bss, align = $0100;
   BSS:      load = RAM,     type = bss;
   NO_RESET: load = RAM,     type = bss,                define = yes;
}


Declaration in the code:
Code:
.segment "SPRITES"

   Sprites: .res $100
   .export Sprites
   .export _Sprites = Sprites


So, thanks for the hint. I'll correct it in the future and write a better comment.
But binary-wise, it makes no difference because the low byte of the sprites array is always 0.
Otherwise, my sprite clearing function wouldn't work anyway since it goes through the array until the counter reaches 0 again.

So, unfortunately, this was not relevant to the specific issue I'm having.

_________________
Available now: My game "City Trouble".
Website: https://megacatstudios.com/products/city-trouble
Trailer: https://youtu.be/IYXpP59qSxA
Gameplay: https://youtu.be/Eee0yurkIW4
German Retro Gamer article: http://i67.tinypic.com/345o108.jpg


Top
 Profile  
 
PostPosted: Sat Apr 01, 2017 11:44 am 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10066
Location: Rio de Janeiro - Brazil
Yeah, this makes no difference for the final binary. I haven't looked at all the code posted, but the suspicion​ is that rendering is disabled long enough for the OAM data to decay and corrupt the sprite data, so when rendering is enabled again the sprites aren't properly arranged to trigger the sprite 0 hit and/or the sprite overflow. The quick fix in this case would be to do an OAM DMA before enabling rendering again, to make sure the sprite data is valid.

I don't think any emulators simulate OAM decay, so it actually makes a lot of sense that they wouldn't exhibit this problem. But like I said, I didn't read all of the code in this thread, so I'm sorry if this theory has already been debunked.


Top
 Profile  
 
PostPosted: Sat Apr 01, 2017 1:17 pm 
Offline
User avatar

Joined: Sat Sep 07, 2013 2:59 pm
Posts: 1402
Hmm. O.k., I'll try it. (Memblers is checking the ROMs on real hardware for me.)

However, I'm not sure whether this can actually be the issue.

Here, let me show you a shortened version of the code:

Code:
   JSR Initialize
   ; Sets PpuMaskValue to %00011110 in C.

   LDA #true
   STA WaitForNmi

   LDA #%10010000
   STA PpuCtrl

@gameLogic:

   LDA WaitForNmi
   BNE @gameLogicEnd

   ;;; ReadController ;;;

   JSR ProcessNextFrameTop

   LDA PpuMaskValue
   BEQ @skipScrollingTop

   WAIT_FOR_PPU_STATUS %00100000
   SET_SCROLLING_POSITION ScrollingPositionMiddle, NameTableMiddle

@skipScrollingTop:

   JSR ProcessNextFrameMiddle

   LDA PpuMaskValue
   BEQ @skipScrollingBottom

   WAIT_FOR_PPU_STATUS %01000000
   SET_SCROLLING_POSITION ScrollingPositionBottom, NameTableBottom

@skipScrollingBottom:

   JSR ProcessNextFrameBottom

   LDA #true
   STA WaitForNmi

@gameLogicEnd:

   JMP @gameLogic

At first, I set WaitForNmi to true, then I enable the rendering, then comes the game logic.
If WaitForNmi is still true, the game logic skips right to the end where it jumps back to the beginning.

Only if WaitForNmi is false is the game logic done and the sprite 0 split actually checked.

Therefore, no wait for sprite 0 or for sprite overflow can appear unless NMI has run. Also, it's only checked if the PpuMask is actually on.

Furthermore, this is a shortened version of Nmi:
Code:
Nmi:

   ;;;;; PHA TXA PHA TYA PHA ;;;;;

   LDA WaitForNmi
   BEQ @end

   LDA #false
   STA WaitForNmi

   LDA PpuMaskValue
   STA PpuMask
   BEQ @end

   LDA #0
   STA OamAddr
   LDA #>Sprites
   STA OamDma

   JSR UpdatePpu

   LDA #0
   STA PpuScroll
   STA PpuScroll

   LDA #%10010000
   STA PpuCtrl

@end:

   JSR FamiToneUpdate

   ;;;;; PLA TAY PLA TAX PLA ;;;;;

As you see, the NMI is the only place where the PPUMASK register is actually set.

And right after this, it's always a sprite update (except if PPUMASK was set to 0 of course, but in this case, the game logic will skip sprite 0 and sprite overflow check anyway).

So, after seeing this, can the decaying still be the issue?

_________________
Available now: My game "City Trouble".
Website: https://megacatstudios.com/products/city-trouble
Trailer: https://youtu.be/IYXpP59qSxA
Gameplay: https://youtu.be/Eee0yurkIW4
German Retro Gamer article: http://i67.tinypic.com/345o108.jpg


Top
 Profile  
 
PostPosted: Sat Apr 01, 2017 11:07 pm 
Offline
User avatar

Joined: Mon Apr 04, 2011 11:49 am
Posts: 1905
Location: WhereverIparkIt, USA
Have you tried simply waiting more vblanks before making any PPU writes?

_________________
If you're gonna play the Game Boy, you gotta learn to play it right. -Kenny Rogers


Top
 Profile  
 
PostPosted: Sun Apr 02, 2017 12:50 am 
Offline
User avatar

Joined: Sat Sep 07, 2013 2:59 pm
Posts: 1402
Yes, I compiled a version where I wait three instead of two and one where a loop lets the game wait 10 vblanks.

I sent all of those to Memblers, including the ones with the additional OAMDMA. It remains to be seen which version still has the error.

_________________
Available now: My game "City Trouble".
Website: https://megacatstudios.com/products/city-trouble
Trailer: https://youtu.be/IYXpP59qSxA
Gameplay: https://youtu.be/Eee0yurkIW4
German Retro Gamer article: http://i67.tinypic.com/345o108.jpg


Top
 Profile  
 
PostPosted: Mon Apr 03, 2017 10:45 am 
Offline
User avatar

Joined: Sat Sep 07, 2013 2:59 pm
Posts: 1402
O.k., the publisher tested my test ROMs. I tried all of the things you told me and some more, each thing in a separate ROM:

Setting the "system sprites" and manual OAMDMA at startup.
10 vblanks before doing anything else.
Checking PPUMASK directly instead of my temporary variable.
etc.

The only thing that helped was completely disabling the wait for the "nine sprites overflow" flag. When I did this, the game always worked.
Sprite 0 flag wait is fine. This one doesn't freeze anything.
But nine sprites overflow causes the freezing.

Now my question:
Is it possible that the overflow flag doesn't hit at the beginning, even though sprite 0 already does?
I mean, it's not that the game stops midway during playing. Also, it almost always works after a soft reset.
(So, a mundame error like "Did you actually position the sprites correctly" is also pretty much out of the question.)

But it looks like you cannot check for nine sprites at the beginning, even though you can already check for nine sprites overflow.

Alternately, can you please check whether my sprites overflow wait is even correct?

Code:
.macro WAIT_FOR_PPU_STATUS value
   .local @waitForNotPpuStatus, @waitForPpuStatus

@waitForNotPpuStatus:

   LDA PpuStatus
   AND #value
   BNE @waitForNotPpuStatus

@waitForPpuStatus:

   LDA PpuStatus
   AND #value
   BEQ @waitForPpuStatus

.endmacro

   ;;; Do some stuff ;;;

   WAIT_FOR_PPU_STATUS %00100000

   ;;; Do some stuff ;;;

   WAIT_FOR_PPU_STATUS %01000000

   ;;; Do some stuff ;;;

_________________
Available now: My game "City Trouble".
Website: https://megacatstudios.com/products/city-trouble
Trailer: https://youtu.be/IYXpP59qSxA
Gameplay: https://youtu.be/Eee0yurkIW4
German Retro Gamer article: http://i67.tinypic.com/345o108.jpg


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 15 posts ] 

All times are UTC - 7 hours


Who is online

Users browsing this forum: No registered users and 5 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group