Zelda 1 Vertical Mirroring Bug

Discuss emulation of the Nintendo Entertainment System and Famicom.

Moderator: Moderators

Post Reply
siliconandsolder
Posts: 9
Joined: Tue Apr 20, 2021 1:25 pm

Zelda 1 Vertical Mirroring Bug

Post by siliconandsolder »

When playing Zelda 1, my emulator is able to do horizontal mirroring without issue. However, when trying to move to east/west the screen transition is full of zeros: https://streamable.com/njl14z

At first I thought this might be an MMC1 dynamic mirroring bug, but after debugging my emulator is switching to vertical mirroring mode when moving east or west. This might be a long shot, but I'm wondering if anyone here has encountered a similar issue. The thread viewtopic.php?f=3&t=23260 discusses a similar issue, but no resolution was ever posted. Any suggestions?
User avatar
Quietust
Posts: 1920
Joined: Sun Sep 19, 2004 10:59 pm
Contact:

Re: Zelda 1 Vertical Mirroring Bug

Post by Quietust »

siliconandsolder wrote: Mon Jul 05, 2021 7:41 pm When playing Zelda 1, my emulator is able to do horizontal mirroring without issue. However, when trying to move to east/west the screen transition is full of zeros
When I was trying to diagnose the problem in that other thread, I remarked that emulating 4-screen VRAM (i.e. four distinct nametables) produced the exact same effect you are observing - are you absolutely certain that your PPU code is actually obeying your mirroring settings?
Quietust, QMT Productions
P.S. If you don't get this note, let me know and I'll write you another.
siliconandsolder
Posts: 9
Joined: Tue Apr 20, 2021 1:25 pm

Re: Zelda 1 Vertical Mirroring Bug

Post by siliconandsolder »

Quietust wrote: Mon Jul 05, 2021 8:08 pm
siliconandsolder wrote: Mon Jul 05, 2021 7:41 pm When playing Zelda 1, my emulator is able to do horizontal mirroring without issue. However, when trying to move to east/west the screen transition is full of zeros
When I was trying to diagnose the problem in that other thread, I remarked that emulating 4-screen VRAM (i.e. four distinct nametables) produced the exact same effect you are observing - are you absolutely certain that your PPU code is actually obeying your mirroring settings?
As far as I can tell, yes. I stepped through the emulator from MMC1 selecting the mirroring mode

Code: Select all

match regVal & 3 {
    0 => {
        self.mirrorMode = MIRROR::ONESCREEN_LO;
    }
    1 => {
        self.mirrorMode = MIRROR::ONESCREEN_HI;
    }
    2 => {
        self.mirrorMode = MIRROR::VERTICAL;
    }
    3 => {
        self.mirrorMode = MIRROR::HORIZONTAL;
    }
    _ => { panic!("Should never reach this."); }
}
to the PPU reading the next tile address

Code: Select all

let realAddr = addr & 0x0FFF;

match self.cart.borrow().getMirrorType() {
    MIRROR::HORIZONTAL => {
        return match realAddr {
            a if a < 0x0800 => { self.tblName[(a & 0x03FF) as usize].clone() }
            _ => { self.tblName[(realAddr & 0x0BFF) as usize].clone() }
        };
    }
    MIRROR::VERTICAL => {
        return self.tblName[(realAddr & 0x0FFF) as usize].clone();
    }
    _ => { panic!("Unrecognized Mirror Type: {:?}", self.cart.borrow().getMirrorType()); }
}
I haven't even implemented 4-screen mirroring yet; the emulator would panic if MMC1 selected 4-screen mirroring.
User avatar
Quietust
Posts: 1920
Joined: Sun Sep 19, 2004 10:59 pm
Contact:

Re: Zelda 1 Vertical Mirroring Bug

Post by Quietust »

siliconandsolder wrote: Mon Jul 05, 2021 8:28 pm to the PPU reading the next tile address

Code: Select all

let realAddr = addr & 0x0FFF;

match self.cart.borrow().getMirrorType() {
    MIRROR::HORIZONTAL => {
        return match realAddr {
            a if a < 0x0800 => { self.tblName[(a & 0x03FF) as usize].clone() }
            _ => { self.tblName[(realAddr & 0x0BFF) as usize].clone() }
        };
    }
    MIRROR::VERTICAL => {
        return self.tblName[(realAddr & 0x0FFF) as usize].clone();
    }
    _ => { panic!("Unrecognized Mirror Type: {:?}", self.cart.borrow().getMirrorType()); }
}
I haven't even implemented 4-screen mirroring yet; the emulator would panic if MMC1 selected 4-screen mirroring.
Your logic is wrong for both mirroring types - horizontal is effectively using nametables 0 and 2 (rather than 0 and 1, which is very important when mirroring can be dynamically switched), and vertical is allowing access to all 4 nametables at once (i.e. it's doing 4-screen VRAM, exactly as I mentioned above).

Horizontal mirroring should look like this:

Code: Select all

            a if a < 0x0800 => { self.tblName[(a & 0x03FF) as usize].clone() }
            _ => { self.tblName[((realAddr & 0x03FF) | 0x0400) as usize].clone() }
And vertical mirroring should look like this:

Code: Select all

        return self.tblName[(realAddr & 0x07FF) as usize].clone();
For what it's worth, the other modes should look like this:

Code: Select all

    MIRROR::ONESCREEN_LO => {
        return self.tblName[(realAddr & 0x03FF) as usize].clone();
    }
    MIRROR::ONESCREEN_HI => {
        return self.tblName[((realAddr & 0x03FF) | 0x400) as usize].clone();
    }

Also, while this setup will work for most games, I should warn you that it will not be sufficient for emulating some of the more advanced mappers where each nametable slot can be individually configured by the mapper, so at some point you're going to have to change it to accept raw bank numbers for each nametable (and potentially even custom data such as CHR ROM or the MMC5's internal EXRAM).
Quietust, QMT Productions
P.S. If you don't get this note, let me know and I'll write you another.
Post Reply