GoldenAxe missing magic blue bottle and palette corruption

Ask anything your want about Megadrive/Genesis programming.

Moderator: BigEvilCorporation

Post Reply
sbroumley
Interested
Posts: 29
Joined: Wed Feb 11, 2009 11:23 pm

GoldenAxe missing magic blue bottle and palette corruption

Post by sbroumley » Mon Mar 30, 2009 6:35 pm

For the most part I have GoldenAxe running well in my emulator, but there are a couple of really weird bugs I'm having trouble tracking down:

1). The magic blue bottles that you get from attacking the elf guys or activating your magic spell either appear or don't appear for each level. Vram address 0xaa80 is where the bottle graphic should be, but when they don't appear this area of vram is left blank. When they appear this area of vram is setup correctly from 68k level setup code that uses vdp vram writes to fill the area (just before the level fades up).

2) The other problem is random palette corruption at stages where the next part of the level is reached (when the "go" arrow appears). This one is just a bad palette copied via 68k vdp cram writes.

Has anyone seen these issues before or have any tips on how to solve them?

cheers
Steve

Eke
Very interested
Posts: 884
Joined: Wed Feb 28, 2007 2:57 pm
Contact:

Post by Eke » Tue Mar 31, 2009 5:00 am

do you check that games does not perform a data write command after setting a read command ?

I know some are doing this and writes should be ignored, don't know anything about golden axe in particular though...

also check your DMA operations (VRAM fill, copy,..), auto-increment address and address limits/masking

sbroumley
Interested
Posts: 29
Joined: Wed Feb 11, 2009 11:23 pm

Post by sbroumley » Tue Mar 31, 2009 9:48 am

I think I've got those cases covered.

When writing, I'm checking the VDP (code & 0xf) register for vram write, cram write, and vsram-write only. Other case are ignored and the address register does not get incremented. Similarly for the read.

One thing I haven't implemented is freezing the 68k during 68->vram dma or emulating returing that dma is busy for dma fills/vram-copies.

Another thing I've found out is that the blue pot graphic is copied into vram via VDP writes, and not VDP dma. Same with the palette (write to CRAM).

sbroumley
Interested
Posts: 29
Joined: Wed Feb 11, 2009 11:23 pm

Post by sbroumley » Tue Mar 31, 2009 10:12 am

A bit more info:

From debugging I can see the bottle graphics being downloaded into cram instead of vram. (this would explain the missing bottle, and the corrupt palette).

Can you spot anything wrong with my code register update? This is the only function where I update it:

cheers,
Steve

Code: Select all

void VDP::WriteControlPort( const uint32 val )
{
    // Writing 2nd part of control data?
    if( writePending )
    {
        writePending    = FALSE;
        codeRegister    = ( ( codeRegister & 0x3 ) | ( ( val >> 2 ) & 0x3c ) ) & 0x3f;
        addressRegister = ( ( addressRegister & 0x3fff ) | ( val << 14 ) & 0xc000 ) & 0xffff;
        CheckDMA();
        return;
    }

    // Write to VDP register?
    if( ( val & 0xc000 ) == 0x8000 )
    {
        registers[ ( val >> 8 ) & 0x1f ] = val & 0xff;
        codeRegister = 0;   // Reset code register (address register is left as is)
        return;
    }

    // Flag for next write
    writePending    = TRUE;
    codeRegister    = ( codeRegister & 0x3c ) | ( ( val >> 14 ) & 0x3 );
    addressRegister = ( addressRegister & 0xc000 ) | ( val & 0x3fff ) & 0xffff;
    return;
}

Gerrie
Interested
Posts: 16
Joined: Tue Dec 12, 2006 9:33 pm
Contact:

Post by Gerrie » Tue Mar 31, 2009 10:55 am

sbroumley wrote:A bit more info:

From debugging I can see the bottle graphics being downloaded into cram instead of vram. (this would explain the missing bottle, and the corrupt palette).

Can you spot anything wrong with my code register update? This is the only function where I update it:
I don't have any source at hand, but you're missing an ELSE statement, because you're incorrectly updating the control data:

Code: Select all

void VDP::WriteControlPort( const uint32 val ) 
{ 
    // Writing 2nd part of control data? 
    if( writePending ) 
    { 
        writePending    = FALSE; 
        codeRegister    = ( ( codeRegister & 0x3 ) | ( ( val >> 2 ) & 0x3c ) ) & 0x3f; 
        addressRegister = ( ( addressRegister & 0x3fff ) | ( val << 14 ) & 0xc000 ) & 0xffff; 
        CheckDMA(); 
        return; 
    } 
   else /* update first part of control data */
   {

    // Write to VDP register? 
    if( ( val & 0xc000 ) == 0x8000 ) 
    { 
        registers[ ( val >> 8 ) & 0x1f ] = val & 0xff; 
        // Dont reset the code register      codeRegister = 0;   // Reset code register (address register is left as is) 
        // don't return, addr and code needs to be updated    return; 
    }
    else
    {
        // Flag for next write 
        writePending    = TRUE; 
    }
    codeRegister    = ( codeRegister & 0x3c ) | ( ( val >> 14 ) & 0x3 ); 
    addressRegister = ( addressRegister & 0xc000 ) | ( val & 0x3fff ) & 0xffff; 

   }
    return; 
} 

Eke
Very interested
Posts: 884
Joined: Wed Feb 28, 2007 2:57 pm
Contact:

Post by Eke » Tue Mar 31, 2009 1:35 pm

"else" is unnecessary since a "return" is performed in the "if" case :wink:

The error is probably here:

Code: Select all

addressRegister = ( ( addressRegister & 0x3fff ) | ( val << 14 ) & 0xc000 ) & 0xffff;


should be

Code: Select all

addressRegister = ( ( addressRegister & 0x3fff ) | ( ( val << 14 ) & 0xc000 ) ) & 0xffff;

compiler usually apply logical operation from left to right
I bet you were applying 0xc000 mask on the whole address register
also note the 0xffff mask is redundant in this case

Now, two things about your implementation, compared to the one in genesis plus (which is told to be correct according to its author):

1/ code should not be reseted on register write (code and address registers are updated as usual). I think the golden axe 2 sega logo relies on that one.

2/ address register is latched when the second CTRL word is wrote and the first CTRL word access use this latched value to compute the new address, NOT the current address value.

This means that the following sequence could occurs:

(1) write first CTRL word
(2) write 2nd CTRL word: ADDR value is latched
(3) write DATA word: ADDR is incremented
...
(4) write first CTRL word
(5) DATA write

In that case, only the first half of ADDR is updated when (4) occurs, but remaining bits use the LATCHED value, not the current ADDR value !

I don't know if you are directly incrementing addressRegister when DATA writes occur but in that case, you have to keep the latched value in a separate variable.

sbroumley
Interested
Posts: 29
Joined: Wed Feb 11, 2009 11:23 pm

Post by sbroumley » Tue Mar 31, 2009 8:14 pm

thanks for pointing out the small errors. they didn't actually solve the problem but that's because that was not the cause.

I tracked down the issue to the main loop performing vram writes (for the bottle graphic), but it gets interrupted by a 68k vbl which performs a mem->cram dma. this trashes the vdp reigsters and so when the main loop continues, it is now writing to the bottle graphic to cram - hence the missing bottle and the corrupt palette.

So it looks like my problem is elsewhere. I'm not putting the 68k to sleep during rom/ram dmas so that might be the issue, or I have a bug with the vbl not being masked when it should.

For the vdp to fire off a vbl int on the 68k, I'm just testing bit 5 of vdp register 1 at start of scan line 225. Is there anything else I should be testing for? Also if the 68k is asleep during a vbl trigger, does it get ignored or does it happen as soon as the 68k unfreezes (end of dma)?

cheers

sbroumley
Interested
Posts: 29
Joined: Wed Feb 11, 2009 11:23 pm

Post by sbroumley » Tue Mar 31, 2009 10:11 pm

Okay so I've found a solution and was wondering if anyone else is doing the same:

Basically golden axe turns off the display during level initialization (when the bottle graphic is written to vram). Disabling vbl triggering when the display is turned off fixes the problem.

I also implemented (what I think) is correct vdp vbl pending logic. From what I understand, vbl pending is set on line 225, and cleared after line 262 or before if a vbl int is acknowledged and taken by the 68k. In the 68k update function, if a vdp vbl is pending, the 68k takes and acknowledges it if it's interrupt masks are setup to accept it.

I'm testing more games with these changes right now. Do these ring a bell with anyone?

cheers
Steve

TascoDLX
Very interested
Posts: 262
Joined: Tue Feb 06, 2007 8:18 pm

Post by TascoDLX » Tue Mar 31, 2009 11:22 pm

Eke wrote:The error is probably here:

Code: Select all

addressRegister = ( ( addressRegister & 0x3fff ) | ( val << 14 ) & 0xc000 ) & 0xffff;
should be

Code: Select all

addressRegister = ( ( addressRegister & 0x3fff ) | ( ( val << 14 ) & 0xc000 ) ) & 0xffff;

compiler usually apply logical operation from left to right
That's a bitwise operation not a logical operation, but in either case, AND is evaluated before OR. So, it will execute correctly. However, I agree with the addition of parentheses as it makes the statement less ambiguous.

sbroumley
Interested
Posts: 29
Joined: Wed Feb 11, 2009 11:23 pm

Post by sbroumley » Wed Apr 01, 2009 3:35 am

thanks guys - yes that was a typo, but (luckily) it didn't caused any problems.

anyway, now that I've fixed that, I think I've cracked the golden axe problems:

1) I do not trigger 68k vbl interrupts when the vdp display is disabled (happens when the screen is black just before a level starts in golden axe - this is during level initialization)

2) I do not trigger or request 68k vbl if the vbl is disabled via the 68k SR register (ie when the int pri is above 6). This fixes the mid level palette corruption where golden axe disables 68k ints, decompresses data, then re-anbles ints. this decompression takes several frames.

Are other people performing the same checks before issuing vbl ints from the vdp?

cheers
Steve

Eke
Very interested
Posts: 884
Joined: Wed Feb 28, 2007 2:57 pm
Contact:

Post by Eke » Wed Apr 01, 2009 5:12 am

1) I do not trigger 68k vbl interrupts when the vdp display is disabled (happens when the screen is black just before a level starts in golden axe - this is during level initialization)
I don't think it's correct, interrupts still occur when display is OFF
2) I do not trigger or request 68k vbl if the vbl is disabled via the 68k SR register (ie when the int pri is above 6). This fixes the mid level palette corruption where golden axe disables 68k ints, decompresses data, then re-anbles ints. this decompression takes several frames.
more generally, about interrupts handling:

(1) disabling VINT trough VDP register clear the interrupt line, but not the interrupt pending flag
(2) enabling VINT trough VDP register assert the interrupt line if there is a pending interrupt
(3) pending flag is set on line 224 (counting from line 0) and remains set until interrupt is acknowledged by 68k (it is not cleared at the end of the frame)
(4) asserting an interrupt should be followed by interrupt processing if interrupt is not masked
(5) unmasking an interrupt on 68k side should be followed by interrupt processing if interrupt line is asserted
(6) processing of the interrupt clears the interrupt pending flag on VDP side and clears the interrupt line (INTACK)

So basically, you have to handle 3 flags for an interrupt:
interrupt.asserted
interrupt.pending
interrupt.masked

This is all I can think about, there are some other quirks with VDP latency or Hint/Vint being triggered on the same line but that should work with most games

(1) and (2) are important for many games, make sure you handle pending interrupts correctly

sbroumley
Interested
Posts: 29
Joined: Wed Feb 11, 2009 11:23 pm

Post by sbroumley » Wed Apr 01, 2009 5:36 am

Okay - so after looking over my int code very closely I found the real problem (ignore my last posts) and your info confirms this Eke.

My previous 68k frame execute loop only checked for handling ints at the beginning of every scan line. I updated the code so that a check for ints is handled immediately after an SR update instruction happens. This makes sure an interrupt fires off immediately on the next instruction as opposed to several instructions later when the next scan line begins.

I then removed all the vdp int hacks, and golden axe works great

thanks so much for your help Eke!

Post Reply