I wanted to cover the top part of the screen to use as a status bar for a game. My idea was to use the Game Boy's window feature to do so. But I ran into a problem: The window's size is fixed to be the entire size of the screen, and while it can be lowered so that the window slides down towards the bottom of the screen revealing the background towards the top, the reverse can't be done.

So I thought "That's ok, I'll use a horizontal interrupt to move the window off screen at a specified scanline." I was thinking that if during vBlank the window Y coordinate was set to 0, it would only cover the screen up to the specified scanline, and then disappear for the rest of the screen draw, resulting in a status bar fixed at the top of the screen (think Zelda: Oracle of Seasons). But this isn't what happens at all! I'll show you what happens:

This is what happens if I use an LYC interrupt to set the WY register to scanline (decimal) 20. It works as intended. There is no reset of the WY register during vBlank in this screenshot.

pic 1

Now, this is what I get when I set WY to 20 at scanline 20 using an LYC interrupt, and then during vBlank set WY to zero. The window covers up the screen the entire time? That doesn't make sense...

pic 2

...unless the Game Boy has a hardware limitation that the window can only be moved once per frame. Is that the case?

(It could be a bug in my code so I'll post it below:

; This code is copied to &A000 and executed there.
; The LYC register contains 20.
    push af
    gb_in <ly
    gb_out <wy
    pop af


; This code is copied to &FF80 and executed there during vblank. 
; (The stack was moved to &BFFF early on in the program)
    push af
    ld a,>GBSpriteCache         ;high byte of gba sprite cache
    gb_out <dma                 ;start the transfer
    ld a,&28                    ;delay
DMACopyWait:                    ;wait for DMA to finish before returning program counter to ROM
    dec a
    jr nz,DMACopyWait

    xor a
    gb_out <wy      ;reset the window back to cover the screen
    pop af

EDIT: I figured out a way to do what I want but it makes no sense. I ported my code to a minimal example and used these interrupts:

VBlankProc:            ;&0040 jumps here
    push af
       xor a            ;ld a,0. Move window back to cover the whole screen
       ld (&ff4b),a     ;this is WX, yet it acts like WY!
    pop af

LYCProc:                ;&0048 jumps here, triggers at scanline #32
    push af
       ld a,&ff            ;move window off screen
       ld (&ff4b),a        ;this is WX, yet it acts like WY!
    pop af

And this is the result (screenshot is from BGB but VisualBoyAdvance does the same thing)


This is what I was trying to do earlier! So now I'm even more confused than before. Why does altering the window X register do what I thought Window Y would do?

  • Setting WX to a large coordinate is a good way to hide the window. Setting WX to 7 (do not set to values below 7) fully obscures the screen.
    – knol
    Nov 7, 2021 at 5:40
  • Did you try setting WY to 20 at line 19 (or earlier)? I'll wager any amount that the hardware does only an exact equality test, at a single point during the line. So it's possible that by waiting until you're in line 20 to set the register to 20, you've missed your chance.
    – Tommy
    Nov 7, 2021 at 13:05
  • @Tommy Hmm, I haven't thought of that. I read that the window has its own LY counter (which isn't accessible to the programmer) so I didn't think it would matter. Either way, with the method I used, I would have thought that the Window would be oriented differently (it's hard to put into words but imagine the last picture rotated 90 degrees counterclockwise, that's what I would have expected to see) Nov 8, 2021 at 11:16

1 Answer 1


The WX can be freely changed during a frame but the WY cannot. From pandocs:

writing to WX, WY etc. mid-frame shows a more articulated behavior.

For the window to be displayed on a scanline, the following conditions must be met:

WY condition was triggered: i.e. at some point in this frame the value of WY was equal to LY (checked at the start of Mode 2 only)

WX condition was triggered: i.e. the current X coordinate being rendered + 7 was equal to WX

Window enable bit in LCDC is set

To me that says the window is activated for the rest of the frame when LY is matched, so to hide the window mid-frame you'll need to manipulate WX (as you've found).

General advice:

Try running your rom through a different emulator.

Try running dmg-acid2 on the emulator you're currently using to see if it handles all the various combinations of mid-frame parameter changes.

The right side of the chin is drawn using the window. After the right eye was drawn, the window was hidden by setting WX to an off-screen value. For the right side of the chin, the WX value is restored to an on-screen value. After the chin has been drawn, the window is disabled using bit 5 of the LCDC register so the window does not cover the footer text.

For the right side of the chin, the window has been updated to use the tile map from the VRAM beginning at $9800. Because 16 rows of window have already been drawn for the eye, the right side of the chin is rendered starting from address $9840.

dmg-acid2 runs on real hardware and shows the correct graphic, and it seems to do a lot of window manipulation.

  • dmg-acid2 shows the face as it's supposed to, but my code doesn't work. It doesn't work in VisualBoyAdvance either. Nov 7, 2021 at 0:44
  • I originally thought you could manipulate WY within a frame but I found a reference that says that's not useful, edited :)
    – knol
    Nov 7, 2021 at 5:47
  • 1
    Good to know. I didn't see that in the Pandocs but I might not have looked hard enough. Funny how the Game Boy is like a NES in reverse in this regard - it's easy to display a status bar on the bottom of the Game Boy screen but the top is harder. (NES is the other way around) Nov 8, 2021 at 11:17
  • Yeah I think Tennis changes WX for its corner windows.
    – knol
    Nov 8, 2021 at 11:23

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .