Batman OAM corruption with rapid-fire?

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
Batman OAM corruption with rapid-fire?
by on (#85165)
Hello again, I just finished up adding auto-fire support to my emulator, and decided to test the effects of a super fast auto fire frequency on various games (for amusement and curiosity).

My auto-fire is currently toggling the button being pressed/released each time the joypad state is read from the software, as long as the corresponding keyboard key is being held down.

In most games (Mega Man series, Contra, etc), this causes no visible issues. But that story completely changes with Batman. If I dare touch an auto-fire mapped key, the OAM gets corrupted. If I hold it long enough, Batman crashes entirely. Even more strange is that only Batman's sprites are changed, all other sprites (enemies, items, etc) remain as they were before the corruption. As you can see here.

Can anyone confirm that this is a bug with the game, and not my emulator? I know Batman uses the DMC, could this be an unintended consequence of the way the game reads it's pad data to ensure DMC DMA doesn't interfere?
Re: Batman OAM corruption with rapid-fire?
by on (#85166)
beannaich wrote:
My auto-fire is currently toggling the button being pressed/released each time the joypad state is read from the software

[...]

I know Batman uses the DMC, could this be an unintended consequence of the way the game reads it's pad data to ensure DMC DMA doesn't interfere?

Yes. Actual turbo controllers use an oscillator to turn the button state on and off, which is tuned somewhere between 15 Hz and 30 Hz. If you toggle for each cycle of the strobe, the game will keep rereading, comparing, finding a change, and assuming it was a bit deletion due to DMA. What happens if you try your current turbo code with the following games?
  • Super Mario Bros. 3 (Nintendo)
  • Tetris (Nintendo)
  • LJ65 (Pin Eight)
Re: Batman OAM corruption with rapid-fire?
by on (#85168)
tepples wrote:
What happens if you try your current turbo code with the following games?
  • Super Mario Bros. 3 (Nintendo)
  • Tetris (Nintendo)
  • LJ65 (Pin Eight)

  • Super Mario Bros. 3 (Crashes after an awesome looking glitched scrolling effect and game slowing down to ~30 fps)
  • Tetris (Nothing happens, the pieces won't even rotate)
  • LJ65 (Is this an unlicensed game? I don't have it in my library)
Re: Batman OAM corruption with rapid-fire?
by on (#85169)
beannaich wrote:
LJ65 (Is this an unlicensed game? I don't have it in my library)

LJ65 is a homebrew tetromino game, sort of like Tetris but with faster control feel.
Try changing your turbo controller to toggle the button states after every vblank and then re-run your tests.

by on (#85170)
If I set the frequency to 30 hz, then the above glitches don't happen. I mainly posted this to confirm it wasn't my emulator breaking, and also as a "wtf?". Batman (And only Batman's sprites) glitch when you toggle $4016 too fast, seems weird, but it probably makes sense if you look at the assembler code.

Also, could we get the controller page on the wiki updated with the auto-fire information (The part about the oscillators working at 15-30hz)?

by on (#85171)
Done. Thank you for the suggestion.

As for how Batman screws up: I haven't traced it, but I imagine that rapidly toggling the button will cause certain parts of processing to overflow available frame time, and an incomplete display list gets copied to OAM during the next NMI handler.

by on (#85172)
tepples wrote:
As for how Batman screws up: I haven't traced it, but I imagine that rapidly toggling the button will cause certain parts of processing to overflow available frame time, and an incomplete display list gets copied to OAM during the next NMI handler.


It would make sense that the actual sprites for batman aren't determined until the joypad state is known. in-case of jumping, attacking, etc. wouldn't want to do more than one DMA in-case your prediction is wrong ;)

by on (#85250)
Batman uses an MMC3 IRQ to swap out one of the sprite CHR banks just below the health bar (this is why Batman is never allowed to go that high on the screen). When the game crashes, the swap never occurs, and Batman is rendered using the health bar graphics. The same effect can be observed on a top-loading NES or Famicom by holding the Reset button.

by on (#85253)
But it was your emulator breaking. The proper way to do rapid fire you've found is to have the switch closed one frame, then open the next frame, and so on. You assumed that games would read joypad data once per frame which not all do. While you might get that behavior if you specifically programmed a device and plugged it into the controller port, it probably is otherwise never going to occur and it isn't Batman's programming at fault.

Still it is an interesting thing to see. Lots of interesting behaviors can happen with various emulation errors.

by on (#85254)
Various photos of Batman in prototype form in old magazines also showed screens with a glitched-up status bar. IIRC, it also happened when I ran FC batman on an old Famiclone, so this glitch can be triggered in a few different ways.

Not helpful, but interesting perhaps.

by on (#85325)
MottZilla wrote:
But it was your emulator breaking. The proper way to do rapid fire you've found is to have the switch closed one frame, then open the next frame, and so on. You assumed that games would read joypad data once per frame which not all do. While you might get that behavior if you specifically programmed a device and plugged it into the controller port, it probably is otherwise never going to occur and it isn't Batman's programming at fault.


If I emulate in a fashion where the state of a button is toggled on a certain number of concurrent reads, you get a *MUCH* faster auto-fire. The magic number for this seems to be 3 reads. 2 reads works for some games, but not for games like Contra, where as every read breaks Batman and SMB 3, but doesn't break games like Mega Man 1-6. 3 reads seems to be the number that works for all games, while still maintaining the quickest possible toggle.

Sure, I could emulate this the way it's actually done, and it would work reasonably well, but the way I'm doing it now is much more efficient at it's job. Sometimes accuracy takes a back seat to function, though I may end up adding an option to switch between the two rapid fire behaviors, because I know some people want the most accurate operation.

MottZilla wrote:
Still it is an interesting thing to see. Lots of interesting behaviors can happen with various emulation errors.

I fully understand (I think) why this bug occurs, if you had a controller toggling the state as fast as I was on a real NES, I am almost positive the same thing would occur. But it is indeed interesting what happens, as stated earlier if you do this with SMB 3, it slows down to around half it's normal speed (The happy calypso music sounds eerie as hell at half speed, believe me!). Then when you let go of the rapid fire, the game just crashes entirely, and the screen looks weird, almost as if the scroll register is being written to mid-scanline each scanline.

by on (#85332)
I don't understand what you mean by it's so fast. The fastest you can push a button is 30 times per second, every other frame the button is pushed, and on the ones between it is not pushed. This is as easy as having a frame flip-flop variable.

I understand why you are doing it this odd way since it is I suppose quite easy to implement but given that it will never be perfect or work right for all games, implementing it the right way (30 or 15 presses per second) is a better idea.

The reason Batman and SMB3 have odd behavior and crash is most likely because normally after a couple attempts to read consistent controller data it will succeed and continue on. But with your impossible situation (no rapid fire controller would toggle faster than 30 presses per second) it keeps trying to get the valid controller data, well past the time allotted for it. It may crash because whenever you release the button and allow it to get valid data the current scanline it is on may be vastly different than what it should be. Or the huge delay may have caused somehow program banking register or other program flow errors so it crashes.

Believe me, this wouldn't happen under any normal circumstances on real hardware. To get this to happen you would need faulty controllers/controller ports, or a specially programmed device to feed such bad data into the ports.

by on (#85334)
Such a "specially programmed device to feed bad data" might be as simple as gating A and B button signals with a flip-flop set to toggle on strobe edges. Just ask qbradq ;-)

by on (#85340)
@MottZilla, I think you're misunderstanding me. What I stated is, if you had a controller that does what my current controller does, only in reality, and plugged it into a real NES, that it would behave the same way when the auto-fire button is pressed. Also, both implementations are easy, so it's not a question of the path of least resistance for me. In my emulator, I can hook all sorts of events, one of them being a "FrameRendered" event. If I wanted to, I could easily just hook my auto-fire state switching method to that event, and voila.

@tepples, I'd love to see this actually done, but it seems like too much effort just to prove a point. ;)

by on (#85362)
Yes I suppose someone might construct a turbo function in an actual controller this way. Seems foolish given what is known about consecutive reads. Still I don't blame Batman's programming too much, though I suppose they could have had a maximum read-retry variable to fail and assume no input after so many attempts at getting valid data.

by on (#85374)
MottZilla wrote:
I suppose they could have had a maximum read-retry variable to fail and assume no input after so many attempts at getting valid data.

That's probably why LJ65 uses only two reads and falls back to the previous frame's input on failure.

by on (#85393)
tepples wrote:
That's probably why LJ65 uses only two reads and falls back to the previous frame's input on failure.

I assume the behavior for that is similar to:

Code:
    PHA ; preserve a
    TXA
    PHA ; preserve x
    LDX #$00
    LDA $4016 ; get current joypad state
    STA $11 ; set pad read buffer
read:
    LDA $4016 ; get current joypad state
    INX
    CPX #$05 ; don't waste too much time attempting
    BEQ retn
    CMP $11 ; pad read buffer
    BNE read ; keep trying if the two reads aren't the same
    STA $10 ; set pad data to current valid data
retn:
    PLA
    TAX
    PLA
    RTS

by on (#85399)
Similar, but I see a couple problems in code fragment:
  1. LDA $4016 gives you one bit, not the whole state. To fix this, replace the LDA $4016 instructions with calls to a subroutine that reads the gamepad once, which in fact is possible without modifying X or Y by using ROL as a loop counter.
  2. If the first read is the corrupt one and all five reads after that are not corrupt, the state won't be updated. To fix this, move the STA below read, which will wait until two consecutive reads of the same state.

Possibly fixed (but still untested) code:
Code:
    PHA ; preserve a
    TXA
    PHA ; preserve x
    LDX #$00
    JSR readOnce ; get current joypad state
read:
    STA $11 ; set pad read buffer
    JSR readOnce ; get current joypad state
    INX
    CPX #$05 ; don't waste too much time attempting
    BEQ retn
    CMP $11 ; pad read buffer
    BNE read ; keep trying if the two reads aren't the same
    STA $10 ; set pad data to current valid data
retn:
    PLA
    TAX
    PLA
    RTS

readOnce:
    LDA #$01
    STA $12  ; initialize the output register/loop counter
    STA $4016  ; send the strobe pulse
    LSR A  ; load a zero
    STA $4016  ; finish sending the strobe pulse
readBit:
    LDA $4016,Y  ; read one player's gamepad
    AND #$03  ; mask off non-button bits
    CMP #1  ; C <- either D0 or D1 is set
    ROL $12  ; add a bit to the output register
    BCC readBit ; keep going while initial 1 hasn't been shifted out

    LDA $12
    RTS

by on (#85416)
The LDA $4016 was just short-hand, but I'll admit I forgot about the 1-bit at a time thing when I wrote that. Consider it 6502 pseudo-code at best, as I've never actually programmed anything in 6502 assembler.

I still find it amazing, that a seemingly small error can have such interesting implications in emulation. It's also always fun to try and figure out why two seemingly unconnected things are logically connected. At first, I had absolutely no clue why spamming a button would cause Batman to freak out, but now it would be weird to think the game wouldn't do that!